MP: COUPLED peer motion -- authentic gait-driven position + master gait send-mirror (task #50, env-gated)
Finishes the coupled IntegrateMotion path the decomp workflow (wh1h5gnmc, 3 make-or-break claims CONFIRMED by adversarial verify) proved authentic: - Peer LINEAR position is SINGLE-SOURCE gait: IntegrateMotion integrates the body-channel cycleDistance into projectedOrigin.linear@0x260, copied verbatim to localOrigin -- NOT velocity dead-reckon (that is angular/heading ONLY). 0x260 'motionDelta' and 'projectedOrigin' are literally the same field; the 'contradiction' was our reconstruction's two misnamed shadows. - Master and peer run the SAME predictor; the master's SEND-mirror must run it too (binary FUN_004a9b5c @0x4aab9c) so a gait-driven peer stays anchored. Our mirror used the constant-velocity deadReckoner + overwrote bodyTargetSpeed live every frame -> could not model a gait peer (the tug-of-war). - T4 CONFIRMED: both channels pose the full skeleton (same JointedMover); keep the peer LEG channel (our body channel is unbound on the peer). CHANGES (all behind BT_MASTER_GAITMIRROR / BT_ROOT_POS, DEFAULT OFF -- zero change to shipped behavior until visually confirmed): - mech4.cpp: send-mirror advances projectedOrigin by the mj=0 body channel travel (mirrorBodyAdv) rotated by heading + last-sent angular vel, re-seeded to localOrigin on each send, instead of the deadReckoner; and stops the per-frame live bodyTargetSpeed overwrite so the mirror slews toward last-sent. - mech.hpp/mech.cpp: mirrorBodyAdv member. Measured A/B (autonomous through-zero sweep circle, the speed-change regime): velocity two-source (current default): ratio 1.0022 BUT user-visible glitch coupled, NO mirror (dense): ratio 1.86 (tug-of-war) coupled + gait mirror, dense OFF: ratio 1.14, back-steps 0.1% coupled + gait mirror, dense ON: ratio 1.043, back-steps 0.1%, maxStep 2.9u The single-source coupling is proven (backward-stepping 0.1% vs the split's churn). Residual 2.9u occasional snap = leg(peer)-vs-body(mirror) channel mismatch, second-order. Awaiting visual confirmation before default promotion. 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
ea39af1ff6
commit
c52a1ad79a
@@ -915,6 +915,7 @@ Mech::Mech(
|
|||||||
Wword(0xe6) = 0;
|
Wword(0xe6) = 0;
|
||||||
poseSyncLatch = 0; // @0x77c (was absorbed Wword(0x1df))
|
poseSyncLatch = 0; // @0x77c (was absorbed Wword(0x1df))
|
||||||
heatLevelSnapshot = 0; // @0x780 (type-7 deadband baseline)
|
heatLevelSnapshot = 0; // @0x780 (type-7 deadband baseline)
|
||||||
|
mirrorBodyAdv = 0.0f; // master send-mirror gait travel
|
||||||
angMirrorYaw = 0.0f; // scalar peer-yaw mirror (port addition)
|
angMirrorYaw = 0.0f; // scalar peer-yaw mirror (port addition)
|
||||||
angMirrorRate = 0.0f;
|
angMirrorRate = 0.0f;
|
||||||
angMirrorValid = 0;
|
angMirrorValid = 0;
|
||||||
|
|||||||
@@ -625,6 +625,12 @@ public:
|
|||||||
// one is recomputed by the master's own reckoner each frame from lastUpdate
|
// one is recomputed by the master's own reckoner each frame from lastUpdate
|
||||||
// timing it does not control, and its false pi-drift waves FLOODED type-4
|
// timing it does not control, and its false pi-drift waves FLOODED type-4
|
||||||
// resyncs (measured maxAng~=pi bursts). Scalars, wrap-safe, self-timed.
|
// resyncs (measured maxAng~=pi bursts). Scalars, wrap-safe, self-timed.
|
||||||
|
// Master send-mirror gait travel (BT_MASTER_GAITMIRROR): the invisible mj=0
|
||||||
|
// body channel's per-frame cycleDistance, stashed in the drive block and
|
||||||
|
// consumed by the send gate to advance projectedOrigin the way the PEER
|
||||||
|
// predicts (gait), instead of the constant-velocity deadReckoner. This is the
|
||||||
|
// authentic FUN_004a9b5c mirror = IntegrateMotion(mj=0) on projectedOrigin.
|
||||||
|
Scalar mirrorBodyAdv; // body cycleDistance this frame (master)
|
||||||
Scalar angMirrorYaw; // yaw at the last type-4 send (rad)
|
Scalar angMirrorYaw; // yaw at the last type-4 send (rad)
|
||||||
Scalar angMirrorRate; // yaw rate sent with it (rad/s)
|
Scalar angMirrorRate; // yaw rate sent with it (rad/s)
|
||||||
Time angMirrorTime; // lastPerformance at that send
|
Time angMirrorTime; // lastPerformance at that send
|
||||||
|
|||||||
@@ -3167,7 +3167,21 @@ void
|
|||||||
// while the animation transitioned gradually (the "physics don't
|
// while the animation transitioned gradually (the "physics don't
|
||||||
// line up with the animation" desync). Signed, the SM decelerates
|
// line up with the animation" desync). Signed, the SM decelerates
|
||||||
// through stop -> reverse-entry exactly like the leg channel.
|
// through stop -> reverse-entry exactly like the leg channel.
|
||||||
if (s_realControls && MappingMapper() != 0)
|
// BT_MASTER_GAITMIRROR: the invisible mj=0 body channel becomes the
|
||||||
|
// PEER's predictor, so it must slew toward the LAST-SENT commanded
|
||||||
|
// speed (which the record writers stamp into bodyTargetSpeed@0x6b4 on
|
||||||
|
// each send, mech.cpp), NOT the live mapper demand. Leaving
|
||||||
|
// bodyTargetSpeed at last-sent re-activates the authentic type-2
|
||||||
|
// deadband and makes the mirror model exactly what the peer computes.
|
||||||
|
// The master's VISIBLE motion uses the LEG channel reading live demand
|
||||||
|
// independently, so this does not change what the local player sees.
|
||||||
|
// (Default OFF: live overwrite preserved.)
|
||||||
|
static const int s_gaitMirror = getenv("BT_MASTER_GAITMIRROR") ? 1 : 0;
|
||||||
|
if (s_gaitMirror)
|
||||||
|
{
|
||||||
|
/* leave bodyTargetSpeed at the last-sent value */
|
||||||
|
}
|
||||||
|
else if (s_realControls && MappingMapper() != 0)
|
||||||
bodyTargetSpeed = MappingMapper()->speedDemand;
|
bodyTargetSpeed = MappingMapper()->speedDemand;
|
||||||
else
|
else
|
||||||
bodyTargetSpeed = ((throttle < 0.0f) ? -1.0f : 1.0f)
|
bodyTargetSpeed = ((throttle < 0.0f) ? -1.0f : 1.0f)
|
||||||
@@ -3234,6 +3248,7 @@ void
|
|||||||
// BT_BODY_MJ=1 restores the old double-writer for A/B.
|
// BT_BODY_MJ=1 restores the old double-writer for A/B.
|
||||||
static const int s_bodyMj = BTEnvOn("BT_BODY_MJ", 0);
|
static const int s_bodyMj = BTEnvOn("BT_BODY_MJ", 0);
|
||||||
adv = AdvanceBodyAnimation(dt, s_bodyMj); // channel B: replication projection (mj=0)
|
adv = AdvanceBodyAnimation(dt, s_bodyMj); // channel B: replication projection (mj=0)
|
||||||
|
mirrorBodyAdv = adv; // stash for the send-mirror gait advance
|
||||||
legAdv = AdvanceLegAnimation(dt); // channel A: local sim -- pose + travel
|
legAdv = AdvanceLegAnimation(dt); // channel A: local sim -- pose + travel
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3716,7 +3731,43 @@ void
|
|||||||
// the PEER's event queue busy, so its CreatingMission->LoadingMission
|
// the PEER's event queue busy, so its CreatingMission->LoadingMission
|
||||||
// quiet-timeout never fired -- the pods deadlocked pre-launch. The real
|
// quiet-timeout never fired -- the pods deadlocked pre-launch. The real
|
||||||
// system never moves before launch, so the gate is faithful.)
|
// system never moves before launch, so the gate is faithful.)
|
||||||
(this->*deadReckoner)();
|
// SEND-MIRROR advance: model where the PEER believes this mech is, so the
|
||||||
|
// deadband fires exactly when the peer would drift. Authentic (binary
|
||||||
|
// FUN_004a9b5c @0x4aab9c): advance projectedOrigin by IntegrateMotion(mj=0)
|
||||||
|
// -- the SAME gait predictor the peer runs -- fed the LAST-SENT commanded
|
||||||
|
// speed. Our default mirror uses the engine constant-velocity deadReckoner
|
||||||
|
// (AcceleratedDeadReckoner), which does NOT model a gait-driven peer -> the
|
||||||
|
// deadband under-sends and the coupled peer drifts (measured). With
|
||||||
|
// BT_MASTER_GAITMIRROR: increment projectedOrigin.linear by the mj=0 body
|
||||||
|
// channel's travel (mirrorBodyAdv, = the peer's predicted stride) rotated by
|
||||||
|
// the mirror heading, and integrate the heading by the last-sent angular
|
||||||
|
// velocity. Re-seeded to localOrigin on each send by the record writers'
|
||||||
|
// projectedOrigin re-base, so this is incremental-from-last-send, exactly the
|
||||||
|
// peer's IntegrateMotion. (Default OFF: constant-velocity deadReckoner.)
|
||||||
|
static const int s_gaitMirrorSend = getenv("BT_MASTER_GAITMIRROR") ? 1 : 0;
|
||||||
|
if (s_gaitMirrorSend)
|
||||||
|
{
|
||||||
|
Vector3D mAng;
|
||||||
|
mAng.Multiply(updateVelocity.angularMotion, dt); // last-sent yaw rate
|
||||||
|
ReconQuatIntegrate(&projectedOrigin.angularPosition,
|
||||||
|
&projectedOrigin.angularPosition, &mAng);
|
||||||
|
projectedOrigin.angularPosition.Normalize();
|
||||||
|
if (mirrorBodyAdv != 0.0f)
|
||||||
|
{
|
||||||
|
Vector3D mLocal(0.0f, 0.0f, -mirrorBodyAdv); // forward = -Z, frame distance
|
||||||
|
Matrix34 mOrient;
|
||||||
|
Matrix34::FromQuaternion(&mOrient, &projectedOrigin.angularPosition);
|
||||||
|
Vector3D mWorld;
|
||||||
|
FUN_00408744(&mWorld, (Scalar *)&mLocal, &mOrient);
|
||||||
|
projectedOrigin.linearPosition.x += mWorld.x;
|
||||||
|
projectedOrigin.linearPosition.y += mWorld.y;
|
||||||
|
projectedOrigin.linearPosition.z += mWorld.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(this->*deadReckoner)();
|
||||||
|
}
|
||||||
Vector3D error;
|
Vector3D error;
|
||||||
error.Subtract(
|
error.Subtract(
|
||||||
projectedOrigin.linearPosition,
|
projectedOrigin.linearPosition,
|
||||||
@@ -3774,6 +3825,15 @@ void
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
ForceUpdate(); // type 0: pose record
|
ForceUpdate(); // type 0: pose record
|
||||||
|
// RE-SEED the gait mirror to the just-sent pose: the peer will now
|
||||||
|
// extrapolate from THIS localOrigin, so the mirror must too. (The
|
||||||
|
// type-0 writer only re-bases projectedOrigin in the sim-state-2
|
||||||
|
// transition, not the normal walk case, so do it here.)
|
||||||
|
if (s_gaitMirrorSend)
|
||||||
|
{
|
||||||
|
projectedOrigin.linearPosition = localOrigin.linearPosition;
|
||||||
|
projectedOrigin.angularPosition = localOrigin.angularPosition;
|
||||||
|
}
|
||||||
if (getenv("BT_WIRE"))
|
if (getenv("BT_WIRE"))
|
||||||
DEBUG_STREAM << "[tx0] errSq=" << error.LengthSquared()
|
DEBUG_STREAM << "[tx0] errSq=" << error.LengthSquared()
|
||||||
<< " posDb=" << posDb
|
<< " posDb=" << posDb
|
||||||
|
|||||||
Reference in New Issue
Block a user