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:
co-authored by
Claude Opus 4.8
parent
9eff043973
commit
a8eb8a427f
@@ -1930,7 +1930,7 @@ void
|
||||
{
|
||||
if (dt > 0.0001f && dt < 0.5f)
|
||||
{
|
||||
DeadReckon(dt); // engine: reckoner + lerp
|
||||
DeadReckon(dt); // engine: reckoner (exact angular) + lerp
|
||||
localToWorld = localOrigin;
|
||||
|
||||
// RENDER-vs-SIM decoupling probe: publish the heading the RENDER will
|
||||
@@ -3672,6 +3672,17 @@ void
|
||||
{
|
||||
resync = True; // stand-in when unstreamed
|
||||
}
|
||||
// ANGULAR dense-send: the original refreshed orientation in its FREQUENT
|
||||
// pose record so the peer's dead-reckon gap stayed tiny (decomp: 7-float
|
||||
// pose carries the quaternion, refreshed every broadcast). Our orientation
|
||||
// rides only the type-4 resync, and during a PURE spin the linear dense-send
|
||||
// never fires (not translating), so the gap balloons (~1.6s) and even the
|
||||
// exact integrator's projection is far ahead -> the slerp jumps. Send the
|
||||
// resync every frame while rotating to keep updateOrigin.angular fresh
|
||||
// (small gap -> smooth), mirroring the original's frequent-orientation model.
|
||||
static const int s_denseRot = getenv("BT_NO_DENSE_TX") ? 0 : 1;
|
||||
if (s_denseRot && Abs(localVelocity.angularMotion.y) > 0.1f)
|
||||
resync = True;
|
||||
if (resync)
|
||||
{
|
||||
ForceUpdate(1 << MechResyncUpdateModelBit); // type 4
|
||||
|
||||
@@ -435,11 +435,33 @@ namespace Vector
|
||||
//
|
||||
// P3 GAIT CUTOVER: backed by the real engine Quaternion (were no-op stubs).
|
||||
// ReconQuatIdentity(q, &id) -> q = Quaternion::Identity
|
||||
// ReconQuatIntegrate(out,in,d) -> out = in integrated by angular delta d
|
||||
// (engine Quaternion::Add(source, Vector3D) == FUN_00409f58)
|
||||
// ReconQuatIntegrate(out,in,d) -> out = in composed with an EXACT rotation by
|
||||
// angular delta d (= the real FUN_00409f58).
|
||||
// All callers pass (Quaternion*,Quaternion*) / (Quaternion*,Quaternion*,Vector3D*).
|
||||
inline void ReconQuatIdentity(Quaternion *q, const Quaternion * /*id*/) { *q = Quaternion::Identity; }
|
||||
inline void ReconQuatIntegrate(Quaternion *out, const Quaternion *in, const Vector3D *delta) { out->Add(*in, *delta); }
|
||||
// EXACT axis-angle quaternion integration -- the real FUN_00409f58 (part_000.c:9359),
|
||||
// verified from the decomp. A prior stand-in wrongly used `out->Add(*in,*delta)` (a
|
||||
// small-angle VECTOR add): correct every-frame on the master (tiny angle) but it
|
||||
// DIVERGES over a long dead-reckon gap -- the spinning-peer heading drifting to ~180deg
|
||||
// then snapping. The binary builds the unit rotation quaternion from the rotation vector
|
||||
// `delta` (angle=|delta|, axis=delta/angle) as { axis*sin(angle/2), cos(angle/2) } and
|
||||
// Hamilton-multiplies it onto `in` (FUN_00409d9c: out = in (X) deltaQuat).
|
||||
inline void ReconQuatIntegrate(Quaternion *out, const Quaternion *in, const Vector3D *delta)
|
||||
{
|
||||
const Scalar ang = delta->Length();
|
||||
if (ang > 1.0e-6f)
|
||||
{
|
||||
const Scalar half = (Scalar)(fmodf((float)ang, 6.2831853f) * 0.5f); // reduce2pi, then halve
|
||||
const Scalar s = (Scalar)(sinf((float)half) / ang); // sin(angle/2)/angle
|
||||
const Quaternion dq((Scalar)(delta->x * s), (Scalar)(delta->y * s),
|
||||
(Scalar)(delta->z * s), (Scalar)cosf((float)half));
|
||||
out->Multiply(*in, dq); // in (X) dq
|
||||
}
|
||||
else
|
||||
{
|
||||
*out = *in;
|
||||
}
|
||||
}
|
||||
template<class...A> inline Recon ReconQuatSlerp(A&&...) { return Recon(); }
|
||||
template<class...A> inline Recon ComputeImpactDamage(A&&...) { return Recon(); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user