trigger: a phosphor persistence, driven by the plotter's density image
The trigger pane gains the display the user asked for and the library now provides, and the
persistence it already had is renamed to what it is.
**Persistence is one image; the last-N-traces display is "multiple events".** Both live in a new
`Advanced` expander in the trigger panel, together with the settings that are set up once and left
alone. `persistence` (a switch) turns on the plotter's `Plot::set_density()` for every row:
every triggered acquisition is accumulated into a screen-sized intensity grid drawn behind the
traces, brightness being how many acquisitions covered a cell -- no depth to choose, no memory
that grows with the acquisitions (2.2 MB over four 573x412 rows in the probe's run). `fades by`
picks the fade the user asked for: **acquisitions** (each keeps a factor of what was before it,
0.5 by default -- the phosphor afterglow) or **time** (a time constant in stream seconds, so the
display fades while you watch whatever the trigger rate).
- **Trigger mode only, as asked.** `apply_density()` (the tick) states the display on for `Held`
and `Capturing` and off for `Live`, `Waiting`, `Paused` and a file, and turning it off drops the
grid at once rather than at an acquisition a quiet trigger never sends. It is unpickable and
reserves no layout space, exactly as the invalid-span bands are: a density can never be in the
way of a cursor or a drop, and it cannot move the framing.
- **One deposit per acquisition, one frame after the frame that frames it.** `Plot::accumulate_density()`
deposits what the plot holds through the transforms of its *last* layout -- the frame's own is
computed inside the paint that follows the tick -- so `density_pending` is set beside
`record_pending` and consumed by the next tick. In the same tick it was right on a Held-to-Held
transition and wrong on the first latch, whose axis was still the live ring's. Switching the
display on under an acquisition already on screen deposits it too, so the switch does not appear
to do nothing until the next latch (`single` mode may never send one).
- **The session carries it**: `trigger_density`, `trigger_density_decay` ("acquisitions"/"time",
the words the panel shows), `trigger_density_tau` and `trigger_density_factor`, written whether
or not it is on -- and `trigger_persistence` still means the multiple-event depth, so an older
file is unchanged.
- **The library changed twice under this**: the deposit now counts a cell **once per trace per
sweep** (counting the hits put one dense sweep at the ceiling of the brightness scale and hid the
sweeps behind it -- the probe's `on` leg read `peak=18` for a single sweep and reads `peak=1`
now), and a frame's upload is every row's rather than the last row's. Both are in the plotter's
own commit.
`UDPSCOPE_PROBE_DENSITY_MS` reads it all back off the plots -- the plotter's own `ink()`/`peak()` --
with the panel's own widgets driving the switch and the fade, in four legs: **off** (`rows=0
ink=0.0 peak=0.0000`), **on** (`rows=4`, `peak=1.0000`), **step-acquisitions** (`peak=1.0000 ->
1.5000`, the factor then the new sweep) and **step-time** (`ratio=0.367879`, `1/e` for `dt = tau`).
It also reads the frame cost as an A/B **inside one run** (the accumulators at the switch against
the frames after it): `tick 0.74 -> 9.99 ms`, `draw 2.09 -> 20.91 ms` at four rows and 2.2 M cells
on llvmpipe.
Acceptance: clean build 0 errors and exactly the three documented warnings (12352-byte GResource
blob, two `MathNode::source`), `test-ui` 13/13, `test-help` green, a plain run with **0
Gtk-CRITICAL / Gtk-WARNING** and `gtk warnings: 0`, and the panel's own frame reading
`boxes=16 controls_inside=15/15 advanced_widgets=5/5 advanced_inside=5/5` (the block's controls
counted from the expander's child, which is not in the widget-parent chain -- and the search
block's walk now names *its* expander instead of "the first one", which the second expander would
have stolen).
Docs: the trigger/pinning invariants carry the rule and its five consequences, `probe-readings.md`
the four legs and the dump line, `verification.md` the probe's sequence and assertions, and the
manual's trigger page explains both displays to a user.
This commit is contained in:
1 parent
e14e1ee069
commit
974d6aa2d9
15 files changed
+993
-41
No files matched your search
@@ -1899,27 +1899,31 @@ through one row type, for the reason in `invariants-gtk-and-chrome.md`. The form
|
||||
## `trigger panel:`
|
||||
|
||||
```
|
||||
[udpscope] trigger panel: boxes=17 controls_inside=16/16 search_widgets=4/4 search_inside=4/4
|
||||
[udpscope] trigger panel: boxes=16 controls_inside=15/15 search_widgets=4/4 search_inside=4/4 advanced_widgets=5/5 advanced_inside=5/5
|
||||
```
|
||||
|
||||
The trigger panel's frame, read from the window: **the `.ui`'s own panel** (`trigger_body`), not the box
|
||||
this app wraps it in — the heading row and the locked summary are the code's arrangement, and reading the
|
||||
*wrapper* made this line report `boxes=2 … search_inside=0/4` the moment the heading moved out of the file.
|
||||
That is the failure this line is for, so it is worth knowing it can happen to itself. `boxes` is the row
|
||||
boxes the `.ui` holds, `controls_inside`
|
||||
boxes the `.ui` holds and `controls_inside`
|
||||
counts the sixteen controls the code built and then appended into those rows -- the last three being the
|
||||
noise band and the HF reject, whose rows arrived with the noise-rejection work, and the persistence depth
|
||||
whose row arrived with the persistence display (the Arm/Force pair is a row of its own too, so `boxes` counts
|
||||
it while `controls_inside` does not: the pair is a *button* row, not a value) -- `search_widgets` is the four
|
||||
search buttons having resolved by id, and `search_inside` is those four being inside the block the expander
|
||||
opens onto. A control built and never placed -- an id that drifted between the file and the code -- reads as
|
||||
`controls_inside` below 16 while every other line still looks right, which is the panel that has a field
|
||||
missing.
|
||||
counts the fifteen controls the code built and then appended into the panel's own rows -- the noise band and
|
||||
the HF reject, the persistence-depth spin and the record switch among them, and the Arm/Force *pair* not,
|
||||
because that row holds buttons and a `boxes` count reads the rows either way.
|
||||
|
||||
The search walk starts at `gtk_expander_get_child()`, not at the panel: an expander's child is not in the
|
||||
widget-parent chain at all, so walking up from a search button reaches no ancestor of the panel (see
|
||||
`invariants-gtk-and-chrome.md`). `[defaults] trigger:` prints the same panel's ten spins in tree order, so
|
||||
the two lines together read what the file gave and what the code put in it.
|
||||
`boxes` is **16** since the trigger's display settings moved into the `Advanced` expander: the multiple-event
|
||||
depth and the four density controls left the panel's own child list, and the expander that holds them is not a
|
||||
box. Those five are what `advanced_widgets`/`advanced_inside` count, for the same two reasons the search pair
|
||||
exists -- the ids resolving, and the controls being *in* the block -- and an expander's child is **not** in the
|
||||
widget-parent chain, so a control there is invisible to `controls_inside` by construction (see
|
||||
`invariants-gtk-and-chrome.md`). The walk for a block therefore starts at **its own** expander,
|
||||
`gtk_expander_get_child(GTK_EXPANDER(app.search_exp))`: the first version of this line took "the first
|
||||
expander in the panel", which stopped naming the search block the moment a second expander was added *before*
|
||||
it -- and would have read `search_inside=0/4` on a panel that was entirely correct. `[defaults] trigger:`
|
||||
prints the same panel's spins in tree order, so the two lines together read what the file gave and what the
|
||||
code put in it.
|
||||
|
||||
## `[persist-probe]` and `persistence:`
|
||||
|
||||
@@ -1972,6 +1976,58 @@ which are bound event series on the same rows — moved neither the axis nor the
|
||||
waits for one), because a latch landing mid-frame leaves the mode `Held` while the rows still hold the live
|
||||
ring's bindings, and a reading taken there says nothing about either.
|
||||
|
||||
## `[density-probe]` and `density:`
|
||||
|
||||
```
|
||||
[density-probe] arming on 'square', window 0.5 s, display off
|
||||
[density-probe] off enabled=0 fade=acquisitions tau=1.000 factor=0.500 rows=0 cells=0 ink=0.0 peak=0.0000 ratio=1.000000
|
||||
[density-probe] on enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 cells=2226150 ink=226596.0 peak=1.0000 ratio=1.000000
|
||||
[density-probe] step-acquisitions enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 cells=2226150 ink=262722.0 peak=1.5000 ratio=1.159429
|
||||
[density-probe] time-before enabled=1 fade=time tau=1.000 factor=0.500 rows=4 cells=2226150 ink=213362.2 peak=1.2182 ratio=1.000000
|
||||
[density-probe] step-time enabled=1 fade=time tau=1.000 factor=0.500 rows=4 cells=2226150 ink=78491.6 peak=0.4481 ratio=0.367879
|
||||
[density-probe] frame cost: tick before=0.74 ms (163 frames) after=9.99 ms (12 frames) draw before=2.09 ms (157) after=20.91 ms (7)
|
||||
[udpscope] density: frames=504 rows=4 cells=2226150 grid_bytes=8904600 ink=632845.4 peak=2.8 upload_bytes=1081350
|
||||
```
|
||||
|
||||
The **phosphor density** (`Plot::set_density()`, the trigger pane's `persistence` switch): one
|
||||
screen-sized intensity image per row, every acquisition accumulated into it, drawn behind the traces and
|
||||
fading by a count or by the clock. The numbers are the plotter's own -- `DensityGrid::ink()`/`peak()` summed
|
||||
and maxed over the rows the display is on, which is the same source the library's tests read -- and the
|
||||
probe drives the panel's **own widgets** for the switch and the fade, so what it measures is this app's
|
||||
wiring.
|
||||
|
||||
- **`off` is the display without it**: `rows=0`, so there is no grid at all and both numbers are zero.
|
||||
That is also the run the frame cost is read against, and it is a plain run -- the density is off by
|
||||
default and nothing draws it.
|
||||
- **`on` is one sweep**: `peak=1.0000` exactly, and `ink=226596` is the cells that sweep covered over
|
||||
four rows. A cell takes **1.0 once per trace per sweep** however many of the trace's own samples crossed
|
||||
it, which is what makes a sweep's peak *one* rather than the eighteen a per-hit deposit read on the same
|
||||
run -- and what makes the sweeps behind it visible at all.
|
||||
- **`step-acquisitions`** deposits one more sweep: `peak=1.0000 -> 1.5000`, which is `1 + 0.5 x 1` -- the
|
||||
panel's factor 0.5 applied to what was there, then the new sweep's own pass on top. `ink` rises from
|
||||
`226596` to `262722` for the same reason: the old image halved and one sweep landed on it. The ratio is
|
||||
**not** a constant to expect -- the acquisition on screen can move between the two reads, so what it says
|
||||
is "the old image halved and a pass landed on it"; the exact relation is the *library's* case
|
||||
(`a_density_halves_per_acquisition_and_keeps_the_newest_brightest`), where the same frame is deposited
|
||||
twice.
|
||||
- **`step-time` is `1/e` by construction**: one step of `dt = tau` through the plot's own `decay_density()`,
|
||||
so `ratio=0.367879` is `exp(-1)`. The leg before it (`time-before`) reads the same image under the time
|
||||
fade, and the pair is the reading that says the *panel's* constant reaches the grid.
|
||||
- **`frame cost:` is the A/B inside one run**: the accumulators as of the tick the display was switched on
|
||||
against the frames after it, so both means are the same stream, the same layout and the same mock. At four
|
||||
rows of 573x412 cells it reads **tick 0.74 -> 9.99 ms** and **draw 2.09 -> 20.91 ms**: the per-frame
|
||||
conversion, upload and deposit. It is llvmpipe, and the arithmetic is the grid's -- 2.2 M cells, one byte
|
||||
each, every frame the time fade moves it.
|
||||
- **The dump's `density:` line** is captured on the last tick the display was **on**, not read at exit: the
|
||||
exit probe switches the trigger off before it dumps, which takes the display with it, so a dump that asked
|
||||
the plots would say `rows=0` about a run whose tail had the image up. `upload_bytes` is the **most** any
|
||||
one frame uploaded -- one byte a cell, over the rows that moved -- and not the last frame's, for the same
|
||||
reason.
|
||||
|
||||
**Read `peak` as the display's ceiling**: with the default `full_scale` of 1.0 a cell at 1.0 is full ink, so
|
||||
a steady display under the acquisition fade sits between 1 and 2, and a `peak` well above that (the
|
||||
per-hit deposit read 189 after two sweeps) is the defect this probe's `on` leg now catches.
|
||||
|
||||
**And `N = 1` is the display without the feature**, which is the same command at
|
||||
`UDPSCOPE_PROBE_PERSIST_DEPTH=1`:
|
||||
|
||||
|
||||
Reference in new issue
Block a user