#119: heatLoad filter restored to the POD's 28 Hz sample cadence

The clipping coolant-leak voice ("warning... war... warning coo...")
measured to a ReportLeak relaxation oscillation at a drained tank:
draw = zoneDamage x heatLoad hunts across the authored 0.0025/0.003
hysteresis band (constants byte-verified) as the dry sink's conduction
collapses and the tank trickle refills it. State machine, constants,
refill dynamics and the sequencer's chase-and-cut stop (T0 AUDSEQ.cpp)
are all the binary's own -- the ONE divergence was the 15-sample
heatLoad filter: per-Perform in the binary = a 0.536 s window at the
pod's 28 Hz, but only 0.25 s at the port's ~59 fps -- heatLoad twice as
twitchy, the flap at double the pod cadence (the myomer-kinetic dt-less
class, third instance).

UpdateHeatLoad now accrues real time and samples at 28 Hz on any
machine (catch-up capped at the 15-sample window). Per-instance clock
in a static map -- the heat-family layouts are factory-size-locked
(sizeof(Myomers) == 0x358 exact), no new members. Ctor/reset prime
calls sample unconditionally.

A/B (260 s leak-to-empty bench): trigger fires 12 -> 8, transitions
11 -> 7. The residual slow restart at a bone-dry tank is authentic 1995
behavior (same engine sequencer chase-cut); a minimum-retrigger
interval would be an opt-in deviation for the operator to decide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-02 20:05:06 -05:00
co-authored by Claude Fable 5
parent 1efe8efc68
commit 1df2c571ee
3 changed files with 46 additions and 5 deletions
+41 -3
View File
@@ -777,7 +777,7 @@ void
{
heatEnergy += pendingHeat;
currentTemperature = heatEnergy / thermalMass;
UpdateHeatLoad();
UpdateHeatLoad(time_slice); // 28 Hz filter cadence (#119)
// DIAG census (BT_HEAT_LOG, viewpoint mech): PER-INSTANCE 5-s timers --
// the old shared static timer aliased to whichever instance crossed the
@@ -850,8 +850,22 @@ void
// @004ad7f0 -- recompute the radiated/instantaneous heat and feed it through
// the running-average filter to produce the smoothed heatLoad reading.
//
// POD-CADENCE SAMPLING (#119, 2026-08-02). The binary adds ONE sample per
// Perform -- a dt-less per-frame term (the myomer-kinetic class, FUN_0041c018
// proves Performs ride the frame rate). 15 samples at the pod's 28 Hz = a
// 0.536 s smoothing window; sampled at the port's ~59 fps the window halves
// and heatLoad turns 2x as twitchy -- which doubles the cadence of the
// ReportLeak relaxation oscillation at a drained tank (draw = damage x
// heatLoad hunting across the authored 0.0025/0.003 hysteresis band), and
// THAT is the rapidly-clipping "warning coo-- war--" leak voice Oracle
// reproduced twice in 716. Sim calls pass their slice and samples accrue at
// 28 Hz on any machine; ctor/reset prime calls (negative dt) sample
// unconditionally. Per-instance clock lives in a static map (the census
// precedent above) because the heat-family layouts are factory-size-locked
// (sizeof(Myomers) == 0x358 exact) -- no new members.
//
void
HeatSink::UpdateHeatLoad()
HeatSink::UpdateHeatLoad(Scalar time_slice)
{
radiatedHeat = currentTemperature * coolantLevel;
@@ -865,7 +879,31 @@ void
sample = HeatLoadMaximum;
}
heatFilter.AddSample(sample); // FUN_0043ade4
if (time_slice >= 0.0f)
{
static std::map<const void *, Scalar> s_filterClock;
Scalar &clock = s_filterClock[this];
clock += time_slice;
const Scalar kPodTick = 1.0f / 28.0f; // [T1] pod Perform cadence
if (clock < kPodTick)
{
return; // heatLoad holds the last average
}
int catchUp = 0;
while (clock >= kPodTick && ++catchUp <= 15) // >15 samples saturate the window
{
clock -= kPodTick;
heatFilter.AddSample(sample); // FUN_0043ade4
}
if (catchUp > 15)
{
clock = 0.0f; // hitch: window already saturated
}
heatLoad = heatFilter.Average(); // FUN_0043ae0b
return;
}
heatFilter.AddSample(sample); // FUN_0043ade4 (prime call)
heatLoad = heatFilter.Average(); // FUN_0043ae0b
}