fix: the x labels go relative on what a label can write, not on the span
A `t0 = ...` caption appeared on every sub-second window, including a triggered one whose axis is already relative to the edge -- so the ticks were re-anchored away from the one position a triggered axis exists to show, and the caption had nothing to explain. The form is now decided by the tick step against `TickFormat::Time`'s own resolution at the axis' magnitude (three significant figures below a minute, whole seconds through `m:ss` to a day, three figures of days above). The span said nothing about that and was wrong both ways: a 2 s window at twenty minutes of uptime reads `20:00` on every tick and stayed absolute. The step is read back from the last layout's majors (a tick value depends on the range and the axis width, not on the label form); with no ticks yet the form is left alone rather than guessed, since the hysteresis would latch a guess. Entering the relative form carries a ULP of slack, because `step == res` is readable and a bare comparison of two differently computed doubles flipped the form between frames. `[timelabels-probe]` and the dump gain `distinct=`, which asserts that no two major labels read the same -- the thing the relative form is for, and the only reading that holds whatever the stream's uptime is. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
b505706d92
commit
e2985aa159
4 files changed
+145
-32
No files matched your search
@@ -435,20 +435,31 @@ Two lines of the self-check output are assertions in disguise:
|
||||
*not* every channel, and never a stream's timestamp signal, which is what an acquired-but-
|
||||
unplotted list put first and auto-selected), and `drop_h` must be a combo box's height
|
||||
(tens of pixels — a scrolled-window wrapper asked it to grow to 160).
|
||||
- `[timelabels-probe] <action> relative=D window=.. origin=.. note=[..] ticks=[..]
|
||||
first_value=.. first_rel=.. range=[..]` + the dump's `time labels: relative=D origin=..
|
||||
note=[..] x_origin_set=.. ticks=[..] first_rel=..` (stderr,
|
||||
- `[timelabels-probe] <action> relative=D distinct=D window=.. origin=.. note=[..] ticks=[..]
|
||||
first_value=.. first_rel=.. range=[..]` + the dump's `time labels: relative=D distinct=D
|
||||
origin=.. note=[..] x_origin_set=.. ticks=[..] first_rel=..` (stderr,
|
||||
`UDPSCOPE_PROBE_TIMELABELS_MS`) — the x label form across a zoom, driven through
|
||||
`apply_time_window()`: **under the figure's x link group the group owns the window, so a
|
||||
row-level `set_time_window()` does nothing** (a probe that changes the window must use the
|
||||
app's path). Absolute at 2 s and at 500 ms, `relative=1` below a second, and the caption
|
||||
must be non-empty exactly then. The probe *races the paint* — it reads the axis on a timer,
|
||||
app's path). **`distinct` must be 1 on every line, and that is the assertion** — it says no two
|
||||
major labels on the axis are the same string, which is the one thing the relative form exists to
|
||||
buy and the only reading that holds whatever the stream's uptime is. `relative=D` deliberately
|
||||
is *not* fixed per window: the form is decided by the tick step against the label resolution at
|
||||
the axis' **magnitude** (see the invariant below), so the same 50 ms window reads absolute at
|
||||
2 s of uptime (`2.13s, 2.14s, 2.15s`) and relative at 17 s (`100µs, 10.1ms, 20.1ms`, with the
|
||||
caption) — both correct, and a probe pinned to one of them would assert the stream's clock
|
||||
rather than the app. The **caption must be non-empty exactly when `relative=1`**. Run it after
|
||||
`UDPSCOPE_ARM_TRIGGER_MS` as well, which is the case the span rule got wrong: a triggered 50 ms
|
||||
window must read `relative=0 note=[] ticks=[-20ms,-10ms,0,10ms]` — a `t0` caption on an axis
|
||||
that is already edge-relative re-anchors the ticks away from the trigger, which is the one
|
||||
position the axis exists to show. The probe *races the paint* — it reads the axis on a timer,
|
||||
and the ticks it sees are the previous layout's — so its `first_rel` (the first major minus
|
||||
the axis' origin) is a fraction of a step; the dump runs after the last paint and is the
|
||||
race-free reading. Either way the assertion that matters is the *sum*: `first_value` must
|
||||
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.
|
||||
the absolute form. Note the last two steps both report `window=0.05`: `RowSet::set_time_window()`
|
||||
floors the window there, so the 20 ms step is clamped.
|
||||
- `[fit-probe] <action> pinned=D 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 the notice and the control are the handler's half. A fit is the **undo
|
||||
@@ -1406,6 +1417,26 @@ far the largest file.
|
||||
reasons (an event pin) call `sync_window_display()` alone, which only restates the control and
|
||||
never moves the plot — calling `apply_time_window()` there would clamp a legitimately short
|
||||
capture up to the requested window.
|
||||
- **The x labels go relative on the tick *step against the label resolution at the axis'
|
||||
magnitude*, never on the span.** `RowSet::update_time_labels()`'s question is only ever "can
|
||||
this step be written at this magnitude": `TickFormat::Time` carries three significant figures
|
||||
below a minute, **whole seconds** through `m:ss`/`h:mm:ss` up to a day, and three figures of
|
||||
days above — so the resolution is a step function of the magnitude, which `time_label_resolution()`
|
||||
states and nothing else may re-derive. The old test was `span < 1.0` and it was wrong in both
|
||||
directions: a 2 s window at twenty minutes of uptime reads `20:00` on every tick and stayed
|
||||
absolute, and a **triggered** sub-second window — whose axis is already `[-25ms, +25ms]` about
|
||||
the edge — went relative and so re-anchored its ticks *away from the trigger*, with a `t0`
|
||||
caption that had nothing to explain. Three details hold it up. The step is read back from the
|
||||
majors the **last layout** produced, which is not a feedback loop (a tick's value depends on the
|
||||
range and the axis' pixel width, never on the label form) but *is* absent before the first
|
||||
layout — and with no ticks the form must be **left alone**, because a step guessed from the span
|
||||
would be latched by the hysteresis below. The hysteresis is a 1.05 band on *leaving* the relative
|
||||
form. And *entering* it needs a ULP of slack (`1 - 1e-9`): a step differenced from two tick
|
||||
values and a resolution from `pow(10, n)` differ in their last bits when they are the same
|
||||
number, and `step == res` is a readable axis (`1.73s`, `1.74s`, `1.75s` — one in the last digit
|
||||
is exactly enough), so the bare comparison picked a different form from one frame to the next.
|
||||
The guard is `[timelabels-probe]`'s `distinct=1`, which asserts what the form is *for* rather
|
||||
than which form a given window gets.
|
||||
- **`fit_x()` restores the *whole of what the view may show*, which is not always `window_s_`.**
|
||||
Live, that is the newest `window_s_` at the live edge (`set_time_window(window_s_)`, the same
|
||||
path a resume takes). Pinned — a held event, a filling capture or a paused view — it is the
|
||||
|
||||
Reference in new issue
Block a user