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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7102412a9f
commit
3270284cfe
@@ -71,6 +71,15 @@ public:
|
||||
* from its own boot, not from the Unix epoch. Pass the result to
|
||||
* ClockOffset::map() to land it on the wall clock; latching that arbitrary
|
||||
* epoch difference is precisely what ClockOffset is for.
|
||||
*
|
||||
* @warning Never subtract two of these results to measure a short interval.
|
||||
* The rate is refitted on every add() and wobbles by a few parts in 1e4,
|
||||
* while hrt is already ~1e11 ticks by the time a scope attaches to a
|
||||
* long-running producer — so the division carries that relative wobble
|
||||
* multiplied by the entire elapsed epoch, tens of milliseconds of jitter on
|
||||
* an absolute value. The jitter is common to both operands only if the rate
|
||||
* did not change between them, which is exactly what it does. To measure an
|
||||
* interval, difference the raw ticks and divide once by ticksPerSecond().
|
||||
*/
|
||||
double toSeconds(uint64_t hrt) const;
|
||||
void reset();
|
||||
|
||||
Reference in New Issue
Block a user