# 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 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.