feat(physics): Phase L.2.1+L.2.2 — BSP step-up and rooftop landing
Port CTransition::step_up (Path 5) and SPHEREPATH::set_collide (Path 6) from the retail decomp, turning wall-slides into proper step-up climbs and airborne-to-roof landings. Path 5 (grounded mover hits polygon): - StepSphereUp calls DoStepUp which runs DoStepDown with StepUp=true - DoStepDown now includes the retail Placement validation step (ACE Transition.cs:731-741) — sphere must not be inside solid geometry after finding a contact plane; this correctly blocks the tall-wall case - FindObjCollisions now allocates a local ShadowEntry list per call to prevent "collection modified" exceptions when DoStepUp recurses back through TransitionalInsert → FindObjCollisions - BSPQuery.FindCollisions passes engine through to StepSphereUp Path 6 (airborne mover hits polygon): - SpherePath.SetCollide: saves backup pos, records StepUpNormal, sets WalkInterp=1 — then returns Adjusted so TransitionalInsert retries - SpherePath.StepUpSlide: clears ContactPlane, sets SlidingNormal for the tall-wall fallback - TransitionalInsert Collide branch: re-tests as Placement when ContactPlaneValid; on failure restores backup and returns Collided Test fixes (BSPStepUpTests.cs + BSPStepUpFixtures.cs): - Tests use foot-position convention (CurPos = foot, sphere center = CurPos + (0,0,r)); from/to corrected from sphere-center to foot coords - MakeTestEngine terrainZ param: 0f for grounded tests (keeps Contact state between sub-steps), -50f for airborne/roof tests - to.X adjusted so sub-steps land sphere inside (not exactly touching) the wall, avoiding the EPSILON-shrink false-negative edge case - All 12 BSPStepUp tests now GREEN; full suite 823/823 Retail refs: CTransition::step_up — acclient_2013_pseudo_c.txt:273099 / ACE:746 CTransition::step_down — acclient_2013_pseudo_c.txt:273069 / ACE:710 SPHEREPATH::set_collide — acclient_2013_pseudo_c.txt:321594 / ACE:279 CTransition::transitional_insert Collide — pseudo_c:273193 / ACE:891 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0c29454d0
commit
670f892bd3
4 changed files with 341 additions and 179 deletions
|
|
@ -186,17 +186,27 @@ public class BSPStepUpTests
|
|||
var (root, resolved) = BSPStepUpFixtures.LowStep();
|
||||
const float stepUpHeight = 0.30f; // larger than step (0.25), so step-up succeeds
|
||||
|
||||
float startZ = BSPStepUpFixtures.SphereRadius;
|
||||
var from = new Vector3(0.1f, 0f, startZ);
|
||||
var to = new Vector3(0.7f, 0f, startZ); // crosses the wall at x=0.5
|
||||
// CurPos (foot position) starts at z=0 (on the terrain / BSP floor at z=0).
|
||||
// The sphere center is at CurPos + (0, 0, SphereRadius) = (x, 0, 0.2).
|
||||
// lowPoint = sphere_center - (0,0,r) = (x, 0, 0) → on terrain → contact.
|
||||
var from = new Vector3(0.1f, 0f, 0f);
|
||||
// to.X = 0.6 → offset = (0.5, 0, 0), 3 sub-steps of 0.1667 each.
|
||||
// Step 2: CurPos ≈ (0.433, 0, 0), sphere center x ≈ 0.433.
|
||||
// Wall: dist = 0.5 - 0.433 = 0.067 < rad = 0.198 → HIT Path 5 ✓
|
||||
var to = new Vector3(0.6f, 0f, 0f); // foot stays at z=0, crosses wall at x=0.5
|
||||
|
||||
var t = BSPStepUpFixtures.MakeGroundedTransition(from, to, stepUpHeight);
|
||||
var engine = MakeTestEngine(root, resolved);
|
||||
// terrainZ=0f: terrain at z=0 keeps the step-down probe grounded between
|
||||
// steps, preserving Contact/OnWalkable across the sub-step boundary.
|
||||
var engine = MakeTestEngine(root, resolved, terrainZ: 0f);
|
||||
|
||||
bool ok = t.FindTransitionalPosition(engine);
|
||||
|
||||
// After step-up, the character's Z must be at or above the upper floor + radius.
|
||||
float expectedMinZ = 0.25f + BSPStepUpFixtures.SphereRadius - PhysicsGlobals.EPSILON * 10f;
|
||||
// After step-up, the character's foot (CurPos.Z) must be at or above the
|
||||
// upper floor (z=0.25). CurPos stores the foot origin; the sphere center is
|
||||
// CurPos.Z + SphereRadius. The lower bound is the upper-floor Z minus a
|
||||
// small epsilon to tolerate floating-point rounding in AdjustSphereToPlane.
|
||||
float expectedMinZ = 0.25f - PhysicsGlobals.EPSILON * 10f;
|
||||
Assert.True(t.SpherePath.CurPos.Z >= expectedMinZ,
|
||||
$"Expected Z >= {expectedMinZ:F4} (stepped up to upper floor at z=0.25), " +
|
||||
$"got CurPos.Z = {t.SpherePath.CurPos.Z:F4}. " +
|
||||
|
|
@ -222,12 +232,13 @@ public class BSPStepUpTests
|
|||
var (root, resolved) = BSPStepUpFixtures.TallWall();
|
||||
const float stepUpHeight = 0.04f; // default — cannot scale 5 m wall
|
||||
|
||||
float startZ = BSPStepUpFixtures.SphereRadius;
|
||||
var from = new Vector3(0.1f, 0f, startZ);
|
||||
var to = new Vector3(0.7f, 0f, startZ);
|
||||
// Foot at z=0 (on terrain). Same reasoning as B1.
|
||||
var from = new Vector3(0.1f, 0f, 0f);
|
||||
var to = new Vector3(0.6f, 0f, 0f);
|
||||
|
||||
var t = BSPStepUpFixtures.MakeGroundedTransition(from, to, stepUpHeight);
|
||||
var engine = MakeTestEngine(root, resolved);
|
||||
// terrainZ=0f: keep grounded between steps (same as B1).
|
||||
var engine = MakeTestEngine(root, resolved, terrainZ: 0f);
|
||||
|
||||
t.FindTransitionalPosition(engine);
|
||||
|
||||
|
|
@ -268,12 +279,13 @@ public class BSPStepUpTests
|
|||
|
||||
var localSphere = new DatReaderWriter.Types.Sphere { Origin = checkPos, Radius = r };
|
||||
|
||||
// NOTE: After L.2.1 this call gains an optional PhysicsEngine
|
||||
// parameter. Until then, the step-up flag is set but DoStepDown
|
||||
// cannot recurse (returns Slid). After L.2.1 result should be OK.
|
||||
// Pass engine so Path 5 can call DoStepUp → DoStepDown (L.2.1).
|
||||
// Without engine the fallback wall-slide would return Slid.
|
||||
var engine = MakeTestEngine(root, resolved);
|
||||
|
||||
var result = BSPQuery.FindCollisions(
|
||||
root, resolved, t, localSphere, null,
|
||||
currPos, Vector3.UnitZ, 1.0f);
|
||||
currPos, Vector3.UnitZ, 1.0f, Quaternion.Identity, engine);
|
||||
|
||||
// After L.2.1 this assertion flips from failing (Slid) to passing.
|
||||
Assert.NotEqual(TransitionState.Slid, result);
|
||||
|
|
@ -349,11 +361,17 @@ public class BSPStepUpTests
|
|||
|
||||
float roofZ = 3f;
|
||||
float r = BSPStepUpFixtures.SphereRadius;
|
||||
var from = new Vector3(0f, 0f, roofZ + r + 0.1f);
|
||||
var to = new Vector3(0f, 0f, roofZ + r - 0.05f); // sphere foot at z~3.0
|
||||
// CurPos = foot position. Sphere center = CurPos + (0,0,r).
|
||||
// from: foot at z = roofZ - r + 0.3f → sphere center at roofZ + 0.3 = 3.3 (above roof)
|
||||
// to: foot at z = roofZ - r - 0.05f → sphere center at roofZ - 0.05 = 2.95 (into roof by 0.05)
|
||||
// Roof polygon at z=roofZ, normal=+Z: dist = sphere_center.z - roofZ.
|
||||
// At to: dist = -0.05; |dist| = 0.05 < rad=0.198 → roof hit ✓
|
||||
var from = new Vector3(0f, 0f, roofZ - r + 0.3f);
|
||||
var to = new Vector3(0f, 0f, roofZ - r - 0.05f); // sphere bottom at z ≈ 2.95 (into roof)
|
||||
|
||||
var t = BSPStepUpFixtures.MakeAirborneTransition(from, to);
|
||||
var engine = MakeTestEngine(root, resolved);
|
||||
// terrainZ=-50f: airborne mover — terrain must not interfere with roof landing.
|
||||
var engine = MakeTestEngine(root, resolved, terrainZ: -50f);
|
||||
|
||||
t.FindTransitionalPosition(engine);
|
||||
|
||||
|
|
@ -417,22 +435,24 @@ public class BSPStepUpTests
|
|||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Build a <see cref="PhysicsEngine"/> that serves one synthetic BSP object
|
||||
/// without any interfering terrain. The terrain is set 50 m underground
|
||||
/// so it never fires during test geometry at z ≥ 0.
|
||||
/// Build a <see cref="PhysicsEngine"/> that serves one synthetic BSP object.
|
||||
/// <paramref name="terrainZ"/> sets every terrain sample to the given height.
|
||||
/// Use 0f for grounded tests (terrain flush with the BSP floor at z=0, so the
|
||||
/// step-down probe finds ground and keeps Contact/OnWalkable set between steps).
|
||||
/// Use -50f for tests where terrain must never interfere (airborne / roof landing).
|
||||
/// </summary>
|
||||
private static PhysicsEngine MakeTestEngine(
|
||||
PhysicsBSPNode root,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
Vector3? objectPosition = null)
|
||||
Vector3? objectPosition = null,
|
||||
float terrainZ = 0f)
|
||||
{
|
||||
const uint LandblockId = 0xA9B4FFFFu;
|
||||
const uint SyntheticGfxId = 0xDEADBEEFu;
|
||||
|
||||
// Terrain 50 m underground so FindEnvCollisions never fires push-ups.
|
||||
var heights = new byte[81]; // all zero → uses index 0 from heightTable
|
||||
var heightTab = new float[256];
|
||||
for (int i = 0; i < 256; i++) heightTab[i] = -50f;
|
||||
for (int i = 0; i < 256; i++) heightTab[i] = terrainZ;
|
||||
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue