//===========================================================================// // 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 // JointedMover -- engine base (MUNGA) #endif #if !defined(CONTROLS_HPP) # include #endif #if !defined(DAMAGE_HPP) # include #endif #if !defined(ALARM_HPP) # include // GaugeAlarm #endif #include // PlatformTool #include // AffineMatrix #include // AverageOf #include // Scalar #include // 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 ::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< 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 controllableSubsystems; // @0x65c this[0x197] (FUN_00427768) ReconChain watchedSubsystems; // @0x6bc this[0x1af] (FUN_00427768) ReconChain heatableSubsystems; // @0x7ac this[0x1eb] (class 0x51155c) ReconChain 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 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) // COLLISION-DAMAGE ECONOMY GUARD (the MP ram one-shot, 2026-07-12): the // mech's TRUE motion at frame entry, snapshotted just before the frame's // ProcessCollisionList walk. Each StaticBounce in that walk MUTATES // worldLinearVelocity (the += delta_v reflection); with several solids in // one frame's list (2007 WinTesla puts TERRAIN in the collision tree; the // 1995 ground was a heightfield probe, never a list entry) the reflections // COMPOUND x(1+e) per contact, and a mech-vs-mech contact later in the // list priced its dispatched damage off a velocity amplified 4x-40x // (62-pt bump economy -> 1074/112375-pt one-shots; mp_a.log:32651). // Mech::ProcessCollision restores worldLinearVelocity from this snapshot // per contact, so contact damage is always priced at the mech's REAL // approach speed -- which is all the binary's StaticBounce ever saw. // By-name access only; declared after the layout-locked fields. Vector3D frameEntryWorldVelocity; // RAM CONTACT EDGE (port analog of the binary's bounce-separation): in the // 1995 mover, StaticBounce REVERSED worldLinearVelocity, so the frame after // a bump read as separating and the damage gate stopped repeat dispatch. // Our gait re-derives velocity every frame (still closing while the stick // is held / while respawn-overlapped), so sustained contact re-priced a // fresh full-speed ram EVERY frame (~60 dmg x 60fps = melt; the respawn // explode-loop). One bump = one damage event; re-arms after the contact // breaks for ramContactLinger seconds. Entity *ramLastVictim; Scalar ramContactLinger; // 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 void MaintainViewClipSet(); // task #59 port: interior(viewpoint)/exterior(others) 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) Scalar lastInflictingDamage; // task #60: killing-blow magnitude (port-only; reuses // the retired phantom `stance` slot -- feeds the kill score) 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