UN-2 RESOLVED: GetMaxSpeed x4 is byte-verified retail; doc-comment was the misread
The register's UN-2 row recorded a contradiction: the GetMaxSpeed XML doc claimed the bare run rate was retail-correct (~5.9 m/s catch-up, calling the xRunAnimSpeed multiply a misread), while the implementation multiplied by RunAnimSpeed citing ACE. Settled against the binary, not the pseudo-C: - BN pseudo-C (acclient_2013_pseudo_c.txt:305127) renders get_max_speed as void with a bare `this->my_run_rate;` because it DROPS x87 instructions. - Disassembling the PDB-matched v11.4186 binary at VA 0x00527cb0: all THREE return paths end `fld <rate>; fmul dword ptr [0x007C8918]; ret`, and the .rdata dword at 0x007C8918 is 4.0f. Sibling get_adjusted_max_speed (0x00527d00) carries the same trailing fmul. Verifier committed at tools/verify_un2_fmul.py (PE parse + byte decode, rerunnable). - Retail paths: weenie null -> 1.0 x4; InqRunRate ok -> queried x4; InqRunRate failed -> my_run_rate x4. ACE MotionInterp.cs:665-676 matches. Changes: - Doc-comment rewritten: the implementation is retail-correct; the catch-up speed 2 x get_max_speed ~= 23.5 m/s at run 200 IS retail. The 1-Hz remote-blip symptom the old comment attributed to this multiply is therefore UNEXPLAINED by it (if it recurs: #41 family, not this). - Weenie-null path aligned to retail's LITERAL 1.0 default (was MyRunRate). - Tests re-pinned to the three retail paths (the old NoWeenie test pinned the non-retail fallback). - Register: UN-2 row deleted per the retire rule (6 -> 5 UN rows); shortlist renumbered. This is the 2nd confirmed instance of the BN x87-dropout artifact class (memory: feedback_bn_decomp_field_names) deciding a register row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f6a30f4aae
commit
0cb97aa594
4 changed files with 114 additions and 66 deletions
|
|
@ -935,52 +935,47 @@ public sealed class MotionInterpreter
|
|||
// ── CMotionInterp::get_max_speed (0x00527cb0) ─────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Return the run rate. Mirrors retail
|
||||
/// <c>CMotionInterp::get_max_speed</c> at <c>0x00527cb0</c>.
|
||||
/// Return the maximum movement speed in m/s: run rate × RunAnimSpeed (4.0).
|
||||
/// Mirrors retail <c>CMotionInterp::get_max_speed</c> at <c>0x00527cb0</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Decomp (named-retail/acclient_2013_pseudo_c.txt:305127):</b>
|
||||
/// <code>
|
||||
/// void get_max_speed(this) {
|
||||
/// weenie_obj = this->weenie_obj;
|
||||
/// this_1 = nullptr;
|
||||
/// if (weenie_obj == 0) return;
|
||||
/// if (weenie_obj->vtable->InqRunRate(&this_1) != 0) return;
|
||||
/// this->my_run_rate; // x87 fld leaves my_run_rate on FPU stack
|
||||
/// }
|
||||
/// </code>
|
||||
/// Binary Ninja shows the return type as <c>void</c> because the float
|
||||
/// return rides the x87 FPU stack rather than EAX. Both branches
|
||||
/// emit an <c>fld</c> of either <c>this_1</c> (the InqRunRate
|
||||
/// out-param value) or <c>my_run_rate</c>, leaving the run rate on
|
||||
/// ST0 as the return value.
|
||||
/// <b>The ×4.0 is byte-verified retail (UN-2 resolved 2026-06-12).</b>
|
||||
/// The Binary Ninja pseudo-C (named-retail/acclient_2013_pseudo_c.txt:305127)
|
||||
/// renders this function as <c>void</c> with a bare <c>this->my_run_rate;</c>
|
||||
/// statement because it drops x87 instructions — a known BN artifact class.
|
||||
/// Disassembling the PDB-matched v11.4186 binary at VA <c>0x00527cb0</c>
|
||||
/// shows all THREE return paths end with
|
||||
/// <c>fmul dword ptr [0x007C8918]</c>, and the .rdata dword at
|
||||
/// <c>0x007C8918</c> is <c>0x40800000</c> = 4.0f (the sibling
|
||||
/// <c>get_adjusted_max_speed</c> 0x00527d00 carries the same trailing
|
||||
/// fmul). Re-derive with <c>py tools/verify_un2_fmul.py</c>. The three
|
||||
/// retail paths: weenie_obj == null → 1.0×4; InqRunRate success →
|
||||
/// queried×4; InqRunRate failure → my_run_rate×4. ACE's
|
||||
/// MotionInterp.cs:665-676 ports it identically (RunAnimSpeed = 4.0f).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Critical:</b> this returns the BARE run rate (typically 1.0 to
|
||||
/// ~3.0), NOT a velocity in m/s. We previously multiplied by
|
||||
/// <c>RunAnimSpeed</c> to get a m/s value, reasoning that
|
||||
/// <c>2 × bare_rate</c> would be too slow a catch-up speed for the
|
||||
/// caller (<c>InterpolationManager::adjust_offset</c>). That was a
|
||||
/// misread of the decomp — retail's catch-up IS that slow on purpose.
|
||||
/// The multi-second 1-Hz blip the user reported when observing retail
|
||||
/// remotes from acdream traced to body racing at the wrong (overshot)
|
||||
/// catch-up speed (~23.5 m/s instead of the retail-correct ~5.9 m/s
|
||||
/// for a run-skill-200 char).
|
||||
/// Consequence: the dead-reckoning catch-up speed
|
||||
/// (<c>InterpolationManager::adjust_offset</c> 0x00555d30, pc:353122)
|
||||
/// is <c>2 × get_max_speed()</c> ≈ 23.5 m/s for a run-rate-2.94
|
||||
/// (run-skill-200) character — that IS retail's value. An earlier
|
||||
/// doc-comment here claimed the bare rate (~5.9 m/s catch-up) was
|
||||
/// retail-correct and blamed the ×4 for the multi-second 1-Hz blip on
|
||||
/// observed retail remotes; that reading trusted the BN x87 dropout
|
||||
/// and is refuted by the binary. If the blip recurs, its root cause is
|
||||
/// elsewhere (node-fail handling / progress-quantum abandonment /
|
||||
/// position-queue feed — the #41 family), NOT this multiply.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public float GetMaxSpeed()
|
||||
{
|
||||
// Resolve current run rate: prefer WeenieObj.InqRunRate, fall back to MyRunRate.
|
||||
// Then multiply by RunAnimSpeed (4.0). Matches ACE's MotionInterp.cs:670-678
|
||||
// which is verified against retail (the ACE MotionInterp file is a
|
||||
// line-by-line port). Returns the maximum world-space velocity in m/s
|
||||
// — for run skill 200 with rate ≈ 2.94, this is ≈ 11.76 m/s. Used by
|
||||
// InterpolationManager.AdjustOffset to compute the catch-up speed
|
||||
// (= 2 × maxSpeed).
|
||||
float rate = MyRunRate;
|
||||
if (WeenieObj is not null && WeenieObj.InqRunRate(out float queried))
|
||||
rate = queried;
|
||||
// Retail 0x00527cb0: weenie null → 1.0; InqRunRate ok → queried;
|
||||
// InqRunRate failed → my_run_rate. Every path × RunAnimSpeed (4.0,
|
||||
// .rdata 0x007C8918). Note the weenie-null default is the LITERAL 1.0
|
||||
// (.rdata 0x007928B0), not my_run_rate.
|
||||
float rate = 1.0f;
|
||||
if (WeenieObj is not null && !WeenieObj.InqRunRate(out rate))
|
||||
rate = MyRunRate;
|
||||
return RunAnimSpeed * rate;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue