Gait v5: display+travel from the LEG channel (kills the stutter class)
User-reported recurrence of "legs stutter and lose sync". Live [sync] showed
advSum/legSum drifting 5->92 during walk-up churn, then locking. Reproduced
headlessly (BT_FORCE_OSC throttle feathering + BT_FORCE_TURN + the new
per-frame BT_SYNC_LOG probe) and root-caused in two parts:
- SEED: the bring-up turn-in-place trigger arms only the LEG channel, so the
walk re-entry lands one frame apart -> a persistent clip phase offset.
- AMPLIFIER: with offset clips the two end-of-clip callbacks fire on different
frames; a demand change landing between them picks DIFFERENT next states
(one channel winds down, the other keeps walking) -> opposite-phase churn
(observed bs=6 ls=7 -- body on walk-R, leg on walk-L).
Under v4 (display+travel = BODY) the out-of-phase LEG pose showed through on
every frame the body didn't write joints (Standing/wind-down) = the visible
stutter/pop.
DISASM GROUND TRUTH (neither Advance fn has a static decomp caller; found by
byte-scanning the CODE section for e8 calls):
- master perf (0x4a9b5c gap) -> AdvanceLegAnimation @0x4aa399 (air @0x4aa388)
-> -dist/dt into localVelocity (+0x1cc): the LEG drives LOCAL travel and
(writing last) the displayed pose;
- IntegrateMotion (0x4ab1c8, body advance @0x4ab312, caller FUN_004ab430 =
the projected-origin updater) -> -dist/dt into projectedVelocity (+0x2a0):
the BODY is the dead-reckoning PROJECTOR -- locally invisible.
v4's reading of FUN_004ab430 as the travel source was wrong.
FIX: advance body FIRST (projection; its writes get overwritten), leg LAST
(displayed pose); travel = legAdv under the two-channel split. Display ==
travel through the LEG by construction -- body-channel drift can no longer
become visible, structurally (not by input-symmetry luck).
Verified: trn+feathering repro clean; straight drive locked (adv==proj);
solo goto=enemy kill chain intact; MP 2-node drive-to-range cross-pod kill
intact (241 hits, DESTROYED); no crashes. KB corrected (locomotion.md roles
+ P3_LOCOMOTION.md v5 entry). New gated instruments: BT_SYNC_LOG,
BT_FORCE_OSC.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9353d59eb9
commit
d4e139a0d9
+20
-6
@@ -24,12 +24,26 @@ z=0. The `.MOD` physics governed thrust VEHICLES, NOT walk gait. [T1]
|
||||
|
||||
## The two-channel gait (real controls)
|
||||
The BT `SequenceController` (`seqctl.cpp`, from `SelectSequence@004277a8`+`Advance@0042790c`) drives
|
||||
locomotion. Under `BT_REAL_CONTROLS` two channels run:
|
||||
- **LEG channel** writes the skeleton JOINTS from the live demand (a local sim; its joint writes are
|
||||
overwritten by the body).
|
||||
- **BODY channel** is the DISPLAYED motion — travel comes from the SAME Advance that writes the
|
||||
displayed joints (`move_joints=1`), so display == travel BY CONSTRUCTION (v4 fix — killed the
|
||||
desync class). [T2]
|
||||
locomotion. Under `BT_REAL_CONTROLS` two channels run — **roles corrected 2026-07-09 (task #49) from
|
||||
raw disasm** (both Advance fns had NO static decomp caller; found by byte-scanning for `e8` calls):
|
||||
- **LEG channel = the LOCAL sim.** The master perf (the `0x4a9b5c` gap region) calls
|
||||
`AdvanceLegAnimation` @0x4aa399 (airborne @0x4aa388) and stores `-dist/dt` into **localVelocity
|
||||
(+0x1cc)** — the leg drives the local mech's TRAVEL and, advancing LAST (mj=1), the DISPLAYED pose.
|
||||
It reads the LIVE `speedDemand` and owns trn/turn-in-place + the early-stand wind-down guard. [T1]
|
||||
- **BODY channel = the replication PROJECTOR.** `IntegrateMotion` (0x4ab1c8; body advance @0x4ab312,
|
||||
from `FUN_004ab430`, the projected-origin/dead-reckon updater) stores `-dist/dt` into
|
||||
**projectedVelocity (+0x2a0)** — the dead-reckoning feed for update records. Its phase drift is
|
||||
locally INVISIBLE in the binary. (The recon does not yet wire this store — follow-up.) [T1]
|
||||
**v5 (current): display == travel BY CONSTRUCTION through the LEG** (body advances first, leg last).
|
||||
History: v3 (travel=leg, display=body) foot-slipped — two channels, different consumers; v4 unified
|
||||
BOTH on the body (mis-reading 0x4ab430 as the travel source) — feet planted, but whenever the two
|
||||
state machines phase-split, the out-of-phase LEG pose showed through on frames the body didn't write
|
||||
(Standing/wind-down) = the RECURRING "legs stutter / lose sync" report. The split mechanism [T2,
|
||||
reproduced via `BT_FORCE_OSC` + `BT_FORCE_TURN` + `BT_SYNC_LOG`]: (a) SEED — the bring-up trn trigger
|
||||
arms only the leg (authentic trn dispatcher still unlocated), so the walk re-entry lands 1 frame
|
||||
apart; (b) AMPLIFIER — with offset clips the end-of-clip callbacks fire on different frames, so a
|
||||
demand change landing between them picks DIFFERENT next states (one winds down, one keeps walking)
|
||||
→ opposite-phase churn. Under v5 both effects still exist but only skew the projection channel. [T2]
|
||||
The gait STATE MACHINE (`AdvanceBodyAnimation`, mech2.cpp) slews `bodyCycleSpeed` toward the demand
|
||||
through walk/run caps (`LoadLocomotionClips`, mech3.cpp:326 — measures `walkStride`/`standSpeed` via
|
||||
`legAnimation.SelectSequence`). Transition callbacks (`Mech::BodyClipFinished @0x4a6d8c`,
|
||||
|
||||
Reference in New Issue
Block a user