Root cause -- NOT issue #2 (the experience mis-seed pins everyone to VETERAN,
which PASSES the novice lockout; the gates were never the blocker). Two
unwired handlers, the same silent-swallow pattern ToggleCooling had
(Receiver::Receive -> NullHandler for an unregistered id):
- Myomers (handler table @0x51158C {9,"ToggleSeekVoltage"@004b8a48}): the
reconstruction had an EMPTY stub (the address falls in the untagged decomp
gap 0x4b8837..0x4b8a8c) and never registered the id. Body reconstructed
faithfully from the raw disassembly (tools/disas2.py 0x4b8a48): novice
lockout (@4ac9c8) -> heat-model gate (@4ad7d4) -> press-only (msg+0xc>0) ->
currentSeekVoltageIndex@0x320 = (idx+1) % (maxSeekVoltageIndex@0x32C + 1).
Modulo from zero, min index not consulted, no ResetFiringState.
- Emitter (energy-weapon table @0x511DB8 {0xb,"ToggleSeekVoltage"@004ba478}):
the faithful body already existed as the orphaned "AdvanceSeekVoltage"
(ZERO callers) -- converted to the registered message handler (same guards;
cycle + ResetFiringState when not wrapping, disasm re-verified). PPC and
GaussRifle copy-inherit the set; the ammo branch keeps its own id 0xb ==
EjectAmmo (still unwired, tracked separately).
Both registered via MESSAGE_ENTRY on top of the inherited chains. Permanent
diagnostics: [seek] gear log (BT_SEEK_LOG) + BT_SEEKTEST scripted dispatch
harness (mech4.cpp, the cooltoggle pattern).
VERIFIED headless: [seek] Myomers -> 3,0,1,2,3... and [seek] PPC_1 ->
3,0,1,2,3... (modulo-4 wrap, matching the binary's arithmetic). Awaiting
the tester's live ADV-mode confirmation for the real MFD button route (the
routing layer is the same streamed-EventMapping path ToggleCooling verified).
KB: decomp-reference.md message-table rows marked wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>