fix: a still frame's measurements are the frame's, not the viewport's
A measurement is a statistic of the window the pane is *about*, and on a pinned view -- a held event, a paused stream, an opened file -- that is the frame, not the part of it on screen. The pane followed the viewport instead, so a wheel on a latched event moved `average` and `top`: the reported "when measuring a still frame, moving the zoom makes measurements such as average or top change". Nothing about the data had changed; the user was only looking closer. The window is `RowSet::pinned_range()` while the pin frames the view and the viewport otherwise, and the choice is made in `update_measures()` -- the call site -- rather than inside `MeasureWindowReader`, because it is *booked* as well as read: that window is what the "it moved" test compares against `App::measure_window`, so a gesture inside the pin spends no recompute either, and a reader that chose its own would leave a pinned view recomputing every gesture frame for a number that cannot change. `pinned_range()` is now the one expression of the pin's extent, shared with the framing (`hold_pinned_window()`, `fit_x()`), and the reader is handed the window instead of reading the plot. Live is unchanged: there the window really moves and the statistic follows it, and the accumulator's rule about what resets the history is about neither. `UDPSCOPE_PROBE_MEASUREPIN_MS` is the guard, three legs of before/after a gesture on the row's own axis. On a latched event zoomed to a quarter (`view=[-1..1]` -> `[-0.25..0.25]`, `pin` unchanged): `same=4/4 for_same=1 pass_delta=0`, every value bit-identical -- `average=0.000000 top=0.957975 peak to peak=2.000000 amplitude=1.915950` both sides. The same on a pause taken from it. Since the key `for=` follows `pin=` now, `[measure-trace]` gained the field, so a held run reads `for==pin` with `swin` free to sit inside it. On a live view a zoom to an eighth reads `same=0/4 for_same=0 pass_delta=3 span_after=0.125000 follows=1` with the values moving -- the case the rule must not swallow. Before the fix the same probe reads `same=1/4 for_same=0 pass_delta=1 span_after=0.500000` on the held event (`top` `0.957975` -> `0.957966`) and `same=0/4 span_after=0.125000` on the pause (`top` `0.957944` -> `0.358614`). Live reads the same either way. Also fixed, found by the pause leg: pausing while an event is *held* froze an **empty** picture. `on_pause_toggled()` read `stream_at_x0()` after setting the pause state, and that accessor follows the display -- `paused` with no pause view published yet answers `-display_epoch()`, which on a held event is 0, so the cut searched the ring at edge-relative x, found nothing, and the frame bound an empty snapshot: every measurement `--`, every ruler gone (`ev=650001` on the probe's line now, `ev=0` before). The offset and the window are read first, which is the offset the axis is actually displaying. The live case is unaffected, which is why the pause probe, which pauses a live view, never saw it. Docs corrected where they asserted the old half-truth: the invariants now separate the accumulator's rule (unchanged) from the window a value is computed over (this change), the trigger/pinning file names the pin as load-bearing for both the framing and the numbers and records the pause ordering rule, `verification.md`/`probe-readings.md` describe the new probe and `pin=`, and the README and the manual say what a still frame measures. Checked: `cmake --build build --clean-first -j4` 0 errors and the documented three warnings; `make -C build test-ui` 13 ok; `make -C build test-help` green; an exit-dump run 0 Gtk-WARNING/Gtk-CRITICAL and `gtk warnings: 0`; `[measurepin-probe]`, `[pan-probe]` (`held=1`, `allowed=1`, `limited=1`), `[pause-probe]` (`event_size` non-zero exactly while paused, `axis` inside `frozen_x`, `skips` 0 live / 30 paused), `[stats-probe]` (`dn=1` across the pause), `[zoom-probe]` (`gap_px=0.0` at every step) and `[measure-probe]` + the dump's `measures window:` (all-types run `marks_in_window=4/4`, `pin=off` live) on the fast mock. `measure cost:` is unchanged in kind: `raw=37298 (0.0%)` of `covered=121704537`, 1.04 ms a pass -- the runs that read higher are the probe's own zoomed-to-a-slice windows, where the window is legitimately shorter than a bucket.
This commit is contained in:
1 parent
d91957a2fe
commit
0a209becb1
13 files changed
+719
-114
No files matched your search
@@ -81,13 +81,20 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
the far threshold of the side it is leaving. Seeding the previous side from `0` -- the undecided code --
|
||||
fires on a signal that starts *inside* the band and leaves it without ever reaching the level, an event the
|
||||
trigger is not supposed to see.
|
||||
- **A pinned window is *clamped* into the event every frame, and a zoom in is allowed.**
|
||||
**A pinned window is *clamped* into the event every frame, and a zoom in is allowed.**
|
||||
While an event is held, a capture is filling, or the view is paused, `tick()` calls
|
||||
`RowSet::hold_pinned_window()`, which caps the span at `[-pinned_pre_, +pinned_post_]`'s and
|
||||
clamps the position into it (and states `Off`, since a following group would re-derive from a
|
||||
live edge). **A zoom in must keep working** -- "not outside the event" is not "not readable",
|
||||
and this function's first version *re-stated* the pin every frame, which made the wheel a no-op
|
||||
in single trigger mode: an acquired event could not be examined at all.
|
||||
**What a gesture inside the pin may *not* move is a measurement**, and it is not this function
|
||||
that holds that: `pinned_range()` is the one expression of the pin's extent, and
|
||||
`update_measures()` measures *it* rather than the viewport while the view is pinned, so a pan
|
||||
or a zoom on a still frame changes no value and spends no recompute (see
|
||||
[invariants-measurements.md](invariants-measurements.md); `[measurepin-probe]` is the reading,
|
||||
`pin=`/`for=` in `[measure-trace]` the field to watch). The pin is therefore load-bearing twice
|
||||
over, and a change to what `pinned_range()` says changes both the framing and the numbers.
|
||||
**The request is read from the row's own axis, not from the group.** A member's `set_x_range`
|
||||
-- what the controller writes for a gesture -- is *not* adopted into the group while the follow
|
||||
is off, so a clamp that read the group only ever saw its own previous answer (the probe showed
|
||||
@@ -148,6 +155,18 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
afterwards if the probe zoomed in. Before the fix the two disagreed outright:
|
||||
`axis=[-1.44..0.56]` over data that started at `0.0`. (The event pin already worked this way; the
|
||||
pause now agrees.)
|
||||
- **The pause reads the display offset *before* it sets the pause state, and a pause taken on a
|
||||
held event depends on that order.** `on_pause_toggled()` reads the row's axis and
|
||||
`stream_at_x0()`, cuts `view + offset` out of the ring, and hands the same offset to the
|
||||
snapshot as its `x_offset`. `stream_at_x0()` follows the *display*: the moment `paused` is true
|
||||
with no pause view published yet it answers `-display_epoch()`, which on a `Held` view is **0**
|
||||
-- so the cut searched the ring at the event's edge-relative x, found nothing, and froze an
|
||||
**empty** picture: `ev=0` on the probe's line, every measurement `--` and every ruler gone,
|
||||
which is a much louder symptom than the empty margin above and reaches a user the same way
|
||||
(pause while an event is on screen). Read first, it is the offset the axis is actually
|
||||
displaying, which is what the whole scheme requires. The live case is unchanged -- there the
|
||||
epoch does not depend on `paused()`, which is why the pause probe, which pauses a *live* view,
|
||||
never saw it.
|
||||
- **The event axis is pinned from the snapshot's own stamps, not from the request.** A
|
||||
capture may legitimately be shorter than asked for — the ring need not reach back a
|
||||
full pre-trigger share on the first latch after arming, so `snap=[-0.339..1.000]`
|
||||
|
||||
Reference in new issue
Block a user