0ce56438
scenario: guard scale change + tighten captureCb errdefer
a73x 2026-04-19 13:39
Fixes two pre-fixture bugs:
- Runtime scale change in scenario mode would leave rc.cell_w
etc. stale → silent golden drift. Now fails fast with a
clear error and exit code 6.
- captureCb's readback allocation only had a narrow errdefer;
any error after golden diff would leak img.pixels. Now the
errdefer spans the whole function.
Plus a tidy: `try` in place of `catch |err| return err`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
diff --git a/src/main.zig b/src/main.zig index 22028e4..cc6cdfc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -237,9 +237,15 @@ pub fn runScenarios(alloc: std.mem.Allocator, argv: []const [:0]const u8) !u8 { }; defer rc.deinit(); runTerminal(alloc, &rc) catch |err| { std.debug.print("scenario {s}: runtime error: {s}\n", .{ scenario_name, @errorName(err) }); return @intFromEnum(scenario_runtime.ExitCode.other_error); runTerminal(alloc, &rc) catch |err| switch (err) { error.ScenarioScaleChangeNotSupported => { // already printed; return exit 6 (other render error) return @intFromEnum(scenario_runtime.ExitCode.other_error); }, else => { std.debug.print("scenario {s}: runtime error: {s}\n", .{ scenario_name, @errorName(err) }); return @intFromEnum(scenario_runtime.ExitCode.other_error); }, }; if (rc.tick_fatal) |err| { @@ -608,6 +614,10 @@ fn runTerminal(alloc: std.mem.Allocator, tick_ctx: ?*scenario_runtime.RunContext if (!render_pending) continue; if (scale_pending) { if (tick_ctx) |_| { std.debug.print("scenario: runtime scale change not supported; please pin the compositor output to scale 1 (or re-run on a scale-1 monitor).\n", .{}); return error.ScenarioScaleChangeNotSupported; } vk_sync.waitIdleForShutdown(ctx.vkd, ctx.device); geom = try rebuildFaceForScale( diff --git a/src/scenario_runtime.zig b/src/scenario_runtime.zig index 4b3152f..a66c361 100644 --- a/src/scenario_runtime.zig +++ b/src/scenario_runtime.zig @@ -155,10 +155,10 @@ pub fn captureCb(ctx: *anyopaque, label: []const u8) anyerror!png.Image { // --- readback --- const pixels = try rc.alloc.alloc(u8, @as(usize, rc.px_w) * rc.px_h * 4); errdefer rc.alloc.free(pixels); try rctx.readbackOffscreen(offscreen, pixels); const img: png.Image = .{ .width = rc.px_w, .height = rc.px_h, .pixels = pixels }; errdefer rc.alloc.free(img.pixels); // covers all subsequent error paths // --- write out/<scenario>/<label>.png --- const out_dir = try std.fmt.allocPrint( @@ -230,7 +230,7 @@ pub fn captureCb(ctx: *anyopaque, label: []const u8) anyerror!png.Image { return img; } const diff = imgdiff.compare(img, golden) catch |err| return err; const diff = try imgdiff.compare(img, golden); if (diff.rmse > imgdiff.RMSE_DEFAULT or diff.max_pixel > imgdiff.PIXEL_MAX_DEFAULT) { const lbl = try rc.alloc.dupe(u8, label); errdefer rc.alloc.free(lbl);