MP: root-motion peer-position EXPERIMENT (env-gated BT_ROOT_POS; default unchanged) + KB notes (task #50)
The user's architectural question -- 'should animation and velocity even be
allowed to be uncoupled?' -- is decomp-CONFIRMED correct: the original peer
(FUN_004ab430 -> FUN_004ab1c8) drove POSITION FROM THE CLIP'S ROOT TRAVEL
between records (feet<->ground locked by construction) with a pose-sync
offset decay absorbing record corrections. Our port dead-reckons position
from velocity while the legs run on commanded speed -- the two only agree at
steady state, mismatching exactly during speed changes (the reported glitch).
This commit lands the peer half of the coupled architecture, env-gated:
- BT_ROOT_POS=1: peer position += clip travel rotated by heading (mirror of
the master world-step @3325, == IntegrateMotion tail @004ab1c8); pose
records absorbed via the authentic offset-decay (motionEventVector
mechanism) instead of snapping.
- Measured A/B (through-zero sweep circle, harshest speed-change regime):
velocity-lerp (default): step ratio 1.0022 (clean)
root-motion + sparse records: 11-17u anchor snaps (master's velocity
mirror no longer models a gait-driven peer -> under-sends)
root-motion + dense + offset-decay: evenness OK but ratio 1.86 --
authority tug-of-war (double-authoring, exactly the D5 risk).
CONCLUSION: the coupled peer requires the MASTER side of the original
architecture too (gait-driven send-gate mirror / channel-B IntegrateMotion
projection) -- a coherent rebuild for a fresh session, not a peer-only
patch. Default therefore stays velocity dead-reckon.
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
2c6db6a2de
commit
702822f9c5
@@ -1944,6 +1944,16 @@ void
|
||||
// (the keyboard-steering skip); pure spin/walk never showed it (one stream).
|
||||
// Save the heading, let DeadReckon own the LINEAR channel, then override
|
||||
// the angular result with the original's incremental integration.
|
||||
// ROOT-MOTION peer POSITION (authentic FUN_004ab1c8: the clip's travel
|
||||
// drives the body between records; pose records re-anchor). Save the
|
||||
// position so the engine velocity-lerp result can be discarded below
|
||||
// (BT_DR_POS=1 restores the velocity dead-reckon for A/B).
|
||||
// DEFAULT: engine velocity dead-reckon (best-measured: ratio 1.0022). The
|
||||
// coupled root-motion experiment (BT_ROOT_POS=1) needs the MASTER-side
|
||||
// gait mirror to stop the authority tug-of-war (measured ratio 1.86) --
|
||||
// the completion path is mapped in the session notes.
|
||||
static const int s_drPos = getenv("BT_ROOT_POS") ? 0 : 1;
|
||||
Point3D replPrevPos = localOrigin.linearPosition;
|
||||
Quaternion replPrevHeading = localOrigin.angularPosition;
|
||||
DeadReckon(dt); // engine reckoner: LINEAR position/velocity
|
||||
if (angSyncLatch)
|
||||
@@ -1960,6 +1970,8 @@ void
|
||||
&replPrevHeading, &replAngStep); // exact FUN_00409f58
|
||||
}
|
||||
localOrigin.angularPosition.Normalize();
|
||||
if (!s_drPos)
|
||||
localOrigin.linearPosition = replPrevPos; // root-motion mode: gait applies travel below
|
||||
localToWorld = localOrigin;
|
||||
|
||||
// RENDER-vs-SIM decoupling probe: publish the heading the RENDER will
|
||||
@@ -2122,7 +2134,48 @@ void
|
||||
// once so a direct run-state entry can't slew from -4.3e8.
|
||||
if (legCycleSpeed < -100.0f || legCycleSpeed > 200.0f)
|
||||
legCycleSpeed = 0.0f;
|
||||
(void)AdvanceLegAnimation(dt); // joints only; travel = DeadReckon
|
||||
const Scalar replLegAdv = AdvanceLegAnimation(dt); // joints + ROOT TRAVEL
|
||||
// AUTHENTIC coupled position (mirror of the master's world-step at
|
||||
// mech4.cpp:3325-3336 == Mech::IntegrateMotion tail @004ab1c8): between
|
||||
// records the body advances by the CLIP'S OWN travel rotated by the
|
||||
// heading -- feet and ground are locked BY CONSTRUCTION, so cadence-vs-
|
||||
// travel mismatch (the speed-change glitch) is structurally impossible.
|
||||
// A pose record re-anchors onto the authoritative position
|
||||
// (poseSyncLatch, armed by every type-0 read). adv is SIGNED by the SM
|
||||
// (reverse clips return negative travel; master dir==1.0 in SM mode).
|
||||
if (!s_drPos)
|
||||
{
|
||||
// POSE-SYNC OFFSET DECAY (authentic FUN_004ab1c8 motionEventVector
|
||||
// mechanism): a record does NOT snap the body -- the correction offset
|
||||
// is absorbed over ~1/3s while the clip's root motion keeps driving, so
|
||||
// the feet never pop and the position converges to authority.
|
||||
static Vector3D s_rmErr[2]; // per-instance would be a member; one replicant per node in practice
|
||||
Vector3D &rmErr = s_rmErr[0];
|
||||
if (poseSyncLatch)
|
||||
{
|
||||
rmErr.Subtract(updateOrigin.linearPosition, localOrigin.linearPosition);
|
||||
poseSyncLatch = 0;
|
||||
}
|
||||
{
|
||||
Scalar k = dt * 3.0f; if (k > 1.0f) k = 1.0f; // ~1/3s absorption
|
||||
localOrigin.linearPosition.x += rmErr.x * k;
|
||||
localOrigin.linearPosition.y += rmErr.y * k;
|
||||
localOrigin.linearPosition.z += rmErr.z * k;
|
||||
rmErr.x -= rmErr.x * k; rmErr.y -= rmErr.y * k; rmErr.z -= rmErr.z * k;
|
||||
}
|
||||
if (replLegAdv != 0.0f)
|
||||
{
|
||||
Vector3D rmLocal(0.0f, 0.0f, -replLegAdv); // forward = -Z, per-frame DISTANCE
|
||||
Matrix34 rmOrient;
|
||||
Matrix34::FromQuaternion(&rmOrient, &localOrigin.angularPosition);
|
||||
Vector3D rmWorld;
|
||||
FUN_00408744(&rmWorld, (Scalar *)&rmLocal, &rmOrient);
|
||||
localOrigin.linearPosition.x += rmWorld.x;
|
||||
localOrigin.linearPosition.y += rmWorld.y;
|
||||
localOrigin.linearPosition.z += rmWorld.z;
|
||||
}
|
||||
localToWorld = localOrigin; // refresh with the coupled position
|
||||
}
|
||||
|
||||
// GAIT-EVENNESS probe (BT_GAITEV, 1s stats): the metric of the VISIBLE
|
||||
// speed-change glitch -- per-frame leg-clip advance regularity + leg-state
|
||||
@@ -3707,6 +3760,10 @@ void
|
||||
// makes the cadence regular + dense (records ~1 frame apart), so the
|
||||
// horizon prediction matches and the lerp tracks smoothly. On a LAN
|
||||
// this is a handful of small packets/frame -- negligible.
|
||||
// Dense while moving: with the gait-driven peer, the master's velocity
|
||||
// mirror no longer models the peer, so the deadband under-sends and the
|
||||
// peer diverges (measured 11u snaps). Dense records keep the correction
|
||||
// offset tiny; the peer absorbs it via the authentic pose-sync decay.
|
||||
static const int s_denseTx = getenv("BT_NO_DENSE_TX") ? 0 : 1;
|
||||
const Logical moving = (liveSpdSq > 0.25f);
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user