From 1e376c244b5254c63f94b0b28063e92babb17abb Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 5 Sep 2026 13:26:06 +0200 Subject: [PATCH] fix(overhaul): integrate reviewed resize capture sequencing --- ...-09-01-campaign-overhaul-world-solidity.md | 5 + .../476-lead-verification.md | 37 +++++ .../s5-consumers-material-closeout-packet.md | 9 ++ .../Testing/RetailUiAutomationScriptRunner.cs | 11 ++ .../UI/RetailUiAutomationProbeTests.cs | 143 ++++++++++++++++++ 5 files changed, 205 insertions(+) diff --git a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md index fcc63f3a5..65baf8a9f 100644 --- a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md +++ b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md @@ -773,6 +773,11 @@ images), narrow scheduling diagnosis active. Settled hands rest/combat/rest provisional PASS; no owner acceptance. All eight PNG paths/hashes in 476-480-graphical-r1.md. No FPS work, no main merge; G4 remains FAIL. +F2 diagnostic-only resize yield now integrates: old-extent capture was valid, +automation queued it before next-frame recreation. Two files, root mutation +and restored68PASS, narrow reviewPASS; no GPU/retail change or new deviation. +Exact integrated graphical replay still owed; see476-lead-verification.md. + ## 14. Risk register (condensed) - **R1 second scene graph.** Graph records own only topology/membership facts; diff --git a/docs/research/2026-09-01-overhaul/476-lead-verification.md b/docs/research/2026-09-01-overhaul/476-lead-verification.md index a924177ef..cdc775032 100644 --- a/docs/research/2026-09-01-overhaul/476-lead-verification.md +++ b/docs/research/2026-09-01-overhaul/476-lead-verification.md @@ -133,3 +133,40 @@ alongside preserved AD-118 retirement, counted105total/13retired/92active. Fresh integrated Release/tests and current-frame graphical PASS are still owed. Owner12:53 FPS deferral remains binding; no profiling/optimization is part of this capture repair or its graphical correctness run. + +## F2 — synchronous automation resize, not stale GPU bytes + +The exact integrated0ca10cf24 graphical run is recorded in +476-480-graphical-r1.md: startup and settled capture coherent, but both +first-resize requests captured the old extent. Lead and James independently +traced synchronous window resize inside the already-open render; the test +driver queued its screenshot before next-boundary swapchain recreation. +476-fix-round2-resize-driver.md at58185285d bounds the diagnostic-only fix. + +Frozen new scratch s5-476-resize-driver: two files only, runner+11 and +probe tests+143. Successful resize advances once then yields its command +loop; explicit following framebuffer wait remains. No GPU, capture policy, +route, retail algorithm, frame timing policy or FPS changes. No new retail +deviation/register row; AD-121 unchanged. Existing async wait still passes. + +Implementer authoritative focused-r5 TRX33PASS; earlier attempts produced +no test evidence or failed before testing, not counted. Root removed the +actual production yield and the synchronous witness failed Assert.Empty: +`first-resized-frame` already requested in the resize Tick. Root's first +sandboxed invocation failed at NuGet.Config access before compiling/testing; +the permitted --no-restore invocation produced the intended mutation failure. +Exact source restoration, then Release build and probe/capture/orchestrator +68PASS/0FAIL/0SKIP. Wegener narrow F2 review PASS, no findings; no unchanged +GPU or retail lens was reopened. No command/client remains active. + +| F2 seal (scratch-relative artifacts) | SHA256 | +| --- | --- | +| RetailUiAutomationScriptRunner.cs | 8890A79B19A4EC6138F4E4EFE4AD0611448B4F3B3E67E892F8F5FD07A035650D | +| RetailUiAutomationProbeTests.cs | F3E8D7B10B7D93F76B0272A9A1893FCC260376EC17445C7799E665EA313A373F | +| artifacts/476-fix-round2/focused-r5/476-fix-round2-focused.trx | 51EB0646891A5E1EA0B9ED9488721549968602CCD6EAD6D743EFBD89F5E57A78 | +| artifacts/476-fix-round2/root-mutation/root-476-f2-mutation.trx | 7EA5EC8B71F718859677C5F12EE05891715D4EDF4B7926E9D0044CF77CFCD902 | +| artifacts/476-fix-round2/root-restored/root-476-f2-restored.trx | FBE295ED40D025B3E983F0DF337EE59079B4BE131DCBE90769BCE3CA0284292A | + +Lead integrates the exact two blobs with this ledger; fresh integrated +Release/68 tests and unchanged first-request/two-resize graphical route next. +R1 FAIL remains evidence until an actual passing replay, no waived image. diff --git a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md index a0c365e51..f5dccc7d4 100644 --- a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md +++ b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md @@ -4873,3 +4873,12 @@ item and NPC weapons through rest/combat/rest: provisional narrow PASS. 476-480-graphical-r1.md records exact paths, hashes, scope and lifecycle facts. No new production change, no register deviation. #478/#479 follow capture resolution. #477 owner-crash soak still owed; FPS/C2 remain deferred, G4 FAIL. + +F2 diagnosis independently confirmed: automation resized the window and +queued capture within the same old-extent render; GPU bytes were current. +Bounded contract58185285d, two-file OpenAI return, root actual-yield-removal +mutation fails and exact restoration/Release68PASS. Wegener narrow F2 PASS, +no findings; no GPU/capture/retail/route change or new deviation. Integrating +successful-resize command-loop yield, then fresh exact-binary graphical +replay. 476-lead-verification.md has seals. No third fix round authorized; +unchanged FPS deferral and G4 FAIL remain. diff --git a/src/AcDream.App/UI/Testing/RetailUiAutomationScriptRunner.cs b/src/AcDream.App/UI/Testing/RetailUiAutomationScriptRunner.cs index c71889922..97f5af48b 100644 --- a/src/AcDream.App/UI/Testing/RetailUiAutomationScriptRunner.cs +++ b/src/AcDream.App/UI/Testing/RetailUiAutomationScriptRunner.cs @@ -246,8 +246,19 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable var command = _commands[_index]; bool finished = Execute(command); if (!finished) break; + bool yieldAfterResize = command.Parts.Length > 0 + && string.Equals( + command.Parts[0], + "resize", + StringComparison.OrdinalIgnoreCase); _index++; _activeIndex = -1; + // Resize is applied while automation is inside private presentation, + // after Vulkan prepared the current frame. Yield so the following + // framebuffer wait cannot observe the new window size until the + // next render has crossed its frame-boundary swapchain recreation. + if (yieldAfterResize) + break; } if (_index >= _commands.Count && !_completed) diff --git a/tests/AcDream.App.Tests/UI/RetailUiAutomationProbeTests.cs b/tests/AcDream.App.Tests/UI/RetailUiAutomationProbeTests.cs index b3b43d0d8..4a71ae729 100644 --- a/tests/AcDream.App.Tests/UI/RetailUiAutomationProbeTests.cs +++ b/tests/AcDream.App.Tests/UI/RetailUiAutomationProbeTests.cs @@ -52,6 +52,8 @@ public sealed class RetailUiAutomationProbeTests public int RenderPackDisableCount { get; private set; } public int RenderPackReenableCount { get; private set; } public List<(int Width, int Height)> FramebufferResizes { get; } = []; + public bool PublishFramebufferSizeOnResize { get; set; } + public bool ResizeSucceeds { get; set; } = true; public int ClientCloseRequestCount { get; private set; } public List Checkpoints { get; } = new(); public HashSet ScreenshotRequests { get; } = new(); @@ -90,6 +92,16 @@ public sealed class RetailUiAutomationProbeTests public bool TryResizeFramebuffer(int width, int height, out string error) { FramebufferResizes.Add((width, height)); + if (!ResizeSucceeds) + { + error = "resize rejected"; + return false; + } + if (PublishFramebufferSizeOnResize) + { + FramebufferWidth = width; + FramebufferHeight = height; + } error = string.Empty; return true; } @@ -1023,6 +1035,137 @@ public sealed class RetailUiAutomationProbeTests } } + [Fact] + public void ScriptRunner_synchronousResizeYieldsBeforeWaitAndFirstScreenshot() + { + var (root, _, _, _, objects) = RootWithTwoItemLists(); + var runtime = new FakeRuntime { PublishFramebufferSizeOnResize = true }; + var probe = new RetailUiAutomationProbe(root, objects); + string path = Path.Combine( + Path.GetTempPath(), + Path.GetRandomFileName() + ".ui-probe.txt"); + File.WriteAllLines(path, + [ + "resize 1024 768", + "wait framebuffer 1024 768 1000", + "screenshot first-resized-frame 1000", + ]); + + try + { + using var runner = new RetailUiAutomationScriptRunner( + probe, + path, + dumpOnStart: false, + runtime: runtime); + + runner.Tick(0d); + + Assert.Equal([(1024, 768)], runtime.FramebufferResizes); + Assert.Empty(runtime.ScreenshotRequests); + Assert.False(runner.Completed); + + runner.Tick(0.001d); + + Assert.Equal([(1024, 768)], runtime.FramebufferResizes); + Assert.Equal(["first-resized-frame"], runtime.ScreenshotRequests); + Assert.False(runner.Completed); + + runtime.CompletedScreenshots.Add("first-resized-frame"); + runner.Tick(0.001d); + Assert.True(runner.Completed); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void ScriptRunner_sequentialSynchronousResizesYieldOnceEachWithoutRepeating() + { + var (root, _, _, _, objects) = RootWithTwoItemLists(); + var runtime = new FakeRuntime { PublishFramebufferSizeOnResize = true }; + var probe = new RetailUiAutomationProbe(root, objects); + string path = Path.Combine( + Path.GetTempPath(), + Path.GetRandomFileName() + ".ui-probe.txt"); + File.WriteAllLines(path, + [ + "resize 1024 768", + "wait framebuffer 1024 768 1000", + "resize 1600 900", + "wait framebuffer 1600 900 1000", + "screenshot second-resized-frame 1000", + ]); + + try + { + using var runner = new RetailUiAutomationScriptRunner( + probe, + path, + dumpOnStart: false, + runtime: runtime); + + runner.Tick(0d); + Assert.Equal([(1024, 768)], runtime.FramebufferResizes); + Assert.Empty(runtime.ScreenshotRequests); + + runner.Tick(0.001d); + Assert.Equal([(1024, 768), (1600, 900)], runtime.FramebufferResizes); + Assert.Empty(runtime.ScreenshotRequests); + + runner.Tick(0.001d); + runner.Tick(0.001d); + Assert.Equal([(1024, 768), (1600, 900)], runtime.FramebufferResizes); + Assert.Equal(["second-resized-frame"], runtime.ScreenshotRequests); + Assert.False(runner.Completed); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void ScriptRunner_failedResizeStopsWithoutYieldingIntoFollowingCommands() + { + var (root, _, _, _, objects) = RootWithTwoItemLists(); + var runtime = new FakeRuntime { ResizeSucceeds = false }; + var probe = new RetailUiAutomationProbe(root, objects); + var logs = new List(); + string path = Path.Combine( + Path.GetTempPath(), + Path.GetRandomFileName() + ".ui-probe.txt"); + File.WriteAllLines(path, + [ + "resize 1024 768", + "screenshot must-not-run 1000", + ]); + + try + { + using var runner = new RetailUiAutomationScriptRunner( + probe, + path, + dumpOnStart: false, + log: logs.Add, + runtime: runtime); + + runner.Tick(0d); + runner.Tick(0.001d); + + Assert.True(runner.Completed); + Assert.Equal([(1024, 768)], runtime.FramebufferResizes); + Assert.Empty(runtime.ScreenshotRequests); + Assert.Contains(logs, line => line.Contains("resize rejected", StringComparison.Ordinal)); + } + finally + { + File.Delete(path); + } + } + [Fact] public void ScriptRunner_closeClientRequestsNormalRuntimeShutdownExactlyOnce() {