A quiet sender and a stalled loop are not the same silence
With the clamp in, spikes now occur only in windows where updates stopped arriving. Steady state is clean - zero spikes, 250 of 250 steps blending, the prediction within 18ms - while the one window holding a 1.236s gap carried all four spikes. The clamp is doing exactly what it was built to do: 0.01976 / (0.25 + 0.01976) is 0.0733, matching the logged blend floor of 0.0732601 against the old 0.0097. So what is left is not jitter. It is the absence of data for over a second, and no predictor can invent motion it was never told about. The useful question is whose silence it is, and the answer is already in the arrival pattern. A long gap followed by ordinary 30ms gaps means the sender went quiet - their machine or the connection. A long gap followed by a burst of near-zero gaps means the packets were sitting in the queue while OUR loop was busy elsewhere, and we read them all at once the moment it came back. From inside the dead reckoner the two are indistinguishable, and they want opposite fixes. Record the widest gap, the count over 200ms and the count under 5ms, per entity, and let the trace name which pattern it saw. This matters more than it might: the camera station only recently began rastering the map, which is exactly the kind of work that stalls a main loop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,14 @@ static const Scalar kMinimumPredictedInterval = 0.010f;
|
||||
//
|
||||
static const Scalar kMaximumPredictedInterval = 0.25f;
|
||||
|
||||
//
|
||||
// A gap this long is a stall, not jitter - six times the observed rate.
|
||||
// A gap this short cannot be a sender keeping to 30ms, so it is a packet
|
||||
// that was already waiting when we finally got round to reading it.
|
||||
//
|
||||
static const Scalar kLongGapThreshold = 0.200f;
|
||||
static const Scalar kQueuedGapThreshold = 0.005f;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// RP412NETPREDICT=0 restores the original single-sample prediction, so the
|
||||
@@ -745,7 +753,21 @@ void
|
||||
<< "m, predicting " << predictedInterval
|
||||
<< "s worst miss " << worst_error << "s\n"
|
||||
<< std::flush;
|
||||
|
||||
DEBUG_STREAM << "CamLog: replicant arrivals - widest gap "
|
||||
<< widestGap << "s, " << longGapCount
|
||||
<< " long, " << queuedGapCount
|
||||
<< " queued ("
|
||||
<< ((longGapCount > 0 && queuedGapCount > 0)
|
||||
? "our loop stalled"
|
||||
: (longGapCount > 0
|
||||
? "sender went quiet"
|
||||
: "clean"))
|
||||
<< ")\n" << std::flush;
|
||||
}
|
||||
widestGap = 0.0f;
|
||||
longGapCount = 0;
|
||||
queuedGapCount = 0;
|
||||
next_say = ((Scalar) Now()) + 5.0f;
|
||||
steps = 0;
|
||||
spikes = 0;
|
||||
@@ -1025,6 +1047,14 @@ void
|
||||
}
|
||||
predictedInterval = predicted;
|
||||
|
||||
//
|
||||
// Arrival statistics, for telling a quiet sender from
|
||||
// our own stalled loop. See the members.
|
||||
//
|
||||
if (diff > widestGap) { widestGap = diff; }
|
||||
if (diff > kLongGapThreshold) { longGapCount++; }
|
||||
if (diff < kQueuedGapThreshold) { queuedGapCount++; }
|
||||
|
||||
nextUpdate += predicted;
|
||||
}
|
||||
else
|
||||
@@ -2061,6 +2091,9 @@ Mover::Mover(
|
||||
ResetUpdateIntervals();
|
||||
predictedInterval = 0.0f;
|
||||
predictionError = 0.0f;
|
||||
widestGap = 0.0f;
|
||||
longGapCount = 0;
|
||||
queuedGapCount = 0;
|
||||
|
||||
normalizeCount = 0;
|
||||
if (IsInitialStasis())
|
||||
|
||||
@@ -311,6 +311,19 @@ protected:
|
||||
predictedInterval,
|
||||
predictionError;
|
||||
|
||||
//
|
||||
// Arrival statistics for the window a trace reports over. A long gap
|
||||
// followed by normal gaps means the sender went quiet; a long gap
|
||||
// followed by a burst of near-zero ones means OUR loop stalled and the
|
||||
// packets queued up behind it. The two look identical from inside the
|
||||
// dead reckoner and want opposite fixes.
|
||||
//
|
||||
Scalar
|
||||
widestGap;
|
||||
int
|
||||
longGapCount,
|
||||
queuedGapCount;
|
||||
|
||||
Scalar
|
||||
PredictUpdateInterval(Scalar latest);
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user