Cockpit: GENERATOR POWER ROUTING -- assign weapons to generators (task #12)

The pilot's second heat-management tool: the PoweredSubsystem message table
@0x50F4EC (ids 4-8) registered and implemented -- SelectGeneratorA-D
(@004b099c..@004b0a74: FindGeneratorByNumber roster walk on generatorNumber
@0x1E0 -> AttachToVoltageSource re-tap with tap accounting -> modeAlarm
Connected) and ToggleGeneratorMode (@004b0abc: Manual -> Auto -> detach+Manual
cycle).  Weapons inherit via the MechWeapon handler chain.  Desktop: F5-F8
assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
reconnect mode; BT_GENSEL_TEST scripts a headless verify.

Verified live end-to-end: dispatch -> handler -> re-tap ("PPC_1 ->
GeneratorD (tapped)") -> the charging I^2R physically moved (GenD cold ~90K
baseline -> ~1570K carrying the PPC; GenA relieved), stable over a sustained
autofire soak with thermal-breaker trips.

FOUR defects found and fixed on the way [all T1/T2]:
- THE e17 HEAT EXPLOSION: Generator::SourceLevel misread *(this[0x38]+0x158)
  as linkedSinks->heatEnergy; it is the engine-base DamageZone @0xE0 ->
  damageLevel [0..1] (the same named-member pattern as the bank radiator's
  zone read).  A breaker-restarting generator emitted (1 - 4e8) x 10000
  volts; squared through the customers' I^2R feed, one restart blew the
  whole thermal network to e17.  Authentic: a damaged generator yields
  proportionally less voltage.
- FUN_004ac9c8 is NOT "IsDamaged": raw body = owner -> mech+0x190 player ->
  roleClassIndex(+0x274) == 0 -- the ROOKIE-role lockout for advanced
  cockpit systems.  New bridge BTPlayerRoleLocksAdvanced (NULL player =
  unlocked [T3]; bring-up role 2 = unlocked).  The old stand-in gated the
  handlers off permanently (healthy subsystems have simulationState==1).
- MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the accessor:
  as namespace-scope arrays they are read by other TUs' static-init chains
  before their own initializers run -- Build copies zeros and every id in
  the table is silently dropped (ids 9/10 only worked by TU-order luck;
  both tables relocated; gotcha recorded, reconstruction-gotchas #9).
- The AutoConnect hunt scanned GetSegment() -- the raw @004b0bd0 walks the
  subsystem ROSTER (+0x124/+0x128); EntitySegments were being cast to
  Subsystems (the classic +0x128 gotcha).

Also: the dense handler table's GAP slots (skipped ids) are uninitialized
heap -- the name-based Find strcmp-walks them and AVs; diagnostic probes use
the id-based Find only (the 1995 binary's own tables carry the same holes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 23:16:48 -05:00
co-authored by Claude Fable 5
parent 092408041c
commit d7b900d108
9 changed files with 355 additions and 29 deletions
+23 -2
View File
@@ -175,8 +175,29 @@ authentic path scoped.
the only MechWeapon attribute" claim was wrong (fixed in mechweap.hpp).
* Tails: weapon+0x110 (write 0/-1 on session open/close; port name vitalSubsystemIndex)
has NO confirmed reader yet; @004d1acc (+0x48 L4 Notify override) is unexported —
re-export from Ghidra; the PoweredSubsystem generator-select handlers (ids 4-8,
@004b099c..@004b0abc) are decoded but not yet registered in the port.
re-export from Ghidra.
* **Task #12 (2026-07-11) [T2]: the PoweredSubsystem POWER-ROUTING handlers landed** --
ids 4-8 registered (table @0x50F4EC): SelectGeneratorA-D = FindGeneratorByNumber
(@004b0b18 ROSTER walk matching generatorNumber@0x1E0) -> AttachToVoltageSource ->
modeAlarm=Connected; ToggleGeneratorMode (@004b0abc) = <2 -> AutoConnect, ==2 ->
Detach + ManualConnect. Weapons inherit via the MechWeapon handler chain (the old
qualified GetMessageHandlers() calls silently resolved to the engine Receiver ROOT
set -- same trap as the task #6 weapon handlers). The AutoConnect hunt's
GetSegment() scan corrected to the subsystem ROSTER (the +0x128 gotcha). Desktop:
F5-F8 assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
Manual/Auto; BT_GENSEL_TEST scripts a headless re-tap. Note: Myomers/Sensor still
chain the root set (their binary tables unverified) -- extend if aux maps prove
they receive these ids. TWO MORE TRAPS found landing this: (a) MESSAGE_ENTRY
tables must be function-local statics INSIDE the accessor (cross-TU static-init
order emptied the table -- see [[reconstruction-gotchas]] #9 last bullet); (b)
**FUN_004ac9c8 is NOT IsDamaged** -- raw body: `owner(+0xD0) -> mech+0x190 player
-> roleClassIndex(+0x274) == 0` = the ROOKIE-role lockout for advanced cockpit
systems (port bridge BTPlayerRoleLocksAdvanced, btplayer.cpp; NULL player =
unlocked [T3]; bring-up role defaults to 2 = unlocked). AUDIT TAIL: the other
powersub.cpp sites annotated FUN_004ac9c8 (the coolant-draw gate ~:427,
ForceShortRecovery ~:444, the Generator/PowerWatcher site ~:1085) still call the
heat-family IsDamaged (simulationState != 0) stand-in -- each needs its raw fn
re-checked and swapped to the role bridge where the binary calls 4ac9c8.
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
+12
View File
@@ -160,6 +160,18 @@ real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a fact
Receiver::NextMessageID` early branch does NOT save you — it lacks a `return`, and the console IDs
aren't in that range anyway.) [T2]
- **MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the GetMessageHandlers()
accessor** (task #12). A namespace-scope `HandlerEntry MessageHandlerEntries[]` can be read
by ANOTHER TU's static-init chain (DefaultData -> accessor -> Build) before its own TU's
dynamic initializers run -- Build copies ZEROS, and every id in that table is silently
dropped at dispatch (the set LOOKS built; ids added later in the chain still work, which
hides it). Symptom: message transmitted, handler never runs, no error. The engine's own
APP.cpp idiom (table + set both function-local in the accessor) is init-order-proof --
always use it. Related trap: the dense handler table (Build indexes slots by id-1) leaves
GAP slots (skipped ids) as uninitialized heap -- the NAME-based `Find(const char*)`
strcmp-walks every slot and AVs on a gap's garbage entryName (the id-based Find is safe).
The 1995 binary's own tables carry the same holes. [T2]
## 10. Container-Execute must override (gauges)
The 2007 engine `Gauge::Execute` base is `Fail("not overridden")``abort()` (GAUGE.cpp:598);