Files
BT411/docs/VEHICLE_SUBSYSTEMS.md
T
arcattackandClaude Fable 5 092408041c Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
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>
2026-07-11 19:39:35 -05:00

13 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 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.