{"version":3,"file":"predictedEventChannel.mjs","sources":["../../src/predict/predictedEventChannel.ts"],"sourcesContent":["/**\n * Typed optimistic-event CHANNEL — the high-level face of {@link PredictedEvents}.\n *\n * A channel owns one logical event type end-to-end: **birth** in the predicted\n * simulation (`ctx.predict(channel, payload)` inside a rollback `step`, or\n * `channel.predict(payload)` outside the sim), **feedback** (`onPredict` fires\n * immediately — the optimistic celebration/sound/spawn), and **settlement**\n * against the server (below).\n *\n * Identity is the channel OBJECT — there is no registry key. Where the\n * low-level store keys entries by strings the app must keep in sync, a channel\n * is declared once, holds its typed callbacks, and every call site references\n * the binding. The optional {@link PredictedEventChannelOptions.label} is\n * debug metadata only; nothing resolves by it.\n *\n *     const goals = predict.defineEvent<Team>({\n *         onPredict: (team) => { celebrate(team); hidePuck(); },\n *         onReject:  ()     => showPuck(),\n *     });\n *\n *     // inside the reconciler step — live-only by construction, replay-safe:\n *     if (crossedGoalLine) ctx.predict(goals, scoredBy);\n *\n *     // settlement — the authoritative broadcast:\n *     room.onMessage(\"score\", () => goals.confirm());\n *\n * SETTLEMENT — how a pending prediction resolves:\n *\n *   - **`confirm()`** — the app saw the authoritative signal (a broadcast, a\n *     state change): the prediction was right.\n *   - **Auto-reject (sim-born)** — the server's ack watermark passed the\n *     entry's birth seq by {@link PredictedEventChannelOptions.graceTicks}\n *     without a confirm. The server processed the very timeline that\n *     predicted the event and stayed silent — the event didn't happen. The\n *     confirm signal travels the same ordered socket as the acks, so a real\n *     event always confirms before this fires; no wall clock, no RTT\n *     estimate, no state polling. (Both obvious alternatives false-reject\n *     under latency: the predicted world flickers on reconciles, and\n *     authoritative state shows PRE-input history for ~RTT after predicting.)\n *   - **TTL (UI-born)** — entries with no sim timeline fall back to\n *     wall-clock eviction (`ttlMs`).\n *   - **`reject()` / `clear()`** — explicit app verdicts, any time.\n *\n * Settlement/timing semantics (TTL policy, serverNow clock, accepted-entry\n * exemption) are delegated to an internal {@link PredictedEvents} store — one\n * source of truth for both layers.\n */\n\nimport {\n    PredictedEvents,\n    DEFAULT_TTL_POLICY,\n    type PredictedEventsClock,\n} from \"./predictedEvents.ts\";\nimport { isReplaying, type PredictSink } from \"./rollback.ts\";\n\n/** Entry key for non-primitive payloads when no\n *  {@link PredictedEventChannelOptions.uniqueBy} is given — the channel holds\n *  at most ONE pending prediction. */\nconst SINGLETON_KEY = 0;\n\n/** Default {@link PredictedEventChannelOptions.graceTicks}: server progress\n *  past a prediction before an unconfirmed sim-born entry rejects (~333ms of\n *  simulation at 30Hz) — generous against contested-event divergence, and\n *  anchored to the ack stream so latency can't induce a false reject. */\nexport const DEFAULT_GRACE_TICKS = 10;\n\nexport interface PredictedEventChannelOptions<T> {\n    /**\n     * The optimistic feedback — fires the moment the event is predicted (from\n     * the live sim step or `predict()`), ~RTT before the server could confirm.\n     * Runs SYNCHRONOUSLY at the predicting call site; keep it light (play the\n     * sound, set the flags, start the tween). A callback that needs a render\n     * pose should take it from the payload rather than reading a game object\n     * mid-step.\n     *\n     * OPTIONAL: flag-shaped consumers skip callbacks entirely and DERIVE from\n     * the pending set instead — poll {@link PredictedEventChannel.has} each\n     * frame, OR'd with authoritative state (`!enemy.alive || kills.has(id)`).\n     * Mispredicts then recover implicitly: the entry settles, `has()` flips,\n     * the derived view snaps back.\n     */\n    onPredict?: (payload: T) => void;\n    /**\n     * The prediction was wrong — undo the optimistic feedback (show the entity\n     * again, refund the item). Fired by TTL expiry, explicit {@link reject},\n     * or the ack-anchored auto-reject (see\n     * {@link PredictedEventChannelOptions.graceTicks}). NOT fired by\n     * {@link confirm} or {@link clear}.\n     */\n    onReject?: (payload: T) => void;\n    /** The server agreed (fired by {@link confirm}, once per settled entry).\n     *  Usually empty — the optimistic feedback already played. */\n    onConfirm?: (payload: T) => void;\n    /**\n     * A {@link confirm} settled NOTHING — the authoritative signal arrived for\n     * an event this client never predicted (a remote actor's doing, or a\n     * prediction that already timed out). The push form of `confirm()`'s\n     * `returns 0` contract: play the feedback `onPredict` skipped (the FX for\n     * a remote player's pickup, the un-muted goal horn). Receives the key\n     * `confirm()` was called with (`undefined` for a keyless settle-all).\n     */\n    onUnpredicted?: (key: string | number | undefined) => void;\n    /**\n     * SIM-BORN settlement deadline, in input ticks: an entry auto-rejects\n     * once the server has processed this many ticks PAST its birth seq\n     * without a {@link PredictedEventChannel.confirm} — \"the server ran the\n     * predicted timeline and stayed silent\". The slack absorbs client/server\n     * divergence in WHEN the event lands (a contested crossing can occur a\n     * few ticks later server-side than predicted); the confirm signal shares\n     * the ordered socket with the acks, so a real event's confirm always\n     * arrives well before this fires. Default {@link DEFAULT_GRACE_TICKS}.\n     */\n    graceTicks?: number;\n    /**\n     * Entry IDENTITY: two payloads mapping to the same value are \"the same\n     * event\" — they dedupe while pending, and {@link confirm}/{@link reject}/\n     * {@link has} take the value to address one entry among several.\n     *\n     * DEFAULT — pick by payload type:\n     *   - a `string`/`number` payload IS its own identity (`(by) => by`):\n     *     one pending entry per value — an enemy id, a hit category.\n     *   - any other payload shares ONE anonymous pending slot (a goal, a\n     *     death): while an entry is pending, further predictions are no-ops.\n     *     (Keying objects by value isn't derivable — declare it.)\n     *\n     * Declare `uniqueBy` when an object payload carries presentation data\n     * alongside its identity:\n     *\n     *     predict.defineEvent<{ projectileId: string; x: number; y: number }>({\n     *         uniqueBy: (h) => h.projectileId,   // payload carries FX data; identity is the projectile\n     *         onPredict: (h) => spawnImpactFx(h.x, h.y),\n     *     });\n     *\n     * To force a single pending slot for a primitive payload: `uniqueBy: () => 0`.\n     */\n    uniqueBy?: (payload: T) => string | number;\n    /**\n     * Eviction window for unconfirmed UI-BORN predictions (`predict()` with\n     * no sim timeline — nothing to anchor a deadline to but the wall clock).\n     * Number for a static TTL; function of the current smoothed RTT for a\n     * dynamic policy. Defaults to {@link DEFAULT_TTL_POLICY}\n     * (`max(2 × rtt, 600ms)`). Sim-born entries are EXEMPT — they settle by\n     * server progress (see {@link graceTicks}).\n     */\n    ttlMs?: number | ((rtt: number) => number);\n    /**\n     * Minimum gap (ms) between `onPredict` fires, channel-wide — predictions\n     * arriving inside the window are dropped entirely (no entry, no feedback).\n     * The optimistic mirror of a server-side broadcast cooldown: give it the\n     * same value and predicted + confirmed feedback pair one-to-one.\n     */\n    cooldownMs?: number;\n    /** Debug metadata (panel/telemetry display). Nothing resolves by it. */\n    label?: string;\n}\n\nexport class PredictedEventChannel<T> implements PredictSink<T> {\n    private readonly opts: PredictedEventChannelOptions<T>;\n    /** Settlement engine: TTL clock + accepted-exemption live here. */\n    private readonly store: PredictedEvents<string | number>;\n    /** Pending payloads by entry key (payload echoed to the settle callbacks).\n     *  `acked` is the emitting controller's watermark (sim-born entries only). */\n    private readonly entries = new Map<string | number, { payload: T; seq: number; acked?: () => number }>();\n    private readonly now: () => number;\n    private cooldownUntil = -Infinity;\n    private warnedReplayPredict = false;\n\n    constructor(opts: PredictedEventChannelOptions<T>, clock: PredictedEventsClock | null) {\n        this.opts = opts;\n        this.now = () => clock?.serverNow() ?? performance.now();\n        this.store = PredictedEvents.get<string | number>({ clock }, {\n            ttlMs: opts.ttlMs ?? DEFAULT_TTL_POLICY,\n            // one internal rejection path — TTL prune and explicit reject() both land here\n            onReject: (key) => {\n                const e = this.entries.get(key);\n                this.entries.delete(key);\n                if (e) this.opts.onReject?.(e.payload);\n            },\n        });\n    }\n\n    /** Sim-born prediction — reached via `ctx.predict(channel, payload)`, which\n     *  only forwards LIVE steps (replays are filtered before this is called). */\n    _predictFromSim(seq: number, payload: T, acked?: () => number): void {\n        this.add(seq, payload, acked);\n    }\n\n    /**\n     * Predict from OUTSIDE the sim (a UI-optimistic action with no rollback\n     * timeline). Inside a reconciler `step`, use `ctx.predict(channel, payload)`\n     * instead — this form is backstopped (a call that lands inside a rollback\n     * replay no-ops and warns once) but the ctx form is live-only by\n     * construction.\n     */\n    predict(payload: T): void {\n        if (isReplaying()) {\n            if (!this.warnedReplayPredict) {\n                this.warnedReplayPredict = true;\n                console.warn(\n                    `@colyseus/sdk: PredictedEventChannel${this.opts.label ? ` \"${this.opts.label}\"` : \"\"}` +\n                    `.predict() was called during a rollback replay and ignored. ` +\n                    `Inside a reconciler step, use ctx.predict(channel, payload).`,\n                );\n            }\n            return;\n        }\n        this.add(-1, payload);\n    }\n\n    private add(seq: number, payload: T, acked?: () => number): void {\n        // identity: uniqueBy > primitive payload keys itself > anonymous slot\n        const key = this.opts.uniqueBy !== undefined\n            ? this.opts.uniqueBy(payload)\n            : (typeof payload === \"string\" || typeof payload === \"number\" ? payload : SINGLETON_KEY);\n        if (this.entries.has(key)) return;   // already pending — re-derivations don't re-fire\n        const t = this.now();\n        if (this.opts.cooldownMs !== undefined) {\n            if (t < this.cooldownUntil) return;\n            this.cooldownUntil = t + this.opts.cooldownMs;\n        }\n        const handle = this.store.predict(key, t);\n        // sim-born entries settle by server progress (see prune), not wall clock\n        if (acked !== undefined) handle.accept();\n        this.entries.set(key, { payload, seq, acked });\n        this.opts.onPredict?.(payload);\n    }\n\n    /** Is a prediction pending? With `key`, that entry; without, any. */\n    has(key?: string | number): boolean {\n        return key === undefined ? this.entries.size > 0 : this.entries.has(key);\n    }\n\n    /** Number of pending (unsettled) predictions. */\n    get pendingCount(): number { return this.entries.size; }\n\n    /**\n     * The server agreed: settle the entry for `key` — or EVERY pending entry\n     * when omitted (the single-pending shape). Fires `onConfirm` per settled\n     * entry; never `onReject`. Returns how many entries were settled — `0`\n     * means the event arrived unpredicted (the caller usually plays the\n     * feedback it skipped optimistically); {@link\n     * PredictedEventChannelOptions.onUnpredicted} is the push form of that.\n     *\n     * ORDERING GUARANTEES:\n     *   - the entry is removed BEFORE its `onConfirm` fires — `has(key)`\n     *     inside `onConfirm` is already `false`. Branch on the callbacks\n     *     (`onConfirm` = we predicted it, `onUnpredicted` = we didn't), never\n     *     on a `has()` pre-read racing the confirm.\n     *   - when nothing settles, `onUnpredicted` fires with the key.\n     */\n    confirm(key?: string | number): number {\n        let n = 0;\n        for (const k of this.settleKeys(key)) {\n            const e = this.entries.get(k);\n            if (e === undefined) continue;\n            this.entries.delete(k);\n            this.store.confirm(k);    // silent drop — confirm never fires onReject\n            this.opts.onConfirm?.(e.payload);\n            n++;\n        }\n        if (n === 0) this.opts.onUnpredicted?.(key);\n        return n;\n    }\n\n    /** The server overruled: reject the entry for `key` — or every pending\n     *  entry when omitted. Fires `onReject` per entry. Returns the count. */\n    reject(key?: string | number): number {\n        let n = 0;\n        for (const k of this.settleKeys(key)) {\n            if (!this.entries.has(k)) continue;\n            this.store.reject(k);     // internal wiring deletes the entry + fires onReject(payload)\n            n++;\n        }\n        return n;\n    }\n\n    private settleKeys(key: string | number | undefined): (string | number)[] {\n        return key !== undefined ? [key] : [...this.entries.keys()];\n    }\n\n    /** Drop every pending entry SILENTLY (no callbacks) — respawns, scene\n     *  teardown, \"this life's predictions no longer apply\". */\n    clear(): void {\n        this.entries.clear();\n        this.store.clear();\n    }\n\n    /** Per-frame drive (the owning Predict calls this from `tick()`):\n     *  sim-born grace-tick auto-rejects first, then wall-clock TTL eviction. */\n    prune(): void {\n        if (this.entries.size > 0) {\n            const grace = this.opts.graceTicks ?? DEFAULT_GRACE_TICKS;\n            for (const [key, e] of [...this.entries]) {\n                // sim-born auto-settle: the server processed `grace` ticks past\n                // the prediction without confirming — the event didn't happen\n                if (e.acked !== undefined && e.acked() >= e.seq + grace) this.store.reject(key);\n            }\n        }\n        this.store.prune();   // wall-clock TTL — UI-born entries (sim-born are accept()ed)\n    }\n\n    /** Set by {@link dispose} — the owning Predict drops a dead channel from\n     *  its drive list on the next tick. */\n    dead = false;\n\n    /** Teardowns drained by {@link dispose} — populated by the owning\n     *  Predict's `defineEvent` (`confirmOn` bindings). @internal */\n    private teardowns: Array<() => void> = [];\n\n    /** @internal */\n    _addTeardown(fn: () => void): void {\n        this.teardowns.push(fn);\n    }\n\n    /** Stop being driven and drop all entries (silently). */\n    dispose(): void {\n        for (const fn of this.teardowns.splice(0)) fn();\n        this.dead = true;\n        this.clear();\n        this.store.dispose();\n    }\n}\n"],"names":[],"mappings":"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"}