Files
TeslaRel410/restoration/source410/BT/SEQCTL.NOTES.md
T
CydandClaude Fable 5 c2b0a371ba BT410 5.3.90: the gait player is real -- SequenceController::Advance was the Fail() stub under the legs
Asked what was next for the rebuild, went to measure it, and found I had told
the operator the wrong thing one turn earlier.

THE CORRECTION.  I said mech2.cpp (the gait state machine) was the next
package.  It sits on top of SequenceController's playback methods, and all
three of those -- SelectSequence, Advance, Reset -- were Fail() stubs.  Advance
is the function that actually walks a clip's keyframes and writes each
animated joint.  mech2 without it would have called straight into a Fail.  So
the dependency runs:

    Mech::AdvanceLegAnimation      mech2.cpp -- the STATE MACHINE, still absent
      -> SequenceController::Advance   THIS -- keyframes -> joint writes
        -> Joint::SetHinge / SetRotation / SetTranslation
          -> the joint DCS flush fixed in 5.3.88

5.3.88 fixed the bottom link, this fixes the middle, the top is still missing.

WHAT THIS DOES NOT DO: make the legs move.  Nothing calls any of it yet -- no
gait state is ever selected because mech2 is unreconstructed -- so a run looks
identical to yesterday's.  This removes a blocker.  Saying otherwise would be
easy and wrong.

RECONSTRUCTED (compile-verified, BT 50/50, links clean):

  SelectSequence @004277a8 -- find + lock the clip, parse its layout.  The
  fetch is FindResourceDescription and NOT SearchList: the ID arriving here is
  already resolved, and SearchList would treat it as a resource LIST and walk
  the clip bytes as IDs.

  Advance @0042790c -- snap through every keyframe the new time has passed
  (writing authored poses), then interpolate the partial frame.  Returns the
  forward distance covered, which is what the gait feeds into mech motion.

  Reset @004283b8 -- return every animated joint to neutral, so an abandoned
  gait doesn't leave the skeleton frozen on a stale frame.

THE PART THAT CANNOT BE SEEKED.  The pose block is packed BY JOINT TYPE -- 8
bytes hinge, 12 ball, 24 ball+translation -- so the root-translation table
behind it is not reachable by arithmetic on any stored count.  SelectSequence
must walk the entire skeleton summing per-joint sizes to find it.  Which also
means the parse depends on the mech's own skeleton agreeing with the clip: an
unresolvable slot returns NULL and contributes 0, keeping the walk honest
rather than drifting keyframeData onto garbage.

TWO CONTRACTS worth recording before mech2 is written against them:

  move_joints == 0 is NOT "do nothing" -- it advances the clock and
  accumulates distance while leaving the skeleton alone.  That is how the body
  channel measures a stride without fighting the leg channel over the same
  joints.

  The finished callback is RE-ENTRANT BY DESIGN.  At end of clip it picks the
  next state, re-arms this controller through SelectSequence (rewinding it to
  frame 0), and advances the carryover itself -- so its return value is the
  distance that carryover covered, folded straight into Advance's own return.
  mech2's BodyClipFinished has to honour that or the gait double-counts.

NOT CARRIED OVER from the donor: its BT_HIP_LOG diagnostic and audio
footstep-broadcast path, neither of which is 1995 code.  footStepThreshold IS
parsed -- the field is real and authored -- but nothing reads it yet.

AND A CAVEAT ON THE 91% I QUOTED: seqctl.cpp is not in the 50-TU BT census at
all (its code sits below the BT address range the census was built from), and
that figure counts a TU as done if the FILE EXISTS -- 18 files still carry 26
Fail() stubs between them.  The census understates what "playable" needs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 00:36:39 -05:00

113 lines
5.8 KiB
Markdown

# SEQCTL.HPP / .CPP — reconstruction notes
**Status: FULLY RECONSTRUCTED (compile-verified 2026-08-02). The three
per-frame playback methods -- SelectSequence / Advance / Reset -- are now real;
no `Fail()` stub remains in this TU.**
`SequenceController` — the BT keyframe-animation player embedded in the Mech as
`legAnimation` (@0x65c) and `bodyAnimation` (@0x6bc). Plays a gait clip: walks
keyframes, interpolates each animated joint's rotation, writes it through the
joint subsystem, returns the root-translation distance advanced. Absent from the
4.10 archive and the RP engine; reconstructed from the shipped binary + BT411's
seqctl.cpp decomp.
## What's real vs staged
- **Real (ctor-time):** default ctor (zeroes all fields) and `Init(Mech*)` — the
Mech ctor calls `legAnimation.Init(this)` / `bodyAnimation.Init(this)`
(binary; BT411 mech.cpp:685-686), so Init must run at construction. Init binds
`owner` and caches `jointSubsystem = owner->GetJointSubsystem()`
(JMOVER.HPP:104, returns `JointSubsystem*`), `clipResource = NULL`.
- **Staged (per-frame, after boot):** `SelectSequence` (@004277a8, clip parse +
resource lock), `Advance` (@0042790c, keyframe interpolation + joint writes —
the gait engine, ~145 lines in the decomp), `Reset` (@004283b8). These fire
only once the mech is ticking, well past the current ctor frontier, so they
Fail loudly until reconstructed.
## Field layout
Field set + order from BT411's binary-offset map (keyframeCount +0x14 …
callbackArg3 +0x50). Byte-exact offsets are not required for the functional
build (only wire/update-record layout needs that, and SequenceController is not
serialised) — the members are declared as named fields and BC4.52 lays them out;
the Mech's legAnimation/bodyAnimation slots size to `sizeof(SequenceController)`
self-consistently.
## Placement
1995 filename unknown (decomp-only; BT411 used seqctl.cpp). Filed as
BT/SEQCTL.HPP + SEQCTL.CPP. Not in the authentic BT.MAK member list, so the
build appends seqctl.obj to bt.lib via the extra-objs fallback (order is
immaterial — no static init).
## Playback reconstructed (2026-08-02)
`Advance` is **the thing that actually writes mech joints** — the reason the
legs never moved is that this was a `Fail()` stub, not anything in the render
path. It is worth being precise about the dependency, because it reverses an
earlier claim of mine:
Mech::AdvanceLegAnimation (mech2.cpp -- the gait STATE MACHINE, still absent)
-> SequenceController::Advance (THIS -- walks keyframes, writes joints)
-> Joint::SetHinge / SetRotation / SetTranslation
-> the render side's joint DCS flush (BT410 5.3.88)
5.3.88 fixed the bottom link (hinges now flush as full matrices the renderer
applies) and this fixes the middle one. The top link is still missing, so
**nothing calls any of this yet** — mech2.cpp is unreconstructed and no gait
state is ever selected. This commit removes a blocker; it does not make legs
move, and a run will look identical.
### The clip layout, and the one part that can't be seeked
```
int frameCount hdr[0]
int jointCount hdr[1]
Scalar footStepThreshold hdr[2] authored contact height
int jointIndices[jointCount] slot -> skeleton joint
Scalar frameTimes[frameCount] keyframe timestamps
<pose> per frame, per joint, PACKED BY DOF
Keyframe rootTranslations[frameCount] .stride == the forward step
```
The pose block is packed by joint TYPE — 8 bytes for a hinge, 12 for a ball,
24 for ball+translation — so the root-translation table behind it cannot be
reached by arithmetic on any stored count. `SelectSequence` has to walk the
whole skeleton summing per-joint sizes to find it. That walk is `PoseSize`.
A consequence worth recording: the parse depends on the MECH'S OWN skeleton
agreeing with the clip's. If `jointIndices` named a joint the mech lacks, the
size walk would drift and `keyframeData` would point at garbage. Slots that
don't resolve return NULL and contribute 0, which keeps the walk honest.
### Two things in Advance that are easy to get wrong
**`move_joints == 0` is not "do nothing".** It advances the clock and
accumulates distance while leaving the skeleton alone. That is how the body
channel measures a stride without fighting the leg channel over the same
joints — both channels play clips, only one drives the pose.
**The finished callback is re-entrant by design.** At end of clip it picks the
next gait state, re-arms THIS controller via `SelectSequence` (rewinding it to
frame 0), and advances the carryover itself — so by the time it returns, the
controller is already playing the next clip and the callback's return value is
the distance that carryover covered. `Advance` folds it straight into its own
return. Reconstructing mech2's `BodyClipFinished` will need to honour that
contract exactly or the gait will double-count distance.
### Deliberately not carried over from the donor
BT411's `Advance` carries a `BT_HIP_LOG` diagnostic and an audio
footstep-broadcast path. Neither is 1995 code — the log is the port's own
debugging and the audio belongs to its `AudioComponent` work — so neither is
here. `footStepThreshold` IS parsed, because the field is real and in the
authored resource; nothing reads it yet.
### Not measured by the manifest
`seqctl.cpp` is **not in the 50-TU BT census** — its code sits in the
0x4277a8-0x428xxx range, below the BT range the census was built from. So the
"91% of the census reconstructed" figure never counted this file at all, and
the census understates what the gait needs. Worth remembering before quoting
that percentage as progress toward playable.