Files
BT412/docs/VEHICLE_SUBSYSTEMS.md
T
arcattackandClaude Opus 4.8 f4ad0f4340 docs: vehicleSubSystems DONE + render-verified
Mark the engineering-screen cluster-panel system complete in the spec + CLAUDE.md
§10.  Render-verified (BT_DEV_GAUGES_DOCK): 7 authentic panels build + draw --
SENSOR CLUSTER, MYOMERS, ER MED LASER RANGE 500M x3, PPC RANGE 500M x2 -- with bar
gauges, recharge dials, and lamps; 0 crashes, combat un-regressed.  Records that
the aux-screen blocker resolved cleanly via the PoweredSubsystem bridge (the fields
were already populated; no MechSubsystem core re-base was needed) and lists the
non-blocking follow-ups.

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

11 KiB
Raw Blame History

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

Status: DONE — reconstructed, registered, and RENDERING (Phase 1 + Phase 2 complete). The whole engineering-screen (MFD) subsystem-panel system is live: each subsystem builds its authentic cluster panel on its real aux-screen position. Render-verified (docked dev-gauge composite) — the panels show SENSOR CLUSTER (HeatSinkCluster), MYOMERS (MyomerCluster), ER MED LASER · RANGE 500M ×3 + PPC · RANGE 500M ×2 (Energy/Ballistic weapon clusters) with bar gauges, recharge dials, lamps, and the coolant/heat panel. 0 crashes, combat un-regressed. Full decomp captured in the session scratchpad (vss_full.txt/vss_sub.txt/vss_kids.txt, Ghidra headless of BTL4OPT.EXE). Remaining polish noted at the bottom.

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 — BOTH DONE

  • Phase 1 (DONE): reconstructed the cluster family (base SubsystemCluster + 5 subclasses) + the 4 sub-gauge classes (AnimatedSubsystemLamp/AnimatedSourceLamp/ScalarBarGauge/ConfigMapGauge) + the 3 connections + the Make dispatcher + registered in BTL4MethodDescription[]. Plus 4 previously declared-but-undefined base gauges the clusters build (HorizTwoPartBar, OneOfSeveralInt, OneOfSeveralStates, BitMapInverseWipe/LeakGauge) + StateConnection + SeekVoltageGraph's virtuals — their /FORCE-stubbed ctors were segfaulting when a cluster built them.
  • Phase 2 (DONE — NOT the feared core-layout re-base): the blocker resolved cleanly. The aux-screen assignment turned out to be PoweredSubsystem fields (auxScreenNumber/auxScreenPlacement/ auxScreenLabel, resource +0x104/+0x108/+0x10C) that our ctor already populated — the raw sub[0x1dc] reads were garbage only because the reconstructed heat-leaf branch isn't byte-exact. Fix = a bridge BTGetSubsystemAuxScreen() (powersub.cpp) that casts through the real PoweredSubsystem type + returns the NAMED fields (and is the FUN_0041a1a4/0x50f4bc type filter); the Make + SubsystemCluster read via the bridge, not raw offsets. No MechSubsystem re-base was needed. Render-verified: 7 authentic panels build with real aux-screen positions/labels/placements, 0 crashes, combat un-regressed.

Remaining polish (non-blocking follow-ups)

  • Two minor cluster children deferred as marked NULLs: the SegmentArc270 recharge dial (WeaponCluster) + the BitMapInverseWipeScalar eject wipe (BallisticWeaponCluster) — their ctors are placeholders in btl4gaug.hpp (the SegmentArc270 @004c6244 body + a BitMapInverseWipeScalar class need reconstruction).
  • SeekVoltageGraph::Execute + BitMapInverseWipe::Execute are minimal-safe (vtable-complete, non-crashing) — the full seek-voltage plot + the reveal-wipe column math are deferred.
  • Many cluster child value pointers read raw subsystem offsets (0x31c/0x324/0x334/0x364/0x43c/…) still on the non-byte-exact heat-leaf branch — marked BEST-EFFORT; they render but may show wrong/zero data. The authentic fix is the heat-leaf-branch re-base (the same §10 deferred item for the Sensor RadarPercent gauge) or per-field bridges like the aux-screen one.
  • ConfigMapGauge's control-mapper state loop is guarded off (raw App ModeManager offsets, DAT_00518eb8 unrecovered).
  • The separate dev-gauge window (960×384) didn't appear in the headless test run (the panels render in BT_DEV_GAUGES_DOCK=1 docked mode); the separate-window path is existing dev-gauge infra, unrelated to this reconstruction.