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

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 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.