BT410 Phase 5.3.14: AggregateHeatSink -- the bank + the ambient radiator (the heat EXIT)

The central heat bank is now its real class: the binary's 0x0BBE
AggregateHeatSink (the value our VDATA enum named HeatSinkClassID -- there is
no streamed plain HeatSink; the segment 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 [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; tail
  tops 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, then flips to actively radiating
  (-48K..-167K/step) once fired heat pushes it past ambient. Until now heat
  only ever POOLED in the central sink; the mech now genuinely sheds it.
- Reservoir master path: capacity = 0.05 x bankCount x streamed = 6 (the
  authentic tank), refilled.

Zero Fail; the expert economy regression stays green (202 heat events under
forced spam).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 08:54:21 -05:00
co-authored by Claude Fable 5
parent c01e57ab22
commit 6451a0a38d
5 changed files with 253 additions and 1 deletions
+135
View File
@@ -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);
}
}
+67
View File
@@ -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
+23
View File
@@ -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.
+6 -1
View File
@@ -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);
+22
View File
@@ -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();