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>
160 lines
13 KiB
Markdown
160 lines
13 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 mapping state from
|
||
`app->GetControlsManager()` (`DAT_004efc94+0x3c` == the LBE4ControlsManager — NOT the ModeManager,
|
||
which is `+0x50`): `buttonGroup[btn]` (array base `+0x1c0`, stride `0x20`) `->GetMapState(*(subsys+0xE8),
|
||
subsys, *(subsys+0xEC), 0x10000)` [T0 CONTROLS.h `ControlsUpdateManager<T>::GetMapState`, returns
|
||
0 unmapped / 1 mappedByOthers / 2 mappedByMe / 3 both == the cm_off/other/only/both frame index].
|
||
The 4-slot table `DAT_00518eb4..d0` is RECOVERED from the image ({y,button} pairs, blit x=0xc):
|
||
{0x0d,0x45 Pinky} {0x25,0x46 ThumbLow} {0x3d,0x40 Trigger} {0x55,0x47 ThumbHigh} [T1 PE bytes].
|
||
SetColor `0x4c6ee0` (writes `this+0x94`; Execute is inert while 0 — the activation caller is
|
||
unrecovered in the export). 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** — ✅ RECONSTRUCTED (task #6): the state loop is
|
||
live in btl4gau2.cpp (recovered `DAT_00518eb4` table; `buttonGroup[btn].GetMapState(...,0x10000)` via
|
||
the complete-type bridge `BTSubsystemControlFeed`; `MechSubsystem` +0xE8/+0xEC fixed to
|
||
controlDestination/controlMessageID, MechWeapon ctor defaults the destination to `&fireImpulse`).
|
||
The gauge stays authentically DORMANT (the shipped binary never calls SetColor `0x4c6ee0` — no caller
|
||
exists); `BT_CONFIGMAP=1` is the port's dev enable.
|
||
- 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.
|