fix(p2): Path 5 near-miss = retail num_sphere>1 gate (fixes B1 step-up wedge)
The 2026-06-03 handoff localized the failing Core tests to the BSP Path 5
step-up CLIMB (find_walkable/step_sphere_down). An ITestOutputHelper capture
of B1 disproved that: the climb code is correct (matches ACE
Polygon.adjust_sphere_to_plane / BSPTree.step_sphere_down exactly). The real
bug is the A6.P4 near-miss dispatch in FindCollisions' Path 5 (Contact
branch), which diverged from retail three ways:
1. Recorded a near-miss NegPolyHit UNCONDITIONALLY. Retail gates both
set_neg_poly_hit calls behind `if (num_sphere > 1)`
(acclient_2013_pseudo_c.txt:323852).
2. Checked the foot sphere's near-miss before the head's. Retail checks
the head (sphere1) first.
3. Mapped foot->neg_step_up=false / head->true. Retail maps head(index 0)
->false (slide), foot(index 1)->true (step-up), per
SPHEREPATH::set_neg_poly_hit (:323279, neg_step_up = arg2).
For B1's single foot sphere, the spurious near-miss -> outer loop
`!NegStepUp -> SetCollisionNormal + Collided` -> revert: the grounded mover
wedged at x=0.1 and never advanced to the wall to step up. With the verbatim
gate, a single-sphere near-miss records nothing, the sphere advances,
full-hits the wall, and step_sphere_up climbs the 0.25 m step (verified via
probe capture: foot ends at (0.6, 0, 0.25)).
The Holtburg cottage door still blocks faithfully (door slab (0,-1,0) normal,
stops in front of the door) when the scenario has a real floor — confirmed
this change does not regress the door.
The two BSPQueryTests Path5 near-miss tests used a single sphere (the very
non-retail assumption that caused this wedge); converted to the production
2-sphere shape where the head sphere records the near-miss, matching retail.
Core 1312 pass / 4 fail (the 4 pre-existing: 3 door documents-the-bug + D4
airborne, none regressed here); App 177 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
82045805fd
commit
abbd7615ee
2 changed files with 70 additions and 35 deletions
|
|
@ -592,9 +592,15 @@ public class BSPQueryTests
|
|||
// returns 0 because dpMove >= 0. But the polygon pointer IS recorded
|
||||
// (line 00539509 `*arg5 = this` fires when static-overlap result != 0).
|
||||
//
|
||||
// Expected: Path 5 (Contact branch) sees hitPoly0 != null but hit0 ==
|
||||
// false → NegPolyHitDispatch fires → path.NegPolyHit = true → state
|
||||
// returns OK.
|
||||
// Retail records the near-miss ONLY when num_sphere > 1 (a head sphere
|
||||
// exists) — acclient_2013_pseudo_c.txt:323852-323879. So this is a
|
||||
// TWO-sphere mover (foot + head). Both spheres near-miss; retail checks
|
||||
// the HEAD (sphere1) first → set_neg_poly_hit(0,..) → neg_step_up=FALSE
|
||||
// (slide). Expected: hit1 == false but hitPoly1 != null →
|
||||
// NegPolyHitDispatch fires → path.NegPolyHit = true, NegStepUp = false,
|
||||
// state returns OK. (A single-sphere mover records NOTHING here — that
|
||||
// retail gate is what un-wedged the B1 grounded step-up; see
|
||||
// BSPStepUpTests.B1.)
|
||||
var (root, resolved) = BuildSingleWallBsp();
|
||||
|
||||
var transition = new Transition();
|
||||
|
|
@ -607,6 +613,12 @@ public class BSPQueryTests
|
|||
Origin = new Vector3(0f, 0.3f, 0f),
|
||||
Radius = 0.48f,
|
||||
};
|
||||
// Head sphere — also statically overlaps the wall (Z within poly range).
|
||||
var localSphere1 = new Sphere
|
||||
{
|
||||
Origin = new Vector3(0f, 0.3f, 1.0f),
|
||||
Radius = 0.48f,
|
||||
};
|
||||
|
||||
// localCurrCenter: previous sphere center. movement = current - curr.
|
||||
// Move +X by 0.05 m (small tick step parallel to the wall).
|
||||
|
|
@ -617,7 +629,7 @@ public class BSPQueryTests
|
|||
resolved,
|
||||
transition,
|
||||
localSphere,
|
||||
localSphere1: null,
|
||||
localSphere1: localSphere1,
|
||||
localCurrCenter: localCurrCenter,
|
||||
localSpaceZ: Vector3.UnitZ,
|
||||
scale: 1.0f,
|
||||
|
|
@ -639,7 +651,10 @@ public class BSPQueryTests
|
|||
{
|
||||
// Same overlap geometry, but motion is AWAY from the wall (+Y).
|
||||
// moveDot = dot((0,+1,0), (0,+1,0)) = +1 > 0 → cull rejects.
|
||||
// Static overlap is still true, so retail records the polygon.
|
||||
// Static overlap is still true, so retail records the polygon — but
|
||||
// only because this is a TWO-sphere mover (num_sphere > 1 gate,
|
||||
// acclient_2013_pseudo_c.txt:323852). The head sphere's near-miss
|
||||
// records the NegPolyHit (neg_step_up=FALSE).
|
||||
var (root, resolved) = BuildSingleWallBsp();
|
||||
|
||||
var transition = new Transition();
|
||||
|
|
@ -651,6 +666,11 @@ public class BSPQueryTests
|
|||
Origin = new Vector3(0f, 0.3f, 0f),
|
||||
Radius = 0.48f,
|
||||
};
|
||||
var localSphere1 = new Sphere
|
||||
{
|
||||
Origin = new Vector3(0f, 0.3f, 1.0f),
|
||||
Radius = 0.48f,
|
||||
};
|
||||
var localCurrCenter = localSphere.Origin - new Vector3(0f, 0.05f, 0f);
|
||||
|
||||
var state = BSPQuery.FindCollisions(
|
||||
|
|
@ -658,7 +678,7 @@ public class BSPQueryTests
|
|||
resolved,
|
||||
transition,
|
||||
localSphere,
|
||||
localSphere1: null,
|
||||
localSphere1: localSphere1,
|
||||
localCurrCenter: localCurrCenter,
|
||||
localSpaceZ: Vector3.UnitZ,
|
||||
scale: 1.0f,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue