Files
BT411/game/reconstructed/mech.hpp
T
arcattackandClaude Fable 5 77190c93e5 Combat: the AUTHENTIC consolidated damage delivery -- TakeDamageStream through the message manager (task #7 tail)
ConsolidateAndSendDamage @0049b784 fully implemented: the beam path submits
into AddDamageMessage; each messmgr tick builds ONE Entity::
TakeDamageStreamMessage (id 0x13; wire-verified 0x34+4+Nx12) with the
common impact record + appended {type, amount, subsystemID} entries and
Dispatches it at the victim -- the T0 handler (ENTITY.cpp:817) re-splits
into per-record TakeDamage; replicants reroute cross-pod.  Real
ResolveExplosionID (firing weapon's +0x3E4, guarded) + SubmitExplosion via
the Explosion::Make port; TWO chain-purge bugs fixed (the "iterator dtor
clears it" assumption was wrong -- records re-applied every tick, observed
1->2->3->4 double-counting).  @004b9728 identity corrected (SendDamageMessage,
not DrawWeaponPip).  Verified solo: 25 clean single-record consolidations ->
kill -> death transition.  Residue: weapon-side submission awaits the
damage-economy reconciliation (authored 0.25-scale vs kShotDamage=12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:30:30 -05:00

1019 lines
51 KiB
C++

//===========================================================================//
// File: mech.hpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: Mech -- the BattleMech player/AI entity (declaration) //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/95 ?? Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, BTL4OPT.EXE).
//
// The Mech class is the heart of the game and its implementation is split
// across mech.cpp .. mech4.cpp. This header declares the *whole* class so
// the recovered mech.cpp slice compiles; methods whose bodies live in the
// sibling translation units are marked [mech2] / [mech3] / [mech4].
//
// mech.cpp slice recovered here: 0x004a1232 .. 0x004a5027
// (bounded below by the inter-module gap 0x0049ffc8..0x004a1232 that
// separates it from btmssn.cpp, and above by mech2.cpp @0x004a5028.)
//
// Structural template: Red Planet's player vehicle entity VTV (RP/VTV.h),
// which -- like Mech -- derives from JointedMover and hosts a roster of
// streamed Subsystem objects. Surviving BT headers MECHDMG.HPP / MECHTECH.HPP
// supplied the DamageZone / MechTech shapes.
//
// Field offsets in comments are BYTE offsets into the shipped object
// (sizeof(Mech) == 0x854, see Mech::Make @004a2d48). The decomp addresses
// the object as int words, so this[0xNN] == byte offset 0xNN*4. Names are
// taken from VTV / the surviving BT headers where an analog exists and are
// inferred from usage otherwise (flagged TODO / "best-effort").
//
#if !defined(MECH_HPP)
# define MECH_HPP
#if !defined(JMOVER_HPP)
# include <jmover.hpp> // JointedMover -- engine base (MUNGA)
#endif
#if !defined(CONTROLS_HPP)
# include <controls.hpp>
#endif
#if !defined(DAMAGE_HPP)
# include <damage.hpp>
#endif
#if !defined(ALARM_HPP)
# include <alarm.hpp> // GaugeAlarm
#endif
#include <tool.hpp> // PlatformTool
#include <affnmtrx.hpp> // AffineMatrix
#include <average.hpp> // AverageOf<T>
#include <scalar.hpp> // Scalar
#include <reticle.hpp> // Reticle (the TargetReticle attribute struct)
#include "mechrecon.hpp" // reconstruction shim (proxies, artifact globals, type aliases)
//##################### Reconstruction type aliases #####################
// Names inferred by the decompiler mapped to the real engine types.
// (The actual aliases are now defined in mechrecon.hpp under the same guard.)
#if !defined(BT_RECON_TYPE_ALIASES)
# define BT_RECON_TYPE_ALIASES
typedef ReconMatrix Matrix34;
typedef ReconAlarm AlarmIndicator;
typedef ReconFiltered FilteredScalar;
#endif
//##################### Forward Class Declarations #######################
class Mech;
class Subsystem;
class MechSubsystem;
class MechWeapon;
class MechControlsMapper;
class MechTech;
class Mech__DamageZone; // see MECHDMG.HPP
class HitQuery; // weapon sweep query (mech4)
class Joint; // engine skeleton node (JOINT.h); ResolveJoint result
//
// Streamed-subsystem resource blob handed to each <Class>::CreateStreamedSubsystem.
// The recovered factory only passes it through, so an opaque alias suffices.
//
typedef void SubsystemResourceBlob;
//
// Per-shot impact descriptor consumed by Mech::ResolveWeaponImpact (mech4).
// Fields are those the recovered body reads.
//
struct ShotDescriptor
{
Vector3D dir; // +0x14 shot direction
Scalar damage; // computed impact damage
Scalar fieldAt(int) const { return Scalar(0); } // raw +0x240/0x244/0x248 reads
};
//###########################################################################
//######################### Mech::ModelResource #########################
//###########################################################################
//
// Parsed by Mech::CreateModelResource @004a2da8 from the "gamedata" notation
// block. Layout mirrors the float[] the decomp fills (pfVar3, a 200-byte /
// 0x32-word record). Indices in comments are the word index used by the
// parser (e.g. pfVar3+0x11 == MaxAcceleration).
//
struct Mech__ModelResource:
public JointedMover::ModelResource // == Mover__ModelResource, 0x40 bytes
{
//
// AUTHORITATIVE overlay of the 200-byte (0x32-word) segment-0xf record
// (task #4, 2026-07-11). Writer chain [T1]: FUN_004a2da8 (Mech) after
// FUN_004238bc (Mover, words 0x00-0x0F) + FUN_00435ac8 (Reticle, words
// 0x1A-0x1B). Runtime reader: the ctor pass @0x4a24d1-0x4a2968. The
// old struct had two size errors (animationPrefix char[12] vs 4 bytes;
// a FICTITIOUS char skeletonName[64]) that skewed every later member
// +2/+15 words and pushed the fieldXX block past the record end --
// every fieldXX read was out-of-bounds garbage (the forwardCycleRate
// "floor 25" era). There is NO skeleton-name block in this record.
//
char animationPrefix[4]; // 0x40 w0x10 "AnimationPrefix" (3 letters + NUL)
Scalar maxAcceleration; // 0x44 w0x11 "MaxAcceleration"
Scalar superStopAcceleration; // 0x48 w0x12 "SuperStopAcceleration"
Scalar throttleAdjustment; // 0x4C w0x13 "ThrottleAdjustment"
Scalar lookLeftAngle; // 0x50 w0x14 "LookLeftAngle" (degrees)
Scalar lookRightAngle; // 0x54 w0x15 "LookRightAngle"
Scalar lookFrontAngle; // 0x58 w0x16 "LookFrontAngle"
Scalar lookBackAngle; // 0x5C w0x17 "LookBackAngle"
Scalar walkingTurnRate; // 0x60 w0x18 "WalkingTurnRate" (degrees)
Scalar runningTurnRate; // 0x64 w0x19 "RunningTurnRate"
Scalar reticleX; // 0x68 w0x1A "ReticleX" (Reticle level; default 0)
Scalar reticleY; // 0x6C w0x1B "ReticleY"
Scalar relativeMechValue; // 0x70 w0x1C "RelativeMechValue"
int deathEffectResourceID; // 0x74 w0x1D "DeathEffect" (resolved id)
Scalar deathSplashDamage; // 0x78 w0x1E "DeathSplashDamage"
Scalar deathSplashRadius; // 0x7C w0x1F "DeathSplashRadius"
Scalar maxUnstableAcceleration; // 0x80 w0x20 "MaxUnstableAcceleration"
Scalar unstableAccelerationEffect; // 0x84 w0x21 "UnstableAccelerationEffect"
Scalar unstableGunTheEngineEffect; // 0x88 w0x22 "UnstableGunTheEngineEffect"
Scalar unstableSuperStopEffect; // 0x8C w0x23 "UnstableSuperStopEffect"
Scalar unstableHighVelocityEffect; // 0x90 w0x24 "UnstableHighVelocityEffect"
Scalar unstableStopedTurnEffect; // 0x94 w0x25 "UnstableStopedTurnEffect"
Scalar updatePositionDiffrence; // 0x98 w0x26 "UpdatePositionDiffrence"
Scalar updateTurnVelocityDiffrence;// 0x9C w0x27 "UpdateTurnVelocityDiffrence"
Scalar updateTurnDegreeDiffrence; // 0xA0 w0x28 "UpdateTurnDegreeDiffrence" (deg; ctor x pi/180)
Scalar timeDelay; // 0xA4 w0x29 "TimeDelay"
Vector3D cameraOffset; // 0xA8 w0x2A-0x2C "CameraOffset"
char shadowJointName[20]; // 0xB4 w0x2D-0x31 "ShadowJointName" (<=19 chars)
};
static_assert(sizeof(Mover::ModelResource) == 0x40, "Mover model record 0x40");
static_assert(sizeof(Mech__ModelResource) == 0xC8, "Mech model record 200 bytes");
static_assert(offsetof(Mech__ModelResource, maxAcceleration) == 0x44, "MaxAcceleration w0x11");
static_assert(offsetof(Mech__ModelResource, relativeMechValue) == 0x70, "RelativeMechValue w0x1C");
static_assert(offsetof(Mech__ModelResource, updatePositionDiffrence) == 0x98, "UpdatePositionDiffrence w0x26");
static_assert(offsetof(Mech__ModelResource, cameraOffset) == 0xA8, "CameraOffset w0x2A");
static_assert(offsetof(Mech__ModelResource, shadowJointName) == 0xB4, "ShadowJointName w0x2D");
//###########################################################################
//######################### Mech::UpdateRecord ##########################
//###########################################################################
//
// The Mech-level replication records (task #1, 2026-07-11). The record TYPE
// is the inherited Simulation::UpdateRecord `recordID` (u16 @+6) == the
// updateModel BIT INDEX that requested it (Simulation::WriteSimulationUpdate,
// SIMULATE.cpp:302-328). Writer @004a0c2c (slot 7, 9-case jump table) /
// reader @004a1232 (slot 6). Field layouts are byte-exact from the writer
// disasm (reference/decomp/mech_writeupdate_004a0c2c.disasm.txt) cross-mapped
// against the reader pseudocode (part_012.c:9576-9692). [T1]
//
// EVERY record's trailing Scalar is the controls-mapper's speedDemand
// (subsystemArray[0]+0x128), stored into mech+0x6b4 `bodyTargetSpeed` on BOTH
// sides -- the authentic replicant-gait feed (it is NOT a sequence number).
//
// type 0 (len 0x78): the full Mover pose record + the speedDemand tail.
struct Mech__PoseUpdateRecord:
public Mover::UpdateRecord // 0x74
{
Scalar speedDemand; // +0x74
};
// type 2 (len 0x14): speedDemand alone (sent when the commanded speed
// drifts from the last-replicated bodyTargetSpeed).
struct Mech__SpeedUpdateRecord:
public Simulation::UpdateRecord // 0x10
{
Scalar speedDemand; // +0x10
};
// type 3 (len 0x20): leg/body state + stability.
struct Mech__StateUpdateRecord:
public Simulation::UpdateRecord
{
int legResetLatch; // +0x10 <- 0x654 ; -> 0x658
int legState; // +0x14 <- legStateAlarm.currentLevel (0x3b0)
int stability; // +0x18 <- stabilityAlarm.currentLevel (0x4d8)
Scalar speedDemand; // +0x1c
};
// type 4 (len 0x2c): orientation + angular-velocity re-sync (pose delta).
struct Mech__ResyncUpdateRecord:
public Simulation::UpdateRecord
{
Scalar eulerX, eulerY, eulerZ; // +0x10 localOrigin.angularPosition as euler
Vector3D angularVelocity; // +0x1c live localVelocity.angularMotion (0x1d0)
Scalar speedDemand; // +0x28
};
// types 5 (knockdown) / 6 (death) / 7 (impact), all len 0x2c: heat +
// throttle + the ground-contact basis. Same payload, different side
// effects on each end.
struct Mech__FallUpdateRecord:
public Simulation::UpdateRecord
{
int heatLevel; // +0x10 <- heatAlarm.currentLevel (0x464)
int throttleState; // +0x14 <- 0x4a4
Vector3D fallDirection; // +0x18 <- 0x4a8 (ctor (0,0,1); name [T4])
Scalar fallScalar; // +0x24 <- 0x4b4 (name [T4])
Scalar speedDemand; // +0x28
};
// type 8 (len 0x18): the airborne/reverse cycle-rate selector flag.
struct Mech__AirborneUpdateRecord:
public Simulation::UpdateRecord
{
int airborne; // +0x10 <- 0x3f4
Scalar speedDemand; // +0x14
};
static_assert(sizeof(Simulation::UpdateRecord) == 0x10, "Simulation record header 0x10");
static_assert(sizeof(Mover::UpdateRecord) == 0x74, "Mover pose record 0x74");
static_assert(sizeof(Mech__PoseUpdateRecord) == 0x78, "type 0 record 0x78");
static_assert(sizeof(Mech__SpeedUpdateRecord) == 0x14, "type 2 record 0x14");
static_assert(sizeof(Mech__StateUpdateRecord) == 0x20, "type 3 record 0x20");
static_assert(sizeof(Mech__ResyncUpdateRecord) == 0x2c, "type 4 record 0x2c");
static_assert(sizeof(Mech__FallUpdateRecord) == 0x2c, "type 5/6/7 record 0x2c");
static_assert(sizeof(Mech__AirborneUpdateRecord) == 0x18, "type 8 record 0x18");
//###########################################################################
//########################## Mech::MakeMessage ##########################
//###########################################################################
//
// Creation message. Carries the three resource-name strings copied into the
// badge/color/insignia slots (this[0x211..0x213]) at +0x7c / +0x90 / +0xa4.
//
class Mech__MakeMessage:
public JointedMover::MakeMessage
{
public:
char resourceNameA[20]; // +0x7c -> this[0x211] (vehicle badge)
char resourceNameB[20]; // +0x90 -> this[0x212] (vehicle color)
char resourceNameC[20]; // +0xa4 -> this[0x213] (vehicle patch / insignia)
//
// Construction message, mirrored on VTV__MakeMessage (RP/VTV.h): the
// entity_ID form carrying the three resource-name strings copied into
// the badge/color/patch slots. Built by BTPlayer::CreatePlayerVehicle.
//
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));
}
};
//###########################################################################
//############################## Mech ###############################
//###########################################################################
//
// The BattleMech entity. vtable @0050cfa8 (16 slots); slot 0 == ~Mech
// @004a452c. Engine-base slots (0x41xxxx / 0x42xxxx) are inherited from
// JointedMover/Mover/Entity and are NOT redefined here. In-module overrides:
// slot 0 (0050cfa8) ~Mech @004a452c [mech.cpp]
// slot 6 (0050cfc0) ReadUpdateRecord @004a122c [mech.cpp]
// slot 7 (0050cfc4) (pose write/simulate)@004a0c2c [mech.cpp gap]
// slot 15 (0050cfe4) (state/print) @004abb40 [mech3/4]
//
class Mech:
public JointedMover
{
friend class Mech__DamageZone; // per-zone damage code reads the roster / segment table
friend struct MechBaseLayoutCheck; // base-region layout locks (mech4.cpp; P3 STEP-6 audit)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations; // @0050bdb4
static SharedData DefaultData; // @0050bde4
enum { MechClassID = 0xBB9 }; // stamped by CreateModelResource
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Subsystem roster -- streamed-class registry
//
// The constructor (@004a1674) walks the model's segment table and
// instantiates one Subsystem per segment, dispatching on the segment's
// streamed ClassID. ClassIDs confirmed against CLASSMAP.md are named;
// the rest are best-effort from the factory address + object size.
//
public:
enum SubsystemClassID {
CockpitClassID = 0xBBD, // ctor @004ae568 (size 0x230)
SensorClassID = 0xBBE, // ctor @004ae8d0 -> this[0x1f7]
CondenserClassID = 0xBC0, // ctor @004af408 (size 0x230)
GeneratorClassID = 0xBC1, // ctor @004b225c (CLASSMAP)
PoweredSubsystemClassID = 0xBC2, // ctor @004b0f74 (CLASSMAP)
MyomersClassID = 0xBC3, // ctor @004b1d18 (CLASSMAP)
GyroClassID = 0xBC4, // ctor @004b3778 -> this[0x14a]
SinkSourceClassID = 0xBC5, // ctor @004b6b0c -> this[0x10e]
ActuatorClassID = 0xBC6, // ctor @004b8fec
WeaponEmitterClassID = 0xBC8, // ctor @004bb120 (+weapon count)
JumpJetClassID = 0xBCB, // ctor @004bd5c4
MechWeaponClassID = 0xBCD, // ctor @004bc3fc (CLASSMAP)
MissileWeaponClassID = 0xBCE, // ctor @004bdcb4 (+weapon count)
BallisticWeaponClassID = 0xBD0, // ctor @004bcff0 (+weapon count)
MechControlsMapperID = 0xBD3, // ctor @0049bca4 -> this[0x10d]
GaussWeaponClassID = 0xBD4, // ctor @004bb888 (+weapon count)
MechTechClassID = 0xBD6, // ctor @004b7f94 -> this[0x16d]
LegSubsystemClassID = 0xBD8, // ctor @004b84dc
HeatableClassID = 0xBDC, // ctor @004ad228 (heat.cpp family; exact class TBD)
DisplayClassID = 0xBDE // ctor @004b8718
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
Logical
TestInstance() const; // @004a4c7c
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// App / cockpit interface (called by the BT_L4 layer: btl4app/mppr/pb).
// Declarations only -- definitions live in mech.cpp (factory pass / bring-up).
//
public:
enum ResetMode { MissionReviewReset = 0 }; // reset-mode selector (Reset arg, value 0)
void SetMappingSubsystem(Subsystem *mapper); // btl4app.cpp:567
Logical GetMissionReviewMode(); // reads this+0x414 (btl4mppr.cpp:366)
void SetTargetRange(Scalar range); // writes this+0x404 (btl4mppr.cpp:407)
void Reset(const Origin &origin, int mode); // btl4pb.cpp:555 (FUN_0049fb74)
//~~~ Skeleton joint access (shared by Torso, Gyroscope) ~~~~~~~~~~~~~~~~
// Resolve a named skeleton node to its live Joint* via the PUBLIC segment
// path (GetSegment -> segment joint index -> JointSubsystem::GetJoint);
// the protected EntitySegment::jointPointer is never read. The Torso ctor
// (@004b6b0c) and Gyroscope ctor (@004b3778) inline this exact sequence
// (binary FUN_00424b60); hoisted here as the single reuse point.
Joint* ResolveJoint(const char *joint_name);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef Mech__ModelResource ModelResource;
typedef Mech__MakeMessage MakeMessage;
typedef Mech__DamageZone DamageZone;
// (No UpdateRecord typedef: the Mech records are per-type structs --
// Mech__PoseUpdateRecord etc. above -- and a class-local UpdateRecord
// typedef is the silent-non-override trap: an override declared with
// the shadowing typedef does NOT override the engine virtual. See
// mechweap.hpp:174 / [[reconstruction-gotchas]].)
static Mech*
Make(MakeMessage *creation_message); // @004a2d48
Mech(
MakeMessage *creation_message,
SharedData &shared_data = DefaultData
); // @004a1674
~Mech(); // @004a452c (vtable slot 0)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Resource authoring (offline tools)
//
public:
static ResourceDescription::ResourceID
CreateModelResource( // @004a2da8
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories,
ModelResource *model = 0
);
static Logical
CreateModelResourceStub( // @004a2d78 (best-effort)
ModelResource *model
); // stamps model->classID = MechClassID
typedef Logical (*FindNameFunction)(
const char *control_name, ControlsMapping *mapping);
static ResourceDescription::ResourceID
CreateControlMappingStream( // @004a3794
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
);
static ResourceDescription::ResourceID
CreateDamageZoneStream( // @004a474c
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Simulation / replication (task #1: the Mech-level update records)
//
// The record type == the updateModel BIT INDEX (Simulation::
// WriteSimulationUpdate walks the mask, SIMULATE.cpp:302-328). Bits 0/1
// are the engine's (Mover pose / Entity damage-zone); 2..8 are Mech's.
// Request one with ForceUpdate(1<<bit); the emitter consumes-and-zeroes
// the mask, so each request produces exactly one record. [T1 senders
// mapped from the binary: 27 call sites, perf loop + events]
//
public:
enum {
MechSpeedUpdateModelBit = 2, // commanded-speed change
MechStateUpdateModelBit = 3, // leg/body state + stability
MechResyncUpdateModelBit = 4, // orientation/angular-vel delta
MechKnockdownUpdateModelBit= 5, // knockdown (SetBodyAnimation 0x20)
MechDeathUpdateModelBit = 6, // death
MechImpactUpdateModelBit = 7, // heat/impact state changed
MechAirborneUpdateModelBit = 8 // airborne/reverse cycle selector
};
//
// @004a4c54 -- Mech::ForceUpdate: OR record-request bits into the
// engine's Simulation::updateModel. A disabled/destroyed mech
// (simulationState 2 or 9) is masked to pose + damage-zone + high
// bits (mask &= 0xfe03): the event records 2..8 stop flowing from a
// wreck. SHADOWS the non-virtual Simulation::ForceUpdate. [T1]
// (Two earlier mislabels of the same binary fn are retired:
// "SetInstanceFlags(Word)" -- +0x18 is updateModel, NOT the entity
// instance flags -- and "RequestActionFlags(Word)" writing a
// side-member the emitter never read.)
//
void
ForceUpdate(Word mask = Simulation::DefaultUpdateModelFlag)
{
if (IsDisabled()) // FUN_0049fb54 (simState 2||9)
{
mask &= 0xfe03;
}
updateModel |= mask;
}
protected:
void
ReadUpdateRecord( // @004a1232 (vtable slot 6)
Simulation::UpdateRecord *message
);
void
WriteUpdateRecord( // @004a0c2c (vtable slot 7)
Simulation::UpdateRecord *message,
int record_type
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Subsystem roster (host pattern -- cf. VTV / CLASSMAP shared base)
//
// this[0x47] segmentCount / this[0x48] segment[] (JointedMover base)
// this[0x49] subsystemCount / this[0x4a] subsystem[] (Mech-owned table)
// The capability sub-rosters below are ChainOf<> views built in the ctor
// by IsDerivedFrom() tests against the streamed subsystem classes.
//
protected:
// NOTE: subsystemCount (this[0x49]) and the subsystem table (this[0x4a])
// are the INHERITED Entity base members `subsystemCount` / `subsystemArray`
// (Entity reads them in GetSimulation/GetSubsystem). They are NOT
// re-declared here -- doing so shadowed the base fields, so the engine's
// control-mapping / per-frame code never saw the streamed roster. The
// ctor streams into the inherited subsystemArray/subsystemCount directly.
// NOTE: damageZoneCount (this[0x47]) and the zone table (this[0x48]) are the
// INHERITED Entity base members `damageZoneCount` / `damageZones` -- the engine
// Entity ctor (ENTITY.cpp:961-987 == binary FUN_0041ff38) reads the count from
// the type-0x14 resource and allocates damageZones[count] (entries left
// uninitialised); the Mech ctor populates THAT inherited array. They are NOT
// re-declared here -- doing so shadowed the base fields (count read 0xCDCDCDCD,
// the populate loop never ran, and the engine damage router read the empty
// base array). Same lesson as subsystemCount/subsystemArray above. Use Zone(i)
// for typed (Mech__DamageZone*) access to the engine-stored DamageZone* entries.
ReconChain<Subsystem*> controllableSubsystems; // @0x65c this[0x197] (FUN_00427768)
ReconChain<Subsystem*> watchedSubsystems; // @0x6bc this[0x1af] (FUN_00427768)
ReconChain<Subsystem*> heatableSubsystems; // @0x7ac this[0x1eb] (class 0x51155c)
ReconChain<Subsystem*> weaponRoster; // @0x7bc this[0x1ef] (class 0x511830 =
// MechWeapon::ClassDerivations -- the
// reticle AddWeapon loop @part_014.c:5386
// asserts weapon attrs on every member.
// Was mislabeled "poweredSubsystems";
// PoweredSubsystem is 0x50f4bc.)
ReconChain<Subsystem*> damageableSubsystems; // @0x7cc this[499] (class 0x50e4fc)
int weaponCount; // @0x448 this[0x112]
Subsystem *sensorSubsystem; // @0x7dc this[0x1f7] (0xBBE)
Subsystem *gyroSubsystem; // @0x528 this[0x14a] (0xBC4)
public:
// Cache accessors for the controls mapper (@004afd10 reads mech+0x438/+0x5b4).
Subsystem *GetTorsoSubsystem() { return sinkSourceSubsystem; }
// Gyro accessor (task #56): the firing-recoil kick (projweap.cpp,
// binary @4bc194) reads mech+0x528.
Subsystem *GetGyroSubsystem() { return gyroSubsystem; }
// The mapper's ONE true home (task #7): roster slot 0 -- the binary's
// SetMappingSubsystem @0049fe40 installs there, and every binary
// mapper consumer reads **(mech+0x128). Null-safe: the binary never
// ran these paths mapper-less; our reachability is wider.
MechControlsMapper *MappingMapper()
{
return (GetSubsystemCount() > 0)
? (MechControlsMapper *)GetSubsystem(0) : 0;
}
class SubsystemMessageManager *GetMessageManager()
{
return messageManager;
}
Subsystem *GetHudSubsystem() { return hudSubsystem; }
// Reachable horizontal firing half-arc (radians) the mech's torso can bring
// its guns to bear -- the wider torso twist limit, or 0 for a fixed torso.
// Defined in mechmppr.cpp (which knows the full Torso type). Used by the
// weapon fire path (mech4.cpp) to derive the real, per-mech firing arc.
Scalar GetHorizontalFiringReach();
protected:
Subsystem *sinkSourceSubsystem; // @0x438 this[0x10e] (0xBC5 -> real Torso)
// The @0x5b4 cache holds the HUD, NOT the MechTech: raw factory (part_012.c:
// 10155-10164) case 0xbd6 (alloc 0x2a4, HUD ctor @004b7f94) writes param_1[0x16d];
// case 0xbdc (MechTech, alloc 0x104) stores NO cache. Also required by the
// controls mapper (@004afd10 writes cockpit+0x28c -- past MechTech's alloc,
// inside the HUD's). Was misnamed mechTechSubsystem.
Subsystem *hudSubsystem; // @0x5b4 this[0x16d] (0xBD6 -> real HUD)
// THE UNTANGLE (task #7): binary [0x10d]/mech+0x434 caches the
// SubsystemMessageManager (0xBD3, the per-mech damage/explosion
// consolidation hub, ctor @0049bca4) -- NOT a controls mapper. The
// binary-wide census found exactly ONE reader (@0x4b984b in
// MechWeapon::SendDamageMessage @004b9728) and it is pure
// message-manager semantics. The old `controlsMapper` name/type was
// the misread that made the whole drive lean on this cache. The
// REAL mapper's one true home is roster slot 0 (MappingMapper()).
class SubsystemMessageManager *messageManager; // @0x434 this[0x10d] (0xBD3)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Pose / kinematics (subset touched by the recovered slice)
//
protected:
Matrix34 orientation; // @0xd0 this[0x34] (FUN_0040ab44)
Quaternion bodyRotation; // @0x100 this[0x40]
// (task #1 corrections: the old `BTVal torsoRotation @0x130 [0x4b]`
// and `Vector3D worldPosition @0x138 [0x4e]` members were MISREADS of
// the ENGINE base fields -- word 0x4b = byte 0x12c = Entity::
// updateOrigin, and 0x138 = updateOrigin.angularPosition (Quaternion).
// The update-record reader/writer now uses the inherited engine
// members directly. The 24 bytes stay as PADDING so every following
// member keeps its layout-locked OUR-offset (mech4 static_asserts).)
int retiredUpdateRecordPad[6];
BTVal turretBase; // @0x1c4 this[0x71] (proxy: .SetIdentity)
Quaternion headPitch; // @0x1cc this[0x73] (look)
Quaternion torsoTwist; // @0x1dc this[0x77]
Vector3D legAngle; // @0x1f4 this[0x7d]
Vector3D hipAngle; // @0x200 this[0x80]
BTVal torsoAimCurrent; // @0x298 this[0xa6] (proxy: .Mul/.SetIdentity)
BTVal torsoAimTarget; // @0x2c8 this[0xb2] (proxy: .SetIdentity)
EulerAngles netOrientation; // @0x2d4 this[0xb5] (from update record)
// SHADOW JOINT (task #20): the model record's ShadowJointName (record+0xB4,
// 'jointshadow' on the BLH) resolved to the live skeleton Joint that tilts
// the flat *_tshd shadow proxy to the terrain angle. Binary: part_012.c:
// 10285 (record+0xb4 -> CString -> GetSegment -> GetJointIndex -> GetJoint
// -> this[0x10b] @0x42c). Accessed BY NAME only; declared after the locked
// fields so it does not shift them.
public:
Joint *shadowJointNode; // binary @0x42c this[0x10b]
void UpdateShadowJoint(const Vector3D &ground_normal);
// Update-record state (task #1; by-name access, declared after the locked
// fields so nothing shifts):
int poseSyncLatch; // binary @0x77c -- armed by every type-0 record
// on both sides; consumed by the dead-reckoner
// (FUN_004ab1c8: replicant motionEventVector =
// updateOrigin - localOrigin; master
// projectedOrigin re-base); cleared on the
// simState-2 edge
Vector3D fallDirection; // binary @0x4a8 -- knockdown/death/impact record
// payload; ctor (0,0,1) [T4 name]
Scalar fallScalar; // binary @0x4b4 -- same record family; ctor 0 [T4 name]
int heatLevelSnapshot; // binary @0x780 -- heatAlarm level at frame
// entry; the type-7 impact-record deadband
// The AUTHENTIC update-record deadbands (task #3): streamed by the ctor
// from the model record (disasm @0x4a26d7-0x4a26ef: 0x770 <- rec+0xa0 x
// pi/180 [const @0x4a2d44 = 0.01745329], 0x76c <- rec+0x9c raw, 0x768 <-
// rec+0x98 raw = UpdateTurnDegreeDiffrence / UpdateTurnVelocityDiffrence /
// UpdatePositionDiffrence). Consumed by the perf-loop senders. [T1]
Scalar updatePositionDeadband; // binary @0x768 -- type-0 pose trigger
Scalar updateTurnVelocityDeadband; // binary @0x76c -- type-4 yaw-rate trigger
Scalar updateTurnAngleDeadband; // binary @0x770 -- type-4 orientation trigger (rad)
// AUTHENTIC GROUND MODEL ctor products (task #15, ground-model-decode;
// binary part_012.c:9938-9940 + 9974-9975). By-name access only; declared
// after the layout-locked fields so nothing shifts.
Scalar standingTemplateMaxY; // binary @0x518 collisionTemplate->maxY at ctor
Scalar duckedTemplateMaxY; // binary @0x51c 0.6 x standing (duck preset)
Scalar templateBottomLift; // binary @0x4b8 0.05 x (volume maxX-minX)
// AUTHENTIC TARGETING (task #36): the engine Reticle struct (MUNGA/RETICLE.h
// [T0]) -- the mech's "TargetReticle" attribute (id 0x1d) binds to it, per the
// RP VTV analog (VTV.h: `Reticle targetReticle` + TargetReticleAttributeID).
// The pilot slews reticlePosition; the pick ray through it fills
// rayIntersection / targetEntity / targetDamageZone; the mech's engine-Entity
// target slots (+0x37c/+0x388/+0x38c) are fed from this each frame (the
// binary's writer is in an un-exported gap -- semantics per RETICLE.h).
// By-name access only; declared after the layout-locked fields.
Reticle targetReticle;
// Ray pick against this mech (the Reticle "pick point intersection"):
// world ray -> local frame -> slab test vs the collision template's
// ExtentBox (BoundingBox::HitBy clips the Line at the entry distance)
// -> world hit point. Defined in mech.cpp.
Logical PickRayHit(const Point3D &start, const Vector3D &dir,
Scalar max_range, Point3D *hit_world);
// Per-weapon beam render walk (task #33; extracted task #51 so it runs for
// EVERY mech -- player, dummy masters, and MP replicants whose emitters
// carry replicated discharge state). Defined in mech4.cpp.
void DrawWeaponBeams(Scalar dt);
protected:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Alarms / indicators
//
protected:
AlarmIndicator masterAlarm; // @0x39c this[0xe7] (33 levels)
AlarmIndicator heatAlarm; // @0x450 this[0x114] (3 levels)
AlarmIndicator stabilityAlarm; // @0x4c4 this[0x131] (2 levels)
AlarmIndicator statusAlarm; // @0x714 this[0x1c5] (33 levels)
FilteredScalar telemetryFilter[5]; // @0x7e0 this[0x1f8..0x204] (15-sample)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Misc recovered state (names best-effort)
//
protected:
BTVal mechName; // @0x360 this[0xd8] (proxy: .Copy)
Scalar maxSpeed; // @0x400 this[0x100] = FLT_MAX
int stateFlags; // @0x410 this[0x104]
int throttleState; // @0x4a4 this[0x129] = 2 (initial)
int heatLevel; // @0x518 this[0x146]
int heatCapacity; // @0x51c this[0x147] = 0.6 * heatLevel
Time creationTime; // @0x778 this[0x1de]
// Cylinder hit-location table (STEP 6) -- DamageLookupTable* cached by the
// ctor from the type-0x1d resource (FUN_0049ea48 = the table ctor), read by
// Mech::TakeDamageMessageHandler to resolve an unaimed (-1) hit's zone from
// its impact point. MUST be a real named member: the old home Wword(0x111)
// is the recon ABSORBER bank (BTVal stores nothing, reads 0) -> the cached
// table silently vanished and every unaimed hit no-op'd. (Was mislabeled
// "ammoExpended"; binary slot this[0x111] is this pointer.)
int damageLookupTable; // @0x444 this[0x111] (FUN_0049ea48)
int deathHandler; // @0x850 this[0x214] (FUN_0042a984)
// Wreck-smoke cadence (port addition, [T3]): re-fires the death/rubble
// smoke plume (psfx 1, DDTHSMK) every 10s (its authored emission window)
// while the mech is a destroyed wreck, so a dead mech keeps smoking.
Scalar wreckSmokeTimer;
// The cockpit eye-slew angles (the "EyepointRotation" attribute the
// binary's DPLEyeRenderable composes into the view every frame). Was
// bound to the shared junk attrPad -> the cockpit camera got rotated by
// garbage (the canted-horizon / black-screen views). Zero until the
// eye-slew system (HUD freeAimSlew / gyro eye chain) writes it.
EulerAngles eyepointRotation;
// Three ref-counted creation-name objects (badge/color/insignia).
void *resourceNameA; // @0x844 this[0x211]
void *resourceNameB; // @0x848 this[0x212]
void *resourceNameC; // @0x84c this[0x213]
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Reconstruction additions -- members & methods whose bodies live in the
// mech2 / mech3 / mech4 slices (declared here so the whole class is visible).
// Offsets cite the mech2/mech3 offset maps; types inferred from usage.
//
public:
static Receiver::MessageHandlerSet MessageHandlers; // SharedData ctor operand
// Mech's OWN handler table (overrides Entity's TakeDamage with the
// cylinder hit-location resolver); chained to Entity::GetMessageHandlers().
static const Receiver::HandlerEntry MessageHandlerEntries[];
enum { StateCount = 0x21 };
//
// Attribute Support -- the named attributes the cockpit gauges bind to.
// The config (content/GAUGE/L4GAUGE.CFG) binds the bare names LinearSpeed /
// MaxRunSpeed / DuckState / RadarRange / RadarLinearPosition /
// RadarAngularPosition, resolved by Simulation::GetAttributePointer ->
// activeAttributeIndex->Find(name). The reconstruction shipped an EMPTY,
// unchained Mech::AttributeIndex, so EVERY bare Mech gauge (and even the
// inherited attributes) resolved to NullAttribute.
//
// The full binary id set (0x15..0x38, table VA 0x50be84) is declared so the
// published table can be extended without renumbering. The chain starts at
// JointedMover::NextAttributeID (0x15); ids MUST stay contiguous, because the
// built index is a dense array sized to max(id) and AttributeIndexSet::Find
// strcmps EVERY slot -- an unfilled slot holds a garbage entryName -> AV.
//
enum {
MaxAccelerationAttributeID = JointedMover::NextAttributeID, // 0x15
CollisionStateAttributeID, // 0x16
CollisionNormalAttributeID, // 0x17
CollisionSpeedAttributeID, // 0x18
CollisionMaterialTypeAttributeID, // 0x19
CurrentSpeedAttributeID, // 0x1a
MaxRunSpeedAttributeID, // 0x1b
EyepointRotationAttributeID, // 0x1c
TargetReticleAttributeID, // 0x1d
FootStepAttributeID, // 0x1e
AnimationStateAttributeID, // 0x1f
ReplicantAnimationStateAttributeID, // 0x20
LinearSpeedAttributeID, // 0x21
ClimbRateAttributeID, // 0x22
AccelerationLastFrameAttributeID, // 0x23
AngularSpeedAttributeID, // 0x24
ArmorDamageLevelAttributeID, // 0x25
SubsystemDamageLevelAttributeID, // 0x26
MyomerDamageLevelAttributeID, // 0x27
TestButton1AttributeID, // 0x28
TestButton2AttributeID, // 0x29
TestButton3AttributeID, // 0x2a
TestButton4AttributeID, // 0x2b
TestButton5AttributeID, // 0x2c
TestButton6AttributeID, // 0x2d
ReduceButtonAttributeID, // 0x2e
RadarRangeAttributeID, // 0x2f
RadarLinearPositionAttributeID, // 0x30
RadarAngularPositionAttributeID, // 0x31
RearFiringAttributeID, // 0x32
RequestDuckAnimationAttributeID, // 0x33
UnstablePercentageAttributeID, // 0x34
SuperStopAttributeID, // 0x35
IncomingLockAttributeID, // 0x36
DuckStateAttributeID, // 0x37
DistanceToMissileAttributeID, // 0x38
NextAttributeID // 0x39
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet& GetAttributeIndex();
// Cockpit-gauge attribute backing members (APPENDED to Mech's own region --
// never at a locked base/gait offset). attrPad is a shared read-only 0 for
// the dense-prefix ids the BLH cockpit does not bind; linearSpeed is the live
// forward ground speed the speed readout / arc / radar consume (binary @0x81c).
Scalar attrPad;
Scalar linearSpeed;
// Radar/map gauge attributes (binary @0x404/0x408/0x40c/0x3f8). The map
// widget reads position/angle as POINTERS into the mech's live origin, so
// radarLinearPosition/radarAngularPosition point at localOrigin.{linear,
// angular}Position (set in the ctor); radarRange is the radar scale.
Scalar radarRange; // 0x2f RadarRange (scale/max)
Point3D *radarLinearPosition; // 0x30 RadarLinearPosition
Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition
int duckState; // 0x37 DuckState (crouch posture)
// --- raw / engine-shim members touched by the ctor slice -------------
void *vtable; // installed Mech vtable ptr
Word instanceFlags; // entity instance flags (this+0x18 region)
ReconFiltered mechNameFilter; // @0xdb name filter
ReconSlot voltageBus; // resolve-able power-bus slot
void *controlSource; // @0x128 controls subsystem handle
// --- locomotion / gait animation state (mech2/mech3 maps) ------------
// movementMode UNIFIED with the engine (task #1, 2026-07-11): binary
// mech+0x40 is Simulation::simulationState.currentState (StateIndicator
// @0x2c: +0x10 oldState, +0x14 currentState) -- the "gait/death
// selector" and the replicated simulation state are ONE field. Every
// update record carries it (header +0xC); Simulation::ReadUpdateRecord
// applies it on the replicant, so death (9) / limbo (2) / airborne
// (3/4) now replicate for free. The old parallel `int movementMode`
// member is GONE -- reads/writes go through the engine StateIndicator
// (SetState collapses old=current then fires only on change, exactly
// the binary's FUN_0041bbd8). [T1: writers in the binary are
// SetLevel(this+0x2c, n) calls -- FUN_0049f674 sets 2, death paths 9]
// (The old `Word actionRequestFlags @0x18` member is also gone: +0x18
// is Simulation::updateModel; see ForceUpdate above.)
public:
int MovementMode() { return (int)GetSimulationState(); }
void SetMovementMode(int m) { SetSimulationState((unsigned)m); }
// (the members below were and stay PUBLIC -- the recon TUs
// (btplayer/dmgtable/mechmppr/emitter) reach them directly)
int movementFlags;
int motionEventArmed; // @0x5a4
int motionEventPending;
int airborneSelect;
Scalar forwardCycleRate; // @0x344
Scalar legCycleSpeed; // @0x348
Scalar reverseStrideLength; // @0x34c
Scalar gimpStrideLength; // @0x350
AlarmIndicator legStateAlarm; // @0x39c
int legAnimationState; // @0x3b0
Scalar gimpSpeedMax; // @0x52c
Scalar standSpeed; // @0x530
Scalar walkStrideLength; // @0x534
Scalar reverseSpeedMax; // @0x538
Scalar jumpRunSpeedMax; // @0x53c
Scalar jumpWalkSpeedMax; // @0x540
Scalar jumpRunStrideLength; // @0x544
Scalar jumpWalkStrideLength; // @0x548
Scalar groundCycleRate; // @0x5b8
Scalar airborneCycleRate; // @0x5bc
// Forward-throttle scale read by the controls mapper (@004afd10:
// speedDemand = topSpeed@0x34c * throttle * THIS). No writer found in the
// decomp windows (likely model/status-driven); ctor inits 1.0 (neutral).
Scalar forwardThrottleScale; // @0x5c0
Scalar gyroRumbleTimer; // @0x5c4 binary: FLOAT rumble-period countdown
// (engaged-gait rumble @4aa2eb-4aa35f, task #56);
// reset at clip load. (Was mis-typed int clipLoadGuard.)
Scalar globalTimeScale; // @0x5a8
Scalar idleStrideScale; // @0x5ac
Scalar gimpCycleRate; // @0x5b0
int jumpCapable; // @0x580
int hasReverseGimpSet; // @0x584
int hasCrashSet; // @0x588
int deathAnimationLatched; // @0x650
int legResetLatch; // @0x654
int bodyResetLatch; // @0x658
ReconSeq legAnimation; // @0x65c channel-A controller
ReconSeq bodyAnimation; // @0x6bc channel-B controller
AlarmIndicator bodyStateAlarm; // @0x714
int bodyAnimationState; // @0x728
Scalar bodyTargetSpeed; // @0x6b4
Scalar bodyCycleSpeed; // @0x6b8
Scalar reverseSpeedMax2; // @0x7a0
Scalar arrivalTime;
Scalar simTime;
Scalar spinRate;
ResourceDescription::ResourceID animationClips[40]; // @0x5cc gait clips
// namedClip is NOT a separate array in the binary: namedClip@0x5e0 == &animationClips[5]
// (0x5cc + 5*4). LoadLocomotionClips writes namedClip[i]; SetBodyAnimation/MeasureClipStride
// read animationClips[i+5] -- SAME storage. Aliased via this pointer (set in the ctor) so the
// array syntax in mech3.cpp keeps working AND the writes land where the state machine reads.
ResourceDescription::ResourceID *namedClip; // -> &animationClips[5]
ResourceDescription::ResourceID crashClipA, crashClipB, crashClipC; // @0x5d8/0x5d4/0x5dc
ResourceDescription::ResourceID gimpBaseClip; // @0x64c
CString motionEventName; // @0x598 cleared to "" on fall/reset
// --- gait helpers (mech2) -------------------------------------------
void SetLegAnimation(int state); // @004a7fc4
void SetBodyAnimation(int state); // @004a800c
// (RequestActionFlags(Word) @004a4c54 retired: it was Mech::ForceUpdate
// -- the "action request bits" ARE the updateModel record requests.
// The gait/knockdown call sites now call ForceUpdate directly and
// their masks match the binary's senders byte-for-byte.)
// P3 STEP-7 body finished-callback (the real PTR_LAB_0050d6fc == FUN_004a6d8c):
// called by SequenceController::Advance at end-of-clip -- picks the next gait state
// from commanded speed vs the loaded caps, re-arms via SetBodyAnimation, recursively
// advances the carryover. Static (the cb is a plain fn ptr, owner Mech is arg1).
// BodyTransition = the shared tail (SetBodyAnimation(next) + bodyAnimation.Advance).
// LoopBodyClip = bring-up loop for the inline cutover path.
static Scalar BodyClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints); // FUN_004a6d8c
static Scalar LoopBodyClip(Mech *m, unsigned a2, Scalar carryover, int move_joints);
Scalar BodyTransition(int next_state, Scalar adv_time, int move_joints);
// LEG-channel finished-callback (the real PTR_LAB_0050d6f0 == FUN_004a6928):
// same transition machine keyed on legAnimationState@0x3b0, but compares the
// LIVE mapper speedDemand (subsystemArray[0]+0x128) and slews legCycleSpeed.
static Scalar LegClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints); // FUN_004a6928
Scalar LegTransition(int next_state, Scalar adv_time, int move_joints);
Scalar AdvanceLegAnimation(Scalar time_slice); // @004a5028
Scalar AdvanceBodyAnimation(Scalar time_slice, int loop);
Scalar AdvanceBodyAnimationAirborne(Scalar time_slice, int loop);
Scalar AdvanceLegAnimationAirborne(Scalar time_slice);
Logical IsDisabled(); // FUN_0049fb54
// --- death sequence (the un-exported master-perf death branch, rebuilt
// from its exported consumers + the RP VTV::DeathShutdown analog) ---
Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9
void UpdateDeathState(Scalar dt); // death freeze + subsystem DeathShutdown + wreck smoke
// --- clip loaders (mech3) -------------------------------------------
ResourceDescription::ResourceID *
ResolveAnimationClip(const char *prefix, const char *suffix); // @004a7f50
// Body-animation looping callback (bring-up gait). Non-virtual so adding
// it does NOT change object layout; passed to AnimationInstance::SetAnimation
// (reinterpret_cast to JointedMover::AnimationCallback) so the clip re-arms at
// frame 0 when it ends instead of dereferencing a NULL finishedCallback.
Scalar OnBodyAnimFinished(ResourceDescription::ResourceID animation_number,
Scalar carryover, Logical animate_joints);
void MeasureClipStride(int slot, Scalar *total, Scalar *lastKey); // @004a8054
void LoadLocomotionClips(Mech__ModelResource *model); // @004a80d4
void LoadLocomotionClipsExt(Mech__ModelResource *model); // @004a86c8
static int
CreateSubsystemStream(
int subsystem_index, const char *type_name, int pass,
NotationFile *model_file, const char *model_name,
const char *subsystem_name, SubsystemResourceBlob *out_blob,
NotationFile *subsystem_file,
const ResourceDirectories *directories);
static ResourceDescription::ResourceID
CreateSubsystemsStream(
NotationFile *model_file, const char *model_name,
NotationFile *model_notation,
const ResourceDirectories *directories);
static SharedData *
SubsystemDefaultData(const char *type_name);
// --- simulation / damage (mech4) ------------------------------------
void DeadReckonPose(Scalar fraction);
Logical IntegrateMotion(Scalar time_slice, int loop);
void Simulate(Scalar time_slice);
// --- BRING-UP locomotion (Tier 2: drivable player mech) -------------
// Overrides the engine per-frame Simulation::PerformAndWatch (virtual;
// the simulation director calls it every frame on every entity). The
// shipped game's per-frame tick (Simulate via activePerformance) is the
// uncaptured/reconstructed-but-unsafe chain (raw this+0xNN offset bugs,
// stubbed subsystems), so for the drivable bring-up we override
// PerformAndWatch directly and integrate the player's localOrigin /
// localToWorld from throttle+turn input. localToWorld is the matrix the
// render tree + chase camera (btl4vid.cpp) are bound to, so the mech
// walks and the camera follows. See mech4.cpp. MUST match the base
// signature exactly or it hides instead of overriding.
void PerformAndWatch(const Time& till, MemoryStream *update_stream);
// THE REAL per-contact collision responder (task #15, ground-model-decode):
// binary FUN_004abb40, Mech vtable slot +0x3c -- the override of the engine
// protected virtual Mover::ProcessCollision (MOVER.h:359-365, signature
// exact). The earlier draft misread this function as a weapon sweep
// ("ResolveWeaponImpact") -- it is the mech-vs-world collision policy:
// BoxedSolid resolver -> StaticBounce -> owner classification (Mover /
// CulturalIcon crush sentinel 0.00123f). Gated: falls through to the
// engine base when GroundReal() is off.
virtual void ProcessCollision(Scalar time_slice, BoxedSolidCollision &collision,
const Point3D &old_position, Damage *damage);
// The authentic per-frame ground block (binary FUN_004a9b5c @4aa630-4aab5f):
// MoveCollisionVolume -> ground probe/snap -> collisions -> response policy.
// old_position = the START-OF-FRAME position (the blocking-hit restore target);
// must NOT alias localOrigin.linearPosition.
void AuthenticGroundAndCollide(Scalar dt, const Point3D &old_position);
void FeedHeatCapacityGauge();
void FeedHeatLevelGauge();
static Logical
LookupDamageState(const char *keyword, int *out);
void RaiseStatusAlarm(int level); // subsystem -> mech status alarm
// --- damage-table support (dmgtable) --------------------------------
Point3D WorldToLocal(const Point3D &world); // FUN_00408bf8 (owner+0xd0)
void *TorsoOrientationSource(); // *(this+0x438)
// Height reference the cylinder table normalises impact height against --
// the binary's *(mech+0x2ec)+0xc, a stance height set from mech+0x518
// (standing) / +0x51c (ducked). We return the standing value
// (collisionTemplate->maxY captured at ctor).
Scalar CylinderReferenceHeight();
// Live torso twist (yaw) in radians -- torso+0x1d8, via the roster torso
// (sinkSourceSubsystem @0x438). 0 for a fixed-torso mech.
Scalar TorsoHeading();
// Mech override of Entity::TakeDamageMessageHandler: resolve an unaimed
// (invalidDamageZone) hit's zone via the cylinder table, then base-route.
void TakeDamageMessageHandler(TakeDamageMessage *message);
// --- damage-routing support (mechdmg / mech4) -----------------------
// Typed access to the inherited Entity::damageZones[] (engine stores DamageZone*;
// our entries are Mech__DamageZone, populated by the Mech ctor). Defined in
// mech.cpp where Mech__DamageZone is a complete type.
Mech__DamageZone *Zone(int i) const;
// First VITAL damage zone index (0 if none) -- for concentrated fire that kills.
// Defined in mech4.cpp (Mech__DamageZone::vitalDamageZone is protected; Mech has access).
int FirstVitalZone() const;
int flags; // entity flags word (this+0x28 region)
int stance; // posture state
int ammoState; // @0x44c 0 none / 1 leaking / 2 dry
AlarmIndicator graphicAlarm; // body graphic-state alarm
EntityID lastInflictingID; // @0x43c last attacker (read by DamageZone routing)
Logical IsAirborne();
// --- gait world-step accumulators (P3 STEP-6 base-region reconciliation) ---
// RELOCATED out of the engine base: IntegrateMotion used raw 1995 binary
// offsets that, in the 2007 engine layout, stomp live engine fields
// (0x260->projectedOrigin, 0x26c->previousOrigin, 0x298->projectedVelocity,
// 0x100->localOrigin, 0x12c->updateOrigin). Declared here in the Mech's OWN
// region (appended -- shifts no locked offset) and accessed BY NAME. See
// btbuild/P3_LOCOMOTION.md "BASE-REGION RECONCILIATION".
Quaternion motionDelta; // was raw @0x260 (orientation delta; FromQuat src)
Quaternion worldPose; // was raw @0x26c (integrated world pose)
Quaternion worldPoseBase; // was raw @0x138 (dead-reckon base pose; DeadReckonPose)
Quaternion angularAccum; // was raw @0x298 (per-frame angular accumulator)
Vector3D motionSourceA; // was raw @0x100 (motion-event source A)
Vector3D motionSourceB; // was raw @0x12c (motion-event source B)
Vector3D motionEventVector; // was raw @0x598 (motion-event delta = B - A)
Quaternion aimRate; // was raw @0x1dc (aim/torso angular-rate telemetry,
// cleared each frame; stomped localAcceleration)
//
// --- Everything past here is owned by mech2.cpp .. mech4.cpp ---
// [mech2] MoveAndCollide / Simulate / WriteUpdateRecord / damage routing
// [mech3] PrintState / status reporting / cockpit display feed
// [mech4] AI / pathing / scoring hooks
// The remaining members up to sizeof==0x854 are declared by those slices.
//
};
#endif