docs: the three new features are described where each of their readers looks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-10-08 10:43:39 +02:00
1 parent aefac2d796
commit ffc3ec1b4d
5 files changed
+231

No files matched your search

+111
View File
@@ -588,3 +588,114 @@ Two lines of the self-check output are assertions in disguise:
- `[color] '<name>' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `<span foreground="…">` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit.
- `[zoomkeys] ctrl+plus x=[..]->[..] y_held=1 | alt+plus y=[..]->[..] x_held=1` (stderr, `UDPSCOPE_PROBE_ZOOMKEYS_MS`) — **both `_held` must be 1 and both zooms must still happen.** Run it inside a pinned state, where a frame cannot re-frame the other axis for its own reasons: `UDPSCOPE_PROBE_PAUSE_MS=1500 UDPSCOPE_PROBE_ZOOMKEYS_MS=2200`. A `_held=0` is the modifier not reaching the plotter's controller; an unchanged range on the *zoomed* axis is the opposite failure, the key doing nothing. Ctrl restricts to x and Alt to y; Ctrl+Alt zooms neither (no special case needed — neither flag alone fires).
- `status bar: glance=[● live · 1.09 MS/s · 2 s window] max_glance_chars=53 detail_fields=11 popover_rebuilds=0` (exit dump) — **`max_glance_chars` must stay small** (under about 60) and **`popover_rebuilds` must be 0**. The status row is directly above the plot, so this is the "layout must not depend on content" rule read from the bar's own side: the state field is one word from a fixed set (`connecting`, `live`, `paused`, `armed`, `capturing`, `event held`, `disarmed` — a word added to it must be **no wider than the widest already there**, which is what let `connecting` join at ten characters beside `event held`; the dead-port glance reads `max_glance_chars=51` against a live run's 54), the rate field is `%.3g` (so `10 kS/s`, never `10.0 kS/s`), and the two fields a *state* adds are both elapsed times — trigger mode's `last event … ago` and a pause's `for <elapsed>` — which step units rather than growing digits (`format_elapsed()`). A **paused** glance is not in the dump at all: it is read by `[pause-probe]`'s `glance=` field, since the dump's own line is taken live. `detail_fields` is the popover's label count, a literal, so adding a label without thinking about layout shows up here. The glance rate format matters: `%.3g` drops the trailing zero that a `%.4g` or a fixed-precision format would keep — a rate that reads differently in the dump and in the help page is a format disagreement, and `14-status.txt`'s example must match it.
## `[condition-probe]`
`UDPSCOPE_PROBE_CONDITION_MS`, with the mock's `--glitch-at`/`--glitch-width` (legs 0 and 1) or
`--quiet-at` (leg 3).
Four legs, selected by `UDPSCOPE_PROBE_CONDITION_SHAPE`. Every leg prints one line of the form:
```
[condition-probe] type=T cmp=C width=W t0=T0 measured=M at0=A earlier=E level2=L2 mode=D
```
Measured readings (`--rate 10000 --freq 5`, each on a fresh port):
```
shape=0 type=1 cmp=0 width=0.010000 t0=1.842100 measured=0.002000 at0=nan earlier=nan
shape=1 type=1 cmp=1 width=0.010000 t0=2.160100 measured=0.200000 at0=nan earlier=nan
shape=2 type=2 cmp=0 width=0.010000 t0=1.706700 measured=nan at0=-0.500907 earlier=-0.498185
shape=3 type=3 cmp=0 width=0.010000 t0=4.120200 measured=0.300100 at0=nan earlier=nan
```
`measured` is the reading, not a phase. It is the pulse width read back from the held snapshot's
own stamps, which are edge-relative: the snapshot is the ground truth, not the mock's absolute
clock. Three things are worth knowing before calling a reading wrong:
- **Leg 0**: `measured=0.002` is the injected glitch width. A `measured` near the signal's own
half-period means the width qualifier did not qualify — the trigger caught a normal edge instead
of the runt.
- **Leg 1**: `measured=0.200` is an ordinary square half-period; every square pulse satisfies
`> 0.010`.
- **Leg 2**: `at0` must lie **outside** `[-0.5, +0.5]` and `earlier` **inside** it — those are the
two sample values around the band crossing. The probe prints `nan` for any field a leg cannot
measure. **This leg uses `sine`**, not `square`: the square is exactly ±1 and has no sample
inside the `[-0.5, +0.5]` band, so a window condition on the square can never fire. **Use a
1.0 s window with 50 % pre** or the probe cannot reach the opening edge of a 0.2 s pulse.
- **Leg 3**: `measured=0.300` is the dropout timeout. **This leg also uses `sine`**. A timeout of
0.050 s against a sine crossing zero every 0.1 s latches on a healthy signal, so use 0.300 s
or longer; **use a 1.0 s window with 50 % pre** as for leg 2.
The channel-selection line `[condition-probe] <name>=<index> of <N> channels` is printed before
the trigger fires; if the channel is not found this reads `-1` and the probe fails immediately.
A failure line `[condition-probe] no latch after N polls` means the condition never fired — a
wrong shape, a missing mock flag, or the wrong signal selected.
## `[stats-probe]`
`UDPSCOPE_PROBE_STATS_MS`. Needs `UDPSCOPE_ENABLE_ALL_MS` ahead of it and
`UDPSCOPE_PROBE_MEASURE_MS` to add measurements. Run against the fast mock
(`--rate 1300000 --batch 2000 --freq 1`) for readable figures.
Three phases, one line each. Every line carries `phase=`, `on=` (the toggle), `passes=` (the
pane's recompute count since start) and one `| n=… mean=… sd=… lo=… hi=…` group per
measurement. Phase 1 adds `dn=` per group.
Measured readings (fast mock, 1 Hz sine, with
`UDPSCOPE_ENABLE_ALL_MS=800 UDPSCOPE_PROBE_MEASURE_MS=1500 UDPSCOPE_PROBE_STATS_MS=2500`):
```
phase=0 on=1 passes=36 | n=30 mean=-0.0370313 sd=0.0953509 lo=-0.246367 hi=0.105167 | n=30 mean=0.633511 … | n=30 mean=1.99567 … hi=2 | n=30 mean=0.997834 … hi=1
phase=1 … dn=0 (on all four measurements)
phase=2 … n=1 mean=-4.84467e-14 | n=1 mean=0.63662 | n=1 mean=2 | n=1 mean=1
```
- `phase=0` — the running count. **`n` must be in the low tens**, not thousands: a count in the
thousands means the accumulator is fed per sample, not per recompute. `mean=0.633511` (≈ 2/π)
for the `top` measurement on a unit sine is the check that the threshold types are correct, not
merely plausible. `sd` must be 0 exactly when `n < 2`, and `lo`/`hi` must bracket `mean`.
- `phase=1` — after a two-second pause. **`dn` must be 0 or 1**: a pinned window recomputes once
when it settles and then never again. A `dn` near 120 is the second defect — accumulating per
frame, which adds a hundred-odd observations of the identical window and collapses `sd` towards
zero. Note that `n` is **not** reset by the pause or the resume; `passes` continues growing.
- `phase=2` — after Reset. **`n` must be 0 or 1** (0 if the report ran before the next recompute,
1 if after); anything larger means Reset cleared nothing.
## `[search-probe]`
`UDPSCOPE_PROBE_SEARCH_MS`, scheduled after `UDPSCOPE_PROBE_CONDITION_MS`. Run with
`--rate 2000 --batch 64 --freq 5 --glitch-at 0.25 --glitch-width 0.004` and
`UDPSCOPE_EXIT_AFTER_MS=12000`.
Measured output (port 8419):
```
[condition-probe] type=1 cmp=0 width=0.010000 t0=1.710000 measured=0.004000 at0=nan earlier=nan level2=0.500000 mode=2
[search-probe] started on channel 0, armed t0=5.710000
[search-probe] state=2 hits=12 scanned=4.5115 total=4.5115 chunks=2 worst=0.106ms total=0.116ms armed t0=5.710000 vs scanned first=5.710000 err=0 agree=1
[search-probe] jumped to hit 0 t=1.710000 mode=4 axis=[-0.1000..0.1000] pos=1
[search-probe] marks placed=1 of 1 in view (12 found) locked=1 mode=4 axis=[-0.1000..0.1000]
[udpscope] search marks: placed=1 hits=12 locked=1
```
- **`agree=1` is the load-bearing line of the whole feature.** The search and the live trigger run
the same `EventDetector` over the same stamps, so `agree=0` means one of the two is reading a
different clock — which is exactly the class of defect a search that merely "returns plausible
times" hides. Here `err=0`: exact agreement, not within tolerance.
- `state=2` is `BufferSearch::State::Done` (the enum's ordinal). `state=1` would be `Running`.
- `mode=4` after the jump is `DisplayMode::Paused` (the enum's ordinal). `mode=2` would be
`Capturing`.
- **`placed=1 of 1 in view` is the correct answer, not an under-count.** The jump frames a 0.2 s
window and the glitches are 0.4 s apart (one per square period at 5 Hz / square half-frequency),
so only one hit can be in view at a time. `1 of 1` against `(12 found)` is the marks agreeing
with the window, not with the hit list.
- **`total=4.5115` far short of the run is not a defect.** `condition_probe` enables the channel
(which starts acquisition) and then calls `apply_time_window()`, and each of those rebuilds the
buffer and drops history. 4.51 s was the genuinely retained span.
- `locked=1` says the marks are out of the pointer's reach, like every other readout. The exit
dump's `search marks: placed=1 hits=12 locked=1` is the same assertion from the dump path; its
`placed=` counts marks in the **visible window at exit**, which is the live view after the probe
disarmed the trigger — so `placed=0` there is legitimate and asserts nothing about the search.
The load-bearing `placed=` is the `[search-probe] marks` line, emitted a frame after the jump,
which quotes the window the scope has actually moved to.