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:
parent
2163308032
commit
987313aa54
7 changed files with 357 additions and 130 deletions
|
|
@ -70,29 +70,42 @@ public static class PortalProjection
|
|||
return ndc;
|
||||
}
|
||||
|
||||
/// <summary>Faithful homogeneous projection (retail PrimD3DRender::xformStart + the w=0 clip of
|
||||
/// <summary>Faithful homogeneous projection (retail PrimD3DRender::xformStart + the W=0 clip of
|
||||
/// ACRender::polyClipFinish, decomp 424310 / 702749): transform the portal to clip space and clip
|
||||
/// ONLY the eye plane (w >= <see cref="EyePlaneW"/>), keeping homogeneous coords — NO perspective
|
||||
/// divide, NO frustum side-plane clamp. The screen bound is applied later by <see cref="ClipToRegion"/>
|
||||
/// ONLY the eye plane (w >= 0, EXACT), keeping homogeneous coords — NO perspective divide, NO
|
||||
/// frustum side-plane clamp. The screen bound is applied later by <see cref="ClipToRegion"/>
|
||||
/// against the view region (the root region is the full screen), exactly as retail clips the portal
|
||||
/// against the accumulated portal_view rather than fixed side planes. Keeping w means a near/grazing
|
||||
/// portal never collapses to a zero-area edge sliver (the flap) nor blows up under an early divide
|
||||
/// (the void). Returns <3 verts when the portal is entirely behind the eye.</summary>
|
||||
/// against the accumulated portal_view rather than fixed side planes.
|
||||
///
|
||||
/// <para>The W=0 clip is exact on purpose (the knife-edge port, 2026-06-11; pseudocode at
|
||||
/// docs/research/2026-06-11-polyclipfinish-w0-clip-pseudocode.md): boundary intersections land
|
||||
/// at w == 0 — homogeneous DIRECTIONS — so a portal the eye is crossing (stair openings, decks)
|
||||
/// yields the correct UNBOUNDED half-region, which the bounded view-region clip then cuts to the
|
||||
/// screen. The previous EyePlaneW = 1e-4 produced finite ~1e4-NDC boundary verts whose region
|
||||
/// intersections sat at the dedup/merge degeneracy threshold — the climb-strobe class. A w=0
|
||||
/// vertex can never survive ClipToRegion into its divide (a nonzero direction fails at least one
|
||||
/// edge test of any BOUNDED convex region), so no divide-by-zero path exists; the measure-zero
|
||||
/// corner case is guarded in ClipToRegion. Matches polyClipFinish part 1: clip pass runs only
|
||||
/// when some vertex has w < 0; <3 survivors → reject (empty).</para></summary>
|
||||
public static Vector4[] ProjectToClip(IReadOnlyList<Vector3> localPoly, Matrix4x4 cellToWorld, Matrix4x4 viewProj)
|
||||
{
|
||||
if (localPoly == null || localPoly.Count < 3) return System.Array.Empty<Vector4>();
|
||||
|
||||
Matrix4x4 m = cellToWorld * viewProj;
|
||||
var clip = new List<Vector4>(localPoly.Count);
|
||||
bool anyBehind = false;
|
||||
foreach (var lp in localPoly)
|
||||
clip.Add(Vector4.Transform(new Vector4(lp, 1f), m));
|
||||
{
|
||||
var v = Vector4.Transform(new Vector4(lp, 1f), m);
|
||||
if (v.W < 0f) anyBehind = true;
|
||||
clip.Add(v);
|
||||
}
|
||||
|
||||
// Eye plane ONLY (w >= EyePlaneW), in clip space, homogeneous — no side planes, no divide.
|
||||
// Retail's polyClipFinish clips at w = 0; EyePlaneW is a hair above 0 so the later divide in
|
||||
// ClipToRegion never hits the w = 0 singularity. Everything in front of the eye is kept,
|
||||
// including a portal the camera is standing in (it covers the screen) — the screen bound comes
|
||||
// from ClipToRegion against the view region, not from a near plane here.
|
||||
clip = ClipPlane(clip, v => v.W - EyePlaneW);
|
||||
// polyClipFinish part 1 (0x006b6d5d): the W pass runs only when some vertex sits behind
|
||||
// the eye plane (w < 0); an all-in-front polygon passes through untouched (and an
|
||||
// all-behind one clips to empty inside the pass).
|
||||
if (anyBehind)
|
||||
clip = ClipPlane(clip, v => v.W);
|
||||
return clip.Count >= 3 ? clip.ToArray() : System.Array.Empty<Vector4>();
|
||||
}
|
||||
|
||||
|
|
@ -123,11 +136,21 @@ public static class PortalProjection
|
|||
// Divide survivors → NDC. They are inside the region now, so |x| ≤ |w| and |y| ≤ |w|: the
|
||||
// divide is bounded by construction (this is why the homogeneous clip avoids the early-divide
|
||||
// blow-up). Normalize to CCW so the result is a valid clip region for the next portal hop.
|
||||
//
|
||||
// W=0 port (2026-06-11): with ProjectToClip clipping at exactly w >= 0, a w == 0 vertex
|
||||
// (a direction) cannot survive the bounded region clip above — a nonzero direction fails at
|
||||
// least one edge's inside test of any bounded convex region — EXCEPT the measure-zero case
|
||||
// of a direction lying exactly on a region corner with d == 0 on the adjoining edges. That
|
||||
// case divides to ±Inf/NaN; treat it as the degenerate knife-edge sliver it is and return
|
||||
// empty (retail's effective result for the same input: a <1 px degenerate region).
|
||||
var ndc = new Vector2[poly.Count];
|
||||
for (int i = 0; i < poly.Count; i++)
|
||||
{
|
||||
float w = poly[i].W;
|
||||
ndc[i] = new Vector2(poly[i].X / w, poly[i].Y / w);
|
||||
var v = new Vector2(poly[i].X / w, poly[i].Y / w);
|
||||
if (!float.IsFinite(v.X) || !float.IsFinite(v.Y))
|
||||
return System.Array.Empty<Vector2>();
|
||||
ndc[i] = v;
|
||||
}
|
||||
|
||||
// T2 (BR-4): retail's post-divide vertex merge — Render::copy_view
|
||||
|
|
@ -227,11 +250,6 @@ public static class PortalProjection
|
|||
if (area2 < 0f) System.Array.Reverse(poly);
|
||||
}
|
||||
|
||||
// Eye plane for the homogeneous clip — a hair above retail's w = 0 so the post-region divide in
|
||||
// ClipToRegion never divides by zero. Far closer than any near plane: a portal the eye is standing
|
||||
// in is kept (it covers the screen), so the cell behind it stays visible.
|
||||
private const float EyePlaneW = 1e-4f;
|
||||
|
||||
// Minimum clip-space w (≈ metres in front of the eye) to keep a vertex. Excludes the eye
|
||||
// (w=0) singularity and the ~5 cm right at it (bounding the perspective divide), but is
|
||||
// INTENTIONALLY far closer than the projection's 1.0 m near plane so a doorway the camera is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue