diff --git a/restoration/source410/BT/HEAT.CPP b/restoration/source410/BT/HEAT.CPP index 2a361ff7..48af947d 100644 --- a/restoration/source410/BT/HEAT.CPP +++ b/restoration/source410/BT/HEAT.CPP @@ -946,3 +946,138 @@ void DEBUG_STREAM << flush; } } + +//########################################################################### +//######################### AggregateHeatSink ########################### +//########################################################################### + +Derivation + AggregateHeatSink::ClassDerivations( + HeatSink::ClassDerivations, + "HeatSinkBank" + ); + +AggregateHeatSink::SharedData + AggregateHeatSink::DefaultData( + AggregateHeatSink::ClassDerivations, + HeatSink::MessageHandlers, + Subsystem::AttributeIndex, + Subsystem::StateCount + ); + +// +//############################################################################# +// The heat-sink bank (binary ctor @4ae8d0): the aggregate count scales the +// conductance (0.1 x count, byte-verified), the ambient setpoint defaults +// 300 K (the mission's [mission] temperature overwrites it in the PlayerLink +// pass -- that override joins the Mech-PlayerLink wave), and a master runs +// the RADIATOR Performance instead of the base heat step. +//############################################################################# +// +AggregateHeatSink::AggregateHeatSink( + Mech *owner, + int subsystem_ID, + SubsystemResource *subsystem_resource, + SharedData &shared_data +): + HeatSink(owner, subsystem_ID, subsystem_resource, shared_data) +{ + Check(owner); + Check_Pointer(subsystem_resource); + + heatSinkCount = subsystem_resource->heatSinkCount; + ambientTemperature = 300.0f; + + thermalConductance = 0.1f * (Scalar)heatSinkCount * thermalConductance; + + if (getenv("BT_POWER_LOG")) + { + DEBUG_STREAM << "[bank] '" << GetName() + << "' heatSinkCount=" << heatSinkCount + << " conductance=" << thermalConductance << endl << flush; + } + + if (owner->GetInstance() != Entity::ReplicantInstance) + { + SetPerformance(&AggregateHeatSink::RadiatorSimulation); + } + + Check_Fpu(); +} + +AggregateHeatSink::~AggregateHeatSink() +{ +} + +Logical + AggregateHeatSink::TestClass(Mech &) +{ + return True; +} + +Logical + AggregateHeatSink::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// RadiatorSimulation -- the bank's per-frame step (binary @4ae73c), replacing +// the base HeatSinkSimulation: under the heat-model gate, absorb + recompute +// the load, then the AMBIENT RADIATOR -- relax the bank toward the setpoint +// target (300 - (300 - ambient) x 3) with rate k = conductance x (1 - own +// damage) x (coolant/capacity) x flowScale / mass. This is the system's ONLY +// heat exit. Tail: top the bank's coolant up from the attached store via the +// DrawCoolant virtual (the reservoir-attach routing joins that wave; the base +// supplies 0 until then, a harmless no-op). +//############################################################################# +// +void + AggregateHeatSink::RadiatorSimulation(Scalar time_slice) +{ + Check(this); + + if (HeatModelActive()) + { + heatEnergy += pendingHeat; + currentTemperature = heatEnergy / thermalMass; + UpdateHeatLoad(); + pendingHeat = 0.0f; + + Scalar target = 300.0f - (300.0f - ambientTemperature) * 3.0f; + Scalar decay = 1.0f - (Scalar)exp( + (double)(-(time_slice * thermalConductance + * (1.0f - GetSubsystemDamageLevel()) + * (coolantLevel / thermalCapacity) * coolantFlowScale) + / thermalMass) + ); + Scalar shed = -((currentTemperature * massScale - target) + * thermalMass * decay); + pendingHeat += shed; + + if (getenv("BT_HEAT_LOG")) + { + static Scalar bankAccum = 0.0f; + bankAccum += time_slice; + if (bankAccum >= 5.0f) + { + bankAccum = 0.0f; + DEBUG_STREAM << "[heat-t] " << GetName() << " (bank)" + << " T=" << currentTemperature + << " shed=" << shed + << " cool=" << coolantLevel << "/" << thermalCapacity + << " load=" << heatLoad << endl << flush; + } + } + } + + // + // Coolant top-up from the attached store. + // + Scalar deficit = thermalCapacity - coolantLevel; + if (deficit > 1.0e-4f) + { + coolantLevel += DrawCoolant(deficit); + } +} diff --git a/restoration/source410/BT/HEAT.HPP b/restoration/source410/BT/HEAT.HPP index fb61bcb5..f01e0c7f 100644 --- a/restoration/source410/BT/HEAT.HPP +++ b/restoration/source410/BT/HEAT.HPP @@ -423,4 +423,71 @@ Scalar refrigerationFactor; }; +//########################################################################### +//##################### AggregateHeatSink Resource ###################### +//########################################################################### + + struct AggregateHeatSink__SubsystemResource: + public HeatSink__SubsystemResource + { + int heatSinkCount; + }; + +//########################################################################### +//######################### AggregateHeatSink ########################### +//########################################################################### +// +// The mech's heat-sink BANK (binary classID 0x0BBE -- the value our VDATA +// enum names HeatSinkClassID; there is no streamed plain-HeatSink class). +// Holds the aggregate heat-sink count and the ambient-temperature setpoint, +// and runs the AMBIENT RADIATOR -- the only place heat ever LEAVES the mech. +// + class AggregateHeatSink: + public HeatSink + { + public: + static Derivation ClassDerivations; + static SharedData DefaultData; + + static Logical + TestClass(Mech &); + Logical + TestInstance() const; + + public: + typedef AggregateHeatSink__SubsystemResource SubsystemResource; + + AggregateHeatSink( + Mech *owner, + int subsystem_ID, + SubsystemResource *subsystem_resource, + SharedData &shared_data = DefaultData + ); + ~AggregateHeatSink(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // The radiator (binary @4ae73c) -- replaces the base HeatSinkSimulation on + // the bank: absorb, then relax toward the ambient setpoint (the heat EXIT), + // then top the bank's coolant up from the attached store. + // + public: + typedef void + (AggregateHeatSink::*Performance)(Scalar time_slice); + void + SetPerformance(Performance performance) + { + Check(this); + activePerformance = (Simulation::Performance)performance; + } + void + RadiatorSimulation(Scalar time_slice); + + int + GetHeatSinkCount() const { Check(this); return heatSinkCount; } + + protected: + int heatSinkCount; + Scalar ambientTemperature; + }; + #endif diff --git a/restoration/source410/BT/HEAT.NOTES.md b/restoration/source410/BT/HEAT.NOTES.md index 820c49ee..74aaf105 100644 --- a/restoration/source410/BT/HEAT.NOTES.md +++ b/restoration/source410/BT/HEAT.NOTES.md @@ -200,3 +200,26 @@ VERIFIED: spawn shares 6 x 0.166667; one MoveValve press -> Condenser1 valve=5 flow=0.5, the others 0.1 (valve/sum exactly); the flush arms via the real button message; NOVICE: both presses swallowed by the lockout (valve lines stay spawn-only, zero flush). Zero Fail throughout. + +## The heat-sink BANK (Phase 5.3.14, 2026-07-24) + +**AggregateHeatSink reconstructed** -- the binary's 0x0BBE class (the value our +VDATA enum names HeatSinkClassID; there is NO streamed plain-HeatSink -- the +walk now builds the bank). Ctor (@4ae8d0): heatSinkCount from res +0xFC +(bhk1 = 6, matching its six condensers), thermalConductance x 0.1 x count +(231000 -> 138600), ambient setpoint 300 (the mission-temperature overwrite +joins the Mech-PlayerLink wave). + +**RadiatorSimulation** (@4ae73c) replaces the base heat step on the bank -- +THE system's only heat exit: relax toward the ambient target with rate +k = conductance x (1 - damage) x (coolant/capacity) x flowScale / mass, then +top the bank's coolant from the attached store via the DrawCoolant virtual +(base 0 until the reservoir attach wave). VERIFIED signed-correct: the bank +warms 77 -> 300 from the cold start (shed positive, absorbing toward +ambient), then flips negative (-48K.. -167K/step) as fired heat pushes it +past ambient -- actively radiating the mech's heat away. + +**Reservoir master rescale**: capacity = 0.05 x bankCount x streamed +(0.05 x 6 x 20 = 6 -- the authentic tank), coolant refilled to it. + +Zero Fail; the full expert economy regression stays green. diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index f4c3efcb..e873228a 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -239,7 +239,12 @@ Mech::Mech( made = new Condenser(this, id, (Condenser::SubsystemResource *)seg); break; case HeatSinkClassID: - made = new HeatSink(this, id, (HeatSink::SubsystemResource *)seg); + // + // The 0x0BBE stream class is the mech's heat-sink BANK + // (AggregateHeatSink) -- there is no streamed plain-HeatSink; our + // VDATA enum name simply predates that discovery. + // + made = new AggregateHeatSink(this, id, (AggregateHeatSink::SubsystemResource *)seg); break; case HeatWatcherClassID: made = new HeatWatcher(this, id, (HeatWatcher::SubsystemResource *)seg); diff --git a/restoration/source410/BT/RESERVR.CPP b/restoration/source410/BT/RESERVR.CPP index c78fa6c3..8cea36c8 100644 --- a/restoration/source410/BT/RESERVR.CPP +++ b/restoration/source410/BT/RESERVR.CPP @@ -101,6 +101,28 @@ Reservoir::Reservoir( if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&Reservoir::CoolantSimulation); + + // + // The authentic master path scales the tank by the bank's aggregate + // heat-sink count: capacity = 0.05 x count x streamed (binary + // @4af408, CoolantCapacityScale byte-verified 0.05). The linked sink + // (from the resource) IS the bank. (The bank-side Attach that routes + // its DrawCoolant top-ups here joins the attach wave.) + // + HeatSink *link = (HeatSink *)linkedSinks.Resolve(); + if (link != NULL + && link->IsDerivedFrom(AggregateHeatSink::ClassDerivations)) + { + Scalar masterScale = + (Scalar)((AggregateHeatSink *)link)->GetHeatSinkCount(); + thermalCapacity = 0.05f * masterScale * thermalCapacity; + coolantLevel = thermalCapacity; + if (getenv("BT_POWER_LOG")) + { + DEBUG_STREAM << "[resv] capacity rescaled by bank count -> " + << thermalCapacity << endl << flush; + } + } } Check_Fpu();