//===========================================================================// // File: powersub.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Powered (electrically driven) subsystems -- voltage routing, // // generators, myomer drive and the low-voltage watchdog // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 ?? Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, module cluster // @0x4b02f0 .. @0x4b2678). The single tagged function is the PoweredSubsystem // constructor @004b0f74 (file=bt/powersub.cpp); the surrounding cluster was // recovered by following the call graph, the class vtables and the // "d:\tesla_bt\bt\POWERSUB.CPP" assertion strings (@0050f72d / @0050f780). // See powersub.cpp for per-method @ADDR evidence. // // Method names and member names follow the embedded string pool // (PoweredSubsystem, Generator, Myomers, PowerWatcher, the *ClassID enum, // "VoltageSource", "ThermalResistivityCoefficient", "RatedVoltage", ...) and // the sibling heat-subsystem source (heat.hpp / heat.cpp); the remainder is // inferred from usage and flagged where uncertain. Field offsets noted in // comments are the byte offsets observed in the decompiled object // (e.g. "@0x1dc" == this[0x77]). // // This file derives from the heat subsystems: a PoweredSubsystem IS-A // HeatSink (it dissipates heat while it consumes power), so the thermal // machinery in heat.hpp/heat.cpp is the immediate base for everything here. // #if !defined(POWERSUB_HPP) # define POWERSUB_HPP #if !defined(HEAT_HPP) # include #endif #if !defined(HEATFAMILY_RESLICE_HPP) # include // HeatWatcher -- PowerWatcher's real base (RESOURCE_AUDIT.md B) #endif //##################### Forward Class Declarations ####################### class Mech; class Generator; //########################################################################### //######################### PoweredSubsystem ############################ //########################################################################### // // Base class for any subsystem that is driven from a voltage source (a // Generator segment elsewhere in the mech). It tracks a startup timer, an // electrical state machine (Starting -> Ready, with NoVoltage / Shorted / // GeneratorOff fault states) and which generator currently powers it. // // vtable @0050f9d8, ctor @004b0f74, dtor @004b115c. // RegisteredClass::PoweredSubsystemClassID == 0x0bc2 (resource +0x20), // streamed model size 0x190 (resource +0x24). // class PoweredSubsystem: public HeatSink { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation *GetClassDerivations(); // @0050f4bc // task #12: the binary's PoweredSubsystem handler table @0x50F4EC -- // {4,"SelectGeneratorA"->@004b099c} .. {8,"ToggleGeneratorMode"->@004b0abc}. // (The previous qualified calls resolved to the inherited engine // Receiver::GetMessageHandlers() root set, so the generator-select // messages had no handlers anywhere.) MechWeapon's set chains this // one, so every weapon inherits the five handlers. The entry table is // a function-local static INSIDE the accessor (static-init order). static Receiver::MessageHandlerSet& GetMessageHandlers(); static SharedData DefaultData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Messaging Support -- the cockpit POWER-ROUTING messages (task #12). // Ids PINNED to the binary table (MechWeapon extends the shared id space // with 9/10, the config-mode session). The aux-panel type-6 EventMappings // deliver these to a weapon: assign it to Generator 1-4, or toggle its // Manual/Auto reconnect mode. // public: enum { SelectGeneratorAMessageID = 4, // @004b099c SelectGeneratorBMessageID = 5, // @004b09e4 SelectGeneratorCMessageID = 6, // @004b0a2c SelectGeneratorDMessageID = 7, // @004b0a74 ToggleGeneratorModeMessageID = 8 // @004b0abc }; // (ControlsButton == int, engine CONTROLS.h; spelled int here so TUs // that include this header without the controls layer still compile.) void SelectGeneratorAMessageHandler(ReceiverDataMessageOf *message), SelectGeneratorBMessageHandler(ReceiverDataMessageOf *message), SelectGeneratorCMessageHandler(ReceiverDataMessageOf *message), SelectGeneratorDMessageHandler(ReceiverDataMessageOf *message), ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf *message); protected: void SelectGenerator(int generator_number); // the shared @004b099c.. body Subsystem * FindGeneratorByNumber(int generator_number); // @004b0b18 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Electrical state machine (electricalStateAlarm @0x264, level @0x278, // reported by PrintState @004b1224). // public: enum ElectricalState { Starting = 0, // powering up; startTimer counting toward startTime NoVoltage = 1, // voltage source missing / not resolvable Shorted = 2, // source state == 3 (shorted) GeneratorOff = 3, // source state == 1 or 4 (off / not ready) Ready = 4 // powered and operating }; // Three-level connection-mode indicator (modeAlarm @0x2b8, level @0x2cc). // Drives the auto-reconnect search in the simulation. enum ConnectMode { ManualConnect = 0, Connected = 1, // ctor default AutoConnect = 2 // scan all GeneratorClassID segments and attach }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support (gauge data-binding wave) // // The engineering subsystem clusters bind PoweredSubsystem/InputVoltage -- // the power-source gate that decides whether the power-lamp / generator- // voltage-bar / state-lamp sub-branch is even constructed (SubsystemCluster // ctor skips it when InputVoltage resolves NULL). Published on top of // HeatSink's dense index; Sensor/Myomers/MechWeapon chain to GetAttributeIndex() // below and inherit it (so InputVoltage + the HeatSink temps reach every // powered/weapon subsystem's cluster). // public: enum { InputVoltageAttributeID = HeatSink::NextAttributeID, NextAttributeID }; private: static const IndexEntry AttributePointers[]; public: static AttributeIndexSet& GetAttributeIndex(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Streamed model resource. // // Extends the HeatableSubsystem/HeatSink resource record. The // power-specific fields begin at +0xFC (see CreateStreamedSubsystem // @004b13ac). // struct PoweredSubsystem__SubsystemResource: public HeatSink::SubsystemResource { int voltageSourceIndex; // +0xFC "VoltageSource" (segment index, +2 bias; -1 == unset) Scalar thermalResistivityCoefficient; // +0x100 "ThermalResistivityCoefficient" int auxScreenNumber; // +0x104 "AuxScreenNumber" int auxScreenPlacement; // +0x108 "AuxScreenPlacement" (-1 == unset) char auxScreenLabel[64]; // +0x10C "AuxScreenLabel" char engScreenLabel[64]; // +0x14C "EngScreenLabel" Scalar startTime; // +0x18C "StartTime" }; typedef PoweredSubsystem__SubsystemResource SubsystemResource; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; // @004b1208 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: PoweredSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @004b0f74 ~PoweredSubsystem(); // @004b115c static int CreateStreamedSubsystem( // @004b13ac NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support // public: typedef void (PoweredSubsystem::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void PoweredSubsystemSimulation(Scalar time_slice); // @004b0bd0 (Performance, PTR @0050f5b0) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (slots on vtable @0050f9d8) // public: void ResetToInitialState(Logical powered = True); // slot 10, @004b0e6c Logical HandleMessage(int message); // slot 9, @004b0efc LWord GetStatusFlags(); // slot 12, @004b0f48 void PrintState(); // slot 13, @004b1224 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Voltage-source linkage helpers // public: int AttachToVoltageSource(Subsystem *source); // @004b0dd8 (returns tap index, -1 on fail) void DetachFromVoltageSource(); // @004b0e30 Logical IsSourceShorted(Subsystem *source = 0); // @004b0b5c // @004b11bc -- on a short event drive the live source's alarm to shorted. void ForceShortRecovery(); // virtual "has usable voltage" query (vtable+0x40). virtual Logical HasVoltage(); // resolve our powering source (used by PowerWatcher). Subsystem* ResolveVoltageSource() { return voltageSource.Resolve(); } // @004b0d50 -- the charge time-scale for the powered charge integrators // (Emitter::TrackSeekVoltage divides the source-voltage gap by this): // voltageScale stretched by (1 + thermalResistivityCoefficient x the // SOURCE generator's temperature rise) -- a hot generator recharges its // weapons slower. Reads voltageScale@0x310 back every tick (task #10: // the old "the binary never reads it back" claim was wrong). Scalar ChargeTimeScale(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data for the powered-subsystem class. // Offsets are byte offsets into the shipped object. // public: SubsystemConnection voltageSource; // @0x1D0 0xC reference to the powering Generator segment int auxScreenNumber; // @0x1DC resource +0x104 int auxScreenPlacement; // @0x1E0 resource +0x108 char auxScreenLabel[64]; // @0x1E4 resource +0x10C char engScreenLabel[64]; // @0x224 resource +0x14C // The binary alarms are 0x54-byte AlarmIndicators (ctor @004b0f74: // FUN_0041b9ec(param_1+0x99,5) @0x264 and (param_1+0xae,3) @0x2B8; level @+0x14 // == 0x278/0x2CC). Now byte-exact via GaugeAlarm54 -- part of the subsystem-tree // alarm unification (the whole PoweredSubsystem subtree byte-exacted together). GaugeAlarm54 electricalStateAlarm; // @0x264 5-level (ElectricalState); level @0x278 -> ends 0x2B8 GaugeAlarm54 modeAlarm; // @0x2B8 3-level (ConnectMode); level @0x2CC -> ends 0x30C Scalar thermalResistivityCoefficient; // @0x30C resource +0x100 Scalar voltageScale; // @0x310 init 1.0f Scalar startTime; // @0x314 resource +0x18C Scalar startTimer; // @0x318 init = startTime, counts up while Starting -> ends 0x31C friend struct PoweredSubsystemLayoutCheck; }; struct PoweredSubsystemLayoutCheck { static_assert(offsetof(PoweredSubsystem, voltageSource) == 0x1D0, "PoweredSubsystem::voltageSource @0x1D0"); static_assert(offsetof(PoweredSubsystem, auxScreenNumber) == 0x1DC, "PoweredSubsystem::auxScreenNumber @0x1DC (res +0x104)"); static_assert(offsetof(PoweredSubsystem, auxScreenLabel) == 0x1E4, "PoweredSubsystem::auxScreenLabel @0x1E4"); static_assert(offsetof(PoweredSubsystem, engScreenLabel) == 0x224, "PoweredSubsystem::engScreenLabel @0x224"); static_assert(offsetof(PoweredSubsystem, electricalStateAlarm) == 0x264, "PoweredSubsystem::electricalStateAlarm @0x264"); static_assert(offsetof(PoweredSubsystem, modeAlarm) == 0x2B8, "PoweredSubsystem::modeAlarm @0x2B8"); static_assert(offsetof(PoweredSubsystem, thermalResistivityCoefficient)== 0x30C, "PoweredSubsystem::thermalResistivityCoefficient @0x30C"); static_assert(sizeof(PoweredSubsystem) == 0x31C, "sizeof(PoweredSubsystem) 0x31C"); }; // (task #14: the "Myomers" class that lived here -- vtable 0050fb0c / ctor // @4b1d18 / classID 0xBC3, members outputVoltage/powered/voltageAvailable -- // was a MISLABEL of **Sensor** (the real reconstruction is sensor.cpp/hpp; // the real Myomers, classID 0xBC6, is myomers.cpp/hpp). Its definitions // ODR-DUPLICATED ?DefaultData@Myomers@@ etc. against the real class, and // /FORCE silently picked the winner by link order -- the real Myomers could // receive this block's SharedData or vice versa. RETIRED whole.) //########################################################################### //############################# Generator ############################## //########################################################################### // // A voltage SOURCE. Sibling of PoweredSubsystem (also a HeatSink, not a // PoweredSubsystem): a generator spins up over StartTime, can be toggled // on/off, and recovers from a short over ShortRecoveryTime. PoweredSubsystem // segments resolve their "VoltageSource" name to one of these. // // vtable @0050fd74, ctor @004b225c, dtor @004b237c, shared data @0050fb50. // RegisteredClass::GeneratorClassID == 0x0bc1 (matched by the auto-connect // scan in PoweredSubsystemSimulation). // class Generator: public HeatSink { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation *GetClassDerivations(); // @0050fb50 region static Derivation ClassDerivations; // @0050f4bc datum (IsDerivedFrom operand) static SharedData DefaultData; void ForceShortRecovery(); // @004b11bc (short-event recovery) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support (gauge data-binding wave) // // The GeneratorCluster (engineering-screen buttons 9-12) binds Generator/ // OutputVoltage for its 0..12000 voltage bar. The temps (CurrentTemperature/ // DegradationTemperature/FailureTemperature/CoolantMassLeakRate) already reach // it via the inherited HeatSink table; OutputVoltage is a real Generator member // (@0x1DC, NOT the Emitter alias). Dense from HeatSink::NextAttributeID. // public: enum { OutputVoltageAttributeID = HeatSink::NextAttributeID, NextAttributeID }; private: static const IndexEntry AttributePointers[]; public: static AttributeIndexSet& GetAttributeIndex(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Generator state machine (stateAlarm @0x1FC, level @0x210, 5 levels). // Names best-effort from the embedded "GeneratorState" / message strings. // public: enum GeneratorState { GeneratorStarting = 0, // spinning up; startTimer -> startTime GeneratorIdle = 1, // off / no output GeneratorReady = 2, // producing RatedVoltage GeneratorShorted = 3, // shorted; shortTimer -> shortRecoveryTime GeneratorRecovered = 4 // TODO: confirm 4th/5th level names }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Streamed model resource (power-source fields begin at +0xFC). // struct Generator__SubsystemResource: public HeatSink::SubsystemResource { Scalar ratedVoltage; // +0xFC "RatedVoltage" int maxTapCount; // +0x100 "MaxTapCount" (max simultaneous loads, -1 == unset) Scalar startTime; // +0x104 "StartTime" Scalar shortRecoveryTime; // +0x108 "ShortRecoveryTime" }; typedef Generator__SubsystemResource SubsystemResource; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: Generator( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @004b225c ~Generator(); // @004b237c static int CreateStreamedSubsystem( // @004b23bc NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation / overrides // public: typedef void (Generator::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void GeneratorSimulation(Scalar time_slice); // @004b1f7c (Performance, PTR @0050fc14) Logical HandleMessage(int message); // slot 9, @004b21d0 void ResetToInitialState(Logical powered = True); // slot 10 -- recovered verbatim from GNRATOR.TCP // Source-state queries used by PoweredSubsystem / PowerWatcher. int GeneratorStateOf() { return stateAlarm.GetLevel(); } // source+0x210 Scalar MeasuredVoltage() { return outputVoltage; } // source+0x1dc Scalar RatedVoltageOf() { return ratedVoltage; } // source+0x1d8 // The generator's "short" state is its stateAlarm level (GeneratorShorted==3) + // outputVoltage (IsSourceShorted @004b0b5c reads 0x210/0x1dc). There is NO // separate shortFlag field on the Generator (it ends at 0x250 == stateAlarm end). // // task #12 CORRECTION (THE e17 HEAT EXPLOSION): the raw read // *(this[0x38]+0x158) is the engine-base DamageZone pointer @0xE0 and // the ZONE's damageLevel [0..1] -- the SAME named-member pattern as // the bank radiator's zone read (heatfamily_reslice.cpp:138), NOT // linkedSinks->heatEnergy. The old misread made a restarting // generator emit (1.0 - heatEnergy~4e8) x 10000 volts; squared // through the customers' I^2R feed, one generator breaker-restart // blew the whole thermal network to e17 (observed live, task #12 // duty run). Authentic: a DAMAGED generator yields proportionally // less voltage -- outputVoltage = (1 - zoneDamage) x ratedVoltage. Scalar SourceLevel() { ::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38) return (ownZone != 0) ? (Scalar)ownZone->damageLevel : (Scalar)0.0f; // +0x158 } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data. // public: Scalar percentVoltageAvailable; // @0x1D0 init 1.0f -- outputVoltage / ratedVoltage (clamped 0..1) int generatorOn; // @0x1D4 init 1 Scalar ratedVoltage; // @0x1D8 resource +0xFC Scalar outputVoltage; // @0x1DC init = ratedVoltage int generatorNumber; // @0x1E0 derived from segment-name suffix ('A' -> 1 ...) int maxTapCount; // @0x1E4 resource +0x100 int currentTapCount; // @0x1E8 init 0 -- attached loads Scalar startTime; // @0x1EC resource +0x104 Scalar startTimer; // @0x1F0 init = startTime Scalar shortRecoveryTime; // @0x1F4 resource +0x108 Scalar shortTimer; // @0x1F8 init = shortRecoveryTime GaugeAlarm54 stateAlarm; // @0x1FC 0x54 alarm, 5-level (GeneratorState); level @0x210 -> ends 0x250 // stateAlarm is the LAST field (ctor @004b225c: FUN_0041b9ec(param_1+0x7f,5) at 0x1FC). // The old shortFlag@0x25C was a phantom: HandleMessage @004b0efc reads *(owner+0x190)+0x25c // (the message-manager's short-event flag), NOT a Generator field -- Generator ends at 0x250. friend struct GeneratorLayoutCheck; }; struct GeneratorLayoutCheck { static_assert(offsetof(Generator, percentVoltageAvailable) == 0x1D0, "Generator::percentVoltageAvailable @0x1D0 (word 0x74)"); static_assert(offsetof(Generator, outputVoltage) == 0x1DC, "Generator::outputVoltage @0x1DC (word 0x77)"); static_assert(offsetof(Generator, stateAlarm) == 0x1FC, "Generator::stateAlarm @0x1FC (word 0x7f)"); static_assert(sizeof(Generator) == 0x250, "sizeof(Generator) 0x250 (factory alloc, stateAlarm is last)"); }; //########################################################################### //############################ PowerWatcher ############################# //########################################################################### // // A lightweight monitor subsystem that watches its voltage source and raises // a "BadVoltage" / self-test alarm when the available voltage drops below // MinVoltagePercent of rated. Used to drive a cockpit warning gauge. // BEST-EFFORT: its base lies just below the captured window (base ctor // @004aeb40, in the heat/sensor cluster); only the power-specific override // bodies were recovered. // // vtable @0050f99c, ctor @004b18a4, dtor @004b1930. // RegisteredClass classID 0x0bd5 (resource +0x20), model size 0xF4. // class PowerWatcher: public HeatWatcher // RESOURCE-LAYOUT FIX (RESOURCE_AUDIT.md root cause B): real // base is HeatWatcher (ctor chains @004aeb40, parse @004aec54); // was approximated as HeatableSubsystem, which sized the resource // at 0xFC and slid minVoltagePercent to 0xFC instead of 0xF0. { public: static Derivation *GetClassDerivations(); // @0050f5cc static SharedData DefaultData; struct PowerWatcher__SubsystemResource: public HeatWatcher::SubsystemResource // HeatWatcher record ends 0xF0 { Scalar minVoltagePercent; // +0xF0 "MinVoltagePercent" }; // ends 0xF4 typedef PowerWatcher__SubsystemResource SubsystemResource; static_assert(offsetof(PowerWatcher__SubsystemResource, minVoltagePercent) == 0xF0, "PowerWatcher minVoltagePercent must be at 0xF0"); static_assert(sizeof(PowerWatcher__SubsystemResource) == 0xF4, "PowerWatcher record must end at 0xF4"); typedef void (PowerWatcher::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } static Logical TestClass(Mech&); Logical TestInstance() const; // @004b1970 PowerWatcher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @004b18a4 ~PowerWatcher(); // @004b1930 static int CreateStreamedSubsystem( // @004b198c NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); Logical HandleMessage(int message); // @004b179c (chains to base @004aea84) void ResetToInitialState(Logical powered); // @004b1804 (slot 10; chains to base @004aea9c) void UpdateWatch(); // @004b181c -- watchdog mirror of the watched // subsystem's electrical state (+ brownout); // the Torso sims call this directly void Simulation(Scalar time_slice); // the registered Performance (PTR @0050f5fc // -> @004b181c): forwards to UpdateWatch() public: // BASE-CHAIN RE-BASE: the shadow watchedLink was DELETED -- the binary has a // SINGLE watchedLink, inherited from HeatWatcher@0x114. PowerWatcher own // fields now follow HeatWatcher (which ends 0x180): minVoltage@0x180, then a // 0x54-byte WatcherGaugeAlarm@0x184 that ends exactly at 0x1D8 = Torso start. Scalar minVoltage; // @0x180 init = MinVoltageScale * resource->minVoltagePercent WatcherGaugeAlarm watchdogAlarm; // @0x184 (0x54) 5-level self-test -> ends 0x1D8 }; // BASE-CHAIN RE-BASE locks: PowerWatcher own = minVoltage@0x180 + watchdogAlarm@0x184; // sizeof==0x1D8 proves the shared Watcher base ends exactly where Torso own-fields // (currentTwist@0x1D8, the gyro cross-link target) begin. static_assert(offsetof(PowerWatcher, minVoltage) == 0x180, "PowerWatcher minVoltage must be at 0x180"); static_assert(offsetof(PowerWatcher, watchdogAlarm)== 0x184, "PowerWatcher watchdogAlarm must be at 0x184"); static_assert(sizeof(PowerWatcher) == 0x1D8, "PowerWatcher must end at 0x1D8 (== Torso currentTwist)"); //########################################################################### //####################### VoltageSourceConnection ####################### //########################################################################### // // The small ref-counted handle that links a PoweredSubsystem to its powering // Generator segment. Functionally a specialised SharedData (cf. HeatSink's // linkedSinks). vtable @0050f994, ctor @004b1bb1, dtor @004b1bd0. // class VoltageSourceConnection: public SubsystemConnection { public: VoltageSourceConnection(int initial = 0); // @004b1bb1 ~VoltageSourceConnection(); // @004b1bd0 }; //########################################################################### //####### Cross-family helpers provided by the Mech game layer ########## //########################################################################### // // These reach into the owning Mech (subsystem message manager / power bus / // myomer back-pointer). They are NOT part of the heat/power family -- they // are declared here (definitions live in the mech family) so the recovered // power bodies link against them without this family editing mech.hpp. // See "CROSS-FAMILY NEEDS" in the porting notes. // class SubsystemMessageManager; SubsystemMessageManager* BT_GetMessageManager(Mech *owner); // *(owner+0x190) Logical BT_IsBusLive(Mech *owner); // *(*(owner+0x190)+0x274) void BT_ClearMyomers(Mech *owner); // owner+0x374 = 0 #endif