The MadCat torso twists, the view turns with it, and targeting follows. Three reconstruction fronts closed: THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up): - PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog MIRRORS the watched subsystem's electrical level (+0x278), brownout downgrade when gen output <= minVoltage% x rated. @004b1804 relabeled ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong. - The factory watcher-CONNECT pass reconstructed (vtable slot +0x38, @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes): watchedLink.Add(roster[watchedSubsystem]) on the master node. Was the SubProxy::Start() no-op -- every watchdog sat at 0 forever. - MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f = permanent brownout) and PowerWatcher's Derivation chains its REAL base HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the whole Torso/Searchlight/ThermalSight family). - KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not "HeatSink"); the btl4gaug heat-widget gate now tests it via the BTIsHeatWatcher bridge. THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes): - The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in every twist-capable .SKL; the camera + canopy ride the same hinge subtree through HingeRenderable's live matrix-stack compose -- ALREADY WORKING in the port. The crosshair stays screen-centered (center IS the boresight); the twist reads on the tape carets/compass/radar. - The real bug was the port's gBTAimX = tan(twist) slew (the falsified "body-mounted view" model): the camera already carried the twist, so the crosshair counter-slid to hull-forward and the fire ray with it. Deleted; the pick ray inherits the twist from the yawing eye basis. - Two instrumentation traps documented (chase-eye-as-default-camera, BT_FORCE_TORSO clobbering real joints -> the hook now only fills unresolved ones); an over-correcting explicit eye compose was added on those false readings and retired the same day. CONTROLS + REPLICATION: - Q/E spring-center on release (the axis is a twist-RATE demand; the old hold-deflection model drifted forever); X also zeroes the axis and pulses the authentic torso Recenter (@004b6918). M cycles control mode via the real CycleControlMode body. - Torso update-record DIRECTION fixed: engine truth is Write=serialize / Read=apply; @004b6a78 is the READ (was mislabeled Write) and the missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C, twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD -140-degree ghost twist. - Marching-ghost desync: 4 Standing-case guards zero stale reverse cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate). - Kill credit rerouted to the OBSERVED killer (lastInflictingID -> killer's player link) -- kills count, target K/D populates. KB: subsystems.md (watcher chain), multiplayer.md (record direction), combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view re-correction), decomp-reference.md (new addresses + tag fix), open-questions.md (dead capability-roster loops 2-4, snapshot CD read). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
612 lines
26 KiB
C++
612 lines
26 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
|
|
// 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<int> *message),
|
|
SelectGeneratorBMessageHandler(ReceiverDataMessageOf<int> *message),
|
|
SelectGeneratorCMessageHandler(ReceiverDataMessageOf<int> *message),
|
|
SelectGeneratorDMessageHandler(ReceiverDataMessageOf<int> *message),
|
|
ToggleGeneratorModeMessageHandler(ReceiverDataMessageOf<int> *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
|