Files
TeslaRel410/restoration/source410/BT/SEQCTL.HPP
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

110 lines
3.1 KiB
C++

#if !defined(SEQCTL_HPP)
# define SEQCTL_HPP
//##################### Forward Class Declarations #######################
class Mech;
class Joint;
class JointSubsystem;
//
// A clip's finished callback: the gait state machine's chance to pick the
// next animation. It re-arms the controller and consumes the carryover
// time itself, returning the distance that carryover covered -- which is
// why Advance can fold its result straight into its own return.
//
typedef Scalar
(*ClipFinishedCallback)(Mech *, unsigned, Scalar, int);
//##########################################################################
//###################### SequenceController ##########################
//##########################################################################
//
// The BT keyframe-animation player embedded in the Mech as legAnimation and
// bodyAnimation. It plays a gait clip: walks the clip's keyframes,
// interpolates each animated joint's rotation, writes it through the joint
// subsystem, and returns the root-translation distance advanced this step.
//
// Reconstructed from the shipped binary (ctor/Init @00427768, SelectSequence
// @004277a8, Advance @0042790c, Reset @004283b8). Not in the surviving 4.10
// archive nor the RP engine. See SEQCTL.NOTES.md.
//
class SequenceController
{
public:
//
// One root-translation entry (per keyframe); ".stride" is the forward
// (local Z) distance the gait reads.
//
struct Keyframe
{
Scalar x, y, stride;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction / Destruction
//
public:
SequenceController();
~SequenceController();
void
Init(Mech *owner_mech);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Playback
//
public:
void
SelectSequence(
int clip_id,
void *finished_callback,
unsigned callback_arg2,
unsigned callback_arg3
);
Scalar
Advance(Scalar time_slice, int move_joints);
void
Reset(int loop);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Skeleton access
//
private:
//
// The clip stores its own slot ordering, so a keyframe slot reaches the
// skeleton only through the clip's jointIndices map. NULL when the
// subsystem is absent or the slot names a joint this mech lacks -- the
// playback paths all tolerate that and simply skip the pose entry.
//
Joint *
GetAnimatedJoint(int slot) const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Clip data (parsed by SelectSequence; keyframe accessors read by the gait)
//
public:
int keyframeCount;
int jointCount;
Scalar *footStepThreshold;
int *jointIndices;
Scalar *keyframeTimes;
void *keyframeBase;
void *keyframeCursor;
Keyframe *keyframeData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Runtime state / links
//
public:
int currentFrame;
Scalar currentTime;
JointSubsystem *jointSubsystem;
Mech *owner;
void *clipResource;
void *finishedCallback;
unsigned callbackArg2;
unsigned callbackArg3;
};
#endif