BT410 5.3.94: THE LEGS WALK -- AdvanceLeg/BodyAnimation live, 16 of 22 joints animating in paired strides
The last link is in. AdvanceLegAnimation (@004a5028) and AdvanceBodyAnimation
(@004a5678) reconstructed and wired into Mech::Simulate, and the first live
run put a walking gait on the wire:
before 22 handles, 1 animating (the vehicle root)
after 22 handles, 16 animating, ZERO 2-float records
root 812 poses; six PAIRS at 650/649, 599/596, 563/563,
542/540, 531/529, 434/421; three slow joints at 25
Six left/right pose-count pairs is six joints per leg cycling in alternating
strides -- the symmetry is itself evidence that the handed clip alternation
(Right, Left, Right, each clip one stride) is running correctly. Chain, end
to end, every stage previously verified in isolation and now live together:
mapper demand -> AdvanceLegAnimation (state machine) -> SelectSequence
-> SequenceController::Advance (keyframes -> Joint::SetHinge/SetRotation)
-> BTL4HingeRenderable (5.3.88 matrix transport) -> 12-float flush
-> render bridge applies. Mission drove itself clean, no fault.
RECONSTRUCTED FROM THE RAW DECOMP, NOT THE DONOR -- and the sidecar says why:
BT411's versions carry port-era replicant accommodations (its mapper cell
does not replicate; the binary's does) and a turn-in-place dispatcher it
relocated INTO the leg machine from mech4's master performance. None of that
is 1995 code. Here the leg version reads the mapper unconditionally, exactly
as decompiled, and state 4's ARMING stays where the binary has it -- in
mech4, not yet reconstructed.
THE TWO CHANNELS DIFFER MORE THAN THEIR ClipFinished TWINS DO, all
binary-verified: the leg version has the wind-down block, the turn-in-place
case and the "Standing Not Supported" guard; the body version has none of
those, its case 4 sits in the plain-advance group, and move_joints reaches
every body Advance AND its reset's Reset -- the caller decides whether the
body channel poses joints or only measures stride. Wired accordingly: leg
poses, body measures (move_joints 0), body distance dropped at a seam marked
STAGED -- consuming it as the forward step is IntegrateMotion's job (mech4).
Also faithful: case 0 FALLS THROUGH so a freshly armed clip advances the same
frame it was selected; the plain group's Standing guard is unreachable via
that fall-through and catches direct entry only; each cycle plays its clip at
cycle/stride of the authored rate (a slow walk IS the walk clip played slow);
and the reverse cycle's caps are all negative with the advance ratio folded
positive at the end.
UNSOURCED, named in the sidecar rather than invented: idleStrideScale
(+0x5ac, defaults 1) and runSpeedMax (+0x7a0, the run cycle's upward cap --
LoadLocomotionClips does not set it; defaulted huge so it never binds until
its real writer is found). ForceUpdate(8) is a STAGED no-op pending the
replication emitter.
Still deferred: the airborne Advance* flavours (@004a5bf8/@004a71f4, jump
jets) and the Gimp*ClipFinished limp machines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -242,6 +242,13 @@ Mech::Mech(
|
||||
gimpLeftStrideLength = 1.0f;
|
||||
gimpRightStrideLength = 1.0f;
|
||||
gyroRumbleTimer = 0.0f;
|
||||
idleStrideScale = 1.0f;
|
||||
runSpeedMax = 1.0e9f; // UNSOURCED cap -- never binds until
|
||||
// its real source is found
|
||||
motionEventArmed = 0;
|
||||
deathAnimationLatched = 0;
|
||||
legResetLatch = 0;
|
||||
bodyResetLatch = 0;
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < AnimationSlotCount; ++i)
|
||||
@@ -1207,6 +1214,24 @@ void
|
||||
}
|
||||
bodyTargetSpeed = speedDemand;
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// THE GAIT (mech2.cpp). The leg channel poses the skeleton from the live
|
||||
// demand -- this is what makes the legs actually move; the body channel
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------
|
||||
//
|
||||
AdvanceLegAnimation(time_slice);
|
||||
AdvanceBodyAnimation(time_slice, 0);
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// Accelerate the actual body speed toward the demand (bounded per frame by
|
||||
|
||||
@@ -403,6 +403,25 @@
|
||||
int
|
||||
LoadClipSlot(int slot, const char *prefix, const char *suffix);
|
||||
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
// The per-frame gait entry points (mech2.cpp). Each advances its
|
||||
// channel's clip by the frame's time slice -- slewing the channel's
|
||||
// cycle speed toward its demand, driving the state machine at clip
|
||||
// boundaries through the *ClipFinished callbacks -- and returns the
|
||||
// cycle DISTANCE covered, which is what locomotion integrates.
|
||||
//
|
||||
// The leg version always moves the joints; the body version takes
|
||||
// move_joints explicitly so the caller can run it as a pure stride
|
||||
// measurement. (The airborne flavours @004a5bf8/@004a71f4 are a
|
||||
// later increment.)
|
||||
//--------------------------------------------------------------------
|
||||
//
|
||||
Scalar
|
||||
AdvanceLegAnimation(Scalar time_slice);
|
||||
Scalar
|
||||
AdvanceBodyAnimation(Scalar time_slice, int move_joints);
|
||||
|
||||
//
|
||||
// The respawn heal-and-move (binary @0049fb74): reposition the SAME
|
||||
// entity at the drop-zone origin, kill all motion, clear the death
|
||||
@@ -654,6 +673,45 @@
|
||||
Scalar gimpRightStrideLength;
|
||||
Scalar gyroRumbleTimer; // binary mech+0x5c4; the
|
||||
// clip loader zeroes it
|
||||
|
||||
//
|
||||
// Per-frame gait state (the Advance* entry points).
|
||||
//
|
||||
// idleStrideScale scales ONLY the idle/transition clip group's
|
||||
// advance rate (binary +0x5ac); the cycles use their own
|
||||
// cycle/stride ratio instead. UNSOURCED -- nothing observed sets
|
||||
// it yet; defaulted 1.
|
||||
//
|
||||
// runSpeedMax (+0x7a0) caps the run cycle's upward slew the way
|
||||
// walkStrideLength caps the walk's. UNSOURCED -- not set by
|
||||
// LoadLocomotionClips; defaulted huge so it never binds until its
|
||||
// real source is found.
|
||||
//
|
||||
// The two reset latches and the death latch are one-shots the
|
||||
// fall/reset clip group clears; motionEventName/Armed are cleared
|
||||
// there too (their consumer is a later TU).
|
||||
//
|
||||
Scalar idleStrideScale;
|
||||
Scalar runSpeedMax;
|
||||
CString motionEventName;
|
||||
int motionEventArmed;
|
||||
int deathAnimationLatched;
|
||||
int legResetLatch;
|
||||
int bodyResetLatch;
|
||||
|
||||
//
|
||||
// STAGED (binary @004a4c54, inline in the original mech.hpp): the
|
||||
// action-request bits are the update-record request mask -- setting
|
||||
// bit 3 (mask 8) asks the replication layer to emit the leg-state /
|
||||
// stability record. Our replication emitter is not reconstructed,
|
||||
// so the request has nowhere to go yet; the call sites keep the
|
||||
// binary's shape.
|
||||
//
|
||||
void
|
||||
ForceUpdate(int /*record_mask*/)
|
||||
{
|
||||
Check(this);
|
||||
}
|
||||
AverageOf<Scalar> telemetryFilter[5];
|
||||
CString resourceNameA;
|
||||
CString resourceNameB;
|
||||
@@ -753,11 +811,13 @@
|
||||
// reconstructed with named fields.
|
||||
//
|
||||
//
|
||||
// 51 ints carved out for the gait channel above (2 AlarmIndicators = 4,
|
||||
// 8 Scalars, animationClips[0x21] = 33, the optional limp set = 6);
|
||||
// 191 -> 140.
|
||||
// 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
|
||||
// block is a layout BUDGET, not offset-exact -- MECH-LAYOUT.md holds
|
||||
// the real offsets.
|
||||
//
|
||||
int reservedState[140];
|
||||
int reservedState[131];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -483,6 +483,453 @@ Scalar
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// @004a5028 -- the LEG channel's per-frame update (ground flavour).
|
||||
//
|
||||
// Reads the LIVE demand from the controls mapper, arms the death clips off
|
||||
// the movement mode, slews legCycleSpeed toward the demand inside each
|
||||
// cycle's caps, and advances the clip -- which is what writes the leg
|
||||
// joints. Returns the cycle distance covered this frame.
|
||||
//
|
||||
// Reconstructed from the RAW decomp rather than the BT411 donor: the donor
|
||||
// carries port-era replicant accommodations and a relocated turn-in-place
|
||||
// dispatcher that belong to ITS network model, not to the binary. In the
|
||||
// binary the trn dispatcher lives in the master performance (mech4), and a
|
||||
// replicant's mapper cell replicates -- so this function reads the mapper
|
||||
// unconditionally, exactly as decompiled.
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
Mech::AdvanceLegAnimation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// The binary reads the roster's slot 0 with no null check -- a mech
|
||||
// always has its controls mapper by the time it ticks.
|
||||
//
|
||||
MechControlsMapper
|
||||
*mapper = (MechControlsMapper *)subsystemArray[0];
|
||||
Check_Pointer(mapper);
|
||||
Scalar
|
||||
demand = mapper->GetSpeedDemand(),
|
||||
distance = 0.0f;
|
||||
|
||||
//
|
||||
// One-shot: movement modes 5..8 are the falls/deaths; latch the matching
|
||||
// crash clip exactly once.
|
||||
//
|
||||
if (!deathAnimationLatched)
|
||||
{
|
||||
switch (GetSimulationState())
|
||||
{
|
||||
case 5: SetLegAnimation(0x1c); deathAnimationLatched = 1; break;
|
||||
case 6: SetLegAnimation(0x1d); deathAnimationLatched = 1; break;
|
||||
case 7: SetLegAnimation(0x1e); deathAnimationLatched = 1; break;
|
||||
case 8: SetLegAnimation(0x1f); deathAnimationLatched = 1; break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Wind-down: once the cycle speed has decayed to nothing during a
|
||||
// walk-transition state, drop straight to standing.
|
||||
//
|
||||
{
|
||||
int
|
||||
state = (int)legStateAlarm.GetLevel();
|
||||
if (
|
||||
legCycleSpeed <= 0.0f &&
|
||||
(state == 6 || state == 7 || state == 8 || state == 9)
|
||||
)
|
||||
{
|
||||
legStateAlarm.SetLevel(0);
|
||||
legResetLatch = 1;
|
||||
}
|
||||
}
|
||||
|
||||
switch (legStateAlarm.GetLevel())
|
||||
{
|
||||
case 0:
|
||||
//
|
||||
// Standing. A demand above standSpeed begins the walk; a NEGATIVE
|
||||
// demand backs up; anything in between stays put. Arming a state
|
||||
// FALLS THROUGH so the new clip advances this same frame.
|
||||
//
|
||||
if (standSpeed < demand)
|
||||
{
|
||||
SetLegAnimation(5);
|
||||
}
|
||||
else
|
||||
{
|
||||
distance = 0.0f;
|
||||
if (demand >= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SetLegAnimation(0x10);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case 2: case 3: case 5: case 8: case 9: case 10: case 0xb:
|
||||
case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
|
||||
advance_clip:
|
||||
//
|
||||
// The plain-advance group: transitions, falls and deaths play at the
|
||||
// global rate scaled by the idle/transition stride scale. The
|
||||
// Standing guard is the binary's own (MECH2.CPP:0xd3) -- unreachable
|
||||
// through the fall-through above (arming rewrote the level), it
|
||||
// catches a DIRECT entry with the alarm still at 0.
|
||||
//
|
||||
if (legStateAlarm.GetLevel() == 0)
|
||||
{
|
||||
Fail("Standing Not Supported");
|
||||
}
|
||||
distance = legAnimation.Advance(
|
||||
time_slice * globalTimeScale * idleStrideScale, 1);
|
||||
legCycleSpeed = distance / time_slice;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
distance = 0.0f;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
//
|
||||
// Turn-in-place. A demand outside [0, standSpeed] abandons the turn
|
||||
// -- drop to standing and request the leg-state update record --
|
||||
// otherwise the turn clip advances like any transition. (What ARMS
|
||||
// state 4 is the master performance's dispatcher, mech4 -- not here.)
|
||||
//
|
||||
if (standSpeed < demand)
|
||||
{
|
||||
legStateAlarm.SetLevel(0);
|
||||
ForceUpdate(8);
|
||||
break;
|
||||
}
|
||||
distance = 0.0f;
|
||||
if (demand < 0.0f)
|
||||
{
|
||||
legStateAlarm.SetLevel(0);
|
||||
ForceUpdate(8);
|
||||
break;
|
||||
}
|
||||
goto advance_clip;
|
||||
|
||||
case 6: case 7:
|
||||
//
|
||||
// The walk cycle. Slew the cycle speed toward the demand at
|
||||
// forwardCycleRate: upward capped by the demand then the walk
|
||||
// stride, downward floored by the demand then standSpeed. The clip
|
||||
// advances at (cycle / walkStride) of its authored rate -- a slow
|
||||
// walk IS the walk clip played slow.
|
||||
//
|
||||
if (demand > legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed += forwardCycleRate * time_slice;
|
||||
if (legCycleSpeed > demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed > walkStrideLength)
|
||||
{
|
||||
legCycleSpeed = walkStrideLength;
|
||||
}
|
||||
}
|
||||
else if (demand < legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed -= forwardCycleRate * time_slice;
|
||||
if (legCycleSpeed < demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed < standSpeed)
|
||||
{
|
||||
legCycleSpeed = standSpeed;
|
||||
}
|
||||
}
|
||||
distance = legAnimation.Advance(
|
||||
time_slice * (legCycleSpeed / walkStrideLength) * globalTimeScale,
|
||||
1);
|
||||
break;
|
||||
|
||||
case 0xc: case 0xd:
|
||||
//
|
||||
// The run cycle -- same slew, its own caps: up to runSpeedMax, down
|
||||
// no further than reverseSpeedMax (the drop-out threshold the
|
||||
// ClipFinished handler tests).
|
||||
//
|
||||
if (demand > legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed += forwardCycleRate * time_slice;
|
||||
if (legCycleSpeed > demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed > runSpeedMax)
|
||||
{
|
||||
legCycleSpeed = runSpeedMax;
|
||||
}
|
||||
}
|
||||
else if (demand < legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed -= forwardCycleRate * time_slice;
|
||||
if (legCycleSpeed < demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed < reverseSpeedMax)
|
||||
{
|
||||
legCycleSpeed = reverseSpeedMax;
|
||||
}
|
||||
}
|
||||
distance = legAnimation.Advance(
|
||||
time_slice * (legCycleSpeed / reverseStrideLength) * globalTimeScale,
|
||||
1);
|
||||
break;
|
||||
|
||||
case 0x12: case 0x13:
|
||||
//
|
||||
// The reverse cycle. Everything is NEGATIVE here -- the demand, the
|
||||
// cycle speed, and both caps (gimpSpeedMax ~ -4, gimpStrideLength
|
||||
// ~ -20 on the Mad Cat), so "up" slews toward zero and "down" toward
|
||||
// full reverse, at the reverse's own gimpCycleRate. The advance
|
||||
// ratio is folded positive: a reverse clip is authored backward, it
|
||||
// is not played backward.
|
||||
//
|
||||
if (demand > legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed += gimpCycleRate * time_slice;
|
||||
if (legCycleSpeed > demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed > gimpSpeedMax)
|
||||
{
|
||||
legCycleSpeed = gimpSpeedMax;
|
||||
}
|
||||
}
|
||||
else if (demand < legCycleSpeed)
|
||||
{
|
||||
legCycleSpeed -= gimpCycleRate * time_slice;
|
||||
if (legCycleSpeed < demand)
|
||||
{
|
||||
legCycleSpeed = demand;
|
||||
}
|
||||
if (legCycleSpeed < gimpStrideLength)
|
||||
{
|
||||
legCycleSpeed = gimpStrideLength;
|
||||
}
|
||||
}
|
||||
{
|
||||
Scalar
|
||||
ratio = legCycleSpeed / gimpStrideLength;
|
||||
if (ratio <= 0.0f)
|
||||
{
|
||||
ratio = -ratio;
|
||||
}
|
||||
distance = legAnimation.Advance(
|
||||
ratio * time_slice * globalTimeScale, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
//
|
||||
// The reset group -- limp exits and the four falls. Clear the
|
||||
// motion-event state and every one-shot, drop to standing, and put
|
||||
// the skeleton back to its neutral pose.
|
||||
//
|
||||
motionEventName = "";
|
||||
motionEventArmed = 0;
|
||||
legResetLatch = 0;
|
||||
deathAnimationLatched = 0;
|
||||
legStateAlarm.SetLevel(0);
|
||||
legAnimation.Reset(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
Fail("Unsupported mech animation");
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// @004a5678 -- the BODY channel's per-frame update (ground flavour).
|
||||
//
|
||||
// The displayed-motion twin. Differences from the leg version, all
|
||||
// binary-verified: the demand is bodyTargetSpeed (the snapshot, no mapper
|
||||
// access); there is NO wind-down block and NO turn-in-place case (state 4
|
||||
// sits in the plain group); move_joints arrives as a parameter and reaches
|
||||
// every Advance AND the reset's Reset call -- so the caller decides whether
|
||||
// this channel poses the skeleton or only measures the stride.
|
||||
//#############################################################################
|
||||
//
|
||||
Scalar
|
||||
Mech::AdvanceBodyAnimation(Scalar time_slice, int move_joints)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
Scalar
|
||||
demand = bodyTargetSpeed,
|
||||
distance = 0.0f;
|
||||
|
||||
if (!deathAnimationLatched)
|
||||
{
|
||||
switch (GetSimulationState())
|
||||
{
|
||||
case 5: SetBodyAnimation(0x1c); deathAnimationLatched = 1; break;
|
||||
case 6: SetBodyAnimation(0x1d); deathAnimationLatched = 1; break;
|
||||
case 7: SetBodyAnimation(0x1e); deathAnimationLatched = 1; break;
|
||||
case 8: SetBodyAnimation(0x1f); deathAnimationLatched = 1; break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (bodyStateAlarm.GetLevel())
|
||||
{
|
||||
case 0:
|
||||
distance = 0.0f;
|
||||
if (standSpeed < demand)
|
||||
{
|
||||
SetBodyAnimation(5);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (demand >= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SetBodyAnimation(0x10);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case 2: case 3: case 4: case 5: case 8: case 9: case 10: case 0xb:
|
||||
case 0xe: case 0xf: case 0x10: case 0x11: case 0x14: case 0x15:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20:
|
||||
distance = bodyAnimation.Advance(
|
||||
time_slice * globalTimeScale * idleStrideScale, move_joints);
|
||||
bodyCycleSpeed = distance / time_slice;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
distance = 0.0f;
|
||||
break;
|
||||
|
||||
case 6: case 7:
|
||||
if (demand > bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed += forwardCycleRate * time_slice;
|
||||
if (bodyCycleSpeed > demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed > walkStrideLength)
|
||||
{
|
||||
bodyCycleSpeed = walkStrideLength;
|
||||
}
|
||||
}
|
||||
else if (demand < bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed -= forwardCycleRate * time_slice;
|
||||
if (bodyCycleSpeed < demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed < standSpeed)
|
||||
{
|
||||
bodyCycleSpeed = standSpeed;
|
||||
}
|
||||
}
|
||||
distance = bodyAnimation.Advance(
|
||||
time_slice * (bodyCycleSpeed / walkStrideLength) * globalTimeScale,
|
||||
move_joints);
|
||||
break;
|
||||
|
||||
case 0xc: case 0xd:
|
||||
if (demand > bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed += forwardCycleRate * time_slice;
|
||||
if (bodyCycleSpeed > demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed > runSpeedMax)
|
||||
{
|
||||
bodyCycleSpeed = runSpeedMax;
|
||||
}
|
||||
}
|
||||
else if (demand < bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed -= forwardCycleRate * time_slice;
|
||||
if (bodyCycleSpeed < demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed < reverseSpeedMax)
|
||||
{
|
||||
bodyCycleSpeed = reverseSpeedMax;
|
||||
}
|
||||
}
|
||||
distance = bodyAnimation.Advance(
|
||||
time_slice * (bodyCycleSpeed / reverseStrideLength) * globalTimeScale,
|
||||
move_joints);
|
||||
break;
|
||||
|
||||
case 0x12: case 0x13:
|
||||
if (demand > bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed += gimpCycleRate * time_slice;
|
||||
if (bodyCycleSpeed > demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed > gimpSpeedMax)
|
||||
{
|
||||
bodyCycleSpeed = gimpSpeedMax;
|
||||
}
|
||||
}
|
||||
else if (demand < bodyCycleSpeed)
|
||||
{
|
||||
bodyCycleSpeed -= gimpCycleRate * time_slice;
|
||||
if (bodyCycleSpeed < demand)
|
||||
{
|
||||
bodyCycleSpeed = demand;
|
||||
}
|
||||
if (bodyCycleSpeed < gimpStrideLength)
|
||||
{
|
||||
bodyCycleSpeed = gimpStrideLength;
|
||||
}
|
||||
}
|
||||
{
|
||||
Scalar
|
||||
ratio = bodyCycleSpeed / gimpStrideLength;
|
||||
if (ratio <= 0.0f)
|
||||
{
|
||||
ratio = -ratio;
|
||||
}
|
||||
distance = bodyAnimation.Advance(
|
||||
ratio * time_slice * globalTimeScale, move_joints);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
motionEventName = "";
|
||||
motionEventArmed = 0;
|
||||
bodyResetLatch = 0;
|
||||
deathAnimationLatched = 0;
|
||||
bodyStateAlarm.SetLevel(0);
|
||||
bodyAnimation.Reset(move_joints);
|
||||
break;
|
||||
|
||||
default:
|
||||
Fail("Unsupported mech animation");
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// @004a7f50 -- prefix + suffix -> the clip's resource ID.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# MECH2.CPP — reconstruction notes
|
||||
|
||||
**Status: the TRANSITION MACHINE reconstructed (compile-verified 2026-08-02,
|
||||
BT 51/51, links clean). The four per-frame `Advance*` entry points and the two
|
||||
`Gimp*ClipFinished` machines are NOT yet written — see "What is deferred".**
|
||||
**Status: THE GAIT IS LIVE (2026-08-03). Transition machine + clip loader +
|
||||
the two GROUND `Advance*` entry points reconstructed and wired into
|
||||
`Mech::Simulate`; first live run showed 16 of 22 joints animating in paired
|
||||
left/right strides. Still deferred: the two AIRBORNE `Advance*` flavours and
|
||||
the `Gimp*ClipFinished` limp machines — see "What is deferred".**
|
||||
|
||||
`mech2.cpp` is the mech's gait: which walk clip is playing, when it changes,
|
||||
and to what. It sits between the locomotion demand and the clip player:
|
||||
@@ -235,3 +237,55 @@ this TU is the four `Advance*` entry points (wired into `Mech::Simulate`) and
|
||||
the two `Gimp*ClipFinished` machines. When `AdvanceLegAnimation` lands, the
|
||||
gait will select clips and SEQCTL will write joints — the first frame where
|
||||
the legs actually move.
|
||||
|
||||
## The entry points are in — and the legs walk (2026-08-03)
|
||||
|
||||
`AdvanceLegAnimation` (@004a5028) and `AdvanceBodyAnimation` (@004a5678)
|
||||
reconstructed **from the raw decomp, not the BT411 donor** — the donor's
|
||||
versions carry port-era replicant accommodations and a turn-in-place
|
||||
dispatcher relocated from mech4's master performance, none of which is 1995
|
||||
code. In the binary the mapper cell replicates, so the leg version reads the
|
||||
mapper unconditionally, and what ARMS state 4 is mech4's dispatcher (not yet
|
||||
reconstructed — a mech can hold a turn clip if something else arms it, but
|
||||
nothing arms it yet).
|
||||
|
||||
Wired into `Mech::Simulate`: leg channel with joints, body channel as pure
|
||||
measurement (`move_joints` 0). The body's measured distance is dropped for
|
||||
now — consuming it as the forward step is `IntegrateMotion`'s job (mech4).
|
||||
The seam is marked STAGED at the call site.
|
||||
|
||||
**Live verification** (arena mission, auto-throttle, `BT_JOINTS=1`):
|
||||
|
||||
```
|
||||
before 22 handles on the wire, 1 animating (the root)
|
||||
after 22 handles on the wire, 16 animating, 0 two-float records
|
||||
root 812 poses; six PAIRS at 650/649, 599/596, 563/563,
|
||||
542/540, 531/529, 434/421; three slow joints at 25
|
||||
```
|
||||
|
||||
Six left/right pairs is six joints per leg cycling in alternating strides —
|
||||
the pose-count symmetry is itself evidence the handed clip alternation is
|
||||
running correctly. No fault, mission drove itself clean.
|
||||
|
||||
### Structure notes for the next reader
|
||||
|
||||
* **The channels differ more than the ClipFinished twins do.** The leg
|
||||
version has the wind-down block (cycle decayed → drop to standing), the
|
||||
turn-in-place case 4, and the "Standing Not Supported" guard; the body
|
||||
version has NONE of those — its case 4 sits in the plain group, and its
|
||||
reset passes `move_joints` through to `Reset`. All binary-verified.
|
||||
* **Case 0 falls THROUGH.** Arming stand→walk (or stand→reverse) drops into
|
||||
the plain-advance group so the new clip advances the same frame. The
|
||||
Standing guard inside that group is unreachable via the fall-through
|
||||
(arming rewrote the level) — it catches direct entry only.
|
||||
* **A slow walk is the walk clip played slow.** Each cycle advances its clip
|
||||
at `cycle/strideLength` of the authored rate, with the cycle speed slewed
|
||||
toward the demand inside per-gait caps. The reverse cycle's caps are all
|
||||
NEGATIVE and its advance ratio is folded positive — the clip is authored
|
||||
backward, not played backward.
|
||||
* **Two members remain UNSOURCED**: `idleStrideScale` (+0x5ac, defaults 1)
|
||||
and `runSpeedMax` (+0x7a0, the run cycle's upward cap — not set by
|
||||
LoadLocomotionClips; defaulted huge so it never binds). Finding their real
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user