Replace 250ms click timer and double-click detection with immediate single-click rename for all combatant types. Add a BookOpen icon before the name on bestiary rows as the dedicated stat block trigger. Remove auto-show stat block on turn advance. Update specs to match: consistent collapse/expand terminology, book icon requirements, no row-click stat block behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
492 lines
39 KiB
Markdown
492 lines
39 KiB
Markdown
# Feature Specification: Combatant State
|
|
|
|
**Feature Branch**: `003-combatant-state`
|
|
**Created**: 2026-03-03
|
|
**Status**: Implemented
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Combatant State covers all per-combatant data tracked during an encounter: hit points, armor class, conditions, concentration, and initiative.
|
|
|
|
**Structure**: This spec is organized by topic area. Each topic section contains its own user scenarios, requirements, and edge cases. The Combatant Row Layout section covers cross-cutting UI concerns.
|
|
|
|
## Domain Model Reference
|
|
|
|
```ts
|
|
interface Combatant {
|
|
readonly id: CombatantId; // branded string
|
|
readonly name: string;
|
|
readonly initiative?: number; // integer, undefined = unset
|
|
readonly maxHp?: number; // positive integer
|
|
readonly currentHp?: number; // 0..maxHp
|
|
readonly ac?: number; // non-negative integer
|
|
readonly conditions?: readonly ConditionId[];
|
|
readonly isConcentrating?: boolean;
|
|
readonly creatureId?: CreatureId; // link to bestiary entry
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Hit Points
|
|
|
|
### User Stories
|
|
|
|
**Story HP-1 — Set Max HP (P1)**
|
|
As a game master, I want to assign a maximum HP value to a combatant so that I can track their health during the encounter.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant exists, **When** the user sets max HP to a positive integer, **Then** the combatant's max HP is stored and current HP defaults to that value.
|
|
2. **Given** a combatant has max HP 20 and current HP 20, **When** the user lowers max HP to 15, **Then** current HP is clamped to 15.
|
|
3. **Given** a combatant has max HP 20 and current HP 20 (full health), **When** the user increases max HP to 30, **Then** current HP increases to 30 (stays at full).
|
|
4. **Given** a combatant has max HP 20 and current HP 12 (not full), **When** the user increases max HP to 30, **Then** current HP remains at 12.
|
|
5. **Given** the max HP inline edit is active, **When** the user clears the field and confirms, **Then** max HP is unset and HP tracking is removed entirely.
|
|
|
|
**Story HP-2 — Apply HP Delta (P1)**
|
|
As a game master in the heat of combat, I want to type a damage or healing number and immediately apply it to a combatant's HP so that I can keep up with fast-paced encounters without mental arithmetic.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has 20/20 HP, **When** the user types 7 into the delta input and presses Enter, **Then** current HP decreases to 13.
|
|
2. **Given** a combatant has 10/20 HP, **When** the user types 15 and presses Enter, **Then** current HP is clamped to 0.
|
|
3. **Given** a combatant has 10/20 HP and types 5 then clicks the heal button, **Then** current HP increases to 15.
|
|
4. **Given** a combatant has 18/20 HP and types 10 then clicks the heal button, **Then** current HP is clamped to 20.
|
|
5. **Given** any confirmed delta, **Then** the input field clears automatically and is ready for the next entry.
|
|
6. **Given** the user types 0 and presses Enter, **Then** the input is rejected and HP remains unchanged.
|
|
7. **Given** the delta input is focused and the user presses Escape, **Then** the input clears without applying any change.
|
|
|
|
**Story HP-3 — Click-to-Adjust Popover (P1)**
|
|
As a DM running combat, I want to click on a combatant's current HP value to open a small adjustment popover so that the combatant row is visually clean and I can still quickly apply damage or healing when needed.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant with max HP set, **When** viewing the row at rest, **Then** only the current HP number and max HP are visible — no delta input or action buttons.
|
|
2. **Given** a combatant with max HP set, **When** the user clicks the current HP number, **Then** a small popover opens with an auto-focused numeric input and Damage/Heal buttons.
|
|
3. **Given** the HP popover is open with a valid number, **When** the user presses Enter, **Then** damage is applied and the popover closes.
|
|
4. **Given** the HP popover is open with a valid number, **When** the user presses Shift+Enter, **Then** healing is applied and the popover closes.
|
|
5. **Given** the HP popover is open, **When** the user presses Escape, **Then** the popover closes without changes.
|
|
6. **Given** the HP popover is open, **When** the user clicks outside, **Then** the popover closes without changes.
|
|
7. **Given** a combatant with no max HP set, **When** viewing the row, **Then** the HP area shows only the max HP clickable placeholder — no current HP value.
|
|
|
|
**Story HP-4 — Direct HP Entry (P2)**
|
|
As a game master, I want to type a specific absolute current HP value directly so I can apply large corrections in one action.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has max HP 50, **When** the user types 35 into the current HP field, **Then** current HP is set to 35.
|
|
2. **Given** a combatant has max HP 50, **When** the user types 60, **Then** current HP is clamped to 50.
|
|
3. **Given** a combatant has max HP 50, **When** the user types -5, **Then** current HP is clamped to 0.
|
|
|
|
**Story HP-5 — HP Status Indicators (P1)**
|
|
As a game master, I want to see at a glance which combatants are bloodied or unconscious so I can narrate the battle and make tactical decisions without mental math.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has max HP 20 and current HP 10 (at half), **Then** no bloodied indicator is shown (10 is not less than 20/2 = 10).
|
|
2. **Given** a combatant has max HP 20 and current HP 9 (below half), **Then** the bloodied indicator is visible (amber color treatment on HP value).
|
|
3. **Given** a combatant has max HP 21 and current HP 10 (below 10.5), **Then** the bloodied indicator is shown.
|
|
4. **Given** a combatant has max HP 20 and current HP 0, **Then** the unconscious/dead indicator is shown (red color; row visually muted).
|
|
5. **Given** a combatant at 0 HP is healed above 0, **Then** the unconscious indicator is removed and the correct status is applied.
|
|
6. **Given** a combatant has no max HP set, **Then** no status indicator is shown.
|
|
7. **Given** a combatant at full HP 20/20, **When** 11 damage is dealt (-> 9/20), **Then** the indicator transitions to bloodied.
|
|
8. **Given** a bloodied combatant 5/20, **When** 5 damage is dealt (-> 0/20), **Then** the indicator transitions to unconscious/dead.
|
|
9. **Given** an unconscious combatant 0/20, **When** 15 HP is healed (-> 15/20), **Then** the indicator transitions directly to healthy (skips bloodied since 15 > 10).
|
|
|
|
**Story HP-6 — HP Persists Across Reloads (P2)**
|
|
As a game master, I want HP values to survive page reloads so that I do not lose health tracking mid-session.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has max HP 30 and current HP 18, **When** the page is reloaded, **Then** both values are restored exactly.
|
|
|
|
### Requirements
|
|
|
|
- **FR-001**: Each combatant MAY have an optional `maxHp` value (positive integer >= 1). HP tracking is optional per combatant.
|
|
- **FR-002**: When `maxHp` is first set, `currentHp` MUST default to `maxHp`.
|
|
- **FR-003**: `currentHp` MUST be clamped to [0, `maxHp`] at all times.
|
|
- **FR-004**: The system MUST provide an inline HP delta input per combatant (hidden behind a click-to-open popover on the current HP value).
|
|
- **FR-005**: The HP popover MUST contain a single auto-focused numeric input and Damage and Heal action buttons.
|
|
- **FR-006**: Pressing Enter in the popover MUST apply the entered value as damage; Shift+Enter MUST apply it as healing; Escape MUST dismiss without change; clicking outside MUST dismiss without change.
|
|
- **FR-007**: When a damage value is confirmed, the system MUST subtract the entered amount from `currentHp`, clamping to 0.
|
|
- **FR-008**: When a healing value is confirmed, the system MUST add the entered amount to `currentHp`, clamping to `maxHp`.
|
|
- **FR-009**: After any delta is applied, the input MUST clear automatically.
|
|
- **FR-010**: The delta input MUST only accept positive integers. Zero, negative, and non-numeric values MUST be rejected.
|
|
- **FR-011**: Direct editing of the absolute `currentHp` value MUST remain available alongside the delta input.
|
|
- **FR-012**: `maxHp` MUST display as compact static text with click-to-edit. The value is committed on Enter or blur; Escape cancels. Intermediate editing (clearing the field to retype) MUST NOT affect `currentHp` until committed.
|
|
- **FR-013**: When `maxHp` is reduced below `currentHp`, `currentHp` MUST be clamped to the new `maxHp`.
|
|
- **FR-014**: When `maxHp` increases and the combatant was at full health, `currentHp` MUST increase to match the new `maxHp`.
|
|
- **FR-015**: When `maxHp` increases and the combatant was NOT at full health, `currentHp` MUST remain unchanged (unless clamped by FR-013).
|
|
- **FR-016**: `maxHp` MUST reject zero, negative, and non-integer values.
|
|
- **FR-017**: HP values MUST persist across page reloads via the existing persistence mechanism.
|
|
- **FR-018**: The HP status MUST be derived as a pure domain computation: `healthy` (currentHp >= maxHp / 2), `bloodied` (0 < currentHp < maxHp / 2), `unconscious` (currentHp <= 0). The status is not stored — computed on demand.
|
|
- **FR-019**: The HP area MUST display the bloodied color treatment (amber) on the current HP value when status is `bloodied`.
|
|
- **FR-020**: The HP area MUST display the unconscious color treatment (red) and the combatant row MUST appear visually muted when status is `unconscious`.
|
|
- **FR-021**: Status indicators MUST NOT be shown when `maxHp` is not set.
|
|
- **FR-022**: Visual status indicators MUST update within the same interaction frame as the HP change — no perceptible delay.
|
|
|
|
### Edge Cases
|
|
|
|
- `maxHp` of 1: at 1/1 the combatant is healthy; at 0/1 unconscious. No bloodied state is possible.
|
|
- `maxHp` of 2: at 1/2 the combatant is healthy (1 is not strictly less than 1); at 0/2 unconscious.
|
|
- When `maxHp` is cleared, `currentHp` is also cleared; the combatant returns to the no-HP state.
|
|
- Entering a non-numeric value in any HP field is rejected; the previous value is preserved.
|
|
- Entering a very large number (e.g., 99999) is applied normally; clamping prevents invalid state.
|
|
- Submitting an empty delta input applies no change; the input remains ready.
|
|
- When the user rapidly applies multiple deltas, each is applied sequentially; none are lost.
|
|
- HP tracking is entirely absent for combatants with no `maxHp` set — no HP controls are shown.
|
|
- There is no temporary HP in the MVP baseline.
|
|
- There is no death/unconscious game mechanic triggered at 0 HP; the system displays the state only.
|
|
- There is no damage type tracking, resistance/vulnerability calculation, or hit log in the MVP baseline.
|
|
- There is no undo/redo for HP changes in the MVP baseline.
|
|
|
|
---
|
|
|
|
## Armor Class
|
|
|
|
### User Stories
|
|
|
|
**Story AC-1 — Set and Display AC (P1)**
|
|
As a game master, I want to assign an Armor Class value to a combatant and see it displayed as a shield shape so I can reference it at a glance during combat.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant exists, **When** the user clicks the AC shield area and enters 17, **Then** the combatant's AC is stored and the shield shape displays "17".
|
|
2. **Given** a combatant with AC 15, **When** viewing the row, **Then** the AC number is displayed inside a shield-shaped outline (not a separate icon + number).
|
|
3. **Given** a combatant with no AC set, **When** viewing the row, **Then** the shield shape is shown in an empty/placeholder state.
|
|
4. **Given** multiple combatants with different AC values, **When** viewing the encounter list, **Then** each displays its own correct AC.
|
|
|
|
**Story AC-2 — Edit AC (P2)**
|
|
As a game master, I want to edit an existing combatant's AC inline so I can correct or update it without navigating away.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant with AC 15, **When** the user clicks the shield, **Then** an inline input appears pre-filled with 15 and selected.
|
|
2. **Given** the inline AC edit is active, **When** the user types 18 and presses Enter, **Then** AC updates to 18 and the display returns to static mode.
|
|
3. **Given** the inline AC edit is active, **When** the user blurs, **Then** AC is committed and the display returns to static mode.
|
|
4. **Given** the inline AC edit is active, **When** the user presses Escape, **Then** the edit is cancelled and the original value is preserved.
|
|
5. **Given** the inline AC edit is active, **When** the user clears the field and presses Enter, **Then** AC is unset and the shield shows an empty state.
|
|
|
|
### Requirements
|
|
|
|
- **FR-023**: Each combatant MAY have an optional `ac` value, a non-negative integer (>= 0).
|
|
- **FR-024**: AC MUST be displayed inside a shield-shaped visual element. The separate shield icon is replaced by the shield shape itself.
|
|
- **FR-025**: The shield shape MUST be shown in all cases (set, unset/empty state). No AC value means an empty-state shield, not hidden.
|
|
- **FR-026**: Clicking the shield MUST open an inline edit input with the current value pre-filled and selected.
|
|
- **FR-027**: The inline AC edit MUST commit on Enter or blur and cancel on Escape.
|
|
- **FR-028**: Clearing the AC field and confirming MUST unset AC.
|
|
- **FR-029**: AC MUST reject negative values. Zero is a valid AC.
|
|
- **FR-030**: AC values MUST persist via the existing persistence mechanism.
|
|
- **FR-031**: The AC shield MUST scale appropriately for single-digit, double-digit, and any valid AC values.
|
|
|
|
### Edge Cases
|
|
|
|
- AC 0 is valid and MUST be displayed.
|
|
- Negative AC is not accepted; the input is rejected.
|
|
- MVP baseline does not include AC-based calculations (to-hit comparisons, conditional formatting based on AC thresholds).
|
|
|
|
---
|
|
|
|
## Conditions & Concentration
|
|
|
|
### User Stories
|
|
|
|
**Story CC-1 — Add a Condition (P1)**
|
|
As a DM running an encounter, I want to quickly apply a condition to a combatant so I can track status effects during combat.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant row is not hovered and has no conditions, **Then** no condition UI is visible.
|
|
2. **Given** a combatant row is hovered, **When** no conditions are active, **Then** a "+" button appears inline after the creature name.
|
|
3. **Given** the "+" button is visible, **When** the user clicks it, **Then** a compact condition picker opens showing all 15 conditions as icon + label pairs.
|
|
4. **Given** the picker is open, **When** the user clicks a condition, **Then** it is toggled on and its icon appears inline after the creature name.
|
|
5. **Given** the picker is open with active conditions already marked, **When** viewing the picker, **Then** active conditions are visually distinguished from inactive ones.
|
|
|
|
**Story CC-2 — Remove a Condition (P1)**
|
|
As a DM, I want to remove a condition from a combatant when the effect ends so the tracker stays accurate.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has active conditions, **When** the user clicks an active condition icon tag inline, **Then** the condition is removed and the icon disappears.
|
|
2. **Given** the condition picker is open, **When** the user clicks an active condition, **Then** it is toggled off and removed from the row.
|
|
3. **Given** a combatant with one condition and it is removed, **Then** only the hover-revealed "+" button remains.
|
|
|
|
**Story CC-3 — View Condition Name via Tooltip (P2)**
|
|
As a DM, I want to hover over a condition icon to see its name so I can identify conditions without memorizing icons.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has an active condition, **When** the user hovers over its icon, **Then** a tooltip shows the condition name (e.g., "Blinded").
|
|
2. **Given** the user moves the cursor away from the icon, **Then** the tooltip disappears.
|
|
|
|
**Story CC-4 — Multiple Conditions (P2)**
|
|
As a DM, I want to apply multiple conditions to a single combatant so I can track complex combat situations.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant with one condition, **When** another is added, **Then** both icons appear inline.
|
|
2. **Given** a combatant with many conditions, **When** viewing the row, **Then** icons wrap within the name column without increasing row width; row height may increase.
|
|
3. **Given** "poisoned" was applied first and "blinded" second, **When** viewing the row, **Then** "blinded" appears before "poisoned" (fixed definition order, not insertion order).
|
|
|
|
**Story CC-5 — Toggle Concentration (P1)**
|
|
As a DM, I want to mark a combatant as concentrating on a spell by clicking a Brain icon in the row gutter so I can track spells requiring concentration.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant row is not hovered and concentration is inactive, **Then** the Brain icon is hidden.
|
|
2. **Given** a combatant row is hovered and concentration is inactive, **Then** the Brain icon appears in a muted/faded style.
|
|
3. **Given** the Brain icon is visible, **When** the user clicks it, **Then** concentration activates and the icon remains visible with an active style.
|
|
4. **Given** concentration is active and the row is not hovered, **Then** the Brain icon remains visible.
|
|
5. **Given** concentration is active, **When** the user clicks the Brain icon again, **Then** concentration deactivates and the icon hides (unless the row is still hovered).
|
|
6. **Given** the Brain icon is visible, **When** the user hovers over it, **Then** a tooltip reading "Concentrating" appears.
|
|
|
|
**Story CC-6 — Visual Feedback for Concentration (P2)**
|
|
As a DM, I want concentrating combatants to have a visible row accent so I can identify them at a glance without interacting.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** concentration is active, **When** viewing the encounter tracker, **Then** the combatant row shows a colored left border accent (`border-l-purple-400`).
|
|
2. **Given** concentration is inactive, **Then** no concentration accent is shown.
|
|
3. **Given** concentration is toggled off, **Then** the left border accent disappears immediately.
|
|
|
|
**Story CC-7 — Damage Pulse Alert (P3)**
|
|
As a DM, I want a visual alert when a concentrating combatant takes damage so I remember to call for a concentration check.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant is concentrating, **When** the combatant takes damage (HP reduced), **Then** the Brain icon and row accent briefly pulse/flash for 700 ms.
|
|
2. **Given** a combatant is concentrating, **When** the combatant is healed, **Then** no pulse/flash occurs.
|
|
3. **Given** a combatant is NOT concentrating, **When** damage is taken, **Then** no pulse/flash occurs.
|
|
4. **Given** a concentrating combatant takes damage, **When** the animation completes, **Then** the row returns to its normal concentration-active appearance.
|
|
|
|
### Requirements
|
|
|
|
- **FR-032**: The MVP MUST support the following 15 standard D&D 5e conditions: blinded, charmed, deafened, exhaustion, frightened, grappled, incapacitated, invisible, paralyzed, petrified, poisoned, prone, restrained, stunned, unconscious.
|
|
- **FR-033**: Each condition MUST have a fixed icon and color mapping (Lucide icons; no emoji):
|
|
|
|
| Condition | Icon | Color |
|
|
|---------------|------------|---------|
|
|
| Blinded | EyeOff | neutral |
|
|
| Charmed | Heart | pink |
|
|
| Deafened | EarOff | neutral |
|
|
| Exhaustion | BatteryLow | amber |
|
|
| Frightened | Siren | orange |
|
|
| Grappled | Hand | neutral |
|
|
| Incapacitated | Ban | gray |
|
|
| Invisible | Ghost | violet |
|
|
| Paralyzed | ZapOff | yellow |
|
|
| Petrified | Gem | slate |
|
|
| Poisoned | Droplet | green |
|
|
| Prone | ArrowDown | neutral |
|
|
| Restrained | Link | neutral |
|
|
| Stunned | Sparkles | yellow |
|
|
| Unconscious | Moon | indigo |
|
|
|
|
- **FR-034**: Active condition icons MUST appear inline after the creature name within the same row, not on a separate line.
|
|
- **FR-035**: Conditions MUST be displayed in the fixed definition order (blinded -> unconscious), regardless of application order.
|
|
- **FR-036**: The "+" condition button MUST be hidden by default and appear only on row hover (or touch/focus on touch devices).
|
|
- **FR-037**: Clicking "+" MUST open the compact condition picker showing all conditions as icon + label pairs.
|
|
- **FR-038**: Clicking a condition in the picker MUST toggle it on or off.
|
|
- **FR-039**: Clicking an active condition icon tag in the row MUST remove that condition.
|
|
- **FR-040**: Hovering an active condition icon MUST show a tooltip with the condition name.
|
|
- **FR-041**: Condition icons MUST NOT increase the row's width; row height MAY increase to accommodate wrapping.
|
|
- **FR-042**: The condition picker MUST close when the user clicks outside of it.
|
|
- **FR-043**: Conditions MUST persist as part of combatant state (surviving page reload).
|
|
- **FR-044**: The condition data model MUST be extensible for future additions (e.g., mechanical effects, descriptions).
|
|
- **FR-045**: `isConcentrating` MUST be stored as an optional boolean on the combatant, separate from the `conditions` array.
|
|
- **FR-046**: Concentration MUST NOT appear in or interact with the condition tag system.
|
|
- **FR-047**: The Brain icon toggle MUST be hidden at row rest and revealed on hover (same hover pattern as the "+" button).
|
|
- **FR-048**: The Brain icon MUST remain visible whenever concentration is active, regardless of hover state.
|
|
- **FR-049**: A tooltip reading "Concentrating" MUST appear when hovering the Brain icon.
|
|
- **FR-050**: The active Brain icon MUST use `text-purple-400`; the inactive (hover-revealed) Brain icon MUST use a muted style (`text-muted-foreground opacity-50`).
|
|
- **FR-051**: The concentration left border accent MUST use `border-l-purple-400`.
|
|
- **FR-052**: The concentration toggle's clickable area MUST extend to fill the full gutter between the left border and the initiative column.
|
|
- **FR-053**: When a concentrating combatant takes damage, the Brain icon and row accent MUST briefly pulse/flash for 700 ms.
|
|
- **FR-054**: The pulse animation MUST NOT trigger on healing or when concentration is inactive.
|
|
- **FR-055**: Concentration MUST persist across page reloads via existing storage.
|
|
|
|
### Edge Cases
|
|
|
|
- When all 15 conditions are applied, icons wrap within the row; row height increases but width does not.
|
|
- When a combatant is removed, all its conditions and concentration state are discarded.
|
|
- When the picker is open and the user clicks outside, it closes.
|
|
- When a condition is toggled on then immediately off in the picker, it does not appear in the row.
|
|
- When concentration is toggled during an active pulse animation, the animation cancels and the new state applies immediately.
|
|
- Multiple combatants may concentrate simultaneously; concentration is independent per combatant.
|
|
- Conditions have no mechanical effects in the MVP baseline (no auto-disadvantage, no automation).
|
|
|
|
---
|
|
|
|
## Initiative
|
|
|
|
### User Stories
|
|
|
|
**Story INI-1 — Set Initiative Value (P1)**
|
|
As a game master running an encounter, I want to assign an initiative value to a combatant so that the encounter's turn order reflects each combatant's rolled initiative.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant with no initiative set, **When** the user sets initiative to 15, **Then** the combatant has initiative value 15.
|
|
2. **Given** a combatant with initiative 15, **When** the user changes initiative to 8, **Then** the combatant has initiative value 8.
|
|
3. **Given** a combatant with no initiative set, **When** the user attempts to set a non-integer value, **Then** the system rejects the input and initiative remains unset.
|
|
4. **Given** a combatant with initiative 15, **When** the user clears initiative, **Then** the initiative is unset and the combatant moves to the end of the turn order.
|
|
5. **Given** a combatant has an initiative value displayed as plain text, **When** the user clicks it, **Then** an inline editor opens to change or clear it.
|
|
|
|
**Story INI-2 — Roll Initiative for a Single Combatant (P1)**
|
|
As a DM, I want to click a d20 icon next to a combatant's initiative slot to randomly roll initiative (1d20 + initiative modifier) so the result is immediately placed into the initiative field and the tracker re-sorts.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant linked to a bestiary creature (e.g., Aboleth, initiative modifier +7) with no initiative, **When** the user clicks the d20 icon, **Then** a random value in the range [8, 27] is stored as initiative and the list re-sorts descending.
|
|
2. **Given** a combatant NOT linked to a bestiary creature, **When** viewing the row, **Then** the initiative slot shows "--" (clickable to type a value manually) — no d20 button.
|
|
3. **Given** a combatant whose initiative modifier is negative (e.g., -2), **When** the d20 button is clicked, **Then** the result ranges from -1 to 18.
|
|
4. **Given** a combatant already has an initiative value, **Then** the d20 button is replaced by the value as plain text; clicking it opens the inline editor.
|
|
|
|
**Story INI-3 — Roll Initiative for All Eligible Combatants (P2)**
|
|
As a DM, I want a "Roll All Initiative" button in the top bar to roll for all bestiary combatants at once so I can set up the initiative order quickly at the start of combat.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** 3 bestiary combatants (no initiative) and 1 manual combatant, **When** the roll-all button is clicked, **Then** all 3 bestiary combatants receive rolled initiative values; the manual combatant is unchanged.
|
|
2. **Given** bestiary combatants that already have initiative values, **When** the roll-all button is clicked, **Then** those combatants are skipped; only bestiary combatants without initiative are rolled.
|
|
3. **Given** no bestiary combatants, **When** the roll-all button is clicked, **Then** no changes occur.
|
|
|
|
**Story INI-4 — Display Initiative Modifier in Stat Block (P1)**
|
|
As a DM viewing a creature's stat block, I want to see the creature's initiative modifier and passive initiative so I can reference it when rolling.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a creature with DEX 9, CR 10, initiative proficiency multiplier 2 (e.g., Aboleth), **When** the stat block is displayed, **Then** the initiative line shows "Initiative +7 (17)" (-1 + 2x4 = +7; passive = 17).
|
|
2. **Given** a creature with proficiency multiplier 1, **Then** the initiative modifier includes 1x the proficiency bonus.
|
|
3. **Given** a creature with no `initiative` field in bestiary data, **Then** only the DEX modifier is shown (e.g., DEX 14 -> "Initiative +2 (12)").
|
|
4. **Given** a creature with a negative initiative modifier (e.g., DEX 8, no proficiency), **Then** the line uses a minus sign (e.g., "Initiative -1 (9)").
|
|
5. **Given** a combatant without bestiary data, **Then** no initiative line is shown in the stat block.
|
|
|
|
**Story INI-5 — Combatants Without Initiative (P2)**
|
|
As a game master, I want combatants without initiative set to appear at the end of the turn order so the encounter remains usable while I am still entering values.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** combatants A (initiative 15), B (no initiative), C (initiative 10), **Then** order is A, C, B.
|
|
2. **Given** A (no initiative) and B (no initiative), **Then** their relative order is preserved from when they were added.
|
|
3. **Given** combatant A (no initiative), **When** initiative is set to 12, **Then** A moves to its correct sorted position.
|
|
|
|
### Requirements
|
|
|
|
- **FR-056**: System MUST allow setting an integer initiative value for any combatant.
|
|
- **FR-057**: System MUST allow changing an existing initiative value.
|
|
- **FR-058**: System MUST allow clearing (unsetting) a combatant's initiative value.
|
|
- **FR-059**: System MUST reject non-integer initiative values and return a domain error.
|
|
- **FR-060**: System MUST accept zero and negative integers as valid initiative values.
|
|
- **FR-061**: System MUST automatically reorder combatants highest-to-lowest initiative whenever a value is set, changed, or cleared.
|
|
- **FR-062**: Combatants without initiative MUST be placed after all combatants with initiative values.
|
|
- **FR-063**: System MUST use a stable sort so combatants with equal initiative (or multiple without initiative) retain their relative order.
|
|
- **FR-064**: System MUST preserve the active combatant's turn through reorders — the active turn tracks combatant identity, not list position.
|
|
- **FR-065**: System MUST emit a domain event when a combatant's initiative is set or changed.
|
|
- **FR-066**: System MUST display a d20 icon button in the initiative slot for every combatant that has a linked bestiary creature and does not yet have an initiative value.
|
|
- **FR-067**: System MUST NOT display the d20 button for combatants without a linked bestiary creature; they see a "--" placeholder that is clickable to type a value.
|
|
- **FR-068**: When the d20 button is clicked, the system MUST generate a uniform random integer in [1, 20], add the creature's initiative modifier, and set the result as the initiative value.
|
|
- **FR-069**: The initiative modifier MUST be calculated as: DEX modifier + (proficiency multiplier x proficiency bonus), where DEX modifier = floor((DEX - 10) / 2) and proficiency bonus is derived from challenge rating.
|
|
- **FR-070**: The initiative proficiency multiplier MUST be read from `initiative.proficiency` in bestiary data (0 if absent, 1 for proficiency, 2 for expertise).
|
|
- **FR-071**: System MUST provide a "Roll All Initiative" button in the top bar. Clicking it MUST roll for every bestiary-linked combatant that does not already have an initiative value; all others MUST be left unchanged.
|
|
- **FR-072**: After any initiative roll (single or batch), the encounter list MUST re-sort descending per existing behavior.
|
|
- **FR-073**: Once a combatant has an initiative value, the d20 button is replaced by the value as plain text using a click-to-edit pattern (consistent with AC and name editing).
|
|
- **FR-074**: The stat block header MUST display initiative in the format "Initiative +X (Y)" where X is the modifier (with + or - sign) and Y = 10 + X.
|
|
- **FR-075**: The initiative display in the stat block MUST be positioned adjacent to the AC value, matching Monster Manual 2024 layout.
|
|
- **FR-076**: No initiative line MUST be shown in the stat block for combatants without bestiary data.
|
|
- **FR-077**: The initiative display in the stat block is display-only. It MUST NOT modify encounter turn order or trigger rolling automatically.
|
|
- **FR-078**: The d20 roll-initiative icon MUST be displayed larger than 20x20 px while remaining contained within the initiative column.
|
|
- **FR-079**: The "Roll All Initiative" d20 in the top bar MUST be sized at 24 px; the clear-encounter (trash) icon MUST be sized at 20 px. Both are visually grouped together, separated from turn navigation controls by spacing.
|
|
|
|
### Edge Cases
|
|
|
|
- Setting initiative to zero is valid and treated normally in sorting.
|
|
- Negative initiative values are valid (some game systems use them).
|
|
- When a combatant is added without initiative during an ongoing encounter, it appears at the end of the order.
|
|
- When all combatants have the same initiative value, their insertion order is preserved.
|
|
- When the active combatant's own initiative changes causing a reorder, the active turn still points to that combatant.
|
|
- When another combatant's initiative changes causing a reorder, the active turn still points to the current active combatant.
|
|
- When a combatant's initiative modifier produces a roll result of 0 or negative, the value is stored as-is.
|
|
- When multiple combatants roll the same initiative, ties are resolved by preserving relative insertion order.
|
|
- A minus sign is used for negative modifiers in the stat block display, not a hyphen.
|
|
- When a creature has DEX producing a modifier of exactly 0 and no initiative proficiency, display "Initiative +0 (10)".
|
|
- For manually-added combatants: no initiative modifier is available, so no d20 button and no stat block initiative line.
|
|
- The passive initiative value shown in the stat block is reference-only; only the active modifier (+X) is used for rolling.
|
|
- Random number generation for dice rolls uses standard browser randomness; cryptographic randomness is not required.
|
|
|
|
---
|
|
|
|
## Combatant Row Layout
|
|
|
|
### User Stories
|
|
|
|
**Story ROW-1 — Compact Resting State (P1)**
|
|
As a DM, I want each combatant row to display a minimal, uncluttered view at rest so I can scan the encounter list quickly during play.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant row is not hovered, **Then** no delta input, action buttons, "+" condition button, remove button, or Brain icon are visible.
|
|
2. **Given** a combatant has no conditions, **Then** the row occupies exactly one line height at rest.
|
|
3. **Given** a combatant has conditions applied, **Then** condition icons appear inline after the creature name on the same row (not a separate line).
|
|
|
|
**Story ROW-2 — Hover Reveals Controls (P1)**
|
|
As a DM, I want secondary controls to appear when I hover over a row so they are accessible without cluttering the resting view.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** any combatant row, **When** hovered, **Then** the "+" condition button appears inline after the name/conditions.
|
|
2. **Given** any combatant row, **When** hovered, **Then** the remove (x) button becomes visible.
|
|
3. **Given** any combatant row, **When** hovered and concentration is inactive, **Then** the Brain icon becomes visible.
|
|
4. **Given** the remove button appears on hover, **Then** no layout shift occurs — space is reserved.
|
|
|
|
**Story ROW-3 — Book Icon Opens Stat Block (P1)**
|
|
As a DM, I want a dedicated book icon on bestiary combatant rows so I can open the stat block with an explicit, discoverable control — while clicking the name always starts a rename.
|
|
|
|
Acceptance scenarios:
|
|
1. **Given** a combatant has a linked bestiary creature, **When** the user views the row, **Then** a small BookOpen icon is visible next to the name.
|
|
2. **Given** a combatant does NOT have a linked creature, **When** the user views the row, **Then** no BookOpen icon is displayed.
|
|
3. **Given** a bestiary combatant row, **When** the user clicks the BookOpen icon, **Then** the stat block panel opens for that creature.
|
|
4. **Given** the user clicks the combatant's name, **Then** inline rename mode is entered — the stat block does NOT open.
|
|
5. **Given** the stat block is already open for a creature, **When** the user clicks its BookOpen icon again, **Then** the panel closes (toggle behavior).
|
|
|
|
### Requirements
|
|
|
|
- **FR-080**: Condition icons MUST render inline after the creature name within the same row.
|
|
- **FR-081**: The "+" condition button MUST be hidden at rest and appear on row hover (or touch/focus on touch devices).
|
|
- **FR-082**: The remove (x) button MUST be hidden at rest and appear on row hover (or touch/focus on touch devices).
|
|
- **FR-083**: Layout space for the remove button MUST be reserved so appearing/disappearing does not cause layout shifts.
|
|
- **FR-084**: Bestiary-linked combatant rows MUST display a BookOpen icon as the dedicated stat block trigger (see also `specs/004-bestiary/spec.md`, FR-062).
|
|
- **FR-085**: Clicking the combatant name MUST enter inline rename mode, not open the stat block.
|
|
- **FR-086**: Non-bestiary combatant rows MUST NOT display the BookOpen icon.
|
|
- **FR-087**: The BookOpen icon MUST have a tooltip ("View stat block") and `aria-label="View stat block"` for accessibility.
|
|
- **FR-088**: All existing interactions (condition add/remove, HP adjustment, AC editing, initiative editing/rolling, concentration toggle, combatant removal) MUST continue to work.
|
|
- **FR-089**: Browser scrollbars MUST be styled to match the dark UI theme (thin, dark-colored scrollbar thumbs).
|
|
- **FR-090**: Turn navigation (Previous/Next) MUST use StepBack/StepForward icons in outline button style with foreground-colored borders. Utility actions (d20/trash) MUST use ghost button style to create clear visual hierarchy.
|
|
- **FR-091**: Previous and Next turn buttons MUST be positioned at the far left and far right of the top bar respectively, with the round/combatant info centered between them.
|
|
- **FR-092**: The "Initiative Tracker" heading MUST be removed to maximize vertical space for combatants.
|
|
- **FR-093**: All interactive elements in the row MUST remain keyboard-accessible (focusable and operable via keyboard).
|
|
- **FR-094**: On touch devices, hover-only controls ("+" button, "x" button) MUST remain accessible via tap or focus.
|
|
|
|
### Edge Cases
|
|
|
|
- When a combatant has so many conditions that they exceed the available inline space, they wrap within the name column; row height increases but width does not.
|
|
- The condition picker dropdown positions relative to the "+" button, flipping vertically if near the viewport edge.
|
|
- When the stat block panel is already open and the user clicks the same BookOpen icon again, the panel closes.
|
|
- Clicking the combatant name starts inline rename; it does not open the stat block.
|
|
- Tablet-width screens (>= 768 px / Tailwind `md`): popovers and inline edits MUST remain accessible and not overflow or clip.
|
|
|
|
---
|
|
|
|
## Success Criteria *(mandatory)*
|
|
|
|
- **SC-001**: Users can set max HP and adjust current HP for any combatant in under 5 seconds per action.
|
|
- **SC-002**: `currentHp` never exceeds `maxHp` or drops below 0, regardless of input method.
|
|
- **SC-003**: HP values survive a full page reload without data loss.
|
|
- **SC-004**: A user can apply damage to a combatant in 2 interactions or fewer (click HP -> type number -> Enter).
|
|
- **SC-005**: A user can apply healing in 3 interactions or fewer (click HP -> type number -> click Heal).
|
|
- **SC-006**: Users can identify bloodied combatants at a glance without reading HP numbers — 100% of combatants below half HP display a distinct amber treatment.
|
|
- **SC-007**: Users can identify unconscious/dead combatants at a glance — 100% of combatants at 0 HP or below display a distinct red treatment that differs from the bloodied indicator.
|
|
- **SC-008**: Visual status indicators update within the same interaction frame as the HP change.
|
|
- **SC-009**: Users can set an AC value for any combatant within the existing edit workflow with no additional steps.
|
|
- **SC-010**: AC is visible at a glance in the encounter list without expanding or hovering.
|
|
- **SC-011**: A condition can be added to a combatant in 2 clicks or fewer (click "+", click condition).
|
|
- **SC-012**: A condition can be removed in 1 click (click the active icon tag).
|
|
- **SC-013**: All 15 D&D 5e conditions are available and visually distinguishable by icon and color.
|
|
- **SC-014**: Condition state survives a full page reload without data loss.
|
|
- **SC-015**: Users can toggle concentration on/off for any combatant in a single click.
|
|
- **SC-016**: Concentrating combatants are visually distinguishable from non-concentrating combatants at a glance.
|
|
- **SC-017**: When a concentrating combatant takes damage, the visual pulse alert draws attention within the same interaction flow.
|
|
- **SC-018**: Concentration state survives a full page reload.
|
|
- **SC-019**: Users can set initiative for any combatant in a single action.
|
|
- **SC-020**: After any initiative change, the encounter list immediately reflects the correct descending sort.
|
|
- **SC-021**: A single combatant's initiative can be rolled with one click (the d20 button).
|
|
- **SC-022**: All eligible combatants' initiative can be rolled with one click (roll-all button).
|
|
- **SC-023**: Manual combatants (no stat block) are never affected by roll actions.
|
|
- **SC-024**: Every bestiary creature displays an initiative value in its stat block matching D&D Beyond / Monster Manual 2024.
|
|
- **SC-025**: The initiative line is visible without scrolling in the stat block header.
|
|
- **SC-026**: Each combatant row without conditions takes up exactly one line height at rest.
|
|
- **SC-027**: The DM can open a stat block by clicking anywhere on the combatant name area without needing a dedicated icon.
|
|
- **SC-028**: The AC number is visually identifiable as armor class through the shield shape alone.
|
|
- **SC-029**: No layout shift occurs when hovering/unhovering rows.
|
|
- **SC-030**: All HP, AC, initiative, condition, and concentration interactions remain fully operable using only a keyboard.
|