fix: store the stream clock as f64, so a stamp's precision does not decay with uptime
The time ring was f32 like the values, holding each stamp relative to an epoch fixed at the first sample ever seen -- so the number stored grew for as long as the app ran while the interval it had to resolve stayed one sample period. At 1 MS/s the f32 ULP reaches the sample period after about sixteen seconds, so two to four consecutive samples rounded onto one stamp after half a minute and eight after a minute. On screen that is a plateau the LOD envelope draws as a zero-width segment -- a vertical spike where the waveform should be -- and in an export it was three distinct diffs at 2, 2.5 and 3 ULP instead of one clock. A value's precision is a property of the signal; a timestamp's is a property of how long the scope has been running. Only the clock widens: the values are the memory budget. That makes the two slices different sizes per slot, so plan_partition() weighs its growth loop in bytes rather than in slots (and bytes_for() is what bounds the doubling); live_window_locked() loses the f32 round-trip of its bounds, which existed only to keep a sample sitting exactly on one; and EventSnapshot::stamps is f64 because a pause view is not rebased and so carries full stream time. Verified against the fast mock: np.diff over two million exported rows now holds exactly one step (1e-06, 1999999 times), no duplicate or backwards stamps, and the 200 MB ring still spans 8.39 s. Every documented probe assertion unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
eb4372242c
commit
ec93a9831d
10 files changed
+159
-81
No files matched your search
+8
-8
@@ -24,7 +24,7 @@ const plot::data::MinMaxPyramid& null_pyramid() {
|
||||
// --- ChannelBuffer ----------------------------------------------------------
|
||||
|
||||
ChannelBuffer::ChannelBuffer(std::shared_ptr<const plot::data::RingBuffer<f32>> values,
|
||||
std::shared_ptr<const plot::data::RingBuffer<f32>> times, u64 stride,
|
||||
std::shared_ptr<const plot::data::RingBuffer<f64>> times, u64 stride,
|
||||
f64 epoch, f64 nominal_dt)
|
||||
: values_(std::move(values)), times_(std::move(times)), stride_(stride < 1 ? 1 : stride),
|
||||
epoch_(epoch), dt_(nominal_dt) {}
|
||||
@@ -64,11 +64,11 @@ void ChannelBuffer::acquire() noexcept {
|
||||
|
||||
void ChannelBuffer::invalidate() noexcept {
|
||||
published_ = 0;
|
||||
window_x0_ = 0.0f;
|
||||
window_x0_ = 0.0;
|
||||
pyramid_.clear();
|
||||
}
|
||||
|
||||
f32 ChannelBuffer::time_of(u64 absolute_index) const noexcept {
|
||||
f64 ChannelBuffer::time_of(u64 absolute_index) const noexcept {
|
||||
// Channel sample `j` is produced on the tick that also pushes time slot
|
||||
// `j*stride + stride - 1` (see `Acquisition::on_data`): the producer stamps the
|
||||
// time ring every `time_stride` ticks and the channel every `stride` of those,
|
||||
@@ -102,10 +102,10 @@ f32 ChannelBuffer::time_of(u64 absolute_index) const noexcept {
|
||||
// whole trace's geometry collapsing.
|
||||
const u64 base = times_->base();
|
||||
const u64 produced = times_->produced();
|
||||
if (produced == 0) return 0.0f;
|
||||
if (produced == 0) return 0.0;
|
||||
u64 slot = index < base ? base : index;
|
||||
if (slot >= produced) slot = produced - 1;
|
||||
f32 out = 0.0f;
|
||||
f64 out = 0.0;
|
||||
times_->copy_from(slot, 1, &out);
|
||||
return out;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ f64 ChannelBuffer::dx() const {
|
||||
|
||||
f64 ChannelBuffer::x0() const {
|
||||
if (pyramid_.size() == 0) return epoch_;
|
||||
return epoch_ + f64(window_x0_);
|
||||
return epoch_ + window_x0_;
|
||||
}
|
||||
|
||||
f64 ChannelBuffer::x_at(u64 i) const {
|
||||
@@ -131,7 +131,7 @@ f64 ChannelBuffer::x_at(u64 i) const {
|
||||
// ring-span too new made it refuse the whole visible range -- which left the renderer
|
||||
// gathering the entire ring at a coarse level and drawing it off the axis.
|
||||
if (i == 0) return x0();
|
||||
return epoch_ + f64(time_of(absolute(i)));
|
||||
return epoch_ + time_of(absolute(i));
|
||||
}
|
||||
|
||||
bool ChannelBuffer::neighbours(f64 x, u64& i0, u64& i1) const {
|
||||
@@ -171,7 +171,7 @@ u64 ChannelBuffer::snap_index(f64 x) const {
|
||||
plot::Range ChannelBuffer::x_bounds() const {
|
||||
const u64 n = pyramid_.size();
|
||||
if (n == 0) return plot::Range{epoch_, epoch_};
|
||||
return plot::Range{x0(), epoch_ + f64(time_of(absolute(n - 1)))};
|
||||
return plot::Range{x0(), epoch_ + time_of(absolute(n - 1))};
|
||||
}
|
||||
|
||||
plot::Range ChannelBuffer::y_bounds() const {
|
||||
|
||||
Reference in new issue
Block a user