using System; namespace AcDream.Core.Physics; /// /// Retail MovementSystem (named-retail decomp pc 695958+): the pure /// formula layer PlayerWeenie (the CACQualities-shaped composition) /// calls once burden, skill, and stamina inputs are assembled. See /// docs/research/2026-07-30-stat-coupled-movement-pseudocode.md §6. /// /// /// x87-mush disclosure: 's general-case /// arithmetic and 's pk!=0 branch are /// entirely dropped by the BN decompiler (not partially garbled — the /// operand expressions never survive translation, unlike the polarity-only /// ambiguities elsewhere in this port). Both are cross-referenced against /// references/ACE/Source/ACE.Server/Physics/Animation/MovementSystem.cs, /// which already matched this exact acdream port's PRE-P1 code /// (PlayerWeenie.GetRunRate/GetJumpHeight already cited "decompiled /// + ACE MovementSystem" before this slice) — no behavior change for the /// formulas already live; only the retail-named surface and the 3rd/4th /// scaling parameters (every known call site passes 1f) are new. /// /// public static class MovementSystem { /// /// MovementSystem::GetRunRate 0x006b0950. The 800 branch is an /// EXACT-EQUALITY sentinel, not a cap: raw byte decode of the PDB-paired /// binary (#266, 2026-07-30) shows fild skill; fcom [800f]; fnstsw; /// test ah, 0x44; jp general — the C2/C3 parity idiom that falls /// through to 18/4 ONLY when skill == 800 (<, >, and /// unordered all take the general path). InqMaxRunRate (0x00591b20) /// passes skill=9999 and therefore gets the general formula's ~3.6961, /// NOT 4.5. The general-path arithmetic is byte-verified /// instruction-by-instruction: (loadMod * (skill/(skill+200) * 11) + /// 4) / scaling / 4. /// /// /// ACE's MovementSystem.GetRunRate reads this branch as /// >= 800 ("max run speed?") — that is ACE's misread of the /// same x87 mush, NOT a tiebreaker. Porting >= made every /// maxed character run a flat 4.5 (vs retail's ~3.70) and erased the /// vitae speed differential entirely (#266). Do not "fix" this back /// from ACE. /// public static float GetRunRate(float burden, int runSkill, float scaling = 1f) { if (runSkill == 800) return 18f / 4f; float loadMod = EncumbranceSystem.LoadMod(burden); return ((loadMod * ((float)runSkill / (runSkill + 200) * 11f) + 4f) / scaling) / 4f; } /// /// MovementSystem::GetJumpHeight 0x006b09b0. Fully readable except /// the extent-clamp micro-branch (x87 mush, ACE's Math.Clamp(power,0,1) /// is the tiebreaker — matches the pre-P1 acdream port unchanged). /// public static float GetJumpHeight( float burden, int jumpSkill, float power, float scaling = 1f) { power = Math.Clamp(power, 0f, 1f); float loadMod = EncumbranceSystem.LoadMod(burden); float result = loadMod * ((float)jumpSkill / (jumpSkill + 1300f) * 22.2f + 0.05f) * power / scaling; return result < 0.35f ? 0.35f : result; } /// /// MovementSystem::JumpStaminaCost 0x006b0a40. The pk==0 /// branch is fully readable: ceil((load + 0.5) * power * 8 + 2) — /// note load (not power) carries the +0.5; the /// campaign plan's shorthand had the operands swapped (see pseudocode /// doc §6). The pk!=0 branch is entirely dropped by BN; ACE's /// (power+1.0)*100.0 is the tiebreaker. is /// the real PlayerKillerStatus/LastPkAttackTimestamp /// 20-second-window predicate as of TS-23 (Campaign P Slice P3, /// 2026-07-30) — see . /// public static int JumpStaminaCost(float power, float burden, bool pk) { if (pk) return (int)((power + 1.0f) * 100.0f); return (int)Math.Ceiling((burden + 0.5f) * power * 8f + 2f); } /// /// MovementSystem::GetJumpPower — the algebraic inverse of /// , solving for the extent affordable at a /// given stamina. Present in ACE (uncommented, live utility) but its /// retail call site is the charge-power-meter UI outside /// CMotionInterp (0x0056afac, out of R3/P1 scope — see /// ChargeJump's doc comment in MotionInterpreter.cs). Not /// consumed by P1; ported for signature completeness and to leave the /// formula available for the charge-meter follow-up without a second /// decomp pass. /// public static float GetJumpPower(uint stamina, float burden, bool pk) { if (pk) return stamina / 100.0f - 1.0f; return (stamina - 2.0f) / (burden * 8.0f + 4.0f); } }