# 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 ` + `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 (0–18); slots 14–18 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). NotifyOfDisplayModeChange-override@4d1ae4 (vtbl+0x4C; ex-"SetControlMode", (mode bits18-20, │ table @0051dbe4={0x40000,0x80000,0x100000}). SetPresetMode@4d1b24 (group*5+item -> {clear,set} mask table @0051dbf0[15]). │ EnterConfiguration@4d1840 (+0x38) / ExitConfiguration@4d18dc (+0x3C) (task #6 relabel (buttons 0x40..0x47, hat-skip 0x41->0x45, │ mode 0x8000=ConfigReady). AddOrErase evt/dir RET no-ops @4d195c/@4d1964 (+0x40/+0x44; TM can't regroup). 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,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 0xBBD–0xBDE); 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*,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)@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 0xBBD–0xBDE; remaining gaps 0xBC7, 0xBC9–0xBCA, 0xBCC, 0xBCE–0xBD2, 0xBD7, 0xBD9–0xBDB, 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 0x12–0x18 → 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 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 0x124–0x160, 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)