feat(render): indoor render WORKS — terminating portal flood + every-cell seal + look-in FPS

Checkpoint of the unified retail-faithful indoor render. The two-week HANG/grey is fixed and the
interior seals (live-verified by the user). Commits the session render-rewrite foundation together
with the fixes that made it functional.

- HANG fix: PortalVisibilityBuilder.Build portal flood did not terminate (the faithful ProjectToClip
  near-side clip drifts per round, defeating the CellView dedup; the BFS had no bound after U.2a removed
  MaxReprocessPerCell). Fix = drift-tolerant snapped/canonical CellView.Add dedup (PortalView.cs) plus
  restored MaxReprocessPerCell=16 bounded re-enqueue (PortalVisibilityBuilder.cs). Re-enqueue is kept
  (load-bearing for late-slice propagation, Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit);
  only its count is capped. CellViewDedupTests added.
- Seal (DrawCells Task 2): RetailPViewRenderer.DrawEnvCellShells draws EVERY visible cell via
  IndoorDrawPlan.ShellPass (was gated on the ClipFrameAssembler slot filter, leaving slot-less cells grey).
- Look-in FPS: GameWindow exterior look-in candidates limited to the player landblock +-1 (was all ~81
  loaded LBs iterated every outdoor frame). No behaviour change (far cells were >48m, already culled).

Remaining dominant issue = the FLAP at transitions: viewer-cell metastability (render roots at the
camera-eye cell, which oscillates outdoor-indoor as the 3rd-person boom drifts across the doorway,
confirmed in render-sig). SEPARATE fix, NOT the DrawCells port. Full handoff + flap fix plan + tracked
follow-ups (#78 terrain, look-in-from-inside, look-in FPS, L-spotlight):
docs/research/2026-06-07-indoor-render-session-handoff.md.

Baselines: build 0 err; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-07 10:14:43 +02:00
parent bff1955066
commit 1405dd8e90
27 changed files with 3635 additions and 814 deletions

View file

@ -280,9 +280,9 @@ public static class RenderingDiagnostics
/// DrawInside vs the outdoor <c>LScape::draw</c> on <c>is_player_outside</c> — the
/// <b>PLAYER's</b> cell (<c>(player-&gt;m_position.objcell_id &amp; 0xFFFF) &lt; 0x100</c>,
/// <c>SmartBox::is_player_outside</c> 0x451e80) — NOT the camera/viewer cell. When the
/// player is inside it then roots the flood at the <b>viewer</b> cell
/// (<c>this-&gt;viewer_cell</c>). So the inside/outside <i>decision</i> follows the player;
/// only the indoor <i>root</i> follows the camera.</para>
/// player is inside, acdream roots the portal flood at the player's transition-owned
/// physics cell and projects from the camera eye, so the shell around the player remains
/// sealed during chase-camera cell transitions.</para>
///
/// <para>acdream historically branched on the camera cell (a non-null
/// <c>visibility.CameraCell</c>). A 3rd-person chase camera lags the player, so when the
@ -292,9 +292,9 @@ public static class RenderingDiagnostics
/// only entities (which bypass the gate) showing through. Branching on the player removes it.</para>
///
/// <param name="playerCellId">The player's current cell id (0 if unresolved → outside).</param>
/// <param name="viewerCellResolved">Whether a viewer/camera cell is available to root
/// DrawInside at. Indoor render needs both: the player inside AND a cell to root at.</param>
/// <param name="renderRootResolved">Whether the player's indoor render root is loaded and
/// available to DrawInside.</param>
/// </summary>
public static bool ShouldRenderIndoor(uint playerCellId, bool viewerCellResolved)
=> viewerCellResolved && IsEnvCellId(playerCellId);
public static bool ShouldRenderIndoor(uint playerCellId, bool renderRootResolved)
=> renderRootResolved && IsEnvCellId(playerCellId);
}

View file

@ -37,12 +37,13 @@ public sealed class WorldEntity
public PaletteOverride? PaletteOverride { get; init; }
/// <summary>
/// EnvCell ID that owns this entity (room geometry or static object inside
/// EnvCell or outdoor cell ID that owns this entity (room geometry, static
/// object, or live object inside/outside a cell).
/// the cell). Used by portal visibility to filter interior entities — only
/// entities whose ParentCellId appears in the visible set are rendered.
/// Null for outdoor entities (stabs, scenery, live server spawns).
/// Null for outdoor dat scenery/building stabs or unresolved live entities.
/// </summary>
public uint? ParentCellId { get; init; }
public uint? ParentCellId { get; set; }
/// <summary>
/// True when this entity originates from <c>LandBlockInfo.Buildings[]</c>