test: the noise probe reads the de-noise node, and the math probe builds one

The probe gains a staged chain rather than one phase, and the schedule moves with it:
`UDPSCOPE_PROBE_NOISE_MS` now times the *setup* stage, which installs the de-noise graph through
`apply_math_channels()` -- a structural rebuild that replaces the buffer and drops its history --
five seconds before anything scans it. The legs and the five measurements then run at about
10.5 s, and the reading that says the median rejected every impulse at 15.5 s, so the documented
run needs a 20 s mock and an 18 s exit.

The de-noise channel is installed **disabled** and enabled only at 13 s, for its own reading. An
acquired channel is a share of the ring, so a fifth one shortens every channel's retained span --
and the six `BufferSearch` leg counts and `pkpk bare` are all peaks and counts *of that window*.
Installing it enabled (as the plan had it) moved `bare` from 49 to 21, `ratio` from 7.00 to 3.00
and `pkpk bare` from 2.988 to 2.488, i.e. it would have rewritten the older feature's documented
proof to record a regression that is not one. With it enabled only at the end, every reading from
Tasks 4 and 8 is bit-for-bit what the doc records, and the new line reads
`denoise raw=2.987696/1 denoised=2.000000/1 ratio=0.669 channels=1` -- `denoised` exactly the
clean sine's 2.0, which is the claim the node makes.

Two other findings, both folded in: a derived channel whose source is switched off through the
panel's checkbox follows it off and does *not* come back when the source returns (the enable-all
probe's last act switches `sine` off at ~3 s), and the probes reload leftover math channels from
`$XDG_CONFIG_HOME/udpscope`, which is why a `setup channels=` count above 1 means an earlier run's
session, not a defect.

`math_probe` gains a third channel with a `DeNoise` node so the editor's median row is built and
synced on a baseline run: `3 math channels on 'sine' and 'noise'`, every other line as before,
`math cost: 103.3 ns/slot` (one reading of the same path).

verification.md: the recipe, and why the two stages may not be collapsed -- a probe that rebuilt
the buffer under its own legs reads `bare=0` and looks like a broken comparator. Also that the
noise and math probes may not share a run: both write `app.math`.
This commit is contained in:
Martino Ferrari committed 2026-10-13 16:01:44 +02:00
1 parent 5c2c221777
commit c38630643d
6 files changed
+181 -21

No files matched your search

+22 -7
View File
@@ -532,21 +532,36 @@ same run).
```bash
cd /home/martino/Projects/tools/udpscope
python3 tools/mock_udpstreamer.py --port 8108 --rate 1300000 --batch 1000 --duration 16 \
python3 tools/mock_udpstreamer.py --port 8108 --rate 1300000 --batch 1000 --duration 20 \
--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=14000 UDPSCOPE_ENABLE_ALL_MS=1000 \
UDPSCOPE_PROBE_NOISE_MS=6000 \
xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=18000 UDPSCOPE_ENABLE_ALL_MS=1000 \
UDPSCOPE_PROBE_NOISE_MS=3000 \
./build/udpscope --server 127.0.0.1 --port 8108 2>&1 | grep -E 'noise-probe|CRITICAL'
```
The probe arms at 6 s rather than the 8 s a single-phase probe would use: its first phase enables the signals
it measures and waits 2.5 s for the ring to refill after that structural change. `--spike-every`/`--spike-amp`/
`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
immediately would read `bare=0` on every search leg, which looks exactly like a broken comparator.
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
feature and are the proof it still works, so the new channel stays out of the acquisition plan until they
have run. `--spike-every`/`--spike-amp`/
`--spike-width` are the mock's own; `--batch` must stay at or below 1000 (2000 samples x 5 signals x f64 is
80 kB, past UDP's ~65507-byte payload, and the mock dies on its first `sendto` while the run still exits 0).
The three lines and what each must read are in `probe-readings.md`.
`UDPSCOPE_PROBE_NOISE_MS` and `UDPSCOPE_PROBE_MEASURE_MS` must **not** be scheduled in the same run: both push
onto `app.measures`, and the later one would read the earlier one's rows as its own. The noise probe carries
`UDPSCOPE_PROBE_NOISE_MS`, `UDPSCOPE_PROBE_MEASURE_MS` and `UDPSCOPE_PROBE_MATH_MS` must **not** be scheduled
in the same run. The noise and measurement probes both push onto `app.measures`, and the later one would read
the earlier one's rows as its own; the noise and math probes both write `app.math`, so the later one's graph
lands on top of the earlier one's and both sets of readings describe a state neither probe set up. The noise probe carries
its own measurement legs (see `probe-readings.md`), so nothing is lost by keeping them apart.