Guidance for coding agents working in this repository. Humans: see
README.md to run it and docs/ for the full design.
An AI-driven, web-based D&D experience: an LLM Dungeon Master narrates, a
deterministic rules engine adjudicates, multiple players share a real-time
session, and art is generated on demand. Monorepo of npm workspaces
(packages/engine, protocol, server, web).
Read docs/architecture.md first — it's the code map
and the turn-flow walkthrough. The deeper rationale is in
docs/DESIGN.md (cited from source comments as docs/DESIGN.md §N).
The engine is the source of truth; the LLM only narrates.
The LLM proposes actions through the tool layer (server/src/gameEngine.ts); the
engine (packages/engine) validates, rolls dice (seeded), and applies results. The
model never sets HP, decides hits, or invents numbers. If a change would let the LLM
mutate state directly, it's wrong — route it through a tool that calls the engine.
Node 20+ (CI uses 22). From the repo root:
npm install
npm run build --workspace @ai-dm/engine # MUST build engine first — others consume its dist/
npm run typecheck # all workspaces
npm test # engine unit tests + server integration testsRun locally:
npm run dev:server # http + ws on :8787 (scripted DM, JSON store — no cloud needed)
npm run dev:web # Vite on :5173, proxies /api and /ws to the serverA single package: append --workspace @ai-dm/<name> to typecheck/test/build.
Always, from the root: npm run build --workspace @ai-dm/engine && npm run typecheck && npm test.
If you touched a package's behavior, add or update its tests. Report failures with the
actual output — don't claim green you haven't seen. Tests are colocated as *.test.ts
(engine: pure unit tests; server: backend.test.ts + focused loop/memory/dm tests).
- Strict TypeScript (
tsconfig.base.json):strict,noUncheckedIndexedAccess,noImplicitOverride,verbatimModuleSyntax. - ESM / NodeNext: relative imports use a
.jsextension even from.tsfiles (e.g.import { x } from "./store.js"). Type-only imports useimport type. - Seeded randomness only. Never call global
Math.random()in the engine or game logic — use the session'sRNG, so turns replay identically and snapshots are exact. - Wire types live in
@ai-dm/protocol. Don't redefine DTOs/messages in the web; import them. Changing a shape? Update protocol, then both sides. - Match the surrounding style (comment density, naming, idiom). Files carry a
short top-of-file comment explaining their role and citing
docs/DESIGN.md §Nwhere relevant — keep that habit. - Client never sees enemy stat blocks.
CreatureView.sheetis for PCs only.
| You want to change… | Look in |
|---|---|
| Rules / dice / combat math | packages/engine/src/* |
| What the DM can do (tools) | server/src/gameEngine.ts |
| DM prompt / context / scripted fallback | server/src/dm.ts, geminiDm.ts |
| The turn loop / WebSocket | server/src/index.ts |
Session shape, snapshots, toView |
server/src/session.ts |
| DM memory (recap / key facts) | server/src/memory.ts |
Long-term recall (embeddings, retriever, recall() tool) |
server/src/embeddings.ts, recall.ts |
| Persistence | server/src/store.ts, postgres.ts |
| Scaling (broadcast/lock/presence) | server/src/realtime.ts, runtime.ts |
| Character building / spells / inventory / XP | server/src/srd.ts, srdContent.ts |
| REST endpoints | server/src/routes.ts |
| Wire types | packages/protocol/src/index.ts |
| UI screens / play table | packages/web/src/screens/*, components.tsx |
- This repo deploys on push to
mainvia GitHub Actions → Cloud Run. Don't push or commit unless the user asks; branch offmainif they do. - Commit messages end with the trailer:
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> - After a deploy, verify the run's conclusion (
gh run view <id> --json conclusion), not just that the command exited —gh run watchcan report success for a run that already failed. - Deployment setup and topology:
docs/DEPLOY.md; infra is Terraform ininfra/.
- Build the engine before typecheck/test, or you'll hit
Cannot find module '@ai-dm/engine'. - Backtick characters inside SQL string templates in
postgres.tsbreak the JS template literal — avoid them in comments there. ensureCharacterFieldsruns on read and must stay idempotent (returnstrueonly when it actually changed something). New backfills go there.- Cloud Run reserves
/healthzat the frontend — the health endpoint is/api/health.