knife-edge port: polyClipFinish W=0 eye-plane clip + degenerate-view propagation; EyeInsidePortalOpening rescue DELETED

Ports retail ACRender::polyClipFinish (0x006b6d00, pc:702749) near-eye
semantics into PortalProjection.ProjectToClip - the fundamental fix for
the in-plane portal clip family (climb strobes, tower-top roof/floor
flap while turning; live-corroborated this session: [viewer-diff]
0xAAB30108 strobing 27x mid-climb, whole interior dropping at the top).
Pseudocode: docs/research/2026-06-11-polyclipfinish-w0-clip-pseudocode.md.

Three legs, all decomp-driven:

1. ProjectToClip clips at w >= 0 EXACTLY (was EyePlaneW=1e-4), with
   retail's any-negative-w gate. Boundary intersections land at w == 0
   (homogeneous directions), so a portal the eye is CROSSING yields the
   correct unbounded half-region that the bounded view-region clip cuts
   to the screen. A w=0 vertex cannot survive a bounded region clip
   into the divide (direction fails some edge of any bounded convex
   region); the measure-zero corner case is guarded non-finite->empty.

2. CellView.CanonicalKey keys ALL-COLLINEAR (zero-area) views as their
   snapped segment ("L:" + extremes) instead of rejecting them - retail
   PROPAGATES degenerate views (ClipPortals decomp:433651-433711
   forwards any count!=0 GetClip output, no area gate anywhere), keeping
   the cell behind an exactly-in-plane portal in the draw list (cells
   draw whole; onward floods die naturally). Rejection dropped the
   whole chain for the frame - the parked-eye knife-edge band. Finite
   key space unchanged -> dedup + strict-growth convergence intact.

3. The EyeInsidePortalOpening rescue is DELETED (the T2-documented
   compensation for the 1e-4 divergence) along with EyeStandingPerpDist
   + PointInPoly2D. Empty clip = no flood, period (retail's rule).
   CornerFloodReplay - the gate that REFUTED the previous deletion
   attempt - passes WITHOUT the rescue under the W=0 port.

Harness criterion corrected to retail's rules (it codified the rescue):
cells fully BEHIND the camera are not required (all-behind portals clip
empty in retail); monotone area holds per root regime; the two
manufactured exact-on-plane steps assert root-only (boundary root pick
is ambiguous; the in-plane portal there is ~perpendicular to the gaze =
genuinely off-screen). Build_CollapsedInteriorPortalNearEye test
inverted to pin the retail empty-clip rule (it pinned the rescue).

New pins: eye-crossing portal -> w==0 boundary verts + half-region (not
sliver); gaze-along-plane degenerate view accepted + segment-key dedup;
non-finite guard. Replay harnesses (CornerFloodReplay, Issue120,
TowerAscent, HouseExit, Issue127) all green.

Suites: App 246+1skip / Core 1430+2skip / UI 420 / Net 294.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-11 21:44:23 +02:00
parent 2163308032
commit 987313aa54
7 changed files with 357 additions and 130 deletions

View file

@ -340,4 +340,119 @@ public class PortalProjectionTests
Assert.True(ndc.Length >= 3);
foreach (var v in ndc) { Assert.InRange(v.X, -0.301f, 0.301f); Assert.InRange(v.Y, -0.301f, 0.301f); }
}
// ---------------------------------------------------------------------------
// The W=0 knife-edge port (2026-06-11) — retail ACRender::polyClipFinish part 1
// (0x006b6d00, pc:702749; pseudocode at docs/research/2026-06-11-polyclipfinish-
// w0-clip-pseudocode.md). The eye-plane clip is at w >= 0 EXACTLY: boundary
// intersections land at w == 0 (homogeneous directions), so a portal the eye is
// CROSSING (stair openings on a spiral climb, the tower deck) yields the correct
// unbounded half-region that the bounded view-region clip then cuts to the
// screen. The previous EyePlaneW = 1e-4 made the boundary verts finite ~1e4-NDC
// points and the resulting regions sat at the merge/dedup degeneracy threshold —
// the climb-strobe class that the (now deleted) EyeInsidePortalOpening rescue
// compensated for.
// ---------------------------------------------------------------------------
[Fact]
public void ProjectToClip_EyeCrossingPortal_BoundaryVertsLandAtWZero()
{
// A horizontal floor opening 5 mm BELOW the eye, spanning from 1.5 m ahead to
// 0.5 m behind — the spiral-climb crossing frame. The two edges crossing the
// eye plane must emit intersections at exactly w == 0 (retail polyClipFinish
// t = w0/(w0-w1)), not at an epsilon offset.
var opening = new[]
{
new Vector3(-1f, -0.005f, -1.5f), new Vector3(1f, -0.005f, -1.5f),
new Vector3(1f, -0.005f, 0.5f), new Vector3(-1f, -0.005f, 0.5f),
};
var clip = PortalProjection.ProjectToClip(opening, Matrix4x4.Identity, ViewProj());
Assert.True(clip.Length >= 3, "an eye-crossing portal must keep its forward half");
int atZero = 0;
foreach (var v in clip)
{
Assert.True(v.W >= 0f, $"no survivor may sit behind the eye plane, got w={v.W}");
if (v.W == 0f) atZero++;
}
Assert.True(atZero >= 2, $"the two eye-plane crossings must land at exactly w==0, got {atZero}");
}
[Fact]
public void ClipToRegion_EyeCrossingFloorOpening_YieldsHalfRegionNotSliver()
{
// Same crossing frame: the visible set through an opening the eye is inside is
// the half-screen below the opening's plane horizon — NOT the degenerate sliver
// the epsilon clip produced. Full screen area is 4.0; the half-region must hold
// a substantial part of it.
var opening = new[]
{
new Vector3(-1f, -0.005f, -1.5f), new Vector3(1f, -0.005f, -1.5f),
new Vector3(1f, -0.005f, 0.5f), new Vector3(-1f, -0.005f, 0.5f),
};
var clip = PortalProjection.ProjectToClip(opening, Matrix4x4.Identity, ViewProj());
var ndc = PortalProjection.ClipToRegion(clip, FullScreenCcw());
Assert.True(ndc.Length >= 3, "the crossing frame must produce a region, not empty (the climb strobe)");
foreach (var v in ndc)
{
Assert.True(float.IsFinite(v.X) && float.IsFinite(v.Y), $"region verts must be finite, got ({v.X},{v.Y})");
Assert.InRange(v.X, -1.001f, 1.001f);
Assert.InRange(v.Y, -1.001f, 1.001f);
}
float area = AbsArea(ndc);
Assert.True(area > 1.5f,
$"the region must approximate the lower half-screen (area ~2.0 of 4.0), got {area} (sliver = the strobe bug)");
}
[Fact]
public void EyeInPortalPlane_GazeAlongPlane_DegenerateViewPropagates()
{
// The spiral-climb knife edge: the eye sits IN a horizontal portal's plane with
// the gaze ALONG the plane (climbing stairs through the opening). The opening is
// visibly edge-on ON screen: ProjectToClip + ClipToRegion yield a zero-area
// collinear region — and retail PROPAGATES it (ClipPortals forwards any count!=0
// clip; no area gate), keeping the cell behind in the draw list. CellView.Add
// must therefore ACCEPT the collinear polygon (the "L:" segment key) instead of
// rejecting it as degenerate — rejection dropped the whole chain for the frame.
var view = Matrix4x4.CreateLookAt(Vector3.Zero, new Vector3(0, 0, -1), Vector3.UnitY);
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 1.0f, 5000f);
var vp = view * proj;
// Horizontal opening in the y=0 plane (contains the eye), ahead of the camera.
var opening = new[]
{
new Vector3(-1f, 0f, -1f), new Vector3(1f, 0f, -1f),
new Vector3(1f, 0f, -4f), new Vector3(-1f, 0f, -4f),
};
var clip = PortalProjection.ProjectToClip(opening, Matrix4x4.Identity, vp);
Assert.True(clip.Length >= 3, "the in-plane opening's forward part must survive the W clip");
var ndc = PortalProjection.ClipToRegion(clip, FullScreenCcw());
Assert.True(ndc.Length >= 3, "the edge-on opening must yield its (zero-area) collinear region");
var cellView = new CellView();
Assert.True(cellView.Add(new ViewPolygon(ndc)),
"a zero-area collinear view must be ACCEPTED (retail propagates degenerate views; " +
"rejecting it drops the cell chain at the knife edge)");
// Re-emission of the same degenerate view dedups (finite segment-key space = convergence).
Assert.False(cellView.Add(new ViewPolygon(ndc)),
"a re-emitted degenerate view must dedup via its segment key");
}
[Fact]
public void ClipToRegion_NeverReturnsNonFiniteVerts()
{
// The measure-zero guard: whatever survives the bounded region clip must divide
// to finite NDC. Exercise with a portal whose vertices sit ON the eye plane
// (w == 0 inputs) plus one in front — degenerate input, must yield empty or finite.
var degenerate = new[]
{
new Vector3(-1f, 0f, 0f), new Vector3(1f, 0f, 0f), new Vector3(0f, 1f, -2f),
};
var clip = PortalProjection.ProjectToClip(degenerate, Matrix4x4.Identity, ViewProj());
if (clip.Length >= 3)
{
var ndc = PortalProjection.ClipToRegion(clip, FullScreenCcw());
foreach (var v in ndc)
Assert.True(float.IsFinite(v.X) && float.IsFinite(v.Y),
$"non-finite NDC vert leaked from the divide: ({v.X},{v.Y})");
}
}
}