fix(phys): A6.P4 door — pos_hits_sphere records near-miss polygon
Retail's CPolygon::pos_hits_sphere at
acclient_2013_pseudo_c.txt:322974-322993 records the polygon pointer
(*arg5 = this at line 00539509) on STATIC overlap BEFORE the front-
face cull (dot(N, movement) >= 0 return 0 at line 0053952f). So when
a sphere statically overlaps a wall but is moving parallel/away from
the wall normal, retail returns 0 (no full hit) but the polygon
pointer IS set so Path 5's set_neg_poly_hit dispatch at
acclient_2013_pseudo_c.txt:0053a6ea fires and the outer
transitional_insert loop slides the sphere along the wall.
Pre-fix our PosHitsSphere set hitPoly only when both the static-
overlap AND the front-face cull passed. Near-miss polygons were
dropped → Path 5's `if (hitPoly0 is not null)` branch never fired →
NegPolyHit stayed false → outer loop never slid → inside-out cottage
doors let spheres squeeze through walls they were touching.
The handoff (docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md)
hypothesized swept-sphere intersection + closest-considered-polygon
tracking. Reading the actual retail decomp of pos_hits_sphere AND
polygon_hits_sphere_slow_but_sure (acclient_2013_pseudo_c.txt:322504-
322635) showed both functions are STATIC tests; the motion vector is
used only for the front-face cull. The fix is a one-line reordering.
Adds 3 unit tests in BSPQueryTests covering:
- Sphere overlaps wall + moves parallel → NegPolyHit fires (RED→GREEN)
- Sphere overlaps wall + moves away → NegPolyHit fires (RED→GREEN)
- Sphere overlaps wall + moves into → Slid (regression guard, already
passed)
Verification:
* 3 new Path 5 tests pass.
* Full Core suite: 14 failures with-fix vs 17 failures baseline-no-fix.
The with-fix failure set is a STRICT SUBSET of baseline — zero
regressions. The 14 remaining failures are pre-existing static-leak
flakiness between test classes (documented in CLAUDE.md) and 2 stale-
capture LiveCompare_* document-the-bug tests.
* All handoff "must-stay-green" tests pass:
- Directional_OutsideIn_SouthApproach_BlocksAtSlabSouthFace
- Directional_InsideOut_NorthApproach_BlocksAtSlabNorthFace
- CornerSlide_AlcoveEastToCottageNorth_ShouldBlock
- Geometric_DoorSlabAtSphereHeight_OverlapsInZ
- CellarUpTrajectoryReplayTests.LiveCompare_FirstCap_FixClosesCottageFloorCap
(issue #98 CRITICAL — no regression).
Per CLAUDE.md: needs visual verification at Holtburg cottage door
inside-out off-center (~50 cm) scenario before the A6.P4 phase is
marked complete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2deb539953
commit
3253d841ac
2 changed files with 226 additions and 1 deletions
|
|
@ -177,6 +177,21 @@ public static class BSPQuery
|
|||
/// hits after a sphere has already penetrated past a polygon.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Near-miss recording (A6.P4 door bug fix, 2026-05-25): the
|
||||
/// <paramref name="hitPoly"/> out-pointer is written whenever the sphere
|
||||
/// statically overlaps the polygon — BEFORE the movement check. So when
|
||||
/// the sphere is touching a wall but moving parallel/away, the function
|
||||
/// returns false (cull rejected) but the polygon IS recorded for the
|
||||
/// outer dispatch's <c>NegPolyHit</c> path. This mirrors retail's
|
||||
/// <c>CPolygon::pos_hits_sphere</c> at
|
||||
/// <c>acclient_2013_pseudo_c.txt:322974-322993</c> (<c>*arg5 = this</c>
|
||||
/// at <c>00539509</c> fires on static-overlap, BEFORE the
|
||||
/// <c>dot(N, movement) >= 0 → return 0</c> cull at <c>0053952f</c>).
|
||||
/// Without this ordering Path 5's near-miss dispatch is dead code (the
|
||||
/// inside-out cottage door walkthrough bug).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>ACE: Polygon.cs pos_hits_sphere.</para>
|
||||
/// </summary>
|
||||
private static bool PosHitsSphere(
|
||||
|
|
@ -191,11 +206,15 @@ public static class BSPQuery
|
|||
sphere.Center, sphere.Radius,
|
||||
ref contactPoint);
|
||||
|
||||
// Retail: *arg5 = this is set BEFORE the movement cull when the
|
||||
// static overlap succeeded. Path 5's near-miss dispatch reads this
|
||||
// to fire NegPolyHit when the sphere is touching but moving away.
|
||||
if (hit) hitPoly = poly;
|
||||
|
||||
// ACE: dist = Dot(movement, Plane.Normal); if dist >= 0 return false;
|
||||
float moveDot = Vector3.Dot(movement, poly.Plane.Normal);
|
||||
if (moveDot >= 0f) return false;
|
||||
|
||||
if (hit) hitPoly = poly;
|
||||
return hit;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue