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:
@@ -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
|
||||
|
||||
@@ -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 '"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user