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:
co-authored by
Claude Opus 4.8
parent
c18d2b0213
commit
4aab10ba89
@@ -268,7 +268,12 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
MessageHandlers set is default-constructed EMPTY — unwired.)
|
||||
- **Myomers-class** @0x51158C: {9, "ToggleSeekVoltage"→@004b8a48}; **energy-weapon** variant
|
||||
@0x511DB8: {0xb, "ToggleSeekVoltage"→@004ba478}; **ammo-weapon** @0x512210: {0xb,
|
||||
"EjectAmmo"→@004bb9b8}.
|
||||
"EjectAmmo"→@004bb9b8}. **Both ToggleSeekVoltage handlers WIRED 2026-07-21 (issue #19)**:
|
||||
Myomers' was an empty stub (body recovered by raw disasm of the untagged gap: guards @4ac9c8
|
||||
novice + @4ad7d4 heat-model + press-only, then idx@0x320=(idx+1)%(max@0x32C+1), NO
|
||||
ResetFiringState); Emitter's faithful body existed as the orphaned "AdvanceSeekVoltage" (zero
|
||||
callers) -- both registered via MESSAGE_ENTRY (ids 9/0xb). Verify: BT_SEEKTEST=1 +
|
||||
BT_SEEK_LOG=1 → `[seek] <name> -> gear N` cycling. EjectAmmo remains unwired.
|
||||
- **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the
|
||||
RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress,
|
||||
unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -294,10 +294,20 @@ class NotationFile;
|
||||
void
|
||||
ContinueDischarge();
|
||||
|
||||
// @004ba478 -- advance the seekVoltage index, wrapping modulo the count;
|
||||
// re-arm if it wrapped. (best-effort name)
|
||||
int
|
||||
AdvanceSeekVoltage(UpdateRecord *message);
|
||||
public:
|
||||
// @004ba478 -- "ToggleSeekVoltage": the ENERGY-WEAPON handler table
|
||||
// @0x511DB8 {0xb, "ToggleSeekVoltage"}. Issue #19 (2026-07-21): the
|
||||
// faithful body existed (as AdvanceSeekVoltage) but the id was never
|
||||
// registered, so the ADV-mode weapon seek-gear press was silently
|
||||
// swallowed. Now the registered message handler; cycles
|
||||
// seekVoltageIndex@0x3F0 modulo (maxSeekVoltageIndex@0x3FC + 1) and
|
||||
// re-arms (ResetFiringState) when not wrapping. (The AMMO-weapon
|
||||
// table @0x512210 binds id 0xb to EjectAmmo instead -- ProjectileWeapon
|
||||
// branch, unaffected.)
|
||||
enum { ToggleSeekVoltageMessageID = 0x0b };
|
||||
void
|
||||
ToggleSeekVoltageMessageHandler(ReceiverDataMessageOf<int> *message); // @004ba478
|
||||
protected:
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support (EMITTER.TCP)
|
||||
|
||||
@@ -5613,6 +5613,47 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// issue #19 verify (BT_SEEKTEST): dispatch the seek-gear messages every
|
||||
// ~2s -- ToggleSeekVoltage id 9 to Myomers (0xBC6) and id 0xb to the
|
||||
// first ENERGY weapon (Emitter 0xBC8 / GaussRifle 0xBCE / PPC 0xBD4;
|
||||
// NOT the ammo branch, whose id 0xb is EjectAmmo). BT_SEEK_LOG shows
|
||||
// the gear cycling.
|
||||
if (getenv("BT_SEEKTEST") && (Entity *)this == application->GetViewpointEntity())
|
||||
{
|
||||
static int s_seekTest = 0;
|
||||
if ((++s_seekTest % 120) == 0)
|
||||
{
|
||||
int sentMyo = 0, sentWpn = 0;
|
||||
for (int s = 1; s < GetSubsystemCount(); ++s)
|
||||
{
|
||||
Subsystem *sub = GetSubsystem(s);
|
||||
if (sub == 0)
|
||||
continue;
|
||||
int cid = (int)sub->GetClassID();
|
||||
if (!sentMyo && cid == 0xBC6)
|
||||
{
|
||||
ReceiverDataMessageOf<ControlsButton> msg(
|
||||
9 /*Myomers::ToggleSeekVoltageMessageID*/,
|
||||
sizeof(ReceiverDataMessageOf<ControlsButton>),
|
||||
(ControlsButton)1 /*press*/);
|
||||
sub->Dispatch(&msg);
|
||||
sentMyo = 1;
|
||||
}
|
||||
else if (!sentWpn && (cid == 0xBC8 || cid == 0xBCE || cid == 0xBD4))
|
||||
{
|
||||
ReceiverDataMessageOf<ControlsButton> msg(
|
||||
0x0b /*Emitter::ToggleSeekVoltageMessageID*/,
|
||||
sizeof(ReceiverDataMessageOf<ControlsButton>),
|
||||
(ControlsButton)1 /*press*/);
|
||||
sub->Dispatch(&msg);
|
||||
sentWpn = 1;
|
||||
}
|
||||
if (sentMyo && sentWpn)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// task #6 dev harness: the config-session BRACKET. 'G' edge ->
|
||||
// dispatch ConfigureMappables (id 9) press/release to the selected
|
||||
// weapon -- the exact ReceiverDataMessageOf<ControlsButton> payload
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -271,10 +271,21 @@ class Mech;
|
||||
void
|
||||
DisconnectFromMover(SubsystemMessage &message); // @004b95b8 (detach SpeedEffect feed)
|
||||
|
||||
// ToggleSeekVoltage command handler @004b8a48 (cycles currentSeekVoltageIndex;
|
||||
// present in the binary, not captured in the decompiled shards -- best-effort).
|
||||
public:
|
||||
// ToggleSeekVoltage message handler @004b8a48 (Myomers handler table
|
||||
// @0x51158C: {9, "ToggleSeekVoltage"}). Issue #19: was an EMPTY stub +
|
||||
// never registered, so the ADV-mode seek-gear button was silently
|
||||
// swallowed (Receiver::Receive NullHandler). Body reconstructed from
|
||||
// the raw disassembly (tools/disas2.py 0x4b8a48 -- the address falls in
|
||||
// the untagged decomp gap 0x4b8837..0x4b8a8c):
|
||||
// novice lockout (@4ac9c8) -> heat-model gate (@4ad7d4) -> press only
|
||||
// (msg+0xc > 0) -> currentSeekVoltageIndex@0x320 =
|
||||
// (idx + 1) % (maxSeekVoltageIndex@0x32C + 1). No ResetFiringState
|
||||
// (unlike the Emitter sibling @004ba478).
|
||||
enum { ToggleSeekVoltageMessageID = 9 };
|
||||
void
|
||||
ToggleSeekVoltage(); // @004b8a48 (best-effort)
|
||||
ToggleSeekVoltageMessageHandler(ReceiverDataMessageOf<int> *message); // @004b8a48
|
||||
protected:
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CROSS-FAMILY compile shims (owning Mech / JointedMover roster / DamageZone).
|
||||
|
||||
Reference in New Issue
Block a user