Implement the 003-remove-combatant feature that adds the possibility to remove a combatant from an encounter
This commit is contained in:
101
specs/003-remove-combatant/spec.md
Normal file
101
specs/003-remove-combatant/spec.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Feature Specification: Remove Combatant
|
||||
|
||||
**Feature Branch**: `003-remove-combatant`
|
||||
**Created**: 2026-03-03
|
||||
**Status**: Draft
|
||||
**Input**: User description: "RemoveCombatant: allow removing a combatant by id from Encounter (adjust activeIndex correctly, keep roundNumber, emit CombatantRemoved, error if id not found) and wire through application + minimal UI."
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Remove a Combatant from an Active Encounter (Priority: P1)
|
||||
|
||||
A game master is running a combat encounter and a combatant is defeated or leaves. The GM removes that combatant by clicking a remove action. The combatant disappears from the initiative order and the turn continues correctly without disruption.
|
||||
|
||||
**Why this priority**: Core functionality — removing combatants is the primary purpose of this feature and must work correctly to maintain encounter integrity.
|
||||
|
||||
**Independent Test**: Can be fully tested by adding combatants to an encounter, removing one, and verifying the combatant list, activeIndex, and roundNumber are correct.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an encounter with combatants [A, B, C] and activeIndex 1 (B's turn), **When** the GM removes combatant C (index 2, after active), **Then** the encounter has [A, B], activeIndex remains 1, roundNumber unchanged, and a CombatantRemoved event is emitted.
|
||||
2. **Given** an encounter with combatants [A, B, C] and activeIndex 2 (C's turn), **When** the GM removes combatant A (index 0, before active), **Then** the encounter has [B, C], activeIndex becomes 1 (still C's turn), roundNumber unchanged.
|
||||
3. **Given** an encounter with combatants [A, B, C] and activeIndex 1 (B's turn), **When** the GM removes combatant B (the active combatant), **Then** the encounter has [A, C], activeIndex becomes 1 (C is now active — the next combatant takes over), roundNumber unchanged.
|
||||
4. **Given** an encounter with combatants [A, B, C] and activeIndex 2 (C's turn, last position), **When** the GM removes combatant C (active and last), **Then** the encounter has [A, B], activeIndex wraps to 0 (A is now active), roundNumber unchanged.
|
||||
5. **Given** an encounter with combatants [A] and activeIndex 0, **When** the GM removes combatant A, **Then** the encounter has [], activeIndex is 0, roundNumber unchanged.
|
||||
6. **Given** an encounter with combatants [A, B, C], **When** the GM attempts to remove a combatant with an ID that does not exist, **Then** a domain error is returned with a descriptive error code, and the encounter is unchanged.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Remove Combatant via UI (Priority: P2)
|
||||
|
||||
A game master sees a list of combatants in the encounter UI. Each combatant has a remove action. Clicking it removes the combatant and the UI updates to reflect the new initiative order.
|
||||
|
||||
**Why this priority**: Provides the user-facing interaction for the core domain functionality. Without UI, the feature is not accessible.
|
||||
|
||||
**Independent Test**: Can be tested by rendering the encounter UI, clicking the remove action on a combatant, and verifying the combatant disappears from the list.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an encounter with combatants displayed in the UI, **When** the GM clicks the remove action on a combatant, **Then** that combatant is removed from the displayed list.
|
||||
2. **Given** an encounter displayed in the UI, **When** a removal results in a domain error (ID not found), **Then** the removal is silently ignored and the encounter state remains unchanged.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when removing the only combatant? The encounter becomes empty with activeIndex 0.
|
||||
- What happens when removing the active combatant who is last in the list? activeIndex wraps to 0.
|
||||
- What happens when removing from an empty encounter? This is covered by the "ID not found" error since no combatant IDs exist.
|
||||
- What happens if the same ID is passed twice in sequence? The first call succeeds; the second returns an error (ID not found).
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST remove a combatant identified by CombatantId from the encounter's combatant list.
|
||||
- **FR-002**: System MUST return a domain error with code `"combatant-not-found"` when the given CombatantId does not match any combatant in the encounter.
|
||||
- **FR-003**: System MUST preserve the roundNumber unchanged after removal.
|
||||
- **FR-004**: System MUST adjust activeIndex so that the same combatant remains active after removal when the removed combatant is before the active one (activeIndex decrements by 1).
|
||||
- **FR-005**: System MUST keep activeIndex unchanged when the removed combatant is after the active one.
|
||||
- **FR-006**: System MUST advance activeIndex to the next combatant (same index position) when the active combatant is removed, allowing the next-in-line to take over.
|
||||
- **FR-007**: System MUST wrap activeIndex to 0 when the active combatant is removed and it was the last in the list.
|
||||
- **FR-008**: System MUST set activeIndex to 0 when the last remaining combatant is removed (empty encounter).
|
||||
- **FR-009**: System MUST emit exactly one CombatantRemoved event on successful removal, containing the removed combatant's ID and name.
|
||||
- **FR-010**: System MUST expose the remove-combatant operation through the application layer via a use case / port interface.
|
||||
- **FR-011**: System MUST provide a UI control for each combatant that triggers removal.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **Encounter**: The combat encounter containing an ordered list of combatants, an activeIndex, and a roundNumber.
|
||||
- **Combatant**: A participant in the encounter identified by a unique CombatantId and a name.
|
||||
- **CombatantRemoved** (event): A domain event recording the removal, carrying the removed combatant's ID and name.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Removing a combatant from any position in the initiative order preserves correct turn tracking (the intended combatant remains or becomes active).
|
||||
- **SC-002**: All six acceptance scenarios pass as automated tests.
|
||||
- **SC-003**: The round number never changes as a result of removal.
|
||||
- **SC-004**: The UI reflects combatant removal immediately after the action, with no stale state displayed.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- ID generation and lookup is the caller's responsibility, consistent with the addCombatant pattern.
|
||||
- Removal does not trigger a round advance — roundNumber is always preserved.
|
||||
- The domain function is pure: deterministic given identical inputs, no I/O.
|
||||
- The CombatantRemoved event follows the same plain-data-object pattern as existing domain events.
|
||||
- When the active combatant is removed, the next combatant in order inherits the turn (no automatic turn advance or round increment occurs).
|
||||
- Error feedback for invalid removal is a silent no-op for MVP. MVP baseline does not include user-visible error messages for removal failures.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
| Principle | Status | Evidence |
|
||||
|-----------|--------|----------|
|
||||
| I. Deterministic Domain Core | PASS | removeCombatant is a pure state transition with no I/O |
|
||||
| II. Layered Architecture | PASS | Domain function → use case → UI adapter |
|
||||
| III. Agent Boundary | N/A | No agent layer involved |
|
||||
| IV. Clarification-First | PASS | All activeIndex rules fully specified; no ambiguity |
|
||||
| V. Escalation Gates | PASS | All requirements within original spec scope |
|
||||
| VI. MVP Baseline Language | PASS | No permanent bans; confirmation dialog excluded via MVP baseline language |
|
||||
| VII. No Gameplay Rules | PASS | Encounter management only, no game mechanics |
|
||||
Reference in New Issue
Block a user