fix(render): flood the neighbour when the eye stands in an interior portal
When the chase camera roots in a thin doorway cell and the eye stands in an interior portal opening (live capture: vestibule->room portal D=0.16m, proj=0), the 2D projection degenerates and the neighbour was culled (cells=1) -> only the thin cell drew -> bluish void / transparent ceiling. Retail's 3D clip imposes no constraint for a portal the eye is inside, so the neighbour is fully visible. When the clipped region is empty but the eye stands in the opening (EyeInsidePortalOpening: within 0.5m of the portal plane AND point-in-opening), flood the neighbour with the current view. Guarded so an off-screen degenerate portal stays culled (no #95 blowup; over-include is mesh-frustum-culled at draw). Visual-verified: cellar ceiling now solid. Band-aid for thin-cell-root coverage; likely superseded by the boom-stability + viewer-cell dead-zone + w=0 near-plane clip fix next session (reassess / maybe revert). 2 RED->GREEN tests; cyclic/hub termination guards unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f596f2d25
commit
9f95252d20
2 changed files with 127 additions and 8 deletions
|
|
@ -58,6 +58,46 @@ public class PortalVisibilityBuilderTests
|
|||
$"OutsideView width {outsideWidth} should be a sliver, far less than full window {windowOnlyWidth}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_EyeStandingInInteriorPortal_FloodsNeighbour()
|
||||
{
|
||||
// R1 void fix (2026-06-05): when the chase camera roots in a thin doorway cell and the eye is
|
||||
// STANDING IN an interior portal opening, the live capture showed the vestibule->room portal at
|
||||
// D=0.16 m projecting to 0 verts (proj=0), so the neighbour was wrongly culled (cells=1) and
|
||||
// only the thin cell drew -> bluish void. Retail's 3D portal clip imposes no constraint for a
|
||||
// portal the eye is inside, so the neighbour is fully visible. The flood MUST reach the neighbour.
|
||||
var cam = Cell(0x0001, new CellPortalInfo(0x0002, 0, 0, 0));
|
||||
cam.PortalPolygons.Add(Quad(0f, 0f, 0.3f, 0.3f, -0.03f)); // opening 3 cm in front — eye standing in it
|
||||
var room = Cell(0x0002, new CellPortalInfo(0xFFFF, 0, 0, 0));
|
||||
room.PortalPolygons.Add(Quad(0f, 0f, 1.0f, 1.0f, -6f));
|
||||
var all = new Dictionary<uint, LoadedCell> { [0x0001] = cam, [0x0002] = room };
|
||||
|
||||
var frame = Build(cam, all);
|
||||
|
||||
Assert.True(frame.CellViews.ContainsKey(0x0002),
|
||||
"eye standing in the doorway must flood the neighbour (degenerate projection was culling it -> void)");
|
||||
Assert.Contains(0x0002u, frame.OrderedVisibleCells);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_DegeneratePortalToTheSide_NotFlooded_NoOverInclusion()
|
||||
{
|
||||
// Guard against the fix over-flooding: a portal whose opening the eye is NOT standing in (3 cm
|
||||
// in front but 2 m to the SIDE) also projects degenerate, but the eye is OUTSIDE the opening, so
|
||||
// it must stay culled — otherwise the eye-in-doorway fix would blow up the visible set (#95) by
|
||||
// flooding every degenerate-projecting portal regardless of where the eye actually is.
|
||||
var cam = Cell(0x0001, new CellPortalInfo(0x0002, 0, 0, 0));
|
||||
cam.PortalPolygons.Add(Quad(2.0f, 0f, 0.3f, 0.3f, -0.03f)); // 2 m to the side — eye NOT in it
|
||||
var room = Cell(0x0002, new CellPortalInfo(0xFFFF, 0, 0, 0));
|
||||
room.PortalPolygons.Add(Quad(0f, 0f, 1.0f, 1.0f, -6f));
|
||||
var all = new Dictionary<uint, LoadedCell> { [0x0001] = cam, [0x0002] = room };
|
||||
|
||||
var frame = Build(cam, all);
|
||||
|
||||
Assert.False(frame.CellViews.ContainsKey(0x0002),
|
||||
"a degenerate portal the eye is NOT standing in must stay culled (no over-inclusion / #95 blowup)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_SealedCellar_NoExitPortal_OutsideViewEmpty()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue