{"version":3,"file":"drift.mjs","sources":["../../src/predict/drift.ts"],"sourcesContent":["/**\n * Rolling reconcile-drift telemetry, shared by {@link Reconciler} and\n * {@link SimReconciler} so both report it identically.\n *\n * Each reconcile feeds the max |correction| (world/pose units) into a pair:\n *\n *   - **ema** — EMA of the magnitude: the *persistent* component. A steady\n *     nonzero `ema` means the prediction is genuinely *diverging* from the\n *     server (wrong `dt`, a non-shared `step`, a skipped input) — the\n *     rubber-banding you otherwise can't tell apart from jitter.\n *   - **peak** — a decaying max: *recent* spikes. A `peak` well above a low\n *     `ema` is network *jitter* (occasional rollbacks), not divergence.\n *\n * Both ~0 ⇒ the prediction matched the server. Fed once per reconcile (patch\n * rate, not per render frame), so the cost is two flops.\n */\nexport interface Drift {\n    /** EMA of reconcile-correction magnitude — the persistent/divergence component. */\n    ema: number;\n    /** Decaying max of reconcile-correction magnitude — recent jitter spikes. */\n    peak: number;\n}\n\nconst DRIFT_EMA_ALPHA = 0.1;\nconst DRIFT_PEAK_DECAY = 0.9;\n\n/** A fresh zeroed {@link Drift}. */\nexport function newDrift(): Drift {\n    return { ema: 0, peak: 0 };\n}\n\n/** Fold one reconcile's max |correction| (`mag`, world/pose units) into `d`. */\nexport function updateDrift(d: Drift, mag: number): void {\n    d.ema += (mag - d.ema) * DRIFT_EMA_ALPHA;\n    d.peak = Math.max(mag, d.peak * DRIFT_PEAK_DECAY);\n}\n\n/** Zero `d` — call on a hard resync (respawn) so a deliberate pose jump doesn't\n *  inflate the telemetry into looking like divergence. */\nexport function resetDrift(d: Drift): void {\n    d.ema = 0;\n    d.peak = 0;\n}\n\n/** The actionable read of the rolling drift — see {@link classifyDrift}. */\nexport type DriftStatus = \"matched\" | \"jitter\" | \"diverging\";\n\n/** Drift (world/pose units) at or below this is treated as float-noise:\n *  deterministic replay reproduces the server *exactly* (correction 0), so\n *  anything at this level is bit-rounding, not a real disagreement. Used as the\n *  matched/diverging floor only when the caller declared no tolerance. */\nconst FLOAT_NOISE = 1e-3;\n\n/**\n * Turn the rolling {@link Drift} into an actionable status. The debug panel and\n * the `warnOnDivergence` warning both classify through here, so they can never\n * disagree about what the numbers mean:\n *\n *   - **matched** — `ema` and `peak` both within the floor: the prediction\n *     reproduces the server. Nothing to do.\n *   - **jitter** — `ema` within the floor but `peak` above it: corrections\n *     happen yet DON'T persist (they decay out of the EMA) → transient network\n *     jitter (packet loss / reorder), not a bug; smoothing absorbs it.\n *   - **diverging** — `ema` above the floor: the *persistent* correction is real\n *     → the client and server simulations disagree (a determinism bug).\n *\n * The EMA's smoothing IS the persistence test — a one-off spike barely moves it,\n * so only sustained corrections read as `diverging`. The floor is the caller's\n * `tolerance` (`warnOnDivergence`) when set — so the matched/diverging cut needs\n * no game-specific magic number — and falls back to {@link FLOAT_NOISE} (\"is it\n * ~0\") when it isn't.\n */\nexport function classifyDrift(d: Drift, tolerance?: number): DriftStatus {\n    const floor = tolerance !== undefined && tolerance > FLOAT_NOISE ? tolerance : FLOAT_NOISE;\n    if (d.ema >= floor) { return \"diverging\"; }\n    if (d.peak >= floor) { return \"jitter\"; }\n    return \"matched\";\n}\n"],"names":[],"mappings":";;;;;;AAuBA,MAAM,eAAe,GAAG,GAAG;AAC3B,MAAM,gBAAgB,GAAG,GAAG;AAE5B;SACgB,QAAQ,GAAA;IACpB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;AAC9B;AAEA;AACM,SAAU,WAAW,CAAC,CAAQ,EAAE,GAAW,EAAA;AAC7C,IAAA,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,eAAe;AACxC,IAAA,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,gBAAgB,CAAC;AACrD;AAEA;AAC0D;AACpD,SAAU,UAAU,CAAC,CAAQ,EAAA;AAC/B,IAAA,CAAC,CAAC,GAAG,GAAG,CAAC;AACT,IAAA,CAAC,CAAC,IAAI,GAAG,CAAC;AACd;AAKA;;;AAG0E;AAC1E,MAAM,WAAW,GAAG,IAAI;AAExB;;;;;;;;;;;;;;;;;;AAkBG;AACG,SAAU,aAAa,CAAC,CAAQ,EAAE,SAAkB,EAAA;AACtD,IAAA,MAAM,KAAK,GAAG,SAAS,KAAK,SAAS,IAAI,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW;AAC1F,IAAA,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,EAAE;AAAE,QAAA,OAAO,WAAW;IAAE;AAC1C,IAAA,IAAI,CAAC,CAAC,IAAI,IAAI,KAAK,EAAE;AAAE,QAAA,OAAO,QAAQ;IAAE;AACxC,IAAA,OAAO,SAAS;AACpB;;;;"}