Findings from the review of the sliding-window change:
- A level >= 1 gather emitted only complete, wholly-retained buckets, so the
geometry stopped at the last bucket boundary inside the range: the newest
samples of a live window -- up to `span - 1` of them, about a pixel at the
level level_for() picks -- were not drawn, on the right edge, which is the
part of a scrolling trace that gets watched, and the oldest retained samples
fell outside the geometry too (the level-1 gather of a sliding window started
51 samples in). The two partial edge buckets are now gathered as raw samples
through copy_raw() and bracketed around the envelope -- the same exactness
split reduce/sum/sumsq already use -- so the emitted geometry spans the
requested range exactly. The pyramid still stores no partial bucket, so append
stays amortised O(1); at 1 MS/s the demo stages 511 extra sub-pixel segments
and still draws at 1.05 ms/frame, and the screenshot's trace now reaches one
column further at each end.
- `drop_before`'s doc claimed a no-op "for an empty pyramid": false, and
backwards -- the restart branch is exactly what an empty pyramid hits, which is
what StreamSeries::acquire() depends on when the ring saturated before the
first acquire. The precondition is `absolute_sample <= window_base()`, and the
restart is documented as what it is.
- The pyramid's memory figure was 3x optimistic: an entry is 24 bytes (MinMax
plus the exact sum/sumsq doubles), so level 0 costs 0.375n bytes and the whole
pyramid ~0.75n -- about 19% of 4-byte raw samples, not the 6% a min/max-only
index would cost. Corrected in the header, spec 4.2 and 4.5, with the trade
stated (the doubles buy O(log n) sum/mean/RMS/integral from whole buckets),
and the plan's Task 3 sketch no longer describes per-level prefix sums, a
level() span or an unaligned-end widening that does not exist.
Tests: `gather_at_a_lod_level_reaches_both_ends_of_the_window` pins that the
first and last emitted points are the oldest and newest retained samples -- with
spike values no bucket envelope can produce -- at level 1, at the level a dense
view picks, and for a static series whose end is unaligned. It fails against the
previous commit (first point at the first whole bucket), and so does the sliding
gather mapping check, which now accounts for the raw edge pairs.