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
|
|
@ -0,0 +1,98 @@
|
|||
# ACRender::polyClipFinish — W=0 eye-plane clip pseudocode (the knife-edge port)
|
||||
|
||||
**Source:** `ACRender::polyClipFinish` at `0x006b6d00`,
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt:702749-702988`.
|
||||
Read 2026-06-11 for the knife-edge in-plane portal clip port
|
||||
(handoff `docs/research/2026-06-11-tower-stairs-fundamental-handoff.md` §5).
|
||||
|
||||
## Signature (reconstructed)
|
||||
|
||||
```
|
||||
polyClipFinish(view_vertex** inVerts, // arg1 — homogeneous clip-space verts (x,y,z,w)
|
||||
int inCount, // arg2
|
||||
Vec2Dscreen** outVerts, // arg3 — output vertex pointers
|
||||
int* outCount, // arg4
|
||||
int planeMask) // arg5 — per-edge skip mask (bit set = poly already
|
||||
// fully inside that portal_view edge)
|
||||
```
|
||||
|
||||
## Part 1 — the W=0 eye-plane pass (0x006b6d5d–0x006b6f12)
|
||||
|
||||
```
|
||||
scan = inCount - 1
|
||||
while scan >= 0: # walk verts from the END
|
||||
if inVerts[scan].w < 0: break # found a vertex BEHIND the eye plane → must clip
|
||||
scan -= 1
|
||||
if scan < 0: goto edge_clips # all w >= 0 → skip the W pass entirely
|
||||
|
||||
# homogeneous Sutherland-Hodgman against w = 0, intersections EMITTED:
|
||||
out = []
|
||||
prev = inVerts[0]; prevIn = (prev.w >= 0)
|
||||
for cur in inVerts[last..0]: # retail iterates indices descending
|
||||
curIn = (cur.w >= 0)
|
||||
if curIn != prevIn:
|
||||
t = prev.w / (prev.w - cur.w) # 0x006b6ea0: w0 / (w0 - w1)
|
||||
emit(prev + t*(cur - prev)) # interpolates x, y, z, w → lands at w == 0 exactly
|
||||
if curIn: emit(cur)
|
||||
prev, prevIn = cur, curIn
|
||||
|
||||
if len(out) < 3: return # 0x006b6f00: reject — fewer than 3 survivors
|
||||
inVerts = out # ping-pong to tempPtBuf
|
||||
```
|
||||
|
||||
**x87 flag-decode note** (the BN polarity trap, [[feedback_bn_decomp_field_names]]):
|
||||
the scan loop's `test ah, 0x5` (C0|C2) breaks on **w < 0**, NOT w ≥ 0. Decoded by
|
||||
case analysis: the all-behind polygon must reach the W pass and clip to empty
|
||||
(reject), and the common all-in-front polygon must skip the pass — only the
|
||||
break-on-negative decode yields both. The inside predicate in the clip pass
|
||||
(`test ah, 0x41`, C0|C3) is **inside ⇔ w ≥ 0** (emit-on-sign-change with
|
||||
`t = w0/(w0−w1)` confirms: t∈[0,1] requires opposite signs).
|
||||
|
||||
## Part 2 — portal_view edge clips (0x006b6d82–0x006b7030)
|
||||
|
||||
```
|
||||
for each portal_view edge (vertex pair), mask-gated (planeMask bit set → skip):
|
||||
# homogeneous 2D edge function for vertex P against edge (a → b):
|
||||
# side(P) = (P.x − a.x·P.w)·(b.y − a.y) − (P.y − a.y·P.w)·(b.x − a.x)
|
||||
# (0x006b6e05) — linear in (x, y, w): valid for w = 0 verts (directions).
|
||||
Sutherland-Hodgman with intersection emission (t = s0/(s0 − s1), all 4 comps)
|
||||
if survivors < 3: return # 0x006b6fe1
|
||||
*outCount = survivors # 0x006b7006
|
||||
```
|
||||
|
||||
## The load-bearing semantics for acdream
|
||||
|
||||
1. **Clip at w ≥ 0 EXACTLY** — boundary intersections land at w == 0. A w=0
|
||||
vertex is a homogeneous DIRECTION; the polygon containing it represents the
|
||||
unbounded screen region extending toward that direction. This is what makes
|
||||
an eye-crossing portal (climbing through a stair opening) produce the
|
||||
correct large half-region instead of a bounded sliver:
|
||||
- At `w ≥ ε` (our old `EyePlaneW = 1e-4`), boundary verts are finite NDC
|
||||
points ~1e4 units out along the portal-plane horizon line; the polygon's
|
||||
screen intersection still APPROXIMATES the half-region, but the divide
|
||||
and the dedup/merge operate on degenerate near-collinear coordinates.
|
||||
- At `w = 0`, the edge functions stay exact (linear in homogeneous coords)
|
||||
and no divide ever touches a w=0 vertex (see invariant below).
|
||||
2. **A w=0 vertex can never survive the region clip into the divide** when the
|
||||
clip region is BOUNDED: for a bounded convex CCW region the edge directions
|
||||
wrap 360°, so a nonzero direction fails at least one edge's inside test.
|
||||
Our regions are always bounded (FullScreenQuad and its descendants), so the
|
||||
post-clip divide is safe by construction. The measure-zero exception
|
||||
(direction exactly on a region corner) is guarded by a non-finite check
|
||||
that returns empty — identical net behavior to retail's degenerate sliver.
|
||||
3. **Empty is a verdict, not an error.** `< 3 survivors → reject` at every
|
||||
stage; retail has NO eye-in-opening rescue anywhere in this path. The
|
||||
acdream `EyeInsidePortalOpening` rescue was the documented compensation for
|
||||
the `EyePlaneW = 1e-4` divergence (T2 ledger) and is deleted with this port.
|
||||
|
||||
## What is NOT ported here
|
||||
|
||||
- `cdstW = 0.000199999995` (pinned at `0x007247d5`) — consumed elsewhere
|
||||
(its consumer is still unmapped; `landPolysDraw` 0x006b7040 uses the same
|
||||
0.0002 inline for plane side tests). `PortalSideEpsilon = 0.01` stays as the
|
||||
documented root-lag tolerance (T2 refutation: retail's 0.0002 needs
|
||||
eye-exact viewer-cell tracking first).
|
||||
- The `planeMask` per-edge skip — a perf short-circuit; our ClipToRegion
|
||||
clips against every region edge unconditionally.
|
||||
- Retail's descending vertex iteration order — Sutherland-Hodgman output is
|
||||
order-invariant up to rotation; we keep ascending.
|
||||
Loading…
Add table
Add a link
Reference in a new issue