The gauge-databinding-map workflow found most cockpit gauges resolve NULL because the reconstructed subsystems publish only a fraction of the attributes the config binds. First publishing batch (attribute tables are read-only static data; ids kept a dense prefix from each parent's NextAttributeID): - HeatSink table dense-append: DegradationTemperature/FailureTemperature (the condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the HeatSink link. Condenser/Reservoir inherit this -> all 6 condenser temp bars now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK). - PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp sub-branch is skipped when it resolves NULL). Flows to Sensor/Myomers/weapons. - MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone-> rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData re-pointed at it (they carried an EMPTY default-constructed index -> resolved NOTHING). Verified: the ER MED LASER / PPC / STREAK weapon clusters now render live recharge dials (were blank TEMP/STATUS). - Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale + overlay range readout track the mapper's zoom (was frozen at 1000). - GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG) -- durable diagnostic infra for the wave. Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact), combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving. Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) + Searchlight/LightOn (P1b). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
606 lines
24 KiB
C++
606 lines
24 KiB
C++
//===========================================================================//
|
|
// File: heat.hpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Heatable subsystems -- temperature model, heat sinks, condenser //
|
|
//---------------------------------------------------------------------------//
|
|
// 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 in heat_cluster.c)
|
|
// cross-referenced with the surviving HEAT.TCP fragment and the MUNGA
|
|
// SUBSYSTM.HPP base interface. See heat.cpp for per-method @ADDR evidence.
|
|
//
|
|
// NOTE on the engine base: the WinTesla/MUNGA `Subsystem` (subsystm.h) is a
|
|
// thin damageable-simulation base -- it does NOT carry the heat/power virtual
|
|
// surface (ResetToInitialState / GetStatusFlags / HandleMessage / PrintState /
|
|
// Simulation) that the BT game layer assumed lived on "Subsystem". Because
|
|
// this family OWNS the subsystem base classes that the other BT families
|
|
// derive from, that virtual surface is (re)introduced on HeatableSubsystem
|
|
// here, and the per-class shared-data boilerplate follows the real engine
|
|
// GetClassDerivations()/GetMessageHandlers()/GetAttributeIndex()/StateCount
|
|
// idiom (cf. RP/VTVSUB.cpp), not the 3-arg form the raw decomp guessed.
|
|
//
|
|
|
|
#if !defined(HEAT_HPP)
|
|
# define HEAT_HPP
|
|
|
|
#if !defined(SUBSYSTM_HPP)
|
|
# include <subsystm.hpp>
|
|
#endif
|
|
#if !defined(MECHSUB_HPP)
|
|
# include <mechsub.hpp> // MechSubsystem -- HeatableSubsystem's base (re-base)
|
|
#endif
|
|
#include <affnmtrx.hpp> // AffineMatrix
|
|
#include <average.hpp> // AverageOf<T>
|
|
#include <scalar.hpp> // Scalar
|
|
#include <alarm.hpp> // GaugeAlarm
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
//##################### Reconstruction type aliases #######################
|
|
#if !defined(BT_RECON_TYPE_ALIASES)
|
|
# define BT_RECON_TYPE_ALIASES
|
|
typedef AffineMatrix Matrix34; // 3x4 affine (AFFNMTRX.h)
|
|
typedef GaugeAlarm AlarmIndicator; // GAUGALRM.h
|
|
typedef AverageOf<Scalar> FilteredScalar; // 15-sample running average (AVERAGE.h)
|
|
#endif
|
|
|
|
// NOTE: `DebugStream` and `endl` (the ReconStream trace artifact) come from the
|
|
// shared foundation header (mechrecon.hpp, pulled in via bt.hpp/mech.hpp).
|
|
|
|
//##################### Segment flag bits (model resource) ###############
|
|
// The "is this a master / damaged-copy segment" test. The decomp rendered
|
|
// these as owner->GetSegmentFlags() reads; the flags actually live in the
|
|
// streamed subsystem resource (subsystemFlags).
|
|
#if !defined(BT_SEGMENT_FLAG_BITS)
|
|
# define BT_SEGMENT_FLAG_BITS
|
|
enum {
|
|
SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == copy
|
|
MasterHeatSinkFlag = 0x100 // flags & 0x100 == participates in sim
|
|
};
|
|
#endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
|
|
//###########################################################################
|
|
//################# Reconstruction helper value types ###################
|
|
//###########################################################################
|
|
//
|
|
// The raw decomp leaned on three engine helpers whose real APIs differ from
|
|
// what the pseudo-C assumed. Rather than bend the engine types, the family
|
|
// owns small faithful wrappers that expose exactly the surface the recovered
|
|
// bodies call.
|
|
//
|
|
|
|
// The binary AlarmIndicator (ctor FUN_0041b9ec) is a 0x54-byte object -- base
|
|
// GaugeAlarm + three sub-indicators, level at +0x04. Every subsystem alarm the
|
|
// binary builds via FUN_0041b9ec (heatAlarm, electricalState/mode/state/weapon/
|
|
// condenser/reservoir alarms) is this size; modeling it as 8 bytes is what slid the
|
|
// whole heat-leaf branch 0x4C short. Access is ALWAYS through the named API
|
|
// (SetLevel/GetLevel), never a raw internal offset, so the interior padding is
|
|
// immaterial -- only sizeof must be 0x54. This is the SAME layout as the Watcher
|
|
// branch's WatcherGaugeAlarm (heatfamily_reslice.hpp now typedefs to it), so the
|
|
// already-locked Watcher LayoutChecks stay valid.
|
|
class GaugeAlarm54
|
|
{
|
|
public:
|
|
GaugeAlarm54(int levels = 0) { levelA = levelB = levels; level = 0; }
|
|
void Initialize(int levels) { levelA = levelB = levels; level = 0; }
|
|
void SetLevel(int n) { level = n; }
|
|
int GetLevel() const { return level; }
|
|
int Level() const { return level; }
|
|
// ReconAlarm/AlarmIndicator API aliases (callers that predate the retype use these):
|
|
void SetState(unsigned n){ level = (int)n; }
|
|
unsigned GetState() const { return (unsigned)level; }
|
|
protected:
|
|
// Interior mirrors FUN_0041b9ec: base GaugeAlarm header (+0x00), three count
|
|
// words at +0x0c/+0x10/+0x14, three sub-indicators at +0x18/+0x2c/+0x40. The
|
|
// STATUS level the binary reads (HeatSink+0x184 == alarm+0x14, GetStatusFlags
|
|
// @004add30 / PrintState @004ae050) is param_1[5] at +0x14, so `level` lands
|
|
// there -- a raw `subsystem+0x184` read now returns GetLevel().
|
|
char _hdr[0x0c]; // +0x00 base GaugeAlarm (vtable + FUN_004178cc header)
|
|
int levelA; // +0x0c param_1[3] (level count)
|
|
int levelB; // +0x10 param_1[4]
|
|
int level; // +0x14 param_1[5] -- the status level (Normal/Deg/Fail)
|
|
char _tail[0x54 - 0x18]; // +0x18 three sub-indicators to 0x54
|
|
};
|
|
|
|
// The 8-byte modeling type kept ONLY for HUD::statusAlarm (still on the old
|
|
// HUDLayoutCheck-locked layout; retyping HUD to 0x54 is a separate follow-up F1).
|
|
// All heat/power/weapon alarms now use GaugeAlarm54 above.
|
|
class HeatAlarm
|
|
{
|
|
public:
|
|
HeatAlarm(int levels = 0) { levelCount = levels; level = 0; }
|
|
void Initialize(int levels) { levelCount = levels; level = 0; }
|
|
void SetLevel(int n) { level = n; }
|
|
int GetLevel() const { return level; }
|
|
int Level() const { return level; }
|
|
protected:
|
|
int levelCount;
|
|
int level;
|
|
};
|
|
|
|
// The 15-sample running average behind heatLoad (Initialize/AddSample/Average).
|
|
class HeatFilter
|
|
{
|
|
public:
|
|
void Initialize(int count, Scalar value) { average.SetSize((size_t)count, value); }
|
|
void AddSample(Scalar v) { average.Add(v); }
|
|
Scalar Average() { return average.CalculateAverage(); }
|
|
private:
|
|
AverageOf<Scalar> average;
|
|
};
|
|
|
|
// A ref to a linked subsystem (heat-sink linkage / voltage source / watched
|
|
// subsystem). The original modelled this with a small SharedData-derived
|
|
// connection object exposing Add/Resolve/Clear.
|
|
// The binary connection is a 0xC-byte SharedData-derived link node (the raw
|
|
// resolver FUN_00417ab4 two-level-derefs it: `if(*(p+8)) return *(*(p+8)+8)`),
|
|
// NOT a bare 4-byte pointer. Modeling it as 4 bytes slid every field after an
|
|
// embedded connection low -- the direct cause of the heat-leaf 0x4C deficit
|
|
// (HeatSink::linkedSinks, PoweredSubsystem::voltageSource). Named access
|
|
// (Add/Resolve/Clear on `linked`) is unchanged and inert in bring-up (no plug
|
|
// resolves); the interior _reserved slots reconcile with FUN_00417ab4 when link
|
|
// resolution is wired. Same 0xC layout as heatfamily_reslice.hpp WatchedConnection.
|
|
class SubsystemConnection
|
|
{
|
|
public:
|
|
SubsystemConnection(int = 0) { linked = 0; _reserved[0] = _reserved[1] = 0; }
|
|
void Add(Subsystem *s) { linked = s; }
|
|
Subsystem* Resolve() const { return linked; }
|
|
void Clear() { linked = 0; }
|
|
protected:
|
|
Subsystem *linked; // +0x00
|
|
int _reserved[2]; // pad to the binary 0xC connection size
|
|
};
|
|
static_assert(sizeof(SubsystemConnection) == 0x0C, "subsystem connection must be 0xC (SharedData link node)");
|
|
|
|
// condenserNumber = atoi(lastChar(name)).
|
|
inline int
|
|
NameTrailingNumber(const char *name)
|
|
{
|
|
if (name == 0 || *name == '\0')
|
|
return 0;
|
|
return atoi(name + (strlen(name) - 1));
|
|
}
|
|
|
|
//###########################################################################
|
|
//################# HeatableSubsystem Model Resource ####################
|
|
//###########################################################################
|
|
//
|
|
// Extends the base damageable Subsystem resource. The heat-specific fields
|
|
// begin at +0xE4 -- i.e. immediately after MechSubsystem__SubsystemResource
|
|
// (model size 0xE4), NOT after Subsystem::SubsystemResource (0x30). The class
|
|
// hierarchy is HeatableSubsystem : MechSubsystem : Subsystem, so the resource
|
|
// MUST inherit MechSubsystem__SubsystemResource; inheriting Subsystem::
|
|
// SubsystemResource directly dropped the 0x30..0xE4 chunk and slid every heat
|
|
// field 0xB4 bytes low -- so thermalMass/heatSinkIndex read neighbouring floats
|
|
// (heatSinkIndex came back as 10.0f = 1/thermalMass, GetSegment -> OOB -> no link).
|
|
//
|
|
struct HeatableSubsystem__SubsystemResource:
|
|
public MechSubsystem__SubsystemResource
|
|
{
|
|
Scalar startingTemperature; // +0xE4 "StartingTemperature"
|
|
Scalar degradationTemperature; // +0xE8 "DegradationTemperature"
|
|
Scalar failureTemperature; // +0xEC "FailureTemperature"
|
|
Scalar thermalConductance; // +0xF0 "ThermalConductance"
|
|
Scalar thermalMass; // +0xF4 "ThermalMass"
|
|
int heatSinkIndex; // +0xF8 "HeatSink" (segment index, +2 bias)
|
|
};
|
|
|
|
//###########################################################################
|
|
//################# Condenser Model Resource ############################
|
|
//###########################################################################
|
|
//
|
|
// Condenser adds the refrigeration factor (+0xFC). Declared here because the
|
|
// Condenser class itself is declared in this header (its bodies are split
|
|
// between heat.cpp [best-effort] and heatfamily_reslice.cpp [completion]).
|
|
//
|
|
struct Condenser__SubsystemResource:
|
|
public HeatableSubsystem__SubsystemResource
|
|
{
|
|
Scalar refrigerationFactor; // +0xFC "RefrigerationFactor" (sentinel -1.0f)
|
|
};
|
|
|
|
//###########################################################################
|
|
//######################### HeatableSubsystem ###########################
|
|
//###########################################################################
|
|
//
|
|
// Abstract base for any subsystem that participates in the thermal model and
|
|
// the shared "damageable subsystem" virtual surface the BT families expect.
|
|
// (vtable @0050e210, destructor @004ac868.)
|
|
//
|
|
class HeatableSubsystem:
|
|
public MechSubsystem // was: public Subsystem. mechsub.hpp's MechSubsystem
|
|
// and this are overlapping reconstructions of the SAME
|
|
// binary cluster (vtable 0050e210); re-base + de-shadow so
|
|
// owner/simulationState/damageZone/the virtual surface come
|
|
// from the one real base, not uninitialised duplicates.
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech&);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Damageable-subsystem virtual surface (engine Subsystem lacks these; the
|
|
// heat/power families override them).
|
|
//
|
|
public:
|
|
// These OVERRIDE the MechSubsystem base slots (same vtable, matching sigs) --
|
|
// not new/parallel slots. ResetToInitialState takes (Logical powered) to match.
|
|
virtual void
|
|
ResetToInitialState(Logical powered);
|
|
virtual LWord
|
|
GetStatusFlags();
|
|
virtual Logical
|
|
HandleMessage(int message);
|
|
virtual void
|
|
PrintState();
|
|
virtual void
|
|
Simulation(Scalar time_slice); // heat per-frame (not a MechSubsystem slot)
|
|
Logical
|
|
IsDamaged() { return (simulationState != 0) ? True : False; }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef HeatableSubsystem__SubsystemResource SubsystemResource;
|
|
|
|
HeatableSubsystem(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~HeatableSubsystem();
|
|
|
|
static int
|
|
CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes = 1
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Common subsystem state
|
|
//
|
|
public:
|
|
// owner / simulationState(was "destroyed") / damageZone / hostEntity etc. are
|
|
// INHERITED from MechSubsystem -- re-declaring them shadowed the real base
|
|
// (engine ctor writes the base member; the duplicate is uninitialised + at a
|
|
// wrong compiled offset). flags/statusFlags -> engine Simulation::simulationFlags;
|
|
// statusBits -> ForceUpdate()/dirty word. Only the thermal fields are own.
|
|
Scalar currentTemperature; // @0x114 HEAT.TCP: init 300.0f
|
|
Scalar degradationTemperature; // @0x118
|
|
Scalar failureTemperature; // @0x11C
|
|
Scalar heatLoad; // @0x120 HEAT.TCP: init 0.0f (filtered load)
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################# HeatSink ################################
|
|
//###########################################################################
|
|
//
|
|
// Active heat sink. Accumulates heat energy, conducts heat to a linked
|
|
// sink, draws coolant, and raises a degradation/failure alarm.
|
|
// (vtable @0050edc4, ctor @004adda0, dtor @004adfd4.)
|
|
//
|
|
class HeatSink:
|
|
public HeatableSubsystem
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support -- the cockpit gauge bindings resolved by name through
|
|
// Simulation::GetAttributePointer (see engine GAUGREND.cpp ParseAttribute):
|
|
// HeatSink/CoolantMass -> coolantLevel (@0x12C, live)
|
|
// HeatSink/CoolantCapacity -> thermalCapacity (@0x128)
|
|
// HeatSink/CurrentTemperature -> currentTemperature (@0x114, inherited)
|
|
// Condenser and Reservoir derive from HeatSink and define no override, so
|
|
// their DefaultData resolves Condenser6/CoolantMass/... to this same table.
|
|
// (SimulationState id 1 is preserved by chaining to the parent index.)
|
|
//
|
|
public:
|
|
enum {
|
|
CoolantMassAttributeID = HeatableSubsystem::NextAttributeID,
|
|
CoolantCapacityAttributeID,
|
|
CurrentTemperatureAttributeID,
|
|
// --- dense-append (gauge data-binding wave): the config binds these
|
|
// per condenser/heat-sink (L4GAUGE.CFG GenericHeatGauges1/2 + the Eng
|
|
// clusters). Ids stay contiguous so AttributeIndexSet::Build has no gap.
|
|
DegradationTemperatureAttributeID, // @0x118 constant amber warn line
|
|
FailureTemperatureAttributeID, // @0x11C constant max reference
|
|
NormalizedPressureAttributeID, // @0x120 heatLoad (smoothed radiated heat)
|
|
DegradationPressureAttributeID, // @0x124 coolantEfficiency
|
|
CoolantMassLeakRateAttributeID, // @0x130 coolantDraw (damage-driven leak)
|
|
HeatSinkAttributeID, // @0x164 linkedSinks (link to master sink)
|
|
NextAttributeID
|
|
};
|
|
private:
|
|
static const IndexEntry AttributePointers[];
|
|
public:
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Heat alarm state (this+0x138, reported by PrintState @004ae050)
|
|
//
|
|
public:
|
|
enum HeatState {
|
|
NormalHeat = 0,
|
|
DegradationHeat = 1,
|
|
FailureHeat = 2
|
|
};
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Simulation Support
|
|
//
|
|
public:
|
|
typedef void
|
|
(HeatSink::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
void
|
|
HeatSinkSimulation(Scalar time_slice); // @004ad924 (Performance)
|
|
|
|
// FUN_004ad748 -- the base per-frame step (vtable slot 9), reused by the
|
|
// Condenser / aggregate sink in heatfamily_reslice.cpp.
|
|
void
|
|
HeatSink_Step(Scalar time_slice) { HeatSinkSimulation(time_slice); }
|
|
|
|
// FUN_004ad7d4 -- entity heat-model active flag.
|
|
Logical
|
|
HeatModelActive() { return True; }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Subsystem virtual overrides (slots on vtable @0050edc4)
|
|
//
|
|
public:
|
|
LWord
|
|
GetStatusFlags(); // slot 12, @004add30
|
|
Logical
|
|
HandleMessage(int message); // slot 8, @004add6c
|
|
void
|
|
ResetToInitialState(Logical powered); // slot 10, @004ad760
|
|
void
|
|
PrintState(); // slot 13, @004ae050
|
|
|
|
// slot 14 (vtable+0x38): asks the central cooling system for coolant
|
|
// and returns how much was actually supplied (overridden by Reservoir).
|
|
virtual Scalar
|
|
DrawCoolant(Scalar requested);
|
|
|
|
// FUN: link another sink/reservoir into this one (Reservoir ctor).
|
|
void
|
|
Attach(HeatSink *other) { linkedSinks.Add(other); }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech&);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef HeatableSubsystem__SubsystemResource SubsystemResource;
|
|
|
|
HeatSink(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~HeatSink();
|
|
|
|
static int
|
|
CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes = 1
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Internal model helpers
|
|
//
|
|
public:
|
|
void
|
|
UpdateHeatLoad(); // @004ad7f0
|
|
void
|
|
ClearHeatFilter(); // @004ad884
|
|
void
|
|
ConductHeat(Scalar time_slice); // @004ad8ac
|
|
Scalar
|
|
ComputeHeatFlow( // @004ad9ec
|
|
HeatSink *other,
|
|
Scalar time_slice
|
|
);
|
|
void
|
|
BalanceCoolant( // @004ada94
|
|
Scalar time_slice
|
|
);
|
|
void
|
|
UpdateCoolant(Scalar time_slice); // @004adbf8
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local data for the heat sink class.
|
|
// Offsets are byte offsets into the shipped object.
|
|
//
|
|
public:
|
|
// degradationTemperature/failureTemperature are BASE fields (HeatableSubsystem
|
|
// @0x118/0x11C, written here by the HeatSink ctor from resource +0xE8/+0xEC) --
|
|
// re-declaring them shadowed the base + slid every own field 8 bytes low.
|
|
|
|
// --- coolant model --- (ctor @004adda0 param_1[0x49..0x4e])
|
|
Scalar coolantEfficiency; // @0x124 init 0.5f
|
|
Scalar thermalCapacity; // @0x128 init 1.0f (coolant capacity / divisor)
|
|
Scalar coolantLevel; // @0x12C init = thermalCapacity (1.0f)
|
|
Scalar coolantDraw; // @0x130 init 0 (per-frame coolant demand)
|
|
int coolantAvailable; // @0x134 init 1 (coolant supply present)
|
|
int coolantActive; // @0x138 init 0 (heat-model running flag; GetStatusFlags bit 0x4)
|
|
|
|
// --- thermal parameters --- (ctor param_1[0x4f..0x58])
|
|
Scalar startingTemperature; // @0x13C resource +0xE4 (saved initial temp)
|
|
Scalar thermalConductance; // @0x140 resource +0xF0
|
|
HeatFilter heatFilter; // @0x144 15-sample running average (12 bytes -> 0x150)
|
|
Scalar filterDecay; // @0x150 init 0.4f
|
|
Scalar thermalMass; // @0x154 resource +0xF4
|
|
Scalar heatEnergy; // @0x158 init = thermalMass * startingTemperature
|
|
Scalar coolantFlowScale; // @0x15C init 1.0f (== "word57")
|
|
Scalar massScale; // @0x160 init 1.0f
|
|
|
|
// --- linkage / display --- (ctor param_1[0x59]=linkedSinks, [0x5c]=heatAlarm)
|
|
SubsystemConnection linkedSinks; // @0x164 0xC connection to master/linked heat sink
|
|
GaugeAlarm54 heatAlarm; // @0x170 0x54 alarm; status level (Normal/Deg/Fail) at +0x14 == subsystem+0x184
|
|
|
|
SubsystemResource *resource; // @0x1C4 saved resource pointer (ctor param_1[0x71])
|
|
Scalar pendingHeat; // @0x1C8 init 0 (heat delta queued for next frame; ctor param_1[0x72])
|
|
Scalar radiatedHeat; // @0x1CC currentTemperature * coolantLevel
|
|
// object ends @0x1D0 (Reservoir/Condenser own fields begin here)
|
|
|
|
friend struct HeatSinkLayoutCheck;
|
|
};
|
|
|
|
// Byte-exact layout locks against the ctor @004adda0 (int* param_1[N] == byte N*4).
|
|
// Compile-time proof the heat leaf now matches the shipped object; never silently regresses.
|
|
struct HeatSinkLayoutCheck {
|
|
static_assert(offsetof(HeatSink, coolantActive) == 0x138, "HeatSink::coolantActive @0x138 (param_1[0x4e])");
|
|
static_assert(offsetof(HeatSink, heatEnergy) == 0x158, "HeatSink::heatEnergy @0x158 (param_1[0x56])");
|
|
static_assert(offsetof(HeatSink, linkedSinks) == 0x164, "HeatSink::linkedSinks @0x164 (param_1[0x59])");
|
|
static_assert(offsetof(HeatSink, heatAlarm) == 0x170, "HeatSink::heatAlarm @0x170 (param_1[0x5c])");
|
|
static_assert(offsetof(HeatSink, resource) == 0x1C4, "HeatSink::resource @0x1C4 (param_1[0x71])");
|
|
static_assert(offsetof(HeatSink, pendingHeat) == 0x1C8, "HeatSink::pendingHeat @0x1C8 (param_1[0x72])");
|
|
static_assert(offsetof(HeatSink, radiatedHeat) == 0x1CC, "HeatSink::radiatedHeat @0x1CC");
|
|
static_assert(sizeof(HeatSink) == 0x1D0, "sizeof(HeatSink) 0x1D0");
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################# Condenser ###############################
|
|
//###########################################################################
|
|
//
|
|
// A HeatSink subclass that models a refrigeration output fighting the master
|
|
// heat sink's stored heat, carries a 3-position valve and a condenser number.
|
|
// (vtable @0050ed88, ctor @4ae568, classID CondenserClassID.) The full body
|
|
// set lives in heatfamily_reslice.cpp; heat.cpp carries a best-effort stub.
|
|
//
|
|
class Condenser:
|
|
public HeatSink
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech&);
|
|
Logical
|
|
TestInstance() const;
|
|
void
|
|
ResetToInitialState(Logical powered);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Simulation / overrides
|
|
//
|
|
public:
|
|
void
|
|
RefrigerationSimulation(Scalar time_slice); // @4ae4d8 (vtable slot 9)
|
|
void
|
|
MoveValve(int message); // @4afbe0 (best-effort)
|
|
virtual void
|
|
SetValveSetting(int setting) { valveState = setting; }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef Condenser__SubsystemResource SubsystemResource;
|
|
|
|
Condenser(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~Condenser();
|
|
|
|
static int
|
|
CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes = 1
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local data.
|
|
//
|
|
public:
|
|
// The per-frame refrigeration output reuses the inherited HeatSink massScale
|
|
// slot (word 0x58 @0x160) -- ctor @004ae568 writes param_1[0x58]=res+0xFC. It
|
|
// is NOT an own Condenser field (declaring it as one slid valveState off 0x1D0).
|
|
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
|
|
GaugeAlarm54 condenserAlarm; // @0x1DC (word 0x77) 0x54 alarm, 3 levels -> ends 0x230
|
|
|
|
friend struct CondenserLayoutCheck;
|
|
};
|
|
|
|
struct CondenserLayoutCheck {
|
|
static_assert(offsetof(Condenser, valveState) == 0x1D0, "Condenser::valveState @0x1D0 (word 0x74)");
|
|
static_assert(offsetof(Condenser, condenserAlarm)== 0x1DC, "Condenser::condenserAlarm @0x1DC (word 0x77)");
|
|
};
|
|
|
|
#endif
|