From 936c34c99728ed22fea07a365059418c7674669a Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 14 Jul 2026 10:50:39 -0500 Subject: [PATCH] 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 EOF --- game/reconstructed/mech4.cpp | 59 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index d956306..b480375 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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