fix(udpscope): stop a wrong hrtDt from displacing the trace permanently
On the hrt branch the derived period is not just a spacing: it is the burst width ClockOffset latches against, so a wrong one shifts the whole trace by an amount that is usually too small for kRecalibThresholdS to ever heal. Three routes to a wrong period were open. Packet loss. elapsed spans every packet since the last one seen, but it was divided by prevAccCount alone, so a lost datagram scaled the period by the whole counter gap. Since a burst is anchored on its LAST element, too wide means it ends in the FUTURE: +22.5 ms for one loss, +225 ms for ten, at 10 samples per 25 ms packet, mis-spacing 2.7% of all samples at 1% loss. The declared branch already reads the counter for exactly this; the hrt branch now does too. Producer restart and reorder. Both leave elapsed at zero, so no period can be measured -- and the restart packet is also the one that re-latches after offset.reset(). Falling back to kDefaultDt is only right at 1 kHz; measured standing displacement was +13.5 ms at 10 samples per 25 ms and -89 ms at 100 per 10 ms. Remember the last measured period instead. A stray hrt == 0 packet re-enters the warm-up branch, which spans from packetBurst's lastPacketWall -- a field the hrt branch never wrote, so it still held the start of the session. After 153 packets that emitted a burst 3.8 s in the past, worse the longer the scope had run. Also: rule 2 with no declared rate stacked every element of the array on one instant (as UDPSourceSession.cpp:522 does, harmlessly, for a host-local consumer). Spread it from consecutive time-signal anchors, which measure the burst on the producer's own clock. Reverts the previous commit's wallElapsed <= 0 change: it was measurably inert -- the step floor two lines below already yields the same number -- and its comment claimed a divergence it did not stop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3add2c42b9
commit
f97fd825c4
@@ -10,7 +10,17 @@
|
||||
* Source/Applications/StreamHub/UDPSourceSession.cpp documents this failure and
|
||||
* solves it; these are the same rules, computed from udps_frame_t's own fields.
|
||||
*
|
||||
* Three rules deliberately differ, all in the accumulated-scalar case (rule 3).
|
||||
* They are NOT the same code, and the differences are not a short list. Every
|
||||
* one of them comes from the same root: StreamHub runs on the producer's host,
|
||||
* so its arrival time IS the producer's clock and its local
|
||||
* HighResolutionTimer::Frequency() IS the frequency behind the packet's hrt.
|
||||
* Neither holds over a network, so anything StreamHub can read directly this
|
||||
* decoder has to estimate (HrtRateFit, ClockOffset), and anything it estimates
|
||||
* it must also defend — hence the monotonic clamps, the kWallBleedFraction
|
||||
* bleed, the reorder and restart guards and the duplicate-datagram drop, none of
|
||||
* which exist in UDPSourceSession.cpp. Do not read the three sections below as
|
||||
* exhaustive; they are the three that change where a sample LANDS, and so the
|
||||
* three worth checking first when a trace looks wrong.
|
||||
*
|
||||
* First, the anchor. StreamHub anchors every accumulated-scalar burst on the
|
||||
* packet's own hrt, converted with the LOCAL MARTe HighResolutionTimer
|
||||
@@ -42,6 +52,15 @@
|
||||
* datagram and reinstates a hole that never existed. So a signal that has
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@@ -92,6 +111,19 @@ private:
|
||||
double accProdSec = 0.0;
|
||||
bool lastAccValid = false;
|
||||
uint32_t prevAccCount = 0;
|
||||
/** Last inter-element period the hrt branch actually MEASURED, used
|
||||
* whenever this packet cannot measure one of its own (no previous tick,
|
||||
* or hrt went backwards). The constant kDefaultDt is a poor substitute:
|
||||
* it is only right at 1 kHz, and a wrong period here is not merely a
|
||||
* wrong spacing for one burst — it is the burst width ClockOffset
|
||||
* 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. */
|
||||
double prevAnchorProdSec = 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
|
||||
* next burst is chained onto that end, with the counter gap reinstating
|
||||
|
||||
Reference in New Issue
Block a user