import { type SaveBackend, type SaveStatus, type SaveTimers } from "../game/saveStore.js"; import type { WorldSnapshot } from "./worldSnapshot.js"; /** * The narrow slice of a live `GameContext` a runtime save reads and writes — a * `GameContext` satisfies it directly (`ctx.state`/`ctx.restore`/`ctx.subscribe`). * Depending on this instead of the full context keeps the save controller a deep, * decoupled module: it captures and restores the *whole* opted-in world without * knowing any subsystem. */ export interface RuntimeSaveTarget { snapshot(): WorldSnapshot; hydrate(snapshot: WorldSnapshot): void; /** Complete authoritative capture when the target distinguishes persistence from replication. */ state?(): WorldSnapshot; /** Authoritative inverse of `state`; used together with it instead of replication hydration. */ restore?(state: WorldSnapshot): void; subscribe(listener: () => void): () => void; } /** `"autosave"` writes a fresh capture on a trailing timer while the world changes (at most once per `autosaveMs`, so a never-idle world still persists); `"manual"` writes only on an explicit `save()`/`checkpoint()` — the save-point / quest-trigger model. */ export type RuntimeSaveMode = "autosave" | "manual"; /** How {@link createRuntimeSave} is wired — the live world `target`, the `backend` it persists through, and optional mode/slot/versioning/cadence knobs. */ export interface RuntimeSaveConfig { /** The live world to capture from and restore into — pass the `GameContext`. */ target: RuntimeSaveTarget; /** Where the save lives — `localSaveBackend()` (offline) or a cloud/Convex backend; the only thing that changes between offline and cloud. */ backend: SaveBackend; /** `"autosave"` (default) persists on world change; `"manual"` only when the game calls `save()`/`checkpoint()`. */ mode?: RuntimeSaveMode; /** Storage namespace. Default `"runtime"`. */ key?: string; /** Active save slot. Default `"default"`. */ slot?: string; /** Save-format version; bump + pass {@link migrate} on a breaking world-shape change. */ version?: number; migrate?: (data: unknown, fromVersion: number) => WorldSnapshot; /** Trailing autosave interval (ms) for `autosave` mode — the world persists at most once per interval and always within one interval of its first unsaved change. Default `3000`. */ autosaveMs?: number; now?: () => number; timers?: SaveTimers; onError?: (error: unknown) => void; } /** {@link RuntimeSaveConfig} without `target` — what a host (`createGameContext`) accepts to build `ctx.game.save` and bind it to the context itself. */ export type RuntimeSaveOptions = Omit; /** * Whole-world save/load bound to a live world and a pluggable backend. `save()` * captures `target.state()` and writes it; `load()` reads it back and * calls `target.restore()`. Older targets use `snapshot()`/`hydrate()`. In `autosave` mode it also writes on a * trailing timer while the world keeps changing. Named slots, versioned migration, and offline↔cloud * (backend swap) all come for free from the underlying save store. */ export interface RuntimeSave { status(): SaveStatus; slot(): string; /** Capture the live world and persist it now (serialized against any in-flight write). */ save(): Promise; /** Semantic alias for `save()` — call it from a save point, a quest-complete or area-enter handler. */ checkpoint(): Promise; /** Load the active slot and hydrate the whole world; resolves `true` when a real save was applied, `false` for an empty slot or failed payload read. Failed reads never restore cached state. */ load(): Promise; /** Whether the active slot holds a saved world — gate a title-screen "Continue" on it. Failed payload reads resolve false and report error status. */ hasSave(): Promise; /** Delete the active slot's save. */ clear(): Promise; /** Switch slot and load it, hydrating the world when that slot has a save. Failed payload reads resolve false without restoring cached state. */ switchSlot(slot: string): Promise; /** The slots this game has written — back a load/save menu. */ slots(): Promise; subscribe(listener: () => void): () => void; /** Stop autosave and release the world subscription. */ dispose(): void; } /** * Bridge a live world to a pluggable save backend: whole-state capture/restore * with autosave, save points, named slots, and versioned migration — the same * call for offline (localStorage) and cloud (Convex), only the `backend` * differs. The game keeps control of *when* it saves (the `mode` plus explicit * `checkpoint()` calls from quests/areas) and *when* it restores (`load()` on * boot), so any save mechanic — continuous autosave, manual save points, * checkpoint triggers — is expressible without new engine code. * * @capability runtime-save save/load the whole game world through a pluggable backend, autosave or save points */ export declare function createRuntimeSave(config: RuntimeSaveConfig): RuntimeSave;