Add encounter difficulty indicator (5.5e XP budget)
Live 3-bar difficulty indicator in the top bar showing encounter difficulty (Trivial/Low/Moderate/High) based on the 2024 5.5e XP budget system. Automatically derived from PC levels and bestiary creature CRs. - Add optional level field (1-20) to PlayerCharacter - Add CR-to-XP and XP Budget per Character lookup tables in domain - Add calculateEncounterDifficulty pure function - Add DifficultyIndicator component with color-coded bars and tooltip - Add useDifficulty hook composing encounter, PC, and bestiary contexts - Indicator hidden when no PCs with levels or no bestiary-linked monsters - Level field in PC create/edit forms, persisted in storage Closes #18 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
specs/008-encounter-difficulty/tasks.md
Normal file
171
specs/008-encounter-difficulty/tasks.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Tasks: Encounter Difficulty Indicator
|
||||
|
||||
**Input**: Design documents from `/specs/008-encounter-difficulty/`
|
||||
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md
|
||||
|
||||
**Organization**: Tasks are grouped by user story (ED-1 through ED-4) to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., ED-1, ED-3, ED-4)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Foundational (Level field on PlayerCharacter)
|
||||
|
||||
**Purpose**: Add optional `level` field to `PlayerCharacter` — required by all user stories since the difficulty calculation depends on party levels.
|
||||
|
||||
**⚠️ CRITICAL**: The difficulty indicator cannot function without PC levels. This must complete first.
|
||||
|
||||
- [x] T001 Add `level?: number` to `PlayerCharacter` interface in `packages/domain/src/player-character-types.ts`
|
||||
- [x] T002 [P] Add level validation to `createPlayerCharacter()` in `packages/domain/src/create-player-character.ts` — validate if provided: integer, 1-20, error code `"invalid-level"`
|
||||
- [x] T003 [P] Add level validation to `validateFields()` and apply in `applyFields()` in `packages/domain/src/edit-player-character.ts`
|
||||
- [x] T004 [P] Add level tests to `packages/domain/src/__tests__/create-player-character.test.ts` — valid level, no level, out-of-range, non-integer
|
||||
- [x] T005 [P] Add level tests to `packages/domain/src/__tests__/edit-player-character.test.ts` — set level, clear level, invalid level
|
||||
- [x] T006 Update `CreatePlayerCharacterUseCase` to accept and pass `level` in `packages/application/src/create-player-character-use-case.ts`
|
||||
- [x] T007 [P] Update `EditPlayerCharacterUseCase` to accept and pass `level` in `packages/application/src/edit-player-character-use-case.ts`
|
||||
- [x] T008 Update player characters context to pass `level` in create/edit calls in `apps/web/src/contexts/player-characters-context.tsx`
|
||||
- [x] T009 Add level input field to create player modal in `apps/web/src/components/create-player-modal.tsx` — optional number input, 1-20 range
|
||||
- [x] T010 Add level display and edit support in player character manager in `apps/web/src/components/player-character-manager.tsx`
|
||||
- [x] T011 Export updated `PlayerCharacter` type from `packages/domain/src/index.ts` (verify re-export includes level)
|
||||
|
||||
**Checkpoint**: Player characters can be created/edited with an optional level. Existing PCs without level continue to work. All quality gates pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: User Story 4 — XP Budget Calculation (Priority: P1) 🎯 MVP Core
|
||||
|
||||
**Goal**: Implement the pure domain difficulty calculation with CR-to-XP and XP Budget tables.
|
||||
|
||||
**Independent Test**: Verified with unit tests using known party/monster combinations from the 2024 DMG examples.
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [x] T012 Create `packages/domain/src/encounter-difficulty.ts` with `DifficultyTier` type (`"trivial" | "low" | "moderate" | "high"`), `DifficultyResult` interface, `CR_TO_XP` lookup table (Record mapping all CRs 0 through 30 including fractions to XP values), and `XP_BUDGET_PER_CHARACTER` lookup table (Record mapping levels 1-20 to `{ low, moderate, high }`)
|
||||
- [x] T013 Implement `crToXp(cr: string): number` in `packages/domain/src/encounter-difficulty.ts` — returns XP for given CR string, 0 for unknown CRs
|
||||
- [x] T014 Implement `calculateEncounterDifficulty(partyLevels: number[], monsterCrs: string[]): DifficultyResult` in `packages/domain/src/encounter-difficulty.ts` — sums party budget per level, sums monster XP per CR, determines tier by comparing total XP against thresholds
|
||||
- [x] T015 Export `DifficultyTier`, `DifficultyResult`, `crToXp`, `calculateEncounterDifficulty` from `packages/domain/src/index.ts` (same file as T011 — merge into one edit)
|
||||
- [x] T016 Create `packages/domain/src/__tests__/encounter-difficulty.test.ts` with tests for: all CR string formats (0, 1/8, 1/4, 1/2, integers 1-30), unknown CR returns 0, all difficulty tiers (trivial/low/moderate/high), DMG example encounters (4x level 1 vs Bugbear = Low, 5x level 3 vs 1125 XP = Moderate), mixed party levels, empty arrays, High as cap (XP far exceeding High threshold still returns "high")
|
||||
|
||||
**Checkpoint**: Domain difficulty calculation is complete, tested, and exported. All quality gates pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 — See Encounter Difficulty at a Glance (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Display the 3-bar difficulty indicator in the top bar that updates live as combatants change.
|
||||
|
||||
**Independent Test**: Add PC combatants with levels and bestiary-linked monsters — indicator appears with correct tier. Add/remove combatants — indicator updates.
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [x] T017 Create `apps/web/src/hooks/use-difficulty.ts` — hook that consumes `useEncounterContext()`, `usePlayerCharactersContext()`, and `useBestiary()` to derive party levels and monster CRs from current encounter, calls `calculateEncounterDifficulty()`, returns `DifficultyResult | null` (null when insufficient data)
|
||||
- [x] T018 Create `apps/web/src/components/difficulty-indicator.tsx` — renders 3 bars as small `div` elements with Tailwind classes: empty bars for trivial (all `bg-muted`), 1 green bar for low (`bg-green-500`), 2 yellow bars for moderate (`bg-yellow-500`), 3 red bars for high (`bg-red-500`). Add `title` attribute for tooltip (e.g., "Moderate encounter difficulty"). Accept `DifficultyResult` as prop.
|
||||
- [x] T019 Add `DifficultyIndicator` to `TurnNavigation` in `apps/web/src/components/turn-navigation.tsx` — position to the right of the active combatant name inside the center flex section. Use `useDifficulty()` hook; render indicator only when result is non-null.
|
||||
- [x] T019a Add tests for `useDifficulty` hook in `apps/web/src/hooks/__tests__/use-difficulty.test.ts` — verify correct `DifficultyResult` for known combatant/PC/creature combinations, returns null when data is insufficient, and updates when combatants change. Include tooltip text assertions for `DifficultyIndicator`.
|
||||
|
||||
**Checkpoint**: Difficulty indicator appears in top bar for encounters with leveled PCs + bestiary monsters. Updates live. All quality gates pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 — Indicator Hidden When Data Insufficient (Priority: P1)
|
||||
|
||||
**Goal**: Indicator is completely hidden when the encounter lacks PC combatants with levels or bestiary-linked monsters.
|
||||
|
||||
**Independent Test**: Create encounters with only custom combatants, only monsters (no PCs), only PCs without levels — indicator should not appear in any case.
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [x] T020 Review and verify `useDifficulty()` null-return paths implemented in T017 cover all edge cases: no combatants with `playerCharacterId` that have a level, no combatants with `creatureId`, all PCs without levels, all custom combatants, empty encounter. Fix any missing cases.
|
||||
- [x] T021 Verify `TurnNavigation` in `apps/web/src/components/turn-navigation.tsx` renders nothing for the indicator when `useDifficulty()` returns null — confirm conditional rendering is correct.
|
||||
|
||||
**Checkpoint**: Indicator hides correctly for all insufficient-data scenarios. No visual artifacts when hidden.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Final validation and documentation updates.
|
||||
|
||||
- [x] T022 Run `pnpm check` to verify all quality gates pass (audit, knip, biome, oxlint, typecheck, test/coverage, jscpd)
|
||||
- [x] T023 Verify export compatibility — create a player character with level, export encounter JSON, re-import, confirm level is preserved. Verify old exports (without level) still import correctly. If the added `level` field causes old imports to fail, bump `ExportBundle` version and add migration logic in `validateImportBundle()` per CLAUDE.md convention.
|
||||
- [x] T024 Update `specs/005-player-characters/spec.md` to note that `PlayerCharacter` now supports an optional `level` field (added by spec 008)
|
||||
- [x] T025 Update `CLAUDE.md` to add spec 008 to the current feature specs list
|
||||
- [x] T026 Update `README.md` if encounter difficulty is a user-facing feature worth documenting
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Phase 1 (Foundational)**: No dependencies — can start immediately
|
||||
- **Phase 2 (XP Calculation)**: T012-T014 depend on T001 (level type). T016 tests can be written in parallel with T012-T014.
|
||||
- **Phase 3 (Indicator UI)**: Depends on Phase 1 (level in forms) and Phase 2 (calculation function)
|
||||
- **Phase 4 (Visibility)**: Depends on Phase 3 (indicator exists to hide)
|
||||
- **Phase 5 (Polish)**: Depends on all previous phases
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **ED-4 (Calculation)**: Depends on `level` field existing on `PlayerCharacter` (Phase 1, T001)
|
||||
- **ED-1 (Indicator)**: Depends on ED-4 (calculation) + Phase 1 (level in UI)
|
||||
- **ED-2 (Visibility)**: Depends on ED-1 (indicator rendering) — primarily a verification task
|
||||
- **ED-3 (Level field)**: Implemented in Phase 1 as foundational — all stories depend on it
|
||||
|
||||
### Within Each Phase
|
||||
|
||||
- Tasks marked [P] can run in parallel
|
||||
- Domain tasks before application tasks before web tasks
|
||||
- Type definitions before functions using those types
|
||||
- Implementation before tests (unless TDD requested)
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
**Phase 1 parallel group**:
|
||||
```
|
||||
T002 (create validation) ‖ T003 (edit validation) ‖ T004 (create tests) ‖ T005 (edit tests)
|
||||
T006 (create use case) ‖ T007 (edit use case)
|
||||
```
|
||||
|
||||
**Phase 2 parallel group**:
|
||||
```
|
||||
T012 (tables + types) → T013 (crToXp) ‖ T014 (calculateDifficulty) → T016 (tests)
|
||||
```
|
||||
|
||||
**Phase 3 parallel group**:
|
||||
```
|
||||
T017 (hook) ‖ T018 (component) → T019 (integration into top bar)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (Phase 1 + Phase 2 + Phase 3)
|
||||
|
||||
1. Complete Phase 1: Level field on PlayerCharacter
|
||||
2. Complete Phase 2: Domain difficulty calculation + tests
|
||||
3. Complete Phase 3: Indicator in top bar
|
||||
4. **STOP and VALIDATE**: Indicator shows correct difficulty for encounters with leveled PCs and bestiary monsters
|
||||
5. Demo: add a party of level 3 PCs, add some goblins from bestiary, see bars change
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Phase 1 → Level field works in PC forms → Can assign levels immediately
|
||||
2. Phase 2 → Calculation is correct → Domain tests prove it
|
||||
3. Phase 3 → Indicator visible → Feature is usable
|
||||
4. Phase 4 → Edge cases verified → Feature is robust
|
||||
5. Phase 5 → Docs updated → Feature is complete
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies
|
||||
- [Story] label maps task to specific user story for traceability
|
||||
- Story ED-3 (level field) is implemented in Phase 1 as it's foundational to all other stories
|
||||
- The `useDifficulty` hook is the key integration point — it bridges three contexts into one domain call
|
||||
- No new contexts or ports needed — existing patterns handle everything
|
||||
- Commit after each phase checkpoint
|
||||
Reference in New Issue
Block a user