Campaigns as data
The content tree — the contract for the
data/ tree.
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, thedeath_dayandstart_day/start_hour, thetownslist, theroadsbetween named nodes (route + travel days, ), the proceduralslots(aWorldgenslot is what makes the realm the realm — an authored coast plus a generated vale), and the openingchronicle. - A slot’s
road_fromnames 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, orSlot(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): itsterrain(the ground its venues’ building types are permitted on), knowledgekeys,wards,civic_venuesandwaters,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),productionvenues with their owner-operators, freeitems,residences, and seededrosters. Each trade’s artisan kit is bound from itsdata/professions/*.rontoolfield, 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.