Skip to main content
Glama
Fantasy Gamebook Engine

Fantasy Gamebook Engine

by rc-ventura

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:

  1. Storage — JSONStorage today, PostgresStorage tomorrow. Injected at server startup.

  2. Adventure — swap the lore module without touching the engine. Today a SKILL.md.

  3. 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

Related MCP server: DM20 Protocol

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

  • uv for 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 tracks initial/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 as SKILL.md.

  • Phase 2: a PydanticAI/FastAPI harness with structured Scene output 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.

Available Tools

18 tools
archive_characterA

Archive the hero to the graveyard (death) or hall_of_fame (victory).

Inputs: destination (required). Returns: object

create_characterC

Roll a new hero's attributes and persist a living character sheet.

Inputs: name (required). Returns: The hero's full sheet — the central piece of persistent player state.

end_combatC

Conclude a combat and return its final result.

Inputs: combat_id (required). Returns: The summary handed back when a combat is concluded.

flee_combatC

Flee the combat (if allowed); costs 2 stamina.

Inputs: combat_id (required). Returns: The result of fleeing: a fixed 2 damage and the combat ends. ``hero_alive`` dis…

load_progressB

Restore all state from a named slot (default 'autosave').

Inputs: slot. Returns: object

read_character_sheetA

Return the hero's full character sheet.

Inputs: no named parameters. Returns: The hero's full sheet — the central piece of persistent player state.

read_eventsA

Return the full chronicle of events, in order.

Inputs: no named parameters. Returns: result

read_summaryB

Return the running narrative summary.

Inputs: no named parameters. Returns: result

read_worldA

Return the current world state.

Inputs: no named parameters. Returns: The mutable world state: where the hero is, what they've seen, story flags.

register_eventC

Append a hard fact to the chronicle, stamped with the current turn.

Inputs: data (required), type (required). Returns: An append-only chronicle entry (a hard fact that happened on a turn).

resolve_combat_roundC

Resolve one combat round, optionally testing luck on the hit.

Inputs: use_luck (required), combat_id (required). Returns: The full result of one resolved combat round, after damage is applied.

roll_diceA

Roll a dice expression like '2d6' or '1d6+6'.

Inputs: notation (required). Returns: The outcome of one dice expression: the individual rolls and their total.

save_progressB

Snapshot all state to a named slot (default 'autosave').

Inputs: slot. Returns: object

start_combatC

Start a fight against one or more living enemies.

Inputs: enemies (required), flee_allowed (required). Returns: The persisted state of an in-progress (or just-ended) fight.

test_luckA

Test the hero's luck (2d6 <= current luck); always spends one luck.

Inputs: no named parameters. Returns: The outcome of a luck test; ``luck_after`` is always one less than before.

update_character_sheetA

Patch the character sheet. Scalars/lists are replaced; attribute sub-dicts (skill/stamina/luck) are merged. Invariants are validated; on error the state is left unchanged.

Inputs: changes (required). Returns: The hero's full sheet — the central piece of persistent player state.

update_summaryC

Replace the running narrative summary.

Inputs: text (required). Returns: object

update_worldA

Patch the world. Scalars/lists (current_location, visited_locations, known_npcs, turn) are replaced; 'flags' is merged key-wise so one flag can be set without dropping others. Invariants are validated; on error the state is left unchanged. Sole legal path to set the victory flag and advance the turn counter.

Inputs: changes (required). Returns: The mutable world state: where the hero is, what they've seen, story flags.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 18 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose. Combat tools (start, end, flee, resolve) are separated; read/update tools target different aspects (character, events, summary, world); and utilities like roll_dice, test_luck, save/load progress are unique.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern in snake_case (e.g., create_character, start_combat, read_world). There is no mixing of conventions or vague verbs.

    Tool Count4/5

    18 tools is slightly above the typical 3-15 range but still well-scoped for a gamebook engine. The number covers character management, combat, world state, narrative, and progress, without feeling excessive.

    Completeness4/5

    The tool set covers core CRUD operations for characters, world, events, and combat lifecycle. One minor gap is the lack of a tool to list available save slots, but essential workflows are supported.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      A MCP server enabling LLMs to roll dice
      1
      4
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      A comprehensive MCP server for managing AI-assisted Dungeons & Dragons campaigns, featuring tools for character sheets, combat tracking, and world-building. It enables players and DMs to interact with 5e game mechanics and query personal PDF rulebooks using RAG capabilities.
      64
      97
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server that enables AI agents to act as dynamic dungeon masters for text-based RPGs with dynamically generated rule systems and comprehensive game state management.
      8 npm
      14
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      An MCP server enabling AI agents to autonomously play D\&D as players and Dungeon Masters, with real dice rolls and full campaign management.
      3
      MIT