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 from localStore or multiplayerStore.
  • Modes: The game supports an explicit gameMode state Enum (local or multiplayer). Future or optional modes include spectate and genie-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/components folder for any references to nakama or localStore. None should exist.
  • Criteria 2 (Mode Enum): The game tracks modes deterministically.
    • Verify: Inspect game-mode.svelte.ts to ensure GameMode exists and defaults to local.
  • 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.

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send