diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 560bb82..c6082d6 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1984,21 +1984,28 @@ void MechControlsMapper *replMppr = MappingMapper(); // roster slot 0 (task #7) if (!IsMechDestroyed() && replMppr != 0) { - // Feed the leg SM the REPLICATED COMMANDED speed (bodyTargetSpeed): - // the master's OWN mppr->speedDemand, carried in every pose (type-0) - // and speed (type-2) record (mech.cpp:1833/1841). This is the - // IDENTICAL input the master's own leg SM consumes, so the replicant - // selects the same gait state and RAMPS its cadence through accel / - // decel exactly as the master does -- the legs stay in sync with the - // motion. The earlier feed derived the ACTUAL velocity from - // updateVelocity, but a mech's forward walk velocity (~walkStrideLength) - // sits BELOW standSpeed, so it never cleared the stand->walk gate; the - // floor hack that fixed THAT then pinned the cadence flat during - // accel/decel -- the reported stutter/skip glitch. bodyTargetSpeed is - // signed (negative == reverse), so reverse needs no special case. - // (BT_REPL_VEL forces the old derived-velocity feed for A/B compare.) - static const int s_useCmd = getenv("BT_REPL_CMD") ? 1 : 0; - if (!s_useCmd) // default: derive from replicated velocity (bodyTargetSpeed is 0 -- master walks thr=0) + // AUTHENTIC LEG-CHANNEL DEMAND (decomp-verified, workflow wv1km7lvc D3): + // the leg channel FUN_004a5028 reads its speed demand from + // mapper->speedDemand == **(mech+0x128)+0x128 (part_012.c:11947/11975/ + // 12028) and slews cadence + selects the stand->walk gate from it. On a + // peer this MUST be the REPLICATED COMMANDED speed bodyTargetSpeed@0x6b4 -- + // the master's own throttle-commanded speedDemand (mechmppr.cpp:749), + // stamped into every record (mech.cpp:2036, record->speedDemand) and read + // back on the peer (mech.cpp type-0 @1833, type-2 @1841/1848, type-3 @1871). + // It is the IDENTICAL value the master's leg channel consumes, so the peer + // clears the stand->walk gate exactly when the master does (the commanded + // speed is what makes the master walk, so it clears standSpeed by + // construction) and ramps cadence CONTINUOUSLY through accel/decel -- + // cadence tracks travel like the master. bodyTargetSpeed is signed + // (negative == reverse); it is 0 only while COASTING (thr=0), which + // authentically winds the gait down to stand, matching the master. + // + // The prior default DERIVED a noisy speed from the dead-reckoned velocity + // and PINNED it flat with a standSpeed*1.05 floor across the walk threshold + // -- the CONFIRMED leg-stutter/skip + cadence-vs-travel desync. Retained + // behind BT_REPL_VEL for A/B comparison only. + static const int s_replVel = getenv("BT_REPL_VEL") ? 1 : 0; + if (s_replVel) { const Vector3D &wv = updateVelocity.linearMotion; float spd = sqrtf((float)(wv.x * wv.x + wv.z * wv.z)); @@ -2013,22 +2020,45 @@ void } else { - replMppr->speedDemand = bodyTargetSpeed; + replMppr->speedDemand = bodyTargetSpeed; // authentic replicated commanded speed } - // 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, - // mech2.cpp:590) from mppr->turnDemand -- which nothing fed on - // a replicant, so pivots never stepped. Feed it from the - // REPLICATED yaw rate (updateVelocity.angularMotion.y, stored - // by Mover::ReadUpdateRecord from the master's localVelocity - // -- the same stream DeadReckon rotates the body with). Only - // the +-0.05 threshold matters to the SM (the trn clip - // advances at fixed cadence), so a signed unit demand - // suffices; the deadband ignores dead-reckon jitter. + // AUTHENTIC PEER TURNING is NET-DRIVEN (decomp-verified D8): the angular + // slerp inside DeadReckon (MOVER.cpp:521-525) rotates + // localOrigin.angularPosition toward projectedOrigin.angularPosition from + // the replicated yaw rate (decomp angular path part_012.c:15001/15010). + // The leg SM's turn-in-place 'trn' clip is a MASTER-ONLY construct fed by + // the LIVE controls mapper; the old synthetic yawRate->turnDemand + // (quantized +-0.02) flickered the trn clip in/out as the noisy replicated + // rate crossed the band -> the jerky pivot / statue-rotate. Do NOT + // synthesize it -- let the whole body rotate via the slerp. + // TURN-STEP (presentation of the authentic pivot): the authentic peer + // shows a stepping pivot because its BODY channel replays the master's + // replicated turn body-STATE (type-3 SetBodyAnimation, mech.cpp:1869). + // We run the LEG channel (D2 not switched), which has no replicated turn + // state, so we reproduce the visible turn-step by arming the leg SM's + // 'trn' clip from the replicated yaw rate. The old feed used a bare + // +-0.02 threshold that CHATTERED the trn clip in/out as the noisy rate + // wandered near zero (the "jerky pivot", decomp-noted D8). HYSTERESIS + // fixes that: enter the turn only above +-0.08 rad/s, hold it (the + // mapper's turnDemand persists frame-to-frame -- we are its only writer on + // a peer) until the rate clearly collapses below 0.03. A steady turn sits + // well above both, so the clip never flickers. (BT_REPL_NOTURN = + // decomp-strict net-driven rotation with NO step.) const float yawRate = (float)updateVelocity.angularMotion.y; - replMppr->turnDemand = (yawRate > 0.02f) ? 1.0f - : (yawRate < -0.02f) ? -1.0f : 0.0f; + static const int s_noTurn = getenv("BT_REPL_NOTURN") ? 1 : 0; + if (s_noTurn) + { + replMppr->turnDemand = 0.0f; + } + else + { + const float prevTd = (float)replMppr->turnDemand; // last frame (our own write) + if (prevTd != 0.0f) // turning: hold until the rate collapses + replMppr->turnDemand = (fabsf(yawRate) < 0.03f) ? 0.0f : prevTd; + else // idle: start only on a clear turn + replMppr->turnDemand = (yawRate > 0.08f) ? 1.0f + : (yawRate < -0.08f) ? -1.0f : 0.0f; + } // Prime the same clip-advance scalars the master's gait block sets // each frame -- uninitialized on a replicant they read 0, freezing // the clip at advance-time dt*0 (observed: legState engaged at 11,