2.0 KiB
2.0 KiB
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 |