fix(R4): #161 remote landing stuck in falling pose - apply-pass params decode
Retail's apply_interpreted_movement (0x00528600) does NOT dispatch with ctor-default MovementParameters: the BN decomp smears the bitfield store into the mush expression at raw 305778 - (word & 0x37ff) | cancelMoveTo<<15 | disableJump<<17 - which CLEARS SetHoldKey/ModifyInterpretedState/ CancelMoveTo. ACE MotionInterp.cs:444-449 confirms independently. Three legs fixed, all retail decode, no adaptations added: 1. ApplyInterpretedMovement now builds the pass params retail actually uses (ModifyInterpretedState=false is load-bearing): no dispatch in the pass writes InterpretedState, so the airborne Falling substitution PRESERVES the wire's forward command and HitGround's re-apply dispatches it - the motion table plays the Falling->X landing link. The W6 entry-cache (built on the wrong "retail self-heals via hoisted registers" theory) is deleted; live reads are retail semantics. Raw arg3 decoded as DisableJumpDuringLink -> every (N,0) caller means allowJump=true; all 9 caller polarities fixed. copy_movement_from's current_style copy (raw 0051e757) added to the UM flat-copy. 2. Both GameWindow landing blocks cleared the Gravity state bit BEFORE Motion.HitGround(), whose verbatim state&0x400 gate then no-opped the whole retail re-apply; the UP-driven landing block never called HitGround at all. Both now run HitGround (minterp then moveto, MovementManager::HitGround 0x00524300 order) with Gravity still set, then do the DR bookkeeping clear (register row AP-81 added for the remaining flag dance, retire in R6). 3. K-fix17's forced SetCycle (both copies) deleted: it executed every landing but read the leg-1-clobbered ForwardCommand (0x40000015) and re-set the very Falling cycle it meant to clear. Tests: new HitGround_AfterFall_RedispatchesPreservedForward_ExitsFalling lifecycle pin; AirborneBody state assertion flipped (it had pinned the bug value). Suite 3,964 green incl. the 183-case retail-observer trace. Filed #164 (action-replay Autonomous bit, no current consumer). Awaiting live verify: stand-still landing must exit the falling pose with zero wire input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
790938392f
commit
b1cf01029a
6 changed files with 280 additions and 199 deletions
|
|
@ -1749,11 +1749,14 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
bool isThePlayer = WeenieObj is null || WeenieObj.IsThePlayer();
|
||||
if (isThePlayer && PhysicsObj.LastMoveWasAutonomous)
|
||||
{
|
||||
apply_raw_movement(cancelMoveTo: false, allowJump: false);
|
||||
// Raw (0, 0): arg3 is DisableJumpDuringLink → allowJump: true
|
||||
// (#161 polarity decode; consumed since the apply-pass params
|
||||
// became real).
|
||||
apply_raw_movement(cancelMoveTo: false, allowJump: true);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: false, allowJump: false);
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: false, allowJump: true);
|
||||
}
|
||||
|
||||
// ── FUN_00528920 / FUN_00528970 — SetWeenieObject / SetPhysicsObject ───────
|
||||
|
|
@ -1800,11 +1803,13 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
bool isThePlayer = weenie is null || weenie.IsThePlayer();
|
||||
if (isThePlayer && PhysicsObj.LastMoveWasAutonomous)
|
||||
{
|
||||
apply_raw_movement(cancelMoveTo: true, allowJump: false);
|
||||
// Raw (1, 0): cancelMoveTo set, DisableJumpDuringLink clear →
|
||||
// allowJump: true (#161 polarity decode).
|
||||
apply_raw_movement(cancelMoveTo: true, allowJump: true);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: true, allowJump: false);
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: true, allowJump: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1848,11 +1853,12 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
bool isThePlayer = WeenieObj is null || WeenieObj.IsThePlayer();
|
||||
if (isThePlayer && physicsObj.LastMoveWasAutonomous)
|
||||
{
|
||||
apply_raw_movement(cancelMoveTo: true, allowJump: false);
|
||||
// Raw (1, 0) → allowJump: true (#161 polarity decode).
|
||||
apply_raw_movement(cancelMoveTo: true, allowJump: true);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: true, allowJump: false);
|
||||
ApplyCurrentMovementInterpreted(cancelMoveTo: true, allowJump: true);
|
||||
}
|
||||
|
||||
// ── FUN_00527a50 — jump_charge_is_allowed ─────────────────────────────────
|
||||
|
|
@ -2506,7 +2512,8 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
JumpExtent = 0f;
|
||||
|
||||
RemoveLinkAnimations?.Invoke();
|
||||
apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||
// Raw (0, 0) → allowJump: true (#161 polarity decode).
|
||||
apply_current_movement(cancelMoveTo: false, allowJump: true);
|
||||
}
|
||||
|
||||
// ── FUN_00528ac0 — HitGround ──────────────────────────────────────────────
|
||||
|
|
@ -2551,7 +2558,11 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
return;
|
||||
|
||||
RemoveLinkAnimations?.Invoke();
|
||||
apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||
// Raw (0, 0) → allowJump: true (#161 polarity decode). The
|
||||
// re-apply dispatches the PRESERVED interpreted forward command
|
||||
// (the apply pass never writes it — ModifyInterpretedState=false)
|
||||
// — this IS the falling-pose exit on landing.
|
||||
apply_current_movement(cancelMoveTo: false, allowJump: true);
|
||||
}
|
||||
|
||||
// ── FUN_00528c80 — enter_default_state ────────────────────────────────────
|
||||
|
|
@ -2638,7 +2649,8 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
if (runKeyUp != notCurrentlyRun)
|
||||
{
|
||||
RawState.CurrentHoldKey = holdingRun ? HoldKey.Run : HoldKey.None;
|
||||
apply_current_movement(cancelMoveTo: interrupt, allowJump: false);
|
||||
// Raw (arg3, 0) → allowJump: true (#161 polarity decode).
|
||||
apply_current_movement(cancelMoveTo: interrupt, allowJump: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2694,13 +2706,14 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
if (current == HoldKey.Run)
|
||||
{
|
||||
RawState.CurrentHoldKey = HoldKey.None;
|
||||
apply_current_movement(cancelMoveTo, allowJump: false);
|
||||
// Raw (arg3, 0) → allowJump: true (#161 polarity decode).
|
||||
apply_current_movement(cancelMoveTo, allowJump: true);
|
||||
}
|
||||
}
|
||||
else if (key == HoldKey.Run && current != HoldKey.Run)
|
||||
{
|
||||
RawState.CurrentHoldKey = HoldKey.Run;
|
||||
apply_current_movement(cancelMoveTo, allowJump: false);
|
||||
apply_current_movement(cancelMoveTo, allowJump: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2815,6 +2828,11 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
bool allowJump = MotionAllowsJump(InterpretedState.ForwardCommand) == WeenieError.None;
|
||||
|
||||
// copy_movement_from — flat overwrite, no per-field presence checks.
|
||||
// current_style is the FIRST copied field (raw 0051e757) — it is
|
||||
// what HitGround/LeaveGround re-applies as the style dispatch
|
||||
// (#161: previously omitted, so the landing re-apply read a stale
|
||||
// interpreted style).
|
||||
InterpretedState.CurrentStyle = ims.CurrentStyle;
|
||||
InterpretedState.ForwardCommand = ims.ForwardCommand;
|
||||
InterpretedState.ForwardSpeed = ims.ForwardSpeed;
|
||||
InterpretedState.SideStepCommand = ims.SideStepCommand;
|
||||
|
|
@ -2872,24 +2890,31 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>R3-W4 (closes J11's param-plumbing leg):</b> <paramref name="cancelMoveTo"/>/
|
||||
/// <paramref name="allowJump"/> are retail's <c>arg2</c>/<c>arg3</c> —
|
||||
/// threaded through NOW per the plan (every real caller already has an
|
||||
/// opinion: <see cref="apply_current_movement"/>'s dual-dispatch tail
|
||||
/// passes its own args through unchanged; <see cref="MoveToInterpretedState"/>
|
||||
/// passes <c>(true, motion_allows_jump(OLD forward_command) == 0)</c> per
|
||||
/// the Adjacent finding). Their ONLY retail consumer in this function is
|
||||
/// the garbled tail expression at raw 305778
|
||||
/// (<c>(((arg3&1)<<2)|(arg2&1))<<0xf</c> feeding an
|
||||
/// uninitialized-local byte test before conditionally calling
|
||||
/// <c>InterpretedMotionState::RemoveMotion(0x6500000d)</c> a SECOND time)
|
||||
/// — a BN x87/uninit-local artifact class, not a readable retail
|
||||
/// algorithm. TODO(W5): resolve that tail against a clean decompile (or
|
||||
/// cdb capture) once <c>MovementParameters</c> flows through this
|
||||
/// funnel end to end; until then the params are accepted and preserved
|
||||
/// for signature parity but do not change this function's control flow
|
||||
/// (matches the funnel's existing "no MovementParameters yet" posture,
|
||||
/// e.g. <see cref="DispatchInterpretedMotion"/>'s own TODO(W5)).
|
||||
/// <b>#161 (2026-07-03, closes R3-W4's TODO(W5)):</b> retail builds ONE
|
||||
/// <c>MovementParameters</c> for the whole pass
|
||||
/// (<c>MovementParameters::MovementParameters(&var_2c)</c> @305719)
|
||||
/// and then rewrites its bitfield: the "garbled tail expression" at raw
|
||||
/// 305778 is the BN decompiler smearing that store into its single read
|
||||
/// site — <c>(word & 0x37ff) | (arg2&1)<<15 |
|
||||
/// (arg3&1)<<17</c> clears bits 11/14/15 (<c>SetHoldKey</c> /
|
||||
/// <c>ModifyInterpretedState</c> / <c>CancelMoveTo</c>), re-sets bit 15
|
||||
/// from <c>arg2</c> (= <paramref name="cancelMoveTo"/>) and bit 17 from
|
||||
/// <c>arg3</c> (= <c>DisableJumpDuringLink</c>, so
|
||||
/// <paramref name="allowJump"/> = <c>arg3 == 0</c>). ACE
|
||||
/// MotionInterp.cs:444-449 confirms independently. The load-bearing bit
|
||||
/// is <c>ModifyInterpretedState = false</c>: NO dispatch issued by this
|
||||
/// pass writes <see cref="InterpretedState"/> — the airborne Falling
|
||||
/// substitution leaves the wire's forward command intact, which is what
|
||||
/// lets <see cref="HitGround"/>'s re-apply dispatch the PRESERVED
|
||||
/// pre-fall command (the motion table then plays the Falling→X landing
|
||||
/// link — the falling-pose exit needs no wire input). The previous
|
||||
/// revision dispatched with ctor-default params
|
||||
/// (<c>ModifyInterpretedState = true</c>) — the #161 stuck-falling-pose
|
||||
/// root cause, and the true mechanism behind the W6 "press W and stop
|
||||
/// instantly" regression (the style dispatch could never have clobbered
|
||||
/// forward_command in retail; the entry-cache workaround this body used
|
||||
/// to carry is deleted — with no mid-pass state writes possible, live
|
||||
/// field reads ARE retail's semantics).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void ApplyInterpretedMovement(
|
||||
|
|
@ -2898,67 +2923,61 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
{
|
||||
if (PhysicsObj is null) return;
|
||||
|
||||
// cancelMoveTo/allowJump: accepted for signature parity (see the
|
||||
// TODO(W5) above) — not yet consumed by this function's body.
|
||||
_ = cancelMoveTo;
|
||||
_ = allowJump;
|
||||
|
||||
// ENTRY-CACHE all axis values BEFORE the style dispatch (W6 stop-bug
|
||||
// fix, 2026-07-03). The style dispatch's success path runs
|
||||
// InterpretedMotionState::ApplyMotion(style), whose style branch
|
||||
// resets forward_command to Ready UNCONDITIONALLY (raw 0051ea6c —
|
||||
// verbatim). Retail SELF-HEALS because its compiled apply pass reads
|
||||
// the axis fields into registers before the style call, so the fwd
|
||||
// dispatch re-applies the pre-reset command (proven by our own
|
||||
// 183-case live-retail observer trace: the fwd dispatch carries the
|
||||
// wire's RunForward after the style dispatch on the same UM — the
|
||||
// BN pseudo-C's apparent post-style field reads at 0x528687 are
|
||||
// decompiler rendering of hoisted registers, the same artifact
|
||||
// class as the A1 polarity). A live-field read here dispatched
|
||||
// READY after every style apply, leaving the field permanently
|
||||
// Ready — the "press W and stop instantly" W6 regression.
|
||||
uint entryFwdCmd = InterpretedState.ForwardCommand;
|
||||
float entryFwdSpeed = InterpretedState.ForwardSpeed;
|
||||
uint entrySideCmd = InterpretedState.SideStepCommand;
|
||||
float entrySideSpeed = InterpretedState.SideStepSpeed;
|
||||
uint entryTurnCmd = InterpretedState.TurnCommand;
|
||||
float entryTurnSpeed = InterpretedState.TurnSpeed;
|
||||
|
||||
if (entryFwdCmd == MotionCommand.RunForward)
|
||||
MyRunRate = entryFwdSpeed;
|
||||
|
||||
DispatchInterpretedMotion(currentStyle, 1.0f, sink);
|
||||
|
||||
if (!contact_allows_move(entryFwdCmd))
|
||||
// Retail's rewritten var_2c (raw 305778 decoded; ACE 444-449):
|
||||
// Speed is re-set per axis below, everything else rides the pass.
|
||||
var p = new MovementParameters
|
||||
{
|
||||
DispatchInterpretedMotion(MotionCommand.Falling, 1.0f, sink);
|
||||
SetHoldKey = false,
|
||||
ModifyInterpretedState = false,
|
||||
CancelMoveTo = cancelMoveTo,
|
||||
DisableJumpDuringLink = !allowJump,
|
||||
};
|
||||
|
||||
if (InterpretedState.ForwardCommand == MotionCommand.RunForward)
|
||||
MyRunRate = InterpretedState.ForwardSpeed;
|
||||
|
||||
p.Speed = 1.0f;
|
||||
DoInterpretedMotion(currentStyle, p, sink);
|
||||
|
||||
if (!contact_allows_move(InterpretedState.ForwardCommand))
|
||||
{
|
||||
p.Speed = 1.0f; // raw 305728: var_18_2 = 0x3f800000
|
||||
DoInterpretedMotion(MotionCommand.Falling, p, sink);
|
||||
}
|
||||
else if (StandingLongJump)
|
||||
{
|
||||
DispatchInterpretedMotion(MotionCommand.Ready, 1.0f, sink);
|
||||
DispatchStopInterpretedMotion(MotionCommand.SideStepRight, sink);
|
||||
p.Speed = 1.0f;
|
||||
DoInterpretedMotion(MotionCommand.Ready, p, sink);
|
||||
StopInterpretedMotion(MotionCommand.SideStepRight, p, sink);
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchInterpretedMotion(entryFwdCmd, entryFwdSpeed, sink);
|
||||
if (entrySideCmd == 0)
|
||||
DispatchStopInterpretedMotion(MotionCommand.SideStepRight, sink);
|
||||
p.Speed = InterpretedState.ForwardSpeed;
|
||||
DoInterpretedMotion(InterpretedState.ForwardCommand, p, sink);
|
||||
if (InterpretedState.SideStepCommand == 0)
|
||||
{
|
||||
StopInterpretedMotion(MotionCommand.SideStepRight, p, sink);
|
||||
}
|
||||
else
|
||||
DispatchInterpretedMotion(entrySideCmd, entrySideSpeed, sink);
|
||||
{
|
||||
p.Speed = InterpretedState.SideStepSpeed;
|
||||
DoInterpretedMotion(InterpretedState.SideStepCommand, p, sink);
|
||||
}
|
||||
}
|
||||
|
||||
if (entryTurnCmd != 0)
|
||||
if (InterpretedState.TurnCommand != 0)
|
||||
{
|
||||
DispatchInterpretedMotion(entryTurnCmd, entryTurnSpeed, sink);
|
||||
p.Speed = InterpretedState.TurnSpeed;
|
||||
DoInterpretedMotion(InterpretedState.TurnCommand, p, sink);
|
||||
return; // retail early return — no idle-stop this call
|
||||
}
|
||||
|
||||
// Tail (raw 305766-305786): unconditional StopInterpretedMotion(TurnRight,
|
||||
// params) — the merged StopInterpretedMotion ITSELF performs the
|
||||
// add_to_queue(context, Ready, 0) + RemoveMotion(TurnRight) on
|
||||
// success, so no separate AddToQueue call is needed here anymore
|
||||
// (R3-W5 moves this bookkeeping into the merged function body).
|
||||
DispatchStopInterpretedMotion(MotionCommand.TurnRight, sink);
|
||||
// add_to_queue(context, Ready, 0) on success; with the pass params'
|
||||
// ModifyInterpretedState=false the RemoveMotion(TurnRight) state
|
||||
// clear does NOT fire (retail identical — ACE 497-498).
|
||||
StopInterpretedMotion(MotionCommand.TurnRight, p, sink);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -3263,33 +3282,17 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// R3-W5 internal per-axis dispatch primitive
|
||||
/// (<c>DispatchInterpretedMotion</c>, formerly the S2a funnel's public
|
||||
/// surface). <see cref="ApplyInterpretedMovement"/>'s four
|
||||
/// <c>DoInterpretedMotion</c>-shaped calls (style, forward-or-Falling,
|
||||
/// sidestep, turn) each need to dispatch through an EXPLICIT per-call
|
||||
/// <paramref name="sink"/> (a remote entity's own sink, threaded in from
|
||||
/// <see cref="MoveToInterpretedState"/>) — retail's own
|
||||
/// <c>apply_interpreted_movement</c> calls <c>CMotionInterp::DoInterpretedMotion</c>
|
||||
/// directly (the SAME function
|
||||
/// <see cref="DoInterpretedMotion(uint,MovementParameters,IInterpretedMotionSink?)"/>
|
||||
/// implements) with a fresh local <c>MovementParameters</c> per axis —
|
||||
/// this wraps that shape with a throwaway params object carrying only
|
||||
/// <c>Speed</c> (ctor default for everything else, matching retail's own
|
||||
/// local <c>var_2c</c>).
|
||||
/// R3-W5 internal dispatch primitive (formerly the S2a funnel's public
|
||||
/// surface), now used ONLY by <see cref="MoveToInterpretedState"/>'s
|
||||
/// action-replay loop — retail's action dispatches (raw 305983) use the
|
||||
/// UM apply's ctor-default <c>var_2c</c> (notably
|
||||
/// <c>ModifyInterpretedState = true</c>, so replayed actions DO enter the
|
||||
/// interpreted actions list via <c>InterpretedMotionState::AddAction</c>),
|
||||
/// unlike the apply pass's rewritten params (see
|
||||
/// <see cref="ApplyInterpretedMovement"/>). Known residual gap: retail
|
||||
/// also sets the params' <c>Autonomous</c> bit (0x1000) per action (raw
|
||||
/// 305982) — not yet threaded (tracked in ISSUES, #161 follow-up note).
|
||||
/// </summary>
|
||||
private WeenieError DispatchInterpretedMotion(uint motion, float speed, IInterpretedMotionSink? sink)
|
||||
=> DoInterpretedMotion(motion, new MovementParameters { Speed = speed }, sink);
|
||||
|
||||
/// <summary>
|
||||
/// R3-W5 internal per-axis STOP primitive — the
|
||||
/// <see cref="StopInterpretedMotion(uint,MovementParameters,IInterpretedMotionSink?)"/>
|
||||
/// analogue of <see cref="DispatchInterpretedMotion"/>, used by
|
||||
/// <see cref="ApplyInterpretedMovement"/>'s sidestep-stop/turn-stop tail
|
||||
/// calls (raw 305739/305748/305770 — retail's own
|
||||
/// <c>CPhysicsObj::StopInterpretedMotion</c> calls with a fresh local
|
||||
/// <c>MovementParameters</c>).
|
||||
/// </summary>
|
||||
private WeenieError DispatchStopInterpretedMotion(uint motion, IInterpretedMotionSink? sink)
|
||||
=> StopInterpretedMotion(motion, new MovementParameters(), sink);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue