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
+30 -30
View File
@@ -212,13 +212,13 @@ template<class...A> inline void *FUN_00406db4(A&&...) { return 0; } // ResourceF
// @0x530 standSpeed = last-keyframe stride of the stand->walk clip
// @0x534 walkStrideLength = (s_6+s_7)/(d_6+d_7) (forward walk/run)
// @0x538 reverseSpeedMax = last-keyframe stride of the reverse-base clip
// @0x53c jumpRunSpeedMax = last-keyframe stride of the run-jump clip
// @0x540 jumpWalkSpeedMax = last-keyframe stride of the walk-jump clip
// @0x544 jumpRunStrideLength = s/d of fall-forward jump clip (slot 0x18)
// @0x548 jumpWalkStrideLength = s/d of fall-backward jump clip (slot 0x19)
// @0x580 jumpCapable (int) set 1 iff the model defines the jump clip set
// @0x584 hasReverseGimpSet (int)? set 1 iff the second optional clip set exists
// @0x588 hasCrashSet (int)? set 1 iff the third optional clip set exists
// @0x53c gimpLeftSpeedMax = last-keyframe stride of the run-jump clip
// @0x540 gimpRightSpeedMax = last-keyframe stride of the walk-jump clip
// @0x544 gimpLeftStrideLength = s/d of fall-forward jump clip (slot 0x18)
// @0x548 gimpRightStrideLength = s/d of fall-backward jump clip (slot 0x19)
// @0x580 hasGimpClips (int) set 1 iff the model defines the jump clip set
// @0x584 squatCapable (int)? set 1 iff the second optional clip set exists
// @0x588 turnCapable (int)? set 1 iff the third optional clip set exists
// @0x5b8 groundCycleRate forward-cycle slew rate, on-ground (-> 0x344)
// @0x5bc airborneCycleRate forward-cycle slew rate, airborne (-> 0x344)
// @0x5c4 gyroRumbleTimer (float) reset to 0 at the top of a clip load
@@ -319,7 +319,7 @@ void
// stride @0x534, reverseSpeedMax @0x538, reverse stride @0x34c, gimpSpeedMax
// @0x52c, gimp stride @0x350), then probe for the three OPTIONAL clip sets
// (jump / reverse-gimp / crash) and, if their marker resource exists, resolve
// those too and set the matching capability flag (jumpCapable @0x580 etc.).
// those too and set the matching capability flag (hasGimpClips @0x580 etc.).
//###########################################################################
//###########################################################################
void
@@ -406,10 +406,10 @@ void
// ---- OPTIONAL clip set 1: JUMP JETS ----
// Probe for the run-jump marker; if present, the model is jump-capable.
//
jumpCapable = 0; // this+0x580
hasGimpClips = 0; // this+0x580
if (ResolveAnimationClip(prefix, "wgl") != 0) // FUN_00406ff8 probe (real)
{
jumpCapable = 1;
hasGimpClips = 1;
// SLOT MAP BINARY-VERIFIED (raw :13787-13812 + .data @0050d921-35):
// wgl->0x624 [22], wgr->0x628 [23], ggr->0x62c [24], ggl->0x630 [25],
@@ -417,19 +417,19 @@ void
// shifted the rest one slot low.)
animationClips[22] = *ResolveAnimationClip(prefix, "wgl"); // 0x624 state 0x16
legAnimation.SelectSequence(animationClips[22], PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740);
jumpRunSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
gimpLeftSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
animationClips[23] = *ResolveAnimationClip(prefix, "wgr"); // 0x628 state 0x17
legAnimation.SelectSequence(animationClips[23], PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c);
jumpWalkSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
gimpRightSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
animationClips[24] = *ResolveAnimationClip(prefix, "ggr"); // 0x62c state 0x18
MeasureClipStride(0x18, &s0, &d0);
jumpRunStrideLength = s0 / d0; // 0x544
gimpLeftStrideLength = s0 / d0; // 0x544
animationClips[25] = *ResolveAnimationClip(prefix, "ggl"); // 0x630 state 0x19
MeasureClipStride(0x19, &s0, &d0);
jumpWalkStrideLength = s0 / d0; // 0x548
gimpRightStrideLength = s0 / d0; // 0x548
animationClips[26] = *ResolveAnimationClip(prefix, "gsl"); // 0x634 state 0x1A
animationClips[27] = *ResolveAnimationClip(prefix, "gsr"); // 0x638 state 0x1B
@@ -443,10 +443,10 @@ void
// suffixes/slots and stored into side members SetLeg/BodyAnimation never
// consult -- states 2/3 read animationClips[] directly.)
//
hasReverseGimpSet = 0; // this+0x584 (squatCapable)
squatCapable = 0; // this+0x584 (squatCapable)
if (ResolveAnimationClip(prefix, "squ") != 0)
{
hasReverseGimpSet = 1;
squatCapable = 1;
animationClips[3] = *ResolveAnimationClip(prefix, "squ"); // 0x5d8
animationClips[2] = *ResolveAnimationClip(prefix, "sqd"); // 0x5d4
}
@@ -457,14 +457,14 @@ void
// flag @0x588 (turnCapable); loads trn -> 0x5dc == animationClips[4] --
// the state-4 turn-in-place clip (SetLegAnimation(4) arms it).
//
hasCrashSet = 0; // this+0x588 (turnCapable)
turnCapable = 0; // this+0x588 (turnCapable)
if (ResolveAnimationClip(prefix, "trn") != 0)
{
hasCrashSet = 1;
turnCapable = 1;
animationClips[4] = *ResolveAnimationClip(prefix, "trn"); // 0x5dc
}
DEBUG_STREAM << "[loadclips] end: fScale=" << forwardThrottleScale
<< " gimpSpeedMax=" << gimpSpeedMax << " jumpCapable=" << jumpCapable << "\n" << std::flush;
<< " gimpSpeedMax=" << gimpSpeedMax << " hasGimpClips=" << hasGimpClips << "\n" << std::flush;
}
@@ -549,25 +549,25 @@ void
//
// ---- OPTIONAL clip set 1: JUMP JETS (probe "wgli") ----
//
jumpCapable = 0; // this+0x580
hasGimpClips = 0; // this+0x580
if (ResolveAnimationClip(prefix, "wgli") != 0)
{
jumpCapable = 1;
hasGimpClips = 1;
animationClips[22] = *ResolveAnimationClip(prefix, "wgli"); // 0x624 state 0x16
legAnimation.SelectSequence(animationClips[22], PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740);
jumpRunSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
gimpLeftSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
animationClips[23] = *ResolveAnimationClip(prefix, "wgri"); // 0x628 state 0x17
legAnimation.SelectSequence(animationClips[23], PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c);
jumpWalkSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
gimpRightSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
animationClips[24] = *ResolveAnimationClip(prefix, "ggri"); // 0x62c state 0x18
MeasureClipStride(0x18, &s0, &d0);
jumpRunStrideLength = s0 / d0; // 0x544
gimpLeftStrideLength = s0 / d0; // 0x544
animationClips[25] = *ResolveAnimationClip(prefix, "ggli"); // 0x630 state 0x19
MeasureClipStride(0x19, &s0, &d0);
jumpWalkStrideLength = s0 / d0; // 0x548
gimpRightStrideLength = s0 / d0; // 0x548
animationClips[26] = *ResolveAnimationClip(prefix, "gsli"); // 0x634 state 0x1A
animationClips[27] = *ResolveAnimationClip(prefix, "gsri"); // 0x638 state 0x1B
@@ -576,10 +576,10 @@ void
//
// ---- OPTIONAL clip set 2: SQUAT (probe "squi") ----
//
hasReverseGimpSet = 0; // this+0x584 (squatCapable)
squatCapable = 0; // this+0x584 (squatCapable)
if (ResolveAnimationClip(prefix, "squi") != 0)
{
hasReverseGimpSet = 1;
squatCapable = 1;
animationClips[3] = *ResolveAnimationClip(prefix, "squi"); // 0x5d8
animationClips[2] = *ResolveAnimationClip(prefix, "sqdi"); // 0x5d4
}
@@ -587,14 +587,14 @@ void
//
// ---- OPTIONAL clip set 3: TURN-IN-PLACE (probe "trni") ----
//
hasCrashSet = 0; // this+0x588 (turnCapable)
turnCapable = 0; // this+0x588 (turnCapable)
if (ResolveAnimationClip(prefix, "trni") != 0)
{
hasCrashSet = 1;
turnCapable = 1;
animationClips[4] = *ResolveAnimationClip(prefix, "trni"); // 0x5dc
}
DEBUG_STREAM << "[loadclips] INTERIOR ('i') set: fScale=" << forwardThrottleScale
<< " walkStride=" << walkStrideLength << " jumpCapable=" << jumpCapable
<< " walkStride=" << walkStrideLength << " hasGimpClips=" << hasGimpClips
<< "\n" << std::flush;
}