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>
13 KiB
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 ofBTL4OPT.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)
SubsystemClusterbase @0x4c8140(vt0x51a020) :GraphicGauge— builds ~11 sub-gauges (HorizTwoPartBar temp bar, CoolingLoop ×2, PowerSource ×2, BackgroundBitmap, OneOfSeveralInt modeLamp, GeneratorVoltage/ScalarBarGauge, OneOfSeveralStates stateLamp, VertTwoPartBar ×2, LeakGauge). dtor0x4c87dc, Execute0x4c88e4, draw-bg helper0x4c89c4, BecameActive-draw0x4c8820, TestInstance0x4c8990, ShowInstance0x4c8a28. Reads attributes InputVoltage/HeatSink/CurrentTemperature/ DegradationTemperature/FailureTemperature/CoolantMassLeakRate viaFUN_0041bfc0(=GetAttributeIndexby name, returnsowner + idx - 1or 0).HeatSinkCluster@0x4c8a6c(vt0x519fd4) : SubsystemCluster — adds 4 fail TwoStates (btehfail/btepfail/btesfail) + a NumericDisplayScalar. Execute0x4c8db0scalesthis[0x35]by_DAT_004c8df0(=100.0). dtor0x4c8d18.MyomerCluster@0x4c8df4(vt0x519f88) : SubsystemCluster — adds a SeekVoltageGraph ("EngrGraph", edestryd.pcc) + a seek-step OneOfSeveralInt (bteseek.pcc,subsys+0x800). Execute = base. dtor0x4c8f54.WeaponCluster@0x4c8fc4(vt0x519f38) : SubsystemCluster — adds SegmentArc270 recharge dial (reads "PercentDone") + ConfigMapGauge. Make-time warns "WeaponCluster missing ". Execute0x4c9290toggles a warn lamp whenthis[0x36] > _DAT_004c92e0. DrawWarningLamp0x4c932c, BecameActive0x4c9258, dtor0x4c91d4, slot170x4c92e4.EnergyWeaponCluster@0x4c93b0(vt0x519ee8) : WeaponCluster — adds SeekVoltageGraph (OutputVoltage) + seek-step OneOfSeveralInt (subsys+0x3f0). dtor0x4c94dc.BallisticWeaponCluster@0x4c9558(vt0x519e98) : WeaponCluster — adds 2× NumericDisplayInteger (ammo,link+0x180), jam/fire TwoStates (btejam/btefire), NumericDisplayScalarTwoState, eject BitMapInverseWipeScalar (bteejtm,subsys+0x3f8), a GraphicsViewRecord erase tracker. Execute0x4c9a38tracks weaponAlarm==5 (Jammed,subsys+0x364) + the reload clock. dtor0x4c9940, BecameActive0x4c99cc, TestInstance0x4c9adc, slots0x4c9b24/0x4c9b50.
Sub-gauge classes to reconstruct (btl4gau2, declared not defined)
AnimatedSubsystemLamp(CoolingLoop) @0x4c70a4(vt0x51a174) : OneOfSeveral + connectionFUN_004c3134(vt0x518ea8, subsystem-state driven) at+0x2b. dtor0x4c7134.AnimatedSourceLamp(PowerSource) @0x4c7160(vt0x51a130) : OneOfSeveral + connectionFUN_004c31ec(vt0x518e9c) at+0x2b. dtor0x4c71f0.ScalarBarGauge(GeneratorVoltage) @0x4c721c(vt0x51a0e4) : engine barFUN_00472ef0(VertNormalSlider-ish) + connectionFUN_004c3288(vt0x518e90) at+0x2d. 2nd ctor0x4c72ac(uses the engineFUN_00474855scalar-attr connection). dtor0x4c733c.ConfigMapGauge@0x4c6d80(vt0x51a1b8) : GraphicGauge — 5 pcc names (btjoy/cm_off/cm_other/ cm_only/cm_both) interned into the warehouse; Execute0x4c6f1creads the mapping state fromapp->GetControlsManager()(DAT_004efc94+0x3c== the LBE4ControlsManager — NOT the ModeManager, which is+0x50):buttonGroup[btn](array base+0x1c0, stride0x20)->GetMapState(*(subsys+0xE8), subsys, *(subsys+0xEC), 0x10000)[T0 CONTROLS.hControlsUpdateManager<T>::GetMapState, returns 0 unmapped / 1 mappedByOthers / 2 mappedByMe / 3 both == the cm_off/other/only/both frame index]. The 4-slot tableDAT_00518eb4..d0is 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]. SetColor0x4c6ee0(writesthis+0x94; Execute is inert while 0 — the activation caller is unrecovered in the export). dtor0x4c6e54.
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 inBTL4MethodDescription[]. 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
PoweredSubsystemfields (auxScreenNumber/auxScreenPlacement/auxScreenLabel, resource +0x104/+0x108/+0x10C) that our ctor already populated — the rawsub[0x1dc]reads were garbage only because the reconstructed heat-leaf branch isn't byte-exact. Fix = a bridgeBTGetSubsystemAuxScreen()(powersub.cpp) that casts through the realPoweredSubsystemtype + returns the NAMED fields (and is theFUN_0041a1a4/0x50f4bctype filter); the Make +SubsystemClusterread via the bridge, not raw offsets. NoMechSubsystemre-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)
- ✅
SegmentArc270recharge dial DONE — reconstructed (@004c6244, engine SegmentArc + a Scalar connection + the 0.75 span factor) + wired into WeaponCluster, reading the realPercentDoneattribute. 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 theCoolantMassLeakRateattribute. 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'sFindAttributeIndexstill returns -1 — the summarised stub) and (b) the subsystem's live seek-voltage sampler (Emittervtbl+0x3c) — so even a reconstructed plot would draw nothing until those land.BitMapInverseWipeScalareject 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 (recoveredDAT_00518eb4table;buttonGroup[btn].GetMapState(...,0x10000)via the complete-type bridgeBTSubsystemControlFeed;MechSubsystem+0xE8/+0xEC fixed to controlDestination/controlMessageID, MechWeapon ctor defaults the destination to&fireImpulse). The gauge stays authentically DORMANT (the shipped binary never calls SetColor0x4c6ee0— no caller exists);BT_CONFIGMAP=1is 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=1docked mode); the separate-window path is existing dev-gauge infra, unrelated to this reconstruction.