fix: a clock that stalled on a re-plan, and a dialog that could not close

**The clock.** Trigger mode showed a blank plot and never latched when the session defined a math
channel, because such a session gives the model a plan *before* the stream's own signals are in it:
one derived channel, declared rate 0, so `plan_partition()`'s 1 kHz fallback is the interval those
early frames are stamped at -- 1300x too fast for a 1.3 MS/s stream -- and they have nowhere to go,
since the value loop skips derived channels. Relative mode then stamped a *position* times the plan's
*current* interval, so when the CONFIG's channels landed and the plan took the real rate (769 ns) the
same positions produced times 1300x *smaller* than what was already in the ring, and the monotonic
clamp held the clock at the old value for the rest of the run: one repeated stamp, `history=0`, no
latch, and a window the renderer cannot draw. The same family as the restarted-client poisoning
fixed in 620473a, through a different door. The filter's own output was finite and correct
throughout, and disabling the math channel only appeared to cure it because that is a structural
change which rebuilds the store -- the third time that channel has looked guilty while being a
bystander.

Relative mode now accumulates *seconds* (`rel_base_s_ += n * dt`), so a re-plan is a slope change and
the clock cannot jump backwards by construction; a frame is only stamped into a model that has a
*streamed* channel; and the epoch is adopted once per connection, with "unset" a state rather than
the value zero. A one-shot warning names the family if it returns: `the clock stalled at ...: N
samples stamped [a..b] are all behind it`.

**The dialog.** The Configurations window's Close button and the window-manager close both did
nothing: `close-request` was connected to a bare `void` lambda, and that signal is `gboolean` -- TRUE
means "handled" and suppresses the default handler that destroys the window, while `G_CALLBACK` is a
cast, so GTK read whatever the compiler left in the return register. A standalone GTK program of that
shape reproduces it: `void` handler -> `window_alive=1` after `gtk_window_close()`, `gboolean FALSE`
-> 0. The same audit found `gtk_window_present(GTK_WINDOW(...))` on an `AdwDialog`, a type assertion
and a no-op; it is `adw_dialog_present()` now. The menu row stays: it is the only way to switch or
save a configuration without restarting, and what made it look useless was that it could not close.

Verified: trigger mode with that session's graph reads `ring=[0.0058..0.2446] -> [0.0058..1.9969]`,
`history 0.237 -> 2.0 s`, `edge at 1.120 s, latched at 2.120 s`, `display=event(frozen) latches=1`,
`traceless paints 266` against 1519 before the fix. Baselines hold: 0 errors, the documented three
warnings, test-ui 13 ok, test-help green, `gtk warnings: 0`.
This commit is contained in:
Martino Ferrari committed 2026-10-15 10:50:09 +02:00
1 parent 27c9b34277
commit 074cb611bc
9 files changed
+210 -34

No files matched your search

+21
View File
@@ -1886,6 +1886,27 @@ rate whenever data arrives at all:
from an event view: `ring=[2.5200..2.5200] n=567000 → n=1131000 → n=1689000`, one second apart, with
`now_t` and `holdoff_until` both pinned at `2.520` — the file view's last stamp. The producer's clock was
frozen, not lagging, and the display stayed blank until something re-planned the store.
**It has a second entry, and one of its own readings names it**: under `--relative-time` a re-plan that
*shortened* the plan's interval put every later stamp behind the clock the run had already reached, and
the frame loop's clamp held it there (see the relative-clock invariant in
[invariants-data-path.md](invariants-data-path.md)). The line below is printed once per connection the
first time that happens, so a run that reads it has its whole diagnosis on one line — and the *absence*
of it beside a `ring` that advances is the reading that a re-plan no longer moves the clock:
```
[udpscope] warning: the clock stalled at 0.000384 s: 500 samples stamped [0.000000..0.000384] s are all
behind it, so the timeline stands still while frames keep arriving -- nothing can be drawn and no trigger
can latch. The plan's interval is 7.69230769e-07 s now; ring n=500
```
`history=0.0000/<pre>` on every `trig engine:` line beside a `n` that climbs is the same state seen from
the engine's side, and it is what makes a *trigger-mode* display blank: with no latch the display never
leaves `Waiting`, whose bind is nothing by design. Measured on the mock at 1.3 MS/s with a session that
defines a LowPass channel, in single mode: before the fix `ring=[5.4990..5.4990]` for the whole run,
`latches=0`, `display=nothing`, and the dump read `traceless paints: 1519 (1519 after the first bind)`;
after it, `ring` runs `[0.0058..0.2446] → [0.0058..1.9969]`, `history` reaches the pre-window,
`latches=1` and the frozen event reads `n=2599999 data=[-1.0000..1.0000]`. The same run with the math
channel disabled never showed it at all — which is the control that names the entry, not the filter.
- **`ring` advancing while `now_t` does not** is the *feed*: the store is moving and the engine is not being
handed the samples, which is a trigger-path question and not a clock one.