- Searchlight publishes "LightOn" -> lightState@0x1D8 (the button-5 searchlight lamp; the empty default-constructed index resolved NOTHING). The config binds the name "LightOn", so the enum id is renamed LightOnAttributeID and the table is chained to PowerWatcher's dense index. Verified: LightOn resolves OK. - FIX a genuine reconstruction bug the heatmodel decode caught (heat.cpp UpdateCoolant + heatfamily_reslice RefrigerationSimulation): both read linkedSinks.Resolve()->heatEnergy where the binary reads *(this[0x38]+0x158) = the subsystem's OWN DamageZone.damageLevel (the engine base zone @0xE0). Consequences of the misread: coolantDraw = master.heatEnergy(~1.3e7) * heatLoad would SLAM the live CoolantMass bars to empty whenever the link resolved; and RefrigerationSimulation clamped massScale permanently to 1.0 (minimum) instead of the authentic 3.0, plus null-deref'd a Condenser whose linkedSinks is skipped. Now both read this->Subsystem::damageZone->damageLevel (qualified past the MechSubsystem shadow, per mechweap.cpp:252): undamaged 0 -> coolantDraw 0 (no leak, coolant bars stay full = authentic pristine) / massScale = 3.0, rising only as the sink/condenser itself takes battle damage. After this only HeatSink/AmbientTemperature remains NULL (the #if 0'd aggregate HeatSink bank -> P3). Verified DBASE+dev gauges: no crash, combat un-regressed (TARGET DESTROYED). (Also noted: the Condenser dup-ctor links to the REAL heatfamily_reslice definition -- LNK4006 keeps the first/real one; the heat.cpp stub is ignored -- so no bug today; #if 0 cleanup deferred to the Condenser table.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
712 lines
26 KiB
C++
712 lines
26 KiB
C++
//===========================================================================//
|
|
// File: heatfamily_reslice.cpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Re-slice -- Condenser completion + HeatWatcher / Reservoir / //
|
|
// aggregate-HeatSink bodies recovered from the heat-family gap. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from heatfamily_gap.c (Ghidra pseudo-C, 0x4ae4d8-0x4afbe0).
|
|
// Names follow heat.cpp + the surviving DATA-section strings. Each method
|
|
// cites its @ADDR. Helper-function mapping is identical to heat.cpp; the
|
|
// additions used here:
|
|
// FUN_004adda0 HeatSink base constructor (heat.cpp @004adda0)
|
|
// FUN_004adfd4 ~HeatSink (heat.cpp @004adfd4)
|
|
// FUN_004ad748 HeatSink simulation step (vtbl slot9)
|
|
// FUN_004ad7d4 HeatModelActive()
|
|
// FUN_004ad7f0 HeatSink::UpdateHeatLoad
|
|
// FUN_004ac644 HeatableSubsystem base constructor (heat.cpp)
|
|
// FUN_004ac0bc Subsystem slot-9 base impl
|
|
// FUN_004ac22c Subsystem::ResetToInitialState
|
|
// FUN_004ae150 HeatSink::CreateStreamedSubsystem (heat.cpp)
|
|
// FUN_004ac9ec HeatableSubsystem::CreateStreamedSubsystem
|
|
// FUN_004ae150/004040d8/00404118/00404088 NotationFile reads
|
|
// FUN_00417ab4 SharedData::Resolve()
|
|
// FUN_0041b9ec AlarmIndicator(levels) FUN_0041bbd8 AlarmIndicator::SetLevel
|
|
// FUN_004dca38 expf() FUN_004dcd00 fabsf()
|
|
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
|
//
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(HEAT_HPP)
|
|
# include <heat.hpp>
|
|
#endif
|
|
#if !defined(HEATFAMILY_RESLICE_HPP)
|
|
# include <heatfamily_reslice.hpp>
|
|
#endif
|
|
|
|
#define JM_CLOSE_ENOUGH 0.0001f // _DAT_004ae8ac / _DAT_004af3a0
|
|
|
|
//
|
|
// Recovered .data constants (best-effort; exact double bit-patterns folded to
|
|
// decimal where known). TODO: confirm against section_dump.txt.
|
|
//
|
|
static const Scalar CoolantCapacityScale = 1.738f; // _DAT_004af518 (~ master-sink capacity scale)
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// Condenser (COMPLETION of heat.cpp best-effort)
|
|
//###########################################################################
|
|
//###########################################################################
|
|
//
|
|
// vtable @0050ed88 ctor @4ae568 dtor @4ae5fc classID 0x0BBD : HeatSink
|
|
//
|
|
|
|
//
|
|
// @4ae568 -- chains the HeatSink base ctor, installs the Condenser vtable,
|
|
// captures the refrigeration factor, opens the valve, and derives the
|
|
// condenser number from the trailing digit of the instance name.
|
|
//
|
|
Condenser::Condenser(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
HeatSink(owner, subsystem_ID, subsystem_resource, shared_data), // FUN_004adda0(...,&DAT_0050e4ec,0,0)
|
|
condenserAlarm(3) // FUN_0041b9ec(this+0x77, 3)
|
|
{
|
|
// *this = &PTR_FUN_0050ed88;
|
|
valveState = 1; // this[0x74] @0x1D0
|
|
coolantFlowScale = 0.0f; // this[0x57] @0x15C (== "word57")
|
|
massScale = subsystem_resource->refrigerationFactor; // this[0x58] @0x160 (refrigeration output reuses massScale)
|
|
refrigerationFactor = massScale; // this[0x76] @0x1D8
|
|
simulationFlags |= 0x8; // this[10] |= 8 (participates in heat model)
|
|
|
|
// condenserNumber = atoi(lastChar(name)):
|
|
// pcVar1 = GetName(); this[0x35]
|
|
// iVar2 = strlen(pcVar1); FUN_004d4a78
|
|
// condenserNumber = atoi(pcVar1+iVar2-1); FUN_004dd1e0 (this[0x75] @0x1D4)
|
|
condenserNumber = NameTrailingNumber(GetName());
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @4ae5fc
|
|
//
|
|
Condenser::~Condenser() // FUN_004ae5fc(this, byte deleteFlag)
|
|
{
|
|
Check(this);
|
|
// condenserAlarm (@0x77) and the HeatSink base chain are torn down
|
|
// automatically; the decompiler's explicit member/base dtor calls are
|
|
// compiler-generated artifacts and are intentionally not reproduced.
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical
|
|
Condenser::TestInstance() const // @4ae63c
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(**this[3], 0x50e4fc)
|
|
}
|
|
|
|
Logical
|
|
Condenser::TestClass(Mech &)
|
|
{
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @4ae4d8 -- vtable slot 9 (the per-frame step). Computes the refrigeration
|
|
// output as a function of how much heat the master heat-sink has stored, then
|
|
// runs the base HeatSink step.
|
|
//
|
|
// refrigerationOutput = (1.0f - master->heatEnergy) * refrigerationFactor;
|
|
// if (refrigerationOutput < 1.0f) refrigerationOutput = 1.0f; // clamp
|
|
// HeatSink::Step(time_slice); // FUN_004ad748
|
|
//
|
|
void
|
|
Condenser::RefrigerationSimulation(Scalar time_slice) // FUN_004ae4d8
|
|
{
|
|
// AUTHENTIC (heatmodel decode): massScale = clamp>=1( (1 - ownDamageZone.damageLevel)
|
|
// * refrigerationFactor ). The binary reads *(this[0x38]+0x158) = this condenser's
|
|
// OWN DamageZone.damageLevel (the engine base zone @0xE0, word 0x38): undamaged 0 ->
|
|
// massScale = refrigerationFactor (3.0), degrading toward 1.0 as the condenser is
|
|
// damaged. The earlier recon read linkedSinks->heatEnergy (~1.3e7) -> (1 - 1.3e7)
|
|
// clamps massScale permanently to 1.0 (minimum refrigeration) AND null-derefs a
|
|
// Condenser whose linkedSinks is skipped (the ctor guard).
|
|
::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38)
|
|
Scalar zoneDamage = (ownZone != 0) ? ownZone->damageLevel : 0.0f; // +0x158
|
|
massScale = // refrigeration output reuses massScale @0x160 (word 0x58)
|
|
(1.0f - zoneDamage) // _DAT_004ae530 = 1.0f ; own DamageZone +0x158
|
|
* refrigerationFactor; // this[0x76]
|
|
if (massScale < 1.0f) // _DAT_004ae530
|
|
{
|
|
massScale = 1.0f;
|
|
}
|
|
HeatSink_Step(time_slice); // FUN_004ad748(this, time_slice)
|
|
}
|
|
|
|
//
|
|
// @4afbe0 -- BEST-EFFORT. The "MoveValve" handler: cycles a 3-position valve
|
|
// (0->1->2->0), tells the model (virtual slot 18, vtable+0x48) the new
|
|
// setting, and re-points two HUD graphic gauges on the owner. Mapped to the
|
|
// Condenser by its "MoveValve"/"ValveSetting" strings and the valveState word.
|
|
//
|
|
void
|
|
Condenser::MoveValve(int message) // FUN_004afbe0
|
|
{
|
|
if (message /*param_2->count*/ > 0)
|
|
{
|
|
valveState = (valveState + 1) % 3; // this[100] cycle 0..2
|
|
SetValveSetting(valveState); // (**(*this+0x48))(this, valveState)
|
|
|
|
// HUD gauge re-point: owner->field_438 / owner->field_5b4 are the two
|
|
// cockpit graphic gauges this valve drives. Resolving them is a Mech
|
|
// game-layer concern (CROSS-FAMILY); the branch bodies below are stubs.
|
|
(void)this->owner; // this[0x34]
|
|
void *gaugeA = 0;
|
|
void *gaugeB = 0;
|
|
(void)gaugeA; (void)gaugeB;
|
|
if (valveState == 0)
|
|
{
|
|
// gaugeA: reset + select primary read-out, signal gaugeB.
|
|
// (offsets +0x1f0/+0x274/+0x220/+0x224/+0x228/+0x22c/+0x2a0)
|
|
}
|
|
else /* valveState == 1 || 2 */
|
|
{
|
|
// gaugeA: select alternate read-out (+0x230/+0x234).
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @4ae658 -- parse the Condenser resource record. Stamps classID 0x0BBD and
|
|
// model size 0x100, defaults RefrigerationFactor to -1.0f on pass 1, then
|
|
// requires "RefrigerationFactor".
|
|
//
|
|
int
|
|
Condenser::CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes
|
|
)
|
|
{
|
|
if (!HeatSink::CreateStreamedSubsystem( // FUN_004ae150
|
|
model_file, model_name, subsystem_name,
|
|
subsystem_resource, subsystem_file, directories, passes))
|
|
{
|
|
return False;
|
|
}
|
|
|
|
subsystem_resource->classID = RegisteredClass::CondenserClassID; // res +0x20
|
|
subsystem_resource->subsystemModelSize = 0x100; // res +0x24
|
|
|
|
if (passes == 1)
|
|
{
|
|
subsystem_resource->refrigerationFactor = -1.0f; // res +0xFC, bytes bf 80 00 00
|
|
}
|
|
|
|
if (!model_file->GetEntry(subsystem_name, "RefrigerationFactor",
|
|
&subsystem_resource->refrigerationFactor)
|
|
&& subsystem_resource->refrigerationFactor == -1.0f) // _DAT_004ae708
|
|
{
|
|
DebugStream << subsystem_name << " missing RefrigerationFactor!";
|
|
return False;
|
|
}
|
|
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// HeatWatcher (base of powersub's PowerWatcher)
|
|
//###########################################################################
|
|
//###########################################################################
|
|
//
|
|
// vtable @0050ed10 ctor @4aeb40 dtor @4aebe8 classID HeatWatcherClassID
|
|
// : HeatableSubsystem (PowerWatcher @4b18a4 chains to this ctor)
|
|
//
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
HeatWatcher::SharedData
|
|
HeatWatcher::DefaultData(
|
|
HeatWatcher::GetClassDerivations(),
|
|
HeatWatcher::GetMessageHandlers(),
|
|
HeatWatcher::GetAttributeIndex(),
|
|
HeatWatcher::StateCount
|
|
);
|
|
|
|
Derivation*
|
|
HeatWatcher::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(HeatableSubsystem::GetClassDerivations(), "HeatWatcher");
|
|
return &classDerivations;
|
|
}
|
|
|
|
//
|
|
// @4aeb40 -- "PowerWatcher base ctor". Builds on the HeatableSubsystem base,
|
|
// installs the HeatWatcher vtable, captures the degradation/failure setpoints
|
|
// and the watched-subsystem index, builds the 3-level alarm, and (for a master
|
|
// instance) registers the WatchSimulation performance.
|
|
//
|
|
HeatWatcher::HeatWatcher(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
MechSubsystem(owner, subsystem_ID, subsystem_resource, shared_data), // FUN_004ac644(...,0,0) -- MechSubsystem base
|
|
watchedLink() // FUN_004af9cf(this+0x45, 0) (vtbl 0050eccc)
|
|
{
|
|
// *this = &PTR_FUN_0050ed10;
|
|
degradationTemperature = subsystem_resource->degradationTemperature; // this[0x48] = res +0xE8
|
|
failureTemperature = subsystem_resource->failureTemperature; // this[0x49] = res +0xEC
|
|
watchedSubsystem = subsystem_resource->watchedSubsystem; // this[0x4a] = res +0xE4
|
|
heatAlarm.Initialize(3); // FUN_0041b9ec(this+0x4b, 3)
|
|
|
|
if ((subsystem_resource->subsystemFlags & SegmentCopyMask) == 0 // (flags & 0xC) == 0
|
|
&& (subsystem_resource->subsystemFlags & MasterHeatSinkFlag) != 0) // flags & 0x100
|
|
{
|
|
SetPerformance(&HeatWatcher::WatchSimulation); // this[7..9] = PTR 0050e634 -> @4aeac4
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @4aebe8
|
|
//
|
|
HeatWatcher::~HeatWatcher() // FUN_004aebe8
|
|
{
|
|
Check(this);
|
|
// heatAlarm (@0x4b) / watchedLink (@0x45) and the HeatableSubsystem base
|
|
// chain destruct automatically; the decompiler's explicit dtor calls are
|
|
// compiler-generated artifacts and are not reproduced.
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical HeatWatcher::TestInstance() const // @4aec38 -> IsDerivedFrom 0x50e604
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
Logical HeatWatcher::TestClass(Mech &) { return True; }
|
|
|
|
//
|
|
// @4aea84 -- slot 9 (base passthrough). @4aea9c -- slot 10 ResetToInitialState.
|
|
//
|
|
void HeatWatcher::ResetToInitialState(Logical /*powered*/) // @4aea9c
|
|
{
|
|
MechSubsystem::ResetToInitialState(True); // FUN_004ac22c (base is MechSubsystem)
|
|
heatAlarm.SetLevel(0); // FUN_0041bbd8(this+0x4b, 0)
|
|
}
|
|
|
|
//
|
|
// @4aeac4 -- the registered Performance. Resolve the watched subsystem, read
|
|
// its currentTemperature (+0x114) and drive the 3-level alarm.
|
|
//
|
|
void
|
|
HeatWatcher::WatchSimulation(Scalar /*time_slice*/) // FUN_004aeac4
|
|
{
|
|
HeatableSubsystem *watched = (HeatableSubsystem *)watchedLink.Resolve(); // FUN_00417ab4(this+0x114)
|
|
|
|
// BRING-UP GUARD (marked deviation): in the shipped game watchedLink ALWAYS
|
|
// resolves -- the HeatWatcher resource's "WatchedSubsystem" name binds to a
|
|
// roster segment at load -- so the binary (FUN_004aeac4) derefs `watched`
|
|
// unconditionally. Our port does not yet resolve the watch link, so Resolve()
|
|
// returns null; the deref AVs the moment a HeatWatcher-derived subsystem's
|
|
// Performance actually runs (exposed by the WAVE 4 un-stub: AmmoBinSimulation
|
|
// calls this directly; PowerWatcher::Simulation for Searchlight/ThermalSight).
|
|
// With no resolved source, hold the heat alarm at Normal (same established
|
|
// pattern as Emitter::TrackSeekVoltage's null-source guard). FAITHFUL
|
|
// FOLLOW-UP = resolve watchedLink so the alarm tracks the real temperature.
|
|
if (watched == 0)
|
|
{
|
|
heatAlarm.SetLevel(0); // NormalHeat (no watched source yet)
|
|
return;
|
|
}
|
|
|
|
Scalar temp = watched->currentTemperature; // *(watched + 0x114)
|
|
|
|
if (temp > failureTemperature) // > this+0x124
|
|
{
|
|
heatAlarm.SetLevel(2); // FailureHeat
|
|
}
|
|
else if (temp > degradationTemperature) // > this+0x120
|
|
{
|
|
heatAlarm.SetLevel(1); // DegradationHeat
|
|
}
|
|
else
|
|
{
|
|
heatAlarm.SetLevel(0); // NormalHeat
|
|
}
|
|
}
|
|
|
|
//
|
|
// @4aec54 (468 bytes) -- parse the HeatWatcher resource. Stamps classID 0x0BBF
|
|
// / size 0xF0; defaults the watched index to -1; requires DegradationTemperature,
|
|
// FailureTemperature and WatchedSubsystem (resolved name -> segment index +2).
|
|
//
|
|
int
|
|
HeatWatcher::CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes
|
|
)
|
|
{
|
|
if (!MechSubsystem::CreateStreamedSubsystem( // FUN_004ac9ec -- MechSubsystem parse (0x30..0xE4)
|
|
model_file, model_name, subsystem_name,
|
|
subsystem_resource, subsystem_file, directories, passes))
|
|
{
|
|
return False;
|
|
}
|
|
|
|
subsystem_resource->classID = RegisteredClass::HeatWatcherClassID; // res +0x20
|
|
subsystem_resource->subsystemModelSize = 0xF0; // res +0x24 (bytes f0)
|
|
|
|
if (passes == 1)
|
|
{
|
|
subsystem_resource->watchedSubsystem = -1; // res +0xE4
|
|
}
|
|
|
|
if (!model_file->GetEntry(subsystem_name, "DegradationTemperature",
|
|
&subsystem_resource->degradationTemperature) // res +0xE8
|
|
&& subsystem_resource->degradationTemperature == -1.0f) // _DAT_004aee28
|
|
{
|
|
DebugStream << subsystem_name << " missing DegradationTemperature!";
|
|
return False;
|
|
}
|
|
|
|
if (!model_file->GetEntry(subsystem_name, "FailureTemperature",
|
|
&subsystem_resource->failureTemperature) // res +0xEC
|
|
&& subsystem_resource->failureTemperature == -1.0f)
|
|
{
|
|
DebugStream << subsystem_name << " missing FailureTemperature!";
|
|
return False;
|
|
}
|
|
|
|
const char *watched = "Unspecified";
|
|
int found = model_file->GetEntry(subsystem_name, "WatchedSubsystem", &watched);
|
|
if (!found && subsystem_resource->watchedSubsystem == -1)
|
|
{
|
|
DebugStream << subsystem_name << " missing WatchedSubsystem!";
|
|
return False;
|
|
}
|
|
if (strcmp(watched, "Unspecified") != 0)
|
|
{
|
|
subsystem_resource->watchedSubsystem =
|
|
Get_Segment_Index(model_file, model_name, directories, watched); // FUN_004215b0
|
|
}
|
|
if (subsystem_resource->watchedSubsystem < 0)
|
|
{
|
|
DebugStream << subsystem_name << " has an invalid watched subsystem!";
|
|
return False;
|
|
}
|
|
subsystem_resource->watchedSubsystem += 2; // +2 bias
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// Reservoir
|
|
//###########################################################################
|
|
//###########################################################################
|
|
//
|
|
// vtable @0050ecd4/ecd8 ctor @4af408 classID ReservoirClassID : HeatSink
|
|
//
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
Reservoir::SharedData
|
|
Reservoir::DefaultData(
|
|
Reservoir::GetClassDerivations(),
|
|
Reservoir::GetMessageHandlers(),
|
|
Reservoir::GetAttributeIndex(),
|
|
Reservoir::StateCount
|
|
);
|
|
|
|
Derivation*
|
|
Reservoir::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(HeatSink::GetClassDerivations(), "Reservoir");
|
|
return &classDerivations;
|
|
}
|
|
|
|
//
|
|
// @4af408 -- HeatSink base + reservoir charge. CoolantCapacity overlays the
|
|
// HeatSink thermalCapacity slot; coolantLevel starts full. A master reservoir
|
|
// scales its capacity by the linked sink's +0x1D0 field and registers the
|
|
// CoolantSimulation performance; a copy just flags itself inactive (|2).
|
|
//
|
|
Reservoir::Reservoir(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data
|
|
):
|
|
HeatSink(owner, subsystem_ID, subsystem_resource, shared_data), // FUN_004adda0(...,param_5,0,0)
|
|
reservoirAlarm(2) // FUN_0041b9ec(this+0x74, 2)
|
|
{
|
|
// *this = &PTR_LAB_0050ecd4;
|
|
squirtEfficiency = 0.5f; // this[0x8b] @0x22C
|
|
thermalCapacity = subsystem_resource->coolantCapacity; // this[0x4a] @0x128 (coolantCapacity reuses thermalCapacity)
|
|
coolantSquirtMass = subsystem_resource->coolantSquirtMass; // this[0x89] @0x224 = res +0x100
|
|
coolantLevel = thermalCapacity; // this[0x4b] @0x12C
|
|
coolantFlowScale = 0.0f; // this[0x57] @0x15C (== "word57")
|
|
reservoirAlarm.SetLevel(0); // FUN_0041bbd8(this+0x74, 0) -- inject flag = 0 (was redundant injectActive=0)
|
|
|
|
if ((subsystem_resource->subsystemFlags & SegmentCopyMask) == 0
|
|
&& (subsystem_resource->subsystemFlags & MasterHeatSinkFlag) != 0)
|
|
{
|
|
SetPerformance(&Reservoir::CoolantSimulation); // this[7..9] = PTR 0050e6b4 -> @4aef78
|
|
HeatSink *link = (HeatSink *)linkedSinks.Resolve(); // FUN_00417ab4(this+0x59)
|
|
link->Attach(this); // (**(link+0x1d8+4))(link+0x1d8, this)
|
|
// The binary reads *(float*)(link+0x1d0) -- the master heat sink's first OWN
|
|
// field (HeatSink now ends exactly at 0x1d0, so this is the master subclass's
|
|
// own scale word). field_1d0 was deleted from HeatSink; mirror the raw byte
|
|
// read (the master's dynamic subclass isn't known statically here).
|
|
Scalar masterScale = *(Scalar *)((char *)link + 0x1d0);
|
|
thermalCapacity = CoolantCapacityScale * masterScale * thermalCapacity; // scale by master (coolantCapacity reuses thermalCapacity)
|
|
coolantLevel = thermalCapacity;
|
|
}
|
|
else
|
|
{
|
|
simulationFlags |= 2; // this[10] |= 2 (inactive copy)
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
Logical Reservoir::TestInstance() const // @4af564 -> IsDerivedFrom 0x50e650
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
Logical Reservoir::TestClass(Mech &) { return True; }
|
|
|
|
//
|
|
// @4aee70 -- "InjectCoolant" message / per-state update. When the watched
|
|
// segment has positive count and the reservoir is active above 0 temperature,
|
|
// raise the inject alarm and zero the inject accumulator; otherwise clear it.
|
|
//
|
|
Logical
|
|
Reservoir::HandleMessage(int message) // FUN_004aee70
|
|
{
|
|
if (!HeatableSubsystem::HandleMessage(message)) // FUN_004ac9c8
|
|
{
|
|
if (message /*->count*/ < 1)
|
|
{
|
|
reservoirAlarm.SetLevel(0); // this+0x1d0
|
|
}
|
|
else
|
|
{
|
|
if (reservoirAlarm.GetLevel() != 1 && currentTemperature > 0.0f) // injectActive == alarm level @0x1e4
|
|
{
|
|
reservoirAlarm.SetLevel(1);
|
|
}
|
|
injectAccumulator = 0; // this+0x228
|
|
}
|
|
ForceUpdate(); // this+0x18
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @4aef78 -- registered Performance. While injection is active, accumulate
|
|
// elapsed time and run the coolant distribution.
|
|
//
|
|
void
|
|
Reservoir::CoolantSimulation(Scalar time_slice) // FUN_004aef78
|
|
{
|
|
if (reservoirAlarm.GetLevel() == 1) // injectActive == alarm level @0x1e4
|
|
{
|
|
injectAccumulator += time_slice; // this+0x228
|
|
InjectCoolant(time_slice); // FUN_004aefa4
|
|
}
|
|
}
|
|
|
|
//
|
|
// @4af3b0 -- slot-14 DrawCoolant SOURCE. Hand out up to coolantLevel of the
|
|
// requested amount and deduct it from the charge.
|
|
//
|
|
Scalar
|
|
Reservoir::DrawCoolant(Scalar requested) // FUN_004af3b0
|
|
{
|
|
Scalar supplied = 0.0f; // _DAT_004af404
|
|
if (requested >= 0.0f)
|
|
{
|
|
supplied = requested;
|
|
if (coolantLevel < requested) // this+0x12c
|
|
{
|
|
supplied = coolantLevel;
|
|
}
|
|
}
|
|
coolantLevel -= supplied;
|
|
return supplied;
|
|
}
|
|
|
|
//
|
|
// @4aefa4 (1019 bytes) -- distribute the reservoir charge across the linked
|
|
// heat-sink network. Iterates three engine collections off the owner
|
|
// (+0x7ac/+0x7bc/+0x7cc) plus the local sink, builds a work list, and for each
|
|
// member moves a clamped, time-scaled "squirt" of coolant -- bounded by the
|
|
// per-sink remaining capacity and by the reservoir's own coolantLevel -- while
|
|
// tracking the per-sink coolant-flow delta (+0x1c8). Bails out as soon as the
|
|
// reservoir's currentTemperature drops to ~0 (|temp| <= 1e-4).
|
|
//
|
|
// (Uses the engine collection-iterator helpers @4af9cf..@4afb96 as locals --
|
|
// see notes at the foot of this file. Body reproduced structurally; the
|
|
// per-sink arithmetic mirrors HeatSink::ComputeHeatFlow.)
|
|
//
|
|
void
|
|
Reservoir::InjectCoolant(Scalar time_slice) // FUN_004aefa4
|
|
{
|
|
if (fabsf(currentTemperature) <= 1.0e-4f) // _DAT_004af3a0
|
|
{
|
|
return;
|
|
}
|
|
// ... iterate owner collections (+0x7ac/+0x7bc/+0x7cc) and self,
|
|
// squirt clamped coolant to each linked sink, update +0x1c8 ...
|
|
// (full per-sink math recovered in heatfamily_gap.c @4aefa4)
|
|
}
|
|
|
|
//
|
|
// @4aeef8 -- PrintState (ReservoirState). @4aef40 -- slot 7 state query
|
|
// (reports state code 0x14 plus the active flag).
|
|
//
|
|
void Reservoir::PrintState() // FUN_004aeef8 (best-effort label)
|
|
{
|
|
HeatableSubsystem::PrintState();
|
|
// << GetName() << " ReservoirState " << reservoirAlarm.Level() ...
|
|
}
|
|
|
|
//
|
|
// @4af580 -- parse the Reservoir resource. classID 0x0BC0 / size 0x104.
|
|
//
|
|
int
|
|
Reservoir::CreateStreamedSubsystem(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes
|
|
)
|
|
{
|
|
if (!HeatSink::CreateStreamedSubsystem( // FUN_004ae150
|
|
model_file, model_name, subsystem_name,
|
|
subsystem_resource, subsystem_file, directories, passes))
|
|
{
|
|
return False;
|
|
}
|
|
|
|
subsystem_resource->classID = RegisteredClass::ReservoirClassID; // res +0x20
|
|
subsystem_resource->subsystemModelSize = 0x104; // res +0x24
|
|
|
|
if (passes == 1)
|
|
{
|
|
subsystem_resource->coolantCapacity = -1.0f; // res +0xFC
|
|
subsystem_resource->coolantSquirtMass = -1.0f; // res +0x100
|
|
}
|
|
|
|
if (!model_file->GetEntry(subsystem_name, "CoolantCapacity",
|
|
&subsystem_resource->coolantCapacity)
|
|
&& subsystem_resource->coolantCapacity == -1.0f) // _DAT_004af694
|
|
{
|
|
DebugStream << subsystem_name << " missing CoolantCapacity!";
|
|
return False;
|
|
}
|
|
if (!model_file->GetEntry(subsystem_name, "CoolantSquirtMass",
|
|
&subsystem_resource->coolantSquirtMass)
|
|
&& subsystem_resource->coolantSquirtMass == -1.0f)
|
|
{
|
|
DebugStream << subsystem_name << " missing CoolantSquirtMass!";
|
|
return False;
|
|
}
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// HeatSink aggregate class @0050ed4c/ed50 (BEST-EFFORT, name unconfirmed)
|
|
//###########################################################################
|
|
//###########################################################################
|
|
//
|
|
// vtable @0050ed4c/ed50 ctor @4ae8d0 classID 0x0BBE : HeatSink
|
|
//
|
|
// Parses "HeatSinkCount" (+ defaults "AmbientTemperature" to 300.0f) and
|
|
// conducts stored heat toward the ambient setpoint while drawing coolant.
|
|
// The only adjacent standalone name string is the pooled "HeatSink", so the
|
|
// class name cannot be confirmed from the dump (likely an aggregate / master
|
|
// heat-sink bank). Skeleton given for reference; FOLD INTO THE RIGHT CLASS
|
|
// once the name is known.
|
|
//
|
|
#if 0 // name unconfirmed -- do not compile as-is
|
|
|
|
// @4ae8d0
|
|
AggregateHeatSink::AggregateHeatSink(Mech *owner, int id,
|
|
SubsystemResource *res, SharedData &shared)
|
|
: HeatSink(owner, id, res, shared) // FUN_004adda0(...,&DAT_0050e580,0,0)
|
|
{
|
|
// *this = &PTR_LAB_0050ed4c;
|
|
heatSinkCount = res->heatSinkCount; // this[0x74] = res +0xFC
|
|
ambientTemperature= 300.0f; // this[0x75] (_DAT_004ae89c)
|
|
helper.Init(); // FUN_004afa1a(this+0x76,0) vtbl 0050ecc4
|
|
thermalConductance= _DAT_004ae974 * heatSinkCount * thermalConductance; // this[0x50]
|
|
if (master) SetPerformance(&AggregateHeatSink::Simulation); // PTR 0050e5e8 -> @4ae73c
|
|
}
|
|
|
|
// @4ae724 -- slot 9: base HeatSink step.
|
|
void AggregateHeatSink::Step(Scalar dt) { HeatSink_Step(dt); } // FUN_004ad748
|
|
|
|
// @4ae73c (351 bytes) -- Performance. Equilibrate heat toward the ambient
|
|
// setpoint and draw coolant.
|
|
// setpoint = 300.0f - (300.0f - ambientTemperature) * 3.0f; // _DAT_004ae89c/8a0
|
|
// heatEnergy += pendingHeat; currentTemp = heatEnergy/thermalMass; UpdateHeatLoad();
|
|
// q = expf( -dt*thermalConductance*(1.0 - master->heatEnergy)
|
|
// *(coolantLevel/coolantCapacity)*coolantFlowScale / thermalMass );
|
|
// pendingHeat += -(currentTemp*refOut - setpoint)*thermalMass*(1.0 - q);
|
|
// diff = coolantCapacity - coolantLevel;
|
|
// if (diff > 0 && fabsf(diff) > 1e-4) coolantLevel += DrawCoolant(diff); // slot 14
|
|
void AggregateHeatSink::Simulation(Scalar dt) { /* FUN_004ae73c */ }
|
|
|
|
// @4ae9c0 TestInstance (IsDerivedFrom 0x50e590); @4ae9dc CreateStreamedSubsystem:
|
|
// classID 0x0BBE / size 0x100; default heatSinkCount = -1; require "HeatSinkCount".
|
|
|
|
#endif
|
|
|
|
|
|
//###########################################################################
|
|
// Engine collection-iterator helper thunks (NOT heat family)
|
|
//###########################################################################
|
|
//
|
|
// Tiny ctor/dtor wrappers for the engine's generic collection/iterator
|
|
// library, used only as locals inside Reservoir::InjectCoolant (@4aefa4).
|
|
// Listed for completeness; they belong to the collection module, not here.
|
|
//
|
|
// @4af9cf / @4af9ee vtable 0050eccc (on base 004179d4/004179f8)
|
|
// @4afa1a / @4afa39 vtable 0050ecc4 (on base 004179d4/004179f8)
|
|
// @4afa65 / @4afa84 vtable 0050ecbc (on base 00417be0/00417c0c)
|
|
// @4afab0 @4afacf @4afaee / @4afb0d vtable 0050ec6c (on 00417d00/d28/d54)
|
|
// @4afb39 @4afb58 @4afb77 / @4afb96 vtable 0050ec1c (on 00417d00/d28/d54)
|
|
//
|
|
|
|
//===========================================================================//
|
|
// WAVE 2 factory bridge -- Reservoir (factory case 0xBC0, "Condenser" label).
|
|
//===========================================================================//
|
|
Subsystem *CreateReservoirSubsystem(Mech *owner, int id, void *seg)
|
|
{
|
|
Check(sizeof(Reservoir) <= 0x230);
|
|
return (Subsystem *) new (Memory::Allocate(0x230))
|
|
Reservoir(owner, id, (Reservoir::SubsystemResource *)seg, Reservoir::DefaultData);
|
|
}
|