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
+6
-4
@@ -34,8 +34,10 @@ using plot::u64;
|
||||
/// undecimated case and the common one.
|
||||
class ChannelBuffer final : public plot::data::Series {
|
||||
public:
|
||||
/// `values` is f32 and `times` is f64: see `BufferStore::time()` for why the clock
|
||||
/// alone needs the wider type.
|
||||
ChannelBuffer(std::shared_ptr<const plot::data::RingBuffer<f32>> values,
|
||||
std::shared_ptr<const plot::data::RingBuffer<f32>> times, u64 stride, f64 epoch,
|
||||
std::shared_ptr<const plot::data::RingBuffer<f64>> times, u64 stride, f64 epoch,
|
||||
f64 nominal_dt);
|
||||
|
||||
ChannelBuffer(const ChannelBuffer&) = delete;
|
||||
@@ -94,10 +96,10 @@ class ChannelBuffer final : public plot::data::Series {
|
||||
/// Absolute (channel) index of retained-window index `i`.
|
||||
u64 absolute(u64 i) const noexcept { return pyramid_.window_base() + i; }
|
||||
/// Timestamp of channel sample `absolute_index`, in seconds since `epoch_`.
|
||||
f32 time_of(u64 absolute_index) const noexcept;
|
||||
f64 time_of(u64 absolute_index) const noexcept;
|
||||
|
||||
std::shared_ptr<const plot::data::RingBuffer<f32>> values_;
|
||||
std::shared_ptr<const plot::data::RingBuffer<f32>> times_;
|
||||
std::shared_ptr<const plot::data::RingBuffer<f64>> times_;
|
||||
u64 stride_ = 1;
|
||||
f64 epoch_ = 0.0;
|
||||
f64 dt_ = 1.0;
|
||||
@@ -114,7 +116,7 @@ class ChannelBuffer final : public plot::data::Series {
|
||||
/// the "live plot flickers as if there were no data" defect. The cached value is also the
|
||||
/// *right* answer rather than merely a stable one: it is the stamp sample `window_base`
|
||||
/// actually carries, where the clamp answered with whatever slot happened to be oldest now.
|
||||
f32 window_x0_ = 0.0f;
|
||||
f64 window_x0_ = 0.0;
|
||||
plot::data::MinMaxPyramid pyramid_;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user