// Copyright (c) 2026 Jaap van Vliet // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 #pragma once // Project-owned, measurement-only RelTime edge estimator. Times are seconds. #include #include #include #include #include namespace timing_probe { inline double percentile(std::vector v, double q) { if (v.empty()) return NAN; std::sort(v.begin(), v.end()); return v[std::min(v.size() - 1, size_t(std::ceil(q * (v.size() - 1))))]; } struct Edge { double time, second, width; }; struct EdgeDecision { Edge edge{}; double residual = NAN; bool observed = false, inlier = false; const char *rule = "none"; }; class Estimator { public: std::deque edges; std::deque rtts; std::vector inliers; double origin = 0, intercept = 0, slope = 1, window = .2; double span = 0, driftSigma = NAN, spread = 0, lastUsable = 0; bool fitted = false, sampled = false, primed = false, driftReported = false, precisionFit = false; double lastTime = 0, lastSecond = 0, lastRtt = 0, due = 0; double missedSecond = -1; unsigned phase = 0, rejected = 0, missed = 0, outliers = 0, stalled = 0; bool recovered = false; double dither = 0; bool guided = false; double guideZero = 0, guideSlope = 1; double scheduleAt(double second) const { return guided ? guideZero + second * guideSlope : timeAt(second); } unsigned burst = 0; uint64_t usableEdges = 0; EdgeDecision decision; const char *sampleOutcome = "accepted", *sampleReason = "sample"; void reset() { *this = Estimator{}; } void seed(double second, double time) { if (sampled || primed) return; primed = true; origin = time; intercept = second + .5; slope = 1; due = time + .25; } double rttLimit() const { if (rtts.size() < 12) return .080; return std::clamp(2 * percentile({rtts.begin(), rtts.end()}, .95), .010, .080); } double windowFloor() const { const double rtt = rtts.empty() ? 0 : percentile({rtts.begin(), rtts.end()}, .95); return std::clamp(3 * spread + rtt / 2, .020, .250); } void fit() { if (edges.empty()) return; const double newOrigin = edges.back().time; std::vector used(edges.begin(), edges.end()); // Broad acquisition brackets can seed phase, but must not dominate a // mature precision fit or hide delayed ticks behind their large width. precisionFit = std::count_if(used.begin(), used.end(), [](Edge e) { return e.width <= .100; }) >= 20; if (precisionFit) used.erase( std::remove_if(used.begin(), used.end(), [](Edge e) { return e.width > .100; }), used.end()); double rate = 1, offset = 0, sigma = NAN; // Classify against an unconstrained fit, including while drift is not yet // reportable: a real rate offset must not masquerade as phase outliers. for (unsigned pass = 0; pass <= 6 && !used.empty(); ++pass) { double sx = 0, sy = 0, sxx = 0, sxy = 0, weight = 0; for (auto e : used) { double x = e.time - newOrigin, w = 1 / std::pow(std::max(.020, e.width), 2); weight += w; sx += w * x; sy += w * e.second; sxx += w * x * x; sxy += w * x * e.second; } const double denom = sxx - sx * sx / weight; rate = used.size() >= 10 && denom > 0 ? (sxy - sx * sy / weight) / denom : 1; if (std::abs(rate - 1) > .002) rate = 1; offset = (sy - rate * sx) / weight; std::vector residual, deviations; for (auto e : used) residual.push_back((e.second - (offset + rate * (e.time - newOrigin))) / rate); const double median = percentile(residual, .5); for (double r : residual) deviations.push_back(std::abs(r - median)); const double mad = 1.4826 * percentile(deviations, .5); std::vector keep; double sse = 0; for (size_t i = 0; i < used.size(); ++i) { const auto e = used[i]; if (pass == 6 || std::abs(residual[i] - median) <= std::max({3 * mad, e.width / 2, .010})) { keep.push_back(e); sse += std::pow(e.second - (offset + rate * (e.time - newOrigin)), 2) / std::pow(std::max(.020, e.width), 2); } } if (keep.size() == used.size()) { spread = std::count_if(used.begin(), used.end(), [](Edge e) { return e.width <= .100; }) >= 20 ? mad : 0; sigma = used.size() > 2 && denom > 0 ? std::sqrt(std::max(1. / 12, sse / (used.size() - 2)) / denom) : NAN; break; } used.swap(keep); } if (used.empty()) return; // Retain the previous model if all candidates are unusable. inliers = used; outliers = unsigned(edges.size() - inliers.size()); span = inliers.back().time - inliers.front().time; driftSigma = sigma * 1e6; driftReported = span >= 120 && std::isfinite(sigma) && driftSigma <= 15 && std::abs(rate - 1) > 2 * sigma; slope = driftReported ? rate : 1; origin = newOrigin; // Recenter the offset when the rate is held at unity. double weight = 0, sum = 0; for (auto e : inliers) { if (!driftReported && inliers.back().time - e.time > 60) continue; const double w = 1 / std::pow(std::max(.020, e.width), 2); weight += w; sum += w * (e.second - slope * (e.time - origin)); } intercept = sum / weight; fitted = true; window = std::max(window, windowFloor()); } bool stale(double now, double started, unsigned seconds) const { return now - (usableEdges ? lastUsable : started) > seconds; } double timeAt(double second) const { return origin + (second - intercept) / slope; } // Every adjacent transition is observable, even when too broad to fit. bool sample(double sent, double received, double second) { decision = {}; recovered = false; sampleOutcome = "accepted"; sampleReason = "sample"; const double rtt = received - sent, t = (sent + received) / 2; if (!std::isfinite(sent) || !std::isfinite(received) || rtt < 0 || !std::isfinite(second)) { sampleOutcome = "error"; sampleReason = "invalid-sample"; return false; } const double limit = rttLimit(); rtts.push_back(rtt); if (rtts.size() > 128) rtts.pop_front(); if (rtt > limit) { ++rejected; sampleOutcome = "rejected-rtt"; sampleReason = "adaptive-rtt-limit"; due = t + .34; return false; } bool edge = false; if (sampled && second > lastSecond + 1) { missed += unsigned(second - lastSecond - 1); window = std::min(.25, std::max(windowFloor(), window * 2)); } if (sampled && second == lastSecond + 1 && t > lastTime) { const double width = t - lastTime + (rtt + lastRtt) / 2; const double center = (lastTime + t) / 2 + (rtt - lastRtt) / 4; decision = {{center, second, width}, fitted ? (center - timeAt(second)) * 1000 : NAN, true, false, "wide-bracket"}; if (width <= .65) { edges.push_back(decision.edge); if (edges.size() > 300) edges.pop_front(); fit(); decision.inlier = std::any_of(inliers.begin(), inliers.end(), [center](Edge e) { return e.time == center; }); decision.rule = decision.inlier ? "median-mad-width-floor" : precisionFit && width > .100 ? "wide-bracket" : "median-mad-outlier"; if (decision.inlier) { ++usableEdges; lastUsable = center; edge = true; window = std::max(windowFloor(), window * .25); } } if (!decision.inlier) { ++rejected; ++stalled; } else stalled = 0; // A bad acquisition phase must not schedule one rejected edge per // second forever. Keep RTT history and the caller's request budget. if (stalled >= 3 && inliers.size() < 20) { edges.clear(); inliers.clear(); fitted = primed = false; span = spread = 0; slope = 1; window = .2; phase = 0; stalled = 0; recovered = true; } } // Counter anomalies are observations, not authority to reset a model. // Existing transport/PCM-anchor hooks own discontinuity resets. if (sampled && (second < lastSecond || second - lastSecond > (t - lastTime) * 1.01 + 2)) { ++rejected; sampleReason = "counter-discontinuity"; window = .25; } sampled = true; lastTime = t; lastSecond = second; lastRtt = rtt; if (!guided && decision.observed && !decision.inlier && inliers.size() < 20) { // Search immediately after a miss; extrapolating the same bad fit // here can place every subsequent request on the same tick side. phase = 0; window = .25; due = t + .34; } else if (!guided && edges.size() < 5) { due = t + .34; } else if (decision.observed) { phase = 0; // Deterministic phase dither prevents the sampling schedule locking // to one side of a quantized tick and biasing the drift regression. dither = (std::fmod(++burst * .6180339887498949, 1.) - .5) * 2 * std::min(window, .040); due = dither + scheduleAt(second + 1) - window - percentile({rtts.begin(), rtts.end()}, .5) / 2; } else if (guided || fitted || primed) { const double prediction = dither + scheduleAt(second + 1) - percentile({rtts.begin(), rtts.end()}, .5) / 2; const double near = std::min(window, .020); if (phase == 0 && t < prediction) { phase = 1; due = prediction; } else if (phase <= 1 && t < prediction + near) { phase = 2; due = prediction + near; } else { if (t >= prediction + window && missedSecond != second + 1) { ++missed; missedSecond = second + 1; window = std::min(.25, std::max(windowFloor(), window * 2)); } phase = 0; due = t + .34; } } else due = t + .34; return edge; } std::vector residuals() const { std::vector v; if (fitted) for (auto e : inliers) v.push_back(1000 * std::abs(e.second - (intercept + slope * (e.time - origin))) / slope); return v; } }; class Limiter { std::deque requests; public: void prune(double t) { while (!requests.empty() && t - requests.front() >= 10) requests.pop_front(); } bool take(double t) { prune(t); size_t recent = 0; for (double s : requests) if (t - s < 1) ++recent; if (recent >= 8 || requests.size() >= 30) return false; requests.push_back(t); return true; } // The dedicated worker has only one outstanding request. Move its budget // reservation to the captured first-send time once the reply completes. void sent(double t) { if (!requests.empty() && t > requests.back()) requests.back() = t; } double rate(double t) { prune(t); return requests.size() / 10.; } }; inline bool eligible(bool enabled, bool coordinator, bool playing, bool ours, bool allowed) { return enabled && coordinator && playing && ours && allowed; } } // namespace timing_probe