docs: the noise probe arms at 4 s, not 3, and why

`UDPSCOPE_PROBE_NOISE_MS=3000` collides with `enable_all_probe`'s own last act, which fires at
`ENABLE_ALL_MS + 2000` = 3 s: that lambda walks `signal_list` for the front channel's acquire
tick and clicks it, and a probe that installs a math graph at the same instant rebuilds the
signal panel under it. The off-step then emits `active` on a freed `GtkCheckButton` --
`GLib-GObject-CRITICAL: instance with invalid (NULL) class pointer`, stack in
`enable_all_probe()::{lambda}` -- which the baseline ("no Gtk-CRITICAL at all") counts as a
finding. Confirmed by bisection: armed at 3 s it is reproducible, at 4 s it is gone, and a plain
exit-dump run shows none.

That fragility is the probe's, not the feature's, and it is now documented beside the recipe: every
documented probe time is offset from `ENABLE_ALL_MS + 2000`, not merely from the other probes.
The reading times move with it (legs ~11.5 s, de-noise ~16.5 s, both inside the same 18 s exit),
and the corrected run reproduces every documented figure bit for bit.

Readings at 4 s: `setup channels=1`, `trigger band: bare=49/2 band=7/2 ratio=7.00`,
`trigger hf: hf=7/2 band+hf=7/2 band=7`, `pw`/`period band`/`period hf` exactly 0.200000,
`measure windows=2`, `denoise raw=2.987696/1 denoised=2.000000/1 ratio=0.669 channels=1`, no
`Gtk-CRITICAL`.
This commit is contained in:
Martino Ferrari committed 2026-10-13 16:07:50 +02:00
1 parent 749b34a2cb
commit 667ed0c100
2 files changed
+18 -8

No files matched your search

+14 -6
View File
@@ -536,20 +536,28 @@ python3 tools/mock_udpstreamer.py --port 8108 --rate 1300000 --batch 1000 --dura
--spike-every 0.005 --spike-amp 0.5 --spike-width 2 > /tmp/noise-mock.log 2>&1 &
sleep 1.5
xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=18000 UDPSCOPE_ENABLE_ALL_MS=1000 \
UDPSCOPE_PROBE_NOISE_MS=3000 \
UDPSCOPE_PROBE_NOISE_MS=4000 \
./build/udpscope --server 127.0.0.1 --port 8108 2>&1 | grep -E 'noise-probe|CRITICAL'
```
`UDPSCOPE_PROBE_NOISE_MS` is **not the time its readings are taken**. It times the probe's first stage, which
installs a math graph through `apply_math_channels()` -- and that is a *structural rebuild* that replaces the
buffer and drops its history. Every reading the probe takes is over retained history, so the rebuild has to
happen seconds *before* them, and the chain is what the number is really choosing: `3000` puts the graph in at
3 s, `noise_probe` at 8 s (it enables the signals it measures itself and waits 2.5 s for the ring to refill
after *that* change), the legs and the five measurements at about 10.5 s, the de-noise channel enabled at
13 s, and the de-noise reading at 15.5 s -- hence the 18 s exit and the 20 s mock. **Collapsing the two
stages into one is the failure mode**: a probe that rebuilt the buffer at 10.5 s and then scanned it
happen seconds *before* them, and the chain is what the number is really choosing: `4000` puts the graph in at
4 s, `noise_probe` at 9 s (it enables the signals it measures itself and waits 2.5 s for the ring to refill
after *that* change), the legs and the five measurements at about 11.5 s, the de-noise channel enabled at
14 s, and the de-noise reading at 16.5 s -- hence the 18 s exit and the 20 s mock. **Collapsing the two
stages into one is the failure mode**: a probe that rebuilt the buffer at 11.5 s and then scanned it
immediately would read `bare=0` on every search leg, which looks exactly like a broken comparator.
**Why 4 s and not 3.** `enable_all_probe`'s last act is a timeout of its own, `ENABLE_ALL_MS + 2000` -- 3 s
under this recipe -- which walks `signal_list` for the front channel's acquire tick and clicks it. A probe
that installs a math graph at that same instant rebuilds the signal panel under it, and the off-step then
emits `active` on a freed `GtkCheckButton`: `GLib-GObject-CRITICAL: instance with invalid (NULL) class
pointer`, with the backtrace in `enable_all_probe()::{lambda}`. It is a real fragility of that probe's lookup
and not of the feature, and it is why every documented probe time is offset from `ENABLE_ALL_MS + 2000`, not
merely from each other.
The de-noise channel is installed *disabled* and enabled only for its own reading (stage 3). An acquired
channel is a share of the ring, so a fifth one shortens every channel's retained span -- and the six leg
counts and `pkpk bare` all scale with that span. The readings above the de-noise line belong to the older