Fantasy Gamebook Engine
A solo-play gamebook (Fighting Fantasy–style) engine where an AI acts as the game master and narrator. The whole design turns on one hard rule:
The AI never invents numbers or rolls dice in prose. All randomness, state, and combat math go through an MCP server. The AI only narrates and offers choices.
Everything numeric — dice, attribute generation, luck tests, combat rounds, persistence — is owned by a deterministic Python engine and exposed to the narrator as MCP tools. The narrator (Phase 1: Claude Code) reads real state, calls tools, and writes the story around the results.
Status: Phase-1 MVP — implemented and green. Python engine under src/gamebook/, an MCP server exposing 18 tools, 158 passing tests at 96% coverage across tests/engine, tests/server, and tests/qa. The narrator harness lives as Claude Code skills and commands under .claude/.
---
Why it's built this way
The system decomposes into 8 modules, and the golden rule of the design is that dependency arrows point only at interfaces/contracts, never at concrete implementations. That discipline is what makes three things swappable without touching the rest:
- Storage —
JSONStoragetoday,PostgresStoragetomorrow. Injected at server startup. - Adventure — swap the lore module without touching the engine. Today a
SKILL.md. - Harness — swap who narrates (terminal → web) while reusing the same MCP tool contract.
07 harness ───────► 05 mcp ◄──────── 08 commands
│
┌─────────────┼─────────────┐
▼ ▼ ▼
01 rules 04 combat 03 storage (interface)
│ │ ▲
└──────►──────┘ │ implements
│ │
▼ │
02 domain ◄─────────────┘ (shared data contracts)
06 adventure-module ──(lore consumed by)──► 07 harness
| # | Module | Responsibility | Pluggable? | |----|--------------------|--------------------------------------------------------------------------------|-------------------------| | 01 | rules | Pure rules engine — dice, attributes, luck test, one combat round. No I/O, RNG injected. | — (stable) | | 02 | domain | Shared data contracts + invariant validation. Depends on nothing. | — (stable) | | 03 | storage | Persistence behind the StorageBackend interface. | ✅ JSON ↔ Postgres | | 04 | combat | Combat lifecycle (start → rounds → flee → end). | — | | 05 | mcp | MCP server exposing tools to the harness. No game rules of its own. | — (stable contract) | | 06 | adventure-module | Pluggable static lore (zones, bestiary, victory). Debut: Ignarok. | ✅ Ignarok ↔ others | | 07 | harness | The narrator/master that talks to the player and calls the MCP. | ✅ Claude Code ↔ agent | | 08 | commands | System commands (/hero, /backpack, /map, /save). | ✅ add new ones |
Project layout
src/gamebook/
domain/ # data contracts: CharacterSheet, World, Event, Combat, ArchiveRecord
rules/ # pure rules engine (interfaces + implementation), injectable RNG
storage/ # StorageBackend interface + JSONStorage + in-memory impl
combat/ # combat lifecycle (interfaces + implementation)
mcp/ # FastMCP server over stdio — orchestrates the modules
docs/
00-index.md … 08-commands.md # specs (requirements)
CONTRACTS.md # authoritative English code contract
adrs/ # architecture decision records
learning-lessons/ # captured gotchas
.claude/
skills/ # game-master, combat-sub-agent, ignarok (the Phase-1 harness)
commands/ # /hero, /backpack, /map, /save
tests/
engine/ server/ qa/
Requirements
- Python 3.13 (
.python-version),requires-python >= 3.12 uvfor dependency management and running
Quickstart
# Run the full test suite
uv run pytest -q
# Scope it
uv run pytest tests/engine -q # pure rules (seeded RNG, in-memory storage)
uv run pytest tests/server -q # storage + MCP server
uv run pytest tests/qa -q # plugability / isolation / e2e
# The golden-rule plugability audit (catches any module reaching past an interface)
uv run pytest tests/qa/test_dependencies.py tests/qa/test_isolation.py -q
# Start the MCP server over stdio (exits cleanly on EOF)
uv run python -m gamebook.mcp.server
The server is registered for Claude Code in .mcp.json, so the narrator skills can call it directly.
MCP tools (18)
The authoritative contract is docs/CONTRACTS.md §6. Grouped by purpose:
| Group | Tools | |-------------|-----------------------------------------------------------------------------------------| | Dice & luck | roll_dice, test_luck | | Character | create_character, read_character_sheet, update_character_sheet, archive_character| | World | read_world, update_world | | Chronicle | register_event, read_events, read_summary, update_summary | | Combat | start_combat, resolve_combat_round, flee_combat, end_combat | | Saves | save_progress, load_progress |
Game rules (reference)
- Attributes:
skill= 1d6+6,stamina= 2d6+12,luck= 1d6+6 — each tracksinitial/current. - Luck test: success if roll ≤ current luck; luck always decrements by exactly 1 afterward.
- Combat round: each side's attack strength =
skill+ 2d6; higher AS hits for base
damage 2, a tie deals 0.
- Luck on a hit: won+lucky → 4, won+unlucky → 1, lost+lucky → 1, lost+unlucky → 3.
- Death / flee: hero at 0 stamina →
alive: false; fleeing costs 2 stamina and only if allowed.
rules and combat are tested in full isolation with a seeded RNG and in-memory storage — deterministic, no disk, no AI.
Playing a session (Phase-1 harness)
When you sit down to play rather than develop, Claude Code becomes the Game Master.
Session-opening rule: before narrating anything, the master reads real engine state via MCP (read_character_sheet, read_world, read_events, read_summary). No living character → it offers create_character and starts the adventure's opening. A living character → it resumes from the exact recorded point (never restarts, re-rolls, or contradicts recorded facts). Every number and state change routes through MCP tools.
- Skills (
.claude/skills/):game-master(narrator),combat-sub-agent(runs one
fight), ignarok (the debut adventure — swap this file to swap adventures).
- Commands (
.claude/commands/):/hero,/backpack,/map,/save— read-outs and
checkpoints that reflect real MCP state and don't advance the story.
Documentation
docs/00-index.md— start here; maps every module.docs/CONTRACTS.md— the authoritative English code contract (cross-module interfaces,
domain schema §2, MCP tool contract §6). When code and a spec disagree, CONTRACTS.md governs.
docs/adrs/— architecture decision records (ADR-001 … ADR-010).docs/learning-lessons/— captured gotchas worth not relearning.CLAUDE.md— guidance for Claude Code working in this repo.
Roadmap
- Phase 1 (current): Claude Code as harness,
JSONStorage, adventure asSKILL.md. - Phase 2: a PydanticAI/FastAPI harness with structured
Sceneoutput for a web frontend,
plus PostgresStorage — reusing the same MCP tool contract and adventure module unchanged. That reuse is the entire point of the architecture.






