Files
BT411/docs/VEHICLE_SUBSYSTEMS.md
T
arcattackandClaude Opus 4.8 a5fa9f1c79 vehicleSubSystems: full reverse-engineering + reconstruction spec (blocked)
Reverse-engineered the vehicleSubSystems config primitive end to end (Ghidra
headless, all ~28 functions).  It is NOT a widget: it is the engineering-screen
(MFD) subsystem-panel system.  Its Make (FUN_004cbaf0) is a per-subsystem factory
that builds a SubsystemCluster-family status panel onto one of 12 auxiliary MFD
positions, dispatching on subsystem classID (HeatSink/Myomer/Energy/Ballistic
clusters).  The whole cluster family (base + 5 subclasses) + 4 btl4gau2 sub-gauges
(CoolingLoop/PowerSource/ScalarBarGauge/ConfigMapGauge) are declared in btl4gau2.hpp
but not reconstructed.

BLOCKER: the Make reads base subsystem fields subsystem[0x1dc] (aux-screen position),
[0x1e4]/[0x1e0]/[0x224] that our MechSubsystem reconstruction (ends 0x114) does not
have or populate -- so the panels render nothing until the core subsystem layout is
extended + populated from the resource parse, which touches the working combat/heat
subsystem code (regression risk).  Checkpointed at full spec pending go/no-go on the
large core-touching implementation.

- docs/VEHICLE_SUBSYSTEMS.md: complete reconstruction spec (dispatch table, geometry
  table, class family map, sub-gauge inventory, engine-primitive reuse, the blocker,
  the Phase-1/Phase-2 plan).
- reference/ghidra_scripts/DecompVSS.java: headless address-list decompiler (reusable
  for any function the assert-anchored exporter skipped).
- CLAUDE.md: record the finding in the gauge-widget notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 23:55:56 -05:00

8.1 KiB
Raw Blame History

vehicleSubSystems — the engineering-screen subsystem cluster panels (reconstruction plan)

Status: reconstruction UNDERWAY (from-scratch). This is the largest single gauge item — not a widget but the whole engineering-screen (MFD) subsystem-panel system. Full decomp captured in the session scratchpad (vss_full.txt / vss_sub.txt, Ghidra headless of BTL4OPT.EXE); this doc is the durable spec so the analysis survives across sessions.

What it is

The config primitive vehicleSubSystems(qXXX0.pcc .. qXXX7.pcc) (26 uses in L4GAUGE.CFG, one per vehicle init block after MechInit;) registers a Make dispatcher (FUN_004cbaf0, methodDescription @0x51c080, 8 typeString params = the 8 strip bitmaps). At cockpit-build time the Make walks the mech's subsystem roster (entity+0x124 count / +0x128 array, skipping slot 0 = the controls mapper) and, for each subsystem, builds a status cluster panel onto one of 12 auxiliary MFD positions, dispatching on the subsystem's classID (sub+4):

classID subsystem child cluster ctor new size id string
0xBC3 Sensor HeatSinkCluster (looks up the "HUD" subsystem via FindSubsystem, FUN_0041f98c) FUN_004c8a6c 0xf0 "SensorCluster"
0xBC6 Myomers MyomerCluster FUN_004c8df4 0xd8 "MyomerCluster"
0xBC8 / 0xBD4 Emitter / PPC EnergyWeaponCluster FUN_004c93b0 0xf0 "EnergyWeaponCluster"
0xBCD / 0xBD0 Projectile / Missile BallisticWeaponCluster FUN_004c9558 0x11c "ProjWeaponCluster"
0xBCE Gauss — (error "Gauss rifle not yet supported")
all others skip

Type filter before dispatch: FUN_0041a1a4(**(sub+0xc), "") (an IsA/registry check) must pass.

The per-group geometry table @0x51bf34 (12 groups × 7 dwords, keyed by sub[0x1dc]-1)

Each group = { X, Y, planeMask, subBit, mfdPortName, engPortName, planeIndex }:

grp  X     Y    planeMask  subBit  mfdPort  engPort  idx
 1   0    240    0x1        0x2     mfd1     eng1     0
 2   322  240    0x1        0x4     mfd1     eng1     0
 3   0    28     0x1        0x8     mfd1     eng1     0
 4   322  28     0x1        0x10    mfd1     eng1     1
 5   0    240    0x20       0x40    mfd2     eng2     1
 6   322  240    0x20       0x80    mfd2     eng2     1
 7   0    28     0x20       0x100   mfd2     eng2     1
 8   322  28     0x20       0x200   mfd2     eng2     2
 9   0    240    0x400      0x800   mfd3     eng3     2
10   322  240    0x400      0x1000  mfd3     eng3     2
11   0    28     0x400      0x2000  mfd3     eng3     2
12   322  28     0x400      0x4000  mfd3     eng3     (self)

mfdPortrenderer->GetGraphicsPort(name) (the visible MFD surface); engPort (string) is the engineering-screen render port name passed to the cluster sub-gauges.

Class family (all btl4gau2, all DECLARED in btl4gau2.hpp, NONE reconstructed pre-this-effort

except SeekVoltageGraph)

  • SubsystemCluster base @0x4c8140 (vt 0x51a020) : GraphicGauge — builds ~11 sub-gauges (HorizTwoPartBar temp bar, CoolingLoop ×2, PowerSource ×2, BackgroundBitmap, OneOfSeveralInt modeLamp, GeneratorVoltage/ScalarBarGauge, OneOfSeveralStates stateLamp, VertTwoPartBar ×2, LeakGauge). dtor 0x4c87dc, Execute 0x4c88e4, draw-bg helper 0x4c89c4, BecameActive-draw 0x4c8820, TestInstance 0x4c8990, ShowInstance 0x4c8a28. Reads attributes InputVoltage/HeatSink/CurrentTemperature/ DegradationTemperature/FailureTemperature/CoolantMassLeakRate via FUN_0041bfc0 (=GetAttributeIndex by name, returns owner + idx - 1 or 0).
  • HeatSinkCluster @0x4c8a6c (vt 0x519fd4) : SubsystemCluster — adds 4 fail TwoStates (btehfail/btepfail/btesfail) + a NumericDisplayScalar. Execute 0x4c8db0 scales this[0x35] by _DAT_004c8df0 (=100.0). dtor 0x4c8d18.
  • MyomerCluster @0x4c8df4 (vt 0x519f88) : SubsystemCluster — adds a SeekVoltageGraph ("EngrGraph", edestryd.pcc) + a seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x800). Execute = base. dtor 0x4c8f54.
  • WeaponCluster @0x4c8fc4 (vt 0x519f38) : SubsystemCluster — adds SegmentArc270 recharge dial (reads "PercentDone") + ConfigMapGauge. Make-time warns "WeaponCluster missing ". Execute 0x4c9290 toggles a warn lamp when this[0x36] > _DAT_004c92e0. DrawWarningLamp 0x4c932c, BecameActive 0x4c9258, dtor 0x4c91d4, slot17 0x4c92e4.
    • EnergyWeaponCluster @0x4c93b0 (vt 0x519ee8) : WeaponCluster — adds SeekVoltageGraph (OutputVoltage) + seek-step OneOfSeveralInt (subsys+0x3f0). dtor 0x4c94dc.
    • BallisticWeaponCluster @0x4c9558 (vt 0x519e98) : WeaponCluster — adds 2× NumericDisplayInteger (ammo, link+0x180), jam/fire TwoStates (btejam/btefire), NumericDisplayScalarTwoState, eject BitMapInverseWipeScalar (bteejtm, subsys+0x3f8), a GraphicsViewRecord erase tracker. Execute 0x4c9a38 tracks weaponAlarm==5 (Jammed, subsys+0x364) + the reload clock. dtor 0x4c9940, BecameActive 0x4c99cc, TestInstance 0x4c9adc, slots 0x4c9b24/0x4c9b50.

Sub-gauge classes to reconstruct (btl4gau2, declared not defined)

  • AnimatedSubsystemLamp (CoolingLoop) @0x4c70a4 (vt 0x51a174) : OneOfSeveral + connection FUN_004c3134 (vt 0x518ea8, subsystem-state driven) at +0x2b. dtor 0x4c7134.
  • AnimatedSourceLamp (PowerSource) @0x4c7160 (vt 0x51a130) : OneOfSeveral + connection FUN_004c31ec (vt 0x518e9c) at +0x2b. dtor 0x4c71f0.
  • ScalarBarGauge (GeneratorVoltage) @0x4c721c (vt 0x51a0e4) : engine bar FUN_00472ef0 (VertNormalSlider-ish) + connection FUN_004c3288 (vt 0x518e90) at +0x2d. 2nd ctor 0x4c72ac (uses the engine FUN_00474855 scalar-attr connection). dtor 0x4c733c.
  • ConfigMapGauge @0x4c6d80 (vt 0x51a1b8) : GraphicGauge — 5 pcc names (btjoy/cm_off/cm_other/ cm_only/cm_both) interned into the warehouse; Execute 0x4c6f1c reads the control-mapper state from app->GetModeManager() (DAT_004efc94+0x3c, group 0x1c0 + i*0x20) and blits the matching frame. SetColor 0x4c6ee0 (writes this+0x94). dtor 0x4c6e54.

Engine primitives it calls (ALREADY in munga_engine.lib, callable — do NOT reconstruct): TwoState (FUN_004739d8, vt 0x4fb740), NumericDisplayScalar (0x470888, vt 0x4fbb40), NumericDisplayInteger (0x470cfc), NumericDisplayScalarTwoState (0x473c94), BackgroundBitmap (0x471d00), and the GaugeConnection ctors FUN_00474855/FUN_004749de. Already reconstructed: HorizTwoPartBar/VertTwoPartBar/OneOfSeveralInt/OneOfSeveralStates/BitMapInverseWipe(Scalar)/SegmentArc270/ SeekVoltageGraph.

⚠ THE BLOCKER — core MechSubsystem layout (Phase 2)

The Make reads subsystem[0x1dc] (auxiliary-screen position, INT 1..12; 0/miss → the panel is skipped with "Auxiliary screen position = zero"), [0x1e4] (display label), [0x1e0] (damage-zone index), [0x224] (a name). Our reconstructed MechSubsystem ends at 0x114 — these are unreconstructed base fields not populated from the resource parse. So the panels render NOTHING until the core subsystem layout is extended + populated (from MechSubsystem::CreateStreamedSubsystem / the resource ctor FUN_004ac644) — a change to the WORKING combat/heat subsystem code (regression risk per the §10 systemic rules). The bytes exist in the allocation (subclass layouts are locked to binary offsets, so there is padding between 0x114 and the subclass fields); the task is to CARVE named fields out of that region + wire the resource parse, verifying combat/heat un-regressed. This is the final, sign-off-gated step.

Phasing

  • Phase 1 (SAFE, gated): reconstruct the cluster family + the 4 sub-gauge classes + connections + the Make dispatcher + register in BTL4MethodDescription[]. Gauges only build under BT_DEV_GAUGES/pod; with [0x1dc] unpopulated the Make hits its skip path, so combat is untouched. Buildable + committable.
  • Phase 2 (core-layout, sign-off): extend + populate MechSubsystem[0x1dc/0x1e0/0x1e4/0x224] from the resource → the panels render live on mfd1/mfd2/mfd3. Verify combat/heat un-regressed.