{"version":3,"file":"divergence.mjs","sources":["../../src/predict/divergence.ts"],"sourcesContent":["import { now } from \"../core/utils.ts\";\nimport { debugOverlayActive } from \"../debug-channel.ts\";\n\n/**\n * True when drift telemetry is being watched — the `@colyseus/sdk/debug` overlay\n * is loaded (its live receiver on `globalThis.__colyseusDebug` is what the panel\n * reads). Reconcilers skip ALL drift/correction bookkeeping when this is false\n * and no `warnOnDivergence` tolerance is set, so a production build that imports\n * neither pays nothing for the diagnostic.\n *\n * Delegates to {@link debugOverlayActive} so the pre-load PUBLISH BUFFER (which a\n * plain `Predict`/room publish now installs even without the overlay) doesn't\n * read as active — only the overlay's live receiver flips this on.\n */\nexport function diagnosticsActive(): boolean {\n    return debugOverlayActive();\n}\n\n// -Infinity (not 0) so the FIRST warning always fires: `now()` can be < the\n// throttle window early in a process, and `now() - 0` would wrongly suppress it.\nlet _lastWarnAt = -Infinity;\nconst WARN_THROTTLE_MS = 1000;\n\n/** @internal Reset the cross-controller warn throttle. For tests only. */\nexport function resetDivergenceThrottle(): void {\n    _lastWarnAt = -Infinity;\n}\n\n/**\n * Wiring warning, fired (once per controller) when a render value was read\n * BETWEEN `predict.tick()` and the frame's `input.send()` calls. Such a read is\n * one fixed step stale — the render interpolation clamps at the latest applied\n * step (it never extrapolates), so on late step-boundary frames the read\n * flat-tops, and frame jitter renders as visible stutter on fast objects.\n * Default-on (like the input replay-buffer overflow warning): this is a frame-\n * order bug with an established fix, not tuning telemetry.\n */\nexport function warnReadBeforePump(): void {\n    console.warn(\n        `@colyseus/sdk predict: a predicted value was read BEFORE this frame's input.send() calls. ` +\n        `Reads between predict.tick() and the frame's sends are one fixed step stale and stutter on ` +\n        `late frames. Send the frame's due inputs right after predict.tick(now) — tick and pump in ` +\n        `your earliest per-frame callback, before anything reads value()/pose(). For game logic ` +\n        `(zone checks, hit-reg) read .state/.world instead. See PREDICTION.md §4 (frame order).`,\n    );\n}\n\n/**\n * Dev-only memo collision warning ({@link diagnosticsActive}-gated, warn-once\n * per key per store). Fired when two `ctx.memo` calls in ONE step land on the\n * same slot — two key-less calls, or the same explicit key twice. The live\n * calls each run their compute (last write wins the slot), but every replay of\n * that seq returns the ONE frozen value to both call sites — a silent,\n * latency-dependent re-simulation corruption. Warns instead of throwing: a\n * single key-less memo per step is the common, correct form.\n */\nexport function warnMemoCollision(tick: number, key: string): void {\n    console.warn(key === \"\"\n        ? `@colyseus/sdk predict: two key-less ctx.memo calls ran in one step (input seq ${tick}) — ` +\n          `the key-less form is ONE shared slot per step, so replay returns one frozen value for ` +\n          `both (silent re-simulation corruption). Pass distinct keys: ctx.memo(\"a\", …) / ctx.memo(\"b\", …).`\n        : `@colyseus/sdk predict: ctx.memo(\"${key}\") ran twice in one step (input seq ${tick}) — ` +\n          `both calls share one slot on replay. Give each call site its own key ` +\n          `(include the loop index for a per-iteration memo).`,\n    );\n}\n\n/**\n * Dev-only divergence warning, wired by the opt-in `warnOnDivergence` tolerance\n * on a {@link Reconciler}/{@link SimReconciler}. Fired when the reconciler's\n * verdict is `diverging` (the *persistent* drift `ema` crossed the tolerance —\n * NOT a single jitter spike), throttled to one warning per second across all\n * reconcilers so a sustained divergence doesn't flood the console.\n *\n * The reconcile correction IS the client-vs-server divergence (the client\n * already holds the server's authoritative state it reconciles against), so this\n * needs no extra wire traffic. It names the input seq, the worst field + its\n * signed delta, and the usual cause — actionable, not just \"something's off\".\n */\nexport function warnDivergence(\n    acked: number,\n    lastCorrection: Record<string, number>,\n    ema: number,\n    tolerance: number,\n): void {\n    const t = now();\n    if (t - _lastWarnAt < WARN_THROTTLE_MS) { return; }\n    _lastWarnAt = t;\n\n    let worst = \"\";\n    let worstAbs = -1;\n    for (const f in lastCorrection) {\n        const v = lastCorrection[f];\n        const a = v < 0 ? -v : v;\n        if (a > worstAbs) { worstAbs = a; worst = f; }\n    }\n    const delta = worst ? lastCorrection[worst] : ema;\n    console.warn(\n        `@colyseus/sdk predict: prediction is diverging at input seq ${acked} — ` +\n        `rolling drift ${ema.toFixed(3)} ≥ tolerance ${tolerance}; \"${worst}\" currently off by ${delta.toFixed(3)}. ` +\n        `Most likely the client and server simulations disagree: a different fixed dt, ` +\n        `mismatched constants, a step function that isn't shared, or an input the server skipped. ` +\n        `See PREDICTION.md §5 (Determinism).`,\n    );\n}\n"],"names":[],"mappings":";;;;;;;;;AAGA;;;;;;;;;;AAUG;SACa,iBAAiB,GAAA;IAC7B,OAAO,kBAAkB,EAAE;AAC/B;AAEA;AACA;AACA,IAAI,WAAW,GAAG,CAAC,QAAQ;AAC3B,MAAM,gBAAgB,GAAG,IAAI;AAO7B;;;;;;;;AAQG;SACa,kBAAkB,GAAA;IAC9B,OAAO,CAAC,IAAI,CACR,CAAA,0FAAA,CAA4F;QAC5F,CAAA,2FAAA,CAA6F;QAC7F,CAAA,0FAAA,CAA4F;QAC5F,CAAA,uFAAA,CAAyF;AACzF,QAAA,CAAA,sFAAA,CAAwF,CAC3F;AACL;AAEA;;;;;;;;AAQG;AACG,SAAU,iBAAiB,CAAC,IAAY,EAAE,GAAW,EAAA;AACvD,IAAA,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK;UACf,CAAA,8EAAA,EAAiF,IAAI,CAAA,IAAA,CAAM;YAC3F,CAAA,sFAAA,CAAwF;YACxF,CAAA,gGAAA;AACF,UAAE,CAAA,iCAAA,EAAoC,GAAG,CAAA,oCAAA,EAAuC,IAAI,CAAA,IAAA,CAAM;YACxF,CAAA,qEAAA,CAAuE;AACvE,YAAA,CAAA,kDAAA,CAAoD,CACzD;AACL;AAEA;;;;;;;;;;;AAWG;AACG,SAAU,cAAc,CAC1B,KAAa,EACb,cAAsC,EACtC,GAAW,EACX,SAAiB,EAAA;AAEjB,IAAA,MAAM,CAAC,GAAG,GAAG,EAAE;AACf,IAAA,IAAI,CAAC,GAAG,WAAW,GAAG,gBAAgB,EAAE;QAAE;IAAQ;IAClD,WAAW,GAAG,CAAC;IAEf,IAAI,KAAK,GAAG,EAAE;AACd,IAAA,IAAI,QAAQ,GAAG,EAAE;AACjB,IAAA,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE;AAC5B,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;AAC3B,QAAA,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;AACxB,QAAA,IAAI,CAAC,GAAG,QAAQ,EAAE;YAAE,QAAQ,GAAG,CAAC;YAAE,KAAK,GAAG,CAAC;QAAE;IACjD;AACA,IAAA,MAAM,KAAK,GAAG,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,GAAG;AACjD,IAAA,OAAO,CAAC,IAAI,CACR,CAAA,4DAAA,EAA+D,KAAK,CAAA,GAAA,CAAK;AACzE,QAAA,CAAA,cAAA,EAAiB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA,aAAA,EAAgB,SAAS,CAAA,GAAA,EAAM,KAAK,sBAAsB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA,EAAA,CAAI;QAC7G,CAAA,8EAAA,CAAgF;QAChF,CAAA,yFAAA,CAA2F;AAC3F,QAAA,CAAA,mCAAA,CAAqC,CACxC;AACL;;;;"}