diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index fe6cffe..a63f4f0 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -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 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 } diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index eef1827..4787412 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -512,8 +512,11 @@ inline int // Internal model helpers // public: + // time_slice >= 0: sample the filter at the POD's 28 Hz cadence (#119 + // -- the per-Perform filter is frame-rate-dependent, the myomer-kinetic + // class); negative (the ctor/reset prime calls) samples unconditionally. void - UpdateHeatLoad(); // @004ad7f0 + UpdateHeatLoad(Scalar time_slice = -1.0f); // @004ad7f0 void ClearHeatFilter(); // @004ad884 void diff --git a/game/reconstructed/heatfamily_reslice.cpp b/game/reconstructed/heatfamily_reslice.cpp index e295c4a..8fe9c96 100644 --- a/game/reconstructed/heatfamily_reslice.cpp +++ b/game/reconstructed/heatfamily_reslice.cpp @@ -1233,7 +1233,7 @@ void } heatEnergy += pendingHeat; // [0x56] += [0x72] currentTemperature = heatEnergy / thermalMass; // [0x45] = [0x56]/[0x55] - UpdateHeatLoad(); // FUN_004ad7f0 + UpdateHeatLoad(time_slice); // FUN_004ad7f0 (28 Hz cadence, #119) pendingHeat = 0.0f; Scalar target = 300.0f