- Python 97.6%
- Shell 2.4%
The README had grown to 297 lines written before docs/ existed, so it
carried the log format, the seat wire protocol and the licensing
rationale that PLAYTEST.md, SESSION-LOG.md and LICENSES.md now own. It
also opened with a development-status paragraph and closed with a
roadmap table of five rows reading "done" — a changelog, not a README.
Restructured as a landing page, designer first then developer: what it
is, `init` and the nine-stage design loop (absent from the README
entirely, though docs/DESIGN-LOOP.md is the designer's whole story),
the replay demo, a compact how-it-works band, the six gates, then a
commands table and a documentation index that make the delegation work.
Three of the four demo outputs were wrong rather than merely stale, and
are now re-derived from the commands the README documents:
ROUNDS 3->2 claimed DIVERGED, is ILLEGAL at decision 9
TRADE_ORE_COST 3->1 claimed ILLEGAL, is OK — nobody traded
final scores claimed {p1: 8, p2: 3}, are {p1: 3, p2: 9}
The plain-DIVERGED case is now GATHER_ORE 2->3, a real state divergence
at decision 2. The TILE_POINTS 5->9 honesty beat is kept: verified
correct, no worker reaches `build` in that session. Every other figure
is re-derived too — 417 tests, six gates, 13 wiki entries, 64 files
from init.
The 40-line rulebook_check guarantee statement moves to
docs/ARTIFACTS.md, which previously ended by pointing back at the
README for it; that pointer is inverted and its gate list grows from
four to six. ARTIFACTS.md is vendored into scaffolded games, which
never ship examples/toy_game, so the moved text drops its toy-game
reference.
Verify: 417 tests OK; attribution, invariants, mechanics_check,
rulebook_check, lint, kb_lint all OK; verify-agent-config.sh valid.
Claude-Session: https://claude.ai/code/session_01CCzWbCN91q51DN92pXTuHw
|
||
|---|---|---|
| .claude | ||
| .codex/agents | ||
| docs | ||
| examples | ||
| foundry | ||
| kb | ||
| scripts | ||
| templates/game | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| FOUNDRY_VERSION | ||
| LICENSE | ||
| LICENSE.code | ||
| LICENSE.vendor | ||
| LICENSES.md | ||
| NOTICE.md | ||
| README.md | ||
Board Game Foundry
A precise instruction set plus a minimal runnable harness for designing board games with AI agents. It exists so that a playtest becomes a regression fixture instead of an anecdote: every session — played by a scripted policy, by a language model, or by a person at a terminal — is recorded, and any recorded session can be replayed against changed rules to answer one question honestly.
Would this rules change have altered that game?
Version 0.1.0. Python 3.11+ and a checkout; no installation, no dependencies.
Start a game
python3 -m foundry init ../my-game
cd ../my-game && git init && git add -A && python3 -m foundry gates
init writes 64 files: the vendored harness, docs and wiki, plus the
starter files you own — game/engine.py, game/config.py,
game/policies.py, mechanics/, rulebook/ and a passing test. All six
gates are green from the first commit, so the first failure you ever see is
one you caused. It never overwrites an existing file, and --adopt
fits it around a repository that already exists. Later, upgrade
re-vendors from a newer foundry and reports locally edited files instead of
discarding them.
Then the design loop — nine stages, each with an exit criterion rather than a feeling, ordered so the cheap checks run before the expensive ones:
| Stage | Done when |
|---|---|
| 1 Pitch | One paragraph names the tension, the player count, the length target |
| 2 Shortlist | kb_refs chosen with knob values recorded, or novel declared |
| 3 Core loop | One turn written end to end in mechanics/, playable on paper |
| 4 Numbers | Every tunable in numbers.md with a first value and a rationale |
| 5 First playable | foundry gates green against a real engine |
| 6 Simulate | sim, sweep, search, analyze clean |
| 7 LLM seats | A model given only rulebook/ plays a legal game to completion |
| 8 Human table | A recorded session with note lines; divergences reviewed |
| 9 Rulings | Every ambiguity raised has a dated RULING-### |
Stage 6 precedes stage 8 by rule: an evening at the table is the most
expensive probe the loop has, and it should never be spent discovering what
two hundred seeded sessions would have shown for free.
docs/DESIGN-LOOP.md gives each stage in commands.
Why a playtest becomes a fixture
python3 -m foundry play --seed 42 --seat p1=policy:first --seat p2=policy:builder
python3 -m foundry replay runs/42.jsonl
Every output below is real, from the bundled toy game at seed 42.
42.jsonl: OK 12 decisions, hashes match
Now change a rule in examples/toy_game/config.py and replay that same
recording. Raising TILE_POINTS from 5 to 9 still reports OK — and that
is the honesty the tool exists for, not a bug. Nobody placed a worker on
build in that session, so a change to what tiles are worth genuinely did
not alter it. Changes that did alter it are named precisely:
GATHER_ORE 2 -> 3
42.jsonl: DIVERGED decision 2 (p2): state differs from the recorded session
ROUNDS 3 -> 4
42.jsonl: DIVERGED the recorded session ended after decision 12 (p2) with
scores {'p1': 3, 'p2': 9}, but under the current rules
the game is not over
ROUNDS 3 -> 2
42.jsonl: ILLEGAL decision 9 (p1): {'kind': 'place', 'space': 'gather'} is
no longer a legal action
| Verdict | Meaning |
|---|---|
OK |
The rules still produce the recorded game. |
DIVERGED |
Your change altered this game. The report names the decision where it first differs. |
ILLEGAL |
A recorded action is no longer a legal move — the change removed an option someone actually used. |
replay exits non-zero on DIVERGED or ILLEGAL, so a corpus of recorded
sessions works as a CI gate. Divergence is a feature: it tells you exactly
which recorded sessions — including ones played by real people — your change
would have altered.
How it works
A game is any module exposing nine functions. Duck-typed, no base class to
inherit; examples/toy_game/engine.py is the executable reference.
initial_state(rng, setup) -> State legal_actions(state) -> [Action]
to_move(state) -> seat apply(state, action, rng) -> State
is_terminal(state) -> bool scores(state) -> {seat: int}
observation(state, seat) -> dict serialize(state) -> dict # canonical
render(state, seat) -> str
A seat is anything with choose(view) -> Decision. All three kinds share one
code path and produce the same log, which is what makes a human-played
session replayable at all:
--seat p1=policy:greedy # a deterministic function in the game's policies.py
--seat p2=human # a person at the terminal; 'note <text>' records a remark
--seat p3=cmd:./decider.py # any external process — this is how an LLM plays
One rule matters more than the rest: game code must never import random,
read the clock, or let unordered-set iteration reach state. The harness owns
the only RNG and passes it in explicitly. Violating this silently destroys
every session you have ever recorded, which is why the harness ships its own
splitmix64 generator rather than using random — a recorded session must
replay identically on any future interpreter.
The gates
mechanics/ is the source of truth — one file per system, every tunable in
numbers.md with a stable id and a reason. rulebook/ is derived from it and
never hand-edited. config.py transcribes the numbers by hand, citing each id.
Six committed gates keep them and the wiki from drifting apart:
python3 -m foundry gates
| Gate | Asserts |
|---|---|
attribution |
Every tracked file matches a licensing rule; every .py keeps its SPDX header |
invariants |
Properties that must hold at every state, over generated games |
mechanics_check |
numbers.md ↔ config.py is a bijection with equal values |
rulebook_check |
No numeral contradicts the id cited beside it, and no rule reaches the code without reaching the text a player reads |
lint |
Bare numbers, missing front matter, dangling rulings, any reach for random or the clock |
kb_lint |
Every wiki entry matches its schema, every kb_refs citation resolves, kb/INDEX.md is current |
What a green run does not prove is worth stating, because it is easy to read
more into a gate than it earned: these check that the three artifacts agree,
not that the sentence around a number is true. The exact boundary —
paragraph-scoped numerals, internal: tunables, the not-a-rule escape
hatch — is in docs/ARTIFACTS.md.
Commands
| Command | Does | More |
|---|---|---|
play |
Run one session and log it | PLAYTEST |
replay |
Verify recorded sessions: OK / DIVERGED / ILLEGAL | SESSION-LOG |
sim |
Run a batch and summarise | ANALYSIS |
sweep |
One batch per value of one tunable, patched in memory | ANALYSIS |
search |
Optimal-line search for one seat — proven optimal vs best-found | ANALYSIS |
analyze |
Run the pitfall detectors the game's mechanics cite | ANALYSIS |
gates |
Run the six committed quality gates | ARTIFACTS |
kb |
Knowledge base tools | KB |
init |
Scaffold a game repository from this foundry | UPGRADING |
upgrade |
Re-vendor a game repository from a newer foundry | UPGRADING |
The analysis commands share two rules: honest labels — proven optimal only
when the space was exhausted, a truncated search reports best-found with
its bounds — and traceable numbers, every figure naming the committed command
that produced it.
Documentation
| Document | Covers |
|---|---|
| DESIGN-LOOP | The nine stages and their exit criteria, in commands |
| ARTIFACTS | mechanics/, rulebook/, config.py and exactly what each gate asserts |
| PLAYTEST | The engine protocol, the three seat kinds, the determinism rules |
| SESSION-LOG | The log records, and the replay contract's edge cases |
| ANALYSIS | sim, sweep, search, analyze and the two rules they share |
| KB | The wiki's card schema, citations and derived index |
| UPGRADING | What init vendors and how upgrade reports conflicts |
| LOCALRECALL | The project-memory contract for coding agents |
The full design is in
docs/superpowers/specs/2026-08-26-boardgames-foundry-design.md.
Layout
FOUNDRY_VERSION the version init stamps and upgrade rewrites
foundry/ generic harness — never game-specific
rng.py splitmix64; the only source of randomness
hashing.py canonical JSON + sha256 state hashing
protocol.py the engine contract and its validator
seats.py policy, human and external-command seats
session.py JSONL log writer and reader
runner.py drives one session
replay.py OK / DIVERGED / ILLEGAL
cli.py the ten subcommands above
gates.py the six committed gates and the enforced license map
authoring.py numbers/rulebook/config parsers behind the authoring gates
invariants.py protocol-level properties over generated games
kb.py entry schema, derived index, kb_lint
analysis.py in-memory config patching, batches, traced sessions
search.py budgeted exhaustive search; proven optimal vs best-found
detectors.py the pitfall detectors the wiki's detect: fields contract
scaffold.py init: vendoring, stamping, --adopt
upgrade.py re-vendoring with hash-detected conflicts
templates/game/ the author-owned starter files init copies
examples/toy_game/ a small worker-placement game: the reference and the fixture
kb/ the wiki: 13 entries — mechanics, patterns, pitfalls, teardowns
docs/superpowers/ the design spec and the implementation plan
Development
python3 -m unittest discover -s tests -t . # 417 tests, under two seconds
python3 -m foundry gates --game-root examples/toy_game # all six committed gates
scripts/verify-agent-config.sh # the agent policy is well-formed
The foundry's own root has no mechanics/, which is why its gate run passes
--game-root; a scaffolded game repository runs foundry gates bare.
AGENTS.md is the runtime policy for coding agents working in this
repository; read it first. Its shape comes from
Agent Foundry, whose
validator is vendored as scripts/verify-agent-config.sh. That is static
validation only: it never contacts a memory service, so it proves the policy
is well-formed, not that the deployment behind it is ready.
Licensing
Two halves, because a single instrument fits neither. Prose — docs/, kb/,
README.md, AGENTS.md — under CC BY-NC-SA 4.0. Code — foundry/,
examples/, tests/ — under GPL-3.0-or-later. Four files vendored from
Agent Foundry keep their upstream MIT terms.
Two consequences stated plainly rather than buried: CC BY-NC-SA is not OSI open source — the non-commercial clause disqualifies it by definition, so what the prose half offers is source-available with share-alike — and the two licenses are mutually incompatible, so no file may be under both.
The map is enforced rather than merely documented: attribution fails when a
tracked file matches no rule. Map, reasoning and vendored files are in
LICENSES.md; the paste-ready attribution block is in
NOTICE.md.
© 2026 Claudio Maradonna