AI-powered board game playtesting and balance analysis. Define card-driven games, run thousands of automated playtests with AI agents, spot broken openers, and compare balance variants — all in the browser, no server required.
Prerequisites: Node.js >= 20 and npm >= 9
git clone <repo-url> && cd playtestai
npm install
npm run devOpen http://localhost:3000. Seed data loads automatically on first launch in development and test environments — you'll see example projects ready to simulate.
| Category | Details |
|---|---|
| Rule modeling | Player counts, win conditions, resources, cards with custom stats, CSV import |
| Simulation engine | 100-10,000 automated playtests per run with Random, Greedy, and Balanced AI agents |
| Balance analytics | Win rates by seat, card power rankings, game length distribution, dominant strategy detection, overall balance score (0-100) |
| A/B testing | Duplicate versions, edit suspect cards, compare metrics side-by-side |
| Persistence | SQLite-backed version history and simulation runs survive refreshes |
| Reporting | Printable/exportable balance reports via browser print dialog |
- Framework: Next.js 16 (App Router)
- Language: TypeScript 5 (strict mode)
- Styling: Tailwind CSS 4
- Database: SQLite via
better-sqlite3 - Charts: Recharts
- Validation: Zod 4
- Testing: Vitest
| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Create production build |
npm start |
Serve production build |
npm run lint |
Run ESLint checks |
npm run typecheck |
Run TypeScript compiler checks (no emit) |
npm test |
Run all tests once |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with coverage report |
npm run validate |
Run typecheck + lint + tests (pre-push) |
playtestai/
├── app/ # Next.js App Router
│ ├── api/projects/ # REST API endpoints (6 routes)
│ ├── projects/ # Project list and workbench pages
│ ├── layout.tsx # Root layout with dark theme
│ ├── page.tsx # Landing page
│ └── error.tsx # Global error boundary
├── components/
│ ├── chrome/ # App shell (navigation)
│ ├── projects/ # Project workbench UI
│ │ └── workbench/ # Tab panels (dashboard, definition, simulate, compare, report)
│ └── ui/ # Shared UI primitives
├── lib/
│ ├── api-helpers.ts # Shared API response utilities
│ ├── db/ # Database layer (schema, repos, seed)
│ │ ├── schema.ts # SQLite schema + connection
│ │ ├── projects.ts # Project CRUD
│ │ ├── versions.ts # Version CRUD
│ │ ├── runs.ts # Simulation run CRUD
│ │ └── seed.ts # Demo data bootstrap
│ ├── simulation/ # Simulation engine
│ │ ├── engine.ts # Batch simulation orchestrator
│ │ ├── agents.ts # AI agent strategies
│ │ ├── mechanics.ts # Game rules (deck, combat, scoring)
│ │ ├── analytics.ts # Histograms, balance scoring
│ │ ├── constants.ts # All tuning parameters
│ │ ├── rng.ts # Deterministic PRNG
│ │ └── __tests__/ # Engine + validation tests
│ ├── types.ts # Shared TypeScript interfaces
│ ├── validation.ts # Zod schemas for API input
│ └── utils.ts # Utility functions
├── data/ # SQLite database (auto-created)
├── docs/ # Design docs and reviews
└── public/ # Static assets
The engine uses a simplified card battler loop designed to surface balance issues:
- Economy — All defined resources aggregate into a playable economy pool
- Deck — Players draw from a shared deck built from card quantities
- Decisions — AI agents choose affordable legal cards each turn using strategy-specific heuristics
- Resolution — Cards apply power, score, draw, shields, economy, and damage through custom stats
- Analytics — The engine records per-game outcomes and aggregates statistics across the full batch
| Agent | Strategy |
|---|---|
| Random | Plays any affordable card at random |
| Greedy | Maximizes immediate power + finisher value |
| Balanced | Weighs short-term and long-term value with slight randomness |
The app now boots with a richer deterministic demo catalog designed to exercise the full UI:
- 8 seeded projects spanning deck builder, TCG, party, and strategy styles
- 6 active projects with 5 versions each (latest branches ready for A/B comparison)
- 32-card catalogs on active versions, plus mixed published/draft snapshots
- 25 stored simulation runs with pre-computed analytics, recommendations, and edge-case warnings
- Edge fixtures for an empty archive project, a one-card version, and an intentionally extreme simulation result
- Seeding is transactional and idempotent (
INSERT OR IGNORE), so re-running bootstrap won't duplicate rows. - Existing non-seed databases are left untouched unless the seed marker is already present.
- Automatic seeding is guarded in production. To opt in explicitly, set:
PLAYTESTAI_ENABLE_SEEDING=true| Method | Path | Description | Response |
|---|---|---|---|
GET |
/api/health |
Health check (version, timestamp) | 200 with JSON body |
GET |
/api/projects |
List all projects with summaries | 200 with JSON body |
POST |
/api/projects |
Create a new project | 201 with { project } |
GET |
/api/projects/:id |
Get project with versions and runs | 200 with { project } |
PUT |
/api/projects/:id |
Update project name/description | 200 with { project } |
DELETE |
/api/projects/:id |
Delete project and all related data | 204 No Content |
POST |
/api/projects/:id/versions |
Duplicate a version | 201 with { project } |
PUT |
/api/projects/:id/versions/:vid |
Update version definition | 200 with { project } |
DELETE |
/api/projects/:id/versions/:vid |
Delete a version | 200 with { project } |
POST |
/api/projects/:id/runs |
Save a simulation run | 201 with { project } |
DELETE |
/api/projects/:id/runs/:rid |
Delete a simulation run | 200 with { project } |
DELETE convention: Deleting a project returns
204 No Content(the parent resource no longer exists). Deleting a version or run returns200with the updated parent{ project }payload so the client can refresh state without an extra round-trip.
Tests cover the simulation engine, agent strategies, game mechanics, validation logic, utility functions, CSV parsing, and seed/bootstrap rules:
npm test # Run all 141 tests
npm run test:watch # Watch mode for development
npm run test:coverage # Generate coverage report- Seed data is inserted automatically on first launch in development/test, and only in production when
PLAYTESTAI_ENABLE_SEEDING=true - Database file is created at
data/playtestai.sqlite— gitignored by default - Simulations are deterministic — same seed produces identical results
- Reports can be exported as PDF via the browser print dialog
- All API inputs are validated with Zod schemas before database writes