fix(udpscope): make rule 3 converge in both branches and survive reordering
Eight review findings on FrameDecoder's accumulated-scalar rule. The squeeze that pulls a leading timeline back was expressed as a fraction of the NOMINAL burst width, which cannot converge: inside one timestamps() call the wall clock is frozen, so any positive step raises the lead measured at that instant, and the lead only falls because the wall advances between packets. At the kMinBleedFactor floor the timeline still gained 0.05 * nominal per packet, so a declared SamplingRate of 30 against a producer really flushing 10 samples at 1 kHz ran away without bound (667 s of lead after 1000 s of stream). Cap the burst's total advance at half the wall time really elapsed since this signal's previous burst instead, and the lead strictly falls for any declared rate. SigState gained lastEmittedWall for that reference; lastPacketWall could not be reused because it belongs to packetBurst. The hrt branch contributed zero elapsed for a late datagram but still wrote the hrt reference back to it, so the next packet's delta spanned two intervals and fabricated a whole extra packet of producer time — permanently, since the monotonic clamp discards the correction ClockOffset would have made. Reordering is reachable in production: udps_client.c only counts counter gaps. Simply never regressing the reference is not the fix either, because a producer restart would then freeze the signal forever, so the two are now separated by the size of the backward jump. The hrt branch's clamp was also one-directional, reintroducing on that branch exactly the defect the declared branch's squeeze exists to prevent: a backward wall step (NTP, suspend/resume) left a permanent lead. It now shares the same wall-elapsed cap. Also: anchor an hrt-branch burst's LAST element on arrival, matching the declared branch, so two accumulated scalars in one scope do not sit a burst apart on the shared X axis; treat a non-finite samplingRate off the wire as undeclared, since +inf produced a 0.0/0.0 factor the floor could not catch and turned every stamp NaN; write lastCounter on the hrt branch so duplicate datagrams are dropped there too; and correct two comments that argued for the current code with claims that are false (a counter-gap clamp reaches the opposite outcome, not the same one earlier, and the squeeze's steady state is a sawtooth, not a fixed offset). Seven new tests, each proven non-vacuous by sabotage; 54 pass. Plan document Task 4 re-synced and its stale test count and "agree on the same stream" claim corrected.
This commit is contained in: