# Feature Specification: Add Combatant **Feature Branch**: `002-add-combatant` **Created**: 2026-03-03 **Status**: Draft **Input**: User description: "let us add a spec for the option to add a combatant to the encounter. a new combatant is added to the end of the list." ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Add Combatant to Encounter (Priority: P1) A game master adds a new combatant to an existing encounter. The new combatant is appended to the end of the initiative order. This allows late-joining participants or newly discovered enemies to enter combat. **Why this priority**: Adding combatants is the foundational mutation for populating an encounter. Without it, the encounter has no participants and no other feature (turn advancement, removal) is useful. **Independent Test**: Can be fully tested as a pure state transition with no I/O, persistence, or UI. Given an Encounter value and an AddCombatant action with a name, assert the resulting Encounter value and emitted domain events. **Acceptance Scenarios**: 1. **Given** an empty encounter (no combatants, activeIndex 0, roundNumber 1), **When** AddCombatant with name "Gandalf", **Then** combatants is [Gandalf], activeIndex is 0, roundNumber is 1, and a CombatantAdded event is emitted with the new combatant's id and name "Gandalf" and position 0. 2. **Given** an encounter with combatants [A, B], activeIndex 0, roundNumber 1, **When** AddCombatant with name "C", **Then** combatants is [A, B, C], activeIndex is 0, roundNumber is 1, and a CombatantAdded event is emitted with position 2. 3. **Given** an encounter with combatants [A, B, C], activeIndex 2, roundNumber 3, **When** AddCombatant with name "D", **Then** combatants is [A, B, C, D], activeIndex is 2, roundNumber is 3, and a CombatantAdded event is emitted with position 3. The active combatant does not change. 4. **Given** an encounter with combatants [A], **When** AddCombatant is applied twice with names "B" then "C", **Then** combatants is [A, B, C] in that order. Each operation emits its own CombatantAdded event. 5. **Given** an encounter with combatants [A, B], **When** AddCombatant with an empty name "", **Then** the operation MUST fail with a validation error. No events are emitted. State is unchanged. 6. **Given** an encounter with combatants [A, B], **When** AddCombatant with a whitespace-only name " ", **Then** the operation MUST fail with a validation error. No events are emitted. State is unchanged. --- ### Edge Cases - Empty name or whitespace-only name: AddCombatant MUST return a DomainError (no state change, no events). - Adding to an empty encounter: the new combatant becomes the first and only participant; activeIndex remains 0. - Adding during mid-round: the activeIndex must not shift; the currently active combatant stays active. - Duplicate names: allowed. Combatants are distinguished by their unique id, not by name. ## Domain Model *(mandatory)* ### Key Entities - **Combatant**: An identified participant in the encounter with a unique CombatantId (branded string) and a name (non-empty string). - **Encounter**: The aggregate root. Contains an ordered list of combatants, an activeIndex pointing to the current combatant, and a roundNumber (positive integer, starting at 1). ### Domain Events - **CombatantAdded**: Emitted on every successful AddCombatant. Carries: combatantId, name, position (zero-based index where the combatant was inserted). ### Invariants - **INV-1** (preserved): An encounter MAY have zero combatants. - **INV-2** (preserved): If combatants.length > 0, activeIndex MUST satisfy 0 <= activeIndex < combatants.length. If combatants.length == 0, activeIndex MUST be 0. - **INV-3** (preserved): roundNumber MUST be a positive integer (>= 1) and MUST only increase. - **INV-4**: AddCombatant MUST be a pure function of the current encounter state and the input name. Given identical input, output MUST be identical (except for id generation — see Assumptions). - **INV-5**: Every successful AddCombatant MUST emit exactly one CombatantAdded event. No silent state changes. - **INV-6**: AddCombatant MUST NOT change the activeIndex or roundNumber of the encounter. - **INV-7**: The new combatant MUST be appended to the end of the combatants list (last position). ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: The domain MUST expose an AddCombatant operation that accepts an Encounter and a combatant name, and returns the updated Encounter state plus emitted domain events. - **FR-002**: AddCombatant MUST append the new combatant to the end of the combatants list. - **FR-003**: AddCombatant MUST assign a unique CombatantId to the new combatant. - **FR-004**: AddCombatant MUST reject empty or whitespace-only names by returning a DomainError without modifying state or emitting events. - **FR-005**: AddCombatant MUST NOT alter the activeIndex or roundNumber of the encounter. - **FR-006**: Domain events MUST be returned as values from the operation, not dispatched via side effects. ### Out of Scope (MVP baseline does not include) - Removing combatants from an encounter - Reordering combatants after adding - Initiative score or automatic sorting - Combatant attributes beyond name (HP, conditions, stats) - Maximum combatant count limits - Persistence, serialization, or storage - UI or any adapter layer ## Assumptions - CombatantId generation is the caller's responsibility (passed in or generated by the application layer), keeping the domain function pure and deterministic. The domain function will accept a CombatantId as part of its input rather than generating one internally. - Name validation trims whitespace; a name that is empty after trimming is invalid. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: All 6 acceptance scenarios pass as deterministic, pure-function tests with no I/O dependencies. - **SC-002**: Invariants INV-1 through INV-7 are verified by tests. - **SC-003**: The domain module has zero imports from application, adapter, or agent layers (layer boundary compliance). - **SC-004**: Adding a combatant to an encounter preserves all existing combatants and their order unchanged.