# AGENTS.md - boardgame-phaser ## Project Overview A Phaser 3 framework for building web board games, built on top of `boardgame-core` (state management with Preact Signals + Mutative). Uses a pnpm monorepo with four packages: - **`packages/framework`** (`boardgame-phaser`) — Reusable library: reactive scenes, signal→Phaser bindings, input/command bridge, Preact UI components - **`packages/sample-game`** — Demo Tic-Tac-Toe game using the framework - **`packages/boop-game`** — Boop sample game - **`packages/regicide-game`** — Regicide game ## boardgame-core Usage For detailed boardgame-core API documentation and examples, see `packages/framework/node_modules/boardgame-core/` Key concepts: - **MutableSignal** — Reactive state container with `.value` and `.produce()` - **Command System** — CLI-style parsing with schema validation and prompt support - **Region System** — Spatial management with `createRegion()`, `applyAlign()`, `shuffle()`, `moveToRegion()` - **Part System** — Game pieces with `createPartsFromTable()`, `flip()`, `roll()` - **RNG** — Deterministic PRNG via `createRNG(seed)` for reproducible game states ### Quick Example ```ts import { createGameCommandRegistry, createRegion, IGameContext } from 'boardgame-core'; type GameState = { board: Region; parts: Part<{ player: 'X' | 'O' }>[]; currentPlayer: 'X' | 'O'; }; type Game = IGameContext; const registry = createGameCommandRegistry(); registry.register('place ', async function(game: Game, row: number, col: number) { await game.produceAsync(state => { state.parts.push({ id: `p-${row}-${col}`, regionId: 'board', position: [row, col], player: state.currentPlayer }); }); return true; }); ``` ## Commands ### Root level ```bash pnpm dev # Start sample-game dev server (Vite + HMR) pnpm build # Build framework, then sample-game pnpm build:framework # Build framework only pnpm preview # Preview sample-game production build ``` ### Framework (`packages/framework`) ```bash pnpm --filter boardgame-phaser build # tsup → dist/index.js + dist/index.d.ts pnpm --filter boardgame-phaser typecheck # tsc --noEmit ``` ### Sample game (`packages/sample-game`) ```bash pnpm --filter sample-game dev # Vite dev server with HMR pnpm --filter sample-game build # tsc && vite build pnpm --filter sample-game preview # vite preview pnpm --filter sample-game typecheck # tsc --noEmit (add to scripts first) ``` **Note**: Sample game uses Tailwind CSS v4 with `@tailwindcss/vite` plugin and `@preact/preset-vite` for JSX transformation. ### Dependency setup ```bash # boardgame-core is a local dependency via symlink (link:../../../boardgame-core) # After changes to boardgame-core, simply rebuild it: cd ../boardgame-core && npm build # The symlink automatically resolves to the updated dist/ ``` ### Testing Framework and regicide-game have **Vitest** configured: ```bash # In packages/framework: pnpm --filter boardgame-phaser test # Run all tests pnpm --filter boardgame-phaser test:watch # Watch mode # In packages/regicide-game: pnpm --filter regicide-game test # Run all tests pnpm --filter regicide-game test:watch # Watch mode ``` ## Code Style ### Imports - Use ESM imports only (`import`/`export`) - Group imports: external libraries → workspace packages → relative imports - Use `type` imports for type-only imports: `import type { Foo } from 'bar'` - Path alias `@/*` maps to `src/*` in both packages ### Formatting - 2-space indentation, no semicolons - Single quotes for strings - Trailing commas in multi-line objects/arrays - Max line length: not enforced, but keep reasonable ### TypeScript - Strict mode enabled (see `tsconfig.base.json`) - Prefer explicit types for function return values and public class members - Use generics with constraints: `>` - Define local utility types: `type DisposeFn = () => void` - Use `as any` sparingly; prefer `as unknown as Record` for type narrowing ### Naming conventions - **Classes**: PascalCase (`GameHostScene`, `PhaserGame`, `GameUI`) - **Interfaces**: PascalCase with descriptive names (`GameHostSceneOptions`, `PhaserGameProps`) - **Functions**: camelCase (`spawnEffect`, `bindRegion`) - **Constants**: UPPER_SNAKE_CASE (`CELL_SIZE`, `BOARD_OFFSET`) - **Type aliases**: PascalCase (`DisposeFn`, `CommandResult`) - **Factory functions**: `create*` prefix (`createRegion`, `createRNG`) ### Architecture patterns - **GameHostScene**: Abstract base class extending `Phaser.Scene`. Subclasses implement game-specific logic. Provides `gameHost` property for state access and `addInterruption()`/`addTweenInterruption()` for async flow control. Disposables are auto-cleaned on scene shutdown via `this.events.on('shutdown', ...)`. - **Spawner**: Use `spawnEffect()` for data-driven object creation with signal-based configuration. - **UI components**: Preact functional components with hooks. Use `className` (not `class`) for CSS classes. - **Phaser Bridge**: Use `PhaserGame`, `PhaserScene`, and `GameUI` components from `boardgame-phaser` for React/Phaser integration. ### Error handling - Use `DisposableBag` for managing multiple disposables in scenes - Scene effects are cleaned up via the `shutdown` event - Use `as any` casts only when Phaser types are incomplete (e.g., `setInteractive`) - Game commands return validation errors as `string | null` ### JSX - `jsxImportSource: "preact"` — no React import needed - Use `h()` or JSX syntax interchangeably - Use `className` for CSS class attribute