An AI-driven, web-based D&D experience where an LLM acts as the Dungeon Master for one or more players, with images and maps generated on demand.
See DESIGN.md for the full system design and the decisions behind it.
packages/
engine/ Deterministic D&D 5e SRD 5.1 rules engine — the source of truth.
Pure TypeScript, seeded, no LLM, no I/O.
protocol/ Shared wire types (DTOs + WebSocket messages). Types only.
server/ Fastify game server: REST API (auth, SRD catalog, characters,
adventures), JSON-file persistence, the authenticated WebSocket
play room, the DM orchestration loop + engine-backed tool layer,
and pluggable DM + image providers.
web/ Vite + React site: accounts, dashboard, guided SRD character
creator, character sheet viewer, adventure create/list/resume,
and the live play screen (scene art, narrative log, party/HP
panel, initiative tracker, tile-based tactical map).
- Accounts — email + password (scrypt-hashed, bearer tokens).
- Character creator — guided SRD 5.1: 9 races, 12 classes, 8 backgrounds, standard-array or point-buy ability scores, skill selection, live preview; HP/AC/saves/skills/attack derived by the engine. Write a back-story and the LLM suggests a fitting build (race/class/background/skills) to start from. Each hero gets an AI-generated portrait (from their look + back-story; regenerable), shown on the sheet, dashboard, and the in-play party panel.
- Adventures — start from five pre-canned adventures (each with a player premise and a secret DM-only back-story the DM runs from) or write your own; list them, and resume exactly where you left off (live game state is snapshotted after each turn).
- Multiplayer — copy a shareable invite link; a friend joins with their own character (even mid-combat, rolling into initiative) and the adventure appears on their dashboard. Everyone shares one live, real-time session.
- Play — real-time DM narration, dice, combat with initiative and a tactical map, on-demand images, and a campaign memory that keeps continuity over long play.
See DEPLOY.md for deploying to Cloud Run via GitHub Actions.
The engine is authoritative: the DM (LLM) proposes actions through a tool layer; the engine validates, rolls dice, and applies results. The LLM never edits numbers — it narrates what the engine decided.
Real GPU/cloud services aren't required to run the demo:
| Concern | Default (works offline) | Real backends |
|---|---|---|
| DM | ScriptedDM — deterministic demo adventure |
Gemini (DM_PROVIDER=gemini, Vertex AI or AI Studio) or Ollama (DM_PROVIDER=ollama, local) |
| Images | SVG placeholder cards | Imagen via Vertex AI / @google/genai (IMAGE_PROVIDER=gemini) |
The DM runs a tool-calling loop where the model narrates and the engine resolves all dice/HP/combat (it never invents numbers). Pick a Gemini backend:
# Vertex AI (needs a billed GCP project + credentials):
DM_PROVIDER=gemini GCP_PROJECT=your-project \
GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json \
GEMINI_MODEL=gemini-2.5-flash npm run dev:server
# …or AI Studio (free tier, just an API key):
DM_PROVIDER=gemini GEMINI_API_KEY=your-key npm run dev:serverIf the model backend is unreachable, the server degrades gracefully to the scripted DM.
Set IMAGE_PROVIDER=gemini (uses the same Vertex/AI-Studio credentials as the DM) to
generate real scene/portrait/item art via Imagen instead of placeholder cards:
IMAGE_PROVIDER=gemini GCP_PROJECT=your-project \
GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json \
GEMINI_IMAGE_MODEL=imagen-3.0-generate-002 npm run dev:serverGenerated PNGs are cached on disk under DATA_DIR/images and served at /images/<hash>.png.
Requires Node 20+. From the repo root:
npm install
# Terminal 1 — game server (http + ws on :8787)
npm run dev:server
# Terminal 2 — web client (Vite on :5173, proxies /ws and /api to the server)
npm run dev:webThen open http://localhost:5173: create an account → create a character → start an adventure → play. In play, try “look around”, “enter the cave”, “attack the goblin”. Adventures are saved automatically and can be resumed from the dashboard.
Data is persisted to ./.data/aidm.json (override with DATA_DIR).
docker compose up -d # Postgres (pgvector) + Redis
export DATABASE_URL=postgres://aidm:aidm@localhost:5433/aidm
export REDIS_URL=redis://localhost:6380
npm run dev:serverWith DATABASE_URL set the app uses Postgres (else the JSON file); with REDIS_URL
set, snapshots broadcast across instances via pub/sub and turns are serialized per
adventure with a Redis lock. Both default to in-process/file fallbacks when unset.
With Ollama running and a tool-capable model pulled (e.g. qwen2.5):
DM_PROVIDER=ollama OLLAMA_MODEL=qwen2.5:7b npm run dev:servernpm test # engine unit tests + server integration tests
npm run typecheck # all packages