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 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user