Files
BT411/game/reconstructed/CLASSMAP.md
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

52 KiB
Raw Blame History

BattleTech recovered class map (authoritative)

Reconciled from the reconstruction agents (heat, btplayer, mechweap, mechmppr, powersub). This is the single source of truth for the class hierarchy, vtable/ClassID/ctor addresses, and the shared-base offset layout. Update as modules are recovered.

Hierarchy

Subsystem  (MUNGA base — have source: RP/MUNGA/SUBSYSTM.HPP)
├─ MechSubsystem            vtable 0050e210  classID 0xBBB  ctors @4ac530(name+id)/@4ac644(resource)  dtor @4ac868  [mechsub.cpp]
│    *** DISTINCT BT BASE (NOT a MUNGA Subsystem alias).  MECHTECH.HPP `#include <mechsub.hpp>` + `MechSubsystem::TechStatusTypeCount`.
│    CORRECTION: heat.cpp folded this cluster (@4ac07c..@4ac9ec) into its "Subsystem/HeatableSubsystem base"; it is MechSubsystem.
│    CreateStreamedSubsystem @4ac9ec stamps classID 0xBBB / model 0xE4 and parses ONLY damage fields (PrintSimulationState,
│    CollisionCriticalHitWeight, VideoObjectName, VitalSubsystem) — NO thermal.  Couples subsystem→DamageZone@this+0xE0.
│    members: statusAlarm@0x2C (this+0xb), simulationState@0x40, damageZone*@0xE0, refCount@0xF4, alarmModel@0xF8,
│      printSimulationState@0x104, criticalReference@0x108, collisionCriticalHitWeight@0x10C, vitalSubsystemIndex@0x110(-1).
│    methods: GetStatusFlags@4ac144 (structureLevel tier), HandleMessage@4ac0bc, ResetToInitialState@4ac1d4, ClearStatus@4ac22c,
│      PrintState@4ac8c0 (DefaultState/Destroyed/Exploding), IsDamaged@4ac9c8 (mech bus down), ApplyDamageAndMeasure@4ac07c,
│      DistributeCriticalHit@4ac274 ("ammo explosion damaging"), LookupStatusType@4ac194 (name table @50de74).
│    TechStatusType states @50df17: Destroyed/Damaged/CoolantLeaking/Overheating/AmmoBurning/Jammed/BadPower (count=7).
│    Derivation name "MechSubsystem" @50def5/@50e2fd (distinct from "HeatableSubsystem" @50e700).
├─ HeatableSubsystem        ctor @4ac644   [heat.cpp]   members: currentTemperature@0x114, heatLoad@0x120
│    (REVISIT: string @50e700 confirms HeatableSubsystem is real but is a thin layer above MechSubsystem; the @4ac644 ctor
│     and vtable 0050e210 attributed here actually belong to MechSubsystem — temperature init is HeatSink's @4adda0.)
│  ├─ HeatWatcher           vtable 0050ed10  classID 0xBBF  ctor @4aeb40  [heatfamily_reslice]  watches another subsys temp → 3-lvl alarm
│  │  └─ PowerWatcher       vtable 0050f99c  classID 0xBD5  ctor @4b18a4  [powersub.cpp]
│  │     │   (shared base for the balance/aim subsystems below; CSS @4b198c stamps 0xBD5/size 0xF4 +
│  │     │    parses "MinVoltagePercent"@+0xF0; base ctor minVoltage@0x180, AlarmIndicator(5)@0x184;
│  │     │    per-frame watch @4b181c; slot9 @4b179c, slot10 reset @4b1804.)
│  │     ├─ HUD              vtable 00511510  classID 0xBD6  ctor @4b7f94  dtor @4b816c  CSS @4b81d4  [hud.cpp DONE]  size 0x2A4, model 0x104
│  │     │    cockpit heads-up display (torso-horizon gimbal + reticle/threat/status read-outs).  Confirms the
│  │     │    "Resolved conflict" note: @4b7f94 is the HUD, mislabeled "MechTech" by mech.cpp's 0xBD6 factory case.
│  │     │    DefaultData 00511078; resource +0xF4 FlickerRate, +0xF8 SegmentPageName(idx), +0xFC HorizontalMovementPerSecond, +0x100 HorizontalLimit.
│  │     │    members: flickerRate@0x1D8, torso/target read-outs@0x1DC-0x1EC, lockFlag@0x1F4, visible@0x1F8,
│  │     │      threatVector(Vec3)@0x1FC, compassHeading(Vec3)@0x208 (setter @4b7810), blink/slew accums@0x218-0x230,
│  │     │      linkedEntity@0x234(=Mech+0x438), statusAlarm(2)@0x238, horizontalTorsoOffset@0x294, HMPS@0x298, HorizontalLimit@0x29C, flickerActive@0x2A0.
│  │     │    overrides: HandleMessage@4b7780(s9, body not captured), ResetToInitialState@4b77bc(s10). HudSimulation@4b7830 (Performance, PTR@511170).
│  │     │    attr table @5110c0 (ids 4-0xd: RotationOfTorsoHorizontal/HorizontalTorsoLimit{Right,Left}/SpeedOfTorsoHorizontal/
│  │     │      RangeToTarget/Visible/Lock/HotBoxVector/ThreatVector/CompassHeading) — NB attr name<->offset bindings only
│  │     │      partially agree with ctor/sim usage (UNCERTAIN). TestInstance@4b81b8 (deriv @511088 "HUD"). UpdateFlicker@4b7ed4.
│  │     ├─ Searchlight      vtable 005114d4  classID 0xBD8  ctor @4b84dc  dtor @4b8568  CSS @4b85a8  [searchlight.cpp DONE]  model 0xF4
│  │     │    illumination subsystem (corroborated by btl4vid MakeMechRenderables "0xBD8 SearchLight").  DefaultData 00511198.
│  │     │    empty resource (CSS only stamps). members: lightState@0x1D8 ("LightState" attr id5),
│  │     │    commandedOn@0x1DC (res +0x28; toggled by "ToggleLamp" msg → ToggleLamp@4b860c), requestedOn@0x1E0, stateAlarm(2)@0x1E4.
│  │     │    overrides: ReadUpdate@4b83b8(s6), WriteUpdate@4b83f0(s7, net record tag 0x14); s9/s10 inherit PowerWatcher.
│  │     │    SearchlightSimulation@4b841c (Performance, PTR@511200; gates light by power@0x198 + heat@0x140). TestInstance@4b85f0 (deriv @5111a8).
│  │     ├─ ThermalSight     vtable 00511498  classID 0xBDE  ctor @4b8718  dtor @4b8794  CSS @4b87d4  [thermalsight.cpp DONE]  model 0xF4
│  │     │    cockpit thermal/IR vision mode. DefaultData 00511228. empty resource. members: thermalActive@0x1D8 ("LightState" attr id4),
│  │     │    requestedOn@0x1DC, stateAlarm(2)@0x1E0.  no s6/s7 override (inherits base); s9/s10 inherit PowerWatcher.
│  │     │    ThermalSightSimulation@4b8648 (Performance, PTR@511290): same power/heat gating as Searchlight, AND on state
│  │     │    change for the locally-viewed mech (PTR_DAT_004fd4b8+0x48) toggles the global "pvision" render flag via
│  │     │    FUN_0045fe44 (prints "pvision ON/OFF" @4f2f9d/@4f2fa8). TestInstance@4b881c (deriv @511238 "ThermalSight").
│  │     ├─ Gyroscope        vtable 00510abc  classID 0xBC4  ctor @4b3778  dtor @4b3e88  CSS @4b3eb4  [gyro.cpp DONE]
│  │     │     DefaultData 0050fdb0; resource size 0x21C. Balance/orientation/tip-over model.
│  │     │     Performance GyroscopeSimulation @4b275c (PTR @0050fe08). Members: exageration@0x1D8,
│  │     │       eyeOrientation@0x1DC, eyeAccel@0x230, eyeForce@0x23C, bodyOrientation@0x2B4,
│  │     │       rotationSpringConstant@0x2C0, rotationDampingConstant@0x2CC, bodyAccel@0x308, bodyForce@0x314,
│  │     │       maxAnimationNoise@0x39C, minAnimationNoise@0x3A0, rotationPerSecond@0x3A4, swayBias@0x3A8,
│  │     │       percentageOnNormal@0x3AC, OnDestruction@0x3B0, OnDegradation@0x3B4, OnFailure@0x3B8,
│  │     │       swayAngle@0x3BC, swayVelocity@0x3C0, swayActive@0x3C4, eyeJointNode@0x3C8, mechJointNode@0x3CC.
│  │     │     helpers: IntegrateEyeJoint@4b2ec0, IntegrateBody@4b30ec, WriteEyeJoint@4b33e0(@4b2eac thunk),
│  │     │       WriteMechJoint@4b34ec, UpdateAnimationNoise@4b357c; damage kicks @4b2d8c/@4b2de4/@4b2e50.
│  │     │     overrides: slot9 HandleDeath@4b2660(->4b179c), slot10 ResetToInitialState@4b2678.
│  │     │     CSS parses (all required, deg->rad on Rotation*Spring*): Percentage{Normal,Destruction,
│  │     │       Degradation,Failure}, RotationPerSecond, Exageration, M{ax,in}AnimationNoise, Spring/Damping
│  │     │       Constant{X,Y,Z}, {Pos,Neg}Spring{X,Y,Z}, Rotation{Spring,Damping}Constant{Pitch,Yaw,Roll},
│  │     │       Rotation{Pos,Neg}Spring{Pitch,Yaw,Roll}, {Collision,Ballistic,Explosive,Laser,Energy}Damage
│  │     │       Multiplier + …{Trans,PitchRoll,Yaw,Vibration}, EyeJoint, MechJoint (resolved in skeleton).
│  │     └─ Torso            vtable 0051103c  classID 0xBC5  ctor @4b6b0c  dtor @4b6fc0  CSS @4b6fec  [torso.cpp DONE]
│  │           DefaultData 00510af8; resource size 0x158. Torso twist(yaw)+elevation(pitch) aim model.
│  │           Performance TorsoSimulation @4b5cf0 (master, PTR @00510c10) / TorsoCopySimulation @4b65f8
│  │             (damaged copy, PTR @00510c1c). Members: currentTwist@0x1D8, horizontalLimitRight@0x1DC,
│  │             horizontalLimitLeft@0x1E0, currentElevation@0x1E4, twistVelocity@0x1E8, commands@0x1F8-0x208
│  │             (R/L/Up/Down/Center), statusFlags@0x20C, buttonAccelPerSec@0x210, buttonAccelStart@0x214,
│  │             targetTwist@0x218, twistAtUpdate@0x21C, verticalLimitTop@0x220, verticalLimitBottom@0x224,
│  │             twistRate@0x238, baseTwistRate@0x23C, baseElevRate@0x240, effTwistRate@0x244, effElevRate@0x248,
│  │             isDamagedCopy@0x24C, horizontalEnabled@0x250, lastUpdateTime@0x254, buttonRamp@0x26C,
│  │             recenterActive@0x274, horizontalJointNode@0x278, horizontalShadowJointNode@0x27C.
│  │           helpers: ComputeTargetTwist@4b6510, WriteJoints@4b66b4, UpdateJoints@4b67ec, Recenter@4b6918.
│  │           overrides: slot6 WriteUpdateRecord@4b6a78, slot9 HandleDeath@4b5be0(->4b179c),
│  │             slot10 ResetToInitialState@4b5bf8.  Angular resource values stored in radians (deg*PI/180).
│  │           CSS parses: TorsoHorizontalEnabled(bool,default True), ButtonAcceleration{PerSecond,StartValue},
│  │             {Horizontal,Vertical}RotationPerSecond, HorizontalLimit{Right,Left}, VerticalLimit{Top,Bottom},
│  │             TorsoHorizontalJoint + …ShadowJoint (resolved in skeleton, only when enabled).
│  └─ HeatSink              vtable 0050edc4   ctor @4adda0    [heat.cpp]
│     ├─ Condenser          vtable 0050ed88  classID 0xBBD  ctor @4ae568  [heatfamily_reslice]  refrigeration, 3-pos valve (NOT passive)
│     ├─ Reservoir          vtable 0050ecd4  classID 0xBC0  ctor @4af408  [heatfamily_reslice]  coolant source / InjectCoolant
│     ├─ HeatSinkBank?      vtable 0050ed50  classID 0xBBE  ctor @4ae8d0  [heatfamily_reslice]  aggregate (name unconfirmed)
│     ├─ Generator          vtable 0050fd74   classID 0xBC1  ctor @4b225c [powersub.cpp = gnrator.cpp]
│     │    RECONCILED: surviving GNRATOR.TCP describes THIS Generator (TestClass matches powersub.cpp; ResetToInitialState
│     │    chains HeatableSubsystem::ResetToInitialState + outputVoltage=0).  Original file was gnrator.cpp; binary linker-tagged
│     │    the cluster as powersub.cpp so reconstruction lives there.  [gnrator.cpp] = thin alias note (no new class).
│     │    GNRATOR.TCP reveals one method powersub.cpp omitted: Generator::ResetToInitialState() (slot 10) — fold into powersub.cpp.
│     └─ PoweredSubsystem   vtable 0050f9d8   classID 0xBC2  ctor @4b0f74 [powersub.cpp]
│        ├─ Sensor          vtable 0050fb0c   classID 0xBC3  ctor @4b1d18 [sensor.cpp]  size 0x328 (CORRECTED — was mislabeled "Myomers")
│        ├─ Myomers         vtable 005117dc classID 0xBC6  ctor @4b8fec [myomers.cpp]  size 0x358, resource 0x1B0  (the REAL Myomers — see correction below)
│        │    dtor @4b9114 · CSS @4b9140 (stamps 0xBC6/size 0x1B0) · DefaultData @0051154c.  Mech factory case 0xBC6 (alloc 0x358).
│        │    Artificial-muscle drive: a discrete SeekVoltage[] gear table → "SpeedEffect" drive fed to the JointedMover; throttled by heat + damage.
│        │    members: speedEffect@0x31C(1.0,attr0x12), currentSeekVoltageIndex@0x320(0x13), recommendedSeekVoltageIndex@0x324(0x14),
│        │      minSeekVoltageIndex@0x328(0x15), maxSeekVoltageIndex@0x32C(0x16), seekVoltage[5]@0x330(0x17), outputVoltage@0x344(0x18),
│        │      heatRange@0x348(=failTemp-degradeTemp), heatRangeSquared@0x34C, velocityEfficiency@0x350(res+0x190), accelerationEfficiency@0x354(res+0x194).
│        │    resource (size 0x1B0) adds +0x190 VelocityEfficiency, +0x194 AccelerationEfficiency, +0x198 SeekVoltage[5](fractions×ratedVoltage),
│        │      +0x1AC SeekVoltageRecommendedIndex (parser @4b9140).  attr table @00511588; string pool @00511660 ("Myomers"@0051166d).
│        │    vtable overrides vs PoweredSubsystem: slot0 dtor@4b9114, slot9 HandleMessage@4b8a8c(→4b0efc), slot10 ResetToInitialState@4b8aa4(→4b0e6c),
│        │      slot15 GetSpeedReading@4b8f94 (base 4b1780; km/h÷350 gauge readout, best-effort), slot16 HasAdequateVoltage@4b8f3c (overrides IsSourceShorted base 4b0b5c).
│        │    helpers: AvailableOutput@4b8ac0 (drive = MechBaseSpeed × V/recommendedSeekV × heatFactor × (1-damage)),
│        │      RegisterMaxOutput@4b8ef0 (→ Mech maxSpeed@0x7A0; loop @part_012:10322 over Mech myomer list@0x1EB),
│        │      MyomersSimulation@4b8d18 (Performance, per-tick work→heat accum@0x1C8, gated by FUN_004ad7d4; best-effort registration),
│        │      ConnectToMover@4b9550 / DisconnectFromMover@4b95b8 (route SpeedEffect@0x31C into Mech mover @**(Mech+0x128)),
│        │      ToggleSeekVoltage@4b8a48 (mappable cmd id 0x09; in binary, not in shards, best-effort).
│        │    EXCLUDED neighbors: gimbal/torso-horizon display @4b7f94..4b881c (base 4b18a4, DAT_00511228); MechWeapon trap @4b95ec.
│        └─ MechWeapon      vtable 00511d2c   classID 0xBCD  ctor @4b99a8 [mechweap.cpp]  resource 0x1BC
│           ├─ Emitter          vtable 00512078  classID 0xBC8  ctor @4bb120  [emitter.cpp]  beam/charge weapon base, resource 0x1DC
│           │  ├─ PPC            vtable 0051214c  classID 0xBD4  ctor @4bb888  (PPC.CPP survived; FireWeapon @4bb878 tail-calls Emitter::FireWeapon @4bace8)
│           │  └─ GaussRifle     vtable 00512934                ctor ~@4bd0xx (GAUSS.CPP survived; FireWeapon override sets outputVoltage=0)
│           └─ ProjectileWeapon  vtable 0051242c  classID 0xBCD*  ctor @4bc3fc  dtor @4bc688  CSS @4bc7cc  [projweap.cpp DONE]  resource 0x1D0, object 0x448
│              │   *abstract: CSS re-stamps MechWeapon's 0xBCD (no own registered ClassID); concrete MissileLauncher::CSS overwrites 0xBD0.
│              │   resource adds +0x1BC TracerInterval(int), +0x1C0 AmmoBin(idx,+2bias), +0x1C4 MinTimeOfFlight, +0x1C8 MinVoltagePercentToFire, +0x1CC MinJamChance.
│              │   members: TotalTimeToEject@0x3F0(3.0), TimeToEject@0x3F4, PercentOfEject@0x3F8, minJamChance@0x3FC, ejectState@0x400(-1),
│              │     minVoltagePercentToFire@0x404, tracerCounter@0x408, minTimeOfFlight@0x40C, launchVelocity(Vec3)@0x410, tracerModel@0x41C,
│              │     leadPosition(Pt3)@0x420, tracerOrigin(Vec3)@0x42C, tracerInterval@0x438, ammoBinLink(Connection,12B)@0x43C.
│              │   overrides: HandleMessage@4bcabc(s8,msg2→Jammed), ReadUpdate@4bbae0(s9 thin), Reset@4bbaf8(s10), GetStatusFlags@4bbf88(s12),
│              │     PrintState@4bc6c8(s13), slot16@4bbc20, FireWeapon@4bc104(s18). Performance(sim)@4bbd04. weaponAlarm states 5=Jammed,6=TriggerDuringJam,7=NoAmmo.
│              │     recovered helpers: UpdateEject@4bbb50, DrawFiringCharge@4bbc78, CheckForJam@4bbfcc, ComputeTimeOfFlight@4bc06c. NOT recovered(prologue only): 4bbc20/4bbd04/4bbf88/4bc104.
│              │   MechWeapon vtable = 19 slots (018); slots 1418 are MechWeapon virtuals (16=4b0b5c,17=4b9c9c,18=FireWeapon pure-trap @4ba45c).
│              └─ MissileLauncher vtable 00512570  classID 0xBD0  ctor @4bcff0  dtor @4bd060  CSS @4bd08c  FireWeapon @4bcc60(s18)  [mislanch.cpp]
│                   (MISLANCH.HPP survived) resource 0x1E0: +0x1D0 MissileCount(int), +0x1D4 MuzzleVelocity(Vec3→overwrites launchVelocity@0x410). missileCount@0x448.
│  AmmoBin (HeatWatcher-derived)  vtable 0051286c  classID 0xBCB  ctor @4bd5c4  dtor @4bd6b0  CSS @4bd6f0  [ammobin.cpp DONE]  referenced by ProjectileWeapon@0x43C.
│     resource 0x104: +0xF0 AmmoClassID("ProjectileClassID"→0xBD1/"MissileClassID"→0xBBA), +0xF4 AmmoModelFile, +0xF8 ExplosionModelFile, +0xFC AmmoCount, +0x100 FeedRate.
│     members: ammoCount@0x180, feedTimer@0x184, ammoClassID@0x188, cookOffArmed@0x18C, cookOffTime@0x190,
│       ammoAlarm(6-lvl)@0x194(level@0x1A8), ammoModelFile@0x1E8, explosionModelFile@0x1EC, cookOffHeatSource@0x1F0(heatPerRound@0x1F4), initialAmmoCount@0x220, feedRate@0x224.
│     ammoAlarm levels 0=Feeding,1=Loaded,2=Empty,5=Dumped(transient); watch heatAlarm level @0x140(word0x50)==2(FAILURE)→arm cook-off.
│     methods: AmmoBinSimulation(Performance,ptr@00512654)@4bd394, FeedAmmo@4bd4f4(→1 if dispensed), DumpAmmo@4bd588, CookOff@4bd300,
│       HandleMessage(s8,msg1=arm cook-off)@4bdb94, ReadUpdate(s9,chains @4aea84,dump-on-destroy)@4bd2c0, ResetToInitialState(s10 empty per AMMOBIN.TCP), TestInstance@4bd1e0.
├─ MechTech                 vtable 0050e3a0   classID 0xBDC  ctor @4ad228  [mechtech.cpp]  size 0x104
│    subsystem-monitor / cockpit-diagnostics "technician"; matches surviving MECHTECH.HPP.
│    members: subsystemMonitors ChainOf@0xE4, wooHooMinimumDuration@0xF4, wooHooDurationRange@0xF8,
│    wooHooChance@0xFC, alarmModel@0x100.  Performance = TechnicalAssistance @4ad33c (ptr @0050e2b0).
│    dtor @4ad1b8 · TestInstance @4ad470 (deriv @0050e280) · CreateStreamedSubsystem @4ad48c · DefaultData @0050e270.
│    nested SubsystemMonitor : Plug — vtable 0050e3d0, ctor @4ad124, dtor @4ad180, size 0x80,
│      statusArray[7]@0xC (StatusInfo=0x10: currentStatus,wooHooed,startTime,wooHooEnd), monitoredSubsystem@0x7C;
│      static MemoryBlock AllocatedMemory @0050e248.  (resource size 0x40: alarmModel@0x30, wooHoo*@0x34/0x38/0x3C)
├─ MechControlsMapper       vtable 0050f45c   ctor @4b02f0   [mechmppr.cpp]   (NOT "PowerController" — label settled)
│    └─ L4MechControlsMapper   vtable 0051e440  ctor @4d17ac  dtor @4d1814  [btl4mppr.cpp]  (RP analog L4VTVControlsMapper/RPL4MPPR.cpp)
│         L4-app platform layer over the in-Mech mapper. members: targetRangeExponentDemand@0x1a0(2.0),
│         targetRangeExponent@0x1a4(2.0), previousMissionReviewMode@0x1a8; reuses input-staging slots
│         throttleForward@0x1ac/throttleReverse@0x1b0/leftPedal@0x1b4/rightPedal@0x1b8 (cleared by derived ctors).
│         InterpretControls@4d196c (throttle detent snap 1.0±0.05, BuildPilotArray, review-mode bit 0x200000,
│         target-range ramp range=250*2^exp, exp clamp[0,5]; then chains MechControlsMapper::InterpretControls @4afd10).
│         ZoomTargetRangeIn@4d1b64 / ZoomTargetRangeOut@4d1b9c (±1.0). SetControlMode@4d1ae4 (mode bits18-20,
│         table @0051dbe4={0x40000,0x80000,0x100000}). SetPresetMode@4d1b24 (group*5+item -> {clear,set} mask table @0051dbf0[15]).
│         CreateTemporaryEventMappings@4d1840 / RemoveTemporaryEventMappings@4d18dc (buttons 0x40..0x47, hat-skip 0x41->0x45,
│         mode 0x8000=ConfigReady). NotifyOfControl/ConfigModeChange = no-ops @4d195c/@4d1964. KeypressMessageHandler@4d1bf0
│         (shared dispatcher: zoom/preset/pilot-cycle/look keys + fake-button dev keys; default -> @4d141c).
│         file-scope helpers: SendFakeButtonEvent@4d13f0, SendFakeButtonEventPair@4d133c, DispatchDeveloperFakeEvent@4d141c (GeneratorA-D/Condenser1/LRM15/AmmoBinLRM15/Avionics/Hud test keys).
│         ├─ MechThrustmasterMapper  vtable 0051e3f0  ctor @4d21d0  dtor @4d22b4  TestInstance @4d22e0  ClassDerivations @0051dc78
│         │     InterpretControls@4d2150 (throttleForward/Reverse -> throttlePosition@0x11c/reverseThrust@0x124; pedals -> pedalsPosition@0x120).
│         │     ctor registers scalarGroup[+0xa00/+0x9e0] + keyboard[+0x1a0] msg 0x17(Keypress).  btl4app passes DefaultData &DAT_0051dc68.
│         └─ MechRIOMapper           vtable 0051e3a0  ctor @4d266c  dtor @4d27cc  TestInstance @4d27f8  ClassDerivations @0051dd00
│               InterpretControls@4d25c0 (pedals -> pedalsPosition@0x120). msg table @0051dd30: Aux{1,2,3}{Quad,Eng1-4}(ids 3..0x11)->SetPresetMode @4d22fc-4d24bc,
│               ZoomIn/Out(0x12/0x13)->@4d24dc/@4d24f8, Hotbox->@4d2574 (pilot select, hotbox btn table @0051dcd0={0x37..0x30}).
│               AddOrErase(Receiver*)@4d25e8 / AddOrErase(ControlsButton*)@4d262c (mode 0x10000).  btl4app passes DefaultData &DAT_0051dcf0.
│         NOTE: btl4app.cpp::MakeViewpointEntity builds these two passing MechControlsMapperClassID 0x7dc; both chain L4MechControlsMapper@4d17ac -> MechControlsMapper@4b02f0.
├─ SubsystemMessageManager  vtable 0050b954   classID 0xBD3  ctor @49bca4  [messmgr.cpp]  (MESSMGR.HPP survived)
│    per-mech message hub: subsystems post damage via AddDamageMessage @49b6d8; the Performance
│    ConsolidateAndSendDamage @49b784 (ptr @0050b6fc) folds them into commonDamageInformation, rebroadcasts
│    a ClassID-0x13 notify msg (msg vtable @0050b984, size 0x34), queues terrain/weapon explosions, then
│    SendQueuedExplosions @49baa0 flushes ClassID-0x5C explosion cmds to the graphics pipe (DAT_004efc94+0x60).
│    dtor @49bd7c (slot0) · CreateStreamedSubsystem @49be10 (factory "SubsystemMessageManagerClassID"→@49be10;
│      stamps 0xBD3/size 0x38; reads "TerrainHitExplosion"→res+0x30, "RendererCompensateTime"→res+0x34).
│    vtable: only slot0(~dtor) and slot10(@49c3de empty = ResetToInitialState) overridden; rest inherit Subsystem.
│    ClassDerivations static @0050b6cc.  Located @49bxxx (adjacent to BTMission@49b54c), NOT the 0x4b27xx region.
│    members: messageHelper*@0xE0 (0x160-byte engine obj, ctor FUN_0041de1c; not in HPP),
│      commonDamageInformation{entityHit@0xE4, damageZoneIndex@0xE8(init -1), impactPoint(Pt3)@0xEC},
│      rendererCompensateTime@0xF8, weaponExplosions(VChainOf,hdr0x18)@0xFC (link vtable 0050b938),
│      damageInformation(VChainOf<DamageInformation*,Scalar>,hdr0x18)@0x114 (link vtable 0050b91c),
│      terrainHitExplosionID@0x12C.  object >= 0x130.
│    nested DamageInformation : Plug — vtable 0050b9a8, dtor @49c3e3, size 0x14: damageType@0x0C, subsystemID@0x10.
│      (AddDamageMessage fills damageType=msg+0x2C, subsystemID=msg+0x5C, key=msg+0x30, impactPoint=msg+0x4C.)
└─ VoltageSourceConnection : SharedData       ctor @4b1bb1   [powersub.cpp]

JointedMover (MUNGA) → Mech  vtable 0050cfa8  MechClassID 0xBB9  ctor @4a1674  size 0x854  [mech.cpp + mech2.cpp(+mech3/4 TODO)]
  hosts the subsystem roster above (ctor switch over ClassIDs 0xBBD0xBDE); RP analog = VTV
  damageZones[] roster @mech[0x120], count @mech[0x47]; built by CreateDamageZoneStream @4a474c (mech.cpp)
DamageZone (engine, DAMAGE.HPP) → Mech::DamageZone  vtable 0050bb90 (7 slots)  ctor @49ce50  size 0x1b8  [mechdmg.cpp]
  matches surviving MECHDMG.HPP.  Overrides slot0 ~dtor@49d23c, slot3 SetGraphicState@49c66c, slot6 TakeDamage@49c690
  (slots 1,2,4,5 = 4173b8/417938/41ddd8/41dd98 inherit DamageZone; base ctor @41df5c vtable 04e3cf0).
  members: redirectTable(DamageZoneIndexTable=TableOf<PlugOf<int>*,int>)@0x160, lastHitZone@0x178,
    lastDamageTime@0x17c, lastInflicting(EntityID)@0x180, parentArtifactZone(SlotOf)@0x188, segmentIndex@0x194,
    vitalDamageZone@0x198, descendOnDestruction@0x19c, destroySiblingsOnDestruction@0x1a0, leftLeg@0x1a4,
    rightLeg@0x1a8, criticalSubsystemCount@0x1ac, criticalWeightSum@0x1b0, criticalSubsystems**@0x1b4.
    (base DamageZone: owner Mech@0xc, graphic alarms@0x10/@0x64, dirty-flag@0xb8, 5 armour scalars@0x144..0x154, structureLevel@0x158)
  methods: SetLODParentPointers@49d1d0, LODDamageRouter@49c40c, RandomRedirect@49c600, UpdateLODDamageLevel@49c51c,
    SendSubsystemDamage@49c9a8, RecurseSegmentTable@49cad4, CriticalHit@49ccc4, ShortAttachedGenerators(inlined in TakeDamage),
    CreateStreamedDamageZone@49d304 (per-zone .dmg parser), GetSegmentIndex@49db20 (skl "dzone" walk).
  └─ MechCriticalSubsystem (SIGNATURED)  vtable 0050bb8c (1 slot=dtor)  ctor @49dd18  dtor @49dd44  TestInstance @49dd74  size 0x1c
       members: subsystemPlug(SlotOf<Subsystem*>)@0x04 (ctor@49dd7e/dtor@49dd9d vtable 0050bb84),
       damagePercentage@0x10, damagePercentageUsed@0x14, criticalWeight@0x18.
       parentArtifactZone slot vtable 0050bb7c (ctor@49ddc9/dtor@49dde8).  Generator-short uses Generator::ClassDerivations@50f4bc + ForceShortRecovery@4b11bc.
  NOTE: DMGTABLE (DamageZone lookup table: "PieSlice"/"Layer"/"RotateWithTorso", strings @50bc30..50bd00) is the
    ADJACENT but SEPARATE class cluster @49de14..~49f5fb.  NOW RECONSTRUCTED in [dmgtable.cpp].
    Three-level hit-location resolver, all : Plug:
      DamageLookupTable      vtable 0050bd84  ctor @49ea48  dtor @49eadc  size 0x30 — stack of armour LAYERS;
        ResolveHit @49eb54 (penetration depth → layer), BuildFromNotation @49ed28 (reads "Layer1".."LayerN").
      PieSlice               vtable 0050bd90  ctor @49e740                size 0x30 — angular sector wheel;
        SelectSlice @49e678 (torso-relative angle wrapped at 2π; orientationSource=owner+0x438, torso heading @+0x1d8),
        BuildFromNotation @49e88c (reads "RotateWithTorso" + "PieSlice" pages).
      DamageZonePercentTable vtable 0050bd9c  ctor @49deb0  dtor @49df80  size 0x2c — leaf weighted %-table;
        SelectZone @49de14 (uniform roll over cumulative %; entry+0xc=zoneIdx, entry+0x14=cumPct, percentages must sum to 1.0),
        BuildFromNotation @49e00c (reads "DamageZone" "%s %f" zone/pct lines), stream I/O @49e524/@49e5e4.
      entry comparator @49f301 (sorts by +0x14 within ~1e-4); template TableOf/PlugOf slots @49f204..@49f5b8
      (vtables 0050bd30/0050bd50/0050bd64 — engine instantiations).  _DAT_0049e72c = _DAT_0049e810 = 0x40c90fdb = 2π.
    Streamed from the .tbl "DamageLookupTable" by CreateDamageZoneStream (mech.cpp; @49ed28 invoked from part_012.c:11580,
    @49ea48 stream form from part_012.c:10423).  The dz_* name list @0x51bb70 is SectorDisplay UI data, NOT used by GetSegmentIndex.
Mission (MUNGA) → BTMission  ctor @49b54c  dtor @49b648  [btmssn.cpp, source survived BTMSSN.HPP/CPP]
  members: experienceLevel@0xe4, roleName@0xe8, teamName@0xec, advancedDamageOn@0xf0, missionTemperature@0xf4 (+273.15 C→K), patchName@0xf8;
  base Mission slots: colorName@0x20, badgeName@0x24, dropZoneName@0x28, gameModel@0x2c, dropZoneHost@0x3c, playerBitmapIndex@0x4c, scenarioRoles dict@0x50; BTMission::SetPlayerData @49b6bc = Fail stub
  └─ BTL4Mission  vtable 0051e870 = {dtor@4d2c04, SetPlayerData@4d2c30}  ctor @4d2be0  [btl4mssn.cpp]  (matches surviving BTL4MSSN.HPP; RP analog RPL4Mission)
       overrides SetPlayerData @4d2c30 (the only func the linker tagged to btl4mssn.cpp); TestInstance inlined out of OPT vtable
ScenarioRole : Node (SCNROLE.HPP survived)  ctor(role,model) @429a9c  members: damageInflictedModifier@0x0c, damageReceivedModifier@0x10, friendlyFirePenalty@0x14, damageBias@0x18, killBonus@0x1c, specialCaseDeathPenalty@0x20, roleName@0x24, returnFromDeath@0x28
L4Application → BTL4Application  vtable (OPT subclass 0x3eb)  ctor @4d34c4  [btl4app.cpp]  (matches surviving BTL4APP.HPP)
  └─ BTL4PlaybackApplication  vtable 0051f1a4  ctor @4d3be4  dtor @4d3ea0  TestInstance @4d40d8  [btl4pb.cpp]  (matches surviving BTL4PB.HPP; RP analog RPL4PlaybackApplication/RPL4PB.cpp)
       LBE mission-review/playback app. ClassDerivations @0051ee2c, DefaultData @0051ee5c, ClassID 0x3eb (L4ApplicationClassID).
       member timeBias@0xd4 (set in RunMission); spoolFile inherited @0x18. ctor(resource, spool_file) chains BTL4Application@4d34c4.
       overrides: MakeNetworkManager@4d3c14 (new L4PlaybackNetworkManager), Initialize@4d3c34, LoadBackgroundTasks@4d3c44
       (BTSpoolerTask + 4 stock tasks), ExecuteForeground@4d3d40 (review frame loop; controls only if DAT_004fd550==2),
       DispatchPacket@4d3ecc (route packet to NetworkClient), RunMissionMessageHandler@4d3ef8 (timeBias=Now-spool packet stamp),
       StopMissionMessageHandler@4d3f44 (winners-circle: park Mechs on rank dropzones "win1".."winN", SortAndReloadNameBitmaps, SetViewAngle 45deg).
       message table {RunMission@4d3ef8, StopMission@4d3f44}. winners_spot "win?" @0051ee63.
  BTSpoolerTask : SpoolerTask  vtable 0051f204  ctor @4d3b74  dtor @4d3bb8  [btl4pb.cpp]  (RP analog RPSpoolerTask)
       background task pumping recorded packets. DispatchPacket@4d3b90 -> playbackApp->DispatchPacket; GetTimeBias@4d3ba8 -> playbackApp->GetTimeBias.
       base SpoolerTask = munga_l4/L4SPLR.CPP (base stubs "shouldn't be here!" @0x4fda60).
GraphicGauge → MapDisplay   vtable 0051422c  ctor @4c1c24  [btl4rdr.cpp]  (matches surviving BTL4RDR.HPP)  radar/map
Gauge / GraphicGauge → cockpit gauge-widget library  [btl4gaug.cpp]  (NO header survived; reconstructed)
  RP analog = the MUNGA shared lib MUNGA_L4/L4GAUGE.cpp (ColorState/TwoState/BarGraph*/WipeGauge*/SegmentArc*);
  BattleTech forked that widget set into its own L4 module btl4gaug/gau2/gau3.obj instead of the shared lib.
  TU extent @4c2f94..@4c6394 (anchor @4c3f6c); btl4gau2.cpp begins @4c6798.
  Gauge-derived (palette tints, no GraphicsView; base ctor 00444308):
    ColorMapper            vtable 00518e10  ctor @4c37dc   palette-push Execute @4c3980
    ├─ ColorMapperArmor        vtable 00518dcc  ctor @4c3b98  Make @4c3aa4
    ├─ ColorMapperMultiArmor   vtable 00518d88  ctor @4c3d60  Make @4c3c48
    ├─ ColorMapperCritical     vtable 00518d44  ctor @4c3e40  Make @4c3ddc
    └─ ColorMapperHeat         vtable 00518d00  ctor @4c3f6c  Make @4c3f08  *** ANCHOR (assert BTL4GAUG.CPP:0x68a) ***
  GraphicGauge-derived (base ctor 00444818, GraphicsView@0x48):
    HorizTwoPartBar        vtable 00518cbc  ctor @4c4170  Make @4c407c  Execute @4c4340
    VertTwoPartBar         vtable 00518c78  ctor @4c4724  Make @4c462c  Execute @4c48fc
    VertNormalSlider       vtable 00518c34  ctor @4c4b84  Execute @4c4cc0
    OneOfSeveral           vtable 00518bf0  ctor @4c4d88  Execute @4c4f28   (base)
    ├─ OneOfSeveralInt         vtable 00518bac  ctor @4c5148  Make @4c5068
    ├─ OneOfSeveralPixInt      vtable 00518b68  ctor @4c52d8  Make @4c5204
    └─ OneOfSeveralStates      vtable 00518b24  ctor @4c5470  Make @4c5390
    HeadingPointer         vtable 00518ae0  ctor @4c562c  Make @4c554c  Execute @4c5914
    BitMapInverseWipe(?)   vtable 00518a9c  ctor @4c5b7c  Execute @4c5d08   (name best-effort; base of gau2 LeakGauge)
    BitMapInverseWipe(?)   vtable 00518a58  ctor @4c5e84  Execute @4c5fb8
    └─ ...Scalar(?)            vtable 00518a14  ctor @4c61c8
  ArcGauge-derived (MUNGA L4 arc bases 004745e0 / 00473f44):
    SegmentArc270          vtable 005189d0  ctor @4c6244
    SegmentArcRatio        vtable 0051898c  ctor @4c6394  Make @4c62fc
  File-private GaugeConnection feeds (base ctor 00444124; source@0x10 dest@0x14, Transfer writes *dest):
    Heat @4c3664 Transfer @4c3720 (reads HeatableSubsystem currentTemperature@0x114) ·
    Critical @4c3598/@4c3610 · Armor @4c33a4/@4c3430 · MultiArmor @4c346c/@4c34f4 ·
    State @4c3324/@4c3390 · TorsoHeading @4c3134/@4c31a0 · NameId @4c31ec/@4c3258 · AltId @4c3288/@4c32f4
    (NB: 4c3134/4c31ec/4c3288 are NOT file-private to btl4gaug — btl4gau2 reuses them; treat as shared gauge-lib connections.)
GraphicGauge / Gauge → cockpit COMPOSITE panel gauges  [btl4gau2.cpp]  (NO header survived; reconstructed)
  TU extent @4c6798..@4c9b50 (btl4gau3 begins @4c9bd0).  Name pool @0x519760; instance strings @0x519a..0x519d.
  These AGGREGATE btl4gaug primitives into one subsystem instrument (engineering / weapon-status HUD).
  GraphicGauge-derived (base ctor 00444818):
    SeekVoltageGraph      vtable 0051a1fc  ctor @4c6798  Execute @4c6934  (energy-weapon seek-voltage curve;
       reads CurrentSeekVoltageIndex/Min/Max/SeekVoltage; scale 230x187; helpers 4c6be4/4c6c30/4c6c6c)
    ConfigMapGauge        vtable 0051a1b8  ctor @4c6d80  Execute @4c6f1c  (joystick/config-mgr lamp; btjoy.pcc +
       cm_off/other/only/both.pcc via table DAT_00518eb8; string "ConfigMap")
    AnimatedSubsystemLamp vtable 0051a174  ctor @4c70a4  : OneOfSeveral + conn @4c3134  (best-effort; "CoolingLoop")
    AnimatedSourceLamp    vtable 0051a130  ctor @4c7160  : OneOfSeveral + conn @4c31ec  (best-effort; "PowerSource")
    ScalarBarGauge        vtable 0051a0e4  ctor @4c721c/@4c72ac : BarGraph(00472ef0) + conn @4c3288 / scalar 00474855
       (best-effort; "GeneratorVoltage", range 0..12000)
    GeneratorCluster      vtable 0051a0a0  Make @4c7368 ctor @4c746c  (5 children: VertTwoPartBar+ScalarBar+Leak+2 TwoState)
    SubsystemCluster      vtable 0051a020  ctor @4c8140  Execute @4c88e4  (base engineering panel; ~10 children)
    ├─ HeatSinkCluster        vtable 00519fd4  ctor @4c8a6c  Execute @4c8db0  (+4 fail TwoStates btehfail/btepfail/btesfail + numeric)
    └─ WeaponCluster          vtable 00519f38  ctor @4c8fc4  Execute @4c9290  (+SegmentArc270 recharge + ConfigMap; "WeaponCluster missing")
       ├─ EnergyWeaponCluster    vtable 00519ee8  ctor @4c93b0  (+SeekVoltageGraph "EngrGraph" + seek OneOfSeveralInt bteseek)
       └─ BallisticWeaponCluster vtable 00519e98  ctor @4c9558  Execute @4c9a38  (+2 ammo NumericInt + jam/fire TwoState +
            eject BitMapInverseWipeScalar bteejtm; tracks weaponAlarm==5 Jammed, ammo-bin @0x43C)
  Gauge-group-derived (non-graphic base ctor 004440d4, child list @0x24):
    PrepEngrScreen        vtable 0051a06c  Make @4c7b30 ctor @4c7bf0  Execute @4c7e48  (per-weapon engineering screen;
       walks mech crit-subsystem roster @0x124/@0x128, dispatch on ClassID 0xBC3/0xBC6/0xBC8/0xBD4/0xBCD/0xBD0/0xBCE)
GraphicGauge → multiplayer / tactical HUD displays  [btl4gau3.cpp]  (NO header survived; reconstructed)
  TU extent @4c9bd0..@4cbea0 (btl4grnd begins @4cbea0).  Instance strings @0x51bb34..0x51bb61.  Read the shared
  "Players" node (@00403ad0) and the live mech roster rather than a single subsystem.
    PlayerStatusMappingGroup  (no GraphicGauge base) build @4c9bd0 destroy @4c9ca8  — 28 ColorMapperArmor (dz_door table
       PTR_s_dz_door_0051a240) zone-tint group; adpal.pcc/adpal2.pcc; "PlayerStatusMappingGroup"
    SectorDisplay         vtable 0051beec  Make @4c9d44 ctor @4c9e10  Execute @4ca07c  (14-col bitmap grid + 2 numerics)
    TeamStatusDisplay     vtable 0051bea8  ctor @4ca208  Execute @4ca478  (8-row scoreboard; reads "Players")
    PilotList             vtable 0051be64  Make @4ca90c ctor @4ca958  Execute @4cabd0  ("PilotList"; 8 entries x3 numerics;
       lance lookup FUN_004b0898; helpers DrawMechIcon @4cad70 / DrawAmmoCount @4cae20)
    PlayerStatus          vtable 0051be20  Make @4cae90 ctor @4cb1a8  Execute @4cb358  ("PlayerStatus"; player# 1..8;
       CreateMutantPixelmap8 @4cafac recolours mech outline per player + PlayerStatusMappingGroup)
    MessageBoard          vtable 0051bddc  Make @4cb678 ctor @4cb704  Execute @4cb82c  ("MessageBoard"; rank/message strip cells)
GaugeAlarmManager → BTL4GaugeAlarmManager  [btl4galm.cpp]  (matches surviving BTL4GALM.HPP)
  *** NO distinct override body survived in BTL4OPT.EXE *** — only the MUNGA base GaugeAlarmManager is present
  (gaugalrm.cpp @00448928..@00448f48 in part_006.c): base vtable 0x4eed18 (Create=slot23 @00448ab8 trap, Read=slot24
  @00448ad4 trap, dtor slot0 @00448f1a); base drivers CreateGaugeAlarmStream @00448af0 (reads "gaugeAlarm" resource,
  prints "Model file '...' has undefined alarm type '...' - resource not built") and BuildAlarmsForModel @00448d00
  (makes GaugeAlarm @004489e8 size 0x28 vtable 0x4eed7c, calls the Read virtual per record).  No subclass vtable in
  vtables.tsv; no caller of base ctors @00448ebd/@00448edc/@00448efb; the linker's btl4galm.obj slot @4cc40c..@4cdac0
  holds an unrelated 3-D HUD-model builder (PNAME1..8.bgf).  btl4galm.cpp reconstructed from BTL4GALM.HPP signatures +
  the base calling contract (BEST-EFFORT / low confidence).
L4GaugeRenderer → BTL4GaugeRenderer  vtable 0051cebc  ctor @4cbea0  [btl4grnd.cpp]  (matches surviving BTL4GRND.HPP)
  HUD/cockpit gauge renderer manager (NOT a 3D ground renderer despite the file name).
  dtor @4cbf40 (slot0) · TestInstance @4cbf90 · NotifyOfNewInterestingEntity @4cbfa0 (slot9) ·
  NotifyOfBecomingUninterestingEntity @4cc028 (slot10).  RP analog = RPL4GaugeRenderer (RPL4GRND.cpp).
  ctor builds lampManager(this[0x7033]) + L4Warehouse(this[0x13]@0x4c) and calls
  BuildConfigurationFile("gauge\\l4gauge.cfg", BTL4MethodDescription@0051c910).
  Notify* route entities w/ a GaugeImageStream resource (type 0x12) into inherited
  movingEntities(@0x94, Movers) / staticEntities(@0xb0, Terrain).
  File-local BT lamp-manager subclass (vtable 0051ceb0: 4cc0e1/4cc294/4cc2fc) maps lamp names
  ("coolingLoop1"...) -> indices (helpers @4cc108/4cc148/4cc1a0); best-effort, not in header.
VideoRenderer (MUNGA L4 renderer mgr) → BTL4VideoRenderer   [btl4vid.cpp]  (NO header survived; reconstructed)
  The out-the-window 3D WORLD renderer (despite "btl4grnd"=gauges, THIS is the ground/world view).
  Class name confirmed by embedded string "BTL4VideoRenderer::Material name ..." @0051d6f8 and assert
  path d:\tesla\bt\bt_l4\BTL4VID.CPP.  RP analog = RPL4VideoRenderer (RPL4VID.cpp).  TU extent
  @4cc40c..@4d2bbc (btl4mssn.cpp begins @4d2be0).  Base label uncertain (MUNGA l4video.hpp names the
  concrete mgr "DPLRenderer" in the RP tree; this pre-DPL BT build drives the L4VIDRND.HPP
  VideoComponent/*Renderable hierarchy instead of dpl_DCS).
  CONFIDENT (assert-tagged file=btl4vid.cpp):
    @4cdac0 (549)  ReticleRenderable::AddWeapon  -- append weapon range/pip marker (cap 10:
       "Tried to display too many weapons" line 0x338); clamps range, computes screen pos, pre-builds
       two dpl2d_ display lists (pip ring + extended-range arc).  consts 0.012/0.014/0.7, range 1200.
    @4cef28 (6157) MakeMechRenderables  -- THE per-mech world builder (NOT a per-frame DrawScene as
       the brief guessed).  Walks the JointedMover EntitySegment table (entity@0x300, count@0x318)
       building Root/Hinge/Ball/BallTranslate/StaticChild renderables into a per-segment dcs_array;
       joint type from joint[0x10] (0,1,2=HingeXYZ 3=Static 4=Ball 5=BallTranslate); siteeyepoint→eye
       renderable (inside view); builds targeting reticle from HUD subsystem attrs (inside); walks
       subsystem roster (count@0x124, array@0x128, skips idx0 ControlsMapper) switching on ClassID:
       0xBC0 Reservoir, 0xBC8 Emitter beam, 0xBCD MechWeapon tracer, 0xBD4 PPC beam, 0xBD8 SearchLight.
    @4d0cc0 (1320) SetupMaterialSubstitutionList  -- reads "vehicletable" res (id 0x19), expands
       %color%/%badge%/%patch%/%serno% (serno = per-load '0'..'9'..'A'); parallels RP's version + patch/serno.
  BEST-EFFORT (file=? but in-cluster, by call graph):
    @4d0774  MakeEntityRenderables (ClassID dispatch; 0xBB9 Mech→Setup+@4cef28+@4d11e8, 0xBBA marker,
       0xBDA BTPlayer translocate/POV-start-end, default→VideoRenderer::MakeEntityRenderables @45e744).
    @4cc40c (5096) ReticleRenderable ctor (alloc 0x358; file-private reticle, BT variant of L4VIDRND
       ReticleRenderable).  @4cd918/@4cd938 reticle helpers.  @4d11e8 TearDownMaterialSubstitutionList.
    Inside-view member offsets touched: linkedEntity@0x40(GetLinkedEntity via 417ab4), present-flag@0xb0,
       fog@0xc8..0xd8, mainView@0x124, sceneRoot@0x12c(300), vehicleReticle@0x130, zones@0x2cc/0x2e4/0x318,
       jointToDCS socket@0x3a4.  ReticleRenderable AddWeapon arrays: count@0x38, color@0x64, sim@0xb4,
       mode@0x3c, rangeAttr@0x18c, rangeCache@0x1b4, pipListA@0x2b0/B@0x288; geom minRange@0x230,
       maxRange@0x22c, rangeScale@0x234, originX@0x1fc, originY@0x200, scaleY@0x204, biasX@0x208.
  EXCLUDED (inherited VideoRenderer/RendererOrigin base slots + the L4VIDRND renderable factory ctors
    @0x453xxx-0x460xxx, e.g. 455de4/453578/4537e8/4539b4/453ac4/45848c/4593c0/4590d8/456a68 — engine,
    not this TU).  dpl2d_ prims @0x487f34/0488xxx.  btl4mssn.cpp (@4d2be0+) is a separate TU.
Player (engine) → BTPlayer  vtable 00513300   ctor @4c0bc8   [btplayer.cpp]  size 0x294
  siblings (excluded from btplayer.cpp): BTTeam (vtable 5133dc), BTCameraDirector (vtable 5134c4)
CameraDirector (engine, DIRECTOR.HPP) → BTCameraDirector  vtable 005134c4  ctor @4c1408  size 0x238  [btdirect.cpp DONE]
  (matches surviving BTDIRECT.HPP; RP analog RPCameraDirector). Spectator-camera director: scans the first
  four player slots, picks the lowest-indexed one with a live vehicle, and tells the camera ship which
  mech transform to track; rotates the pick on a 30s timer.
  NOTE: CLASSMAP previously listed "vtable 5134b8" — that address is mid-string in the ClassDerivations
    name "BTCameraDirector" @005134b1; the real vtable starts 0xC later @005134c4 (17 slots).
  Only slot0 (~dtor @4c1484) is a BT override; slots 1..16 inherit CameraDirector/Director/Simulation.
  ClassDerivations @00513414 · DefaultData @00513444 · name str @005134b1.
  methods: ctor @4c1408 (chains base CameraDirector ctor @0042eec8; sets activePerformance=CreateBTCameraShip
    on master, i.e. (simFlags&0xc)!=4), Make @4c1454 (new 0x238), ~ @4c1484 (chains base dtor @0042efa4),
    TestInstance @4c14b0, CreateBTCameraShip @4c13d4 (initial Performance; chains base CreateCameraShip
    @0042ed68 then SetPerformance(&BeABTDirector)), BeABTDirector @4c1230 (steady Performance; BT override
    of base CameraDirector::BeADirector @0042ec3a — 4-player scan & 30.0f interval vs base 1-player/10.0f).
    NB: 4c1230 and 4c13d4 live in a Ghidra-undecompiled gap (@4c11b3..@4c1408); recovered via objdump.
  inherited CameraDirector base layout: activePerformance@0x1c, simulationFlags@0x28, cameraShip-build
    receiver@0x1fc(idx0x7f), followedVehicle Connection@0x20c(idx0x83, ctor @0042f121), switchTimer@0x218
    (idx0x86; base seeds -1.0f, base resets 10.0f, BT resets 30.0f), cameraShip*@0x21c(idx0x87, Dispatch
    target), viewpoint-cut FSM @0x220/0x224/0x228/0x22c with 10.0f@0x230 / 15.0f@0x234 (base @0042eb38).
  Base CameraDirector itself: vtable 004e6b4c, ctor @0042eec8, dtor @0042efa4, Make @0042ef74, size 0x238,
    DefaultData @004e6ad4, GetPlayerByIndex @0042efe4 (walks "Players" EntityGroup @004e6b3c, key +0x1cc).
  bt entity factory @4c15f0: creation request named "camera" (@00513549) → news BTCameraDirector (size 0x238);
    otherwise → news BTPlayer (ctor @4c0bc8, size 0x294). Same TU group as btplayer/btteam; not a BTCamDir member.
ScenarioRole (SCNROLE.HPP survived) — used by BTPlayer @0x208: damageInflictedModifier+0x0c, damageReceivedModifier+0x10

Missile flight-entity cluster (spawned by MissileLauncher::FireWeapon @4bcc60, streamed ClassID 0xBD0):
  Entity → Projectile  vtable 00512a5c  ctor @4be1bc  dtor @4be358  alloc @4be384 (size 0x340)  [projtile.* DONE — only PROJTILE.TCP survived]
        TestClass() takes NO arg (PROJTILE.TCP), TestInstance@4be3b4 (tag 0x512980). ctor registers into world segment table + snapshots launch descriptor.
        members (best-effort, past Entity base): modelUpdateContext@0x250(+bound update PTR_FUN_005129dc@0x254), world@0x300, currentSegment@0x304,
          segmentIndex@0x308(desc+0x84), sourceEntity@0x30C(desc+0x88), sourceWeapon@0x310(desc+0x8C), launchPosition@0x314(desc+0x90),
          launchVelocity@0x320(desc+0x9C), aimDirection@0x32C(desc+0xA8), modelIndex@0x338(desc+0xB4), damageRecord@0x33C(desc+0xB8).
        Performance variants PTR_LAB_005129e8(authoritative)/005129f4(ghost); base MoveAndCollide body past window — live integrator is Missile override @4bef78.
    └─ Missile  vtable 00512f2c  ctor @4bf5b4  dtor @4bf890  alloc @4bf8bc (size 0x368)  [missile.cpp]
         MoveAndCollide @4bef78 (age/guide/integrate/collide; collision FUN_0042291c, detonate FUN_004be078
           streamed ClassID 0x5C), WriteUpdateRecord @4bef4c (slot7, tag 0x78).
         Hosts 2 subsystems: this[0x49]=2 @0x124, roster @0x128 → {Seeker, MissileThruster}.
         enum {SeekerSubsystem=0, MissileThrusterSubsystem=1}.  members (best-effort):
           lifetime@0x340 ageFraction@0x344 thrustScale@0x348 targetConn@0x34C targetOffset@0x350
           age@0x35C explosionModel@0x360 detonationFlag@0x364.
  Subsystem → Seeker  vtable 00512ce8  ctor @4bec34  dtor @4bee44  [seeker.cpp]  (SEEKER.HPP survived)
         targetPosition@0xE4 creationPoint@0xF0 targetEntity@0xFC targetOffset@0x100
         rangeToTarget@0x10C rangeFromCreation@0x110 startingRangeToTarget@0x114.
         LeadTarget @4beae4 (lead window 200..300m, gain ~0.1).  No own resource fields
         (shared default record &DAT_00512bec).  attr IDs 0x341/0x345/0x34D (IndexEntry table @512d50).
         vtable = Subsystem base except slot0 dtor (no virtual overrides).
  Subsystem → MissileThruster  vtable 00512bbc  ctor @4be7c4  dtor @4be8bc  CSS @4bf8ec  [misthrst.* DONE]  (MISTHRST.HPP survived)
         burnTimeRemaining@0xE4 maxThrusterRotationRate@0xE8 (=Degree*PI/180, factor 0x3c8efa35 @4be8b8)
         thrusterAcceleration@0xEC thrusterClimb@0xF0 acceleration(Motion)@0xF4 (linear init (0,0,-thrusterAcceleration); angular@0x100 zero).
         vtable = Subsystem base except slot0 dtor (no virtual overrides; behaviour in Performance PTR_LAB_00512b20=MissileThrusterSimulation, folded into Missile::MoveAndCollide). TestInstance@4be8e8.
         resource: ctor reads +0x30 burnTime, +0x34 maxRotRate, +0x38 thrusterAccel, +0x3C thrusterClimb (Subsystem hdr 0x30).
         CSS @4bf8ec (record size 0x54, type-0xF model record, section "gamedata"): +0x40 MaxThrusterRotationRate, +0x44 BurnTime,
           +0x48 ThrusterAcceleration, +0x4C ThrusterClimb, +0x50 SplashRadius (extra, not in HPP). Missile ctor copies model+0x40..4C → resource+0x34/0x30/0x38/0x3C.
         attr IDs 0xE5/0xE9/0xED (IndexEntry table @512ad0).
  Subsystem region note: AmmoBin (above, classID 0xBCB) is HeatWatcher-derived NOT a bare Subsystem; CSS @4bd6f0 chains HeatWatcher::CSS @4aec54.
         Referenced by ProjectileWeapon as a SharedData connection (connection object ctor @4bcbb0, vtable 00512424) at launcher this[0x10f]@0x43C.

Clarification on the ProjectileWeapon vs Emitter split (mechweap.cpp branch, both : MechWeapon):
  Emitter           CSS @4bb478 parses DischargeTime/GraphicLength/SeekVoltage/SeekVoltageRecommendedIndex.
  ProjectileWeapon  CSS @4bc7cc parses TracerInterval/AmmoBin/MinTimeOfFlight/MinJamChance/MinVoltagePercentToFire.
  MissileLauncher   CSS @4bd08c (chains @4bc7cc) parses MissileCount(+0x1d0)/MuzzleVelocity(+0x1d4).
  MissileLauncher ctor @4bcff0 (chains @4bc3fc) divides damageData.amount by missileCount (per-missile damage).

ClassID registry (from CreateStreamedSubsystem classID stamps)

0xBB9 Mech · 0xBBD Condenser · 0xBBE HeatSinkBank? · 0xBBF HeatWatcher · 0xBC0 Reservoir · 0xBC1 Generator · 0xBC2 PoweredSubsystem · 0xBC3 Sensor (was "Myomers" — corrected) · 0xBC6 Myomers (recovered, [myomers.cpp]; stamped by CreateStreamedSubsystem @4b9140 — the REAL Myomers) · 0xBC8 Emitter (recovered, [emitter.cpp]; stamped by CreateStreamedSubsystem @4bb478) · 0xBCB AmmoBin (recovered, [ammobin.cpp]; stamped by CreateStreamedSubsystem @4bd6f0) · 0xBCD MechWeapon · 0xBD4 PPC (recovered; stamped by PPC::CreateStreamedSubsystem @4bb8e0) · 0xBD3 SubsystemMessageManager (recovered, [messmgr.cpp]; stamped by CreateStreamedSubsystem @49be10) · 0xBC4 Gyroscope (recovered, [gyro.cpp]; CSS @4b3eb4, PowerWatcher-derived) · 0xBC5 Torso (recovered, [torso.cpp]; CSS @4b6fec, PowerWatcher-derived) · 0xBD5 PowerWatcher · 0xBD6 HUD (recovered, [hud.cpp]; CSS @4b81d4, PowerWatcher-derived; was mech.cpp's mislabeled "MechTech" 0xBD6 gimbal) · 0xBD8 Searchlight (recovered, [searchlight.cpp]; CSS @4b85a8, PowerWatcher-derived) · 0xBDE ThermalSight (recovered, [thermalsight.cpp]; CSS @4b87d4, PowerWatcher-derived) · 0xBDC MechTech (recovered, [mechtech.cpp]; stamped by CreateStreamedSubsystem @4ad48c) NB: AmmoBin's "AmmoClassID" resource key also names two SPAWNED entity classes (not subsystem stamps): 0xBD1 Projectile-round entity ("ProjectileClassID") · 0xBBA Missile-round entity ("MissileClassID"). (Mech ctor streams the contiguous subsystem range 0xBBD0xBDE; remaining gaps 0xBC7, 0xBC90xBCA, 0xBCC, 0xBCE0xBD2, 0xBD7, 0xBD90xBDB, 0xBDD = not-yet-recovered subsystems: actuator/ jumpjet/cockpit/GaussRifle/ProjectileWeapon/MissileLauncher/etc. NOTE: Sensor 0xBC3, Gyroscope 0xBC4, Torso 0xBC5, Myomers 0xBC6, Emitter 0xBC8, AmmoBin 0xBCB, SubsystemMessageManager 0xBD3, HUD 0xBD6, Searchlight 0xBD8 and ThermalSight 0xBDE are recovered, not gaps.)

Resolved conflict

  • @0x4b02f0 / vtable 0050f45c: MechControlsMapper (dtor slot0 = 4b044c, mechmppr's; rest inherit Subsystem). powersub's "PowerController" label was incorrect — it correctly did NOT emit the code.
  • @0x4b1d18 / vtable 0050fb0c / classID 0xBC3: Sensor (not "Myomers"). powersub.cpp inferred members outputVoltage/powered/voltageAvailable with no string backing. The shipped string pool @0050fae0 ("Sensor\0RadarPercent\0SelfTest\0BadVoltage\0") and the AttributePointers table @0050fa50 (ids 0x12/0x13/0x14 → offsets 0x31C/0x320/0x324) match SENSOR.HPP exactly: radarPercent@0x31C (1.0f), selfTest@0x320, badVoltage@0x324. CreateStreamedSubsystem @4b1dcc stamps classID 0xBC3 / model size 0x190 and parses NO extra fields (empty Sensor resource). [sensor.cpp]
  • The REAL Myomers is classID 0xBC6, vtable 005117dc, ctor @4b8fec [myomers.cpp] — NOT the @4b1d18/0050fb0c class powersub mislabeled "Myomers" (that is Sensor, above). Proven by the string pool @00511660 ("Myomers"@0051166d, "ToggleSeekVoltage", "SpeedEffect", "CurrentSeekVoltageIndex", "RecommendedSeekVoltageIndex", "MinSeekVoltageIndex", "MaxSeekVoltageIndex", "SeekVoltage", "OutputVoltage", "VelocityEfficiency", "AccelerationEfficiency") and the attribute table @00511588 (ids 0x120x18 → offsets 0x31C/0x320/0x324/0x328/0x32C/0x330/0x344; command id 0x09 ToggleSeekVoltage → handler @4b8a48). CSS @4b9140 stamps 0xBC6 / model size 0x1B0; Mech factory case 0xBC6 allocs 0x358. powersub's invented "outputVoltage/powered/voltageAvailable" triad has NO backing and is retired — the real members are the SpeedEffect / SeekVoltage[] drive table (see myomers.hpp).
  • mech.cpp's this[0x1f7] "sensorSubsystem" (case 0xBBE, ctor 4ae8d0, alloc 0x1E4) is NOT the SENSOR.HPP Sensor (too small for a PoweredSubsystem, which is 0x31C alone). That cache slot holds a smaller HeatSink-family subsystem; the "Sensor" name on it is a mech.cpp mis-guess. The real Sensor is mech.cpp factory case 0xBC3 (ctor 4b1d18, alloc 0x328).
  • MechTech is classID 0xBDC, ctor @4ad228 (not 0xBD6/@4b7f94). mech.cpp's factory switch had two swapped labels: it called @4ad228 "HeatableSubsystem" (case 0xBDC, alloc 0x104 = MechTech's size) and called @4b7f94 "MechTech" (case 0xBD6, alloc 0x2a4). @4b7f94 is actually the HUD torso-horizon gimbal display (base @4b18a4, vtable 00511510; CreateStreamedSubsystem @4b81d4 parses "FlickerRate"/"HorizontalMovementPerSecond"/"HorizontalLimit") and is unrelated to MechTech. Evidence for @4ad228 = MechTech: vtable 0050e3a0 adjacent to the literals "MechTech::SubsystemMonitors" @0050e2f3 and "AlarmModel" @0050e322; CreateStreamedSubsystem @4ad48c stamps classID 0xBDC and reads "AlarmModel"; the ctor builds a ChainOf<SubsystemMonitor*> exactly as MECHTECH.HPP declares. Consequence: mech.cpp's mechTechSubsystem = this[0x16d] cache (set in the 0xBD6 case) actually points at the gimbal display, and the true MechTech (0xBDC case) is not cached — a mech.cpp bug to revisit.

Shared base offset layout (use for all subsystem modules)

  • Subsystem occupies the low offsets (owner Mech ptr commonly at this[0x34]/+0xD0).
  • HeatableSubsystem adds: currentTemperature@0x114, degradation/failureTemperature@0x118/0x11C, heatLoad@0x120, coolant/thermal block 0x1240x160, heat-state flag @0x184.
  • PoweredSubsystem adds from @0x1D0: voltageSource, aux-screen fields, alarms @0x264/0x2B8, thermalResistivityCoefficient@0x30C, startTime@0x314.
  • MechWeapon adds from @0x31C: fire/recharge/range/pip/damage(Damage@0x3A8)/heatCostToFire. NB: MechWeapon's recharge-level slot @0x320 is reused by Emitter as outputVoltage (the normalised discharge readiness; ==1.0 ⇒ fully charged, used in EMITTER.TCP).
  • Emitter adds from @0x3F0 (object ~0x478): seekVoltageIndex@0x3F0, seekVoltageRecommendedIndex@0x3F4, seekVoltageCount@0x3FC, seekVoltage[5]@0x400, currentLevel@0x414, firingActive@0x418, beamOrientation@0x41C, beamScale@0x42C, beamLengthRatio@0x434, graphicLength@0x438, dischargeTime@0x43C, dischargeTimer@0x440, damageFraction@0x444, energyTotal@0x448, damagePortion@0x44C, heatPortion@0x450, energyCoefficient@0x454, seekRate@0x45C, beamEndpoint@0x460, beamFlag@0x46C, targetLocalFlag@0x470, targetEntity@0x474. Firing state machine carried in weaponAlarm level @0x364 (0=Firing,2=Loaded,3=Loading,4=TriggerDuringLoad).

Recovered resource structs (parsed by each CreateStreamedSubsystem)

  • HeatableSubsystem/HeatSink: +0xE4 StartingTemperature, +0xE8 Degradation, +0xEC Failure, +0xF0 ThermalConductance, +0xF4 ThermalMass, +0xF8 HeatSink index
  • PoweredSubsystem (size 0x190): +0xFC VoltageSource, ThermalResistivityCoefficient, AuxScreenNumber/Placement/Label[64], EngScreenLabel[64], StartTime
  • Generator (size 0x10C): RatedVoltage, MaxTapCount, StartTime, ShortRecoveryTime
  • MechWeapon (size 0x1BC): +0x190 RechargeRate, WeaponRange, ExplosionModelFile, DamageAmount, DamageType, HeatCostToFire, Pip*
  • Emitter (size 0x1DC): +0x1BC GraphicLength, +0x1C0 DischargeTime, +0x1C4 SeekVoltage[5], +0x1D8 SeekVoltageRecommendedIndex (parser @4bb478)