Files
TeslaRel410/restoration/source410/BT/MECH.HPP
T
CydandClaude Fable 5 a87c49c158 BT410 5.3.93: the clip loader runs LIVE -- the Mad Cat measures itself, and a 1995 copy-paste bug ships again on purpose
LoadLocomotionClips + ResolveAnimationClip + MeasureClipStride + LoadClipSlot
reconstructed and WIRED into the ctor's GameModel block.  animationClips[] is
no longer an empty array: every gait slot resolves by the model's animation
prefix, and the locomotion constants are now MEASURED from the authored clips
instead of asserted as bring-up defaults.  First live run, arena mission:

  [mech] clips 'mad': standSpeed=5.23 walkStride=18.51 revStride=56.05
                      revSpeedMax=26.26 gimpSpeedMax=-4.23 gimpStride=-20.26
                      limpSet=1

That line carries three verifications at once: the prefix printing as text
proves the Mech__ModelResource layout is right at +0x40; the reverse figures
come out NEGATIVE exactly as the transition machines expect; and the mission
ran clean to live driving afterwards (703 log lines, no fault).

DRIVING FEEL CHANGED, deliberately: speedDemand at 0.6 throttle went 14.4 ->
26.9, because the placeholder top speed (30) gave way to the measured 56.05.
The Mad Cat is simply faster than the bring-up guess.  Authenticity arriving,
not a regression.

TWO BINARY BEHAVIOURS REPRODUCED ON PURPOSE, both documented at the function:

  The speed caps read keyframeData[keyframeCount] -- one entry PAST the last
  frame.  Fencepost is the binary's own (0x690 + 8 + [0x670]*0xc); whether the
  authored table has count+1 entries is unestablished, but the clips were
  authored against this read and the measured values are sane.

  The reverse-cycle stride is computed from STALE locals.  The decomp is
  unambiguous: bbr and bbl are both measured into local_8/local_c, and the
  divide's second terms come from local_10/local_14 -- still holding the
  RUN-LEFT figures.  gimpStrideLength = -((bbl + rrl_stale)/(...)).  A 1995
  copy-paste bug, shipped in every pod for thirty years, reproduced here with
  a comment pointing at the wwr/wwl block that shows the intended pattern.

  (And an earlier scare resolved: the negation IS in the binary -- the very
  next instruction is 0x350 = -0x350.  My first decomp window cut one line
  short and briefly indicted the donor's minus sign.)

ONE DELIBERATE DIVERGENCE, tagged [T3]: the binary dereferences every resolve
result unguarded -- a model missing a mandatory clip crashes on load.  Here a
miss stores NullResourceID (SelectSequence resolves it to an inert controller)
and the dependent measurement is skipped.  Keeps the boot alive on unverified
clip sets; revisit when the fleet's models are known-good.  Measurement binds
pass a NULL finished-callback (measurement parses, never plays -- the binary's
live pointers can never fire there).

Ctor additionally zero-initializes the whole gait channel -- globalTimeScale
defaulting to 1 specifically, because zero would silence every clip advance --
and fills the clip array with NullResourceID before the loader runs, so the
uninitialized-member class of bug (see 5.3.83) is closed here BEFORE the
consumers arrive.

MECH.HPP: the optional limp set carved out (hasGimpClips + 4 measured limp
figures + gyroRumbleTimer); reservedState 150 -> 140.

Next: the four Advance* entry points -- the last link before the legs move.

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

764 lines
27 KiB
C++

//===========================================================================//
// File: mech.hpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: Implementation details for the Mech entity //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#if !defined(MECH_HPP)
# define MECH_HPP
# if !defined(JMOVER_HPP)
# include <jmover.hpp>
# endif
# if !defined(RETICLE_HPP)
# include <reticle.hpp>
# endif
# if !defined(ALARM_HPP)
# include <alarm.hpp>
# endif
# if !defined(STATE_HPP)
# include <state.hpp>
# endif
# if !defined(CHAIN_HPP)
# include <chain.hpp>
# endif
# if !defined(AVERAGE_HPP)
# include <average.hpp>
# endif
# if !defined(CSTR_HPP)
# include <cstr.hpp>
# endif
# if !defined(SEQCTL_HPP)
# include <seqctl.hpp>
# endif
# if !defined(NAMEFILT_HPP)
# include <namefilt.hpp>
# endif
# if !defined(ROTATION_HPP)
# include <rotation.hpp>
# endif
//##################### Forward Class Declarations #######################
class Mech;
class Subsystem;
class SubsystemMessageManager;
class ControlsMapping;
class PlatformTool;
class MechControlsMapper;
class Mech__DamageZone;
class Joint;
class DamageLookupTable;
//###########################################################################
//######################### Mech Model Resource #########################
//###########################################################################
struct Mech__ModelResource:
public JointedMover::ModelResource
{
char animationPrefix[4];
Scalar maxAcceleration;
Scalar superStopAcceleration;
Scalar throttleAdjustment;
Scalar lookLeftAngle;
Scalar lookRightAngle;
Scalar lookFrontAngle;
Scalar lookBackAngle;
Scalar walkingTurnRate;
Scalar runningTurnRate;
Scalar reticleX;
Scalar reticleY;
Scalar relativeMechValue;
int deathEffectResourceID;
Scalar deathSplashDamage;
Scalar deathSplashRadius;
Scalar maxUnstableAcceleration;
Scalar unstableAccelerationEffect;
Scalar unstableGunTheEngineEffect;
Scalar unstableSuperStopEffect;
Scalar unstableHighVelocityEffect;
Scalar unstableStopedTurnEffect;
Scalar updatePositionDiffrence;
Scalar updateTurnVelocityDiffrence;
Scalar updateTurnDegreeDiffrence;
Scalar timeDelay;
Vector3D cameraOffset;
char shadowJointName[20];
};
//###########################################################################
//########################## Mech Make Message ##########################
//###########################################################################
class Mech__MakeMessage:
public JointedMover::MakeMessage
{
public:
char resourceNameA[20];
char resourceNameB[20];
char resourceNameC[20];
Mech__MakeMessage(
Receiver::MessageID message_ID,
size_t length,
const EntityID &entity_ID,
Entity::ClassID class_ID,
const EntityID &owner_ID,
ResourceDescription::ResourceID resource_ID,
LWord instance_flags,
const Origin &origin,
const Motion &velocity,
const Motion &acceleration,
const char *badge,
const char *color,
const char *patch
):
JointedMover::MakeMessage(
message_ID, length, entity_ID, class_ID, owner_ID,
resource_ID, instance_flags, origin, velocity, acceleration
)
{
Str_Copy(resourceNameA, badge, sizeof(resourceNameA));
Str_Copy(resourceNameB, color, sizeof(resourceNameB));
Str_Copy(resourceNameC, patch, sizeof(resourceNameC));
}
};
//###########################################################################
//####################### MechAnimationState ############################
//###########################################################################
//
// The gait clip a mech is currently playing. Recovered VERBATIM from the
// 0x3c-byte-stride name table at .data:0050cfe8 -- the table the binary's
// "Unsupported mech animation" assert indexes -- so the names and their order
// are the original's, not a reading of behaviour.
//
// The cycle alternates LEFT and RIGHT because each clip is one stride: a
// walk is Right -> Left -> Right forever, and the transitions in and out of
// stand / run / reverse each have their own handed pair so the mech always
// leaves a cycle on the correct foot.
//
enum MechAnimationState
{
StandingAnimation = 0x00,
RightStandToWalkAnimation = 0x01,
RightWalkForwardAnimation = 0x02,
LeftWalkForwardAnimation = 0x03,
RightWalkToStandAnimation = 0x04,
LeftWalkToStandAnimation = 0x05,
RightWalkToRunAnimation = 0x06,
LeftWalkToRunAnimation = 0x07,
RightRunAnimation = 0x08,
LeftRunAnimation = 0x09,
RightRunToWalkAnimation = 0x0a,
LeftRunToWalkAnimation = 0x0b,
RightStandToReverseAnimation = 0x0c,
LeftStandToReverseAnimation = 0x0d,
RightReverseAnimation = 0x0e,
LeftReverseAnimation = 0x0f,
RightReverseToStandAnimation = 0x10,
LeftReverseToStandAnimation = 0x11,
LeftWalkToGimpAnimation = 0x12,
RightWalkToGimpAnimation = 0x13,
LeftGimpAnimation = 0x14,
RightGimpAnimation = 0x15,
LeftGimpToStandAnimation = 0x16,
RightGimpToStandAnimation = 0x17,
FallForwardAnimation = 0x18,
FallBackwardAnimation = 0x19,
FallLeftAnimation = 0x1a,
FallRightAnimation = 0x1b,
CrashAnimation = 0x1c,
AnimationCount = 0x1d,
//
// The clip ARRAY is larger than the name table. Slot 0x20 holds the
// bump/crash clip (mech+0x64c) bound on a hard wall impact, so the
// array runs to 0x21 entries even though only 0x1d are named.
//
AnimationSlotCount = 0x21
};
//
// A WARNING ABOUT THE NAMES ABOVE, which are verbatim from the binary's table
// and therefore authoritative as NAMES -- but are NOT a reliable guide to what
// each slot actually plays.
//
// The clip loader's slot assignments (LoadLocomotionClips) disagree with them.
// Slot 0x0e takes the "rwr" run-to-walk clip while the table calls 0x0e
// RightReverseAnimation; the reverse gait actually lives at 0x10-0x15
// (sbr/sbl entry, bbr/bbl cycle, bsr/bsl exit); and the states the gait
// machine alternates between while walking forward are 6 and 7, not the
// pair the "WalkForward" names suggest.
//
// Read the slot map in MECH2.NOTES.md when the question is "what does this
// state play"; read the enum when the question is "what did the original call
// this index". Conflating the two is how BT411 shipped a reverse gait that
// played its coming-to-a-halt frames mid-cycle.
//
//###########################################################################
//############################## Mech ###############################
//###########################################################################
class Mech:
public JointedMover
{
//
// The per-zone damage code walks the roster / segment table / alarms
// directly (the 1995 arrangement, mirrored by the reconstruction).
//
friend class Mech__DamageZone;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static SharedData DefaultData;
static const HandlerEntry
MessageHandlerEntries[];
static MessageHandlerSet
MessageHandlers;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support -- the ENTITY-level rows the cockpit binds without a
// subsystem prefix (the radar trio the map() primitive reads, the speed
// pair the speedometer arc reads, and the duck/crouch state selector).
// Chained onto JointedMover's index.
//
public:
enum {
RadarRangeAttributeID = JointedMover::NextAttributeID,
RadarLinearPositionAttributeID,
RadarAngularPositionAttributeID,
LinearSpeedAttributeID,
MaxRunSpeedAttributeID,
DuckStateAttributeID,
//
// The 3-D renderer reads this BY NAME: DPLRenderer::SetupCull
// (CODE/RP/MUNGA_L4/L4VIDEO.CPP:4990) fetches
// "EyepointRotation" off the viewpoint entity and, for a
// JointedMover, composes it with the siteeyepoint segment to
// build worldToEyeMatrix. Without it the engine Fails every
// frame. The member already existed -- only the publication
// was missing.
//
EyepointRotationAttributeID,
//
// Bound BY NAME by an authored AttributeWatcher (BTL4.RES); the
// shipped binary carries the same string in its pool.
//
UnstablePercentageAttributeID,
CollisionSpeedAttributeID,
DistanceToMissileAttributeID,
FootStepAttributeID,
IncomingLockAttributeID,
//
// The remaining names BTL4.RES binds watchers to. The two
// state indicators already existed as members -- only the
// publication was missing, same as EyepointRotation.
// AnimationState is bound 307 times in the resource: it is how
// the audio system follows the gait.
//
CollisionStateAttributeID,
CollisionNormalAttributeID,
AnimationStateAttributeID,
ReduceButtonAttributeID,
NextAttributeID
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
static Logical
TestClass(Mech &);
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef Mech__ModelResource ModelResource;
typedef Mech__MakeMessage MakeMessage;
typedef Mech__DamageZone DamageZone;
static Mech*
Make(MakeMessage *creation_message);
Mech(
MakeMessage *creation_message,
SharedData &shared_data = DefaultData
);
~Mech();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// App interface
//
public:
void
SetMappingSubsystem(Subsystem *mapper);
//
// Resolve a skeleton joint by name (the shared resolver the subsystems
// use to bind their animated joints -- Torso twist, etc.):
// GetSegment(name) -> segment jointIndex -> JointSubsystem::GetJoint.
//
Joint*
ResolveJoint(const char *joint_name);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per-frame simulation (the mech's body Performance; installed by the ctor
// and dispatched each frame from Simulation::PerformAndWatch).
//
public:
typedef void
(Mech::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
Simulate(Scalar time_slice);
//
//--------------------------------------------------------------------
// The gait transition machine (mech2.cpp).
//
// Set*Animation binds a channel's clip player to the clip for a state
// and records the state; *Transition is the shared tail every handler
// ends in (bind the next state, then spend the leftover time in it).
//
// *ClipFinished are the callbacks the clip players invoke at end of
// clip -- hence static, taking the mech explicitly. They are
// RE-ENTRANT BY CONTRACT: each re-arms its channel and advances the
// carryover itself, returning the distance that carryover covered, so
// SequenceController::Advance folds the result into its own return.
//--------------------------------------------------------------------
//
void
SetLegAnimation(int state);
void
SetBodyAnimation(int state);
Scalar
LegTransition(int next_state, Scalar advance_time, int move_joints);
Scalar
BodyTransition(int next_state, Scalar advance_time, int move_joints);
static Scalar
LegClipFinished(
Mech *mech,
unsigned callback_arg,
Scalar carryover,
int move_joints);
static Scalar
BodyClipFinished(
Mech *mech,
unsigned callback_arg,
Scalar carryover,
int move_joints);
//
//--------------------------------------------------------------------
// The gait clip loader (mech2.cpp). ResolveAnimationClip maps the
// model's animation prefix + a 3-char suffix to a clip resource;
// MeasureClipStride binds a loaded slot and integrates its keyframe
// strides; LoadLocomotionClips fills animationClips[] and measures
// every gait constant from the clips themselves.
//--------------------------------------------------------------------
//
ResourceDescription::ResourceID *
ResolveAnimationClip(const char *prefix, const char *suffix);
void
MeasureClipStride(int slot, Scalar *total, Scalar *last_key);
void
LoadLocomotionClips(ModelResource *model);
int
LoadClipSlot(int slot, const char *prefix, const char *suffix);
//
// The respawn heal-and-move (binary @0049fb74): reposition the SAME
// entity at the drop-zone origin, kill all motion, clear the death
// latch, heal every hull zone and sweep the roster through
// DeathReset. Shared by the initial drop-in and the respawn.
//
void
Reset(const Origin &origin, Logical full_reset);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Resource creation
//
public:
static void
CreateMakeMessage(
MakeMessage *creation_message,
NotationFile *model_file,
const ResourceDirectories *directories
);
static ResourceDescription::ResourceID
CreateModelResource(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories,
ModelResource *model = 0
);
static ResourceDescription::ResourceID
CreateSubsystemStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
);
static ResourceDescription::ResourceID
CreateDamageZoneStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
);
static ResourceDescription::ResourceID
CreateSkeletonStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
);
static ResourceDescription::ResourceID
CreateExplosionTableStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
);
typedef Logical (*FindNameFunction)(
const char *control_name, ControlsMapping *mapping);
static ResourceDescription::ResourceID
CreateControlMappingStream(
const char *mapping_name,
NotationFile *mapping_file,
FindNameFunction find_name,
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories,
PlatformTool *current_tool
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Subsystem roster accessors (the roster itself -- subsystemArray /
// subsystemCount -- lives in the base Entity)
//
public:
Subsystem*
GetGyroSubsystem() { Check(this); return gyroSubsystem; }
Subsystem*
GetTorsoSubsystem() { Check(this); return sinkSourceSubsystem; }
Subsystem*
GetHudSubsystem() { Check(this); return hudSubsystem; }
Subsystem*
GetSensorSubsystem() { Check(this); return sensorSubsystem; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Locomotion parameters (read by MechControlsMapper::InterpretControls to
// shape the demands, and by the drive in Simulate). reverseStrideLength is
// the top/run cycle speed the throttle scales (the naming is the 1995
// field's; LoadLocomotionClips measures it from the run clips), walkStride
// Length the walk speed. Turn rates are radians/sec.
//
public:
Scalar GetReverseStrideLength() const { Check(this); return reverseStrideLength; }
Scalar GetWalkStrideLength() const { Check(this); return walkStrideLength; }
Scalar GetForwardThrottleScale() const { Check(this); return forwardThrottleScale; }
void SetForwardThrottleScale(Scalar s){ Check(this); forwardThrottleScale = s; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Eyepoint / aim-ray composition. The pilot's torso-elevation aim (stick
// pitch) does NOT tilt any skeleton joint on this mech family -- it pitches
// the COCKPIT EYE and the weapon boresight (pixel-calibrated in the BT411
// reverse-engineering: "pitch does not work" turned out to mean nothing
// consumed Torso's currentElevation, not that a joint was un-animated).
// Composed each frame in Simulate; DPLEyeRenderable / the aim ray read it.
//
public:
const EulerAngles&
GetEyepointRotation() const { Check(this); return eyepointRotation; }
//
// The current target (the binary's mech target slot @0x388): read by
// the weapons' HasActiveTarget / UpdateTargeting and the fire path.
// The authentic writer is the reticle targeting step (mech4, the
// render wave); the dev harness sets it directly.
//
Entity*
GetTargetEntity() const { Check(this); return targetEntity; }
void
SetTargetEntity(Entity *target) { Check(this); targetEntity = target; }
//
// Damage entry (overrides the Entity handler): latches the attacker
// (mech+0x43c, read by the zone LOD router) and feeds the gyro
// cockpit bounce before chaining to the base zone routing. The
// cylinder unaimed-hit resolver (type-0x1d table) is a later wave.
//
void
TakeDamageMessageHandler(TakeDamageMessage *message);
//
// Damage-side death flag: the body graphic alarm (statusAlarm,
// binary @0x714) at level >= 9 = the death/fall state.
//
Logical
IsMechDestroyed() { Check(this); return statusAlarm.GetLevel() >= 9; }
//
// The live torso twist (the cylinder table's rotate-with-torso rows
// follow it). NULL-safe: 0 with no torso subsystem.
//
Scalar
CurrentTorsoTwist();
DamageLookupTable*
GetDamageLookupTable() const { Check(this); return damageLookupTable; }
//
// Look-state commit (called by the controls mapper on a look-button
// state change): re-aim the eyepoint from the model's authored look
// angles. look_state = MechControlsMapper::LookState.
//
void
CommitLookState(int look_state);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data
//
protected:
//
// Cached subsystem back-pointers (also held in the base roster).
//
Subsystem *sensorSubsystem;
Subsystem *gyroSubsystem;
Subsystem *sinkSourceSubsystem; // the real Torso
Subsystem *hudSubsystem;
SubsystemMessageManager *messageManager;
int weaponCount;
//
// Capability sub-rosters (Socket views onto the subsystem roster;
// populated per-frame -- phase 5).
//
ChainOf<Subsystem*> controllableSubsystems;
ChainOf<Subsystem*> watchedSubsystems;
ChainOf<Subsystem*> heatableSubsystems;
ChainOf<Subsystem*> weaponRoster;
ChainOf<Subsystem*> damageableSubsystems;
//
// Embedded status / animation / naming state.
//
NameFilter mechNameFilter;
AlarmIndicator masterAlarm;
AlarmIndicator heatAlarm;
AlarmIndicator stabilityAlarm;
AlarmIndicator statusAlarm;
Reticle targetReticle;
StateIndicator animationState;
StateIndicator replicantAnimationState;
StateIndicator collisionState;
SequenceController legAnimation;
SequenceController bodyAnimation;
//
// GAIT ANIMATION (mech2.cpp). Two parallel channels, each a clip
// player plus a state alarm holding the current MechAnimationState:
//
// LEG channel -- the locally-simulated gait. Its transitions read
// the LIVE commanded speed from the controls mapper.
// BODY channel -- the displayed-motion gait, whose advanced distance
// is what feeds the mech's forward motion. Its
// transitions read bodyTargetSpeed instead, so a
// dead-reckoned or networked mech walks correctly
// without a local controls mapper.
//
// The alarms ARE the state (binary mech+0x3b0 / +0x728 mirror the
// alarm level); read them with GetLevel rather than keeping a copy.
//
AlarmIndicator legStateAlarm;
AlarmIndicator bodyStateAlarm;
Scalar legCycleSpeed; // channel-A cycle speed
Scalar bodyCycleSpeed; // channel-B cycle speed
Scalar forwardCycleRate; // cycle-speed slew rate
Scalar gimpCycleRate; // slew rate while limping
Scalar standSpeed; // "moving at all" threshold
Scalar gimpSpeedMax; // limp cycle speed cap
Scalar gimpStrideLength; // limp clip length (NEGATIVE
// as authored -- the machines
// fold the sign)
Scalar globalTimeScale; // multiplies every increment
//
// Clip handle per gait SLOT (binary mech+0x5cc, indexed by state).
//
// SIZED 0x21, NOT AnimationCount. The name table stops at 0x1d but
// the array does not: slot 0x20 (mech+0x64c) is the bump/crash clip
// the mech binds on a hard wall impact. Sizing this by the enum
// leaves that slot off the end.
//
int animationClips[AnimationSlotCount];
//
// The OPTIONAL limp clip set (slots 22-27). LoadLocomotionClips
// probes for the "wgl" clip; a model without it leaves hasGimpClips 0
// and those slots unfilled, so the limp machine must never be entered
// for such a mech. (These four measured figures ARE the limp's --
// unlike gimpSpeedMax/gimpStrideLength above, which despite the names
// are measured from the REVERSE clips. See MECH2.NOTES.md.)
//
int hasGimpClips;
Scalar gimpLeftSpeedMax;
Scalar gimpRightSpeedMax;
Scalar gimpLeftStrideLength;
Scalar gimpRightStrideLength;
Scalar gyroRumbleTimer; // binary mech+0x5c4; the
// clip loader zeroes it
AverageOf<Scalar> telemetryFilter[5];
CString resourceNameA;
CString resourceNameB;
CString resourceNameC;
//
// Locomotion state (Phase 5.3). Turn rates in rad/s, speeds/strides in
// world-units/s. reverseStrideLength = top (run) speed; walkStrideLength
// = walk speed; reverseSpeedMax = the low-speed turn-rate gate; forward
// ThrottleScale multiplies the forward demand; bodyTargetSpeed = the
// demanded speed; currentBodySpeed = the accel-tracked actual speed.
//
Scalar walkingTurnRate;
Scalar runningTurnRate;
Scalar reverseStrideLength;
Scalar walkStrideLength;
Scalar reverseSpeedMax;
Scalar forwardThrottleScale;
Scalar maxBodyAcceleration;
Scalar bodyTargetSpeed;
Scalar currentBodySpeed;
//
// Composed each frame in Simulate: the look-state eye component
// (lookPitch/lookYaw, set by CommitLookState from the authored look
// angles) plus the Torso elevation.
//
EulerAngles eyepointRotation;
Scalar lookPitch;
Scalar lookYaw;
Entity *targetEntity;
EntityID lastInflictingID; // binary mech+0x43c -- last
// attacker (zone LOD router)
Scalar lastInflictingDamage; // the killing-blow
// magnitude (kill score)
int deathTransitionDone; // the once-per-death latch
// (binary movementMode 2||9)
DamageLookupTable *damageLookupTable; // binary mech+0x444 -- the
// cylinder hit-location table
//
// Cockpit-published state (bound by name from L4GAUGE.CFG): the radar
// scale + the pointers the map gauge follows, and the duck selector.
// The position/angle pointers are the 1995 shape -- the gauge holds a
// pointer TO the pointer and re-reads the live transform every frame.
//
Scalar radarRange;
Point3D *radarLinearPosition;
Quaternion *radarAngularPosition;
int duckState;
//
// STAGED VALUE (the instability model is not reconstructed).
// The mech streams the authored unstable* effect constants in its
// resource (maxUnstableAcceleration and friends) but nothing yet
// computes how close the mech is to going over. It is PUBLISHED
// because an authored AttributeWatcher in BTL4.RES binds
// "UnstablePercentage" by name and the engine Fails outright when
// the name does not resolve (WATCHER.CPP:141). Holds 0 = stable
// until the model lands.
//
Scalar unstablePercentage;
//
// The AUDIO watcher set, staged alongside unstablePercentage.
// BTL4.RES binds exactly six attribute watchers by name --
// UnstablePercentage, CollisionSpeed, FootStep, IncomingLock,
// DistanceToMissile (Mech) and SpeedEffect (Myomers) -- and the
// engine Fails outright on any that does not resolve.
// These are the sound triggers: impact volume, footfalls, the
// missile-lock warning, the myomer whine.
//
// TYPES ARE PROVISIONAL. AttributeWatcherOf<T> reads
// *(T*)attributePointer (WATCHER.HPP:288) and the instantiation
// comes from the resource, which we cannot read off the string
// pool. Nothing drives these yet, so nothing ever changes and no
// watcher can fire -- settle the types WITH the models that write
// them (collision response, the gait FSM, the missile threat
// track), not before.
//
Scalar collisionSpeed;
Scalar distanceToMissile;
int footStep;
int incomingLock;
UnitVector collisionNormal; // staged with collisionSpeed
int reduceButton; // staged: cockpit button feed
//
// The model's authored look-view angles (rad; deg in the resource).
//
Scalar lookLeftAngle;
Scalar lookRightAngle;
Scalar lookFrontAngle;
Scalar lookBackAngle;
//
// Remaining per-frame targeting / animation state, advanced by the
// mech2/mech3/mech4 simulation. Reserved until that path is
// reconstructed with named fields.
//
//
// 51 ints carved out for the gait channel above (2 AlarmIndicators = 4,
// 8 Scalars, animationClips[0x21] = 33, the optional limp set = 6);
// 191 -> 140.
//
int reservedState[140];
};
#endif