- Python 83.5%
- TypeScript 14.6%
- Jinja 1.6%
- Makefile 0.2%
- Campaign framer runs at first session open, storing hidden GM knowledge (dramatic question, opening scene, starting aspects, recommended NPCs) on new Game.campaign_notes; applied to the opening session and injected into narration context, never exposed to players - Opening narration on session open: fresh scene for new games, a "previously" recap when resuming; freeform/opening narration modes with optional acting character - Session-close continuity: consequences reconciled to character sheets, scene snapshot resumed next session, AI story-so-far summary - Session open resets stress and Fate Points per FATE rules while carrying consequences over from the sheet - New difficulty and session_summary prompt templates; narration template gains campaign direction / story-so-far / mode - ModelConfig.temperature now optional (provider default when unset) - Frontend: surface new WS events and session flow in play view, lobby, and knowledge base Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .agents/skills/code-documenter | ||
| .claude | ||
| .vscode | ||
| alembic | ||
| backend | ||
| frontend | ||
| .env.example | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .python-version | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| reconciliation.md | ||
| requirements.md | ||
| skills-lock.json | ||
| uv.lock | ||
fate-game-system
An implementation of the FATE Core tabletop RPG system with a multiplayer web front end. FastAPI backend, React/TypeScript frontend, real-time play over WebSockets, and AI-driven GM narration.
Prerequisites
- Python (managed via
uv) - Node.js 18+ / npm
- Docker (only if you want the PostgreSQL dev database; SQLite works without it)
Setup
uv sync # install Python dependencies
cd frontend && npm install # install JS dependencies
Create your environment file:
cp .env.example .env
Then edit .env:
SECRET_KEY(required) — generate withopenssl rand -hex 32- AI provider keys (optional) — set
ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_API_KEY, or run a local Ollama and setOLLAMA_BASE_URL. Without a configured provider everything works except AI narration (the game emits a "narration unavailable" notice instead).
Running (Docker — one command)
make docker-start # postgres + backend (:8000) + frontend (:5173)
This starts all three services with the source bind-mounted and hot reload
active on both backend and frontend. Database migrations are applied
automatically when the backend container starts. Open
http://localhost:5173 when docker compose ps shows all services up
(the first start installs dependencies inside the containers, so give it a
minute; docker compose logs -f backend frontend shows progress).
make docker-stop shuts everything down; data persists in the postgres
volume. make docker-clean removes the volumes too.
Running (on the host, without Docker)
Alternative if you prefer separate terminals. Point DATABASE_URL in .env
at SQLite (sqlite+aiosqlite:///./dev.db) or at a running postgres
(docker compose up -d postgres), then:
make migrate # apply database migrations
make dev-backend # FastAPI on http://localhost:8000 (OpenAPI docs at /docs)
make dev-frontend # Vite on http://localhost:5173 (proxies API + WS to :8000)
First-time walkthrough
- Register — the first account created automatically becomes the system admin.
- Enable AI (optional) — go to Admin (top nav):
- enable the provider(s) you have keys for,
- under AI Module Defaults, pick a provider + model id for at least
narration (e.g.
anthropic/claude-sonnet-4-20250514), - save.
- Create a character — Dashboard → New Character. The form validates the FATE Core skill pyramid and refresh/stunt budget live.
- Create a world (optional) — lore and locations show up in the play view's knowledge base panel.
- Create a game — choose Solo to play by yourself with the AI as narrator, or Multiplayer to invite others by username from the lobby.
- Lobby — assign your character to your seat, then Open Session & Play.
- Play — the three-panel view:
- Left: knowledge base (scene, party sheets with live stress/fate points, NPCs, world lore, rules reference)
- Center: game feed. Pick an action type + skill, set the opposition, toggle aspect invocations (green dot = free invoke, otherwise 1 fate point), describe your intent, and Roll. Rolls resolve through the rules engine, results and dice are shown inline, and the AI narrates.
- Attacks against another character trigger the absorption flow on the target's screen: choose a stress box and/or consequence, or concede.
- Right (multiplayer only): private player chat. Never sent to the AI.
- GMs get a toolbar to prompt players, post rulings, change scenes (with scene aspects), and close the session.
Testing & code quality
make test # backend test suite
make test-engine # rules engine tests only
make cov # coverage report
make check # black + isort + flake8
cd frontend && npm run typecheck # TypeScript
cd frontend && npm run build # production build
Note: the WebSocket integration tests (backend/tests/websocket/test_ws_*.py)
are currently skipped — the Starlette TestClient harness hangs intermittently
in full-suite runs. The WS layer is exercised manually through the running
app; reworking that harness is an open task.
Architecture notes
backend/app/engine/— pure, synchronous FATE Core rules engine. No I/O.backend/app/websocket/pipeline.py— the session pipeline: validate → engine → DB commit → broadcast → AI narration (streamed). State is always persisted before the AI is called.- Player chat is excluded from AI context structurally (the narration context
builder only reads the
gamechannel). - Transient per-session state (stress, consequences, fate points) lives on
GameMembership.session_state; character sheets are never mutated by play.
See CLAUDE.md for conventions and requirements.md for the full
specification.