59 lines
1.9 KiB
Markdown
59 lines
1.9 KiB
Markdown
# Data Model: Turn Navigation
|
|
|
|
## Existing Entities (unchanged)
|
|
|
|
### Encounter
|
|
- `combatants`: readonly array of Combatant
|
|
- `activeIndex`: number (0-based index into combatants)
|
|
- `roundNumber`: positive integer (>= 1)
|
|
|
|
### Combatant
|
|
- `id`: CombatantId (branded string)
|
|
- `name`: string
|
|
- `initiative?`: number
|
|
- `maxHp?`: number
|
|
- `currentHp?`: number
|
|
|
|
## New Domain Events
|
|
|
|
### TurnRetreated
|
|
Emitted on every successful RetreatTurn operation.
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| type | `"TurnRetreated"` (literal) | Discriminant for event union |
|
|
| previousCombatantId | CombatantId | The combatant whose turn was active before retreat |
|
|
| newCombatantId | CombatantId | The combatant who is now active after retreat |
|
|
| roundNumber | number | The round number after the retreat |
|
|
|
|
### RoundRetreated
|
|
Emitted when RetreatTurn crosses a round boundary (activeIndex wraps from 0 to last combatant).
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| type | `"RoundRetreated"` (literal) | Discriminant for event union |
|
|
| newRoundNumber | number | The round number after decrementing |
|
|
|
|
## State Transitions
|
|
|
|
### RetreatTurn
|
|
|
|
**Input**: Encounter
|
|
**Output**: `{ encounter: Encounter, events: DomainEvent[] }` | `DomainError`
|
|
|
|
**Rules**:
|
|
1. If `combatants.length === 0` -> DomainError("invalid-encounter")
|
|
2. If `roundNumber === 1 && activeIndex === 0` -> DomainError("no-previous-turn")
|
|
3. If `activeIndex > 0`: newIndex = activeIndex - 1, roundNumber unchanged
|
|
4. If `activeIndex === 0`: newIndex = combatants.length - 1, roundNumber - 1
|
|
|
|
**Events emitted**:
|
|
- Always: TurnRetreated
|
|
- On round boundary crossing (rule 4): TurnRetreated then RoundRetreated (order matters)
|
|
|
|
## Validation Rules
|
|
|
|
- RetreatTurn MUST NOT produce roundNumber < 1
|
|
- RetreatTurn MUST NOT produce activeIndex < 0 or >= combatants.length
|
|
- RetreatTurn is a pure function: identical input produces identical output
|