2.3 KiB
2.3 KiB
Data Model: Combatant Armor Class Display
Feature: 016-combatant-ac | Date: 2026-03-06
Entity Changes
Combatant (modified)
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| id | CombatantId (branded string) | Yes | Non-empty | Existing — unchanged |
| name | string | Yes | Non-empty after trim | Existing — unchanged |
| initiative | number | undefined | No | Integer | Existing — unchanged |
| maxHp | number | undefined | No | Integer >= 1 | Existing — unchanged |
| currentHp | number | undefined | No | Integer >= 0, <= maxHp | Existing — unchanged |
| ac | number | undefined | No | Integer >= 0 | NEW — Armor Class |
Key Differences from Other Optional Fields
- Unlike
maxHp/currentHp, AC has no paired or derived field — it is a single standalone value. - Unlike
initiative, AC does not affect combatant sort order. - AC validation is
>= 0(not>= 1likemaxHp), because AC 0 is valid in tabletop RPGs.
Domain Events
AcSet (new)
| Field | Type | Description |
|---|---|---|
| type | "AcSet" |
Event discriminant |
| combatantId | CombatantId | Target combatant |
| previousAc | number | undefined | AC before the change |
| newAc | number | undefined | AC after the change |
State Transitions
setAc(encounter, combatantId, ac)
Input: Encounter, CombatantId, ac: number | undefined
Validation:
- Combatant must exist in encounter (error:
"combatant-not-found") - If
acis defined: must be a non-negative integer (error:"invalid-ac")
Behavior:
- Replaces the combatant's
acfield with the new value (orundefinedto clear) - No side effects on other fields (unlike
setHpwhich initializescurrentHp) - No reordering (unlike
setInitiativewhich re-sorts)
Output: { encounter: Encounter, events: [AcSet] } or DomainError
Persistence Format
localStorage JSON (unchanged key: "initiative:encounter")
Combatant objects are serialized as plain JSON. The ac field is included when defined, omitted when undefined (standard JSON behavior).
Rehydration validation: typeof entry.ac === "number" && Number.isInteger(entry.ac) && entry.ac >= 0 — invalid values are silently discarded as undefined.