This is data/README.md from the Kinbarrow repository, published
verbatim. The type pages cite it for the rules that belong to the TREE rather
than to any one file — the derivation formulas, the identity model, and which
values are transcribed from the SRD versus interpreted. It describes content
tree 45d0fe274f851548.
data/ — game data and its organizational contract
Machine-readable game data for the adventure layer (ADR-0066): the SRD
5.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, 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.srd.ron Table<Weapon> (37)
│ ├── armor.srd.ron Table<Armor> (12 + shield)
│ ├── cr_xp.srd.ron Table<CrXp> (34)
│ ├── advancement.srd.ron Table<AdvancementRow> (20, ADR-0091)
│ ├── spell_slots.srd.ron Table<SpellSlotRow> (20, ADR-0214 — the full-caster grid)
│ ├── spell_slots_half.srd.ron Table<SpellSlotRow> (20, ADR-0239 — the half-caster grid)
│ ├── pact_magic.srd.ron Table<SpellSlotRow> (20, ADR-0240 — the warlock pact grid, short-rest)
│ ├── damage_kinds.srd.ron Table<VocabRow> (13, ADR-0230 — open vocabulary)
│ ├── schools.srd.ron Table<VocabRow> (8, ADR-0230 — open vocabulary)
│ ├── creature_types.srd.ron Table<VocabRow> (14, ADR-0234 — open vocabulary)
│ ├── languages.srd.ron Table<VocabRow> (16, ADR-0248 — open vocabulary)
│ ├── quality_profiles.ron Table<QualityProfile> (5 classes × 7 tiers, ours)
│ ├── material_classes.ron Table<MaterialClass> (5, ours — ADR-0070)
│ └── terrain_tags.ron Table<TerrainTag> (7, ours — ADR-0070)
├── 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 (ADR-0214/0238/0239/0240)
│ └── {fighter,rogue,cleric,wizard,barbarian,monk,paladin,ranger,bard,druid,sorcerer,warlock}.srd.ron (12: all the SRD classes)
├── feats/ OPEN collection: one feat per file (ADR-0242)
│ └── grappler.srd.ron the SRD's ONE feat; the rest is a mod surface
├── spells/ OPEN collection: one spell per file (ADR-0214)
│ └── {fire_bolt,magic_missile,fireball,cure_wounds,…}.srd.ron (18)
├── magic_items/ OPEN collection: one magic item per file (ADR-0243)
│ └── {plus_one_longsword,plus_one_chain_shirt,ring_of_protection,
│ cloak_of_protection,wand_of_magic_missiles}.srd.ron (5: the SRD starter set)
├── conditions/ OPEN collection: one condition per file (ADR-0229/0235)
│ └── {paralyzed,blinded,poisoned,prone,restrained,stunned,charmed,…}.srd.ron (13)
│ bundles of engine ConditionComponent reads;
│ charmed WIRED (ADR-0330); deafened ledgered (no bubble read)
├── traps/ OPEN collection: one trap per file (ADR-0249)
│ └── {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 (ADR-0250)
│ └── {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 ADR-0236 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)
└── names/ ours (unsuffixed) — per-race name tables
- 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 ADR-0236 bestiary filled, one exemplar per non-humanoid type.magic_items/(ADR-0243) is the README’s own “later items” made real — an entity collection, not atables/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 ADR-0066 moddable-data amendment — data loads at boot). - Suffix + source provenance. The
.srd.ronsuffix marks files whose content derives from the SRD; every file’s top-level value also carries asourcefield (Srd51orOriginal). A script or test can audit*.srd.ronagainst the repository’sATTRIBUTION.md. Files without the 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-existingnames/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 ADR-0066-a lands.
Transcribe the sketch — don’t invent.
The typing rule — enums for semantics, string ids for content (the moddability doctrine, per the ADR-0066 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(and the structural shapes inside rows —WeaponProperty,DexMod,ArmorClass,Damage,RecipeInput,Source). Damage kinds and spell schools OPENED at ADR-0230 (tables/damage_kinds.srd.ron,tables/schools.srd.ron); creature types OPENED at ADR-0234 (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.CraftTierIS the engine’sTradeTier(ADR-0053: Untrained / Proficient / Expert, 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 (ADR-0008’s spirit: no silently-missed lookups, ever). - Open VOCABULARIES get registry files (ADR-0070): 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.
Why 5.1, not 5.2.1
SRD 5.2.1 (2024 revision, also CC-BY-4.0) restructures exactly what we carry — verified against the official 5.2.1 PDF: species no longer grant ability score increases, age spans, or alignment tendencies; half-elf and half-orc are absent; weapons gain “mastery” properties with no engine substrate. The numbers we share (blocks, armor, weapons) are unchanged in 5.2.1. SRD 5.1 also matches the engine’s existing citations (stats.rs).
Types
The authoritative shapes live in the modding reference, 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.
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:
// Every file's top-level value is a `Table<T>`, and carries its provenance.
enum Source { Srd51, Original }
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 —
Noneand[]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 arequality_overridesand the handful of fields the reference marks with a default. DexMod::NoneandOption’sNoneboth appear inarmor.srd.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):
- Hit dice count =
level. The stat blocks and ADR-0053’s adventure ladder are ONE system: future leveling grows HP and PB with zero new rules. - 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).
- HP = roll
level × d(hit die) + con_mod × level; the printed AVERAGE convention isfloor(level × avg(die)) + con_mod × levelwithavg(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). - Proficiency bonus = f(level), exactly ADR-0053’s 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.) - AC =
10 + dex_modunarmored; else the armor table’s formula —Base(base, dex)with dex Full/Max2/None — plusBonus(2)if a shield is carried. - 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. - Damage = weapon damage dice + the same ability modifier; the
printed average is
floor(avg(dice) + mod); a zero modifier prints bare dice. - Skill bonus =
mod(governing ability)(fromtables/skills.srd.ron)+ pbif the skill is in the proficiency set (SRD p. 256). - Passive Perception =
10 + Perception bonus. Never stored. - Speed comes from the composed race (races/*.srd.ron) — the printed 30 ft. assumes human; a dwarf guard walks at 25.
- XP =
tables/cr_xp.srd.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
(ADR-0236) 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 —
ADR-0206), 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 — ADR-0083) carry theirs. The tools’ WEIGHTS are aligned with the SRD equipment table’s artisan’s-tools rows (noted per file); their VALUES, and every crafted good’s, now DERIVE (ADR-0081):base_value_cp= material cost +hours × labour_rate(tier), and the validator round-trips it. On weapons and armour the SRDcost_cpstays the transcribed TEMPLATE (not a derivation target) — plate is materials ~0.3% of its 1500 gp price and 300 hours: at our labour rate that price is the SRD’s brand/scarcity premium the market layer carries, not the raw craft cost. Thebedroll(ADR-0080) 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.stationids resolve at boot against the union of the building types’stationslists (today: smithy, tannery, carpentry, weaver, ovens, mash_house);tieris the ADR-0053 ladder;toolreferences 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”). - Boot feeds these into the EXISTING ADR-0040 production machinery:
recipes become graph structure, worldgen assigns the venues, and
there is NO parallel crafting system. ADR-0058’s make-check quality
generalizes here later (tier + check → quality); material
weight_lbfeeds ADR-0064’s cart loads. - Batch yield (ADR-0104 — the ammo fix, landed): a
Recipecarries ayield(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 arearrowandbolt(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 isyield: 1, explicit (no serde default).
Worth is derived — anchors, not prices (ADR-0069)
No number in this tree is a market price. Runtime worth is MARKET STATE — venue prices move with supply, demand, and distance; ADR-0064’s arbitrage exists because they do. What the data actually carries:
base_value_cpis an anchor. On RAW goods (the nine the buildings produce) it is authored and authoritative — it prices extraction labor and scarcity: the economy’s seed numeraire.- On recipe-bearing goods it is an EXPECTATION, not an authority.
A constructed good’s PER-UNIT base worth derives through its recipe:
(material cost (Goodinputs at their anchor, aClassinput at its cheapest member) +hours × labour_rate(tier)) ÷yield(ADR-0104 — 1 for a single-unit craft, so unchanged across the corpus; 20 for an ammo batch). The stored number is theexpected/-corpus pattern applied to economics: derive, then verify. As of ADR-0081 the labour rate IS calibrated —data::labor_rate_cp,1 + prof_bonus/2→ **Untrained 1, Proficient 2, Expert 3 cp/hour** (tied to the SRD proficiency bonus, ADR-0053) — so the validator's old materials-≤-price inequality is HARDENED into the derivation check: a recipe-bearing good whosebase_value_cp` does not round-trip the derivation within ±5% (or ±1 cp) is a VALIDATOR FAILURE. (Material and quality factors still multiply at CRAFT time, on the constructed identity; the anchor is the mundane-iron, Normal-quality base.) - SRD
cost_cpon weapons/armor is the TEMPLATE base — the book’s printed price, transcribed; constructed identity multiplies it (material × quality), and the market moves it from there. base_wageis the same doctrine for people — a seed the engine modulates through ADR-0057 tier wages, council pay decisions, and poaching. Nothing pays the file’s number forever.
Professions and buildings (ours)
professions/mirrors the CANONICAL engine set (crates/engine/src/professions.rs, ADR-0010: professions are graph data) — twelve files, same abilities, wage seeds (base_wage, ADR-0069; only the waged four: harbormaster 3, fixer 2, guildmaster 4, watch captain 4), and display names (“Harbormaster”, “Watch Captain”). Each adds two slice-two facts: the tradetoolas a good id (the ADR-0053 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), andproficiencies— 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 ADR-0066 career-guard exemplar; innkeeper → Insight + Persuasion). The picks are ours, noted per file.buildings/holds building TYPES for worldgen to instantiate — never instances.producesis the ADR-0040 PRODUCES relationship as data (rate = units per worked hour, before the ADR-0053 work multiplier; raw extraction names no trade tool).stationscloses the recipe-station references.build_costis the construction composition — ADR-0097 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 (ADR-0017) until the trades exist.
Geography grounds the buildings
Terrain tags are open ids like material classes, resolving against
tables/terrain_tags.ron (ADR-0070) — 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 ADR-0063 two-town divergence generalized: towns differ because their ground does, not because someone wrote two economies.
- Deposits: the
ore_deposittag 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 ADR-0053
hook); DC delta for locks.
- The lock ladder (ADR-0068): 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). ADR-0058’s 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 (ADR-0071): weapons and armor take their
profiles STRUCTURALLY (their own tables); a good referenced as a
tool:anywhere derives “tool”;quality_classon 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 — Untrained caps at Good, Proficient at Excellent,
Masterwork requires Expert, Legendary requires Expert plus a rare
roll; a fumble mints Trash; the material’s
quality_biasLIFTS the check (ADR-0068) — 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_difficultyextends labor and gates, never touching the roll. Price follows quality viavalue_factor— ADR-0058’s “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 againsttables/material_classes.ron(ADR-0070): metal, wood, leather, cloth (stone reserved for masonry’s arrival). The class is the FAMILY WALL — aClass("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 Expert, 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; ADR-0001 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 (ADR-0071): 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 ADR-0068 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, ADR-0066-a/0227 — boot,
the lock tests, CI’s validate_data bin, and the game’s --check-data
door all run the same pass — ADR-0071). “Raw” is now
DEFINED: a good some building type innately produces. Every good must
be one of:
- raw — in at least one building’s
produceslist; - grounded craftable — has a recipe whose inputs all RECURSIVELY
ground out in raws (the DAG is walked; a
Classinput grounds if at least one material of the class grounds); - 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:
- Archetype primitives (
creatures/humanoids/) — base scores, level, proficiencies, gear, CR; - 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);
- 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. (names/human.ron landed in ADR-0086,
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 ADR-0046 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 (ADR-0091), 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 —
ADR-0214), 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), andAlignmentConstraint’s axis encoding. - The class/spell STRUCTURING (ADR-0214): the
SpellEffect/Area/Upcastencodings of the spells’ prose, theskills.fromquick-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). levelas a stored primitive equal to the printed hit-dice count, unifying the blocks with ADR-0053’s ladder.cost_cp: exact integer copper conversion of the book’s price column (1 gp = 100 cp, 1 sp = 10 cp — the SRD’s own rates): longsword “15 gp” → 1500, dart “5 cp” → 5, plate “1,500 gp” → 150000.- 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_biasis entirely our distillation — see below.- All crafting content — the
goods/files, everyrecipes:list, stations, tiers, quantities, hours, and material prices — is original gameplay data, per the Crafting section, and every crafted good’sbase_value_cpnow DERIVES from its recipe (ADR-0081). 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) and ale’s mug price (4 cp — which also round-trips the derivation). Their VALUES were SRD-aligned before ADR-0081 but now derive (see ATTRIBUTION.md); bread deviates from the SRD’s 2 cp (ours 4, and it round-trips). “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 (ADR-0066’s ledger rule, worked
down by ADR-0204/0214): darkvision, the damage resistances, Lucky,
Relentless Endurance, Savage Attacks, the skill-granting traits
(Keen Senses, Menacing, Skill Versatility), the draconic-ancestry
Breath Weapon (ADR-0297 — the activated racial action,
SubraceDef.breath), and the halfling’s Brave (ADR-0328 — 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 (ADR-0214).
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 ADR-0046 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 (ADR-0079)
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 ADR-0079 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, ADR-0063), the proceduralslots(aWorldgenslot is what makes the realm the realm — an authored coast plus a generated vale, ADR-0039), 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 (ADR-0351). 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 ADR-0071 cross-reference pass extended to campaign
files (crates/engine/src/campaign/validate.rs — the one validator,
ADR-0232): every town id names a file,
every venue’s building type is real and PERMITTED by the town’s ground
(requires ⊆ terrain, ADR-0075), terrain tags/staples/professions resolve,
and every good handle a produce/require/stock/carry names is minted by the
town. 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 ADR-0066 overlay/ledger mod model applies unchanged.
License
This work includes material taken from the System Reference Document 5.1 (“SRD 5.1”) by Wizards of the Coast LLC and available at https://dnd.wizards.com/resources/systems-reference-document. The SRD 5.1 is licensed under the Creative Commons Attribution 4.0 International License available at https://creativecommons.org/licenses/by/4.0/legalcode.