Implement the 021-bestiary-statblock feature that adds a searchable D&D 2024 Monster Manual creature library with inline autocomplete suggestions, full stat block display in a fixed side panel, auto-numbering of duplicate creature names, HP/AC pre-fill from bestiary data, and automatic stat block display on turn change for wide viewports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-09 11:01:07 +01:00
parent 04a4f18f98
commit fa078be2f9
30 changed files with 66221 additions and 56 deletions

View File

@@ -0,0 +1,146 @@
# 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 |