fix: a zoomed trace is drawn on the x its readout quotes

gather() staged x as f32 relative to the series' own x0() -- the start of
the whole retained window -- so the quantisation was a fraction of the
ring rather than of the view: 1.9 us at 20 s of retained data, which at a
10 us view across a 716 px axis is over 100 px between where a sample is
drawn and the x a readout quotes for it (crosshair_point() reads x_at()
in exact f64). It halved on every zoom out, which is what made it read as
"zooming in makes the crosshair select a point beside the data".

The origin is now the caller's (plotter c2c8c59): the renderer passes the
visible range's minimum, so the error is a fixed 2^-24 of what is on
screen -- about 4e-5 px over a 700 px axis -- at any zoom. The parameter
sits before out_xy so every call site is a compile error rather than a
silent change of behaviour.

The renderer no longer reads series.x0() at paint time at all, which
removes two of the three symptoms of the torn window_x0_ read by
construction; the ink probe's now-vacuous kept_fixed_x0 walk goes with
them, replaced by a drawn-vs-quoted pixel figure. neighbours() still
tests the visible range against x_at(0), so the cache stays load-bearing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-05 18:49:54 +02:00
1 parent 39f30d913f
commit 2f4530c128
7 files changed
+176 -90

No files matched your search

+5 -5
View File
@@ -189,7 +189,7 @@ void ChannelBuffer::raw(u64 begin, u64 end, f32* out) const {
for (u64 i = count; i < end - begin; ++i) out[i] = 0.0f;
}
void ChannelBuffer::gather(u32 level, u64 begin, u64 end, std::vector<f32>& out_xy,
void ChannelBuffer::gather(u32 level, u64 begin, u64 end, f64 x_origin, std::vector<f32>& out_xy,
plot::data::GatherMode mode) const {
const u64 n = pyramid_.size();
const u64 b = std::min(begin, n);
@@ -197,7 +197,7 @@ void ChannelBuffer::gather(u32 level, u64 begin, u64 end, std::vector<f32>& out_
if (b >= e) return;
const u64 origin = pyramid_.window_base();
const f64 base = x0(); // x is emitted relative to this (see the header)
const f64 base = x_origin; // x is emitted relative to this (see the header)
const auto emit_raw = [&](u64 from, u64 to) {
for (u64 i = from; i < to; ++i) {
@@ -302,13 +302,13 @@ u64 SlotSeries::snap_index(f64 x) const {
return buffer_ != nullptr ? buffer_->snap_index(x) : 0;
}
void SlotSeries::gather(u32 level, u64 begin, u64 end, std::vector<f32>& out_xy,
void SlotSeries::gather(u32 level, u64 begin, u64 end, f64 x_origin, std::vector<f32>& out_xy,
plot::data::GatherMode mode) const {
if (event_ != nullptr) {
event_->gather(level, begin, end, out_xy, mode);
event_->gather(level, begin, end, x_origin, out_xy, mode);
return;
}
if (buffer_ != nullptr) buffer_->gather(level, begin, end, out_xy, mode);
if (buffer_ != nullptr) buffer_->gather(level, begin, end, x_origin, out_xy, mode);
}
void SlotSeries::copy_raw(u64 begin, u64 end, f32* out) const {