feat(physics): MotionInterpreter.GetMaxSpeed for InterpolationManager (L.3.1 Task 2)
Ports retail's CMotionInterp::get_max_speed (0x00527cb0). Returns motion-table-derived max speed (m/s) for InterpretedState.ForwardCommand: - RunForward: RunAnimSpeed (4.0) × (InqRunRate ?? MyRunRate) - WalkForward: WalkAnimSpeed (3.12) - WalkBackward: WalkAnimSpeed × 0.65 (BackwardsFactor from adjust_motion @ 0x00528010) - otherwise: 0 Decomp note: Binary Ninja emits a spurious void return for x87 FPU-returning functions; the actual float return is confirmed by both callers (StickyManager::adjust_offset @ 0x00555430, InterpolationManager::AdjustOffset @ 0x00555d52) which multiply the result by 2.0 to produce a catch-up speed in m/s. The per-command switch is consistent with get_state_velocity (0x00527d50) which uses the same constants. Used by InterpolationManager.AdjustOffset in Task 5 as 2 × GetMaxSpeed(). Until Task 5 wires it, the method is unused — covered by 4 unit tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
927636ec77
commit
9c5634af17
2 changed files with 106 additions and 0 deletions
|
|
@ -932,6 +932,56 @@ public sealed class MotionInterpreter
|
|||
apply_current_movement(cancelMoveTo: false, allowJump: true);
|
||||
}
|
||||
|
||||
// ── CMotionInterp::get_max_speed (0x00527cb0) ─────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Return the motion-table-derived max speed (m/s) for the current
|
||||
/// <see cref="InterpretedMotionState.ForwardCommand"/>.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Retail reference (named-retail, 0x00527cb0):</b>
|
||||
/// <c>CMotionInterp::get_max_speed</c> fetches the run rate via
|
||||
/// <c>InqRunRate</c> (or falls back to <c>my_run_rate</c>) and returns
|
||||
/// the result as a float from the x87 FPU stack (ST0). The Binary Ninja
|
||||
/// decompiler emits a spurious <c>void</c> return type for x87-returning
|
||||
/// functions — the actual return value is confirmed by the two callers:
|
||||
/// <c>StickyManager::adjust_offset</c> (0x00555430) and
|
||||
/// <c>InterpolationManager::AdjustOffset</c> (0x00555d52), both of which
|
||||
/// multiply the result by 2.0 to produce a catch-up speed in m/s. With a
|
||||
/// run rate of ~1.0 the catch-up would be 2.0 m/s — far too slow — so the
|
||||
/// function must return the actual velocity (m/s), not a bare rate.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The per-command switch mirrors <c>get_state_velocity</c>
|
||||
/// (0x00527d50), which uses the same constants and the same
|
||||
/// RunForward / WalkForward / WalkBackward branches, and the
|
||||
/// <c>adjust_motion</c> (0x00527c0e) <c>BackwardsFactor = 0.65</c>
|
||||
/// scaling confirmed at address 0x00528010.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Used by <c>InterpolationManager.AdjustOffset</c> in L.3 Task 5
|
||||
/// as <c>2 × GetMaxSpeed()</c> catch-up speed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public float GetMaxSpeed()
|
||||
{
|
||||
// Resolve current run rate: prefer WeenieObj.InqRunRate, fall back to MyRunRate.
|
||||
// Mirrors the InqRunRate query at the top of CMotionInterp::get_max_speed.
|
||||
float rate = MyRunRate;
|
||||
if (WeenieObj is not null && WeenieObj.InqRunRate(out float queried))
|
||||
rate = queried;
|
||||
|
||||
return InterpretedState.ForwardCommand switch
|
||||
{
|
||||
MotionCommand.RunForward => RunAnimSpeed * rate,
|
||||
MotionCommand.WalkForward => WalkAnimSpeed,
|
||||
MotionCommand.WalkBackward => WalkAnimSpeed * 0.65f, // BackwardsFactor @ adjust_motion 0x00528010
|
||||
_ => 0f, // idle / non-locomotion
|
||||
};
|
||||
}
|
||||
|
||||
// ── private helper ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -817,4 +817,60 @@ public sealed class MotionInterpreterTests
|
|||
var vel = mi.get_state_velocity();
|
||||
Assert.Equal(4.0f * 2.375f, vel.Y, precision: 2);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GetMaxSpeed (CMotionInterp::get_max_speed @ 0x00527cb0)
|
||||
// L.3.1 Task 2 — InterpolationManager catch-up speed source
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public void GetMaxSpeed_RunForward_ReturnsRunAnimSpeedTimesRunRate()
|
||||
{
|
||||
// Retail: get_max_speed returns run rate from InqRunRate; callers
|
||||
// multiply by 2 to get catch-up speed. For RunForward the per-m/s
|
||||
// speed is RunAnimSpeed × rate = 4.0 × 1.5 = 6.0.
|
||||
var weenie = new FakeWeenie { RunRate = 1.5f };
|
||||
var interp = MakeInterp(weenie: weenie);
|
||||
interp.InterpretedState.ForwardCommand = MotionCommand.RunForward;
|
||||
|
||||
float speed = interp.GetMaxSpeed();
|
||||
|
||||
Assert.Equal(MotionInterpreter.RunAnimSpeed * 1.5f, speed, precision: 4); // 6.0
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxSpeed_WalkForward_ReturnsWalkAnimSpeed()
|
||||
{
|
||||
// WalkForward max speed is always WalkAnimSpeed (3.12) — no run-rate scaling.
|
||||
var interp = MakeInterp();
|
||||
interp.InterpretedState.ForwardCommand = MotionCommand.WalkForward;
|
||||
|
||||
float speed = interp.GetMaxSpeed();
|
||||
|
||||
Assert.Equal(MotionInterpreter.WalkAnimSpeed, speed, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxSpeed_WalkBackward_ReturnsWalkAnimSpeedTimesBackwardsFactor()
|
||||
{
|
||||
// BackwardsFactor = 0.65, from adjust_motion @ 0x00528010 in the named retail decomp.
|
||||
var interp = MakeInterp();
|
||||
interp.InterpretedState.ForwardCommand = MotionCommand.WalkBackward;
|
||||
|
||||
float speed = interp.GetMaxSpeed();
|
||||
|
||||
Assert.Equal(MotionInterpreter.WalkAnimSpeed * 0.65f, speed, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxSpeed_Idle_ReturnsZero()
|
||||
{
|
||||
// Ready / non-locomotion commands → 0 (no movement speed).
|
||||
var interp = MakeInterp();
|
||||
interp.InterpretedState.ForwardCommand = MotionCommand.Ready;
|
||||
|
||||
float speed = interp.GetMaxSpeed();
|
||||
|
||||
Assert.Equal(0f, speed, precision: 4);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue