Gitea #53: generators can be switched off -- ToggleGeneratorOnOff reconstructed from @004b1ed0

TWO missing pieces, both now in place.

1. Generator had NO handler set of its own, so every message aimed at it hit a
   default-constructed blackhole.  Proved empirically with the new
   unhandled-message trace by pressing button 0x1A:
     [msg] UNHANDLED: Generator has no handler for message id 4 -- silently dropped
   Added Generator::GetMessageHandlers() chained to **HeatSink**, deliberately NOT
   PoweredSubsystem: id 4 there is SelectGeneratorA (the weapon-side generator
   SELECTION that already works), so chaining to it would have made these four
   buttons invoke the WRONG function instead of doing nothing.  HeatSink owns id 3
   (ToggleCooling), leaving id 4 free.

2. ToggleGeneratorOnOff had no body at all (0 references in game/).  Transcribed
   instruction-for-instruction from @004b1ed0:
     - call @004ac9c8 -> BTPlayerRoleLocksAdvanced: a NOVICE pilot cannot use it
     - [msg+0xc] <= 0 -> press only, ignore the release
     - ON  : startTimer = 0; if heatAlarm(@0x184) < 1 also stateAlarm -> 0
             (Starting, so it spins up -- a heat-faulted generator keeps its fault
             level); generatorOn = 1, coolantAvailable = 1, coolantFlowScale = 1.0
     - OFF : stateAlarm -> 1 (GeneratorIdle); generatorOn = 0,
             coolantAvailable = 0, coolantFlowScale = 0 (its coolant share is
             released back to the loop)
   So taking a generator off line idles it, drops its coolant draw and stops it
   feeding its taps -- the authentic heat-management trade from the manual.

VERIFIED LIVE (BT_BTNTEST=0x1A,400,900 through the real click seam):
  [btntest] PRESS 0x1a at poll 400
  [gensel] GeneratorA generator OFF LINE (stateAlarm 1, coolantFlowScale 0)
and the UNHANDLED warning for Generator is gone.

Answers Cyd's report from two playtests ('still cannot turn of gennies').  Note
this is the ON/OFF control -- generator SELECTION (which generator a subsystem
draws from) is a different message and already worked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
arcattack
2026-07-25 10:15:25 -05:00
co-authored by Claude Opus 5
parent 33ca99eb69
commit 62513b2f8a
2 changed files with 116 additions and 5 deletions
+94 -5
View File
@@ -1085,11 +1085,100 @@ Logical Generator::TestClass(Mech &) { return True; }
Logical Generator::TestInstance() const { return IsDerivedFrom(*GetClassDerivations()); }
//
// Generator vtable slot 10 -- recovered VERBATIM from the surviving GNRATOR.TCP
// fragment (the one Generator method the @0x4b02f0.. cluster decomp omitted; see
// gnrator.cpp). Chains the HeatableSubsystem reset (NOT the HeatSink one --
// matching the inlined base chain seen across the HeatSink family) and forces
// the generator output cold.
Receiver::MessageHandlerSet&
Generator::GetMessageHandlers()
{
//
// Gitea #53. Chains **HeatSink**, deliberately NOT PoweredSubsystem: id 4 on
// PoweredSubsystem is `SelectGeneratorA` (the weapon-side generator SELECTION,
// which already works), so chaining there would make the four ON/OFF buttons
// invoke the wrong function instead of doing nothing. HeatSink owns id 3
// (ToggleCooling) and chains onward to the root, leaving id 4 free for us.
//
static const Receiver::HandlerEntry entries[]=
{
MESSAGE_ENTRY(Generator, ToggleGeneratorOnOff) // id 4 @004b1ed0
};
static Receiver::MessageHandlerSet messageHandlers(
ELEMENTS(entries), entries,
HeatSink::GetMessageHandlers()
);
return messageHandlers;
}
//
// @004b1ed0 -- ToggleGeneratorOnOff: the pod's Generator A-D ON/OFF buttons (RIO
// addresses 0x1A-0x1D on the radar rail, one per generator). Transcribed
// instruction-for-instruction from the binary; the body did not exist before
// (Gitea #53 -- 0 references in game/).
//
// call 0x4ac9c8(this); if (ret) return; -> BTPlayerRoleLocksAdvanced: a
// NOVICE pilot cannot touch it
// if ([msg+0xc] <= 0) return; -> press only, ignore the release
// if ([this+0x1d4] != 0) goto OFF -> generatorOn -> we are turning it off
//
// ON: eax = [this+0x184] -> heatAlarm level
// if (eax >= 1) { [this+0x1f0] = 0; } -> heat degraded/failed: clear the
// start timer but LEAVE the state
// alarm in its fault level
// else { [this+0x1f0] = 0;
// Set_Alarm_Level(this+0x1fc, 0); }
// -> healthy: also drop the state alarm
// to Starting so it spins up
// [this+0x1d4] = 1 -> generatorOn = 1
// [this+0x134] = 1 -> coolantAvailable = 1
// [this+0x15c] = 1.0f -> coolantFlowScale = 1.0
//
// OFF: Set_Alarm_Level(this+0x1fc, 1) -> stateAlarm = GeneratorIdle
// [this+0x1d4] = 0 -> generatorOn = 0
// [this+0x134] = 0 -> coolantAvailable = 0
// [this+0x15c] = 0.0f -> coolantFlowScale = 0 (its coolant
// share is released to the loop)
//
// So taking a generator off line idles it, drops its coolant draw, and -- via the
// state machine reading generatorOn -- stops it supplying voltage to its taps.
// That is the authentic heat-management trade the manual describes.
//
void
Generator::ToggleGeneratorOnOffMessageHandler(ReceiverDataMessageOf<int> *message)
{
if (BTPlayerRoleLocksAdvanced(owner)) // @004ac9c8 -- NOVICE lock
{
return;
}
if (message->dataContents <= 0) // press only
{
return;
}
if (generatorOn == 0)
{
// ---- turning it ON ----
startTimer = 0.0f; // @0x1F0
if ((unsigned)heatAlarm.GetLevel() < 1) // @0x184 -- healthy
{
stateAlarm.SetLevel(0); // @0x1FC -> Starting (spin up)
}
generatorOn = 1; // @0x1D4
coolantAvailable = 1; // @0x134
coolantFlowScale = 1.0f; // @0x15C
}
else
{
// ---- taking it OFF LINE ----
stateAlarm.SetLevel(1); // @0x1FC -> GeneratorIdle
generatorOn = 0; // @0x1D4
coolantAvailable = 0; // @0x134
coolantFlowScale = 0.0f; // @0x15C
}
if (getenv("BT_FIRE_LOG") || getenv("BT_HEAT_LOG"))
DEBUG_STREAM << "[gensel] " << GetName() << " generator "
<< (generatorOn ? "ON LINE" : "OFF LINE")
<< " (stateAlarm " << stateAlarm.GetLevel()
<< ", coolantFlowScale " << coolantFlowScale << ")" << std::endl;
}
//
void
Generator::ResetToInitialState(Logical powered)