fix(render): R-A2b — cull back portal like retail (InitCell side test), kill the indoor flap cycle
Pinned (flap-sidechk.log): the indoor doorway flap is a 0171<->0173 flood cycle. Back portals show camInterior=False (our side test already agrees with retail) but were traversed when eyeIn=True because the side-cull had an bypass (added 2026-06-05 for the void). Within 1.75m of a doorway that bypass kept the BACK portal alive -> mutual re-contribution -> re-enqueue churn (maxPop=16) -> eye-sensitive flood depth -> grey flap + dropped floor. Fix (Option B1): drop the bypass from the side-cull in Build + BuildFromExterior so back portals cull by the side test alone, exactly like retail PView::InitCell (:432962, no eye-in-opening bypass). The forward-portal clip-empty void rescue is a SEPARATE branch and is untouched (Build_EyeStandingInInteriorPortal_FloodsNeighbour stays green). New RED->GREEN test Build_BackFacingPortal_EyeStandingInOpening_StillCulled; full App suite 218 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
89a2032c8e
commit
485e44d163
2 changed files with 37 additions and 7 deletions
|
|
@ -249,6 +249,33 @@ public class PortalVisibilityBuilderTests
|
|||
Assert.True(frame.OutsideView.IsEmpty); // its window never marked
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_BackFacingPortal_EyeStandingInOpening_StillCulled()
|
||||
{
|
||||
// R-A2b (Option B1, the flap fix): retail PView::InitCell (:432962) culls a back-facing portal by
|
||||
// the side test REGARDLESS of eye proximity — there is NO eye-in-opening bypass. The live pin
|
||||
// (flap-sidechk.log, 2026-06-09) showed the back portal 0173->0171 with camInterior=False but
|
||||
// eyeIn=True (eye within 1.75 m of the shared doorway); the OLD `&& !eyeInsideOpening` side-cull
|
||||
// bypass let it through -> the 0171<->0173 flood cycle -> re-enqueue churn -> the doorway flap.
|
||||
// A back-facing portal the eye is STANDING IN must stay culled (the forward-portal clip-empty void
|
||||
// rescue, tested by Build_EyeStandingInInteriorPortal_FloodsNeighbour, is a separate path and stays).
|
||||
var cam = Cell(0x0001, new CellPortalInfo(0x0002, 0, 0, 0));
|
||||
cam.PortalPolygons.Add(Quad(0f, 0f, 0.5f, 0.5f, -1f)); // 1 m in front -> eyeInsideOpening = True
|
||||
// ClipPlane puts the eye on the EXIT side (camInterior = False), like the back portal of a doorway
|
||||
// just crossed: Normal·origin + D = 1 > 0, InsideSide==1 wants dot<=eps -> not interior.
|
||||
cam.ClipPlanes.Add(new PortalClipPlane { Normal = new Vector3(0, 0, 1), D = 1f, InsideSide = 1 });
|
||||
var ground = Cell(0x0002, new CellPortalInfo(0xFFFF, 0, 0, 0));
|
||||
ground.PortalPolygons.Add(Quad(0f, 0f, 1f, 1f, -6f));
|
||||
var all = new Dictionary<uint, LoadedCell> { [0x0001] = cam, [0x0002] = ground };
|
||||
|
||||
var frame = Build(cam, all);
|
||||
|
||||
Assert.False(frame.CellViews.ContainsKey(0x0002),
|
||||
"a back-facing portal (camInterior=False) must stay culled even when the eye is standing in its " +
|
||||
"opening (eyeInsideOpening=True) — retail's side test has no bypass; the bypass WAS the flap cycle");
|
||||
Assert.DoesNotContain(0x0002u, frame.OrderedVisibleCells);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_CwWoundExitPortal_OutsideRegionIsCcw()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue