fix(physics): restore retail step-down placement validation

This commit is contained in:
Erik 2026-07-31 13:13:09 +02:00
parent 4fbd93ecdb
commit 1fd5da67b4
4 changed files with 333 additions and 38 deletions

View file

@ -2050,18 +2050,13 @@ public sealed class Transition
stepDownHeight);
stepDownHeight = probeHeight;
// L.2.3h (2026-04-29): pass runPlacement=false. This
// branch's job is to maintain ground contact during normal
// movement (e.g., walking over small bumps or near walls).
// The Placement check inside DoStepDown is too strict for
// this use — minor wall overlap from a prior wall-slide
// would fail Placement and trigger the L.2.3e edge-block,
// leaving the player stuck near walls. DoStepUp still runs
// Placement for the step-UP-through-walls protection.
// Retail always finishes a successful step-down with a
// PLACEMENT_INSERT. That final pass validates that the
// supported candidate can actually contain the mover.
bool steppedDown = false;
for (int probe = 0; probe < probeCount; probe++)
{
if (DoStepDown(stepDownHeight, zVal, engine, runPlacement: false))
if (DoStepDown(stepDownHeight, zVal, engine))
{
steppedDown = true;
break;
@ -2326,7 +2321,7 @@ public sealed class Transition
Vector3 backToCurrent = sp.GlobalCurrCenter[0].Origin - sp.GlobalSphere[0].Origin;
sp.AddOffsetToCheckPos(backToCurrent);
_ = DoStepDown(stepDownHeight, zVal, engine, runPlacement: false);
_ = DoStepDown(stepDownHeight, zVal, engine);
ci.ContactPlaneValid = false;
ci.ContactPlaneIsWater = false;
@ -5140,8 +5135,7 @@ public sealed class Transition
/// Ported from pseudocode section 5 (StepDown).
/// ACE: Transition.StepDown(float stepDownHeight, float zVal).
/// </summary>
private bool DoStepDown(float stepDownHeight, float walkableZ, PhysicsEngine engine,
bool runPlacement = true)
private bool DoStepDown(float stepDownHeight, float walkableZ, PhysicsEngine engine)
{
var sp = SpherePath;
@ -5156,7 +5150,7 @@ public sealed class Transition
PhysicsDiagnostics.LogStepWalk(
"stepdown-enter", -1, 0, sp, CollisionInfo, ObjectInfo,
Vector3.Zero, Vector3.Zero,
detail: $"height={stepDownHeight:F4} walkableZ={walkableZ:F4} runPlacement={runPlacement}");
detail: $"height={stepDownHeight:F4} walkableZ={walkableZ:F4}");
}
// If NOT in step-up mode, apply the downward offset.
@ -5170,7 +5164,7 @@ public sealed class Transition
PhysicsDiagnostics.LogStepWalk(
"stepdown-after-offset", -1, 0, sp, CollisionInfo, ObjectInfo,
downOffset, downOffset,
detail: $"height={stepDownHeight:F4} walkableZ={walkableZ:F4} runPlacement={runPlacement}");
detail: $"height={stepDownHeight:F4} walkableZ={walkableZ:F4}");
}
}
@ -5183,7 +5177,7 @@ public sealed class Transition
"stepdown-after-insert", -1, 0, sp, CollisionInfo, ObjectInfo,
Vector3.Zero, Vector3.Zero,
transitState,
$"height={stepDownHeight:F4} walkableZ={walkableZ:F4} runPlacement={runPlacement}");
$"height={stepDownHeight:F4} walkableZ={walkableZ:F4}");
}
sp.StepDown = false;
@ -5234,23 +5228,11 @@ public sealed class Transition
return false;
}
// L.2.3h (2026-04-29): Placement validation is for the
// DoStepUp use case (prevents climbing through walls by
// stepping up onto ground beyond a tall wall). For the
// "maintain contact during normal movement" use case (called
// from TransitionalInsert's contact-recovery branch), the
// Placement check is over-strict — slight wall overlap from
// a prior wall-slide makes Placement reject, then the caller
// returns Collided (L.2.3e) and the player gets stuck near
// walls without ever touching them.
//
// ACE Transition.cs:731-741 runs Placement here unconditionally,
// but ACE's pre-step-down state is cleaner — we have residual
// wall-slide artifacts that make Placement misfire.
if (!runPlacement)
return true;
// Placement validation: can we actually stand here?
// Retail CTransition::step_down (0x0050B2A0) always finishes a
// successful transitional support probe with PLACEMENT_INSERT.
// This rejects candidates that found support while still
// overlapping solid geometry, for both ordinary contact
// maintenance and StepUp.
//
// A6.P3 slice 4 (2026-05-22) — reset WalkInterp to 1.0 before
// the placement_insert. The prior TransitionalInsert(5) probe
@ -5324,12 +5306,18 @@ public sealed class Transition
"stepdown-reject", -1, 0, sp, CollisionInfo, ObjectInfo,
Vector3.Zero, Vector3.Zero,
transitState,
$"height={stepDownHeight:F4} walkableZ={walkableZ:F4} runPlacement={runPlacement}");
$"height={stepDownHeight:F4} walkableZ={walkableZ:F4}");
}
return false;
}
internal bool DoStepDownForTest(
float stepDownHeight,
float walkableZ,
PhysicsEngine engine)
=> DoStepDown(stepDownHeight, walkableZ, engine);
// -----------------------------------------------------------------------
// Step-up
// -----------------------------------------------------------------------