BT410 5.3.103: the INTERIOR clip set, the duck, and why state 4 was always the turn

Three finds from one suffix table, closing the last mech2-family loader:

THE 'i' SET.  The 4-char table at .data:0050d94d is the 3-char gait table
with an 'i' appended -- swri, wwri, ... squi, sqdi, trni.  These are the
INTERIOR clips: the gait as seen from inside the cockpit, animating the
inside-view skeleton (they never bind the hip -- you cannot see your own
hip from the seat).  LoadLocomotionClipsExt (@004a86c8) is byte-identical
to LoadLocomotionClips over that table, and is now its reconstructed twin.

THE VIEW DISPATCH (ctor tail @0x4a1674 region): a REPLICANT loads the
exterior set -- you see it from outside -- while the MASTER loads the
interior set.  And the binary carries its own dev override: the L4VIEWEXT
env var forces the exterior set on a master for external-camera work.
Which is exactly what our render-bridge verification rigs need, so
pod_render_joints/limp.conf now set it -- the 1995 authors shipped the
switch our test harness wanted.

THE TAIL THE 5.3.93 LOADER MISSED: two more optional sets past the limp
probes.  squ/sqd resolve into slots 3 and 2 with squatCapable -- the DUCK:
state 2 plays the squat-down, holds ducked at state 1, state 3 stands back
up (three state numbers the machines always handled but never had clips
for).  And trn resolves into SLOT 4 with turnCapable -- which is WHY state
4 is turn-in-place.  The slot map explains the state machine, again.

VERIFIED both paths live: L4VIEWEXT rig run measures the exterior set
bit-identically to 5.3.93 ('mad': standSpeed=5.23428 walkStride=18.5085
revStride=56.0462); the authentic default (master -> interior) measures
THE SAME CONSTANTS from the 'i' clips -- as it must, the mech covers the
same ground from either view, only the joint content differs -- proving
every madXXXi clip resolves.  No fault on either run.

turnCapable is now real, which unblocks the turn-in-place dispatcher
(master perf) as the next locomotion piece.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-03 14:44:58 -05:00
co-authored by Claude Fable 5
parent 05392e227b
commit c85bf98814
5 changed files with 244 additions and 3 deletions
@@ -65,6 +65,7 @@ set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_MECH_LOG=1
set BT_MAP_LOG=1
set BT_MER_LOG=1
@@ -65,6 +65,7 @@ set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_FORCE_LIMP=3
set BT_MECH_LOG=1
set BT_MAP_LOG=1
+28 -1
View File
@@ -246,6 +246,9 @@ Mech::Mech(
gimpStrideLength = -1.0f; // the measured value is negative too
globalTimeScale = 1.0f;
hasGimpClips = 0;
squatCapable = 0;
turnCapable = 0;
usingExteriorClips = 0;
gimpLeftSpeedMax = 1.0f;
gimpRightSpeedMax = 1.0f;
gimpLeftStrideLength = 1.0f;
@@ -609,7 +612,31 @@ Mech::Mech(
model->animationPrefix[0] <= 'z'
)
{
LoadLocomotionClips(model);
//
// The view dispatch (binary ctor tail @0x4a1674 region):
// a REPLICANT loads the exterior gait clips -- you see
// it from outside -- while the MASTER loads the INTERIOR
// 'i' set, the gait as seen from the cockpit. L4VIEWEXT
// (a 1995 dev switch) forces the exterior set on a
// master for external-camera work, which is exactly what
// the render-bridge verification rigs want.
//
if (getenv("L4VIEWEXT") == NULL)
{
if (GetInstance() == ReplicantInstance)
{
LoadLocomotionClips(model);
}
else
{
LoadLocomotionClipsExt(model);
}
}
else
{
usingExteriorClips = 1;
LoadLocomotionClips(model);
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[mech] clips '"
+17 -2
View File
@@ -426,6 +426,16 @@
MeasureClipStride(int slot, Scalar *total, Scalar *last_key);
void
LoadLocomotionClips(ModelResource *model);
//
// The INTERIOR twin (binary @004a86c8): the identical loader over the
// 'i'-suffixed clip table (swri, wwri, ...). The interior clips
// animate the inside-view skeleton -- they never bind the hip -- and
// the MASTER (viewpoint) mech loads them; replicants load the
// exterior set; the L4VIEWEXT env (a 1995 dev switch) forces the
// exterior set on a master for external-camera work.
//
void
LoadLocomotionClipsExt(ModelResource *model);
int
LoadClipSlot(int slot, const char *prefix, const char *suffix);
@@ -717,6 +727,11 @@
// are measured from the REVERSE clips. See MECH2.NOTES.md.)
//
int hasGimpClips;
int squatCapable; // +0x584: squ/sqd resolved
int turnCapable; // +0x588: trn resolved -- the
// turn-in-place dispatcher's gate
int usingExteriorClips; // +0x57c: L4VIEWEXT forced
// the exterior set on a master
Scalar gimpLeftSpeedMax;
Scalar gimpRightSpeedMax;
Scalar gimpLeftStrideLength;
@@ -881,11 +896,11 @@
//
// 60 ints carved out for the gait channel above (2 AlarmIndicators = 4,
// 8 Scalars, animationClips[0x21] = 33, the optional limp set = 6, the
// per-frame gait state = 9 counting CString loosely); 191 -> 131. The
// per-frame gait state = 9 counting CString loosely); 191 -> 128. The
// block is a layout BUDGET, not offset-exact -- MECH-LAYOUT.md holds
// the real offsets.
//
int reservedState[131];
int reservedState[128];
};
#endif
+197
View File
@@ -2046,5 +2046,202 @@ void
LoadClipSlot(27, prefix, "gsr");
}
//
// The OPTIONAL squat (duck) and turn-in-place sets (binary loader tail
// @0x4a84xx). The slots explain three state numbers that never had
// clips before: squ -> slot 3, sqd -> slot 2 (the duck: state 2 plays
// the squat-down and holds at state 1; state 3 stands back up), and
// trn -> SLOT 4 -- which is why state 4 is turn-in-place.
//
squatCapable = 0;
if (ResolveAnimationClip(prefix, "squ") != NULL)
{
squatCapable = 1;
LoadClipSlot(3, prefix, "squ");
LoadClipSlot(2, prefix, "sqd");
}
turnCapable = 0;
if (ResolveAnimationClip(prefix, "trn") != NULL)
{
turnCapable = 1;
LoadClipSlot(4, prefix, "trn");
}
Check_Fpu();
}
//
//#############################################################################
// @004a86c8 -- the INTERIOR twin. Byte-identical structure to
// LoadLocomotionClips over the 'i'-suffixed table (verbatim from the exe at
// .data:0050d94d: swri wwri ... squi sqdi trni). The interior clips animate
// the inside-view skeleton -- the operator's own cockpit and arms -- and
// never bind the hip. KEEP THE TWO TWINS: like the ClipFinished pair, each
// is the other's check.
//#############################################################################
//
void
Mech::LoadLocomotionClipsExt(ModelResource *model)
{
Check(this);
Check_Pointer(model);
const char
*prefix = model->animationPrefix;
//
// Zero-initialized because the guarded skips below can reach the reverse
// divide with the run pair unmeasured -- a path the (unguarded) binary
// does not have, so the stale-pair reproduction must not become an
// uninitialized read on top of it.
//
Scalar
total_a = 0.0f, last_a = 0.0f,
total_b = 0.0f, last_b = 0.0f;
gyroRumbleTimer = 0.0f;
//
// Stand -> walk. standSpeed is the clip's final-entry stride.
//
if (LoadClipSlot(5, prefix, "swri"))
{
legAnimation.SelectSequence(animationClips[5], NULL, 0, 0);
standSpeed =
legAnimation.keyframeData[legAnimation.keyframeCount].stride;
}
//
// The forward walk cycle: stride = (s6 + s7) / (d6 + d7).
//
if (
LoadClipSlot(6, prefix, "wwri") &&
LoadClipSlot(7, prefix, "wwli")
)
{
MeasureClipStride(6, &total_a, &last_a);
MeasureClipStride(7, &total_b, &last_b);
walkStrideLength = (total_a + total_b) / (last_a + last_b);
}
LoadClipSlot(8, prefix, "wsri");
LoadClipSlot(9, prefix, "wsli");
//
// Walk -> run. reverseSpeedMax is measured from wrr the same way
// standSpeed is from swr.
//
if (LoadClipSlot(10, prefix, "wrri"))
{
legAnimation.SelectSequence(animationClips[10], NULL, 0, 0);
reverseSpeedMax =
legAnimation.keyframeData[legAnimation.keyframeCount].stride;
}
LoadClipSlot(11, prefix, "wrli");
//
// The run cycle.
//
if (
LoadClipSlot(12, prefix, "rrri") &&
LoadClipSlot(13, prefix, "rrli")
)
{
MeasureClipStride(12, &total_a, &last_a);
MeasureClipStride(13, &total_b, &last_b);
reverseStrideLength = (total_a + total_b) / (last_a + last_b);
}
LoadClipSlot(14, prefix, "rwri");
LoadClipSlot(15, prefix, "rwli");
//
// The bump/crash stagger clip, slot 0x20 -- the reason the clip array is
// bigger than the state-name table.
//
LoadClipSlot(0x20, prefix, "bmpi");
//
// The reverse set. gimpSpeedMax is measured from the entry clip; the
// cycle stride divide below reproduces the binary's stale-pair bug (see
// the header comment) and is negated exactly where the binary negates.
//
if (LoadClipSlot(16, prefix, "sbri"))
{
legAnimation.SelectSequence(animationClips[16], NULL, 0, 0);
gimpSpeedMax =
legAnimation.keyframeData[legAnimation.keyframeCount].stride;
}
LoadClipSlot(17, prefix, "sbli");
LoadClipSlot(20, prefix, "bsri");
LoadClipSlot(21, prefix, "bsli");
if (
LoadClipSlot(18, prefix, "bbri") &&
LoadClipSlot(19, prefix, "bbli")
)
{
MeasureClipStride(18, &total_a, &last_a);
MeasureClipStride(19, &total_a, &last_a); // the binary's stale pair:
// total_b/last_b still hold
// the run-cycle figures
gimpStrideLength = (total_a + total_b) / (last_a + last_b);
gimpStrideLength = -gimpStrideLength;
}
//
// The OPTIONAL limp set. Probe for wgl; a model without it has no limp
// clips at all, and the limp machine must never be entered for it.
//
hasGimpClips = 0;
if (ResolveAnimationClip(prefix, "wgli") != NULL)
{
hasGimpClips = 1;
if (LoadClipSlot(22, prefix, "wgli"))
{
legAnimation.SelectSequence(animationClips[22], NULL, 0, 0);
gimpLeftSpeedMax =
legAnimation.keyframeData[legAnimation.keyframeCount].stride;
}
if (LoadClipSlot(23, prefix, "wgri"))
{
legAnimation.SelectSequence(animationClips[23], NULL, 0, 0);
gimpRightSpeedMax =
legAnimation.keyframeData[legAnimation.keyframeCount].stride;
}
if (LoadClipSlot(24, prefix, "ggri"))
{
MeasureClipStride(24, &total_a, &last_a);
gimpLeftStrideLength = total_a / last_a;
}
if (LoadClipSlot(25, prefix, "ggli"))
{
MeasureClipStride(25, &total_a, &last_a);
gimpRightStrideLength = total_a / last_a;
}
LoadClipSlot(26, prefix, "gsli");
LoadClipSlot(27, prefix, "gsri");
}
//
// The OPTIONAL squat (duck) and turn-in-place sets (binary loader tail
// @0x4a84xx). The slots explain three state numbers that never had
// clips before: squ -> slot 3, sqd -> slot 2 (the duck: state 2 plays
// the squat-down and holds at state 1; state 3 stands back up), and
// trn -> SLOT 4 -- which is why state 4 is turn-in-place.
//
squatCapable = 0;
if (ResolveAnimationClip(prefix, "squi") != NULL)
{
squatCapable = 1;
LoadClipSlot(3, prefix, "squi");
LoadClipSlot(2, prefix, "sqdi");
}
turnCapable = 0;
if (ResolveAnimationClip(prefix, "trni") != NULL)
{
turnCapable = 1;
LoadClipSlot(4, prefix, "trni");
}
Check_Fpu();
}