# Campaigns as data

The authored SOUL of a world — the cast, venues, factions, and laws of a
test town — lives under `campaigns/`, the way the economy already lives in
`goods/`, `buildings/`, and `professions/`. A campaign is INTERPRETED into a
live world by `crates/engine/src/campaign/` through the same constructors the
fixture (`crates/engine/src/seed.rs`) uses; the equivalence lock
proves the two build the SAME world, node-for-node and tick-for-tick.

```
data/campaigns/
└── <id>/                      one folder per campaign, id == folder name
    ├── campaign.ron           the manifest (Campaign)
    └── towns/
        └── <id>.ron           one authored settlement each (Town), id == stem
```

- **The manifest** (`campaign.ron`, `Campaign`): `name` (the realm-scale
 region), `default_seed`, the `death_day` and `start_day`/`start_hour`, the
 `towns` list, the `roads` between named nodes (route + travel days,
), the procedural `slots` (a `Worldgen` slot is what makes the
 realm the realm — an authored coast plus a generated vale), and
 the opening `chronicle`.
- **A slot's `road_from`** names the settlement at the road's other end, and
 has two forms because a realm's towns are not all named at once:
 `Town("Greyharbor")` for an authored town, or `Slot(0)` for an EARLIER
 slot's town — whose name is drawn from the realm seed at compose time and
 does not exist while the manifest is read. The index form is what lets a
 wide realm CHAIN its towns off each other instead of starring every road off
 the authored coast. It must point strictly backwards, and the
 validator refuses both a name no town bears and a forward reference.
- **A town** (`towns/<id>.ron`, `Town`): its `terrain` (the ground its
 venues' building types are permitted on), knowledge `keys`, `wards`,
 `civic_venues` and `waters`, `power` (the polity, its regent, the dead
 predecessor, the laws), `factions` (charter goal, confidence keys,
 hostilities, enforced laws, the seat-holder), `folk`, `knowledge` (gated
 events, spreading rumors, false beliefs), `goods` (coin, the data staples,
 the engine-seeded kits, the lock ladder), `production` venues with their
 owner-operators, free `items`, `residences`, and seeded `rosters`. Each
 trade's artisan kit is bound from its `data/professions/*.ron` `tool` field,
 not restated per campaign.

**Handles vs ids.** Within a town, goods and venues are named by short
`handle` strings (`"fish"`, `"forge"`) that the interpreter resolves to graph
nodes as it mints them; characters, wards, factions, and laws are referenced
by name/id. Ids that reach OUTSIDE the campaign — staple ids, building type
ids, terrain tags, profession handles — resolve against the loaded `Dataset`.

**The typing rule** is the tree's: enums for the semantic sets the engine
branches on (`Source`, `Ability`, `NeedRef`, `Demand`, `GoalKind`,
`Placement`, `SlotKind`, `FactionKind`), open handles for content. The one
deviation from "every field explicit": a campaign record is large (a
`Character` carries ~25 fields), so absent optionals default — the record
shows what is SET. Named struct forms (`Character(...)`, `Venue(...)`) are
used throughout, matching the goods/buildings style.

**Validation** is the cross-reference pass extended to campaign
files (`crates/engine/src/campaign/validate.rs` — the one validator,
): every town id names a file,
every venue's building type is real and PERMITTED by the town's ground
(`requires ⊆ terrain`), terrain tags/staples/professions resolve,
every good handle a produce/require/stock/carry names is minted by the
town. Every OTHER cross-reference resolves too — a road's endpoints, a ward's
adjacency, a law, a knowledge key, an org, a soul, a deity, an event and the
truth edge a belief denies, an item, a place — against what the campaign
itself mints, of the kind that field names (the interpreter resolves these by
name, and a miss used to panic it). Note the rule: a campaign may reference
only what a campaign MINTS, even though the interpreter would also find
engine-minted nodes (a good's display name, a profession, a furnishing).
A broken campaign fails at BOOT loudly with file and reason.

**Selecting a campaign.** `RPGPT_CAMPAIGN=<id>` interprets that campaign
(default: the fixture-equivalent `thornwood`). A modder ships a
`campaigns/<id>/` folder — a manifest plus town files — and the engine
interprets it; the overlay/ledger mod model applies unchanged.
