The warmup path was the one that skipped the clamp
Live trace of the median predictor: in steady state the tick is gone - zero spikes over five seconds where there were seven, every step blending instead of 182 in 250, the blend fraction floored at 0.294 instead of 0.014, and the prediction within 13 to 20 ms of the gap that followed it. But the log also read "predicting 2.054s", above the 1.0s clamp, which should not have been reachable. It was: the fewer-than-three-samples path returned the raw gap without clamping it. The arithmetic identifies it exactly - 0.0201 / (2.054 + 0.0201) is 0.009685, against a logged blend fraction of 0.00968523. So the old near-stall survived, confined to the first three updates after an entity appears. That is every respawn, and a pod is being watched closely at exactly that moment. Route every path through one ClampPredictedInterval, and tighten the bounds now that the real send rate is known to be about 30ms: nothing slower than half a second enters the sample window, and no prediction reaches beyond 250ms. The second of those puts a floor under the blend fraction itself - at a 20ms step the worst case is 0.02/(0.25+0.02), roughly 7% of the gap per step, so a pod converges on its projection in a dozen steps rather than crawling toward it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+41
-16
@@ -32,16 +32,47 @@ static Scalar gLastPercent = 0.0f;
|
||||
// The second pair are a backstop on the answer, set deliberately wide so
|
||||
// that in every sane case the median decides it and these never bind.
|
||||
//
|
||||
// Measured send rate on a live connection is about 30ms, so half a second
|
||||
// is already sixteen times slower than anything healthy.
|
||||
//
|
||||
static const Scalar kMinimumUpdateInterval = 0.001f;
|
||||
static const Scalar kOutlierUpdateInterval = 2.0f;
|
||||
static const Scalar kOutlierUpdateInterval = 0.5f;
|
||||
static const Scalar kMinimumPredictedInterval = 0.010f;
|
||||
static const Scalar kMaximumPredictedInterval = 1.0f;
|
||||
|
||||
//
|
||||
// Never predict further ahead than this, which puts a floor under the
|
||||
// dead reckoner's blend fraction: at a 20ms step the worst case becomes
|
||||
// 0.02/(0.25+0.02), near enough 7% of the gap per step, so a pod still
|
||||
// converges on its projection in a dozen steps instead of crawling.
|
||||
//
|
||||
static const Scalar kMaximumPredictedInterval = 0.25f;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// RP412NETPREDICT=0 restores the original single-sample prediction, so the
|
||||
// two can be compared on the same build and the same connection.
|
||||
//
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Every path out of PredictUpdateInterval goes through here. It used not
|
||||
// to, and the one that skipped it was the bug.
|
||||
//
|
||||
static Scalar
|
||||
ClampPredictedInterval(Scalar interval)
|
||||
{
|
||||
if (interval < kMinimumPredictedInterval)
|
||||
{
|
||||
return kMinimumPredictedInterval;
|
||||
}
|
||||
if (interval > kMaximumPredictedInterval)
|
||||
{
|
||||
return kMaximumPredictedInterval;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
static Logical
|
||||
UseMedianPrediction()
|
||||
{
|
||||
@@ -918,12 +949,16 @@ Scalar
|
||||
}
|
||||
|
||||
//
|
||||
// Too few samples to hold an opinion. Fall back to the old behaviour
|
||||
// rather than inventing a rate we have no evidence for.
|
||||
// Too few samples to hold an opinion. Fall back to the gap we just saw
|
||||
// rather than inventing a rate we have no evidence for - but clamp it
|
||||
// like any other answer. Leaving this path unclamped let a 2.05s gap
|
||||
// through in the first updates after an entity appeared, which drove
|
||||
// the blend fraction to 0.0097 and stalled the step. That is every
|
||||
// respawn, and it is exactly when the pod is being watched.
|
||||
//
|
||||
if (updateIntervalCount < 3)
|
||||
{
|
||||
return latest;
|
||||
return ClampPredictedInterval(latest);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -950,17 +985,7 @@ Scalar
|
||||
sorted[j + 1] = value;
|
||||
}
|
||||
|
||||
Scalar median = sorted[updateIntervalCount / 2];
|
||||
|
||||
if (median < kMinimumPredictedInterval)
|
||||
{
|
||||
median = kMinimumPredictedInterval;
|
||||
}
|
||||
if (median > kMaximumPredictedInterval)
|
||||
{
|
||||
median = kMaximumPredictedInterval;
|
||||
}
|
||||
return median;
|
||||
return ClampPredictedInterval(sorted[updateIntervalCount / 2]);
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user