Commit Graph
28 Commits
Author SHA1 Message Date
Martino Ferrari 761615c2fe fix: a restarted client is a new stream, so its history and its monotonic mark go
Going live from a file view left the display frozen for seconds: the per-second line showed
`ring=[2.5200..2.5200] n=567000 -> n=1131000 -> n=1689000` — the sample count climbing at the stream's rate
while the retained span was one value, the file view's last stamp. The status bar was green because
everything about the *connection* was fine.

It is the poisoning `invariants-data-path.md` already describes, entered through a different door. The
new client's stream clock sits *behind* the stamps the old stream left in `Acquisition::last_stamp_` (the
file view's samples are still in the store), so the advance test rejects every packet of a perfectly good
stream, four rejections abandon the declared time signal, and the arrival-clock fallback derives its epoch
from the surviving mark — every sample stamped flat at the old value. The guard did exactly what it was
built to do; the *stream* was the thing that had changed and nothing had told it.

So `Acquisition::start()` — which is the one place a client (re)starts — now says so:
`ScopeModel::reset_stream()` drops the previous stream's history (`store_->reset()`, which the data-path
invariants require to leave nothing behind) and moves `plan_generation_`, which is what the producer
already watches to drop the state tied to the old store; and `last_stamp_` is re-based to `-infinity` so
the monotonic guard re-bases on the new clock instead of pinning it. The declaration is kept: the first
packet of the new stream passes, and monotonicity holds from there.

One self-inflicted crash on the way, fixed before this: `reset_stream()` dereferenced `store_`, which is
**null until the first CONFIG has planned anything** — at start-up there is none — so the app died with
SIGSEGV and no output at all. Caught by running it plainly rather than trusting the change.

Verified with the events probe (a replay and a group, then *Live*):

    live restored (after replay): … pending=0
    trig engine: … history=0.4377/0.4000 s now_t=0.298 ring=[-0.1392..0.2985] n=569000
    trig engine: … history=0.8723/0.4000 s now_t=0.733 ring=[-0.1392..0.7331] n=1134000   (pending=1: it latched)
    trig engine: … history=1.2954/0.4000 s now_t=1.156 ring=[-0.1392..1.1562] n=1684000

`ring` moves within a round-trip, `history` fills, and the trigger latches the frame after the restore;
`make test-ui` 14 files, zero `Gtk-CRITICAL`, and the plain run is clean. Docs: the restart's own door into
the poisoning in `invariants-data-path.md`, and the `ring=` reading's entry in `probe-readings.md` now says
what it must do after *Go live*.
2026-10-14 15:38:00 +02:00
Martino Ferrari 62b5f14504 test: the per-second engine line shows the store's retained span, which pins a stalled clock
Diagnosing "going live from an event view does nothing for ~5 s, with the status bar green" needed a
distinction the engine's own fields cannot make. `n` counts every stored sample, so it climbs whenever data
arrives at all; what says *why* nothing moves is the store's stamp pair beside it. So the line gains
`ring=[oldest..newest] n=`:

    trig engine: … history=0.0000/0.4000 s … now_t=2.520 holdoff_until=2.520 ring=[2.5200..2.5200] n=567000
    trig engine: …                                       ring=[2.5200..2.5200] n=1131000
    trig engine: …                                       ring=[2.5200..2.5200] n=1689000

Three seconds apart, after `<live>`: the sample count climbs at the stream's rate (the data *is* arriving and
being stored) while the retained span is a single value — the file view's last stamp. So the producer's clock
is **frozen** after the restart, not lagging, which is why `history` never fills, why an armed trigger never
latches, and why a live view with the trigger off does not advance either: one cause, two faces, and it is
not the trigger.

The reading is what tells that apart from a stalled *feed* (ring moving, `now_t` still), which is a different
bug with a different fix — and neither is visible from `now_t` alone. `n` climbing beside a still `ring` is
the clock; a moving `ring` beside a still `now_t` is the feed.

The fix itself is the next piece of work: the producer's clock has to advance across a client restart (or the
file view's retained history has to go when going live), and this pair is the assertion it must satisfy —
after `<live>`, `ring` must move within a stream round-trip. Verified: `make test-ui` 14 files; the fields
observed in the transition run above; no `Gtk-CRITICAL`.
2026-10-14 15:32:01 +02:00
Martino Ferrari d34bff3f03 feat: the trigger can ignore a noise band and reject HF before comparing 2026-10-13 15:04:52 +02:00
Martino FerrariandClaude Opus 4.6 84c158ff21 perf: the producer's group test is a mask, not a 64-bit division
`plan_partition()` only ever chooses powers of two for `time_stride`, and the
comment there already claimed the test was cheap -- but the stride is a runtime
value, so `tick % period` compiled to a real division that the hot loop paid
once per channel per wire sample. The per-channel masks are built where the
partition is already reacted to, and the shared clock's group end is computed
once per sample instead of three times.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-13 14:50:48 +02:00
Martino Ferrari 8f54841c2c working on todo.md features 2026-10-10 22:52:12 +02:00
Martino FerrariandClaude Opus 4.6 d2d4b7f204 fix: a hole straddling the counter wrap now matches its late packets
The lookup compared raw uint32 counters with >= and <, so a hole spanning the
2^32 wrap matched nothing and every late packet belonging to it was dropped. A
signed delta, the idiom deps/udpsclient already uses throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-09 06:52:24 +02:00
Martino FerrariandClaude Opus 4.6 51d13a09cb fix: the warn-once latches reset on a CONFIG, so a reconnect is diagnosed too
Four latches were process-lifetime -- two of them function-local statics, so
process-wide rather than per-object -- and a condition that warned once stayed
silent about every later occurrence in a long-lived session. A diagnostic that
goes quiet after the first occurrence is the failure mode worth fixing in a
feature whose job is diagnosis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-09 06:52:04 +02:00
Martino FerrariandClaude Opus 4.6 8320988d96 fix: a re-plan drops the holes it invalidated and restarts the tick they are addressed by
rebuild_locked() replaced the store, so produced() restarted at 0 while the
producer's tick_ kept counting: a hole's slot and tick addressing then described
a buffer that was gone, forget_holes_before() could not retire it, and a
backfill's amend fell outside the ring -- repairing nothing while the hole was
still marked repaired, which is the band reporting the opposite of the truth. A
re-plan generation is the signal rather than the stride/channel-count guard,
which a reconnect with an identical CONFIG leaves unchanged. slot_base_ is left
alone: it is relative mode's clock origin and must not walk backwards when the
user retypes the window.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-09 06:51:29 +02:00
Martino FerrariandClaude Opus 4.6 176ef1df9f fix: a full reorder ring keeps the arriving packet and counts the overflow, instead of dropping it silently
A packet that needed a slot and found none was neither held nor delivered: no
counter recorded it and the consumer reserved a hole nothing could ever backfill.
It now commits the gap to free a slot and takes the ordering decision once more
against the sequence that commit left behind, bounded to one retry; past that it
is delivered in order with the counters it skipped as its `lost`, never flagged
late, because a packet ahead of the sequence has no hole to be looked up by. A
duplicate can now replace its slot at a full ring, as hold_packet's own comment
already promised, and the 20 ms default window is a named constant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-09 06:50:43 +02:00
Martino Ferrari 3859d2ca79 feat: a repaired hole keeps a fainter band, so a report is not a false accusation 2026-10-08 23:00:22 +02:00
Martino Ferrari d6afdfef9f fix: a reserved hole is addressed by the counters actually missing, not one past the frame that revealed them 2026-10-08 19:47:07 +02:00
Martino Ferrari 7c60d79b65 feat: a late packet backfills the hole it left, values only 2026-10-08 19:44:24 +02:00
Martino Ferrari fc81e661d0 feat: the status bar and the exit dump report resequencing, holes and stale packets 2026-10-08 18:58:06 +02:00
Martino FerrariandClaude Sonnet 4.6 d38ab3f9c9 fix: the hole reservation now runs after the accumulator re-plan, so value_acc_ is always sized to channels.size() before reserve_hole() writes into it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-08 18:29:57 +02:00
Martino FerrariandClaude Sonnet 4.6 e9d259ac7f fix: a late frame is now dropped before it reaches the timeline, leaving its reserved hole intact
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-08 18:26:26 +02:00
Martino FerrariandClaude Sonnet 4.6 6830b97c02 fix: batch_ is now learned in both stream and relative-time modes so hole reservation fires in stream mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-08 18:22:32 +02:00
Martino Ferrari 5915fc8421 fix: a lost packet reserves the samples it owed instead of vanishing from the timeline 2026-10-08 18:18:12 +02:00
Martino Ferrari 57e2f6aa8b feat: relative mode keeps the stream's absolute clock for the export 2026-10-08 17:20:47 +02:00
Martino Ferrari d11e7d48c4 feat: relative mode stamps a sample from its position and the declared rate 2026-10-08 17:06:09 +02:00
Martino FerrariandClaude Opus 4.6 f179fbbb44 fix: a clock glitch is bridged and a channel is known by name, so a capture keeps its t0
Two defects behind one report ("a trigger during a capture overrides the first,
and the hold-off does not apply"); the second is also what made a spike in one
channel look like the acquisition resetting.

The trigger engine keyed its invalidation on the trigger channel's *index*, which
every rebuild of the channel list renumbers -- enabling a signal that sorts before
it is enough. Any rebuild therefore abandoned an open capture and set no hold-off,
so the next edge inside the post window became t0. It keys on the channel's name
now, which changes only when the source genuinely does.

A packet whose stamps jump more than 256 packet spans ahead is now rejected as a
fault and *bridged* at the declared cadence, instead of poisoning the monotonic
mark (which pinned every later packet flat at the glitched value) and condemning
the next good packet. Only four consecutive failures abandon the declared time
signal, and that fallback no longer wipes the rings: the arrival clock is anchored
to continue from the monotonic mark, so the history is still on one timeline --
the wipe is what made one glitching packet look like the stream restarting from 0.
The declared unit is resolved before those checks, since they weigh the stamps
against a cadence in seconds.

Guards: UDPSCOPE_PROBE_TRIGRENUM_MS prints t0_held=1 through the real widgets,
and the mock's --time-glitch-at/--time-glitch-by injects the clock fault.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-07 10:29:05 +02:00
Martino FerrariandClaude Opus 4.6 ec93a9831d fix: store the stream clock as f64, so a stamp's precision does not decay with uptime
The time ring was f32 like the values, holding each stamp relative to an epoch
fixed at the first sample ever seen -- so the number stored grew for as long as
the app ran while the interval it had to resolve stayed one sample period. At
1 MS/s the f32 ULP reaches the sample period after about sixteen seconds, so two
to four consecutive samples rounded onto one stamp after half a minute and eight
after a minute. On screen that is a plateau the LOD envelope draws as a
zero-width segment -- a vertical spike where the waveform should be -- and in an
export it was three distinct diffs at 2, 2.5 and 3 ULP instead of one clock.

A value's precision is a property of the signal; a timestamp's is a property of
how long the scope has been running. Only the clock widens: the values are the
memory budget. That makes the two slices different sizes per slot, so
plan_partition() weighs its growth loop in bytes rather than in slots (and
bytes_for() is what bounds the doubling); live_window_locked() loses the f32
round-trip of its bounds, which existed only to keep a sample sitting exactly on
one; and EventSnapshot::stamps is f64 because a pause view is not rebased and so
carries full stream time.

Verified against the fast mock: np.diff over two million exported rows now holds
exactly one step (1e-06, 1999999 times), no duplicate or backwards stamps, and
the 200 MB ring still spans 8.39 s. Every documented probe assertion unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-05 10:41:57 +02:00
Martino FerrariandClaude Opus 4.6 eb4372242c fix: time a FIRST_SAMPLE stream by its cadence, not by a second stamp
A UDPS_TIME_FIRST_SAMPLE time signal carries one meaningful value -- the
batch's first sample -- but a scalar signal in Accumulate mode still occupies
num_samples slots on the wire, which a producer fills by repeating that
anchor. Deriving the step from value_of(n - 1) therefore came out as zero, so
every sample of a frame landed on one x, the degeneracy guard tripped, and the
whole stream was timed by packet arrival instead. On a 1 MS/s source that
arrival clock is not monotonic within a frame: the export's time column held
90559 order breaks.

Space the batch by the cadence, exactly as the LAST_SAMPLE branch beside it
already dates its own batch backwards. The export's time column is now
strictly increasing (0 duplicates, 0 backwards steps) and reads the declared
1 MS/s.

mock_udpstreamer gains --time-mode {full,first}; it could only emit
FULL_ARRAY, so nothing exercised this branch. In 'first' it repeats the anchor
across the batch's wire slots -- the shape that produced the defect -- while
still generating the waveforms from the real per-sample stamps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-05 10:24:05 +02:00
Martino Ferrari aa2508e431 added math signals 2026-10-03 17:28:17 +02:00
Martino Ferrari 02620ca946 improved measuraments ,cursors, zooming and user interactions 2026-10-03 16:05:45 +02:00
Martino Ferrari 44fc4cdf36 improved timing acq 2026-10-02 14:03:57 +02:00
Martino Ferrari 30db44eaa0 Correct downsampling and better layout 2026-10-02 08:19:10 +02:00
Martino Ferrari 0d741c401b Fixed triggering mode change when acquiring 2026-10-01 17:54:17 +02:00
Martino Ferrari 8059b90de7 Initial commit 2026-10-01 15:42:48 +02:00