1.8 KiB
1.8 KiB
Data Model: Persist Encounter
Existing Entities (unchanged)
Combatant
| Field | Type | Notes |
|---|---|---|
| id | CombatantId (branded string) | Unique identifier |
| name | string | Display name |
| initiative | number or undefined | Initiative value, optional |
ID Format Convention (existing code)
- Demo combatants use plain numeric IDs:
"1","2","3" - User-added combatants use the pattern
c-{N}(e.g.,"c-1","c-2") - On reload,
nextIdcounter is derived by scanning existing IDs matchingc-{N}and starting frommax(N) + 1 - IDs not matching
c-{N}(e.g., demo IDs) are ignored during counter derivation
Encounter
| Field | Type | Notes |
|---|---|---|
| combatants | readonly Combatant[] | Ordered list of combatants |
| activeIndex | number | Index of the combatant whose turn it is |
| roundNumber | number | Current round (positive integer) |
Persisted State
Storage Key
"initiative:encounter"
Serialized Format
The Encounter object is serialized as-is via JSON.stringify. The branded CombatantId serializes as a plain string. On deserialization, IDs are rehydrated with combatantId().
Validation on Load
- Parse JSON string into unknown value
- Structural check: verify it is an object with
combatants(array),activeIndex(number),roundNumber(number) - Verify each combatant has
id(string) andname(string) - Pass through
createEncounterto enforce domain invariants - On any failure: discard and return
null(caller falls back to demo encounter)
State Transitions
App Load
├─ localStorage has valid data → restore Encounter
└─ localStorage empty/invalid → create demo Encounter
State Change (any use case)
└─ new Encounter saved to React state
└─ useEffect triggers → serialize to localStorage