From c578a9b7904e1bd1707f2e5ebac1a181775722da Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 3 Aug 2026 10:33:05 -0500 Subject: [PATCH] BT410 5.3.95: the stride becomes the speed -- and the hull now breathes with the gait Half of IntegrateMotion's job, taken now because two findings made it safe: THE GAIT SLEW RATE IS THE AUTHORED ACCELERATION. The binary's ctor sets forwardCycleRate, gimpCycleRate and groundCycleRate all from the model's maxAcceleration (rec+0x44 -- 30 for the Mad Cat) and airborneCycleRate from superStopAcceleration. That closes both of yesterday's unsourced-rate worries at once: the ctor default of 1.0 (which would have taken ~27 seconds to reach speed) is retired, and replacing the explicit acceleration model with the stride rate preserves drive feel EXACTLY -- same slew rate, same demand, different (authentic) path for the value to travel. SO: Simulate now takes currentBodySpeed = bodyStride / dt, the binary's own relationship (IntegrateMotion @004ab1c8: local velocity z = -distance/dt). The old acceleration model survives only as a [T3] fallback for a model with no gait clips at all, so unverified fleet mechs stay drivable. LIVE CURVE, arena run, no fault: spd = 7.32 stepping off (stand->walk clip) spd = 26.67 22.16 27.31 30.54 ... the run cycle around the demanded 26.9 The old model pinned spd at exactly 26.9022 every frame. Now the mech steps off slow and its hull speed oscillates within each stride -- heel-strike vs mid-swing. Cadence in the motion is not noise; it is the point. The mech is carried by its animation, which is what "the gait IS the locomotion" meant in the 1995 design. Also queued a correction from the raw decomp: IntegrateMotion's replicant branch writes +0x598 with a VECTOR op, so motionEventName is likely a Point3D rather than the CString the BT411 member map suggested -- flagged in the sidecar for the dead-reckon increment rather than churned now. Still staged from IntegrateMotion: the airborne pick, the dead-reckon fold, the orientation integration, the turn-in-place dispatcher. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/MECH.CPP | 57 ++++++++++++++++--------- restoration/source410/BT/MECH2.NOTES.md | 32 ++++++++++++++ 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 49939d78..b7dc6081 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -546,6 +546,15 @@ Mech::Mech( if (model->maxAcceleration > 1.0f && model->maxAcceleration < 500.0f) { maxBodyAcceleration = model->maxAcceleration; + // + // The gait's slew rates ARE the authored acceleration + // (binary ctor: +0x344/+0x5b0/+0x5b8 all take rec+0x44). + // This is what makes the stride-driven speed model and + // the old acceleration model agree in feel: both slew at + // maxAcceleration toward the demand. + // + forwardCycleRate = model->maxAcceleration; + gimpCycleRate = model->maxAcceleration; } if (model->throttleAdjustment > 0.05f && model->throttleAdjustment < 20.0f) { @@ -1221,29 +1230,39 @@ void // runs as a pure stride measurement (move_joints 0) so the two never // fight over the same joints. // - // STAGED SEAM: the binary's caller is IntegrateMotion (mech4 @004ab1c8), - // which also USES the body distance as the mech's forward step and picks - // the airborne flavours by movement mode. Until mech4 lands, the body - // distance is measured-and-dropped and the acceleration model below - // still carries the hull; the two speeds agree because both track the - // same demand. + // The body channel's measured stride IS the mech's speed (the binary: + // IntegrateMotion @004ab1c8 sets local velocity z = -distance/dt). The + // gait slews its cycle speed at forwardCycleRate == the authored + // maxAcceleration, so this replaces the earlier explicit acceleration + // model at the same rate -- but the speed now rises THROUGH the gait: + // zero while standing, the walk band during the walk cycle, stepping to + // the run band at a clip boundary. + // + // STILL STAGED from IntegrateMotion: the airborne flavour pick, the + // dead-reckon latency fold, and the turn-in-place dispatcher (mech4). //----------------------------------------------------------------------- // AdvanceLegAnimation(time_slice); - AdvanceBodyAnimation(time_slice, 0); - - // - //----------------------------------------------------------------------- - // Accelerate the actual body speed toward the demand (bounded per frame by - // the mech's max acceleration). - //----------------------------------------------------------------------- - // { - Scalar dv = bodyTargetSpeed - currentBodySpeed; - Scalar maxStep = maxBodyAcceleration * time_slice; - if (dv > maxStep) dv = maxStep; - if (dv < -maxStep) dv = -maxStep; - currentBodySpeed += dv; + Scalar stride = AdvanceBodyAnimation(time_slice, 0); + + if (animationClips[5] != ResourceDescription::NullResourceID) + { + currentBodySpeed = stride / time_slice; + } + else + { + // + // [T3 bring-up] A model with NO gait clips would otherwise never + // move. Keep the old acceleration model for those until every + // fleet mech's clip set is verified. + // + Scalar dv = bodyTargetSpeed - currentBodySpeed; + Scalar maxStep = maxBodyAcceleration * time_slice; + if (dv > maxStep) dv = maxStep; + if (dv < -maxStep) dv = -maxStep; + currentBodySpeed += dv; + } } // diff --git a/restoration/source410/BT/MECH2.NOTES.md b/restoration/source410/BT/MECH2.NOTES.md index b8208b14..72946136 100644 --- a/restoration/source410/BT/MECH2.NOTES.md +++ b/restoration/source410/BT/MECH2.NOTES.md @@ -289,3 +289,35 @@ running correctly. No fault, mission drove itself clean. writers is open work (model resource? mech3/mech4?). * `ForceUpdate(8)` (the leg-state update-record request, binary @004a4c54 inline) is a STAGED no-op — our replication emitter is not reconstructed. + +## The stride became the speed (2026-08-03) + +The STAGED seam in `Simulate` is half-closed: the body channel's measured +distance is now the mech's speed (`currentBodySpeed = stride / dt`), which is +the binary's own relationship (`IntegrateMotion` @004ab1c8 sets local velocity +z = −distance/dt). The explicit acceleration model survives only as a [T3] +fallback for a model with no gait clips at all. + +Two findings made this safe rather than speculative: + +**The gait slew rate IS the authored acceleration.** The binary's ctor sets +`forwardCycleRate`, `gimpCycleRate` and `groundCycleRate` all from the model's +maxAcceleration (rec+0x44; 30 for the Mad Cat), and `airborneCycleRate` from +superStopAcceleration. So the stride-driven speed slews at exactly the rate +the old model used — feel preserved, source authentic. Our ctor now sets +`forwardCycleRate`/`gimpCycleRate` from the same guarded model read. (This +also retires the ctor default of 1.0, which would have taken ~27 s to reach +speed.) + +**Live curve, arena run:** first sample 7.32 (stepping off through the +stand→walk clip), then 22–30 oscillating around the demanded 26.9 — the +per-stride speed variation of a real gait, where the old model pinned +26.9022 flat. Cadence in the hull motion is the authentic behaviour. + +Still staged from IntegrateMotion: the airborne flavour pick +(`movementMode 3/4 && +0x580`), the dead-reckon latency fold (+0x778/+0x77c), +the local-velocity/orientation integration proper, and the turn-in-place +dispatcher. One correction queued by the raw decomp: `+0x598` is written by a +VECTOR op in IntegrateMotion's replicant branch (FUN_00408644 on floats), so +`motionEventName` is likely a Point3D, not a CString — revisit when that +branch is reconstructed.