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>
150 lines
12 KiB
Markdown
150 lines
12 KiB
Markdown
# `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)
|
||
```
|
||
`mfdPort` → `renderer->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 <name>". 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.
|