//===========================================================================// // File: heatfamily_reslice.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Re-slice of the heat/power family class bodies that fell OUTSIDE // // the first heat decomp window (gap 0x4ae4d8 - 0x4afbe0). // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (heatfamily_gap.c). This header is a // companion to heat.hpp: it COMPLETES the Condenser class that heat.hpp/.cpp // left best-effort, and adds the three other heat/power-family classes whose // bodies live in the same gap: // // class primary vtable ctor classID base // --------------- -------------- -------- ------- ----------------- // Condenser 0050ed88 @4ae568 0x0BBD HeatSink // 0050ed4c/ed50 @4ae8d0 0x0BBE HeatSink (name unconfirmed) // HeatWatcher 0050ed10 @4aeb40 0x0BBF HeatableSubsystem // Reservoir 0050ecd4/ecd8 @4af408 0x0BC0 HeatSink // // classID order (resource record +0x20) dovetails with the powersub family: // ...0x0BBD Condenser, 0x0BBE , 0x0BBF HeatWatcher, 0x0BC0 Reservoir, // 0x0BC1 Generator, 0x0BC2 PoweredSubsystem, ... 0x0BD5 PowerWatcher. // // HeatWatcher is the missing intermediate base of powersub's PowerWatcher // (ctor @4b18a4 chains to the HeatWatcher base ctor @4aeb40). CLASSMAP.md // previously listed PowerWatcher directly under Subsystem; the real chain is // PowerWatcher : HeatWatcher : HeatableSubsystem : Subsystem. // // Field offsets are byte offsets into the shipped object (word index * 4). // Hex float constants resolved from the CODE-section literal pool: // _DAT_004ae530 = 1.0f _DAT_004ae708 = -1.0f _DAT_004ae89c = 300.0f // _DAT_004ae8a0 = 3.0f _DAT_004ae8a4 = 1.0f _DAT_004ae8a8 = 0.0f // _DAT_004ae8ac = 1.0e-4f _DAT_004aee28 = -1.0f _DAT_004aeedc = 0.0f // _DAT_004af3a0 = 1.0e-4f _DAT_004af3a4 = 0.0f _DAT_004af3a8 = 0.0f // _DAT_004af404 = 0.0f _DAT_004af694 = -1.0f // _DAT_004ae974 ~= 1.738 (double 0x3FFBCC.., thermalMass-per-heat-sink scale) // _DAT_004af518 ~= 0.x (double, coolant-capacity-per-master scale; low word) // #if !defined(HEATFAMILY_RESLICE_HPP) # define HEATFAMILY_RESLICE_HPP #if !defined(HEAT_HPP) # include // HeatableSubsystem, HeatSink, Condenser base decls #endif //########################################################################### //############### Watcher-branch-LOCAL layout types ##################### //########################################################################### // // BASE-CHAIN RE-BASE: the binary's HeatWatcher/PowerWatcher use a 0xC-byte // connection (watchedLink) and a 0x54-byte gauge alarm (heatAlarm/watchdogAlarm // -- a full GaugeAlarm), NOT the 4-byte SubsystemConnection / 8-byte HeatAlarm // used on the HeatSink/PoweredSubsystem heat-leaf branch. These Watcher-LOCAL // types close the 156-byte base deficit so a Watcher-branch leaf's own fields // begin at 0x1D8 (== Torso::currentTwist, the gyro cross-link target), WITHOUT // resizing the shared heat.hpp types the working heat leaves depend on. All // access is through the API (Resolve/GetLevel), never a raw internal offset, so // the interior padding layout is immaterial -- only the total sizeof must match. // struct WatchedConnection // binary watchedLink: 0xC bytes { Subsystem *linked; char _pad[0x0C - sizeof(Subsystem*)]; WatchedConnection(int = 0) { linked = 0; } void Add(Subsystem *s) { linked = s; } Subsystem* Resolve() const { return linked; } void Clear() { linked = 0; } }; static_assert(sizeof(WatchedConnection) == 0x0C, "Watcher connection must be 0xC to close the deficit"); // The Watcher-branch 0x54 alarm is now the SAME type the heat-leaf branch uses // (heat.hpp GaugeAlarm54): identical layout (levelCount@0, level@4, pad to 0x54), // so the already-locked Watcher LayoutChecks stay valid, and there is one shared // 0x54 alarm across the whole subsystem hierarchy. typedef GaugeAlarm54 WatcherGaugeAlarm; // binary watcher alarm: 0x54 bytes static_assert(sizeof(WatcherGaugeAlarm) == 0x54, "Watcher alarm must be 0x54 to close the deficit"); //########################################################################### //############################# Condenser ############################### //########################################################################### // // COMPLETION of heat.hpp's Condenser. It is NOT a passive subsystem (as // heat.hpp guessed) -- it derives from HeatSink, models a refrigeration // output that fights the master heat-sink's stored heat, carries a 3-way // valve, and reports a condenser number parsed from its instance name. // // vtable @0050ed88 ctor @4ae568 dtor @4ae5fc classID CondenserClassID // // NOTE: the Condenser class + its resource record (Condenser__SubsystemResource) // are now declared in heat.hpp (the completed HeatSink-derived form); this file // only supplies the recovered method bodies for it. // --- Condenser instance layout (HeatSink + the following) --- // Scalar refrigerationOutput; // @0x160 (word 0x58) recomputed each frame // int valveState; // @0x1D0 (word 0x74) init 1 (MoveValve 0..2) // int condenserNumber; // @0x1D4 (word 0x75) last digit of name // Scalar refrigerationFactor; // @0x1D8 (word 0x76) from resource +0xFC // Scalar (unused/flow @0x15C set 0)// @0x15C (word 0x57) // AlarmIndicator condenserAlarm; // @0x1DC (word 0x77) 3 levels // // Methods recovered: // @4ae4d8 RefrigerationSimulation(Scalar) -- vtable slot 9 // @4ae568 Condenser(owner,id,res,shared) -- ctor // @4ae5fc ~Condenser(byte flags) -- vtable slot 0 // @4ae63c TestInstance() -- IsDerivedFrom 0x50e4fc // @4ae658 CreateStreamedSubsystem(...) -- parses RefrigerationFactor // @4afbe0 MoveValve(message) [best-effort] -- cycles valveState 0->1->2->0, // drives owner HUD gauges //########################################################################### //########################### HeatWatcher ############################### //########################################################################### // // Abstract monitor that watches ANOTHER subsystem's temperature and raises // a 3-level degradation/failure alarm. Base class of powersub's // PowerWatcher (the "PowerWatcher base ctor @4aeb40" called out in the task). // // vtable @0050ed10 ctor @4aeb40 dtor @4aebe8 classID 0x0BBF // // RESOURCE-LAYOUT FIX (RESOURCE_AUDIT.md root cause A): HeatWatcher is a light // MechSubsystem, NOT a full HeatableSubsystem -- verified from the binary: ctor // @4aeb40 -> FUN_004ac644 (MechSubsystem base ctor), parser @4aec54 -> FUN_004ac9ec // (MechSubsystem parse) + only Degradation/Failure/Watched, record ENDS 0xF0. The // resource MUST inherit MechSubsystem__SubsystemResource (ends 0xE4); inheriting // HeatableSubsystem::SubsystemResource (0xFC) slid these 3 fields to 0xFC/0x100/0x104. struct HeatWatcher__SubsystemResource: public MechSubsystem__SubsystemResource { int watchedSubsystem; // +0xE4 "WatchedSubsystem" (index, +2 bias) Scalar degradationTemperature; // +0xE8 "DegradationTemperature" (sentinel -1.0f) Scalar failureTemperature; // +0xEC "FailureTemperature" (sentinel -1.0f) }; // ends 0xF0 // Compile-time guard: the record is loaded verbatim at binary offsets, so these MUST hold. static_assert(offsetof(HeatWatcher__SubsystemResource, watchedSubsystem) == 0xE4, "HeatWatcher watchedSubsystem must be at 0xE4"); static_assert(offsetof(HeatWatcher__SubsystemResource, degradationTemperature) == 0xE8, "HeatWatcher degradationTemperature must be at 0xE8"); static_assert(offsetof(HeatWatcher__SubsystemResource, failureTemperature) == 0xEC, "HeatWatcher failureTemperature must be at 0xEC"); static_assert(sizeof(HeatWatcher__SubsystemResource) == 0xF0, "HeatWatcher record must end at 0xF0"); class HeatWatcher: public MechSubsystem // was HeatableSubsystem (approximation); real base is // MechSubsystem (ctor @4aeb40 -> FUN_004ac644). Re-basing // also un-shadows degradation/failureTemperature. { public: static Derivation *GetClassDerivations(); // name "HeatWatcher" static SharedData DefaultData; typedef HeatWatcher__SubsystemResource SubsystemResource; typedef void (HeatWatcher::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } static Logical TestClass(Mech&); Logical TestInstance() const; // @4aec38 void ResetToInitialState(Logical powered); // @4aea9c (slot 10) HeatWatcher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @4aeb40 ~HeatWatcher(); // @4aebe8 // @4aeac4 -- the registered Performance: resolve the watched subsystem, // read its currentTemperature (+0x114) and drive the alarm. void WatchSimulation(Scalar time_slice); static int CreateStreamedSubsystem( // @4aec54 (468 bytes) NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); public: WatchedConnection watchedLink; // @0x114 (0xC) resolved subsystem -> 0x120 Scalar degradationTemperature; // @0x120 (word 0x48) resource +0xE8 Scalar failureTemperature; // @0x124 (word 0x49) resource +0xEC int watchedSubsystem; // @0x128 (word 0x4a) resource +0xE4 WatcherGaugeAlarm heatAlarm; // @0x12C (0x54) 3 levels -> ends 0x180 }; // HeatWatcher own region 0x114..0x180 (0x6C); PowerWatcher own follows at 0x180. // BASE-CHAIN RE-BASE locks: prove the Watcher base ends where the binary needs. // (engine Subsystem 0xE0 + MechSubsystem 0x34 = 0x114 == HeatWatcher start.) static_assert(offsetof(HeatWatcher, watchedLink) == 0x114, "HeatWatcher watchedLink must be at 0x114"); static_assert(offsetof(HeatWatcher, heatAlarm) == 0x12C, "HeatWatcher heatAlarm must be at 0x12C (0x54-byte alarm)"); static_assert(sizeof(HeatWatcher) == 0x180, "HeatWatcher must end at 0x180 (PowerWatcher own follows)"); //########################################################################### //############################ Reservoir ################################ //########################################################################### // // Coolant reservoir: a HeatSink that stores a coolant charge and "squirts" // it into the linked sinks on demand (the slot-14 DrawCoolant SOURCE). // // vtable @0050ecd4/ecd8 ctor @4af408 classID 0x0BC0 // struct Reservoir__SubsystemResource: public HeatableSubsystem::SubsystemResource { Scalar coolantCapacity; // +0xFC "CoolantCapacity" (sentinel -1.0f) Scalar coolantSquirtMass; // +0x100 "CoolantSquirtMass" (sentinel -1.0f) }; class Reservoir: public HeatSink { public: static Derivation *GetClassDerivations(); // name "Reservoir" static SharedData DefaultData; typedef Reservoir__SubsystemResource SubsystemResource; typedef void (Reservoir::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } static Logical TestClass(Mech&); Logical TestInstance() const; // @4af564 -> IsDerivedFrom 0x50e650 Reservoir( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @4af408 // @4aef78 -- registered Performance (this[7..9] = &CoolantInjection via 0050e6b4) void CoolantSimulation(Scalar time_slice); // @4aefa4 (1019 bytes) -- distribute coolant ("squirt") across the // linked heat-sink network, clamped per-sink and per-frame. void InjectCoolant(Scalar time_slice); // @4af3b0 -- slot-14 DrawCoolant override: hand out up to coolantLevel. Scalar DrawCoolant(Scalar requested); Logical HandleMessage(int message); // @4aee70 ("InjectCoolant" msg) void PrintState(); // @4aeef8 (ReservoirState) void GetState(/*...*/); // @4aef40 (slot 7 state query) static int CreateStreamedSubsystem( // @4af580 NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); public: // HeatSink base (ends 0x1D0) ... then Reservoir adds (ctor @4aef.., dtor param_1[0x74..0x8b]): // coolantCapacity is the inherited HeatSink thermalCapacity@0x128 (word 0x4a); // "injectActive" is reservoirAlarm's level (@0x1e4 == alarm+0x14, raw @4aef78); // both were redeclared members that over-sized the object past the 0x230 alloc. GaugeAlarm54 reservoirAlarm; // @0x1D0 (word 0x74) 0x54 alarm -> ends 0x224; level@0x1e4 = inject flag Scalar coolantSquirtMass; // @0x224 (word 0x89) resource +0x100 Scalar injectAccumulator; // @0x228 (word 0x8a) inject elapsed-time accumulator Scalar squirtEfficiency; // @0x22C (word 0x8b) init 0.5f -> ends 0x230 // coolantLevel @0x12C (word 0x4b) init = capacity (inherited). friend struct ReservoirLayoutCheck; }; struct ReservoirLayoutCheck { static_assert(offsetof(Reservoir, reservoirAlarm) == 0x1D0, "Reservoir::reservoirAlarm @0x1D0 (word 0x74)"); static_assert(offsetof(Reservoir, coolantSquirtMass) == 0x224, "Reservoir::coolantSquirtMass @0x224 (word 0x89)"); static_assert(offsetof(Reservoir, injectAccumulator) == 0x228, "Reservoir::injectAccumulator @0x228 (word 0x8a)"); static_assert(offsetof(Reservoir, squirtEfficiency) == 0x22C, "Reservoir::squirtEfficiency @0x22C (word 0x8b)"); static_assert(sizeof(Reservoir) == 0x230, "sizeof(Reservoir) 0x230 (factory alloc)"); }; //########################################################################### //######################## AggregateHeatSink ############################ //########################################################################### // // The mech's aggregate / master heat-sink BANK (classID 0x0BBE). A HeatSink // subclass that parses "HeatSinkCount" and holds a frozen "AmbientTemperature" // setpoint (300 K); the numeric-R cockpit gauge binds HeatSink/AmbientTemperature // to this class (L4GAUGE.CFG:4552, the last config-binding NULL). // // vtable @0050ed4c/ed50 ctor @4ae8d0 classID 0x0BBE own GUID 0x50e590 // // DELIBERATE DEVIATION (databinding-safe, zero-regression): the shipped ctor // installs Performance @4ae73c (relax toward the ambient setpoint), which reads // a RAW inherited-base pointer self+0xE0 -> [+0x158] (the engine subsystem's // status/damage model, FUN_0041de1c). That offset does NOT map in our compiled // layout (garbage ptr -> AV/NaN, and this ctor runs for EVERY mech). Since // ambientTemperature is a FROZEN constant (@4ae73c never writes it, so the gauge // reads 300 either way), we keep the VERIFIED base HeatSinkSimulation the HeatSink // base ctor already installs and do NOT reimplement @4ae73c / deref self+0xE0. // The authentic relaxation model is DEFERRED (see the deferred item in ยง10 / P4). // struct AggregateHeatSink__SubsystemResource: public HeatableSubsystem::SubsystemResource // ends 0xFC { int heatSinkCount; // +0xFC "HeatSinkCount" (parser @4ae9dc; ctor reads res+0xFC) }; class AggregateHeatSink: public HeatSink { public: static Derivation *GetClassDerivations(); // own GUID 0x50e590, name "HeatSinkBank" static SharedData DefaultData; enum { HeatSinkCountAttributeID = HeatSink::NextAttributeID, // @0x1D0 AmbientTemperatureAttributeID, // @0x1D4 FROZEN 300 NextAttributeID }; private: static const IndexEntry AttributePointers[]; public: static AttributeIndexSet& GetAttributeIndex(); static Logical TestClass(Mech&); Logical TestInstance() const; // @4ae9c0 -> IsDerivedFrom 0x50e590 typedef AggregateHeatSink__SubsystemResource SubsystemResource; AggregateHeatSink( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @4ae8d0 ~AggregateHeatSink(); static int CreateStreamedSubsystem( // @4ae9dc (offline content-build only) NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); public: // HeatSink base ends 0x1D0; the aggregate adds: int heatSinkCount; // @0x1D0 (word 0x74) res +0xFC Scalar ambientTemperature; // @0x1D4 (word 0x75) = 300.0f, FROZEN SubsystemConnection helper; // @0x1D8 (word 0x76) 0xC link node (vestigial/inert) // object ends @0x1E4 friend struct AggregateHeatSinkLayoutCheck; }; struct AggregateHeatSinkLayoutCheck { static_assert(offsetof(AggregateHeatSink__SubsystemResource, heatSinkCount) == 0xFC, "res heatSinkCount @0xFC"); static_assert(offsetof(AggregateHeatSink, heatSinkCount) == 0x1D0, "heatSinkCount @0x1D0 (word 0x74)"); static_assert(offsetof(AggregateHeatSink, ambientTemperature) == 0x1D4, "ambientTemperature @0x1D4 (word 0x75)"); static_assert(offsetof(AggregateHeatSink, helper) == 0x1D8, "helper @0x1D8 (word 0x76)"); static_assert(sizeof(AggregateHeatSink) == 0x1E4, "sizeof 0x1E4 == factory alloc @9993"); }; //########################################################################### //############# Engine collection-iterator helpers (NOT heat) ########### //########################################################################### // // The thunks @4af9cf..@4afb96 are tiny ctor/dtor wrappers for the engine's // generic collection/iterator library (vtables 0050eccc, 0050ecc4, 0050ecbc, // 0050ec6c, 0050ec1c), used as locals inside Reservoir::InjectCoolant // (@4aefa4). They are out of scope for the heat family and are documented // in heatfamily_reslice.cpp only for completeness. #endif