From d38ab3f9c9e3ebeaec3a3e7dc1a22b634681e915 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Thu, 8 Oct 2026 18:29:57 +0200 Subject: [PATCH] fix: the hole reservation now runs after the accumulator re-plan, so value_acc_ is always sized to channels.size() before reserve_hole() writes into it. Co-Authored-By: Claude Sonnet 4.6 --- src/acquisition.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/acquisition.cpp b/src/acquisition.cpp index 4c9c774..b2d41d6 100644 --- a/src/acquisition.cpp +++ b/src/acquisition.cpp @@ -518,6 +518,19 @@ void Acquisition::on_data(const udps_frame_t* frame) { BufferStore& store = model_.live(); const u64 g = part.time_stride; + // The filter's groups are `g` long, so a re-plan has to start a fresh group rather than + // finish the one it was in with the wrong length. + if (acc_stride_ != g || acc_channels_ != channels.size()) { + acc_stride_ = g; + acc_channels_ = channels.size(); + value_acc_.assign(channels.size(), 0.0); + value_acc_n_.assign(channels.size(), 0); + math_sample_.assign(channels.size(), std::nanf("")); + time_acc_ = 0.0; + abs_time_acc_ = 0.0; + time_acc_n_ = 0; + } + // The gap the counter reports, reserved as NaN before this frame's own samples are // stamped: the hole belongs *ahead* of the data that revealed it. Running the missing // samples through the same per-sample loop below is the whole trick -- `tick_` advances @@ -528,6 +541,11 @@ void Acquisition::on_data(const udps_frame_t* frame) { // Capped at `kGlitchPackets`: 256 packet spans in one go is an outage, not loss, and // reserving one would push the entire retained history out of the ring to make room for // samples that are not coming. + // + // This block must follow the re-plan above: reserve_hole() runs the missing samples + // through the same decimation accumulators (value_acc_, value_acc_n_, math_sample_), + // which must already be sized to the current channel count and stride before any + // per-sample write. const std::uint32_t missing = frame->late ? 0u : frame->lost; if (missing > 0 && batch_ > 0 && double(missing) <= kGlitchPackets) { reserve_hole(missing, part, channels, store, g); @@ -542,19 +560,6 @@ void Acquisition::on_data(const udps_frame_t* frame) { std::fflush(stderr); } - // The filter's groups are `g` long, so a re-plan has to start a fresh group rather than - // finish the one it was in with the wrong length. - if (acc_stride_ != g || acc_channels_ != channels.size()) { - acc_stride_ = g; - acc_channels_ = channels.size(); - value_acc_.assign(channels.size(), 0.0); - value_acc_n_.assign(channels.size(), 0); - math_sample_.assign(channels.size(), std::nanf("")); - time_acc_ = 0.0; - abs_time_acc_ = 0.0; - time_acc_n_ = 0; - } - // Calibration is read per frame; it only changes under the same lock. TriggerEngine& engine = engine_; const int trig_channel = tc.channel;