Commit Graph
5 Commits
Author SHA1 Message Date
Martino FerrariandClaude Opus 4.6 a2efc142c3 fix(udpscope): keep the accumulated-scalar timeline monotonic and bounded
Round 4 of Task 4 review. Four defects in FrameDecoder's rule 3:

- The undeclared-rate (hrt) path positioned each burst at an ABSOLUTE
  hrt/ticksPerSecond(). hrt counts from the producer's boot, so it is ~1e11
  ticks by the time a scope attaches, and the rate is refitted every packet
  with a few parts in 1e4 of wobble. The product is tens of milliseconds of
  jitter in BOTH directions -- not merely imprecise, non-monotonic. Integrate
  short tick deltas into accProdSec instead and let ClockOffset latch the
  epoch that leaves behind.
- The lead bleed used a fixed 0.9 factor, which converges only while the
  declared rate is within ~10%. Squeeze proportionally to the excess instead
  (floored at kMinBleedFactor), settling it in a single burst.
- A single-sample flush fell through to the plain-scalar rule, dating it from
  arrival and leaving lastCounter stale so the next real burst reinstated a
  hole that never existed. Accumulate mode flushes on a timer, so a short
  cycle legitimately yields one sample; keep it on the chain.
- kMaxCounterGap was inert: an absurd gap yields an absurd prediction that the
  arrival backstop already rejects, and no input can distinguish the two
  rules. Removed rather than left implying a behaviour it did not have.

FrameDecoder.h now states the deliberate divergence from StreamHub -- which
converts hrt with the LOCAL MARTe timer frequency, valid only because it runs
on the producer's host -- and why a remote scope's drift is irreducible.

Three new tests, each sabotage-proven non-vacuous: producer restart, short
flushes staying on the chain, and a 20000-packet undeclared run after a
day of producer uptime that asserts SPACING as well as ordering (the
monotonic guard alone restores order while leaving positions wrong).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 22:16:11 +02:00
Martino FerrariandClaude Opus 4.6 3270284cfe fix(udpscope): bound the reconstructed timeline against the wall clock
Round 3 of the Task 4 review. Three defects, all in FrameDecoder rule 3.

The resync backstop was one-directional. `predicted` is never below
lastEmittedEnd + dt, so rejecting a correction that would step backwards
meant only a LAGGING chain could ever be pulled back; a chain running fast
drifted ahead without bound. Two hosts' crystals differ by tens of ppm, so a
declared SamplingRate is always slightly wrong in one direction or the other
and this is certain on a long session. A leading timeline cannot be corrected
in one burst without going backwards -- lastEmittedEnd is by definition past
arrival -- so the excess is bled off by drawing each burst 10 % narrower until
the timeline is back inside the threshold.

A repeated packet counter was treated as a normal packet. The C client
de-duplicates fragments only, so an unfragmented update reaching a host that
joined the group on two interfaces was emitted twice, doubling the values and
advancing the timeline by a burst that never existed.

The samplingRate == 0 path differenced two HrtRateFit::toSeconds() results.
toSeconds() divides an absolute tick count -- ~1e11 on a producer that has
been up a day -- by a rate refitted on every packet, so its few-parts-in-1e4
wobble arrives multiplied by the whole elapsed epoch: tens of milliseconds of
jitter on a value whose consecutive difference is a few milliseconds. Raw
ticks are differenced instead, anchored on the first usable packet so the
wobble applies only to the interval since attach.

The existing hrt-gap test could not have caught the last one: its 10 ms
producer period made the expected answer exactly kDefaultDt, so a decoder
that derived nothing passed. It now uses 25 ms.

Four tests added, all sabotage-proven. The plan is updated to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 21:55:33 +02:00
Martino FerrariandClaude Opus 4.6 7102412a9f fix(udpscope): reconstruct lost accumulated bursts from the packet counter
Review found the decoder was estimating something the wire states exactly.
FrameView::counter increments once per update, so a gap of g means g-1 lost
datagrams; reinstating their duration restores the hole precisely, with no
threshold and no dependence on arrival time. The arrival-anchor comparison
survives only as a backstop for what the counter cannot express — a producer
restart, a counter stuck at zero, a wrong declared rate — and can no longer
step a signal's timestamps backwards, which the ring and trigger forbid.

Also from review: guard the time-signal lookup against a frame carrying more
signals than the installed table, and give FrameBuilder a counter parameter.
Leaving it at zero had hidden the counter rules from every test, and made the
hrt-gap test vacuous — under uniform arrivals the hrt path and packetBurst
agree by construction, so it could not tell which branch answered. Its
arrivals now carry zero-mean jitter.

Each new assertion was proven non-vacuous by sabotage: dropping the gap term,
the backward guard, or the hrt branch fails exactly its own test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 20:16:52 +02:00
Martino FerrariandClaude Opus 4.6 892e3eae28 fix(udpscope): bound accumulated-burst chaining so packet loss cannot displace the trace
Forward-chaining each accumulated burst onto the previous one suppresses
arrival jitter, but an unchecked chain never recovers: one lost datagram, or a
declared sampling rate that differs from the producer's real one, dates every
later sample early for the rest of the run. The chain is now a prediction,
compared each packet against the arrival anchor and abandoned beyond
kBurstResyncThresholdS, which bounds the error instead of accumulating it.

Plan amended so the hrt-fit fallback (unusable here: the fit needs 32 packets
and is itself corrupted by bursty arrivals) cannot come back.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 20:08:05 +02:00
Martino FerrariandClaude Sonnet 4.6 5a8479cda9 feat(udpscope): per-element timestamp reconstruction from UDPS frames
Implements FrameDecoder with five timing rules that mirror
UDPSourceSession.cpp: FullArray (per-element time signal), FirstSample
and LastSample (rate-spread from anchor), accumulated scalar with
declared rate (forward-chain anchoring, immune to arrival jitter), and
PACKET burst (backward-span from previous arrival). 9 new tests, 38
pass total.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 20:01:30 +02:00