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 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-10-08 18:29:57 +02:00
1 parent e9d259ac7f
commit d38ab3f9c9
1 file changed
+18 -13
+18 -13
View File
@@ -518,6 +518,19 @@ void Acquisition::on_data(const udps_frame_t* frame) {
BufferStore& store = model_.live(); BufferStore& store = model_.live();
const u64 g = part.time_stride; 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 // 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 // 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 // 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 // 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 // reserving one would push the entire retained history out of the ring to make room for
// samples that are not coming. // 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; const std::uint32_t missing = frame->late ? 0u : frame->lost;
if (missing > 0 && batch_ > 0 && double(missing) <= kGlitchPackets) { if (missing > 0 && batch_ > 0 && double(missing) <= kGlitchPackets) {
reserve_hole(missing, part, channels, store, g); reserve_hole(missing, part, channels, store, g);
@@ -542,19 +560,6 @@ void Acquisition::on_data(const udps_frame_t* frame) {
std::fflush(stderr); 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. // Calibration is read per frame; it only changes under the same lock.
TriggerEngine& engine = engine_; TriggerEngine& engine = engine_;
const int trig_channel = tc.channel; const int trig_channel = tc.channel;