fix #266: retail run-rate 800 branch is exact-equality sentinel, not a cap

Raw byte decode of MovementSystem::GetRunRate (0x006b0950, PDB-paired
binary): fild skill; fcom [800f]; fnstsw; test ah, 0x44; jp general —
the C2/C3 parity idiom whose 18/4 fall-through executes ONLY at
skill == 800 exactly. ACE read this as >= 800 ('max run speed?') and
Campaign P P1 inherited that misread when BN dropped the arithmetic,
flat-lining every maxed character at 4.5 (retail-true ~3.70, +21%) and
erasing the vitae differential (both 10200 and 15225 sat above 800).

The [stat-chain] live capture proved the enchant chain correct end to
end (vitae 0.67 -> eff run 10200 -> controller), isolating the formula.
General path byte-verified: (loadMod*(skill/(skill+200)*11)+4)/scaling/4.
InqMaxRunRate's skill=9999 probe gets ~3.6961, not 4.5.

Golden tests pin the 799/800/801 straddle and the maxed-skill vitae
differential; pseudocode doc §6 carries the decode plus a do-not-
reimport-ACE warning. Complete Release suite: 10,025 passed / 5 skips.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 19:22:41 +02:00
parent bd3ade625f
commit 61e959169b
5 changed files with 85 additions and 28 deletions

View file

@ -156,22 +156,30 @@ first, don't guess the format string.
## #266 — Local player faster than a comparable retail character
**Status:** OPEN — needs a controlled comparison capture
**Status:** CLOSED 2026-07-30 — root cause: ACE-inherited `>= 800` misread
of retail's exact-equality run-rate sentinel
**Severity:** HIGH (matrix live gate 2026-07-30; core speed parity)
**Component:** movement / stat chain (Campaign P P1) / outbound speed
**Component:** movement / `MovementSystem.GetRunRate`
**Symptom (user report):** side-by-side, the acdream character runs
visibly faster than a character on retail. Confounds to rule out first:
(a) unequal run skills/vitae between the two characters; (b) whether the
comparison retail char had buffs. Diagnosis plan: log both chars' run
skill + vitae + computed runRate; capture our GetRunRate inputs
(effective skill after EnchantSkill, burden) at the moment of comparison;
verify ACE's echoed ForwardSpeed matches what we apply locally
(ApplyServerRunRate) and what retail applies for the same skill. Suspect
list: effective-skill vitae application on the LOCAL run path,
run-rate-cap difference, or an uneven comparison.
**Root cause:** retail `MovementSystem::GetRunRate` (0x006b0950) returns
18/4 = 4.5 ONLY when runSkill == 800 EXACTLY (byte-decoded `fcom [800f];
test ah, 0x44; jp` — the C2/C3 parity equality idiom; <, >, and unordered
all take the general formula). ACE misread the same x87 mush as
`>= 800` ("max run speed?") and our P1 port inherited it via the
ACE cross-reference. Every maxed character therefore ran a flat 4.5
(retail-true ~3.70) — ~21% too fast and completely vitae-independent,
because both the vitae-reduced and unreduced skill sat above 800. The
controlled comparison (33%-vitae +Acdream vs 5%-vitae +Je, both maxed)
showed acdream faster while retail runs them within ~0.4% — exactly the
formula's prediction. The vitae/enchantment chain itself was verified
intact end-to-end via [stat-chain] live capture (vitae 0.67 installed →
eff run 10200 → applied to controller).
---
**Fix:** `==` restores the general formula for all non-800 skills;
golden tests pin 799/800/801 straddle + the maxed-skill vitae
differential; `docs/research/2026-07-30-stat-coupled-movement-pseudocode.md`
§6 corrected with the full byte decode and an explicit "do not re-import
ACE's >= reading" warning.
## #265 — Steep-slope response set: uphill-jump bounce, roof slides lost, edge wedge (TS-4 removal fallout — REVERTED)