Files
BT412/game/reconstructed/powersub.hpp
T
arcattackandClaude Opus 4.8 9d82be46a1 P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact
The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset.  Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact.  An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation.  static_assert-locked chain (verified vs the raw ctors):

  HeatSink 0x1D0
   -> PoweredSubsystem 0x31C   retype electricalStateAlarm@0x264 + modeAlarm@0x2B8   @004b0f74
   -> MechWeapon      0x3F0   retype weaponAlarm@0x350; delete 5 phantom tail fields  @004b99a8
   -> Emitter         0x478   delete outputVoltage/beamLengthRatio/firingArmed aliases
                              + beamHit*/beamColor/beamHitData/energyRampTime phantoms;
                              retype beamOrientation EulerAngles->Quaternion(16B)       @004bb120
   -> PPC             0x478   (no own fields)                                           @004bb888
  PoweredSubsystem -> Sensor  0x328   (no alarm; 3 own fields)                          @004b1d18
  PoweredSubsystem -> Myomers 0x358   (no alarm)                                        @004b8fec

New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
  * alias field    - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
                     beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
  * phantom field  - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
                     (past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
                     beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
                     or the value is a method local)

Non-layout fixes required in the same wave:
  * outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
    removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
  * MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
    Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
    resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
  * DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
    writes the 0x264 alarm)

The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).

Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed).  LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:49:58 -05:00

575 lines
23 KiB
C++

//===========================================================================//
// 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 <heat.hpp>
#endif
#if !defined(HEATFAMILY_RESLICE_HPP)
# include <heatfamily_reslice.hpp> // 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
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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(); }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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");
};
//###########################################################################
//############################# Myomers #################################
//###########################################################################
//
// The mech's artificial-muscle bundle: a PoweredSubsystem whose output
// voltage (and hence the actuator drive available to the locomotion model)
// tracks the powering generator and is throttled back by heat damage.
//
// vtable @0050fb0c, ctor @004b1d18, dtor @004b1d90, shared data @0050fa1c.
// RegisteredClass::MyomersClassID == 0x0bc3.
//
class Myomers:
public PoweredSubsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation *GetClassDerivations(); // @0050fa2c
static SharedData DefaultData; // @0050fa1c
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
static Logical
TestClass(Mech&);
Logical
TestInstance() const; // @004b1e18
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef PoweredSubsystem::SubsystemResource SubsystemResource;
Myomers(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data = DefaultData
); // @004b1d18
~Myomers(); // @004b1d90
static int
CreateStreamedSubsystem( // @004b1dcc
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:
void
MyomersSimulation(Scalar time_slice); // @004b1c4c (Performance, PTR @0050fa94)
void
ResetToInitialState(Logical powered = True); // slot 10, @004b1c18
Logical
HandleMessage(int message); // slot 9, @004b1bfc (-> PoweredSubsystem)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local data.
//
protected:
Scalar outputVoltage; // @0x31C init 1.0f -- drive available to the locomotion model
int powered; // @0x320 init 0 -- True while the generator is on-line
int voltageAvailable; // @0x324 init 0 -- True while electricalState == Ready
};
//###########################################################################
//############################# 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)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
// 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).
// SourceLevel == linked master sink's stored heat energy (this[0x38]+0x158).
Scalar
SourceLevel()
{
HeatSink *master = (HeatSink *)linkedSinks.Resolve();
return (master != 0) ? master->heatEnergy : (Scalar)0.0f;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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
Simulation(Scalar time_slice); // @004b1804 (chains to base @004aea9c)
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