Issue #19: wire ToggleSeekVoltage (Myomers id 9 + energy weapons id 0xb)

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>
This commit is contained in:
arcattack
2026-07-21 12:57:18 -05:00
co-authored by Claude Opus 4.8
parent c18d2b0213
commit 4aab10ba89
6 changed files with 133 additions and 28 deletions
+33 -8
View File
@@ -87,7 +87,16 @@ Derivation
Receiver::MessageHandlerSet&
Myomers::GetMessageHandlers()
{
// issue #19 (2026-07-21): register the binary's Myomers handler table
// @0x51158C {9, "ToggleSeekVoltage" -> @004b8a48} on top of the inherited
// powered ids -- the id was unregistered, so the ADV-mode seek-gear press
// was silently swallowed (Receiver::Receive NullHandler).
static const Receiver::HandlerEntry entries[]=
{
MESSAGE_ENTRY(Myomers, ToggleSeekVoltage) // id 9 @004b8a48
};
static Receiver::MessageHandlerSet messageHandlers(
ELEMENTS(entries), entries,
PoweredSubsystem::GetMessageHandlers()); // copy-inherit (ids 4-8)
return messageHandlers;
}
@@ -563,16 +572,32 @@ void Myomers::DisconnectFromMover(SubsystemMessage &message)
//***************************************************************************
// Myomers::ToggleSeekVoltage @004b8a48
// Myomers::ToggleSeekVoltageMessageHandler @004b8a48
//***************************************************************************
// Mappable command (attribute id 0x09 "ToggleSeekVoltage", bound in the
// @00511588 table to handler @004b8a48). Cycles currentSeekVoltageIndex
// @0x320 between minSeekVoltageIndex@0x328 and maxSeekVoltageIndex@0x32C.
// BEST-EFFORT: present in the binary but not captured in the decompiled
// shards (the address falls in the untagged gap 0x4b8837..0x4b8a8c).
void Myomers::ToggleSeekVoltage()
// Handler table @0x51158C: {9, "ToggleSeekVoltage"}. Reconstructed FAITHFULLY
// from the raw disassembly (tools/disas2.py 0x4b8a48; the decomp shards missed
// the untagged gap 0x4b8837..0x4b8a8c) -- issue #19 fix, 2026-07-21:
// 0x4b8a50 call 0x4ac9c8 novice lockout (BTPlayerRoleLocksAdvanced) -> ret
// 0x4b8a5b call 0x4ad7d4 heat-model experience gate -> ret if OFF
// 0x4b8a68 msg+0xc <= 0 press only (release is negative)
// 0x4b8a6f idx@0x320 = (idx + 1) % (maxSeekVoltageIndex@0x32C + 1)
// Note: modulo (max+1) from ZERO -- minSeekVoltageIndex is NOT consulted, and
// there is no ResetFiringState (both unlike the Emitter sibling @004ba478).
void Myomers::ToggleSeekVoltageMessageHandler(ReceiverDataMessageOf<int> *message)
{
// cycle currentSeekVoltageIndex within [minSeekVoltageIndex, maxSeekVoltageIndex]
if (IsDamaged()) // FUN_004ac9c8 -- NOVICE lockout bridge
{
return;
}
if (!HeatModelActive() || message->dataContents <= 0) // FUN_004ad7d4; msg+0xc
{
return;
}
currentSeekVoltageIndex = // @0x320
(currentSeekVoltageIndex + 1) % (maxSeekVoltageIndex + 1); // @0x32C
if (getenv("BT_SEEK_LOG"))
DEBUG_STREAM << "[seek] " << GetName() << " -> gear "
<< currentSeekVoltageIndex << std::endl;
}