Implement the 005-set-initiative feature that adds initiative values to combatants with automatic descending sort and active turn preservation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-04 17:26:41 +01:00
parent a9df826fef
commit fea2bfe39d
17 changed files with 1107 additions and 1 deletions

View File

@@ -0,0 +1,63 @@
# Data Model: Set Initiative
## Entity Changes
### Combatant (modified)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | CombatantId (branded string) | Yes | Unique identifier |
| name | string | Yes | Display name (non-empty, trimmed) |
| initiative | integer | No | Initiative value for turn ordering. Unset means "not yet rolled." |
**Validation rules**:
- `initiative` must be an integer when set (no floats, NaN, or Infinity)
- Zero and negative integers are valid
- Unset (`undefined`) is valid — combatant has not rolled initiative yet
### Encounter (unchanged structure, new ordering behavior)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| combatants | readonly Combatant[] | Yes | Ordered list. Now sorted by initiative descending (unset last, stable sort for ties). |
| activeIndex | number | Yes | Index of the active combatant. Adjusted to follow the active combatant's identity through reorders. |
| roundNumber | number | Yes | Current round (≥ 1). Unchanged by initiative operations. |
**Ordering invariant**: After any `setInitiative` call, `combatants` is sorted such that:
1. Combatants with initiative come first, ordered highest to lowest
2. Combatants without initiative come last
3. Ties within each group preserve relative insertion order (stable sort)
## New Domain Event
### InitiativeSet
Emitted when a combatant's initiative value is set, changed, or cleared.
| Field | Type | Description |
|-------|------|-------------|
| type | "InitiativeSet" | Event discriminant |
| combatantId | CombatantId | The combatant whose initiative changed |
| previousValue | integer or undefined | The initiative value before the change |
| newValue | integer or undefined | The initiative value after the change |
## State Transitions
### setInitiative(encounter, combatantId, value)
**Input**: Current encounter, target combatant id, new initiative value (integer or undefined to clear)
**Output**: Updated encounter with reordered combatants and adjusted activeIndex, plus events
**Error conditions**:
- `combatant-not-found`: No combatant with the given id exists in the encounter
- `invalid-initiative`: Value is not an integer (when defined)
**Transition logic**:
1. Find target combatant by id → error if not found
2. Validate value is integer (when defined) → error if invalid
3. Record the active combatant's id (for preservation)
4. Update the target combatant's initiative value
5. Stable-sort combatants: initiative descending, unset last
6. Find the active combatant's new index in the sorted array
7. Return new encounter + `InitiativeSet` event