70 lines
2.0 KiB
Markdown
70 lines
2.0 KiB
Markdown
# Data Model: Remove Combatant
|
|
|
|
**Feature**: 003-remove-combatant
|
|
**Date**: 2026-03-03
|
|
|
|
## Existing Entities (no changes)
|
|
|
|
### Encounter
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| combatants | readonly Combatant[] | Ordered list of participants |
|
|
| activeIndex | number | Index of the combatant whose turn it is |
|
|
| roundNumber | number | Current round (≥ 1, never changes on removal) |
|
|
|
|
### Combatant
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| id | CombatantId (branded string) | Unique identifier |
|
|
| name | string | Display name |
|
|
|
|
## New Event Type
|
|
|
|
### CombatantRemoved
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| type | "CombatantRemoved" (literal) | Event discriminant |
|
|
| combatantId | CombatantId | ID of the removed combatant |
|
|
| name | string | Name of the removed combatant |
|
|
|
|
Added to the `DomainEvent` discriminated union alongside `TurnAdvanced`, `RoundAdvanced`, and `CombatantAdded`.
|
|
|
|
## New Domain Function
|
|
|
|
### removeCombatant
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| encounter | Encounter | Current encounter state |
|
|
| id | CombatantId | ID of combatant to remove |
|
|
|
|
**Returns**: `RemoveCombatantSuccess | DomainError`
|
|
|
|
### RemoveCombatantSuccess
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| encounter | Encounter | Updated encounter after removal |
|
|
| events | DomainEvent[] | Exactly one CombatantRemoved event |
|
|
|
|
### DomainError (existing, reused)
|
|
|
|
Returned with code `"combatant-not-found"` when ID does not match any combatant.
|
|
|
|
## State Transition Rules
|
|
|
|
### activeIndex Adjustment
|
|
|
|
Given removal of combatant at index `removedIdx` with current `activeIndex`:
|
|
|
|
| Condition | New activeIndex |
|
|
|-----------|----------------|
|
|
| removedIdx > activeIndex | activeIndex (unchanged) |
|
|
| removedIdx < activeIndex | activeIndex - 1 |
|
|
| removedIdx === activeIndex, not last in list | activeIndex (next slides in) |
|
|
| removedIdx === activeIndex, last in list | 0 (wrap) |
|
|
| Only combatant removed (list becomes empty) | 0 |
|