MP: FIX replicant 'slides before it steps' -- walk-entry floor on derived demand
Root-caused from a live per-frame [replmov] trace (user's session, where replication works -- the headless rig won't spawn a replicant): the replicant's leg SM sat in Standing (legState 0, legFrm pinned at 19) while dead-reckoning forward at spd=6.13, until the master's speed rose to 6.99, then it snapped to StandToWalk. Cause: the replicant derives speedDemand from the master's ACTUAL velocity, but a mech's forward walk velocity == walkStrideLength, which sits BELOW standSpeed (bhk1: walk 6.13 < stand ~6.8). The leg SM's stand->walk gate is , so a steadily-walking peer's derived demand NEVER crosses it -> the peer slides in the standing pose until the master happens to accelerate past standSpeed. The master never hits this: it feeds the leg SM its COMMANDED throttle speed (>> standSpeed), not the actual velocity. Fix: when the peer is clearly moving forward, floor the derived demand just past standSpeed so it enters the walk cycle immediately; the walk case clamps legCycleSpeed back to walkStrideLength, so cadence still matches travel (no foot-slip). Reverse needs no floor (its gate is commandedSpeed < ZeroSpeed, tripped by any negative). Diag: BT_REPL_MOV (per-frame replicant pos/vel/speed/legState/legFrm + master twin). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> EOF
This commit is contained in:
@@ -1952,7 +1952,25 @@ void
|
||||
localToWorld.GetFromAxis(Z_Axis, &zAxR);
|
||||
const float fdot = -((float)wv.x * (float)zAxR.x
|
||||
+ (float)wv.z * (float)zAxR.z); // mech faces -Z
|
||||
replMppr->speedDemand = (fdot < 0.0f) ? -spd : spd;
|
||||
Scalar sd = (fdot < 0.0f) ? -spd : spd;
|
||||
// WALK-ENTRY FLOOR (user-reported "slides forward before it steps",
|
||||
// root-caused 2026-07-14 from a per-frame [replmov] trace): the leg
|
||||
// SM enters walk only when `standSpeed < commandedSpeed`, but a mech's
|
||||
// forward WALK velocity == walkStrideLength which sits BELOW standSpeed
|
||||
// (bhk1: walk 6.13 < stand ~6.8). The master clears the gate because
|
||||
// it feeds the leg SM its COMMANDED throttle speed (>> standSpeed); the
|
||||
// replicant feeds the DERIVED actual velocity, which never crosses the
|
||||
// gate -- so a steadily-walking peer dead-reckons forward frozen in the
|
||||
// standing pose (legState 0, legFrm pinned) until the master happens to
|
||||
// accelerate past standSpeed. When the peer is clearly moving forward,
|
||||
// floor the demand just past standSpeed so it enters the walk cycle at
|
||||
// once; the walk case (6/7) clamps legCycleSpeed back to walkStrideLength,
|
||||
// so the CADENCE still matches the actual travel (no foot-slip). Reverse
|
||||
// needs no floor -- its entry gate is `commandedSpeed < ZeroSpeed`, which
|
||||
// any negative demand already trips.
|
||||
if (sd > walkStrideLength * 0.5f && sd < standSpeed * 1.1f)
|
||||
sd = standSpeed * 1.1f;
|
||||
replMppr->speedDemand = sd;
|
||||
// REPLICANT TURN-STEP (user-reported: a turning peer
|
||||
// statue-rotates while the local mech steps): the leg SM's
|
||||
// Standing case arms the turn-in-place "trn" clip (state 4,
|
||||
@@ -1997,7 +2015,11 @@ void
|
||||
DEBUG_STREAM << "[repltrn] yawRate=" << yawRate
|
||||
<< " turnDemand=" << replMppr->turnDemand
|
||||
<< " legState=" << (int)legStateAlarm.GetLevel()
|
||||
<< " spd=" << replMppr->speedDemand << "\n" << std::flush;
|
||||
<< " bodyState=" << (int)bodyStateAlarm.GetLevel()
|
||||
<< " legFrm=" << legAnimation.currentFrame
|
||||
<< " bodyFrm=" << bodyAnimation.currentFrame
|
||||
<< " spd=" << replMppr->speedDemand
|
||||
<< " tgt=" << bodyTargetSpeed << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
// PER-FRAME heading trace (BT_REPL_HDG, choppy-spin diagnosis): the
|
||||
@@ -2019,6 +2041,29 @@ void
|
||||
<< " yawRate=" << yawRate << " dt=" << dt << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// PER-FRAME travel/animation coupling probe (BT_REPL_MOV, user:
|
||||
// "slides forward before it steps"): position vs the dead-reckon target
|
||||
// vs the derived speed vs the leg SM state, every frame there is
|
||||
// replicated motion. Reveals travel-vs-animation lag + lerp-gap jitter.
|
||||
if (getenv("BT_REPL_MOV"))
|
||||
{
|
||||
const Scalar uvx = (Scalar)updateVelocity.linearMotion.x;
|
||||
const Scalar uvz = (Scalar)updateVelocity.linearMotion.z;
|
||||
const Scalar umag = (Scalar)sqrtf(uvx*uvx + uvz*uvz);
|
||||
if (umag > 0.01f || replMppr->speedDemand != 0.0f)
|
||||
DEBUG_STREAM << "[replmov]"
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ","
|
||||
<< localOrigin.linearPosition.z << ")"
|
||||
<< " proj=(" << projectedOrigin.linearPosition.x << ","
|
||||
<< projectedOrigin.linearPosition.z << ")"
|
||||
<< " uvel=" << umag
|
||||
<< " spd=" << replMppr->speedDemand
|
||||
<< " legState=" << (int)legStateAlarm.GetLevel()
|
||||
<< " legFrm=" << legAnimation.currentFrame
|
||||
<< " nu-lp=" << (nextUpdate.ticks - lastPerformance.ticks)
|
||||
<< " dt=" << dt << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// REPLICANT BEAMS (task #51): the emitters carry live replicated
|
||||
// discharge state (Emitter::ReadUpdateRecord); draw them with the
|
||||
// same per-weapon walk the master uses.
|
||||
@@ -3136,6 +3181,16 @@ void
|
||||
localVelocity.linearMotion = Vector3D(0.0f, worldLinearVelocity.y, -fwdSpeed);
|
||||
localVelocity.angularMotion = Vector3D(0.0f, turn * authTurnRate, 0.0f);
|
||||
|
||||
// MASTER twin of [replmov]: the velocity this mech PUBLISHES vs its own
|
||||
// leg state -- compare the master ramp to the replicant response.
|
||||
if (getenv("BT_REPL_MOV") && (fwdSpeed > 0.01f || throttle != 0.0f))
|
||||
DEBUG_STREAM << "[mastervel] fwdSpeed=" << fwdSpeed
|
||||
<< " thr=" << throttle
|
||||
<< " legState=" << (int)legStateAlarm.GetLevel()
|
||||
<< " pos=(" << localOrigin.linearPosition.x << ","
|
||||
<< localOrigin.linearPosition.z << ")"
|
||||
<< " dt=" << dt << "\n" << std::flush;
|
||||
|
||||
// CRASH motion suppression (spec: the binary zeroes localVelocity while
|
||||
// crashed). While the stagger clip (legState 0x20) plays, freeze the
|
||||
// velocity so a knocked-down mech does not creep back into the obstacle and
|
||||
|
||||
Reference in New Issue
Block a user