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

@ -365,59 +365,18 @@ public class PortalVisibilityBuilderTests
}
// -----------------------------------------------------------------------
// Phase U.4c: the threshold "flap". A chain camera(C0) -> mid(C1) -> exit(C2)
// where the C0->C1 portal's clip plane sits just in front of the camera.
// BOTH poses are legitimately inside C0 and SHOULD see the exit window; they
// straddle the C0->C1 side-test boundary by a few cm. Pre-fix, the pose just
// behind the plane hard-culls C0->C1 (CameraOnInteriorSide), C2 is never
// reached, and OutsideView empties — the flap. The fix must keep the exit
// cell visible (OutsideView non-empty) at BOTH poses.
// Phase U.4c: an earlier synthetic flap test (Build_NearBoundaryIntermediatePortal)
// lived here. It modeled the doorway flap as a BUILDER side-test cull dropping the
// exit cell. The live ACDREAM_PROBE_FLAP capture (2026-05-31) DISPROVED that model:
// the real cause is the visibility ROOT being driven by the 3rd-person camera EYE —
// the eye drifts out of the player's cell, FindCameraCell returns a STALE cell for
// its grace frames, and the doorway is then culled as "behind" the eye. The fix is
// at the GameWindow integration level (root visibility at the PLAYER's cell:
// visRootPos), NOT in the builder — the builder's side test is correct and unchanged,
// so the old test asserted a non-bug and was removed rather than left red. The fix is
// validated by the visual gate + the [flap] probe (RenderingDiagnostics.ProbeFlapEnabled);
// see docs/research/2026-05-31-u4c-flap-characterization.md.
// -----------------------------------------------------------------------
private static Matrix4x4 ViewProjAt(Vector3 eye)
{
var view = Matrix4x4.CreateLookAt(eye, eye + new Vector3(0, 0, -1), Vector3.UnitY);
var proj = Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1.0f, 0.1f, 1000f);
return view * proj;
}
private static (LoadedCell cam, Dictionary<uint, LoadedCell> all) FlapChain()
{
const uint C0 = 0x0001, C1 = 0x0002, C2 = 0x0003;
// C0 -> C1 portal at z=-1, with a clip plane (normal +Z, InsideSide=0) at z=-1.
// dot = camZ + D; with D = 1 the plane is at camZ = -1: inside iff camZ >= -1 - eps.
var c0 = Cell(C0, new CellPortalInfo((ushort)C1, 0, 0, 0));
c0.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -1f));
c0.ClipPlanes.Add(new PortalClipPlane { Normal = new Vector3(0, 0, 1), D = 1f, InsideSide = 0 });
// C1 -> C2 (no clip plane → never culled), C2 has the exit window.
var c1 = Cell(C1, new CellPortalInfo((ushort)C2, 0, 0, 0));
c1.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -4f));
var c2 = Cell(C2, new CellPortalInfo(0xFFFF, 0, 0, 0));
c2.PortalPolygons.Add(Quad(0f, 0f, 1.0f, 1.0f, -7f));
var all = new Dictionary<uint, LoadedCell> { [C0] = c0, [C1] = c1, [C2] = c2 };
return (c0, all);
}
[Fact]
public void Build_NearBoundaryIntermediatePortal_ExitCellStaysVisibleAcrossPose()
{
var (cam, all) = FlapChain();
Func<uint, LoadedCell?> lookup = id => all.TryGetValue(id, out var c) ? c : null;
// Pose A: a few cm IN FRONT of the C0->C1 plane (camZ = -0.9 >= -1 → inside).
var poseA = new Vector3(0, 0, -0.9f);
var frameA = PortalVisibilityBuilder.Build(cam, poseA, lookup, ViewProjAt(poseA));
// Pose B: a few cm BEHIND it (camZ = -1.1 < -1 → pre-fix the side test culls C0->C1).
var poseB = new Vector3(0, 0, -1.1f);
var frameB = PortalVisibilityBuilder.Build(cam, poseB, lookup, ViewProjAt(poseB));
// The exit cell — and therefore OutsideView — must be present at BOTH poses.
Assert.False(frameA.OutsideView.IsEmpty, "pose A should see the exit window");
Assert.False(frameB.OutsideView.IsEmpty,
"pose B (a few cm away) must ALSO see the exit window — this is the flap: " +
"an intermediate side-test flip must not drop the exit cell from the set");
Assert.Contains(0x0003u, frameB.OrderedVisibleCells);
}
// Signed-area-magnitude (shoelace) sum over a CellView's polygons in NDC.
private static float CellViewArea(CellView view)