Files
initiative/specs/016-combatant-ac/research.md

58 lines
3.3 KiB
Markdown

# Research: Combatant Armor Class Display
**Feature**: 016-combatant-ac | **Date**: 2026-03-06
## Research Summary
No NEEDS CLARIFICATION items existed in the Technical Context. Research focused on confirming existing patterns and making design decisions for consistency.
### Decision 1: AC Field Type and Validation
**Decision**: Optional non-negative integer (`ac?: number`), validated as `Number.isInteger(ac) && ac >= 0`.
**Rationale**: Matches the existing pattern for optional numeric combatant fields. AC 0 is valid in tabletop RPGs (e.g., unarmored creatures). Non-negative aligns with D&D 5e rules where AC is always >= 0.
**Alternatives considered**:
- Positive integer only (>= 1): Rejected — AC 0 is valid in edge cases.
- String field for custom values like "17 (with shield)": Rejected — out of MVP scope, adds complexity.
### Decision 2: Domain Function Design
**Decision**: Create a standalone `setAc` pure function following the `setInitiative` pattern (not `setHp`).
**Rationale**: `setInitiative` is the closest analog — a single optional numeric field with no derived state. `setHp` is more complex because it manages the `maxHp`/`currentHp` relationship. AC has no such derived relationship.
**Alternatives considered**:
- Extend `editCombatant` to accept AC: Rejected — `editCombatant` only handles name changes; mixing concerns violates single-responsibility.
- Accept AC during `addCombatant`: Rejected — existing pattern adds combatant with name only, then sets optional fields separately. Consistent UX.
### Decision 3: UI Placement
**Decision**: Display AC as a Lucide `Shield` icon + numeric value in the combatant row, positioned between the name and HP columns.
**Rationale**: AC is a static defensive stat best placed near the combatant identity (name) rather than the dynamic HP section. The shield icon from Lucide React is semantically appropriate and consistent with the existing icon library.
**Alternatives considered**:
- Before the name (left of initiative): Rejected — initiative is the primary sort key and belongs leftmost.
- After HP section: Rejected — AC is more of an identity attribute than a health attribute.
- Emoji shield character: Rejected — Lucide icons are the project standard.
### Decision 4: AC Editing UX
**Decision**: Inline editable field in the combatant row, following the `MaxHpInput` pattern (click to edit, blur/Enter to commit, empty clears).
**Rationale**: Consistent with how initiative and max HP are edited inline. No separate edit dialog needed.
**Alternatives considered**:
- Read-only display with separate edit modal: Rejected — inconsistent with existing inline editing patterns.
- Always-visible input field: Rejected — would add visual noise for combatants without AC.
### Decision 5: Persistence Validation
**Decision**: Validate AC during localStorage rehydration as `typeof entry.ac === "number" && Number.isInteger(entry.ac) && entry.ac >= 0`. Discard invalid values silently (set to `undefined`).
**Rationale**: Matches the defensive deserialization pattern used for `initiative`, `maxHp`, and `currentHp`. Gracefully handles corrupted or manually edited localStorage data.
**Alternatives considered**:
- Strict validation that rejects entire encounter: Rejected — too aggressive for a single optional field.