Multiplayer
Lockstep from a reducer.
Use this for turn-based and event-driven games. Loki sequences actions, commits only from the current host, and tracks stateVersion.
Contract
- JSON-compatible state and actions. Numbers must be finite safe integers.
reduce(state, action, context)must be synchronous, deterministic, and fast (budget 50 ms). No I/O or rendering insidereduce.- Only the current host runs
reduce. - Subscribe to snapshots for
state,members, host, andconnection. - Call
dispatch(action)only whileconnection === "connected".
const room = client.createSynchronizedRoom<State, Action>({
initialState: { board: emptyBoard, turn: "white" },
reduce(state, action, context) {
if (action.type === "move") return applyMove(state, action, context.senderId);
return state;
},
});
const created = await room.create();
room.subscribe((snapshot) => {
render(snapshot.state, snapshot.members, snapshot.connection);
});
await room.dispatch({ type: "move", from: "e2", to: "e4" });
Shared methods: create(), join({ inviteCode }), dispatch(action), leave(), reconnect().
Connection
| connection | What to do |
|---|---|
connected | Dispatch is allowed |
suspended | Page hidden or offline. Lock input. Do not leave. |
reconnecting / resynchronizing | Wait for a snapshot. Host may have changed. |
leave_failed | Retry leave() or close() |
room_closed | Terminal. Return to lobby. |
Coming back replaces the socket and replays unresolved actions with the same (senderId, actionId). Do not resend under a new ID. indeterminate is unknown, not proof of failure.
Prefer createSynchronizedRoom() over low-level sendAction() / sendHostState().