From 2bfeafd3582e0fa90969dc732c6940a2dc2a3bc0 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 26 May 2026 12:40:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20R3.5=20v2=20?= =?UTF-8?q?=E2=80=94=20gate=20depth-clear=20on=20cameraReallyInside=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R3.5 v1 only gated the stencil branch on `cameraReallyInside`; the depth-clear-if-inside at line ~7129 stayed on `cameraInsideCell`. During grace frames after exit: cameraInsideCell = true (grace, holds previous cell for 3 frames) cameraReallyInside = false (PointInCell on camera pos returns false) So depth-clear FIRED (writing depth = 1.0 globally) but the OUTDOOR branch ran (single Draw(All) on every entity). With depth cleared, terrain's depth = 1.0 — every entity below terrain (cellar geometry, basement GfxObjs, anything at world Z < terrain Z) won the depth test and rendered THROUGH the ground. User reported: "stand outside or pass outside → flicker where objects are visible through ground and walls of other buildings are missing." v2 fix: unify depth-related gates on `cameraReallyInside`. During grace frames depth-clear is now ALSO skipped; terrain depth survives; the outdoor pass renders normally with proper terrain occlusion. Sky / lighting / particles continue to use `cameraInsideCell` for smooth grace-aware transitions. The two-flag split is now explicit: cameraInsideCell → sky, lighting (smooth, grace-aware) cameraReallyInside → depth-clear, stencil branch (strict, no grace) Closes the persistent transition flicker observed in R4 visual verification after v1. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 58 ++++++++++++++----------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index b9c481b..e132c65 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7122,17 +7122,42 @@ public sealed class GameWindow : IDisposable _terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length; MaybeFlushTerrainDiag(); - // Conditional depth clear: when camera is inside a building, clear - // depth (not color) so interior geometry writes fresh Z values on top - // of the terrain color buffer. Exit portals show outdoor terrain color - // because we kept the color buffer. Matching ACME GameScene.cs pattern. - if (cameraInsideCell) + // 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 sky/lighting flicker at the + // doorway threshold, but the render-frame mechanisms that + // touch depth (depth-clear AND the stencil pipeline) MUST be + // gated on the stricter PointInCell containment so they don't + // fire during grace frames when the camera is actually outside. + // + // cameraInsideCell — lenient, grace-aware → sky, lighting + // cameraReallyInside — PointInCell, no grace → depth-clear, + // stencil pipeline branch + // + // R3.5 v1 only gated the stencil branch on `cameraReallyInside`; + // depth-clear stayed on `cameraInsideCell`. Result: during grace + // frames the depth-clear ran but the outdoor branch ran (because + // !cameraReallyInside), so terrain depth was destroyed AND + // everything below terrain (cellars, basement geometry) won the + // depth test in the outdoor pass → "objects visible through + // ground." R3.5 v2 unifies the depth-related gates on + // `cameraReallyInside` so terrain depth is preserved during + // grace, eliminating the through-ground artifact. + bool cameraReallyInside = visibility?.CameraCell is not null + && CellVisibility.PointInCell(camPos, visibility.CameraCell); + + // Conditional depth clear: when camera is ACTUALLY inside a cell + // volume (not just in the grace window), clear depth (not color) + // so interior geometry writes fresh Z values on top of the + // terrain color buffer. Matches ACME GameScene.cs pattern. + if (cameraReallyInside) _gl!.Clear(ClearBufferMask.DepthBufferBit); // L-fix1 (2026-04-28): animated-entity id set. Required by both - // the cameraInsideCell branch (to route them to LiveDynamic pass) - // and the outdoor path (where it preserves visibility across - // landblock frustum culling). + // the cameraReallyInside branch (to route them to LiveDynamic + // pass) and the outdoor path (where it preserves visibility + // across landblock frustum culling). HashSet? animatedIds = null; if (_animatedEntities.Count > 0) { @@ -7141,23 +7166,6 @@ public sealed class GameWindow : IDisposable animatedIds.Add(k); } - // 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