1.9 KiB
1.9 KiB
Data Model: Turn Navigation
Existing Entities (unchanged)
Encounter
combatants: readonly array of CombatantactiveIndex: number (0-based index into combatants)roundNumber: positive integer (>= 1)
Combatant
id: CombatantId (branded string)name: stringinitiative?: numbermaxHp?: numbercurrentHp?: 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:
- If
combatants.length === 0-> DomainError("invalid-encounter") - If
roundNumber === 1 && activeIndex === 0-> DomainError("no-previous-turn") - If
activeIndex > 0: newIndex = activeIndex - 1, roundNumber unchanged - 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