From b76f6d112e1f05bd906bb463bf3a911cc81a350d Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 26 May 2026 09:18:13 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20=E2=80=94=20mark-an?= =?UTF-8?q?d-punch=20BEFORE=20indoor=20draw=20(correct=20WB=20order)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second visual verification surfaced three depth-ordering bugs all from one cause: the IndoorOnly dispatcher Draw ran BEFORE MarkAndPunch, so the far-depth punch (gl_FragDepth = 1.0 at stencil=1 portal silhouettes) overwrote any indoor depth that had been written there. Result: • Closed doors leaked outside terrain — door mesh wrote depth 0.6 at the portal silhouette, then the punch overwrote it to 1.0, then terrain at 0.99 won the depth test. • Walls between rooms leaked the far-side door/window opening — same mechanism: wall depth at the far-portal silhouette destroyed by the punch. • Animated character body bled to terrain where it overlapped a portal silhouette on screen — same mechanism: character depth destroyed by the punch. Re-reading WB's RenderInsideOut (VisibilityManager.cs:73-239) confirms the correct order is mark-and-punch FIRST, then indoor cells. Indoor geometry drawn AFTER the punch wins the depth test against 1.0 and correctly occludes the subsequent stencil-gated outdoor pass. The swap is a single block move; MarkAndPunch was already correctly leaving the GL state stencil-disabled for the indoor pass to follow. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 27 +++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 464fbb0..0dbcb49 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7159,18 +7159,33 @@ public sealed class GameWindow : IDisposable if (cameraInsideCell && portalTriCount > 0) { - // WB Step 3: draw indoor entities first, stencil OFF. - // Indoor entities (cell mesh + cell statics) ALWAYS draw - // inside the camera-building, regardless of portal coverage. + // Phase A8 fix (post-visual): the correct WB order is + // mark-and-punch FIRST, then indoor cells. The far-depth + // punch overwrites depth at portal silhouettes to 1.0; + // indoor geometry drawn AFTER the punch wins the depth + // test (its depth is closer than 1.0) and correctly + // occludes the subsequent stencil-gated outdoor pass. + // Drawing indoor first (the old order) had indoor depth + // destroyed by the punch, so terrain at 0.99 won every + // stencil-gated pixel: closed doors leaked outside, + // walls between rooms leaked far-side door openings, + // and characters bled to terrain at portal overlaps. + + // WB Steps 1+2: mark stencil + punch far depth in portal regions. + _indoorStencil!.MarkAndPunch(camera.View * camera.Projection); + + // WB Step 3: draw indoor entities second, stencil OFF + // (MarkAndPunch left the state stencil-disabled). Indoor + // entities ALWAYS draw inside the camera-building, regardless + // of portal coverage. Animated entities (player, NPCs) flow + // through IndoorOnly via the animatedEntityIds override so + // they're not stencil-gated by the outdoor pass. _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum, neverCullLandblockId: playerLb, visibleCellIds: visibility!.VisibleCellIds, animatedEntityIds: animatedIds, set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.IndoorOnly); - // WB Steps 1+2: mark stencil + punch far depth in portal regions. - _indoorStencil!.MarkAndPunch(camera.View * camera.Projection); - // WB Step 4a: terrain, stencil-gated to portal silhouettes. _indoorStencil!.EnableOutdoorPass(); // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup