KINBARROW modding reference
v0.2.0+main.e011511f4 dataset 4a103dcdb658a75a

The identity model: archetypes, not people

The content tree — the contract for the data/ tree.

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.