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:
Martino Ferrari committed 2026-10-16 17:28:54 +02:00
1 parent e14e1ee069
commit 974d6aa2d9
15 files changed
+993 -41

No files matched your search

+38 -4
View File
@@ -93,9 +93,9 @@
offers a pulse width to a window trigger.</p>
</section>
<section id="persistence">
<title>Persistence: keeping the last few acquisitions</title>
<p><gui>Persistence</gui> decides how many of the most recent triggered acquisitions stay on
<section id="multiple-events">
<title>Multiple events: keeping the last few acquisitions</title>
<p><gui>multiple events</gui> decides how many of the most recent triggered acquisitions stay on
screen at once. It is <em>1</em> by default, which is the event alone — the display you get
when you ask for no persistence — and it can be raised to 8. Each older acquisition is drawn
at half the brightness of the one before it (1, 0.5, 0.25, 0.125, …), and they are all aligned
@@ -107,7 +107,7 @@
<p>The older acquisitions are drawn <em>behind</em> the current one and nothing else changes
because of them: the axis still frames the event on screen, the trigger marker still marks
<em>its</em> edge, and the measurements still describe the current frame. Lowering
<gui>Persistence</gui> back to 1 remembers nothing and frees the retained acquisitions
<gui>multiple events</gui> back to 1 remembers nothing and frees the retained acquisitions
immediately.</p>
<p>Two things bound how many are drawn, and the panel tells you when one of them bites.</p>
@@ -120,6 +120,40 @@
so persistence may hold at most as much again as the buffer (<gui>--buffer-mb</gui>) does.
On a long window at a high sample rate that bound is reached before the eighth layer.</p></item>
</list>
<section id="persistence">
<title>Persistence: how often the trace has been here</title>
<p><gui>persistence</gui> — the switch in the trigger panel's <gui>Advanced</gui> block — keeps
one <em>image</em> of the last acquisitions instead of their traces. Every acquisition is
accumulated into it, so a cell's brightness means how many of them passed through it: the
sweep on screen is the brightest, the ones behind it add on top, and where several agree the
ink is brighter still. It is off by default.</p>
<p>Two things make it different from <gui>multiple events</gui> beside it, and the panel offers
both because they answer different questions. <em>It never runs out of room</em>: the image is
the size of the plot on screen, so a display of a thousand acquisitions costs what a display of
one costs, and there is no count to choose. <em>And it fades</em>, in one of two ways —
<gui>fades by</gui> picks them:</p>
<list>
<item><p><gui>acquisitions</gui>: every acquisition keeps
<gui>per acquisition</gui> of what was there before it (0.5, the default, is the schedule a
phosphor's afterglow has: 1, 0.5, 0.25, …). The display then shows the last few sweeps
whatever the trigger rate.</p></item>
<item><p><gui>time</gui>: the image fades by <gui>time constant s</gui> as the display runs —
one constant is the time for it to fall to a third of its ink — so a slow trigger leaves
faint sweeps and a fast one leaves bright ones, and the picture is the same whichever the
stream is doing.</p></item>
</list>
<p>Persistence is a <em>trigger mode</em> display, like the trigger itself: it is drawn while a
captured acquisition is on screen and goes away with the trigger, on a pause, and when you open
a stored waveform. Nothing about it moves the framing — the axis is still the current event's,
the older sweeps only add ink — and it reserves no space in the plot and cannot be clicked, so
it is never in the way of a cursor or a drop.</p>
<p>Both displays live in the trigger panel's <gui>Advanced</gui> block, together with the
trigger's other settings that are set up once and left alone.</p>
</section>
<p>If a depth cannot be shown in full, a notice says how many layers fit, and the panel's
tooltip carries the same rule. Persistence is a display of what has already been captured: it