fix(render): Phase A8 — mark-and-punch BEFORE indoor draw (correct WB order)

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) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-26 09:18:13 +02:00
parent a2ad5c1ac4
commit b76f6d112e

View file

@ -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