fix(udpscope): keep the packet counter in lockstep with the tick reference
The counter is the denominator of the very period lastAccHrt is the numerator of, so any packet that cannot move the tick reference must not move the counter either. Two paths were violating that: a reordered datagram rolled the counter back while the reference correctly held (next burst drawn 0.048x too narrow at distance 20, 83.3% worst spacing error under 2% sustained reordering), and a stray hrt == 0 packet advanced the counter from the warm-up branch without a tick to match (+22.5 ms of future-dating per stray packet). Rules 1 and 2 now record a counter too. The duplicate-datagram guard is keyed on one, so an array rule that recorded none was exempt and plotted every doubly-delivered update twice. Also: rule 2 divides by the count the anchor actually spans and falls back to the last period it derived; the counter-gap test is wrap-safe so 2^32 rollover reads as no information rather than 2e9 lost packets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f97fd825c4
commit
1c61e814c0
@@ -53,14 +53,16 @@
|
||||
* already burst keeps every later update on rule 3 regardless of its length; a
|
||||
* signal that has never burst is a genuine scalar and is left to rule 5.
|
||||
*
|
||||
* Two divergences OUTSIDE rule 3 are known and deliberately left as they are.
|
||||
* Rule 1 keys ClockOffset on the consuming signal, where UDPSourceSession.cpp:516
|
||||
* keys it on the time-signal index, so signals sharing a time signal share an
|
||||
* offset there and not here — immaterial, since the mapping they compute is the
|
||||
* same. And a FIRST_SAMPLE/LAST_SAMPLE signal whose time signal is absent falls
|
||||
* through to rule 4 rather than using its declared rate; that is a malformed
|
||||
* CONFIG, and spanning arrivals is the more honest answer than trusting a rate
|
||||
* whose anchor never arrived.
|
||||
* Divergences OUTSIDE rule 3 exist as well; two are named here only because they
|
||||
* are the ones easily mistaken for bugs. Rules 1 and 2 key ClockOffset on the
|
||||
* CONSUMING signal, where UDPSourceSession.cpp:538 and :516 key it on the
|
||||
* time-signal index, so signals sharing a time signal share an offset there and
|
||||
* not here — immaterial, since the mapping they compute is the same. And a
|
||||
* FIRST_SAMPLE/LAST_SAMPLE signal whose time signal is absent falls through to
|
||||
* rule 4 rather than using its declared rate; that is a malformed CONFIG, and
|
||||
* spanning arrivals is the more honest answer than trusting a rate whose anchor
|
||||
* never arrived. Neither this list nor the three above is closed: any other
|
||||
* difference you find is unexamined, not sanctioned.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@@ -119,10 +121,13 @@ private:
|
||||
* latches against, and the resulting displacement is usually too small
|
||||
* for kRecalibThresholdS to ever heal. Zero until first measured. */
|
||||
double lastHrtDt = 0.0;
|
||||
/** Rule 2 only: the previous packet's time-signal anchor, in PRODUCER
|
||||
* seconds. Consecutive anchors are what lets an array with no declared
|
||||
* sampling rate be spread at all. */
|
||||
/** Rule 2 only: the previous packet's time-signal anchor in PRODUCER
|
||||
* seconds, the element count that anchor spanned, and the last period
|
||||
* actually derived from a pair of them. Consecutive anchors are what
|
||||
* lets an array with no declared sampling rate be spread at all. */
|
||||
double prevAnchorProdSec = 0.0;
|
||||
uint32_t prevAnchorCount = 0u;
|
||||
double prevAnchorDt = 0.0;
|
||||
bool prevAnchorValid = false;
|
||||
/** For accumulated scalars (rule 3, either branch): end timestamp of the
|
||||
* most recently emitted burst, and the packet counter it came from. The
|
||||
@@ -130,6 +135,11 @@ private:
|
||||
* the exact duration of any lost datagrams. */
|
||||
double lastEmittedEnd = 0.0;
|
||||
uint32_t lastCounter = 0u;
|
||||
/** Whether lastCounter holds a counter this signal has actually seen.
|
||||
* Distinct from lastEmittedValid, which is about the emitted TIMELINE:
|
||||
* rules 1 and 2 keep a counter (so the duplicate-datagram guard can
|
||||
* fire for them too) without ever joining rule 3's chain. */
|
||||
bool counterValid = false;
|
||||
/** ARRIVAL time of the packet that produced lastEmittedEnd. Valid
|
||||
* exactly when lastEmittedValid is, so it needs no flag of its own.
|
||||
* Deliberately not lastPacketWall, which belongs to packetBurst() and
|
||||
|
||||
Reference in New Issue
Block a user