diff --git a/ac6recomp_config.toml b/ac6recomp_config.toml index bf6f83ba..2411fbc6 100644 --- a/ac6recomp_config.toml +++ b/ac6recomp_config.toml @@ -10606,6 +10606,26 @@ address = 0x821EFD74 name = "ac6DeltaPrecisionHook" registers = ["r8", "r10", "r29", "r30"] +# Scale the flight model's per-frame vertical lift-deficit drop by the +# frame-step ratio (see ac6_fps_physics_fix). +# +# In the core force step rex_sub_823046A0, f9 = |lift-deficit| is subtracted +# from the vertical velocity AFTER the 1/dt-weighted path smoothing mix, and +# the result is re-normalized into the stored flight-path direction +# [fm+128..136]. Inside that feedback loop the drop's equilibrium +# contribution is amplified by (1 + retention) with retention ~ 1/dt, so the +# steady-state angle of attack scales with framerate: a hands-off glide at +# 60fps settles on a ~2 degree steeper path than at 30fps with identical +# attitude and identical force inputs (measured). This hook fires at +# 0x82304F40 (the `fsubs f11,f11,f9` that applies the drop) and scales f9 by +# frame_ms/33.3ms, which makes the equilibrium framerate-independent while +# changing the drop's small direct velocity share (~1/retention of the +# effect) by under 2%. Exact no-op at the native 30fps cadence. +[[midasm_hook]] +address = 0x82304F40 +name = "ac6PathDropHook" +registers = ["f9"] + # Record frame timestamps at each D3DDevice_Swap call for timing overlay. # Placed before the device[21516] branch so it fires unconditionally — # when that field is non-zero the function skips VdSwap, which caused diff --git a/src/ac6_backend_fixes/ac6_fps_physics_fix.cpp b/src/ac6_backend_fixes/ac6_fps_physics_fix.cpp index ce3e8877..3730a07c 100644 --- a/src/ac6_backend_fixes/ac6_fps_physics_fix.cpp +++ b/src/ac6_backend_fixes/ac6_fps_physics_fix.cpp @@ -45,6 +45,39 @@ // Both symbols are weak in the generated code and registered by address in // ac6recomp_init, so these strong overrides capture direct calls and vtable // dispatch alike. +// +// One term needs a different treatment: the sink-rate / angle-of-attack +// equilibrium (the ac6PathDropHook mid-asm hook at the bottom of this file, +// registered in ac6recomp_config.toml at 0x82304F40). Inside the core force +// step, the stored flight-path direction [this+128..136] - the direction the +// aircraft actually travels, distinct from where the nose points - is rebuilt +// each call as +// +// vel = |V| * normalize(unit(V) + w * path_old) - drop * up +// path_new = normalize(vel) +// +// where w = [this+324]*const/dt is the old-path retention (1/dt-weighted, so +// the smoothing itself is framerate-correct) and drop = |lift-deficit| is +// subtracted from the vertical once per CALL. Because the drop lands inside +// that feedback loop, its steady-state contribution is amplified by (1 + w): +// the equilibrium angle of attack is (drop/|V|) * (1 + w), and w doubles at +// 60fps. Measured on a hands-off glide: every INPUT to the step (force +// vector [this+1328..1340], speed, deficit fields) is identical between +// framerates, yet the path settles ~2 degrees steeper at 60fps - same speed +// down a steeper line means a faster sink and an earlier ground impact +// (reported as gravity pulling harder at high fps, worse inverted since the +// deficit is larger). +// +// The pre/post blend above cannot fix that: blending rescales an update's +// RATE but shares its fixed points, so it can never move a framerate- +// dependent equilibrium (confirmed experimentally - the settle slowed, the +// floor did not move). Instead the hook scales the drop itself by the same +// frame-time ratio at the exact instruction that applies it, which cancels +// the 1/dt amplification: the equilibrium becomes drop*C/33.3ms/|V| at any +// framerate. The drop's direct (unamplified) share of the sink velocity is +// only ~1/w (~3%) of the effect, so scaling it perturbs the true sink rate +// by well under 2%; at the native cadence the multiply is a bit-exact no-op. +// The alternate-mode step applies no such in-loop drop and needs no hook. #include #include @@ -169,3 +202,16 @@ PPC_FUNC_IMPL(rex_sub_82329B40) { BlendFieldDelta(base, self + kTriggerCommandOffset, pre, ratio, "trigger-command(+1456)", s_logged); } + +// Mid-asm hook (registered in ac6recomp_config.toml at 0x82304F40, the +// `fsubs f11,f11,f9` inside rex_sub_823046A0): f9 holds the vertical +// lift-deficit drop about to be subtracted from the velocity that becomes the +// stored flight-path direction. Scaling it by the frame-step ratio makes the +// angle-of-attack equilibrium framerate-independent (see the file header for +// the mechanism). StepRatio() returns exactly 1.0 at the native cadence or +// with the fix disabled, and x *= 1.0 is bit-exact, so this is a strict no-op +// there. Runs on the guest sim thread inside the wrapped force step, where +// the per-frame ratio is already cached. +void ac6PathDropHook(PPCRegister& f9) { + f9.f64 *= StepRatio(); +}