Files
initiative/specs/021-bestiary-statblock/data-model.md

5.7 KiB

Data Model: Bestiary Search & Stat Block Display

Branch: 021-bestiary-statblock | Date: 2026-03-06

Domain Entities

CreatureId (branded type)

A branded string type for creature identity, following the same pattern as CombatantId.

CreatureId = string & { __brand: "CreatureId" }

Format: {source}:{name-slug} (e.g., xmm:goblin, xmm:adult-red-dragon)

Creature

Represents a normalized bestiary entry. All fields are readonly.

Field Type Required Description
id CreatureId yes Unique identifier
name string yes Display name
source string yes Source identifier (e.g., "XMM")
sourceDisplayName string yes Human-readable source (e.g., "MM 2024")
size string yes Size label(s) (e.g., "Medium", "Small or Medium")
type string yes Creature type (e.g., "Dragon (Chromatic)", "Humanoid")
alignment string yes Alignment text (e.g., "Chaotic Evil", "Unaligned")
ac number yes Armor class (numeric value)
acSource string or undefined no Armor source description (e.g., "natural armor", "plate armor")
hp object yes { average: number; formula: string }
speed string yes Formatted speed string (e.g., "30 ft., fly 60 ft. (hover)")
abilities object yes { str, dex, con, int, wis, cha: number }
cr string yes Challenge rating (e.g., "1/4", "17")
proficiencyBonus number yes Derived from CR
passive number yes Passive perception
savingThrows string or undefined no Formatted saves (e.g., "DEX +5, WIS +5")
skills string or undefined no Formatted skills (e.g., "Perception +7, Stealth +5")
resist string or undefined no Damage resistances
immune string or undefined no Damage immunities
vulnerable string or undefined no Damage vulnerabilities
conditionImmune string or undefined no Condition immunities
senses string or undefined no Senses (e.g., "Darkvision 60 ft.")
languages string or undefined no Languages
traits TraitBlock[] or undefined no Creature traits
actions TraitBlock[] or undefined no Actions
bonusActions TraitBlock[] or undefined no Bonus actions
reactions TraitBlock[] or undefined no Reactions
legendaryActions LegendaryBlock or undefined no Legendary actions with preamble
spellcasting SpellcastingBlock[] or undefined no Spellcasting entries

TraitBlock

Field Type Required Description
name string yes Trait/action name
text string yes Pre-rendered plain text (tags stripped)

LegendaryBlock

Field Type Required Description
preamble string yes Introductory text about legendary actions
entries TraitBlock[] yes Individual legendary actions

SpellcastingBlock

Field Type Required Description
name string yes "Spellcasting" or variant name
headerText string yes Pre-rendered header description
atWill string[] or undefined no At-will spells
daily DailySpells[] or undefined no Daily-use spells with uses count
restLong DailySpells[] or undefined no Per-long-rest spells

DailySpells

Field Type Required Description
uses number yes Number of uses
each boolean yes Whether "each" applies
spells string[] yes Spell names (plain text)

BestiarySource

Field Type Required Description
id string yes Source code (e.g., "XMM")
displayName string yes Human-readable name (e.g., "Monster Manual (2024)")
tag string or undefined no Optional tag (e.g., "legacy" for 2014 books)

Extended Existing Entities

Combatant (extended)

Add one optional field:

Field Type Required Description
creatureId CreatureId or undefined no Reference to bestiary creature for stat block lookup

This field is:

  • Set when adding a combatant from the bestiary
  • Undefined when adding a plain-named combatant
  • Persisted to localStorage
  • Used by the web layer to look up creature data for stat block display

State Transitions

Adding a creature from bestiary

  1. User searches → selects creature
  2. System reads creature's name, hp.average, ac
  3. System checks existing combatants for name conflicts
  4. If conflicts: auto-number (rename first to "X 1" if needed, new one gets next number)
  5. Domain addCombatant called with resolved name
  6. creatureId stored on the new combatant

Viewing a stat block

  1. User clicks search result or bestiary-linked combatant
  2. Web layer resolves creatureId to Creature from in-memory bestiary
  3. Stat block panel renders the Creature data
  4. No domain state change

Validation Rules

  • CreatureId must be non-empty branded string
  • Creature.ac must be a non-negative integer
  • Creature.hp.average must be a positive integer
  • Creature.abilities values must be positive integers (1-30 range)
  • Creature.cr must be a valid CR string
  • Auto-numbering suffix must be a positive integer
  • Combatant.creatureId when present must reference a valid creature in the loaded bestiary (graceful degradation if not found — stat block unavailable but combatant still functions)

Proficiency Bonus Derivation

CR Proficiency Bonus
0 - 4 +2
5 - 8 +3
9 - 12 +4
13 - 16 +5
17 - 20 +6
21 - 24 +7
25 - 28 +8
29 - 30 +9