# StateSync _(no submodules)_ ## Purpose `StateSync` is the orchestration layer that turns `StateManager` mutations into wire packets and applies received packets back into the manager. It brokers between a single `StateManager` and a pluggable `StateSyncTransport` — WebSocket, WebRTC data channel, in-process pipe — deciding per call whether to send a delta, a full snapshot, or honour an inbound request. The state-sync algorithm is otherwise transport-agnostic. The Wave B-3 port replaces the upstream's hard dependency on `NetworkAdapter`, `GameEventEmitter`, and `Logger` with a single `StateSyncTransport` interface and OTel-native instrumentation. Push cadence is left to callers — there is no internal `setInterval` — because game loops, dirty-flag policies, and bandwidth limiters belong to the application layer, not the sync engine. ## Features - Three-message protocol (`delta`, `full`, `request`) over an abstract transport. - Automatic fallback to full snapshot when the delta exceeds `maxDeltaSize`. - Initial-sync handshake: when no previous state has been synced, sends a full snapshot. - Inbound `request` triggers an outbound full snapshot. - Per-kind OTel counters + bytes histogram. - Typed errors: transport faults surface as `STATE_SYNC_TRANSPORT_FAILED`. - Statistics snapshot and reset. - Disposal removes the transport listener. ## Usage: Basic ```typescript import { StateManager } from "@suaveplan/game-server/state/state-manager"; import { StateSync } from "@suaveplan/game-server/state/state-sync"; interface World { tick: number } const manager = new StateManager({ tick: 0 }); const sync = new StateSync(manager, transport); // On each game tick: manager.update("tick", manager.get("tick") + 1); await sync.sync(); ``` ## Usage: Advanced ```typescript import type { StateSyncTransport, StateSyncMessage } from "@suaveplan/game-server/state/state-sync"; // In-process pipe for tests / single-process play class InProcessTransport implements StateSyncTransport { private readonly handlers = new Set<(m: StateSyncMessage) => void>(); public peer?: InProcessTransport; isConnected() { return this.peer !== undefined; } onMessage(h: (m: StateSyncMessage) => void) { this.handlers.add(h); return () => { this.handlers.delete(h); }; } async send(m: StateSyncMessage) { for (const h of this.peer?.handlers ?? []) h(m); } } const t1 = new InProcessTransport(); const t2 = new InProcessTransport(); t1.peer = t2; t2.peer = t1; const sync = new StateSync(manager, t1, { config: { maxDeltaSize: 4096 }, }); await sync.requestFullSync(); // ask peer for full state ``` ## API Reference ### `class StateSync` #### `constructor(manager, transport, options?)` `options` — `{ compressor?, clock?, meter?, config?: { maxDeltaSize? } }`. #### `sync(): Promise` Sends a delta or, on first call or when the delta exceeds `maxDeltaSize`, a full snapshot. #### `syncFull(): Promise` / `requestFullSync(): Promise` #### `getStats(): SyncStats` / `resetStats(): void` #### `dispose(): void` ### Types - `StateSyncTransport` — `{ isConnected, send, onMessage }`. - `StateSyncMessage` — `delta | full | request` discriminated union. - `SyncStats` — counters for deltas/fulls/requests sent + received, bytes, ratio, last timestamp. - `StateSyncConfig` — `{ maxDeltaSize? }`. ## Implementation Notes The `snapshotCurrent` step explicitly deep-clones via `JSON.parse(JSON.stringify(...))` so the `lastSyncedState` does not retain references to the manager's deep-frozen snapshots — any subsequent attempt to compute a delta would otherwise compare two snapshots whose primitive leaves are identical but whose container references differ, producing spurious replace ops. JSON-clone is acceptable because the sync protocol already constrains the state to JSON-serialisable shapes. Transport errors are wrapped in `GameServerError(STATE_SYNC_TRANSPORT_FAILED)` with the failing message kind on `context` so structured logs and observability dashboards can pinpoint the misbehaving leg of the connection. Inbound message handling does not catch transport-side errors — those are the transport's problem; the `StateSync` contract is that `onMessage` only delivers well-formed `StateSyncMessage` values.