Catan

Catan is a classic game of trade and strategy. It utilizes the svelte-component integration type and renders fully within the host platform via src/CatanGame.svelte.

Tutorials

Bootstrapping your first Catan node

To run Catan locally and see the game board render:

  1. Ensure your local Nakama container is running (the platform utilizes the settlers_match proxy).
  2. Start the frontend host via vite dev.
  3. Navigate to /play/catan in your browser. The platform will mount CatanGame.svelte and inject the required session variables automatically.

How-To Guides

How to configure scenarios and extensions

Extensions are loaded based on the initialConfig.scenario passed by the lobby. If you want to force test an extension locally without a lobby:

  1. In CatanGame.svelte, override the initialConfig prop mock.
  2. Set initialConfig.scenario = "seafarers".
  3. The component’s onMount block will synchronize this with the GameState and re-invoke gameController.initExtensions() to load the correct ruleset.

Reference

Component Injection Contract

The CatanGame.svelte component accepts standard platform properties:

  • hostUpdate: Callback for sending match lifecycle events (status: 'lobby' | 'playing' | 'finished').
  • initialConfig: Prop containing game settings like victoryPoints and scenario.
  • platformSession / platformSocket: Nakama networking instances.

Network Opcodes

All network events transmit through nakamaManager.sendMatchData using the OpCode enum. Opcodes include BUILD, ROLL, TRADE_OFFER, TRADE_CANCEL, BANK_TRADE, BUY_DEV_CARD, and END_TURN. Ensure you do not collide with reserved platform bus opcodes (e.g. CONFIG_UPDATE = 11, START_GAME = 12).

Explanation

The Kernel + Extension Architecture

Catan uses a data-driven Kernel + Extension pattern. The base rules run automatically, but additional scenarios (like Seafarers or Cities & Knights) are registered into the ExtensionRegistry. Hooks such as onSevenRoll or canBuild allow extensions to override or augment the base game logic seamlessly.

State Management

State is tracked through Svelte 5 Runes in GameState.svelte.ts. UI components derive values reactively from this single source of truth and only issue action requests through the GameController or RobberController.


Security & Gotchas ⚠️

During the integration and platform alignment, several critical security and reactivity hazards were addressed. Developers modifying Catan MUST ensure they do not reintroduce these vulnerabilities:

1. Network Build Validation Bypass

  • The Hazard: When receiving an OpCode.BUILD from the network, previously the applyBuildFromNetwork method skipped positional and resource validation (validate: false). A malicious client could send spoofed packets to build roads/cities for free anywhere on the grid.
  • The Fix: applyBuildFromNetwork now mandates the validate: true flag. The controller strictly verifies resource deductions and adjacency constraints for all incoming network builds.

2. Turn Order Hijacking

  • The Hazard: The endTurn(playerId) function previously executed without verifying that the requesting playerId was actually the currently active player. This allowed malicious users to emit OpCode.END_TURN out of turn, skipping opponents’ turns instantly.
  • The Fix: endTurn now asserts if (endingPlayerId !== game.state.turn.playerId) return;. Additionally, turn skipping is heavily guarded during the 5-6 player Special Building Phase.

3. Bleeding Globals across Matches

  • The Hazard: Because catan mounts directly into the DOM (no iframe sandbox), leaving a match and joining a new one reused the exported game singleton from GameState.svelte.ts. This caused stale properties (like winner or started flags) to bleed into fresh sessions, causing instant lobby drops.
  • The Fix: The CatanGame.svelte entry point implements an onDestroy hook that manually wipes the global game.state (resetting arrays, winners, and flags) when the component unmounts.

4. Client-Authoritative Scoring

  • The Hazard: The client used to calculate its own victory state and directly emit { status: 'finished', winner: 'player_id' } to the host via hostUpdate.
  • The Fix: The client is restricted to sending pure status events. The authoritative server evaluates actual match completion and leaderboard logic.

5. UI State Mutation Leakage

  • The Hazard: UI components (e.g., Lobby.svelte, TurnTimer.svelte, Dice.svelte) directly mutated game.state properties or emitted raw sendMatchData payloads, bypassing central game controllers. This led to state desyncs and bypassed validation.
  • The Fix: All UI mutations are strictly routed through GameController.ts and SetupController.ts. The UI is purely derived and reacts to the Svelte 5 $state changes.

6. Map Generation Adjacency Exploits

  • The Hazard: The generateMap() function previously used a standard shuffle for number tokens, allowing 6s and 8s to spawn adjacent to each other, breaking core game balance.
  • The Fix: generateMap() now implements an explicit validation and fallback loop via hasAdjacentRedNumbers to strictly prohibit 6s and 8s from touching.

7. Infinite Bank Supply (Scarcity Bypass)

  • The Hazard: The bank distributed requested resources indefinitely, ignoring the strict 19-card physical limit per resource type.
  • The Fix: GameState tracks bankSupply: Record<Resource, number>. The production loop calculates total demand and enforces the Scarcity Rule (no one receives resources if total demand exceeds supply and multiple players are involved).

1 item under this folder.

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