Implement the 002-add-combatant feature that adds the possibility to add new combatants to an encounter

This commit is contained in:
Lukas
2026-03-03 23:11:07 +01:00
parent 187f98fc52
commit 0de68100c8
15 changed files with 914 additions and 16 deletions

View File

@@ -0,0 +1,77 @@
# Data Model: Add Combatant
**Feature**: 002-add-combatant
**Date**: 2026-03-03
## Entities
### Combatant (existing, unchanged)
| Field | Type | Constraints |
|-------|------|-------------|
| id | CombatantId (branded string) | Unique, required |
| name | string | Non-empty after trimming, required |
### Encounter (existing, unchanged)
| Field | Type | Constraints |
|-------|------|-------------|
| combatants | readonly Combatant[] | Ordered list, may be empty |
| activeIndex | number | 0 <= activeIndex < combatants.length (or 0 if empty) |
| roundNumber | number | Positive integer >= 1, only increases |
## Domain Events
### CombatantAdded (new)
| Field | Type | Description |
|-------|------|-------------|
| type | "CombatantAdded" (literal) | Discriminant for the DomainEvent union |
| combatantId | CombatantId | Id of the newly added combatant |
| name | string | Name of the newly added combatant |
| position | number | Zero-based index where the combatant was placed |
## State Transitions
### AddCombatant
**Input**: Encounter + CombatantId + name (string)
**Preconditions**:
- Name must be non-empty after trimming
**Transition**:
- New combatant `{ id, name: trimmedName }` appended to end of combatants list
- activeIndex unchanged
- roundNumber unchanged
**Postconditions**:
- combatants.length increased by 1
- New combatant is at index `combatants.length - 1`
- All existing combatants preserve their order and index positions
- INV-2 satisfied (activeIndex still valid for the now-larger list)
**Events emitted**: Exactly one `CombatantAdded`
**Error cases**:
- Empty or whitespace-only name → DomainError `{ code: "invalid-name" }`
## Function Signatures
### Domain Layer
```
addCombatant(encounter, id, name) → { encounter, events } | DomainError
```
### Application Layer
```
addCombatantUseCase(store, id, name) → DomainEvent[] | DomainError
```
## Validation Rules
| Rule | Layer | Error Code |
|------|-------|------------|
| Name non-empty after trim | Domain | invalid-name |