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
-6
@@ -109,9 +109,9 @@ ExportTable collect_export(ScopeModel& model, RowSet& rows) {
|
||||
if (snap != nullptr) {
|
||||
// The snapshot is immutable and already index-aligned, so the visible window is two
|
||||
// binary searches over its stamps and the same index range for every channel.
|
||||
const std::vector<f32>& st = snap->stamps;
|
||||
const auto lo = std::lower_bound(st.begin(), st.end(), static_cast<f32>(view.min));
|
||||
const auto hi = std::upper_bound(st.begin(), st.end(), static_cast<f32>(view.max));
|
||||
const std::vector<f64>& st = snap->stamps;
|
||||
const auto lo = std::lower_bound(st.begin(), st.end(), view.min);
|
||||
const auto hi = std::upper_bound(st.begin(), st.end(), view.max);
|
||||
if (hi <= lo) return table;
|
||||
const std::size_t first = static_cast<std::size_t>(lo - st.begin());
|
||||
const std::size_t n = static_cast<std::size_t>(hi - lo);
|
||||
@@ -122,7 +122,7 @@ ExportTable collect_export(ScopeModel& model, RowSet& rows) {
|
||||
table.time.resize(n);
|
||||
table.stream_time.resize(n);
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
table.time[i] = static_cast<f64>(st[first + i]);
|
||||
table.time[i] = st[first + i];
|
||||
table.stream_time[i] = edge + table.time[i];
|
||||
}
|
||||
for (const int c : chans) {
|
||||
@@ -151,12 +151,12 @@ ExportTable collect_export(ScopeModel& model, RowSet& rows) {
|
||||
if (n == 0) return table;
|
||||
|
||||
BufferStore& store = model.live();
|
||||
std::vector<f32> stamps(static_cast<std::size_t>(n));
|
||||
std::vector<f64> stamps(static_cast<std::size_t>(n));
|
||||
store.time().copy_from(first, n, stamps.data());
|
||||
table.time.resize(static_cast<std::size_t>(n));
|
||||
table.stream_time.resize(static_cast<std::size_t>(n));
|
||||
for (std::size_t i = 0; i < stamps.size(); ++i) {
|
||||
table.stream_time[i] = static_cast<f64>(stamps[i]);
|
||||
table.stream_time[i] = stamps[i];
|
||||
table.time[i] = epoch + table.stream_time[i];
|
||||
}
|
||||
for (const int c : chans) {
|
||||
|
||||
Reference in new issue
Block a user