From 62513b2f8aef99dc78604246788773ec55259e8a Mon Sep 17 00:00:00 2001 From: arcattack Date: Sat, 25 Jul 2026 10:15:25 -0500 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg --- game/reconstructed/powersub.cpp | 99 +++++++++++++++++++++++++++++++-- game/reconstructed/powersub.hpp | 22 ++++++++ 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/game/reconstructed/powersub.cpp b/game/reconstructed/powersub.cpp index 258976b..ffa1b97 100644 --- a/game/reconstructed/powersub.cpp +++ b/game/reconstructed/powersub.cpp @@ -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 *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) diff --git a/game/reconstructed/powersub.hpp b/game/reconstructed/powersub.hpp index 2822dd7..42ccaae 100644 --- a/game/reconstructed/powersub.hpp +++ b/game/reconstructed/powersub.hpp @@ -480,6 +480,28 @@ class Generator; return (ownZone != 0) ? (Scalar)ownZone->damageLevel : (Scalar)0.0f; // +0x158 } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Message handling (Gitea #53 -- the Generator A-D ON/OFF buttons). + // + // Generator had NO handler set of its own, so every message aimed at it hit a + // default-constructed blackhole and was dropped in silence. Proved live with + // the new unhandled-message trace by pressing button 0x1A: + // [msg] UNHANDLED: Generator has no handler for message id 4 + // + // The set MUST chain HeatSink, not PoweredSubsystem: on PoweredSubsystem id 4 + // is `SelectGeneratorA` (a completely different function -- the weapon-side + // generator SELECTION that already works), so chaining there would make these + // four buttons do the WRONG thing rather than nothing. HeatSink owns id 3 + // (ToggleCooling), leaving id 4 free. + // + public: + enum { ToggleGeneratorOnOffMessageID = 4 }; // table @0050fb90, fn @004b1ed0 + + static Receiver::MessageHandlerSet& GetMessageHandlers(); + + void + ToggleGeneratorOnOffMessageHandler(ReceiverDataMessageOf *message); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data. //