fix(physics): preserve retail step-down probe state

This commit is contained in:
Erik 2026-07-31 13:24:25 +02:00
parent 1fd5da67b4
commit acec33eca8
5 changed files with 249 additions and 36 deletions

View file

@ -5234,29 +5234,12 @@ public sealed class Transition
// 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
// above may have consumed WalkInterp down to 0 (e.g. on a
// step-up onto a sloped ramp — AdjustSphereToPlane lifted the
// sphere by step_down_amt and ate all the interp). With
// WalkInterp=0, subsequent AdjustSphereToPlane calls' `interp
// >= path.WalkInterp` check fires immediately (0 >= 0) and
// rejects any push-back — so any geometric overlap that needs
// push-out resolution returns false → placement fails → step-up
// returns failure → step_up_slide loop → player stuck.
//
// Retail's CTransition::step_down (acclient_2013_pseudo_c.txt:272952)
// resets walk_interp = 1 at function entry. The placement_insert
// (line 272989-272990) runs after that initial reset; we believe
// placement-insert mode doesn't consume walk_interp the same way
// because it's a "can we fit here" check, not a movement sweep.
//
// This fix is the cellar-up target (issue #98). May also help
// other "step-up onto sloped surface" scenarios.
// Retail resets walk_interp once at step_down entry. The final
// placement consumes the exact value left by the support and
// check-walkable probes; there is no second reset here.
var savedInsert = sp.InsertType;
float winterpBeforePlacement = sp.WalkInterp;
sp.InsertType = InsertType.Placement;
sp.WalkInterp = 1.0f;
var placeState = TransitionalInsert(1, engine);
@ -5453,12 +5436,9 @@ public sealed class Transition
/// TransitionalInsert before re-testing as Placement.
///
/// <para>
/// Returns true if a walkable surface was found within reach (i.e. the
/// sphere can land here). Returns false if:
/// - ObjectInfo.OnWalkable is NOT set (always walkable by convention).
/// - CheckWalkables() already confirmed a walkable (skip the probe).
/// - The downward probe returned OK (meaning: no walkable was found
/// within reach, so we CANNOT land → transitState == OK → return false).
/// Returns true when no support check is required or when a walkable
/// surface is found within reach. Returns false when the downward probe
/// returns OK (no walkable collision was found).
/// </para>
///
/// ACE: Transition.CheckWalkable (Transition.cs:206-235).
@ -5478,7 +5458,13 @@ public sealed class Transition
if (sp.CheckWalkables())
return true;
sp.SaveCheckPos();
// Retail CTransition::check_walkable (0x0050AFF0) uses stack locals
// for this nested probe. Preserve SPHEREPATH's outer backup pair for
// edge_slide if the support check fails.
Vector3 savedCheckPos = sp.CheckPos;
uint savedCheckCellId = sp.CheckCellId;
Vector3 savedBackupCheckPos = sp.BackupCheckPos;
uint savedBackupCheckCellId = sp.BackupCheckCellId;
float stepHeight = oi.StepDownHeight;
var globSphere = sp.GlobalSphere[0];
@ -5496,7 +5482,9 @@ public sealed class Transition
var transitState = TransitionalInsert(1, engine);
sp.CheckWalkable = false;
sp.RestoreCheckPos();
sp.SetCheckPos(savedCheckPos, savedCheckCellId);
sp.BackupCheckPos = savedBackupCheckPos;
sp.BackupCheckCellId = savedBackupCheckCellId;
// ACE returns (transitState != OK) — i.e. true when we DID find a
// walkable (collision probe returned Adjusted/Collided).