fix(render): Phase U.4c — root indoor visibility at the player's cell (the flap)

The visibility root + portal-side test now use the PLAYER position (visRootPos) in
player mode instead of the camera EYE; the eye still drives the per-frame projection
(envCellViewProj). Live ACDREAM_PROBE_FLAP evidence: the flap was the 3rd-person eye
drifting out of the player's cell -> FindCameraCell returning the STALE cell for its
grace frames -> the doorway portal culled as behind-the-eye -> exit cell + terrain +
shells dropped (res=Grace eyeInRoot=n terrain=Skip on every flap frame). Retail's
CellManager::ChangePosition (0x004559B0) tracks curr_cell by the player; acdream
already roots lighting at the player (GameWindow:7152) for the same chase-cam reason
— visibility was the lone holdout on the eye. Removed the earlier synthetic builder
flap test, which modeled a disproven (side-test) hypothesis; the fix is integration-
level, validated by the visual gate + [flap] probe. App tests 151/151.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-31 14:35:21 +02:00
parent f47895cc73
commit 0ee328a824
2 changed files with 30 additions and 54 deletions

View file

@ -7139,7 +7139,21 @@ public sealed class GameWindow : IDisposable
// Step 4: portal visibility — compute BEFORE the UBO upload so
// the indoor flag drives the sun's intensity to zero for
// dungeons (r13 §13.7).
var visibility = _cellVisibility.ComputeVisibility(camPos);
// Phase U.4c (2026-05-31): root indoor visibility at the PLAYER's cell, not the
// camera EYE. Retail's CellManager::ChangePosition (0x004559B0) tracks curr_cell by
// the player/physics position. The 3rd-person chase EYE drifts out of the player's
// cell (through interior walls into AABB gaps); FindCameraCell then can't place the
// eye and returns the STALE previous cell for its 3 grace frames, from which the
// doorway portal is "behind" the eye → culled → the exit cell + terrain + shells
// flap off. ACDREAM_PROBE_FLAP capture (2026-05-31): every flap frame is
// res=Grace eyeInRoot=n terrain=Skip; every good frame is eyeInRoot=Y. The eye is
// still used for the per-frame PROJECTION (envCellViewProj) — only the cell ROOT +
// portal-side test track the player. This mirrors the playerInsideCell lighting
// decision below, which already roots at the player for exactly this reason.
var visRootPos = (_playerMode && _playerController is not null)
? _playerController.Position
: camPos;
var visibility = _cellVisibility.ComputeVisibility(visRootPos);
bool cameraInsideCell = visibility?.CameraCell is not null;
// Phase U.4 (2026-05-30): the [vis] probe moved DOWN to the unified
@ -7285,9 +7299,12 @@ public sealed class GameWindow : IDisposable
HashSet<uint>? envCellShellFilter = null; // drawable visible cells (cellIdToSlot keys)
if (clipRoot is not null)
{
// Phase U.4c: side test + distance ordering use the PLAYER position (visRootPos,
// stable inside the cell); projection uses the eye's envCellViewProj (the screen
// view). See the visRootPos rationale at the ComputeVisibility call above.
var pvFrame = PortalVisibilityBuilder.Build(
clipRoot,
camPos,
visRootPos,
id => _cellVisibility.TryGetCell(id, out var c) ? c : null,
envCellViewProj);