78 lines
2.0 KiB
Markdown
78 lines
2.0 KiB
Markdown
# 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 |
|