fix(render): #114 - scope the PView shell clip to outdoor-eye roots (first user gate findings)

The 2026-06-11 user gate on 927fd8f: the OUTDOOR half works (phantom
meeting-hall staircase GONE at the original spot) but enabling the clip
for INDOOR roots exposed that our indoor clip regions are admission-
quality, not draw-quality - chopped interior stairs, a neighbour rooms
barrel visible through a clipped-away wall, missing candle-holder
geometry, inner walls vanishing while passing building exits. Retail
crops indoors too, but with pixel-exact recursively-clipped regions;
ours have knife-edge cases indoors that were invisible until the GL
enable made them cut real geometry.

Scope: DrawInside enables the shell clip only when RootCell.IsOutdoorNode
(the regime Issue113MeetingHallFloodTests validates); DrawPortal (from-
outside look-in) keeps it on; indoor roots draw unclipped - yesterdays
user-accepted state. Filed #114 for bringing indoor regions to retail
crop quality (also the home of the remaining gate findings to re-test:
entry-transparency at the hilltop cottage, particles visible through
buildings, camera feel in cramped interiors).

App 224 green; no Core/UI/Net surface touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-10 20:22:29 +02:00
parent 8259598970
commit 9ce335eb17

View file

@ -93,7 +93,16 @@ public sealed class RetailPViewRenderer
DrawLandscapeThroughOutsideView(ctx, clipAssembly, partition); DrawLandscapeThroughOutsideView(ctx, clipAssembly, partition);
UseIndoorMembershipOnlyRouting(); UseIndoorMembershipOnlyRouting();
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells); DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells); // #113 fix scope (#114): GL-clip the shells only for the OUTDOOR root —
// the case the flood replay validated (tight, stable door-aperture
// regions) and the one that produced the phantom staircase. The first
// user gate (2026-06-11) showed INDOOR clip regions are not yet
// draw-quality (chopped stairs / vanishing inner walls at exits /
// see-through to neighbour rooms at the meeting hall) — indoor roots
// stay unclipped (yesterday's user-accepted state) until #114 brings
// the indoor regions to retail's pixel-exact crop.
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells,
clipShells: ctx.RootCell.IsOutdoorNode);
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition); DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition);
return result; return result;
@ -193,7 +202,9 @@ public sealed class RetailPViewRenderer
ctx.EmitDiagnostics?.Invoke(result); ctx.EmitDiagnostics?.Invoke(result);
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells); DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells); // DrawPortal is the from-outside look-in path — same validated outdoor
// regime as the outdoor root (see #114 scope note in DrawInside).
DrawEnvCellShells(ctx, pvFrame, clipAssembly, drawableCells, clipShells: true);
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition); DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition);
RestoreNoClip(ctx.SetTerrainClipUbo); RestoreNoClip(ctx.SetTerrainClipUbo);
@ -335,7 +346,8 @@ public sealed class RetailPViewRenderer
IRetailPViewCellDrawCallbacks ctx, IRetailPViewCellDrawCallbacks ctx,
PortalVisibilityFrame pvFrame, PortalVisibilityFrame pvFrame,
ClipFrameAssembly clipAssembly, ClipFrameAssembly clipAssembly,
HashSet<uint> drawableCells) // param kept this task; removed in Task 4 HashSet<uint> drawableCells, // param kept this task; removed in Task 4
bool clipShells)
{ {
// Retail DrawCells Loop 2: every visible cell's shell, reverse cell_draw_list // Retail DrawCells Loop 2: every visible cell's shell, reverse cell_draw_list
// (far→near), per portal_view slice. No drawableCells filter — a cell without a // (far→near), per portal_view slice. No drawableCells filter — a cell without a
@ -358,6 +370,12 @@ public sealed class RetailPViewRenderer
// Characters/statics stay unclipped (DrawCellObjectLists): retail's mesh path is // Characters/statics stay unclipped (DrawCellObjectLists): retail's mesh path is
// viewcone-check + BoundingType handling, and hard-clipping slices characters at // viewcone-check + BoundingType handling, and hard-clipping slices characters at
// doorways (the original UseIndoorMembershipOnlyRouting observation). // doorways (the original UseIndoorMembershipOnlyRouting observation).
//
// clipShells (#114 scope, 2026-06-11): true only for outdoor-eye roots.
// The first user gate showed indoor clip regions are not draw-quality
// yet (chopped stairs / vanishing walls at exits) — indoor roots draw
// unclipped until #114 lands pixel-exact indoor regions.
if (clipShells)
for (int i = 0; i < ClipFrame.MaxPlanes; i++) for (int i = 0; i < ClipFrame.MaxPlanes; i++)
_gl.Enable(Silk.NET.OpenGL.EnableCap.ClipDistance0 + i); _gl.Enable(Silk.NET.OpenGL.EnableCap.ClipDistance0 + i);
@ -375,6 +393,7 @@ public sealed class RetailPViewRenderer
} }
} }
if (clipShells)
for (int i = 0; i < ClipFrame.MaxPlanes; i++) for (int i = 0; i < ClipFrame.MaxPlanes; i++)
_gl.Disable(Silk.NET.OpenGL.EnableCap.ClipDistance0 + i); _gl.Disable(Silk.NET.OpenGL.EnableCap.ClipDistance0 + i);
} }