Skip to content

Repository layout

This page maps gorge's source tree for a Go developer who has just cloned it: what each top-level directory holds, which way imports are allowed to run, and where a given kind of change belongs. The per-package detail (every package with its purpose, size and importers) is generated from the source on Packages; this page is the guided tour.

Verified 2026-10-21 against develop @ 513a2a0c3: git ls-files, go list ./... and go run ./cmd/pkgmap (99 Go packages).

The tree at a glance

cmd/ product and developer binaries (18) gorged forgec mtgsim repro headdiff pkgmap cardfuzz enginebench ... server/ gorged host (+httpapi) protocol manabrew divergence spellbench bots/ registry botpolicy heuristic hosted bots and their search / nets engine/ the rules core: imports nothing outside engine/ cards state decision events effects rules deck view seat replay in the top row each package imports only those to its left; events.Apply is the only state mutator testkit/ cli/ test fixtures, game runner, flags; importable by other modules internal/ archtest, codeshape, tsgen, testbudget, corpusheap not Go packages web/Svelte client scripts/CI, smoke docs/this site, ADRs .githooks/ .github/CI, docs
Arrows point from the importer to the imported layer. The numbers and names are from go run ./cmd/pkgmap at 513a2a0c3.

Top-level paths

Path What it holds Go packages
engine/ The deterministic rules core: cards (the Forge-script compiler and card registry), state, decision, events (the event log and events.Apply), effects, rules (the engine, with sub-packages chars, combat, cost, pay, resolve, scriptfacts, trigmatch), view, seat (the Seat contract only), replay, deck. 19
bots/ The bot registry (bots, linked by bots/all), the one heuristic policy (botpolicy), the default bot (heuristic), the hosted bots (bot, lethalpressure, sbtactical, azredeal, forgeheuristic) and their inference runtime (azmcts, gonet, netsimd, policynet, searchprobe, searchseat, sanity, spellbench/*, traceboard, explain: the per-decision notes a match record carries). 22
server/ The reference server: gorged (importable: Main, Serve, the Extensions seam), host (tables, sessions, persistence) with host/httpapi (JSON + server-sent events) and host/manabrewhttp, protocol (the wire types the web client is generated from), manabrew (an alternate wire), divergence (human-vs-bot divergence records), matchrecord (the portable one-file record of a played game that gorged review serves), spellbench/* (the SpellBench v2 environment and agent adapters). 30
testkit/ Public test support: testutil (the corpus registry, repo decks, the acceptance decks, feedback snapshot loading), bench (a policy-agnostic head-to-head game runner), paymirror (an A/B check of payment plans). 4
cli/ The command-line layer: GNU-style flags, environment variables and JSON config files on the standard library. 1
internal/ Module-private ratchets and generators: archtest (import-graph and determinism tests), codeshape, corpusheap, testbudget, tsgen (Go → TypeScript). 5
cmd/ Binaries. Product: gorged, forgec. Developer: mtgsim, repro, headdiff, cardfuzz, enginebench, pkgmap, gentypes, codeshape, testbudget, gcgate, deckimport, keywordbench, paymirror, kcensus, spellbench-gorge-env, spellbench-gorge-agent. 18
web/ The TypeScript/Svelte browser client. web/src/protocol.ts is generated by make gentypes; never edit it by hand. Its own go.mod keeps Go tooling out of node_modules. –
scripts/ Contributor and CI scripts: ci_shard.sh, smoke.sh, enginebench-*.sh, docs_sets.py, open-pr.sh (make pr). –
docs/ site/ (this site), decisions/ (architecture decision records), design/ (living design documents), cli.md, and an internal guide for AI coding agents. –
.githooks/, .github/ The opt-in git hooks (git config core.hooksPath .githooks) and the CI, docs, coverage and promote workflows. –
AGENTS.md, CONTRIBUTING.md, README.md, Makefile, go.mod, mkdocs.yml, LICENSE, NOTICE Root files. go.mod has no require lines. –

Untracked paths you will meet locally:

Path What
.cards/ The Forge corpus (cardsfolder/, tokenscripts/, cards.lock) and the compiled IR cache (ir-<fingerprint>.gob.gz). GPL-3.0, gitignored. Without it, corpus tests skip.
bin/ make build outputs (forgec, mtgsim, gorged).
gorged-data/, feedback/ A local server's default persistence and player bug reports.
cmd/repro/testdata/.tokens/ Token-script text stripped from committed replay fixtures.

The research tree, draft events and the oracle pipelines are not here: see The three repositories.

Tiers and import direction

The classification file docs/site/architecture/packages.tiers.json assigns every package a tier and a status. go run ./cmd/pkgmap turns it and go list into the tables on Packages, and draws the tier graph from the real imports with any back-edge in red.

Tier Directories May import
Engine engine/ the standard library and other engine/ packages only
Bots bots/ engine/, other bots/ packages
Server server/ engine/, bots/, other server/ packages
Tooling internal/, testkit/, cli/ engine/, bots/, other tooling packages (measured)
Commands cmd/ anything; no package imports a command

Measured on 2026-10-21, every arrow in the tier graph points down this table except one: server/spellbench/internal/testcorpus (test support for the SpellBench adapter) imports testkit/testutil. Tests may import anything; these rules are about non-test code.

What is actually enforced is a list of edges that must never appear, checked directly and transitively by internal/archtest TestDependencyOrderHolds. The ones a contributor is most likely to hit:

Never Why
engine/effects → engine/rules effects reach the engine only through the small effects.Host interface, so the effect library stays testable on its own
engine/view → engine/rules, server/protocol → engine/rules a client-facing type can never be handed rules knowledge
engine/cards → engine/state, engine/deck → engine/rules the compiler and deck reader sit below the game
bots/botpolicy → engine/view, engine/rules, engine/seat the one policy reads a Board, never an engine or a view
any bots/ package → server/host or testkit/testutil bots plug into a host; they never depend on one
server/host, server/host/httpapi, server/gorged → testkit/testutil test scaffolding stays out of the server
server/protocol → bots the wire is engine-only

Inside engine/rules, the sub-packages (cost, chars, combat, trigmatch, pay, resolve, scriptfacts) have their own layering tests (TestCostVocabularyIsALeaf, TestCombatImportsStayBelowRules, TestTrigmatchImportsStayBelowL5, TestCharsImportsStayBelowRules, …), and each reads the engine through a narrow Board interface whose method count only shrinks. A new package also needs a tier entry, or go run ./cmd/pkgmap -check (the pre-commit hook, and TestClassificationIsComplete) fails.

Where does my change go?

I want to… Go to
Fix how a card plays Start with Your first card fix.
Implement an api: effect engine/effects/: effects.Register("Name", fn) in an init(). If it needs more than effects.Host offers, it is rules work.
Implement a keyword, trigger, static or replacement engine/rules/, declared with effects.RegisterNonAPI("kw:X", …). A trigger mode's matcher goes in engine/rules/trigmatch/. A keyword that rewrites into ordinary script lines is an engine/cards/kw_*.go expander (registerKeyword) instead.
Change a characteristic rule (CR 613 layers) engine/rules/chars/.
Change attack or block legality engine/rules/combat/; the declaration flow stays in engine/rules/combat.go.
Add a count: value head The evaluator in engine/effects/count*.go and modelledValueHeads in engine/effects/value_heads.go (TestValueHeadRegistryMatchesEvaluator holds them together).
Change how scripts parse engine/cards/. Any edit there changes cards.CompilerFingerprint, so every IR cache recompiles (make compile-cards). engine/cards/oracletext is exempt.
Add an event kind Append after the last constant in engine/events/event.go, add its entry to kindInfo in engine/events/kindinfo.go, and put the state change in events.Apply (engine/events/apply.go). See Invariants.
Add a decision kind Don't, without a maintainer decision: the set is closed. See Invariants.
Change what a seat sees engine/view/: card.go (cards, battlefield), stack.go (stack); view.go only for the assembler.
Change the wire server/protocol/, then make gentypes (regenerates web/src/protocol.ts).
Change server routes or seat authorisation server/host/httpapi/; every seat route goes through claimForTable (rest.go).
Change server flags or startup server/gorged/ (cmd/gorged is a 14-line main).
Change the default bot's choices bots/botpolicy/ (the policy), bots/heuristic/ (its seat adapter).
Add a bot bots.Register in an init() (bots/registry.go), blank-imported from bots/all. Read the bots section first.
Add a repo deck testkit/testutil/decks/*.json via go run ./cmd/deckimport; every card in it must be fully supported.
Turn a feedback report into a test go run ./cmd/repro -emit-test <pkg> <feedback-dir> (see Contributing).
Add a package Wherever it fits the tiers, plus an entry in packages.tiers.json in the same commit.
Change the web client web/ (needs Node; see web/README.md).
Change this site docs/site/, built by mkdocs.yml; see Contributing.

Behaviour that looks like a bug but is deliberate is recorded in docs/design/2026-09-22-engine-contracts.md; read it before filing.