the visible limp (#78): there was never a jump-jet clip set -- the 'Airborne' drivers ARE the gimp gait machines

The port had FUN_004a5bf8/71f4 fully reconstructed as AdvanceBody/Leg-
AnimationAirborne, gated on (MovementMode()==3||4) && jumpCapable@0x580 and
believed dead ("the test mech never jumps").  The binary says otherwise:
mech+0x40 in that gate is the graphicAlarm LEVEL (3=left-leg gimp, 4=right)
and +0x580 is hasGimpClips, set by the conditional loader block that probes
'wgl' and fills clip slots 22-27 (wgl/wgr/ggr/ggl/gsl/gsr) plus the four
measurements at 0x53c-0x548 (wg entry strides = speed caps, gg cycle strides).
Renamed the five jump* members + both drivers accordingly.

New: GimpBodyClipFinished @004a6344 / GimpLegClipFinished @004a7970 -- the
gimp transition machines, branched from the normal finished-callbacks.  Phase-
correct limp entry (left-gimp enters 0x16/wgl only from a RIGHT step, right-
gimp 0x17/wgr from a LEFT step), gg cycles at gimp cadence, gs exits, and the
demand clamp to the gimped side's speed cap (leg cb writes it back into the
mapper -- the binary's authentic slowdown; the T3 x0.5 stand-in in mechmppr is
retired, BT_GIMP_SPEED now defaults 1.0).  The binary's gimp machines have no
reverse entry -- the "reverse disabled" behavior is now binary-proven.

Reviving the dead drivers replayed two port-glue bugs (gotcha #24): the raw
*(controlSource) mapper read (null -> crash at first engagement) and the
missing alarm->member state re-sync (machine pinned in one run state).  Both
fixed; bench harness gained BT_SELF_DAMAGE_TICKS=<n> to hold a zone past
LegHalfStructure without destroying it.

Bench-verified (madcat, novice, zone 16): crossing -> alarm 4 -> wgr entry
from a left step -> 8k+ frames stable in the ggl limp cycle at cadence 14.77
(vs 18.5 walk / 22+ run) with raw demand still 50.  [T2]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-29 15:49:39 -05:00
co-authored by Claude Fable 5
parent 520f6eecd3
commit 36f68718c2
9 changed files with 506 additions and 127 deletions
+329 -52
View File
@@ -19,8 +19,8 @@
//
// @004a5028 Mech::AdvanceLegAnimation (1543 bytes)
// @004a5678 Mech::AdvanceBodyAnimation (1333 bytes)
// @004a5bf8 Mech::AdvanceBodyAnimationAirborne(1792 bytes)
// @004a71f4 Mech::AdvanceLegAnimationAirborne (1840 bytes)
// @004a5bf8 Mech::AdvanceBodyAnimationGimp(1792 bytes)
// @004a71f4 Mech::AdvanceLegAnimationGimp (1840 bytes)
//
// The embedded assert path on every one of them is
// "d:\tesla\bt\bt\MECH2.CPP"
@@ -180,10 +180,10 @@ enum MechAnimationState
// @0x530 standSpeed "at rest" / minimum move speed threshold
// @0x534 walkStrideLength forward walk/run clip length (also speed cap)
// @0x538 reverseSpeedMax speed cap while decelerating into Reverse (?)
// @0x53c jumpRunSpeedMax airborne speed cap, movementMode==3
// @0x540 jumpWalkSpeedMax airborne speed cap, otherwise
// @0x544 jumpRunStrideLength airborne clip length, movementMode==3
// @0x548 jumpWalkStrideLength airborne clip length, otherwise
// @0x53c gimpLeftSpeedMax airborne speed cap, movementMode==3
// @0x540 gimpRightSpeedMax airborne speed cap, otherwise
// @0x544 gimpLeftStrideLength airborne clip length, movementMode==3
// @0x548 gimpRightStrideLength airborne clip length, otherwise
// @0x598 motionEventName (string) cleared to "" on fall/reset
// @0x5a4 motionEventArmed (int) reset to 0 on fall/reset
// @0x5a8 globalTimeScale multiplies every clip increment
@@ -313,10 +313,16 @@ Scalar
Scalar
Mech::BodyClipFinished(Mech *m, unsigned /*a2*/, Scalar carryover, int mj)
{
// airborne branch (movementMode 3/4 && jumpCapable) -> FUN_004a6344 (deferred; safe
// no-op while grounded -- the test mech never jumps).
if ((m->MovementMode() == 3 || m->MovementMode() == 4) && m->jumpCapable)
return 0.0f;
// GIMP branch (FUN_004a6d8c top): a limping mech's body transitions run the
// gimp machine instead. Mode = the graphicAlarm level (the binary's real
// mech+0x40: 3=left-leg gimp, 4=right) read via the mechdmg bridge --
// NEVER graphicAlarm directly here (AlarmIndicator ODR split, gotcha #23).
{
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (the TU-safe read)
const int gl = BTMechGimpLevel(m);
if ((gl == 3 || gl == 4) && m->hasGimpClips)
return m->GimpBodyClipFinished(carryover, mj);
}
const Scalar fcr = m->forwardCycleRate; // 0x344
const Scalar gts = m->globalTimeScale; // 0x5a8
@@ -454,9 +460,14 @@ Scalar
Scalar
Mech::LegClipFinished(Mech *m, unsigned /*a2*/, Scalar carryover, int mj)
{
// airborne branch (movementMode 3/4 && jumpCapable) -> FUN_004a7970 (deferred).
if ((m->MovementMode() == 3 || m->MovementMode() == 4) && m->jumpCapable)
return 0.0f;
// GIMP branch (FUN_004a6928 top): route a limping mech's leg transitions
// into the gimp machine (mode = graphicAlarm level via bridge, gotcha #23).
{
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (the TU-safe read)
const int gl = BTMechGimpLevel(m);
if ((gl == 3 || gl == 4) && m->hasGimpClips)
return m->GimpLegClipFinished(carryover);
}
// The binary reads edx = *(mech+0x128) then [edx]+0x128: subsystemArray[0]
// (the roster's ControlsMapper slot 0) -> speedDemand; null (no mapper)
@@ -546,6 +557,250 @@ Scalar
}
//###########################################################################
//###########################################################################
// GimpBodyClipFinished / GimpLegClipFinished (#78 visible limp)
//
// @004a6344 (body) / @004a7970 (leg) -- the GIMP transition machines, entered
// from the normal finished-callbacks when (gimpLevel 3|4) && hasGimpClips.
// Same walk/run/reverse alternation as the normal cbs, PLUS:
// - a top clamp of the demand to the gimped leg's speed cap (body: clamps
// bodyTargetSpeed@0x6b4; leg: clamps the LIVE mapper speedDemand and
// writes it back -- the binary's own "you can't outrun a shot leg");
// - PHASE-CORRECT limp entry from the walk cases: left-gimp (mode 3) enters
// 0x16/wgl only from a RIGHT step (5/6/0xe) -- the L case forces one more
// normal R step first; right-gimp (mode 4) mirrors into 0x17/wgr from the
// L case (7/0xf). The limp always starts on the correct foot.
// - the gg cycles 0x16/0x18 (left, ggr clip, stride @0x544) and 0x17/0x19
// (right, ggl, @0x548), continuing at gimp cadence or exiting through the
// gs transitions 0x1a/0x1b when the demand dies;
// - NO standing->reverse entry exists in the gimp machines (or drivers):
// the binary itself refuses REVERSE while gimped.
// Mode is the graphicAlarm level via the mechdmg bridge (gotcha #23).
//###########################################################################
//###########################################################################
Scalar
Mech::GimpBodyClipFinished(Scalar carryover, int mj)
{
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (TU-safe)
const int mode = BTMechGimpLevel(this); // binary: this+0x40
const int state = bodyAnimationState; // 0x728
const Scalar fcr = forwardCycleRate; // 0x344
const Scalar gts = globalTimeScale; // 0x5a8
// Top clamp (0x4a6352): while in a moving cycle, cap the body target speed
// to the gimped side's entry-clip speed, then floor at zero.
if ((unsigned)(state - 6) < 2 || (unsigned)(state - 0x0c) < 2
|| (unsigned)(state - 0x12) < 2)
{
const Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax; // 0x53c / 0x540
if (bodyTargetSpeed > cap) bodyTargetSpeed = cap;
if (bodyTargetSpeed < ZeroSpeed) bodyTargetSpeed = ZeroSpeed;
}
const Scalar cyc = bodyCycleSpeed; // 0x6b8
const Scalar tgt = bodyTargetSpeed; // 0x6b4 (post-clamp)
const Scalar T = carryover * gts; // plain end-tail time
int next;
switch (state)
{
default: // 0/1 + unmapped: parked
return 0.0f;
case 2:
bodyStateAlarm.SetLevel(1); return 0.0f;
case 3: case 4: case 8: case 9: case 0x14: case 0x15:
case 0x1a: case 0x1b: case 0x20:
bodyStateAlarm.SetLevel(0); return 0.0f;
// -- walk-R (0x4a6440): states 5,6,0xe -- the left-gimp entry point --
case 5: case 6: case 0xe:
{
bool cont = (tgt >= standSpeed) || ((cyc - fcr * carryover) >= standSpeed);
if (!cont) { next = 9; break; } // walk->stand L
bool up = (tgt > walkStrideLength) && ((fcr * carryover + cyc) > walkStrideLength);
if (up) { next = 0xb; break; } // toward run (dead once clamped)
int alt = 7; // normal alternation -> wwl
if (mode == 4) alt = 7; // right-gimp: one more normal step
if (mode == 3) alt = 0x16; // left-gimp: enter wgl on this R step
return BodyTransition(alt, carryover * cyc * gts / walkStrideLength, mj); // LAB_004a6505
}
// -- walk-L (0x4a63ff): states 7,0xf -- the right-gimp entry point --
case 7: case 0xf:
{
bool cont = (tgt >= standSpeed) || ((cyc - fcr * carryover) >= standSpeed);
if (!cont) { next = 8; break; } // walk->stand R
bool up = (tgt > walkStrideLength) && ((fcr * carryover + cyc) > walkStrideLength);
if (up) { next = 0xa; break; }
int alt = 6; // normal alternation -> wwr
if (mode == 3) alt = 6; // left-gimp: force back to the R step
if (mode == 4) alt = 0x17; // right-gimp: enter wgr on this L step
return BodyTransition(alt, carryover * cyc * gts / walkStrideLength, mj);
}
// -- run cycles (0x4a65f7 / 0x4a6663): 10/12 <-> 11/13 --
case 0xa: case 0xc:
{
bool cont = (tgt >= reverseSpeedMax) || ((cyc - fcr * carryover) >= reverseSpeedMax);
if (cont) // LAB_004a6648 run-cadence tail
return BodyTransition(0xd, carryover * cyc * gts / reverseStrideLength, mj);
next = 0xf; break; // run->walk R
}
case 0xb: case 0xd:
{
bool cont = (tgt >= reverseSpeedMax) || ((cyc - fcr * carryover) >= reverseSpeedMax);
if (cont)
return BodyTransition(0xc, carryover * cyc * gts / reverseStrideLength, mj);
next = 0xe; break;
}
// -- back cycles (0x4a66d5 / 0x4a6754): 0x10/0x12 <-> 0x11/0x13 --
case 0x10: case 0x12:
{
bool up = (tgt > gimpSpeedMax) && ((gimpCycleRate * carryover + cyc) > gimpSpeedMax);
if (up) { next = 0x15; break; } // back->stand L
Scalar t = carryover * cyc * gts / gimpStrideLength;
if (t <= 0.0f) t = -t; // 0x350 stored negative
return BodyTransition(0x13, t, mj);
}
case 0x11: case 0x13:
{
bool up = (tgt > gimpSpeedMax) && ((gimpCycleRate * carryover + cyc) > gimpSpeedMax);
if (up) { next = 0x14; break; }
Scalar t = carryover * cyc * gts / gimpStrideLength;
if (t <= 0.0f) t = -t;
return BodyTransition(0x12, t, mj);
}
// -- GIMP cycles (0x4a67cf / 0x4a6836): left 0x16/0x18, right 0x17/0x19 --
case 0x16: case 0x18:
{
bool cont = (tgt >= gimpLeftSpeedMax) || ((cyc - fcr * carryover) >= gimpLeftSpeedMax);
if (cont) // keep limping (ggr cycle)
return BodyTransition(0x18, carryover * cyc * gts / gimpLeftStrideLength, mj);
next = 0x1a; break; // demand died -> gsl exit
}
case 0x17: case 0x19:
{
bool cont = (tgt >= gimpRightSpeedMax) || ((cyc - fcr * carryover) >= gimpRightSpeedMax);
if (cont)
return BodyTransition(0x19, carryover * cyc * gts / gimpRightStrideLength, mj);
next = 0x1b; break; // -> gsr exit
}
}
return BodyTransition(next, T, mj); // shared plain tail
}
Scalar
Mech::GimpLegClipFinished(Scalar carryover)
{
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (TU-safe)
const int mode = BTMechGimpLevel(this); // binary: this+0x40
const int state = legAnimationState; // 0x3b0
MechControlsMapper *mppr = MappingMapper(); // **(this+0x128)
// Top clamp (0x4a7995): cap the LIVE COMMANDED speedDemand itself while in
// a moving cycle, writing the clamp back -- this is the binary's authentic
// gimp slowdown (the mapper re-derives demand each tick; this cb re-caps
// it every clip end, and the gimp cycle cadence below enforces it anyway).
if (((unsigned)(state - 6) < 2 || (unsigned)(state - 0x0c) < 2
|| (unsigned)(state - 0x12) < 2) && mppr != 0)
{
const Scalar cap = (mode == 3) ? gimpLeftSpeedMax : gimpRightSpeedMax; // 0x53c / 0x540
if (mppr->speedDemand > cap) mppr->speedDemand = cap;
if (mppr->speedDemand < ZeroSpeed) mppr->speedDemand = ZeroSpeed;
}
const Scalar spd = (mppr != 0) ? mppr->speedDemand : 0.0f; // (binary derefs; port guards null)
const Scalar fcr = forwardCycleRate; // 0x344
const Scalar gts = globalTimeScale; // 0x5a8
const Scalar cyc = legCycleSpeed; // 0x348
const Scalar T = carryover * gts;
int next;
switch (state) // all leg tails mj=1 (binary)
{
default:
return 0.0f;
case 2:
legStateAlarm.SetLevel(1); return 0.0f;
case 3: case 4: case 8: case 9: case 0x14: case 0x15:
case 0x1a: case 0x1b: case 0x20:
legStateAlarm.SetLevel(0); return 0.0f;
// -- walk-R (0x4a7a52): states 5,6,0xe -- left-gimp entry --
case 5: case 6: case 0xe:
{
bool cont = (spd >= standSpeed) || ((cyc - fcr * carryover) >= standSpeed);
if (!cont) { next = 9; break; }
bool up = (spd > walkStrideLength) && ((fcr * carryover + cyc) > walkStrideLength);
if (up) { next = 0xb; break; }
int alt = 7;
if (mode == 4) alt = 7; // right-gimp: one more normal step
if (mode == 3) alt = 0x16; // left-gimp: enter wgl on this R step
return LegTransition(alt, carryover * cyc * gts / walkStrideLength, 1); // LAB_004a7b38
}
// -- walk-L (0x4a7bb5): states 7,0xf -- right-gimp entry --
case 7: case 0xf:
{
bool cont = (spd >= standSpeed) || ((cyc - fcr * carryover) >= standSpeed);
if (!cont) { next = 8; break; }
bool up = (spd > walkStrideLength) && ((fcr * carryover + cyc) > walkStrideLength);
if (up) { next = 0xa; break; }
int alt = 6;
if (mode == 3) alt = 6; // left-gimp: force back to the R step
if (mode == 4) alt = 0x17; // right-gimp: enter wgr on this L step
return LegTransition(alt, carryover * cyc * gts / walkStrideLength, 1);
}
// -- run cycles (0x4a7c33 / 0x4a7c93): --
case 0xa: case 0xc:
{
bool cont = (spd >= reverseSpeedMax) || ((cyc - fcr * carryover) >= reverseSpeedMax);
if (cont) // LAB_004a7c79
return LegTransition(0xd, carryover * cyc * gts / reverseStrideLength, 1);
next = 0xf; break;
}
case 0xb: case 0xd:
{
bool cont = (spd >= reverseSpeedMax) || ((cyc - fcr * carryover) >= reverseSpeedMax);
if (cont)
return LegTransition(0xc, carryover * cyc * gts / reverseStrideLength, 1);
next = 0xe; break;
}
// -- back cycles: 0x10/0x12 <-> 0x11/0x13 --
case 0x10: case 0x12:
{
bool up = (spd > gimpSpeedMax) && ((gimpCycleRate * carryover + cyc) > gimpSpeedMax);
if (up) { next = 0x15; break; }
Scalar t = carryover * cyc * gts / gimpStrideLength;
if (t <= 0.0f) t = -t;
return LegTransition(0x13, t, 1);
}
case 0x11: case 0x13:
{
bool up = (spd > gimpSpeedMax) && ((gimpCycleRate * carryover + cyc) > gimpSpeedMax);
if (up) { next = 0x14; break; }
Scalar t = carryover * cyc * gts / gimpStrideLength;
if (t <= 0.0f) t = -t;
return LegTransition(0x12, t, 1);
}
// -- GIMP cycles: left 0x16/0x18, right 0x17/0x19 --
case 0x16: case 0x18:
{
bool cont = (spd >= gimpLeftSpeedMax) || ((cyc - fcr * carryover) >= gimpLeftSpeedMax);
if (cont)
return LegTransition(0x18, carryover * cyc * gts / gimpLeftStrideLength, 1);
next = 0x1a; break;
}
case 0x17: case 0x19:
{
bool cont = (spd >= gimpRightSpeedMax) || ((cyc - fcr * carryover) >= gimpRightSpeedMax);
if (cont)
return LegTransition(0x19, carryover * cyc * gts / gimpRightStrideLength, 1);
next = 0x1b; break;
}
}
return LegTransition(next, T, 1); // shared plain tail (mj=1)
}
//###########################################################################
//###########################################################################
// AdvanceLegAnimation (channel A, ground)
@@ -664,7 +919,7 @@ Scalar
// narrow near-zero entry gate (the pre-#64b accommodation). [T3]
const int trnIsRepl = (GetInstance() == ReplicantInstance);
const Scalar trnEntryMax = trnIsRepl ? standSpeed * 0.25f : standSpeed;
if (hasCrashSet != 0 && mppr != 0
if (turnCapable != 0 && mppr != 0
&& commandedSpeed >= ZeroSpeed && commandedSpeed <= trnEntryMax
&& (mppr->turnDemand > 0.05f
|| mppr->turnDemand < -0.05f)
@@ -1112,38 +1367,47 @@ Scalar
//###########################################################################
//###########################################################################
// AdvanceBodyAnimationAirborne (channel B, jump-capable)
// AdvanceBodyAnimationGimp (channel B, limping)
//
// @004a5bf8 (MECH2.CPP:0x2E8)
//
// Airborne variant of AdvanceBodyAnimation selected by Mech::IntegrateMotion
// when jump jets are active. Adds a pre-clamp of bodyTargetSpeed to the jump
// speed limits and handles the FallForward / FallBackward jump cycles
// (states 0x18/0x19); gimp-to-stand (0x16/0x17) and the lateral falls
// (0x1a/0x1b) join the normal advance group here rather than resetting.
// GIMP (limp-gait) variant of AdvanceBodyAnimation, selected when
// (gimpLevel 3|4) && hasGimpClips ("Airborne"/jump-jet was a misread -- the
// clip set is wg/gg/gs, #78). Adds a pre-clamp of bodyTargetSpeed to the
// gimped side's speed cap and drives the gg limp cycles (0x18 left / 0x19
// right) at gimp cadence; the wg entries (0x16/0x17) and gs exits (0x1a/0x1b)
// join the plain advance group. Standing (case 0) only ever enters FORWARD
// walk -- the binary refuses reverse while gimped.
//###########################################################################
//###########################################################################
Scalar
Mech::AdvanceBodyAnimationAirborne(Scalar time_slice, int loop)
Mech::AdvanceBodyAnimationGimp(Scalar time_slice, int loop)
{
Scalar distance = 0.0f;
// RE-SYNC alarm -> state member (the binary's one cell is split in the
// recon, same as the ground drivers at :831/:1181). Without this the
// member freezes at its pre-gimp value the moment this driver takes over
// and the whole machine pins in one state (live-diagnosed 2026-07-30).
bodyAnimationState = (int)bodyStateAlarm.GetLevel();
//
// While in any forward/reverse/gimp *moving* cycle, clamp the target to
// the jump speed limit for the current gait, then floor at zero.
// the gimp speed limit for the current gait, then floor at zero.
//
{
int state = bodyAnimationState; // this+0x728
if ((unsigned)(state - 6) < 2 || (unsigned)(state - 0x0c) < 2
|| (unsigned)(state - 0x12) < 2)
{
if (MovementMode() == 3) // run jump
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (gotcha #23)
if (BTMechGimpLevel(this) == 3) // left leg gimped
{
if (bodyTargetSpeed > jumpRunSpeedMax) bodyTargetSpeed = jumpRunSpeedMax; // 0x53c
if (bodyTargetSpeed > gimpLeftSpeedMax) bodyTargetSpeed = gimpLeftSpeedMax; // 0x53c
}
else // walk jump
{
if (bodyTargetSpeed > jumpWalkSpeedMax) bodyTargetSpeed = jumpWalkSpeedMax; // 0x540
if (bodyTargetSpeed > gimpRightSpeedMax) bodyTargetSpeed = gimpRightSpeedMax; // 0x540
}
if (bodyTargetSpeed < ZeroSpeed) bodyTargetSpeed = ZeroSpeed;
}
@@ -1259,7 +1523,8 @@ Scalar
case 0x18: case 0x19: // FallForward / FallBackward (jump)
{
Scalar ratio;
if (MovementMode() == 3) // run jump: caps 0x53c / 0x544
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (gotcha #23)
if (BTMechGimpLevel(this) == 3) // left-gimp cycle: caps 0x53c / 0x544
{
if (bodyTargetSpeed <= bodyCycleSpeed)
{
@@ -1267,16 +1532,16 @@ Scalar
{
bodyCycleSpeed -= forwardCycleRate * time_slice;
if (bodyCycleSpeed < bodyTargetSpeed) bodyCycleSpeed = bodyTargetSpeed;
if (bodyCycleSpeed < jumpRunSpeedMax) bodyCycleSpeed = jumpRunSpeedMax; // 0x53c
if (bodyCycleSpeed < gimpLeftSpeedMax) bodyCycleSpeed = gimpLeftSpeedMax; // 0x53c
}
}
else
{
bodyCycleSpeed += forwardCycleRate * time_slice;
if (bodyCycleSpeed > bodyTargetSpeed) bodyCycleSpeed = bodyTargetSpeed;
if (bodyCycleSpeed > jumpRunStrideLength) bodyCycleSpeed = jumpRunStrideLength; // 0x544
if (bodyCycleSpeed > gimpLeftStrideLength) bodyCycleSpeed = gimpLeftStrideLength; // 0x544
}
ratio = bodyCycleSpeed / jumpRunStrideLength; // 0x544
ratio = bodyCycleSpeed / gimpLeftStrideLength; // 0x544
}
else // walk jump: caps 0x540 / 0x548
{
@@ -1286,16 +1551,16 @@ Scalar
{
bodyCycleSpeed -= forwardCycleRate * time_slice;
if (bodyCycleSpeed < bodyTargetSpeed) bodyCycleSpeed = bodyTargetSpeed;
if (bodyCycleSpeed < jumpWalkSpeedMax) bodyCycleSpeed = jumpWalkSpeedMax; // 0x540
if (bodyCycleSpeed < gimpRightSpeedMax) bodyCycleSpeed = gimpRightSpeedMax; // 0x540
}
}
else
{
bodyCycleSpeed += forwardCycleRate * time_slice;
if (bodyCycleSpeed > bodyTargetSpeed) bodyCycleSpeed = bodyTargetSpeed;
if (bodyCycleSpeed > jumpWalkStrideLength) bodyCycleSpeed = jumpWalkStrideLength; // 0x548
if (bodyCycleSpeed > gimpRightStrideLength) bodyCycleSpeed = gimpRightStrideLength; // 0x548
}
ratio = bodyCycleSpeed / jumpWalkStrideLength; // 0x548
ratio = bodyCycleSpeed / gimpRightStrideLength; // 0x548
}
distance = bodyAnimation.Advance(
time_slice * ratio * globalTimeScale, loop);
@@ -1315,24 +1580,36 @@ Scalar
//###########################################################################
//###########################################################################
// AdvanceLegAnimationAirborne (channel A, jump-capable)
// AdvanceLegAnimationGimp (channel A, limping)
//
// @004a71f4 (MECH2.CPP:0x672)
//
// Airborne variant of AdvanceLegAnimation. Like the ground version it reads
// the live commanded speed from the controls subsystem, but here it also
// CLAMPS that source value to the jump speed limit (writing it back), and
// handles the FallForward / FallBackward jump cycles (0x18/0x19). No death
// latch / footstep block; gimp-to-stand and lateral falls join the normal
// advance group.
// GIMP variant of AdvanceLegAnimation (see the body variant's note). Like
// the ground version it reads the live commanded speed from the controls
// subsystem, but here it also CLAMPS that source value to the gimped side's
// speed cap (writing it back -- the authentic "can't outrun a shot leg"),
// and drives the gg limp cycles (0x18/0x19). No death latch / footstep
// block; wg entries and gs exits join the normal advance group.
//###########################################################################
//###########################################################################
Scalar
Mech::AdvanceLegAnimationAirborne(Scalar time_slice)
Mech::AdvanceLegAnimationGimp(Scalar time_slice)
{
ReconMotionSource *motionSource = *(ReconMotionSource **)(this->controlSource); // **(this+0x128)
// Binary: **(this+0x128) + 0x128 = the mapper's live speedDemand. The
// port's controlSource@0x128 is NOT wired (null -> the first live gimp
// engagement crashed here, 2026-07-30 bench) -- use the roster idiom the
// ground driver + LegClipFinished use (MappingMapper), pointing the shim
// straight at the speedDemand cell.
MechControlsMapper *gimpMppr = MappingMapper();
static Scalar s_nullDemand = 0.0f; // no mapper -> demand 0, writes inert
ReconMotionSource *motionSource = gimpMppr
? (ReconMotionSource *)&gimpMppr->speedDemand
: (ReconMotionSource *)&s_nullDemand;
Scalar distance = 0.0f;
int mode = MovementMode(); // this+0x40 = simulationState
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (gotcha #23)
int mode = BTMechGimpLevel(this); // binary this+0x40 = gimp level
// RE-SYNC alarm -> state member (see AdvanceBodyAnimationGimp note).
legAnimationState = (int)legStateAlarm.GetLevel();
int state = legAnimationState; // this+0x3b0
//
@@ -1344,13 +1621,13 @@ Scalar
{
if (mode == 3)
{
if (motionSource->commandedSpeed > jumpRunSpeedMax)
motionSource->commandedSpeed = jumpRunSpeedMax; // 0x53c
if (motionSource->commandedSpeed > gimpLeftSpeedMax)
motionSource->commandedSpeed = gimpLeftSpeedMax; // 0x53c
}
else
{
if (motionSource->commandedSpeed > jumpWalkSpeedMax)
motionSource->commandedSpeed = jumpWalkSpeedMax; // 0x540
if (motionSource->commandedSpeed > gimpRightSpeedMax)
motionSource->commandedSpeed = gimpRightSpeedMax; // 0x540
}
if (motionSource->commandedSpeed < ZeroSpeed)
motionSource->commandedSpeed = ZeroSpeed;
@@ -1477,16 +1754,16 @@ Scalar
{
legCycleSpeed -= forwardCycleRate * time_slice;
if (legCycleSpeed < motionSource->commandedSpeed) legCycleSpeed = motionSource->commandedSpeed;
if (legCycleSpeed < jumpRunSpeedMax) legCycleSpeed = jumpRunSpeedMax;
if (legCycleSpeed < gimpLeftSpeedMax) legCycleSpeed = gimpLeftSpeedMax;
}
}
else
{
legCycleSpeed += forwardCycleRate * time_slice;
if (legCycleSpeed > motionSource->commandedSpeed) legCycleSpeed = motionSource->commandedSpeed;
if (legCycleSpeed > jumpRunStrideLength) legCycleSpeed = jumpRunStrideLength;
if (legCycleSpeed > gimpLeftStrideLength) legCycleSpeed = gimpLeftStrideLength;
}
ratio = legCycleSpeed / jumpRunStrideLength; // 0x544
ratio = legCycleSpeed / gimpLeftStrideLength; // 0x544
}
else // walk jump
{
@@ -1496,16 +1773,16 @@ Scalar
{
legCycleSpeed -= forwardCycleRate * time_slice;
if (legCycleSpeed < motionSource->commandedSpeed) legCycleSpeed = motionSource->commandedSpeed;
if (legCycleSpeed < jumpWalkSpeedMax) legCycleSpeed = jumpWalkSpeedMax;
if (legCycleSpeed < gimpRightSpeedMax) legCycleSpeed = gimpRightSpeedMax;
}
}
else
{
legCycleSpeed += forwardCycleRate * time_slice;
if (legCycleSpeed > motionSource->commandedSpeed) legCycleSpeed = motionSource->commandedSpeed;
if (legCycleSpeed > jumpWalkStrideLength) legCycleSpeed = jumpWalkStrideLength;
if (legCycleSpeed > gimpRightStrideLength) legCycleSpeed = gimpRightStrideLength;
}
ratio = legCycleSpeed / jumpWalkStrideLength; // 0x548
ratio = legCycleSpeed / gimpRightStrideLength; // 0x548
}
distance = legAnimation.Advance(time_slice * ratio * globalTimeScale, 1);
}