Files
initiative/specs/005-set-initiative/research.md

50 lines
3.3 KiB
Markdown

# Research: Set Initiative
## R-001: Stable Sort for Initiative Ordering
**Decision**: Use JavaScript's built-in `Array.prototype.sort()` which is guaranteed stable (ES2019+). Combatants with equal initiative retain their relative order from the original array.
**Rationale**: All modern browsers and Node.js engines implement stable sort. No external library needed. The existing codebase already relies on insertion-order preservation in array operations.
**Alternatives considered**:
- Custom merge sort implementation — unnecessary since native sort is stable.
- Separate "sort key" field — over-engineering for the current requirement.
## R-002: Active Turn Preservation Through Reorder
**Decision**: After sorting, find the new index of the combatant who was active before the sort (by `CombatantId` identity). Update `activeIndex` to point to that combatant's new position.
**Rationale**: The existing `removeCombatant` function already demonstrates the pattern of adjusting `activeIndex` to track a specific combatant through array mutations. This approach is simpler than alternatives since we can look up the active combatant's id before sorting, then find its new index after sorting.
**Alternatives considered**:
- Store active combatant as `activeCombatantId` instead of `activeIndex` — would require changing the `Encounter` type and all downstream consumers. Too broad for this feature.
- Compute a position delta — fragile and error-prone with stable sort edge cases.
## R-003: Initiative as Optional Property on Combatant
**Decision**: Add `readonly initiative?: number` to the `Combatant` interface. `undefined` means "not yet set."
**Rationale**: Matches the spec requirement for combatants without initiative (FR-005). Using `undefined` (optional property) rather than `null` aligns with TypeScript conventions and the existing codebase style (no `null` usage in domain types).
**Alternatives considered**:
- Separate `InitiativeMap` keyed by `CombatantId` — breaks co-location, complicates sorting, doesn't match the existing pattern where combatant data lives on the `Combatant` type.
- `number | null` — adds a second "empty" representation alongside `undefined`; the codebase has no precedent for `null` in domain types.
## R-004: Clearing Initiative
**Decision**: Clearing initiative means setting it to `undefined`. The `setInitiative` function accepts `number | undefined` as the value parameter. When `undefined`, the combatant moves to the end of the order (per FR-003, FR-005).
**Rationale**: Reuses the same function for set, change, and clear operations. Keeps the API surface minimal.
**Alternatives considered**:
- Separate `clearInitiative` function — unnecessary given the value can simply be `undefined`.
## R-005: Integer Validation
**Decision**: Validate that the initiative value is a safe integer using `Number.isInteger()`. Reject `NaN`, `Infinity`, and floating-point values. Accept zero and negative integers (per FR-009).
**Rationale**: `Number.isInteger()` handles all edge cases: returns false for `NaN`, `Infinity`, `-Infinity`, and non-integer numbers. Allows the full range of safe integers.
**Alternatives considered**:
- Branded `Initiative` type — adds type complexity without significant safety benefit since validation happens at the domain boundary.