# data/ — game data and its organizational contract

Machine-readable game data for the adventure layer: the SRD
5.1 and SRD 5.2.1 material the engine adopts — skills, races, arms, the CR→XP table,
and the humanoid NPC archetypes — plus our own generation tables
(`names/`). This README is the contract for the TREE: layout rules, the
derivation formulas, the transcribed-versus-interpreted boundary, and the
identity model. The SHAPES the files parse into are not here — they are
generated from the engine's own structs into the
[modding reference](https://kinbarrow.com/modding/latest/), which cannot
disagree with the loader.

## Layout rules

```
data/
├── README.md                      this contract
├── tables/                        one registry per file
│   ├── skills.srd.ron             Table<SkillDef>       (18)
│   ├── weapons.srd52.ron          Table<Weapon>         (38)
│   ├── weapon_masteries.srd52.ron Table<WeaponMasteryAssignment> (36)
│   ├── armor.srd52.ron            Table<Armor>          (12 + shield)
│   ├── cr_xp.srd52.ron            Table<CrXp>           (34)
│   ├── encounter_xp_budgets.srd52.ron Table<EncounterXpBudget> (20)
│   ├── advancement.srd.ron        Table<AdvancementRow> (20)
│   ├── multiclass_spell_slots.srd.ron Table<SpellSlotRow> (20, SRD Multiclass Spellcaster table)
│   ├── damage_kinds.srd.ron       Table<VocabRow>       (13 — open vocabulary)
│   ├── schools.srd.ron            Table<VocabRow>       (8 — open vocabulary)
│   ├── creature_types.srd.ron     Table<VocabRow>       (14 — open vocabulary)
│   ├── languages.srd.ron          Table<VocabRow>       (16 — open vocabulary)
│   ├── quality_profiles.ron       Table<QualityProfile> (5 classes × 7 tiers, ours)
│   ├── alignment.ron              AlignmentRules        (moral motive weights, ours)
│   ├── material_classes.ron       Table<MaterialClass>  (5, ours — [](../docs/adr/0070-vocabularies-are-registries.md))
│   └── terrain_tags.ron           Table<TerrainTag>     (7, ours — [](../docs/adr/0070-vocabularies-are-registries.md))
├── professions/                   ours — one profession per file (17; the
│   └── {fisher,brewer,baker,farmer,miller,tanner,smith,mason,merchant,innkeeper,
│       harbor_clerk,fixer,noble,guildmaster,captain,priest,sage}.ron — canonical
│       set mirrors crates/engine/src/professions.rs
├── buildings/                     ours — one building TYPE per file (21)
│   └── {smithy,tannery,carpentry,masonry,weaver,ovens,mill,mash_house,tavern,
│       fields,dock,mine,lumber_camp,pasture,manor,guildhall,watch_house,house,
│       town_wall,shrine,tower}.ron
├── races/                         one race per file, subraces inside as variants
│   └── {dwarf,elf,halfling,human,dragonborn,gnome,half_elf,half_orc,tiefling}.srd.ron
├── classes/                       one class per file, the SRD subclass inside
│   └── {fighter,rogue,cleric,barbarian,monk,paladin,ranger,bard,druid,sorcerer,warlock,wizard}.srd52.ron  (12)
├── feats/                         OPEN collection: one feat per file
│   ├── grappler.srd.ron           SRD 5.1's ONE feat (prereq-only; riders ledgered)
│   └── {alert,savage_attacker,boon_of_truesight}.srd52.ron  the SRD 5.2.1 feats
│                                   with engine substrate ; the rest is a mod surface
├── spells/                        OPEN collection: one spell per file
│   └── {fire_bolt,magic_missile,fireball,cure_wounds,…}.srd*.ron (83: 79 SRD 5.1,
│                                   4 SRD 5.2.1),
│                                   every one scoped to the class list the book
│                                   gives it — ; ALL of them, since the
│                                   one non-SRD file left for the private
│                                   book-mod tree at . No shipped spell
│                                   pulls: Effect::Pull's lock builds its own)
├── magic_items/                   OPEN collection: one magic item per file
│   └── {plus_one_longsword,plus_one_chain_shirt,ring_of_protection,
│       cloak_of_protection,wand_of_magic_missiles}.srd.ron  (5: the SRD starter set)
│       ring_of_resistance_{acid,…,thunder}.srd.ron  (10: the SRD 5.1 Ring of Resistance gem table, one row per line)
├── conditions/                    OPEN collection: one condition per file
│   └── {blinded,charmed,deafened,frightened,grappled,…}.srd52.ron  (14)
│                                   unconditional ConditionComponent reads plus
│                                   contextual ConditionFact entries
│       {ability_guided_weapon,innately_sorcerous,raging,reckless,
│        walking_speed_flight}.ron  (5 Original class-helper conditions)
├── traps/                          OPEN collection: one trap per file
│   └── {poison_needle,poison_darts,fire_breathing_statue}.srd.ron  (3: the SRD
│                                   "Traps" device samples; the container trap is
│                                   the poison needle — the rest a mod surface)
├── diseases/                       OPEN collection: one disease per file
│   └── {sewer_plague,sight_rot,cackle_fever}.srd.ron  (3: the SRD "Diseases"
│                                   sample diseases — a contraction save, an
│                                   incubation, an effect (exhaustion/condition/
│                                   check penalty), and a long-rest recovery save
│                                   track; the rest a mod surface)
├── creatures/                     OPEN collection: one entity per file,
│   ├── humanoids/                 in stable taxonomic folders
│   │   └── {commoner,guard,bandit}.srd.ron
│   ├── beasts/wolf ; undead/{skeleton,zombie} ; dragons/black_dragon_wyrmling
│   ├── fiends/dretch ; elementals/magma_mephit ; giants/ogre ; oozes/gray_ooze
│   ├── constructs/animated_armor ; monstrosities/worg ; fey/satyr
│   └── celestials/pegasus ; aberrations/gibbering_mouther ; plants/awakened_shrub
│                                   (the  bestiary — one per type)
├── goods/                         ours (unsuffixed) — one material/tool per file
│   └── iron_ore, iron_ingot, coal, timber, hide, leather, cotton, cloth,
│       sinew, bedroll, bed, smiths_tools, leatherworkers_tools,
│       carpenters_tools, weavers_tools  (.ron, source: Original)
├── expected/
│   └── npc_blocks.srd.ron         verification corpus (see Derivations)
├── art/                           runtime pictures a row names by path (see below)
│   └── furniture/                 one per furnishing good's `art:` field
└── names/                         ours (unsuffixed) — per-race name tables
```

- **Art is named by path, and a mod layers it.** A furnishing good's `art:`
 names a picture inside `art/`, e.g. `"furniture/trestle-table.webp"`. It is
 drawn from above facing north (back edge at the top, `width_cells` across,
 `length_cells` down, 128 px per cell) and the board rotates it for the other
 facings. A mod's own `art/` is searched before this one, so a mod replaces a
 picture by shipping the same path or adds one under a new path. A path that
 resolves nowhere fails the load. Every picture carries a provenance header
 that says whether it is an AI placeholder or who made it and under what
 licence; stamp one with `cargo xtask art stamp`
 (
).

- **Registries vs. entities.** Compact row collections (the SRD's
 tables) live as ONE registry file per set under `tables/`. Collections
 that will someday hold tens of thousands of entries (creatures, and
 later items, spells,...) grow one-file-per-entity inside stable
 taxonomic folders — `creatures/humanoids/` plus the thirteen monster
 folders (`beasts/`, `undead/`, `dragons/`, `fiends/`, `elementals/`,
 `giants/`, `oozes/`, `constructs/`, `monstrosities/`, `fey/`,
 `celestials/`, `aberrations/`, `plants/`) the bestiary filled,
 one exemplar per non-humanoid type. `magic_items/` is the
 README's own "later items" made real — an entity collection, not a
 `tables/` registry, because the SRD's Magic Items chapter grows to
 thousands and a mod adds one as a file. Both kinds are OPEN:
 mods add weapons, armor, races, and creatures (the
 moddable-data amendment — data loads at boot).
 A creature's optional `doors` field is a closed set of `Encounter`, `Battle`,
 `Form`, and `Summon`; when absent it defaults to all four. Author at least one
 distinct door to narrow which builders and catalogues may enumerate the body.
 A swarm (any creature with `swarm_member_size`) never opens `Form`, whatever
 its list says: a form takes one body, and a swarm is many.
- **Suffix + source provenance.** A provenance suffix marks files whose
 content derives from an SRD, and names WHICH one: `.srd.ron` for SRD 5.1,
 `.srd52.ron` for SRD 5.2.1.
 Every file's top-level value also carries a required `source` field
 (`Srd51`, `Srd52`, or `Original` in the base), and the two must agree — `data_lock`'s
 `the_suffix_and_the_source_field_agree` fails the build otherwise, so the
 path can be trusted without opening the file. Book mods use their matching
 `Phb2024`, `Dmg2024`, `Mm2024`, `Tce`, or `Xge` source; the loader refuses a
 missing source, a book source in base data, or a book/namespace mismatch.
 That is what makes
 `*.srd*.ron` auditable against the repository's `ATTRIBUTION.md`, which
 states the two grants separately. Files without a suffix (`names/`,
 `goods/`) are ours.
- **id equals filename.** For one-entity-per-file collections, the file
 stem IS the entity's `id`: `half_elf.srd.ron` ↔ `id: "half_elf"`,
 `guard.srd.ron` ↔ `id: "guard"`. The future parse-everything lock test
 asserts this. Ids are snake_case throughout — chosen over kebab
 because the pre-existing `names/` files (`half_orc.ron`) already use
 it, and the race key contract (below) shares filename ids with them.
- **No organization by tunables.** Directories follow stable taxonomy
 ONLY — never CR, level, cost, or any other property that balancing may
 move. By-CR (or any) access is a load-time index. This is
 derive-don't-store applied to layout.

## Format: RON

RON maps 1:1 onto serde-derived Rust structs: real enums for closed
sets, `Option` for the book's "—" cells, nested structs natively, and
`//` comments beside every interpreted value. The engine lock-test
parses every file into the canonical types below when the validator amendment lands.
Transcribe the sketch — don't invent.

**The typing rule — enums for semantics, string ids for content**
(the moddability doctrine, per the moddable-data amendment):

- **Compiled enums** ONLY for semantic sets the engine's code branches
 on: `Ability`, `Size`, `ArmorCategory`,
 `WeaponCategory`, `Skill`, `AlignmentConstraint`,
 `CraftTier`, `QualityTier`, `Rarity`, `MagicRarity`, `Recharge`,
 `ConditionComponent`, `ConditionFact` (and the
 structural shapes inside rows — `WeaponProperty`, `DexMod`,
 `ArmorClass`, `Damage`, `RecipeInput`, `Source`). Damage kinds and
 spell schools OPENED into registry files (`tables/damage_kinds.srd.ron`,
 `tables/schools.srd.ron`); creature types OPENED at
 (`tables/creature_types.srd.ron`) — the engine branches only on well-known
 ids (`types::damage`/`types::school`/`types::creature_type`). These are
 rules vocabulary; a mod adds a row without engine code, and only a NEW
 code branch (a new well-known const) is a recompile — the honest boundary.
 `CraftTier` IS the engine's `TradeTier` (: Apprentice /
 Journeyman / Master, prof bonus +0/+2/+4) — one ladder, two homes.
- **Open string ids** for content identifiers — weapon ids, armor ids,
 race ids, creature/archetype ids, CR keys — because mods add content:
 a new weapon must be a data row, not a recompile. Typo-safety is
 preserved by CROSS-REFERENCE RESOLUTION at parse/boot time: every id
 reference (`armor: ["chain_shirt", "shield"]`, `weapons: ["spear"]`,
 `cr: "1/8"`) must resolve against the loaded registries, failing
 LOUDLY with the file and reason — the same guarantee an enum gives,
 delivered at load instead of at compile (in the spirit of the no-magic-strings rule: no
 silently-missed lookups, ever).
- **Open VOCABULARIES get registry files**: when a set of
 tags is open but referenced across files — material classes,
 terrain tags — the vocabulary itself is a `tables/` registry, and
 every use (`MaterialInfo.class`, `Class(...)` inputs, buildings'
 `requires`) resolves against it at boot. Not enums, by the same
 rule: the engine never branches on a family name — family-level
 behavior, when it arrives, lands as PROPERTIES on the registry rows.

## Source

- **Document:** System Reference Document 5.1 ("SRD 5.1") by Wizards of
 the Coast LLC — the official CC-BY-4.0 PDF, `SRD_CC_v5.1.pdf`.
- **Canonical page:** https://www.dndbeyond.com/srd (also linked from
 https://dnd.wizards.com/resources/systems-reference-document)
- **PDF fetched:** https://media.wizards.com/2023/downloads/dnd/SRD_CC_v5.1.pdf
- **License:** Creative Commons Attribution 4.0 International
 (CC-BY-4.0). See the license statement below and `ATTRIBUTION.md`.
- **Extraction date:** 2026-07-19, transcribed from the official PDF's
 own text (pdftotext). Sections used: Races pp. 3-7; Equipment
 pp. 63-66; Using Ability Scores pp. 77-78; Monsters (statistics rules:
 Hit Dice by Size, Proficiency Bonus by Challenge Rating, Experience
 Points by Challenge Rating, skill/save bonus formulas) pp. 254-257;
 Appendix MM-B: Nonplayer Characters pp. 395-403.
- **Cross-check:** spot values (longsword, plate armor, the guard block,
 half-orc traits) matched the community 5e-bits database
 (dnd5eapi.co). The authoritative source is the official SRD document.

### The 5.2.1 chassis and retained 5.1 content

SRD 5.2.1 (the 2024 revision) is licensed under the SAME CC-BY-4.0 grant as
5.1, so the choice between them is a DESIGN one, never a licensing one, and
the corpus may draw on both.

**5.2.1 is the rules chassis**.
Where both SRDs print a rule or record, the 5.2.1 definition wins. Nonconflicting
5.1 catalogue material retains its own provenance. A `*.srd52.ron` file
transcribes 5.2.1 and says so in its `source`; no active id is defined twice.

The SRD 5.2.1 encounter tables are `tables/encounter_xp_budgets.srd52.ron`
(level 1–20, Low/Moderate/High XP per character) and
`tables/cr_xp.srd52.ron` (XP by CR). Battle sums the selected difficulty's
budget for **each** party member, including mixed levels, and spends that
total on creature XP without a creature-count multiplier. Its strength dial
selects Low at −1, Moderate at 0, and High at +1; −2 halves Low and +2
doubles High as game extensions. The dungeon's occupancy-rated chamber
allotments remain party blind and use the same CR→XP price table.

## Types

**The authoritative shapes live in the [modding reference][ref], not here.**
It is GENERATED from the same Rust structs the game deserialises, so it cannot
disagree with the loader; a sketch maintained by hand can, and did. This section
used to carry 650 lines of that sketch. By the time it was published it was
already describing a `Building.produces: Vec<Produce>` field that no longer
exists — which is exactly the drift the generated reference exists to make
impossible.

[ref]: https://kinbarrow.com/modding/latest/

So: for the shape of any file under `data/` — its fields, their types, which are
required, what each one means, and the legal ids a reference field may take —
read the reference. Every page there is also available as markdown by appending
`.md`, which is the form to hand an agent.

What is NOT in the reference, because it is a property of the TREE rather than
of any one type, stays here:

```rust
// Every file's top-level value is a `Table<T>`, and carries its provenance.
enum Source { Srd51, Srd52, Original, Phb2024, Dmg2024, Mm2024, Tce, Xge }
struct Table<T> { source: Source, entries: Vec<T> }
```

Two conventions the reference cannot state, because they are about the FILES
rather than the shapes:

- **Every field is written explicitly** in the data — `None` and `[]` included.
 These are plain `#[derive(Deserialize)]` types with no serde attributes and no
 RON extensions, so a missing field fails the parse rather than defaulting. The
 documented exceptions are `quality_overrides` and the handful of fields the
 reference marks with a default.
- **`DexMod::None` and `Option`'s `None` both appear** in `armor.srd52.ron`; serde
 disambiguates them by the expected type. They are different things that happen
 to share a spelling.

The sections below are the rules that govern the tree as a whole: how values
DERIVE rather than being stored, what is transcribed from the SRD versus
interpreted, and how identity works.

## Derivations

**Rationale (the designer's):** derivation creates systems, systems
create emergence — a guard without his shield is AC 14 with no special
case, and the book's printed blocks become the proof of faithfulness
rather than the data. The archetype files store primitives ONLY; the
engine derives everything below, and a lock test must reproduce every
printed value in `expected/npc_blocks.srd.ron` from them.

With `mod(s) = floor((s - 10) / 2)` (the SRD ability-modifier formula):

1. **Hit dice count** = `level`. The stat blocks and the adventure ladder
 are ONE system: future leveling grows HP and PB with
 zero new rules.
2. **Hit die** = f(size), the SRD's Hit Dice by Size table (p. 255):
 Tiny d4, Small d6, **Medium d8**, Large d10, Huge d12, Gargantuan
 d20. Size comes from the composed race (below).
3. **HP** = roll `level × d(hit die) + con_mod × level`; the printed
 AVERAGE convention is `floor(level × avg(die)) + con_mod × level`
 with `avg(dN) = N/2 + 0.5` (the SRD's own "2d8 → 9 (2 × 4½)" and
 "Constitution modifier... multiplied by the number of Hit Dice",
 p. 255).
4. **Proficiency bonus** = f(level), exactly the proficiency ladder:
 `2 + floor((level - 1) / 4)` — 2 at levels 1-4. (For monsters
 statted by CR, the SRD's Proficiency Bonus by Challenge Rating table
 p. 256 gives the same +2 at CR 0-4 through +9 at CR 29-30.)
5. **AC** = `10 + dex_mod` unarmored; else the armor table's formula —
 `Base(base, dex)` with dex Full/Max2/None — plus `Bonus(2)` if a
 shield is carried.
6. **Attack to-hit** = `pb + mod(ability)` where ability is Str for a
 melee-category weapon, Dex for a ranged-category weapon, and the
 better of the two if the weapon has Finesse. Thrown melee weapons
 use the melee rule.
7. **Damage** = weapon damage dice + the same ability modifier; the
 printed average is `floor(avg(dice) + mod)`; a zero modifier prints
 bare dice.
8. **Skill bonus** = `mod(governing ability)` (from
 `tables/skills.srd.ron`) `+ pb` if the skill is in the proficiency
 set (SRD p. 256).
9. **Passive Perception** = `10 + Perception bonus`. Never stored.
10. **Speed** comes from the composed race (races/*.srd.ron) — the
 printed 30 ft. assumes human; a dwarf guard walks at 25.
11. **XP** = `tables/cr_xp.srd52.ron[cr]` — that mapping is SRD data, not
 a formula. CR 0 prints "0 or 10": 0 without effective attacks, 10
 with.

**Stored exceptions (the rule that proves the derivations):** monsters
 store NATURAL armor, their own size, and their own speed — they
are raceless, so there is nothing to derive from. For humanoids the
derivation is the system; natural armor is the exception, stored where
the SRD prints it. Everything else still derives: a monster's to-hit and
damage come from its natural-attack dice + the named ability's mod +
`pb_for_cr(cr)` (the CR proficiency-bonus table, not the level ladder —
), its AC from `natural_armor + Dex`, its HP from level × the
size's hit die + Con, and its XP from `cr_xp[cr]` — every value verified
against the SRD's printed block in `expected/npc_blocks.srd.ron`.

**Licensing boundary — why CR is stored:** deriving CR from stats uses
the DMG's monster-building table, which is NOT part of the SRD/CC-BY-4.0
corpus. CR therefore stays a stored primitive whose only job is to feed
`xp(cr)` (and future encounter math), never a derivation target.

## Crafting: goods and embedded recipes (ORIGINAL content)

The SRD prices finished arms but has NO material recipes — its downtime
crafting is gp-rates only. Everything in this section is therefore OURS
(the `alignment_bias` precedent: original additions living inside or
beside `.srd.ron` files, explicitly marked): the `goods/` collection,
every `recipes:` list, and all quantities, tiers, and hours.

- **The goods collection** (`goods/<id>.ron`, `source: Original`, one
 file per material): a starter set of 12 sufficient to give every SRD
 weapon and armor a plausible recipe — iron_ore, coal, iron_ingot,
 timber, hide, leather, cotton, cloth, sinew, and the four artisan's
 tools. Raw naturals (ore, coal, timber, hide, sinew, cotton) have
 no recipes — they are gathered or traded; intermediates (iron_ingot
 from ore+coal at the smithy, leather from hide at the tannery, cloth
 woven from cotton at the loom) carry
 theirs. The tools' WEIGHTS are aligned with the SRD equipment table's
 artisan's-tools rows (noted per file); their VALUES, and every other
 good's, now DERIVE (/0396): worth = material cost ÷ `yield` +
 `labour_rate(tier)` ÷ `rate`, and no file states a price. **Weapons and
 armour are the same rule since §5**: the book's price column is
 no longer transcribed at all. It never reached a price — only a
 materials-≤-price sanity bound, which the derivation makes
 unrepresentable (labour is non-negative, so `worth ≥ materials` holds by
 construction on any `yield: 1` row) — and while it sat in the file it was
 a second, quieter authority over the same quantity. What the SRD's plate
 price actually recorded, a brand/scarcity premium some 300× the raw
 craft cost, belongs to the market layer, and the market layer is where
 it now lives. The `bedroll`
 is the first TERMINAL consumer good — a carpentry product
 off the timber and cloth chains, `quality_class: "good"` marking it a
 fitted item (used, not crafted onward), so the orphan check reads it as
 a legitimate sink; its weight aligns with the SRD's bedroll row and its
 value derives like every crafted good's.
- **Recipes are EMBEDDED in item definitions** — `recipes:` on Weapon,
 Armor, and Good. **Craftable is DERIVED from having recipes, never a
 flag.** `Recipe.station` ids resolve at boot against the union of the
 building types' `stations` lists (today: smithy, tannery, carpentry,
 weaver, ovens, mash_house); `tier` is the ladder; `tool`
 references a good id (`None` = bare hands — a club is whittled).
 Constructive inputs are CLASS-GENERIC (`Class("metal")` — any
 material of the class satisfies; a mithral dagger needs no dagger
 variant); process inputs stay specific ids (`Good("coal")` — iron
 ingots come from iron ore, not "any metal").
- **The strike table (the rare find in the bulk):** an
 EXTRACTION recipe (one with no `inputs`) may carry
 `strike: Some(Strike(good: "<id>", odds: <0..1>))`. Each unit the face
 earns then rolls a SECOND die for its IDENTITY: the rare good at `odds`,
 the bulk good otherwise. It splits identity, NEVER quantity — bulk +
 rare equals exactly what the face would have minted without it, so a
 strike never mints or loses a unit. The mountain mine's `iron_ore` row
 strikes `mithral_ore` at 0.08; its `stone` row strikes `iron_ore` at
 0.15. An unknown good or a non-probability `odds` fails boot.
- **Richer ground, richer vein ( §5):** a `Strike` may carry
 `mods: [(tag, multiplier)]` — the `terrain_mods` rule applied to the
 odds. The effective chance at a venue is `odds × Π(multiplier for each
 town terrain tag present)`, clamped to a probability. One row can
 therefore run a fat seam in one town and a thin one in the next without
 a second recipe or a hand-wired deposit: the ground decides. Tags
 resolve against `tables/terrain_tags.ron`; a negative or non-finite
 multiplier fails boot.
- **Boot feeds these into the EXISTING production machinery**:
 recipes become graph structure, worldgen assigns the venues, and
 there is NO parallel crafting system. The make-check quality rule
 generalizes here later (tier + check → quality); material `weight_lb`
 feeds the arbitrage cart loads.
- **Batch yield (the ammo fix, landed):** a `Recipe` carries a
 `yield` (units one run makes) and the derivation divides by it, so
 ammo-scale goods price sanely instead of costing more per unit than the
 ingot their head is cut from — the ledgered fix, not a price fudge. The
 first batch goods are `arrow` and `bolt` (20 to a fletcher's run at
 carpentry: 27 cp material + 3 h labour ÷ 20 = 1 cp/unit). They are the
 honest ledgered SEAM — craftable and grounded, but nothing CONSUMES them
 yet (the orphan warning stands until combat spends ammunition in a later
 slice). Every prior recipe is `yield: 1`, explicit (no serde default).

## Worth is derived — anchors, not prices

No number in this tree is a market price. Runtime worth is MARKET
STATE — venue prices move with supply, demand, and distance; the arbitrage of it
exists because they do. What the data actually carries:

- **THE ANCHOR IS DERIVED, and no file states it**. One
 formula for every good the data can make, raw or crafted:

 > worth = Σ(input worths × qty) ÷ `yield` + `labour_rate(tier)` ÷ `rate`

 `rate` is units per WORKED HOUR, so `1/rate` is the hours one unit
 takes — at the face and at the bench alike. A recipe with no inputs
 (a raw extraction) reduces to the labour alone. A `Class`
 input resolves to its cheapest member; `yield` divides the
 MATERIALS only (1 for a single-unit craft, 20 for an ammo
 batch); a good with two recipes costs the CHEAPER; and a good the data
 wins only as another row's STRIKE (mithral ore) costs the
 host row's labour ÷ its odds. **`hours` prices nothing** — it is
 's batch commit window.
- **The labour rate is the numeraire** — `data::labor_rate_cp`,
 `1 + prof_bonus/2` → **Apprentice 1, Journeyman 2, Master 3 cp/hour**
 (tied to the SRD proficiency bonus). Those three
 scalars are now the ONLY free numbers in the price system. The
 closure it buys is locked over the corpus: an hour on any recipe row
 pays exactly its tier's rate. (Material and quality factors still
 multiply at CRAFT time, on the constructed identity; the anchor is the
 mundane-iron, Normal-quality base.)
- **NO FILE STATES A PRICE, and every good must be MAKEABLE**
 ( §5). There is no
 `declared_value_cp` and no SRD `cost_cp` any more: a good — and an arm —
 is worth what its recipe costs to run, and a second stored number
 describing the same quantity would be a quieter authority over it. So a
 good the data cannot make would be worth NOTHING, and the loader REFUSES
 it rather than pricing it at zero: give it a recipe, or a bench that wins
 it as a strike. That refusal binds mods too. (`acquisition:` survives as
 narration — a mod may still say where a thing comes from — but it no
 longer buys an exemption from the maker rule, and the shipped corpus
 carries none.)
- **`wage_hours` is the same doctrine for people** — a seed the engine
 modulates through tier wages, council pay decisions, and
 poaching. Nothing pays the file's number forever. It states HOURS, not
 coin ( §6): a post's pay is a share of the day, priced at the
 journeyman labour rate when it is read, so it follows the rate instead
 of standing where an old price level left it.

## Professions and buildings (ours)

- **`professions/`** mirrors the CANONICAL engine set
 (`crates/engine/src/professions.rs`: professions are graph
 data) — twelve files, same abilities, wage seeds (`wage_hours`,
 /0387 §6; only the waged four, in hours of the day:
 harbormaster 3, fixer 2, guildmaster 4, watch captain 4), and
 display names ("Harbormaster", "Watch Captain"). Each adds two
 slice-two facts: the trade `tool` as a good id (the
 PRACTICES key: fisher's tackle, brewer's supplies, baker's cook's
 utensils, tanner's and smith's kits; the desk-and-title professions
 carry none), and `proficiencies` — 2-3 background skills, THE 0066-a
 GENERATION SOURCE: a career is where a soul's skill set comes from
 (fisher → Survival + Athletics; watch captain → Perception +
 Intimidation + Athletics, the career-guard exemplar;
 innkeeper → Insight + Persuasion). The picks are ours, noted per
 file.
- **`buildings/`** holds building TYPES for worldgen to instantiate —
 never instances. `produces` is the PRODUCES relationship as
 data (rate = units per worked hour, before the work
 multiplier; raw extraction names no trade tool). `stations` closes
 the recipe-station references. `build_cost` is the construction
 composition — derives a building's maximum integrity from it
 (Σ qty × weight × class integrity_per_lb × material integrity_factor),
 and building repair re-consumes a damage-proportional fraction of it.
 Lumber camp and pasture have no canonical profession — open-access
 labor until the trades exist.

### Geography grounds the buildings

**Terrain tags** are open ids like material classes, resolving against
`tables/terrain_tags.ron` — the vocabulary today:
`coastal`, `river`, `arable`, `hills`, `mountains`, `forest`,
`ore_deposit`. Each building type states where it can exist via
`requires` — a CONJUNCTION (all tags must be present; `[]` = anywhere).
The map as landed: dock [coastal]; fields [arable]; pasture [arable];
mine [ore_deposit]; lumber_camp [forest]; tannery [river]; everything
else [] — judgment noted per file. Or-semantics (a river-port dock,
hill pasture) is a named seam: no speculative `requires_any` until a
consumer needs it.

- **Settlements/regions will carry terrain tags as worldgen data** —
 the engine consumes them in the worldgen slices; this contract
 defines the data, not the consumer. Building placement validity
 DERIVES from settlement terrain, and production profiles thereby
 derive from geography instead of being authored — the
 two-town divergence generalized: towns differ because their ground
 does, not because someone wrote two economies.
- **Deposits**: the `ore_deposit` tag is where the material
 rarity/region hints from the materials work ground out — a region's
 deposits determine which ores its mines can produce (the mine type
 lists mithral_ore; worldgen instantiates that row only where the
 region says so). Region-side data arrives later; the seam is named
 now.
- **Constructibility footnote**: "raw" ultimately means
 geography-PERMITTED production. A good producible only by buildings
 whose terrain no known region provides is a WARNING tier, not an
 error — regions are data-incomplete until worldgen wiring, so the
 validator warns on geography-gated raws rather than failing them.

## Quality: the seven tiers

`QualityTier` (semantic — code branches): Trash, Poor, Normal, Good,
Excellent, Masterwork, Legendary. `tables/quality_profiles.ron` carries
the default per-tier effects PER ITEM CLASS (weapon, armor, tool, lock,
good): `value_factor` for every class; attack/damage deltas for
weapons; AC delta for armor; work-multiplier for tools (the
hook); DC delta for locks.

- **The lock ladder **: DCs run 1 / 6 / 10 / 14 / 18 / 20 /
 24 (Trash..Legendary) — a trash lock is a lock in name only; DC 30
 remains enchantment's alone (the 0058 designer ruling stands).
 The lock grades collapse in by DC PROXIMITY, not ordinal: Simple =
 Normal (10, exact), Sturdy = Good (15→14), Fine = Masterwork (20,
 exact) — a fine lock took a master smith in 0058, so Masterwork is
 its honest tier, and migrated fine locks REIFY with provenance.
 Burglary calibration is conserved (largest shift: 1 DC).
- **Profile mapping **: weapons and armor take their
 profiles STRUCTURALLY (their own tables); a good referenced as a
 `tool:` anywhere derives "tool"; `quality_class` on a Good
 overrides where structure can't say (the lock); everything else is
 "good".
- **Overrides**: an item may embed `quality_overrides:
 Vec<TierEffects>` ONLY where it deviates from its class profile.
 Resolution rule: an override row REPLACES the class profile's row for
 that tier wholesale (no per-field merging). This is the contract's
 ONE `#[serde(default)]` field — it appears in data only when
 non-empty; no item needs it today.
- **Generation rule** (documented here; the engine lands it in 0066):
 quality derives from the CRAFT CHECK. The worker's tier BOUNDS
 reachable quality — an Apprentice caps at Good, a Journeyman at
 Excellent, Masterwork requires a Master, Legendary a Master plus a rare
 roll; a fumble mints Trash; the material's `quality_bias` LIFTS the
 check — better stock raises the odds of a better tier,
 and that is the ONLY door material effects enter by; effectiveness
 (attack, AC, work, DC) comes from the tier alone, never a second
 material modifier. `work_difficulty` extends labor and gates, never
 touching the roll. Price follows quality via `value_factor` —
 the lock rule "price follows DC", generalized.

## Constructive materials and identity

- **Material metadata** lives on goods as `material: MaterialInfo` —
 only CONSTRUCTIVE materials carry it (iron_ingot, timber, leather,
 cloth, mithral_ingot). Class tags are open ids resolving
 against `tables/material_classes.ron`: metal, wood,
 leather, cloth (stone reserved for masonry's arrival). The class
 is the FAMILY WALL — a `Class("leather")` input can never be
 satisfied by a metal; mithral makes daggers, not gloves. The
 class baseline is its common material (factors 1.0); rare materials
 deviate (mithral: weight 0.5, value 10.0, work_difficulty +5,
 quality_bias +4, min_tier Master, RareRegional).
- **The constructive-identity contract** (engine implements in
 0066-a/b): output identity is CONSTRUCTED from what was consumed —
 template × actual material = the concept ("Iron Dagger", "Mithral
 Dagger"), MINTED LAZILY at first craft (no upfront cross-product;
 respected because the mint is the recorded decision). The
 FIRST-LISTED class input is the naming material (the dagger is named
 for its blade, not its grip). Stats and price derive: value =
 template cost × material value_factor × quality value_factor; weight
 = template weight × material weight_factor. Templates never
 enumerate materials.
- **Durability — the axis material OWNS **: things can
 break, and material decides how hard that is. Object toughness
 (the AC to strike or force) = `10 + class hardness + material
 toughness_bonus`; object integrity (hp) = `weight_lb × class
 integrity_per_lb × material integrity_factor` — more matter, more
 to break through. The one-writer wall from extends: TIER
 writes performance (pick DC, attack, worn AC, work), MATERIAL
 writes substance (weight, worth, toughness, integrity). A
 legendary iron lock and a legendary mithral lock pick at the same
 DC 24 — but only one of them survives a sledgehammer. The numbers
 are OURS: 5e's object-statistics chapter is DMG material, outside
 the CC-BY SRD corpus (the CR precedent), so nothing is
 transcribed. The engine consumes this in 0066-b, where breaking is
 the 0058-ledgered force path — and a jammed lock finally has a
 remedy: smash and refit.
- **The reification threshold**: mundane output is FUNGIBLE — stacks
 keyed (concept, quality). Masterwork and Legendary REIFY: an
 individual item node with provenance (crafted-by, crafted-at,
 material origin) — story-bearing objects for theft, loot, and
 inheritance; the named-cast pattern applied to things.
 Chronicle-headline worthy at birth.

## Constructibility: grounded or declared

Part of the ONE validator (`data/validate.rs` — boot,
the lock tests, CI's `validate_data` bin, and the game's `--check-data`
door all run the same pass). **"Raw" is now
DEFINED: a good some building type innately produces.** Every good must
be one of:

1. **raw** — in at least one building's `produces` list;
2. **grounded craftable** — has a recipe whose inputs all RECURSIVELY
 ground out in raws (the DAG is walked; a `Class` input grounds if at
 least one material of the class grounds);
3. **declared** — `acquisition: Some(...)` names a non-crafting source
 (future loot/quest goods; none needs it today). The
 narrate-or-declare pattern: nothing is unconstructible by accident.

Also enforced: NO recipe cycles (A needs B needs A fails the walk), and
ORPHAN goods are flagged (nothing produces it, no recipe yields it,
nothing consumes or uses it as a tool). All failures are loud, with
file and reason. Tool BOOTSTRAP is explicitly allowed — smith's tools
are made with smith's tools, because tool use is not a recipe input;
worldgen seeds the first kit.

## The identity model: archetypes, not people

Archetype files carry NO person-name semantics — no names, no races. A
living NPC is composed at generation:

1. **Archetype** primitives (`creatures/humanoids/`) — base scores,
 level, proficiencies, gear, CR;
2. **a Race** rolled from region culture tables, whose ability
 adjustments are COMPOSED onto the archetype's base scores, and whose
 size and speed replace the printed human assumptions. This is the
 SRD's own model: the blocks are "Medium humanoid (any race)", and
 Appendix MM-B says so directly — "These stat blocks can be used to
 represent both human and nonhuman NPCs" and "Racial Traits. You can
 add racial traits to an NPC. For example, a halfling druid might
 have a speed of 25 feet and the Lucky trait." (p. 395);
3. **a name** drawn from that race's register in `data/names/` — see
 the race key contract below.

Worked example — a dwarf guard: base 13/12/12/10/11/10, dwarf +2 Con →
13/12/**14**/10/11/10; Con mod +2 makes HP `floor(2 × 4.5) + 2 × 2 =
13`; speed 25 (dwarf), size Medium keeps the d8; AC stays 16 (Dex mod
still +1 under Max2); alignment rolls under the block's constraint with
the dwarf bias (+0.3 moral, +0.5 ethic); the name comes from
`names/dwarf.ron`.

**The rationale, from the designer:** templates never carry identity, so
every generated NPC is a real someone with a story — in this game they
actually do.

### The race key contract

`data/races/<id>.srd.ron` and `data/names/<id>.ron` share the filename
id: `races/half_orc.srd.ron` ↔ `names/half_orc.ron`. **Name generation
falls back to the HUMAN tables** (`names/human.ron`) for any race
without a name register — a modded race works immediately and sounds
human until given a voice. The built-in half-elf is the exception: without
its own register it draws an elf given name and an elf or human surname.
(`names/human.ron` landed with the name tables,
extracted from the engine's names.rs frontier tables; `soul::register_name`
is the wiring, and the fallback target is now this data register.) The
fallback is deliberate: a missing
register is a soft gap, not a boot error (contrast with gear ids, where
a dangling reference IS a boot error — a guard without his spear is a
bug; a race without a dialect is a default).

**The name provider is tiered** (design seam only — no generator code
lives in `data/`): tier 1, the curated tables plus combinatorial
bynames with a living-collision epithet rule (two living "Durin"s force
an epithet); tier 2, the documented overflow for population scale —
deterministic markov chains trained on OUR OWN registers (never on
licensed corpora), so generated names stay in each race's phonology.
`data/` carries only the registers; tiers live in the engine when they
land.

`AlignmentConstraint` encodes the blocks' printed alignment lines as
generation constraints on the axes (±1/3 label thresholds):
`Any` unconstrained; `NonLawful` ethic ≤ +1/3; `NonGood` moral ≤ +1/3;
`Chaotic` ethic < −1/3. The three current blocks use `Any` (commoner,
guard) and `NonLawful` (bandit); `NonGood` and `Chaotic` cover the rest
of Appendix MM-B's phrasing ("any non-good alignment", "any chaotic
alignment") for when those blocks arrive.

## Transcribed vs. interpreted

Transcribed from the SRD (the book's own words and numbers): names,
prices (unit-converted, below), damage dice and types, weights, property
words, armor AC values, ability scores, ability score increases, sizes,
speeds, hit-dice counts, proficiency sets, CR, the CR→XP table, the
Character Advancement XP-per-level table, the class chassis
(hit dice, saves, armor/weapon/skill training lists, feature levels,
cantrips-known steps, the full-caster slot grid) and the spell numbers
(levels, schools, ranges, dice, save abilities, area sizes, upcast dice —
), and every age number the SRD states as a number. The printed composite values
(AC, HP, bonuses, passives, XP lines) are transcribed too — into
`expected/` as verification data, per the derivation rule.

Ours (interpretation, not SRD text):

- The enum vocabularies and string id slugs, the structured shapes
 (`Thrown(Range(...))`, `ArmorClass::Base/Bonus`, `AbilityMinimum`, and
 `ArmorEquipCost`), and
 `AlignmentConstraint`'s axis encoding.
- The class/spell STRUCTURING: the `SpellEffect`/`Area`/`Upcast`
 encodings of the spells' prose, the `skills.from` quick-build ORDER (the
 membership is the SRD's), the auto-resolved defaults where the book gives
 a choice (Defense as the fighting style, the expertise and skill picks,
 the half-elf's two skills as Perception + Persuasion — each noted in its
 file), and the grid approximations (a cube as the centered burst, the
 90° cone, the Chebyshev sphere).
- A `Pull` effect may set `max_target_size` to a typed size such as `Large`.
 Targets above that ceiling are not moved; sibling effects in the same spell
 still resolve. Omitting the field preserves unrestricted pulling for existing
 records and mods.
- Every spell declares a closed `components` record with `verbal`, `somatic`,
 and `material` booleans. At least one must be true. These are executable
 mechanics: for example, a verbal cast ends a successful Hide while a spell
 without a verbal component does not. Spell names never select this behavior.
- `level` as a stored primitive equal to the printed hit-dice count,
 unifying the blocks with the proficiency ladder.
- The book's PRICE COLUMN is deliberately NOT transcribed ( §5) —
 an arm is worth what its recipe costs to run, and nothing in `data/`
 states a price.
- Whitespace normalization in dice formulas: `"2d8 + 2"` → `"2d8+2"`.
- Age values distilled from prose, marked with a comment on the line:
 halfling lifespan 150, human 18/90, gnome lifespan 425, half-elf
 lifespan 180, tiefling 18/95.
- **`alignment_bias` is entirely our distillation** — see below.
- **All crafting content** — the `goods/` files, every `recipes:` list,
 stations, tiers, quantities, hours, and material prices — is original
 gameplay data, per the Crafting section, and every good the data can
 make now DERIVES its worth from its recipes (/0396). SRD-aligned
 exceptions noted per file: the seven tool/kit WEIGHTS (smith's,
 leatherworker's, carpenter's, weaver's, brewer's supplies, cook's
 utensils, fishing tackle). Their VALUES were SRD-aligned before
 and derive now (see ATTRIBUTION.md); ale and bread no longer
 state a price at all — they cost what brewing and baking cost.
 "Mithral" is the SRD 5.1 spelling.
- **Professions, buildings, quality profiles, and material metadata**
 are original game content (the profession set itself mirrors the
 engine's canonical library; the skill picks, wages aside, are ours).
- Racial traits follow the substrate line (the ledger rule, worked
 down by the racial-substrate rulings): darkvision, the damage resistances, Lucky,
 Relentless Endurance, Savage Attacks, the skill-granting traits
 (Keen Senses, Menacing, Skill Versatility), the draconic-ancestry
 Breath Weapon (the activated racial action, `SubraceDef.breath`),
 and the halfling's Brave (advantage on saves vs frightened, now
 that the SRD Fear spell is the first in-bubble fear PRODUCER) are now ENCODED
 because the engine consumes them; still deliberately left
 out (no substrate): Fey Ancestry, Trance, the breath's per-ancestry
 line/cone SHAPE (all cones today) and the monster recharge economy,
 Infernal Legacy, Stonecunning, halfling
 nimbleness, weapon/tool
 training from race, subrace traits beyond their ASI + resistances,
 languages; armor
 don/doff times; weapon "special" rule text (lance, net); height and
 weight prose beyond the size category. The same rule scopes the SPELL
 corpus: a spell whose effect needs concentration, conditions, or
 summons is not transcribed yet.

## Alignment-tendency distillation (ours, not the SRD's)

The SRD gives each race an alignment *prose* tendency. We distill it to
a `(moral, ethic)` bias pair on the engine's axes — moral +1
good / −1 evil, ethic +1 lawful / −1 chaotic, each in −1..+1, where the
nine alignment names derive at ±1/3 thresholds. The bias is a
generation-time mean shift, not a cap.

Magnitude scale:

| Bias | Reading of the prose |
| --- | --- |
| 0.0 | no tendency stated |
| ±0.2 | hedged pull ("not strongly inclined toward...") |
| ±0.3 | clear lean ("more often X than not", "tend toward X", "many end up there") |
| ±0.4 | strong lean ("most often X", "share the X bent", "inherit a tendency toward X") |
| ±0.5 | emphatic majority ("most are X", "lean strongly toward X") — pushes the mean past the ±1/3 label threshold |

Per race (SRD prose quoted in each `races/*.srd.ron` above the value):

| Race | moral | ethic | Reasoning |
| --- | --- | --- | --- |
| Dwarf | +0.3 | +0.5 | "most dwarves are lawful"; "tend toward good as well" |
| Elf | +0.3 | −0.5 | "lean strongly toward... chaos"; "more often good than not" |
| Halfling | +0.5 | +0.5 | "most halflings are lawful good" |
| Human | 0.0 | 0.0 | "no particular alignment" |
| Dragonborn | +0.4 | 0.0 | "most dragonborn are good", but explicitly polarized ("tend to extremes") — the bimodal spread can't live in a scalar mean |
| Gnome | +0.4 | 0.0 | "most often good"; law/chaos split by profession |
| Half-elf | 0.0 | −0.4 | "share the chaotic bent"; no moral tendency stated |
| Half-orc | −0.2 | −0.4 | "tendency toward chaos"; "not strongly inclined toward good"; the "usually evil" clause is conditioned on orc upbringing |
| Tiefling | −0.3 | −0.4 | "many of them end up [evil]" though not innately; "inclines many... toward a chaotic alignment" |

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

## License

This work includes material from the System Reference Document 5.1 ('SRD 5.1') and the System Reference Document 5.2.1 ('SRD 5.2.1') by Wizards of the Coast LLC, available at https://www.dndbeyond.com/srd. The SRD 5.1 and SRD 5.2.1 are licensed under the Creative Commons Attribution 4.0 International License, available at https://creativecommons.org/licenses/by/4.0/legalcode.