Files
BT412/game/reconstructed/CLASSMAP.md
T
arcattackandClaude Fable 5 092408041c Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and
live-verified (hold-configure -> tap fire button -> toggle membership ->
release-commit):
- MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton
  @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept)
  + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/
  MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch).
- Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration /
  +0x40,+0x44 AddOrErase evt/dir.  No "secondary vtable @0050f498", no
  "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP).
  L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the
  NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon
  latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO
  mapper implements the toggle (Thrustmaster no-ops = can't regroup).
- MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID
  (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the
  destination to &fireImpulse (@004b99a8).
- ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table;
  buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale
  was wrong).  Finding [T1]: the shipped binary NEVER enables this gauge (no
  SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable.
- Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon);
  BT_CONFIG_TEST scripts a headless verify.  @004afbc4 corrected to its real
  Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups).
- Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries
  (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong.

TASK #11 -- the user-reported live-play regressions (both real bugs):
- PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the
  victim on its OWN 0.3s cadence whenever fire was held -- desynced once the
  authentic recharges landed.  Retired (the real impact visual flows from
  each discharge via the messmgr SubmitExplosion).
- WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window
  (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike
  jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness,
  and the weapon sticks in Loading at level ~10000 forever (observed live).
  Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick
  -- also fixes the I^2R integral over-heating generators on big steps.
  NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap).
- Duty-cycle measured (max-rate autofire): generators equilibrate in the
  degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker
  never trips solo, PPCs sustain ~11.8-damage full-charge fire.  The slow
  kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's
  counters (this regrouping UI, generator reassignment, coolant valves) are
  the reconstruction queue.

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

492 lines
53 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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). 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<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)