Locomotion: replace task-#64 trn stand-ins with the AUTHENTIC dispatcher (disasm-decoded)
The turn-in-place dispatcher lives in the master-perf gap 0x4a9b5c-0x4ab188 that Ghidra never lifted. Disassembled it with objdump (scratchpad/masterperf.asm) and hand-decoded the real logic, replacing the two [T3] stand-ins I had invented: - Entry gate was `commandedSpeed < 0.25*standSpeed` (a near-zero guess). AUTHENTIC (0x4aa505): arm trn when TURNING (|angVel|>1e-4, port proxy = turnDemand deadband) AND speed in the FULL [0, standSpeed] sub-walk range AND turnCapable. trn is gated on TURNING, not on being slow. - Case-4 exit had a ×4 fast-forward + SetLegAnimation(5) invention. Restored the VERBATIM decompiled leg-SM exits (part_012.c:12013): standSpeed<spd or reverse -> Standing, else advance the pivot. Added the master-perf turn-STOP exit (0x4aa5c6): turn stopped -> Standing + legResetLatch=1 (folded into case 4 since the port has no separate master-perf frame). - Body case-4 twin mirrors the authentic exits (mj=0, tracks state for lockstep). The fast-forward was invented to dodge a turn->walk stutter that was ACTUALLY the body channel leaking joints (mj=1) -- already root-caused + fixed by mj=0. The trn->walk seam (legFrm7 cycle-end -> swr legFrm1) is the authentic pose-matched boundary and the same seam the fast-forward version hit, just at the correct release speed. Also decoded (documented, not yet wired) the authentic turn-RATE lerp (walkingTurnRate@0x574 -> runningTurnRate@0x578 by speed; angVel = turnDemand*rate); the bring-up drive still yaws at the constant kDriveTurnRate. Headless-verified (BT_AUTODRIVE/BT_FORCE_TURN/BT_WALK_DELAY/BT_GAIT_TRACE): turn-in- place enters state 4, ramp past standSpeed -> 4->stand->swr->walk, both channels in lockstep. Feel re-verification pending. [T1 logic / T2 runtime] Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bfdd41bb9d
commit
35c30bda67
@@ -595,22 +595,26 @@ Scalar
|
||||
else
|
||||
{
|
||||
distance = 0.0f;
|
||||
// TURN-IN-PLACE entry. The authentic entry DISPATCHER lives in the
|
||||
// un-decompiled master-perf gap (0x4a9b5c-0x4ab188), the sole reader of
|
||||
// turnDemand(+0x12c) + turnCapable(+0x588); the decompiled lifecycle is
|
||||
// case-4 advance/exit (below) + the clip-finish drop-to-stand. The
|
||||
// authentic gate is turn-IN-PLACE == NEAR-ZERO commanded speed. The old
|
||||
// stand-in gated on `ZeroSpeed <= commandedSpeed` -- i.e. it re-armed trn
|
||||
// for ANY forward speed in [0, standSpeed~6.83], so pushing forward mid-
|
||||
// turn SWALLOWED the throttle (the mech kept pivoting) until spd crossed
|
||||
// standSpeed, then lurched to walk (task #64 user-reported stutter). Gate
|
||||
// on near-zero speed instead: the instant real throttle is applied the trn
|
||||
// re-arm stops, the in-flight pivot finishes to Standing, and case 0 hands
|
||||
// off to walk normally. turnEntrySpeed = 0.25*standSpeed is a STAND-IN for
|
||||
// the gap's threshold constant [T3]; the deadband mirrors the replicant's.
|
||||
const Scalar turnEntrySpeed = standSpeed * 0.25f;
|
||||
// TURN-IN-PLACE entry -- AUTHENTIC, decoded from the master-perf disasm
|
||||
// (0x4aa505-0x4aa588, task #64b). The dispatcher lives in the master perf
|
||||
// FUN_004a9b5c (Ghidra never decompiled it -> objdump); it arms trn from
|
||||
// Standing when: the mech is TURNING (|angularVelocity| > 1e-4, ds:0x4ab16c
|
||||
// -- angularVelocity = turnDemand * turnRate, so turnDemand outside a tiny
|
||||
// deadband) AND 0 <= speedDemand <= standSpeed (the FULL sub-walk range,
|
||||
// ds:0x4ab178=0 .. mech[0x530]) AND turnCapable(mech[0x588]) AND the
|
||||
// legResetLatch(mech[0x654]) debounce is clear. CORRECTS the task-#64a
|
||||
// stand-in `commandedSpeed < 0.25*standSpeed` (that near-zero gate was a
|
||||
// guess; the binary allows the full [0,standSpeed] range -- trn is gated on
|
||||
// TURNING, not on being slow). [T1 from disasm] The turnDemand deadband
|
||||
// (0.05) is the port proxy for the |angVel|>1e-4 test. NOTE: the disasm's
|
||||
// legResetLatch gate is a master-perf ONE-FRAME debounce (cleared every
|
||||
// frame at 0x4a9bff, set on wind-down/turn-stop) -- it does NOT map onto
|
||||
// the port's split leg-SM (wind-down + trn-entry run in different frames/
|
||||
// cases and the port never per-frame-clears the latch), so gating entry on
|
||||
// it here would wrongly block trn after every walk. Omitted by design;
|
||||
// see locomotion.md "turn-in-place dispatcher".
|
||||
if (hasCrashSet != 0 && mppr != 0
|
||||
&& commandedSpeed >= ZeroSpeed && commandedSpeed < turnEntrySpeed
|
||||
&& commandedSpeed >= ZeroSpeed && commandedSpeed <= standSpeed
|
||||
&& (mppr->turnDemand > 0.05f
|
||||
|| mppr->turnDemand < -0.05f)
|
||||
&& bodyAnimationState == StandingAnimation) // weld: enter only when BOTH can
|
||||
@@ -664,47 +668,41 @@ Scalar
|
||||
|
||||
case 4: // TURN-IN-PLACE (trn clip)
|
||||
// state 4 is the turn-in-place animation (animationClips[4] = the "trn" clip,
|
||||
// loaded under turnCapable@0x588). THE POSE-SNAP RULE (task #64, root-caused
|
||||
// from the user's BT_GAIT_TRACE): this engine has NO pose blending -- every
|
||||
// authored transition is pose-MATCHED at its boundary (swr starts from the
|
||||
// stand pose; the walk clips alternate at matched extremes). Any exit that
|
||||
// abandons the pivot MID-CLIP (SetLevel(0) or SetLegAnimation(5)) hard-resets
|
||||
// the next clip to frame 0 while the legs are mid-step -> the legs TELEPORT
|
||||
// (legFrm 7->1 in the trace) = the 100%-repro turn-then-walk stutter. Three
|
||||
// prior fixes all failed because each still cut the clip mid-step, just at a
|
||||
// different speed threshold.
|
||||
// loaded under turnCapable@0x588). AUTHENTIC exits, restored VERBATIM from the
|
||||
// decompiled leg SM (part_012.c:12013 == FUN_004a5028 case 4 [T1]):
|
||||
// * standSpeed < commandedSpeed -> SetLevel(0)+ForceUpdate(8): walk takes over
|
||||
// * commandedSpeed < ZeroSpeed -> SetLevel(0)+ForceUpdate(8): reverse
|
||||
// * else -> goto advance_normally: advance the pivot at idleStrideScale.
|
||||
// PLUS the master-perf turn-STOP exit (disasm 0x4aa5c6-0x4aa5e6 [T1], which the
|
||||
// leg SM does NOT contain -- it lives in the un-decompiled master perf that the
|
||||
// port has no separate frame for, so it folds in here): when the turn stops
|
||||
// (|angularVelocity| <= 1e-4, i.e. turnDemand back inside the deadband) ->
|
||||
// SetLevel(0) + legResetLatch(mech[0x654])=1. Without it the trn clip would
|
||||
// shuffle in place forever after the stick re-centers.
|
||||
//
|
||||
// The reconstruction: when forward is commanded past the turn-in-place band,
|
||||
// FAST-FORWARD the pivot to completion (accelerated advance) -- the feet
|
||||
// hurry down to the plant, the clip's own finished-callback (case 4 ->
|
||||
// SetLevel(0), the DECOMPILED one-shot tail) drops to Standing AT THE STAND
|
||||
// POSE, and the normal pose-matched stand->walk path takes over. No mid-clip
|
||||
// cut anywhere. trn has zero root translation, so the fast-forward moves no
|
||||
// distance. [T3: the release threshold (0.25*standSpeed) + hurry rate (4x)
|
||||
// stand in for the un-decompiled master-perf dispatcher constants (gap
|
||||
// 0x4a9b5c); the completion->Standing tail itself is T1 decompiled.]
|
||||
//
|
||||
// The decompiled REVERSE abort is kept verbatim; the decompiled standSpeed
|
||||
// abort is deliberately SUBSUMED by the hurry (it was the mid-clip pose cut
|
||||
// itself -- any spd above the band fast-forwards to the plant instead, and
|
||||
// Standing picks up the walk one frame after the callback). [T3 deviation
|
||||
// from the literal case-4 exit, in service of the pose-match invariant.]
|
||||
if (commandedSpeed < ZeroSpeed)
|
||||
// The task-#64 fast-forward (4x) + 0.25*standSpeed early-release were STAND-INS
|
||||
// invented to dodge a turn->walk stutter that was ACTUALLY the body channel
|
||||
// leaking joints into the rendered skeleton (mj=1); root-caused + fixed by the
|
||||
// body-channel mj=0 change (AdvanceBodyAnimation, mech4.cpp). With the body no
|
||||
// longer writing joints the authentic mid-clip cut renders cleanly, so the
|
||||
// inventions are removed. trn has zero root translation -> distance stays 0.
|
||||
if (standSpeed < commandedSpeed || commandedSpeed < ZeroSpeed)
|
||||
{
|
||||
legStateAlarm.SetLevel(0);
|
||||
legStateAlarm.SetLevel(0); // -> Standing (leg-SM exit)
|
||||
ForceUpdate(8); // type-3 record
|
||||
distance = 0.0f;
|
||||
break;
|
||||
}
|
||||
if (mppr == 0
|
||||
|| (mppr->turnDemand <= 0.05f && mppr->turnDemand >= -0.05f))
|
||||
{
|
||||
Scalar trnRate = 1.0f;
|
||||
if (standSpeed * 0.25f < commandedSpeed)
|
||||
trnRate = 4.0f; // forward commanded: hurry the plant [T3]
|
||||
distance = legAnimation.Advance( // same advance advance_normally does,
|
||||
time_slice * globalTimeScale * idleStrideScale * trnRate, 1); // rate-scaled
|
||||
legCycleSpeed = distance / time_slice; // trn z=0 -> stays 0 (no travel)
|
||||
legStateAlarm.SetLevel(0); // turn stopped -> Standing (master-perf exit)
|
||||
ForceUpdate(8);
|
||||
legResetLatch = 1; // mech[0x654] (master perf sets it here, 0x4aa5e1)
|
||||
distance = 0.0f;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
goto advance_normally; // still turning, sub-walk -> advance the pivot
|
||||
|
||||
case 6: case 7: // WalkToRun
|
||||
//
|
||||
@@ -918,27 +916,32 @@ Scalar
|
||||
// the SAME live speedDemand, so both clips complete on the same frame and
|
||||
// both channels re-enter walk on the same frame. Without this the body
|
||||
// entered walk ~7-20 frames apart from the leg and the two walk cycles ran
|
||||
// permanently out of phase -- the body's pose flashes through on the leg's
|
||||
// clip-boundary frames (body advances mj=1 FIRST, leg overwrites LAST;
|
||||
// boundary frames the leg doesn't write) = the rhythmic gait skip + halved
|
||||
// bob the user reproduced 100% from turn-then-walk (standstill starts weld
|
||||
// the phases, which is why they never stuttered). [T3 rates/threshold; the
|
||||
// per-channel case-4 machinery itself is T1 decompiled.]
|
||||
// permanently out of phase. Exits mirror the leg twin VERBATIM
|
||||
// (part_012.c:12013 [T1]): speed exits + the master-perf turn-stop exit.
|
||||
// The body does NOT set legResetLatch (leg-channel/master-perf state, set
|
||||
// once by the leg twin). Since the body no longer writes joints (mj=0,
|
||||
// AdvanceBodyAnimation) this channel only tracks state so both channels
|
||||
// re-enter walk on the same frame.
|
||||
{
|
||||
MechControlsMapper *bm = MappingMapper();
|
||||
const Scalar bspd = (bm != 0) ? bm->speedDemand : 0.0f;
|
||||
if (bspd < ZeroSpeed) // reverse abort (leg-symmetric)
|
||||
if (standSpeed < bspd || bspd < ZeroSpeed) // walk / reverse (leg-symmetric)
|
||||
{
|
||||
bodyStateAlarm.SetLevel(0);
|
||||
ForceUpdate(8);
|
||||
distance = 0.0f;
|
||||
break;
|
||||
}
|
||||
Scalar trnRate = 1.0f;
|
||||
if (standSpeed * 0.25f < bspd)
|
||||
trnRate = 4.0f; // hurry the plant (leg-symmetric)
|
||||
distance = bodyAnimation.Advance(
|
||||
time_slice * globalTimeScale * idleStrideScale * trnRate, loop);
|
||||
if (bm == 0
|
||||
|| (bm->turnDemand <= 0.05f && bm->turnDemand >= -0.05f))
|
||||
{
|
||||
bodyStateAlarm.SetLevel(0); // turn stopped (leg-symmetric)
|
||||
ForceUpdate(8);
|
||||
distance = 0.0f;
|
||||
break;
|
||||
}
|
||||
distance = bodyAnimation.Advance( // still turning -> advance the pivot
|
||||
time_slice * globalTimeScale * idleStrideScale, loop);
|
||||
bodyCycleSpeed = distance / time_slice;
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user