{"version":3,"file":"predictedEvents.cjs","sources":["../../src/predict/predictedEvents.ts"],"sourcesContent":["/**\n * Generic optimistic-prediction store with TTL-based mispredict cleanup.\n *\n * INTERNAL machinery — not exported from the public barrels. This store is\n * the settlement engine behind {@link PredictedEventChannel} (`defineEvent`)\n * and `PredictedSpawns`; consumers reach it through those surfaces.\n *\n * Pattern: the client predicts a discrete event happened (death, pickup,\n * door-open, projectile-spawn, …), marks the corresponding key. The render\n * layer reads the predicted state immediately rather than waiting ~RTT for\n * the server's authoritative confirmation. Two cleanup paths:\n *\n *   1. **Confirm** — caller observes the server's authoritative state change\n *      (e.g. via `Callbacks.listen(instance, \"alive\", …)`) and drops the\n *      entry: prediction was correct, schema is now the truth.\n *   2. **Prune** — each frame, entries older than `ttlMs` are dropped: the\n *      server didn't confirm within the window, so the prediction was wrong;\n *      the render reverts when the entry vanishes.\n *\n * Generic over the key type so callers can use string ids, schema instances\n * (via Map/WeakMap-compatible references), or composite tuples.\n *\n * Timing/TTL is injected via {@link configure}. Callers wire `now` (typically\n * `room.clock.serverNow`) and a `ttlMs` policy once at startup; subsequent\n * `predict()` / `prune()` calls can omit those args. Per-call args still\n * override the configured providers when you need explicit control (tests,\n * mixed clocks, etc.).\n */\nexport interface PredictedEventsConfig {\n    /** Source of \"now\" timestamps for the `at` param of `predict()` and the\n     *  comparison clock in `prune()`. Typically `() => room.clock.serverNow()`.\n     *  Defaults to `performance.now()` when unset. */\n    now?: () => number;\n    /** Eviction window for `prune()`. Number for a static TTL; function for\n     *  a dynamic policy (e.g. `() => Math.max(rtt * 2, 600)`). Re-evaluated\n     *  each `prune()` call so RTT-derived policies stay current. */\n    ttlMs?: number | (() => number);\n    /** Invoked when a prediction is dropped as a mispredict — by {@link\n     *  PredictedEvents.reject} (explicit) or by {@link PredictedEvents.prune}\n     *  (TTL expiry). NOT fired by `confirm` (correct) or `cancel` (deliberate\n     *  local undo). The render layer reads this to learn an event was undone. */\n    onReject?: (key: any) => void;\n}\n\n/**\n * Minimal clock shape consumed by {@link PredictedEvents.get}. A strict\n * subset of `RoomClockLike` — declared locally so this module stays\n * portable without an intra-package dependency on `../RoomClock.ts`.\n */\nexport interface PredictedEventsClock {\n    serverNow(): number;\n    smoothedRtt(): number;\n}\n\n/**\n * Default TTL policy for {@link PredictedEvents.get}: `max(2 × smoothedRtt, 600ms)`.\n *\n *   - **2× RTT** absorbs round-trip + ordinary jitter — predictions that\n *     don't confirm within this window were almost certainly mispredicted.\n *   - **600 ms floor** guards against RTT being 0 (clock not bootstrapped)\n *     or pathologically small (loopback testing).\n *\n * Exported so callers can compose against it, e.g.\n * `ttlMs: rtt => Math.max(rtt * 1.5, DEFAULT_TTL_POLICY(rtt))`.\n */\nexport const DEFAULT_TTL_POLICY = (rtt: number): number => Math.max(rtt * 2, 600);\n\n/** Options for the room-aware factory {@link PredictedEvents.get}. */\nexport interface PredictedEventsGetOptions<K = string> {\n    /** Eviction window. Number for a static TTL; function receives the current\n     *  RTT (from `room.clock.smoothedRtt()`) and returns the TTL in ms.\n     *  Defaults to {@link DEFAULT_TTL_POLICY}. */\n    ttlMs?: number | ((rtt: number) => number);\n    /** See {@link PredictedEventsConfig.onReject}. */\n    onReject?: (key: K) => void;\n}\n\n/**\n * Handle returned by {@link PredictedEvents.predict} — the discrete-event\n * counterpart to {@link import('./predictedSpawns.ts').SpawnHandle}. Lets a\n * caller roll back ({@link cancel}) or protect ({@link accept}) an optimistic\n * event, without tracking the key.\n */\nexport interface PredictedEventHandle<K = string> {\n    /** The predicted key. */\n    readonly key: K;\n    /** Drop the prediction now (rollback). No `onReject` — a deliberate undo. */\n    cancel(): void;\n    /** Server-confirmed: keep the predicted effect but exempt it from TTL\n     *  eviction. Await the authoritative schema change, then call {@link\n     *  PredictedEvents.confirm}. */\n    accept(): void;\n}\n\nexport class PredictedEvents<K = string> {\n    /**\n     * Room-aware factory. Auto-binds `now()` to `room.clock.serverNow()` and\n     * wires the optional dynamic-TTL policy with the room's `smoothedRtt()`.\n     * Equivalent to `new PredictedEvents() + configure({...})` but one line.\n     *\n     * Defaults to {@link DEFAULT_TTL_POLICY} (`max(2 × rtt, 600ms)`) when\n     * `opts.ttlMs` is omitted — suitable for most optimistic predictions.\n     *\n     * Use this when you can instantiate after the room is available. For\n     * module-load-time declarations (when the room doesn't exist yet),\n     * prefer `new PredictedEvents() + configure(...)` instead.\n     */\n    static get<K = string>(\n        room: { clock?: PredictedEventsClock | null },\n        opts: PredictedEventsGetOptions<K> = {},\n    ): PredictedEvents<K> {\n        const pe = new PredictedEvents<K>();\n        pe.configure(buildClockConfig(room.clock ?? null, opts.ttlMs ?? DEFAULT_TTL_POLICY));\n        if (opts.onReject) { pe.configure({ onReject: opts.onReject as (key: any) => void }); }\n        return pe;\n    }\n\n    private entries = new Map<K, number>();\n    /** Keys marked server-confirmed via a handle's `accept()` — exempt from TTL\n     *  eviction while the authoritative schema change is still in flight. */\n    private accepted = new Set<K>();\n    private cfg: PredictedEventsConfig = {};\n\n    /** Bind/override default providers for `predict()` and `prune()`. Call\n     *  once after the room (and clock) is available; subsequent calls merge.\n     *  Per-call args still take precedence over the configured providers. */\n    configure(cfg: PredictedEventsConfig): void {\n        this.cfg = { ...this.cfg, ...cfg };\n    }\n\n    /** Record an optimistic prediction. `at` defaults to the configured\n     *  `now()` provider, falling back to `performance.now()`. Returns a handle to\n     *  roll it back ({@link PredictedEventHandle.cancel}) or protect it\n     *  ({@link PredictedEventHandle.accept}). */\n    predict(key: K, at?: number): PredictedEventHandle<K> {\n        this.entries.set(key, at ?? this.cfg.now?.() ?? performance.now());\n        return {\n            key,\n            cancel: () => { this.entries.delete(key); this.accepted.delete(key); },\n            accept: () => { if (this.entries.has(key)) { this.accepted.add(key); } },\n        };\n    }\n\n    /** Is there an unconfirmed prediction for this key? */\n    has(key: K): boolean {\n        return this.entries.has(key);\n    }\n\n    /** The server confirmed the prediction (or the caller wants to drop it). */\n    confirm(key: K): void {\n        this.entries.delete(key);\n        this.accepted.delete(key);\n    }\n\n    /** The server overruled this prediction — drop it now and fire `onReject`\n     *  (immediate mispredict, vs `confirm`'s silent correct-prediction drop). */\n    reject(key: K): void {\n        if (this.entries.delete(key)) {\n            this.accepted.delete(key);\n            this.cfg.onReject?.(key);\n        }\n    }\n\n    /** Drop entries older than `ttlMs` — they're mispredictions the server\n     *  didn't agree with. `now` defaults to the configured `now()` provider;\n     *  `ttlMs` defaults to the configured policy (number or function). */\n    prune(now?: number, ttlMs?: number): void {\n        const t = now ?? this.cfg.now?.() ?? performance.now();\n        const policy = this.cfg.ttlMs;\n        const ttl = ttlMs ?? (typeof policy === \"function\" ? policy() : policy) ?? Infinity;\n        for (const [k, at] of this.entries) {\n            if (this.accepted.has(k)) { continue; } // server-confirmed — exempt from TTL\n            if (t - at > ttl) {\n                this.entries.delete(k);\n                this.cfg.onReject?.(k);\n            }\n        }\n    }\n\n    /** Drop everything. */\n    clear(): void {\n        this.entries.clear();\n        this.accepted.clear();\n    }\n\n    get size(): number {\n        return this.entries.size;\n    }\n\n    /** Set when {@link dispose} is called — a driver auto-pruning this store\n     *  (the channel/spawn surface wrapping it) drops a `dead` child on its\n     *  next tick. */\n    dead = false;\n\n    /** Stop being auto-pruned by the owning Predict and drop all entries. */\n    dispose(): void {\n        this.dead = true;\n        this.entries.clear();\n        this.accepted.clear();\n    }\n}\n\n/** Encode the \"RTT-aware TTL policy\" semantics for the room-aware factory. */\nfunction buildClockConfig(\n    clock: PredictedEventsClock | null,\n    ttlMs: PredictedEventsGetOptions[\"ttlMs\"],\n): PredictedEventsConfig {\n    return {\n        now: () => clock?.serverNow() ?? performance.now(),\n        ttlMs: typeof ttlMs === \"function\"\n            ? () => ttlMs(clock?.smoothedRtt() ?? 0)\n            : ttlMs,\n    };\n}\n"],"names":[],"mappings":";;;;;;;;AAsDA;;;;;;;;;;AAUG;AACI,MAAM,kBAAkB,GAAG,CAAC,GAAW,KAAa,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG;MA6BnE,eAAe,CAAA;AAA5B,IAAA,WAAA,GAAA;AAuBY,QAAA,IAAA,CAAA,OAAO,GAAG,IAAI,GAAG,EAAa;AACtC;AACyE;AACjE,QAAA,IAAA,CAAA,QAAQ,GAAG,IAAI,GAAG,EAAK;QACvB,IAAA,CAAA,GAAG,GAA0B,EAAE;AAoEvC;;AAEiB;QACjB,IAAA,CAAA,IAAI,GAAG,KAAK;IAQhB;AAzGI;;;;;;;;;;;AAWG;AACH,IAAA,OAAO,GAAG,CACN,IAA6C,EAC7C,OAAqC,EAAE,EAAA;;AAEvC,QAAA,MAAM,EAAE,GAAG,IAAI,eAAe,EAAK;QACnC,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA,EAAA,GAAA,IAAI,CAAC,KAAK,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI,EAAE,CAAA,EAAA,GAAA,IAAI,CAAC,KAAK,mCAAI,kBAAkB,CAAC,CAAC;AACpF,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE,EAAE,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAA8B,EAAE,CAAC;QAAE;AACtF,QAAA,OAAO,EAAE;IACb;AAQA;;AAEyE;AACzE,IAAA,SAAS,CAAC,GAA0B,EAAA;QAChC,IAAI,CAAC,GAAG,GAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EAAQ,IAAI,CAAC,GAAG,CAAA,EAAK,GAAG,CAAE;IACtC;AAEA;;;AAG6C;IAC7C,OAAO,CAAC,GAAM,EAAE,EAAW,EAAA;;AACvB,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAA,EAAA,GAAA,EAAE,KAAA,IAAA,IAAF,EAAE,KAAA,MAAA,GAAF,EAAE,GAAI,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAI,CAAC,GAAG,EAAC,GAAG,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,EAAA,CAAI,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,WAAW,CAAC,GAAG,EAAE,CAAC;QAClE,OAAO;YACH,GAAG;YACH,MAAM,EAAE,MAAK,EAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACtE,YAAA,MAAM,EAAE,MAAK,EAAG,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AAAE,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;AAAE,YAAA,CAAC,CAAC,CAAC;SAC3E;IACL;;AAGA,IAAA,GAAG,CAAC,GAAM,EAAA;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC;;AAGA,IAAA,OAAO,CAAC,GAAM,EAAA;AACV,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;AACxB,QAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;IAC7B;AAEA;AAC6E;AAC7E,IAAA,MAAM,CAAC,GAAM,EAAA;;QACT,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;AAC1B,YAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;YACzB,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAI,CAAC,GAAG,EAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,EAAA,EAAG,GAAG,CAAC;QAC5B;IACJ;AAEA;;AAEsE;IACtE,KAAK,CAAC,GAAY,EAAE,KAAc,EAAA;;QAC9B,MAAM,CAAC,GAAG,CAAA,EAAA,GAAA,GAAG,aAAH,GAAG,KAAA,MAAA,GAAH,GAAG,GAAI,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAI,CAAC,GAAG,EAAC,GAAG,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,EAAA,CAAI,mCAAI,WAAW,CAAC,GAAG,EAAE;AACtD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK;QAC7B,MAAM,GAAG,GAAG,CAAA,EAAA,GAAA,KAAK,KAAA,IAAA,IAAL,KAAK,KAAA,MAAA,GAAL,KAAK,IAAK,OAAO,MAAM,KAAK,UAAU,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,QAAQ;QACnF,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE;YAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;gBAAE;AAAU,YAAA,CAAC;AACvC,YAAA,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE;AACd,gBAAA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;gBACtB,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAI,CAAC,GAAG,EAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,EAAA,EAAG,CAAC,CAAC;YAC1B;QACJ;IACJ;;IAGA,KAAK,GAAA;AACD,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;IACzB;AAEA,IAAA,IAAI,IAAI,GAAA;AACJ,QAAA,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI;IAC5B;;IAQA,OAAO,GAAA;AACH,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AAChB,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;IACzB;AACH;AAED;AACA,SAAS,gBAAgB,CACrB,KAAkC,EAClC,KAAyC,EAAA;IAEzC,OAAO;QACH,GAAG,EAAE,MAAK,EAAA,IAAA,EAAA,CAAA,CAAC,OAAA,CAAA,EAAA,GAAA,KAAK,aAAL,KAAK,KAAA,MAAA,GAAA,MAAA,GAAL,KAAK,CAAE,SAAS,EAAE,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,WAAW,CAAC,GAAG,EAAE,CAAA,CAAA,CAAA;AAClD,QAAA,KAAK,EAAE,OAAO,KAAK,KAAK;cAClB,MAAK,EAAA,IAAA,EAAA,CAAA,CAAC,OAAA,KAAK,CAAC,MAAA,KAAK,KAAA,IAAA,IAAL,KAAK,KAAA,MAAA,GAAA,MAAA,GAAL,KAAK,CAAE,WAAW,EAAE,mCAAI,CAAC,CAAC,CAAA,CAAA;AACxC,cAAE,KAAK;KACd;AACL;;;;;"}