/** * Generic optimistic-prediction store with TTL-based mispredict cleanup. * * INTERNAL machinery — not exported from the public barrels. This store is * the settlement engine behind {@link PredictedEventChannel} (`defineEvent`) * and `PredictedSpawns`; consumers reach it through those surfaces. * * Pattern: the client predicts a discrete event happened (death, pickup, * door-open, projectile-spawn, …), marks the corresponding key. The render * layer reads the predicted state immediately rather than waiting ~RTT for * the server's authoritative confirmation. Two cleanup paths: * * 1. **Confirm** — caller observes the server's authoritative state change * (e.g. via `Callbacks.listen(instance, "alive", …)`) and drops the * entry: prediction was correct, schema is now the truth. * 2. **Prune** — each frame, entries older than `ttlMs` are dropped: the * server didn't confirm within the window, so the prediction was wrong; * the render reverts when the entry vanishes. * * Generic over the key type so callers can use string ids, schema instances * (via Map/WeakMap-compatible references), or composite tuples. * * Timing/TTL is injected via {@link configure}. Callers wire `now` (typically * `room.clock.serverNow`) and a `ttlMs` policy once at startup; subsequent * `predict()` / `prune()` calls can omit those args. Per-call args still * override the configured providers when you need explicit control (tests, * mixed clocks, etc.). */ export interface PredictedEventsConfig { /** Source of "now" timestamps for the `at` param of `predict()` and the * comparison clock in `prune()`. Typically `() => room.clock.serverNow()`. * Defaults to `performance.now()` when unset. */ now?: () => number; /** Eviction window for `prune()`. Number for a static TTL; function for * a dynamic policy (e.g. `() => Math.max(rtt * 2, 600)`). Re-evaluated * each `prune()` call so RTT-derived policies stay current. */ ttlMs?: number | (() => number); /** Invoked when a prediction is dropped as a mispredict — by {@link * PredictedEvents.reject} (explicit) or by {@link PredictedEvents.prune} * (TTL expiry). NOT fired by `confirm` (correct) or `cancel` (deliberate * local undo). The render layer reads this to learn an event was undone. */ onReject?: (key: any) => void; } /** * Minimal clock shape consumed by {@link PredictedEvents.get}. A strict * subset of `RoomClockLike` — declared locally so this module stays * portable without an intra-package dependency on `../RoomClock.ts`. */ export interface PredictedEventsClock { serverNow(): number; smoothedRtt(): number; } /** * Default TTL policy for {@link PredictedEvents.get}: `max(2 × smoothedRtt, 600ms)`. * * - **2× RTT** absorbs round-trip + ordinary jitter — predictions that * don't confirm within this window were almost certainly mispredicted. * - **600 ms floor** guards against RTT being 0 (clock not bootstrapped) * or pathologically small (loopback testing). * * Exported so callers can compose against it, e.g. * `ttlMs: rtt => Math.max(rtt * 1.5, DEFAULT_TTL_POLICY(rtt))`. */ export declare const DEFAULT_TTL_POLICY: (rtt: number) => number; /** Options for the room-aware factory {@link PredictedEvents.get}. */ export interface PredictedEventsGetOptions { /** Eviction window. Number for a static TTL; function receives the current * RTT (from `room.clock.smoothedRtt()`) and returns the TTL in ms. * Defaults to {@link DEFAULT_TTL_POLICY}. */ ttlMs?: number | ((rtt: number) => number); /** See {@link PredictedEventsConfig.onReject}. */ onReject?: (key: K) => void; } /** * Handle returned by {@link PredictedEvents.predict} — the discrete-event * counterpart to {@link import('./predictedSpawns.ts').SpawnHandle}. Lets a * caller roll back ({@link cancel}) or protect ({@link accept}) an optimistic * event, without tracking the key. */ export interface PredictedEventHandle { /** The predicted key. */ readonly key: K; /** Drop the prediction now (rollback). No `onReject` — a deliberate undo. */ cancel(): void; /** Server-confirmed: keep the predicted effect but exempt it from TTL * eviction. Await the authoritative schema change, then call {@link * PredictedEvents.confirm}. */ accept(): void; } export declare class PredictedEvents { /** * Room-aware factory. Auto-binds `now()` to `room.clock.serverNow()` and * wires the optional dynamic-TTL policy with the room's `smoothedRtt()`. * Equivalent to `new PredictedEvents() + configure({...})` but one line. * * Defaults to {@link DEFAULT_TTL_POLICY} (`max(2 × rtt, 600ms)`) when * `opts.ttlMs` is omitted — suitable for most optimistic predictions. * * Use this when you can instantiate after the room is available. For * module-load-time declarations (when the room doesn't exist yet), * prefer `new PredictedEvents() + configure(...)` instead. */ static get(room: { clock?: PredictedEventsClock | null; }, opts?: PredictedEventsGetOptions): PredictedEvents; private entries; /** Keys marked server-confirmed via a handle's `accept()` — exempt from TTL * eviction while the authoritative schema change is still in flight. */ private accepted; private cfg; /** Bind/override default providers for `predict()` and `prune()`. Call * once after the room (and clock) is available; subsequent calls merge. * Per-call args still take precedence over the configured providers. */ configure(cfg: PredictedEventsConfig): void; /** Record an optimistic prediction. `at` defaults to the configured * `now()` provider, falling back to `performance.now()`. Returns a handle to * roll it back ({@link PredictedEventHandle.cancel}) or protect it * ({@link PredictedEventHandle.accept}). */ predict(key: K, at?: number): PredictedEventHandle; /** Is there an unconfirmed prediction for this key? */ has(key: K): boolean; /** The server confirmed the prediction (or the caller wants to drop it). */ confirm(key: K): void; /** The server overruled this prediction — drop it now and fire `onReject` * (immediate mispredict, vs `confirm`'s silent correct-prediction drop). */ reject(key: K): void; /** Drop entries older than `ttlMs` — they're mispredictions the server * didn't agree with. `now` defaults to the configured `now()` provider; * `ttlMs` defaults to the configured policy (number or function). */ prune(now?: number, ttlMs?: number): void; /** Drop everything. */ clear(): void; get size(): number; /** Set when {@link dispose} is called — a driver auto-pruning this store * (the channel/spawn surface wrapping it) drops a `dead` child on its * next tick. */ dead: boolean; /** Stop being auto-pruned by the owning Predict and drop all entries. */ dispose(): void; }