Unified State Contract
The Unified State architecture enforces a single source of truth for the UI, regardless of whether the game is running offline or connected to Nakama. This ensures that visual logic and HUD bindings only depend on one standardized object, and do not fork logic based on multiplayer state.
1. Core Principles
- Single Truth: The UI layer binds ONLY to the
unifiedGameState. No component should read directly fromlocalStoreormultiplayerStore. - Modes: The game supports an explicit
gameModestate Enum (localormultiplayer). Future or optional modes includespectateandgenie-gm. - Action Dispatch: Player input is routed to either local simulator functions or Nakama network calls depending on the active
gameMode.
2. Svelte 5 Unified State Pattern
The store uses Svelte 5 runes ($state and $derived) to multiplex the underlying data sources.
// game-mode.svelte.ts
export type GameMode = 'local' | 'multiplayer' | 'spectate';
export const gameState = new class {
mode = $state<GameMode>('local');
// HUD binding object
unifiedGameState = $derived.by(() => {
if (this.mode === 'multiplayer') {
return {
// Read from multiplayer / Nakama state
entities: multiplayerStore.entities,
players: multiplayerStore.players,
isMyTurn: multiplayerStore.activePlayerId === platformSession.userId,
scores: multiplayerStore.scores,
phase: multiplayerStore.phase
};
}
// Read from local bot/practice simulator
return {
entities: localStore.entities,
players: localStore.players,
isMyTurn: true,
scores: localStore.scores,
phase: localStore.phase
};
});
};3. Dispatch Routing Table
Input from the user interface MUST NOT contain conditionals. Instead, actions call a unified dispatcher:
export async function dispatchAction(action: GameAction) {
if (gameState.mode === 'multiplayer') {
// Encode and send via Nakama RPC or Match Data
await nakamaClient.sendMatchState(action.opCode, JSON.stringify(action.payload));
} else {
// Process immediately in the local engine
localEngine.processAction(action);
}
}4. Acceptance Criteria & Verification
- Criteria 1 (HUD Independence): UI components must not import network logic or local fallback logic.
- Verify: Search the
src/componentsfolder for any references tonakamaorlocalStore. None should exist.
- Verify: Search the
- Criteria 2 (Mode Enum): The game tracks modes deterministically.
- Verify: Inspect
game-mode.svelte.tsto ensureGameModeexists and defaults tolocal.
- Verify: Inspect
- Criteria 3 (Action Routing): Dispatch routing perfectly separates concerns.
- Verify: Play the game locally vs. a bot. Disconnect the internet, verify that interactions continue seamlessly through the local dispatcher without throwing network exceptions.