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>
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
# UN-2 verification: prove/disprove that retail CMotionInterp::get_max_speed
|
|
# (VA 0x00527cb0) multiplies by the 4.0f constant at VA 0x007C8918 on its
|
|
# return paths (the fmul the BN pseudo-C drops). Throwaway apparatus.
|
|
import struct
|
|
|
|
p = r"C:\Turbine\Asheron's Call\acclient.exe"
|
|
data = open(p, 'rb').read()
|
|
|
|
pe_off = struct.unpack_from('<I', data, 0x3C)[0]
|
|
nsec = struct.unpack_from('<H', data, pe_off + 6)[0]
|
|
opt_size = struct.unpack_from('<H', data, pe_off + 20)[0]
|
|
sec0 = pe_off + 24 + opt_size
|
|
imgbase = struct.unpack_from('<I', data, pe_off + 24 + 28)[0]
|
|
|
|
def va2off(va):
|
|
rva = va - imgbase
|
|
for i in range(nsec):
|
|
o = sec0 + i * 40
|
|
name = data[o:o + 8].rstrip(b'\x00').decode()
|
|
vsz, vaddr, rsz, roff = struct.unpack_from('<IIII', data, o + 8)
|
|
if vaddr <= rva < vaddr + max(vsz, rsz):
|
|
return roff + (rva - vaddr), name
|
|
return None, None
|
|
|
|
print('imgbase', hex(imgbase))
|
|
off, sec = va2off(0x00527CB0)
|
|
print('get_max_speed VA 0x527cb0 -> file', hex(off), 'sec', sec)
|
|
code = data[off:off + 0x50]
|
|
print('bytes:', code.hex())
|
|
FMUL = bytes.fromhex('d80d18897c00') # fmul dword ptr [0x007C8918]
|
|
print('fmul [0x7C8918] count in get_max_speed:', code.count(FMUL))
|
|
|
|
off2, sec2 = va2off(0x007C8918)
|
|
print('dword @0x7C8918 sec', sec2, '=', struct.unpack_from('<f', data, off2)[0])
|
|
off3, sec3 = va2off(0x007928B0)
|
|
print('dword @0x7928B0 sec', sec3, '=', struct.unpack_from('<f', data, off3)[0])
|
|
|
|
off4, _ = va2off(0x00527D00)
|
|
code4 = data[off4:off4 + 0x70]
|
|
print('get_adjusted_max_speed fmul count:', code4.count(FMUL))
|