fix: only a window change sets the time scale; a fit states the whole of it
The time scale is more than the axis: it is also the trigger's event span, the buffer partition and the control's readout. A zoom or a pan was already clean -- the controller writes the member axis and `clamp_to_data()` caps the span -- but both fits moved `window_s_` alone and only restated the control. The event span then stayed on the previous window until the next decade step or unit change applied the fitted value for real, so one action changed the time scale twice. `apply_window_scale()` is split out of `apply_time_window()` for the fits to use: it moves the partition, the event span, the control and the session, and leaves the view alone, because `set_time_window()` re-arms the live follow and would throw a held event or a paused view back onto the stream. The fits report the span they framed, `fit_all()` delegates its x half to `fit_x()` so the two cannot drift, and the probe drives the menu handlers rather than `RowSet` so it sees the half that lives there. `[fit-probe]` gained `event_span=`, which must equal `window=` on every line. Also moves the plotter pin to 03c41ad (three build warnings, no behaviour). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f6738031a
commit
9888fd9977
7 files changed
+128
-63
No files matched your search
@@ -449,13 +449,18 @@ Two lines of the self-check output are assertions in disguise:
|
||||
equal `origin + first_rel` as a *tick label* plus its caption, i.e. the labels and the
|
||||
caption must share one anchor. `x_origin_set` is read back out of the plot; it must be 0 in
|
||||
the absolute form.
|
||||
- `[fit-probe] <action> window=.. x=[..] span=.. y=[..]` (stderr, `UDPSCOPE_PROBE_FIT_MS`) —
|
||||
the two fits, through the same `RowSet` calls the right-click menu wires. It first puts
|
||||
the y scale and the window where the data is *not* (a fixed `[-100, 100]`, a 50 ms
|
||||
window), so the assertions have something to be wrong about: **`fit x` must widen the
|
||||
window to the data's span and leave `y` exactly as it was**, and **`fit all` must then
|
||||
move `y` to the data's own range**. A `y` that changed on the `fit x` line is the two
|
||||
paths aliased.
|
||||
- `[fit-probe] <action> window=.. event_span=.. x=[..] span=.. y=[..]` (stderr,
|
||||
`UDPSCOPE_PROBE_FIT_MS`) — the two fits, through the **menu handlers** rather than the bare
|
||||
`RowSet` calls, because half of what a fit now does lives there (see the time-scale invariant
|
||||
below). It first puts the y scale and the window where the data is *not* (a fixed
|
||||
`[-100, 100]`, a 50 ms window), so the assertions have something to be wrong about: **`fit x`
|
||||
must widen the window to the data's span and leave `y` exactly as it was**, and **`fit all`
|
||||
must then move `y` to the data's own range**. A `y` that changed on the `fit x` line is the two
|
||||
paths aliased. **`event_span` must equal `window` on every line**: a fit states a new time
|
||||
scale, so the trigger's event span, the buffer partition and the control have to move with it —
|
||||
a fit that moved the view alone left `event_span` on the *old* window, and the next decade step
|
||||
or unit change then applied the fitted value for real, so one action changed the time scale
|
||||
twice.
|
||||
- `[layout-probe] <action> rows=N buttons=B done=D done_box=[x,y,WxH] rows_y=[..] close=[..] add=[..] traces=[..]`
|
||||
(stderr, `UDPSCOPE_PROBE_LAYOUT_MS`) — **`buttons` must be `2N+1`** while the editor is
|
||||
up (one `×` per row, one `+` per boundary) and **0 on the `editor off` line**: the
|
||||
@@ -1356,12 +1361,30 @@ far the largest file.
|
||||
that grew from `1 s` to `123.456 ms` would widen the bar and relayout everything under it
|
||||
on a mere change of window. That is also why the window is typed into a popover rather
|
||||
than shown in the bar.
|
||||
- **`apply_time_window()` is the only place the display window changes.** It clamps to
|
||||
`window_limit()` and moves `RowSet::set_time_window()` and `ScopeModel::set_event_span()`
|
||||
together, because the trigger's pre-trigger share is a share of *this* window. Paths that
|
||||
change the span for their own reasons (Fit, an event pin) call `sync_window_display()`
|
||||
instead, which only restates the control and never moves the plot — calling `apply_` there
|
||||
would clamp a legitimately short capture up to the requested window.
|
||||
- **A plot interaction may not quietly redefine the time scale.** The *time scale* is more than
|
||||
the axis: `RowSet::window_s_` also feeds `ScopeModel::set_event_span()` (the trigger's
|
||||
pre-trigger share is a share of *this* window), `ScopeModel::set_target_span()` (which may
|
||||
re-plan the partition, move the decimation and drop history), the window control's readout and
|
||||
the session file. A **zoom or a pan therefore does not touch it at all** — the controller
|
||||
writes the *member* axis and `clamp_to_data()` caps the span at `window_s_`, so a gesture is
|
||||
view-only by construction and needs no code to keep it that way. The **two fits do** state a
|
||||
new scale, and they must state the whole of it: `on_fit_clicked`/`on_fit_x_clicked` take the
|
||||
span `RowSet::fit_x()`/`fit_all()` report and hand it to **`apply_window_scale()`**. A fit that
|
||||
moved `window_s_` alone — which is what both of them used to do — left the event span on the
|
||||
*old* window, and the next decade step or unit change then fed the fitted value in for real:
|
||||
one action, two changes of time scale, and `window == event_span` true at neither point.
|
||||
- **`apply_window_scale()` is the only place the time scale changes, and `apply_time_window()`
|
||||
is the only place the *view* changes with it.** The first clamps to `window_limit()` and moves
|
||||
`set_target_span()`, `set_event_span()`, the control and the session save together; the second
|
||||
calls it and then `RowSet::set_time_window()`. The split is not cosmetic:
|
||||
`set_time_window()` unpins and re-arms the live follow, which is right for a typed window (a
|
||||
statement about what to watch from now on) and wrong for a fit of a held event or a paused
|
||||
view, which has nothing bound to a live edge — so the fits take the scale half only, and
|
||||
`fit_x()` writes the link group directly while `pinned_`. The partition is moved **before** the
|
||||
axis, so the axis is framed over the rings that will exist. Paths that change the span for
|
||||
their own reasons (an event pin) still call `sync_window_display()` alone, which only restates
|
||||
the control and never moves the plot — calling either `apply_` there would clamp a legitimately
|
||||
short capture up to the requested window.
|
||||
- **The header bar's buttons are icon-only**, so headless probes must reach them
|
||||
through the `App` pointers (`trigger_toggle`, `pause_button`, …), not via
|
||||
`find_labelled_button()`. The **pane switches are toolbar buttons** too (`cursors_toggle`,
|
||||
|
||||
Reference in new issue
Block a user