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.