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
+25 -12
View File
@@ -155,8 +155,18 @@ Derivation
Receiver::MessageHandlerSet&
Emitter::GetMessageHandlers()
{
// issue #19 (2026-07-21): register the energy-weapon handler table
// @0x511DB8 {0xb, "ToggleSeekVoltage" -> @004ba478} on top of MechWeapon's
// ids 9/10 -- the id was unregistered, so the ADV-mode weapon seek press
// was silently swallowed. (PPC/GaussRifle copy-inherit this set; the
// ammo-weapon branch binds 0xb to EjectAmmo in ITS OWN table instead.)
static const Receiver::HandlerEntry entries[]=
{
MESSAGE_ENTRY(Emitter, ToggleSeekVoltage) // id 0xb @004ba478
};
static Receiver::MessageHandlerSet messageHandlers(
MechWeapon::GetMessageHandlers()); // copy-inherit
ELEMENTS(entries), entries,
MechWeapon::GetMessageHandlers()); // copy-inherit (ids 9/10)
return messageHandlers;
}
//
@@ -645,21 +655,23 @@ void
}
//
// @004ba478 -- advance the seek-voltage index, wrapping modulo (count+1). If
// the index wrapped past the recommended one, re-arm via ResetFiringState.
// Reached only when the heat-state query is clear and the message has payload.
// (best-effort -- exact role of param +0xc unconfirmed.)
// @004ba478 -- ToggleSeekVoltage (energy-weapon handler table @0x511DB8, id
// 0xb). Advance the seek-voltage index, wrapping modulo (count+1); when not
// wrapping, re-arm via ResetFiringState. Guards (disasm-verified): novice
// lockout (@4ac9c8) -> heat-model gate (@4ad7d4) -> press only (msg+0xc > 0).
// Issue #19 (2026-07-21): converted from the orphaned "AdvanceSeekVoltage"
// (no callers) into the registered message handler.
//
int
Emitter::AdvanceSeekVoltage(UpdateRecord *message)
void
Emitter::ToggleSeekVoltageMessageHandler(ReceiverDataMessageOf<int> *message)
{
if (MechSubsystem::IsDamaged()) // FUN_004ac9c8 -- NOVICE lockout (seek-voltage cycling is an advanced cockpit control; issue #2)
if (MechSubsystem::IsDamaged()) // FUN_004ac9c8 -- NOVICE lockout bridge
{
return /*non-zero status*/ 1;
return;
}
if (!HeatModelActive() || message->payloadCount <= 0) // FUN_004ad7d4 -- heat-model gate; message+0xc
if (!HeatModelActive() || message->dataContents <= 0) // FUN_004ad7d4 -- heat-model gate; msg+0xc
{
return 0;
return;
}
int prev = seekVoltageIndex; // 0x3f0
@@ -670,7 +682,8 @@ int
{
ResetFiringState(); // @004ba9a8
}
return next;
if (getenv("BT_SEEK_LOG"))
DEBUG_STREAM << "[seek] " << GetName() << " -> gear " << next << std::endl;
}