From 667ed0c100b9817a62307524249675b74fdbfff1 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Tue, 13 Oct 2026 16:07:50 +0200 Subject: [PATCH] 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`. --- docs/claude/probe-readings.md | 6 ++++-- docs/claude/verification.md | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index b1c48b9..d254593 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -1312,8 +1312,10 @@ while the edit runs) and never falls to 0, which is what a rebuild would do. Eve applied *and* acquired, so the channel's first sample is the first one after stage 3, and a peak-to-peak needs a window holding a trough *and* a crest: twelve cycles of the 5 Hz source. -With `UDPSCOPE_PROBE_NOISE_MS=3000` the legs therefore land at about 10.5 s and the last line at 15.5 s, -which is why the documented run needs `UDPSCOPE_EXIT_AFTER_MS=18000`. +With `UDPSCOPE_PROBE_NOISE_MS=4000` the legs therefore land at about 11.5 s and the last line at 16.5 s, +which is why the documented run needs `UDPSCOPE_EXIT_AFTER_MS=18000`. Four seconds and not three: the +enable-all probe's own last act, a click on the front channel's tick at `ENABLE_ALL_MS + 2000`, rebuilds +nothing itself but *is* rebuilt under when a graph is installed at its instant -- see `verification.md`. - **`setup channels=1`** -- one math channel was installed, and it is printed by the *first* stage, before any leg: it is the line that says the graph exists at all. A count above 1 means a session file from an diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 1e99578..a0f6e3d 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -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