refactor(render): R-A1 — canonicalize outdoor-root detection on IsOutdoorNode

Replace ReferenceEquals(clipRoot, _outdoorNode) object-identity checks with the
documented LoadedCell.IsOutdoorNode flag (4 sites) so they survive R-A2 changing
the outdoor root's portals. Behavior-preserving (build + targeted suites green:
App PortalVisibilityBuilderTests 24/24, Core PlayerMovementControllerTests 14/14).

Right-sized from the planned 'collapse to one root': reading the live dispatch,
the viewerRoot ?? outdoorRoot split is already correct (viewerRoot feeds
cameraInsideCell/lighting via the older CellVisibility BFS; clipRoot is the render
root), and the 2026-06-07 cutover flip already made in-world frames single-path
DrawInside. The real flap fix is R-A2 (per-building floods). Dead exterior
DrawPortal look-in deletion deferred to R-A3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-08 18:25:58 +02:00
parent 6996e5645c
commit 7fe98098f5
2 changed files with 375 additions and 4 deletions

View file

@ -7536,7 +7536,7 @@ public sealed class GameWindow : IDisposable
// player; building interiors through the ground). Outdoors the interiors must
// depth-test against terrain+exteriors and appear only through real door openings,
// so issue NO depth clear. Interior roots keep the doorway clear (unchanged).
ClearDepthSlice = ReferenceEquals(clipRoot, _outdoorNode)
ClearDepthSlice = clipRoot.IsOutdoorNode
? null
: slice =>
{
@ -7568,7 +7568,7 @@ public sealed class GameWindow : IDisposable
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbePvInputEnabled && pvFrame is not null)
{
var vp = envCellViewProj;
char pvOutRoot = ReferenceEquals(clipRoot, _outdoorNode) ? 'Y' : 'n';
char pvOutRoot = clipRoot.IsOutdoorNode ? 'Y' : 'n';
// 2026-06-08: disambiguate the idle flap. eye=camera eye-point (drives the flood);
// player=RenderPosition (Lerp of physics, what the eye chases); rawPlayer=raw physics
// body Position; yaw=camera/player heading (F8 rad to catch micro-drift). If the flood
@ -7600,7 +7600,7 @@ public sealed class GameWindow : IDisposable
// outdoor-node root so no live entity blinks out outdoors (spec section 10 regression
// guard). DrawInside's tail clears entity clip routing and disables clip distances, so
// visibleCellIds:null draws them unclipped — identical to the old outdoor path.
if (ReferenceEquals(clipRoot, _outdoorNode)
if (clipRoot.IsOutdoorNode
&& _interiorRenderer is not null
&& pviewResult.Partition.LiveDynamic.Count > 0)
{
@ -9216,7 +9216,7 @@ public sealed class GameWindow : IDisposable
// cells): if bshell=N/N and ids=[node only] but the wall is still see-through, the exterior is
// failing to rasterize (draw/clip bug, not EnvCell sidedness); if ids includes interior cells,
// the outdoor flood is drawing interiors over the exterior.
sb.Append(" outRoot=").Append(ReferenceEquals(clipRoot, _outdoorNode) ? 'Y' : 'n');
sb.Append(" outRoot=").Append(clipRoot is { IsOutdoorNode: true } ? 'Y' : 'n');
if (partition is not null)
{
int shellTotal = 0, shellMesh = 0;