From 38d537491f24733dd08df0979797316b58568476 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 26 May 2026 12:07:36 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20R3.5=20=E2=80=94=20?= =?UTF-8?q?gate=20stencil=20branch=20on=20PointInCell=20containment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the transition-flicker symptom observed during R4 visual verification: brief 1-3 frames after exiting a building where outdoor scenery rendered with wrong stencil mask, "walls disappear and buildings show under ground" shimmer, and sky stayed suppressed. Root cause: CellVisibility.FindCameraCell holds the previous CameraCell for ~3 grace frames after the camera physically exits the cell volume (see _cellSwitchGraceFrames). The grace mechanism prevents flicker at the doorway threshold for sky/lighting/depth-clear, but the new R3 stencil branch was using `cameraInsideCell` directly — so during grace frames it ran MarkAndPunch with the previous cell's portals (now behind/ beside the camera) and the IndoorPass + stencil-gated outdoor produced the garbage frame. Fix: compute `cameraReallyInside` via the stricter CellVisibility.PointInCell containment check and use it (instead of `cameraInsideCell`) as the gate for the stencil branch. Sky, depth-clear, lighting, and particles continue to use `cameraInsideCell` so their smooth grace-aware behavior is unchanged. Handoff item #10 (docs/research/2026-05-26-a8-revert-handoff.md) flagged this exact concern: "Likely the CellSwitchGraceFrameCount = 3 interacting with stencil setup timing." Confirmed and closed. Visual-verification of the fix is part of R4 (re-run). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index c95178d..b9c481b 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7141,7 +7141,29 @@ public sealed class GameWindow : IDisposable animatedIds.Add(k); } - if (cameraInsideCell && _indoorStencilPipeline is not null + // Phase A8 R3.5 — transition-flicker fix. `cameraInsideCell` + // stays true for ~3 grace frames after the camera physically + // exits a cell (see CellVisibility._cellSwitchGraceFrames). + // The grace mechanism prevents lighting/sky flicker at the + // doorway threshold, but if the stencil pipeline runs during + // grace frames it marks/punches at the OLD cell's portal + // silhouettes — which are now behind/beside the camera — and + // the subsequent IndoorPass + stencil-gated outdoor produce + // a brief frame of "walls disappear + buildings under ground" + // garbage. Gate the stencil branch on the stricter + // `PointInCell` containment check so the pipeline only runs + // when the camera is ACTUALLY inside its cell volume; sky / + // lighting / depth-clear continue to use `cameraInsideCell` + // for their smoother grace-aware behavior. + bool cameraReallyInside = visibility?.CameraCell is not null + && CellVisibility.PointInCell(camPos, visibility.CameraCell); + + // The `visibility?.CameraCell is not null` repeat is for the + // compiler's null-flow analysis: `cameraReallyInside` already + // implies it, but flow doesn't propagate through a separate + // local. Restating it inside the if condition lets the branch + // body use the un-`?`d form without null-forgiving. + if (cameraReallyInside && _indoorStencilPipeline is not null && visibility?.CameraCell is not null) { // Phase A8 R3 — WB RenderInsideOut order.