4.3 KiB
4.3 KiB
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 two packages:
packages/framework(boardgame-phaser) — Reusable library: reactive scenes, signal→Phaser bindings, input/command bridge, Preact UI componentspackages/sample-game— Demo Tic-Tac-Toe game using the framework
Commands
Root level
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)
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)
pnpm --filter sample-game dev # Vite dev server
pnpm --filter sample-game build # tsc && vite build
pnpm --filter sample-game preview # vite preview
Dependency setup
# boardgame-core is a local dependency at ../boardgame-core
# After changes to boardgame-core, rebuild it and copy dist:
cd ../boardgame-core && pnpm build
# Then copy dist into the pnpm symlink (pnpm file: links don't include dist/):
cp -r dist/* ../boardgame-phaser/node_modules/.pnpm/boardgame-core@file+..+boardgame-core_*/node_modules/boardgame-core/dist/
Testing
No test framework is configured yet. When adding tests, use Vitest (consistent with boardgame-core):
# In packages/framework:
pnpm add -D vitest
# Run all tests:
pnpm vitest run
# Run a single test file:
pnpm vitest run src/bindings/index.test.ts
# Run tests matching a pattern:
pnpm vitest run -t "bindRegion"
# Watch mode:
pnpm vitest
Code Style
Imports
- Use ESM imports only (
import/export) - Group imports: external libraries → workspace packages → relative imports
- Use
typeimports for type-only imports:import type { Foo } from 'bar' - Path alias
@/*maps tosrc/*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:
<TState extends Record<string, unknown>> - Define local utility types:
type DisposeFn = () => void - Use
as anysparingly; preferas unknown as Record<string, unknown>for type narrowing
Naming conventions
- Classes: PascalCase (
ReactiveScene,InputMapper,PromptHandler) - Interfaces: PascalCase with descriptive names (
ReactiveSceneOptions,BindRegionOptions) - Functions: camelCase (
bindSignal,createInputMapper,createPromptHandler) - Constants: UPPER_SNAKE_CASE (
CELL_SIZE,BOARD_OFFSET) - Type aliases: PascalCase (
DisposeFn,CommandResult) - Factory functions:
create*prefix (createInputMapper,createPromptHandler)
Architecture patterns
- ReactiveScene: Abstract base class extending
Phaser.Scene. Subclasses implementonStateReady()andsetupBindings(). Effects are auto-cleaned on scene shutdown viathis.events.on('shutdown', ...). - Bindings: Use
effect()from@preact/signals-coreto reactively sync signal state to Phaser objects. Always return cleanup functions. - Input/Command bridge: Maps Phaser pointer events to
boardgame-corecommand strings. Usescreate*factory pattern. - UI components: Preact functional components with hooks. Use
className(notclass) for CSS classes.
Error handling
- Command results use
{ success: true, result } | { success: false, error }pattern - Scene effects are cleaned up via the
shutdownevent, not by overridingshutdown() - Use
as anycasts only when Phaser types are incomplete (e.g.,setInteractive) - Prompt handlers return
string | nullfor validation errors
JSX
jsxImportSource: "preact"— no React import needed- Use
h()or JSX syntax interchangeably - Use
classNamefor CSS class attribute