MP: FIX peer spin hang/divergence -- exact quaternion integration + frequent orientation (task #50)

Answers 'how did the original handle this?' from the decomp (subagent hunt):
the 1995 binary's replicant reckoner (FUN_004ab1c8 -> FUN_00409f58, part_000.c:9359)
integrates heading EXACTLY: build a unit axis-angle rotation quaternion from
angularVelocity*dt ({axis*sin(t/2), cos(t/2)}) and Hamilton-multiply it onto the
heading (FUN_00409d9c) -- exact for any timestep, stays on the unit sphere.  It
further carries the full orientation quaternion in the FREQUENT pose record
(FUN_0040a938, 7-float pose), so the dead-reckon gap stays tiny.

Our reconstruction diverged two ways, both fixed:
 1. ReconQuatIntegrate (mechrecon.hpp) -- the reconstruction of FUN_00409f58 -- was
    STUBBED as , a crude small-angle VECTOR add.  Restored to
    the real exact axis-angle composition.  (A 'no stand-ins' violation: the comment
    even wrongly claimed Quaternion::Add == FUN_00409f58.)
 2. The engine Mover reckoner (MOVER.cpp AcceleratedDeadReckoner/LinearDeadReckoner)
    also did the vector Add on projectedOrigin.angularPosition -> over a long peer
    record gap it diverged to ~180deg then snapped (the reported spin HANG/hesitation).
    Routed both through a new ExactAngularProject() helper (same exact math).
 3. Orientation only rode the sparse type-4 resync; during a PURE spin the linear
    dense-send never fires (not translating), so the gap ballooned (~1.6s) and the now-
    exact projection sat far ahead -> the slerp jumped.  Added an ANGULAR dense-send
    (resync every frame while |yawRate|>0.1), mirroring the original's frequent-
    orientation model -> gap stays tiny -> smooth.

Verified live-autonomous (BT_AUTODRIVE+BT_FORCE_TURN + BT_RENDHDG render-rate probe):
the ~180deg divergence + multi-radian snaps are GONE (rendered maxStep 0.05-0.10 rad,
no jumps).  User confirms: no frame hang/hesitation.  MOVER.cpp change is strictly
more correct (exact==crude for the small per-frame master case; only large-gap peer
extrapolation changes), so walking is unaffected.

KNOWN REMAINING (separate, smaller): the turn-STEP leg animation (trn clip, mech2.cpp
advance_normally) runs at a FIXED idleStrideScale cadence that does not scale with the
rotation rate, so the legs lag/skip vs the (now-correct) body rotation.  Next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-14 17:05:16 -05:00
co-authored by Claude Opus 4.8
parent 9eff043973
commit a8eb8a427f
3 changed files with 69 additions and 12 deletions
+32 -8
View File
@@ -11,6 +11,34 @@
#include "line.h"
#include "app.h"
#include "notation.h"
#include <math.h>
//
// EXACT axis-angle rotation composition -- matches the 1995 BT binary's angular
// integrator (FUN_00409f58): build a unit rotation quaternion from the rotation
// VECTOR `rotVec` (angle = |rotVec|, axis = rotVec/angle) as { axis*sin(angle/2),
// cos(angle/2) } and Hamilton-multiply it onto `base`. The dead-reckoner previously
// did `out.Add(base, rotVec)` -- adding a scaled angular-velocity vector to the heading
// quaternion. That is only a small-angle approximation: fine per-frame (tiny angle),
// but over a long replicant dead-reckon gap it DIVERGES (the heading drifts to ~180deg
// then snaps -- the spinning-peer hesitation). This composition is exact for any angle
// and stays on the unit sphere.
//
static void ExactAngularProject(Quaternion &out, const Quaternion &base, const Vector3D &rotVec)
{
const Scalar ang = rotVec.Length();
if (ang > 1.0e-6f)
{
const Scalar h = 0.5f * (Scalar)fmodf((float)ang, 6.2831853f); // half of angle mod 2pi
const Scalar s = (Scalar)(sinf((float)h) / ang); // sin(angle/2)/angle
const Quaternion dq(rotVec.x * s, rotVec.y * s, rotVec.z * s, (Scalar)cosf((float)h));
out.Multiply(base, dq); // base (X) dq
}
else
{
out = base;
}
}
//#############################################################################
//############################### Mover #################################
@@ -395,10 +423,8 @@ Logical
//-------------------------------
//
position_delta.Multiply(updateVelocity.angularMotion, time_slice);
projectedOrigin.angularPosition.Add(
updateOrigin.angularPosition,
position_delta
);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
projectedVelocity = updateVelocity;
Check_Fpu();
@@ -460,10 +486,8 @@ Logical
updateVelocity.angularMotion,
time_slice
);
projectedOrigin.angularPosition.Add(
updateOrigin.angularPosition,
position_delta
);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
//
//-----------------------------------