3.3 KiB
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
editCombatantto accept AC: Rejected —editCombatantonly 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.