Files
BT411/docs/VEHICLE_SUBSYSTEMS.md
T
arcattackandClaude Opus 4.8 8a31abb07d docs: vehicleSubSystems follow-ups -- recharge dial + leak wipe done
Mark SegmentArc270 (recharge dial) and BitMapInverseWipe::Execute (LeakGauge) done;
refine the remaining follow-up list with the honest blocker/scope for each (seek
plot blocked on attribute + sampler; eject wipe needs a new base; secondary lamp/
counter raw reads = the heat-leaf-branch re-base; ConfigMapGauge overlay needs the
ModeManager).

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

12 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)

  • SegmentArc270 recharge dial DONE — reconstructed (@004c6244, engine SegmentArc + a Scalar connection + the 0.75 span factor) + wired into WeaponCluster, reading the real PercentDone attribute. Render-verified: the green segmented recharge circles draw in the weapon panels.
  • BitMapInverseWipe::Execute (LeakGauge) DONE — the authentic vertical-segment reveal wipe (@004c5d08), driven by the CoolantMassLeakRate attribute.
  • SeekVoltageGraph::Execute (the seek-voltage curve plot in the Energy/Myomer clusters) — still a minimal-safe no-op. BLOCKED: it needs (a) the four seek-voltage attribute indices resolved (the ctor's FindAttributeIndex still returns -1 — the summarised stub) and (b) the subsystem's live seek-voltage sampler (Emitter vtbl+0x3c) — so even a reconstructed plot would draw nothing until those land.
  • BitMapInverseWipeScalar eject wipe (BallisticWeaponCluster) — still a marked NULL. Needs a new base class (the unnamed @004c5e84 wipe base, vtable 0x518a58) + the @004c61c8 ctor. Very low value (the eject indicator only shows during a reload).
  • Secondary raw value-pointer reads — the fail/jam/fire lamps + ammo counters read raw subsystem offsets (0x31c/0x320/0x324/0x334/0x364/0x43c/…) on the non-byte-exact heat-leaf branch → they render but show wrong/zero data. NOTE: the KEY gauges already read via the engine ATTRIBUTE system and show real data (temperature bars, the recharge dial, generator voltage, coolant leak); only these secondary lamps/counters are affected. The authentic fix is the heat-leaf-branch re-base (the same §10 deferred item for the Sensor RadarPercent gauge) — a large, regression-risky change to the working combat/heat subsystem code — or a set of per-field bridges like the aux-screen one.
  • ConfigMapGauge's control-mapper state overlay — guarded off (raw App ModeManager offsets, DAT_00518eb8 unrecovered); the base joystick bitmap draws.
  • 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.