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>
3.2 KiB
3.2 KiB
Quickstart: Encounter Difficulty Indicator
Date: 2026-03-27 | Feature: 008-encounter-difficulty
Implementation Order
Phase 1: Domain — Level field + validation
- Add
level?: numbertoPlayerCharacterinplayer-character-types.ts - Add level validation to
createPlayerCharacter()— validate if provided: integer, 1-20 - Add level validation to
editPlayerCharacter()— same rules invalidateFields(), apply inapplyFields() - Add tests for level validation in existing test files
- Export updated types from
index.ts
Phase 2: Domain — Difficulty calculation
- Create
encounter-difficulty.tswith:CR_TO_XPlookup (Record<string, number>)XP_BUDGET_PER_CHARACTERlookup (Record<number, { low, moderate, high }>)crToXp(cr: string): number— returns 0 for unknown CRscalculateEncounterDifficulty(partyLevels: number[], monsterCrs: string[]): DifficultyResultDifficultyTiertype andDifficultyResulttype
- Add comprehensive unit tests covering:
- All CR string formats (0, 1/8, 1/4, 1/2, integers)
- All difficulty tiers including trivial
- DMG example encounters (from issue comments)
- Edge cases: empty arrays, unknown CRs, mixed levels
- Export from
index.ts
Phase 3: Application — Pass level through use cases
- Update
CreatePlayerCharacterUseCaseto accept and passlevel - Update
EditPlayerCharacterUseCaseto accept and passlevel
Phase 4: Web — Level field in PC forms
- Update player characters context to pass
levelin create/edit calls - Add level input field to create player modal (optional number, 1-20)
- Add level display + edit in player character manager
- Test: create PC with level, edit level, verify persistence
Phase 5: Web — Difficulty indicator
- Create
useDifficulty()hook:- Consume encounter context, player characters context, bestiary hook
- Map combatants → party levels + monster CRs
- Call domain
calculateEncounterDifficulty() - Return
DifficultyResult | null(null when insufficient data)
- Create
DifficultyIndicatorcomponent:- Render 3 bars with conditional fill colors
- Add
titleattribute for tooltip - Hidden when hook returns null
- Add indicator to
TurnNavigationcomponent, right of active combatant name - Test: manual verification with various encounter compositions
Key Patterns to Follow
- Domain purity:
calculateEncounterDifficultytakesnumber[]andstring[], not domain types - Validation pattern: Follow
color/iconoptional field pattern in create/edit - Hook composition:
useDifficultycomposes multiple contexts likeuseInitiativeRolls - Component size: DifficultyIndicator should be <8 props (likely 0-1, just the result)
Testing Strategy
- Domain tests (unit): Exhaustive coverage of
calculateEncounterDifficultyandcrToXpwith table-driven tests. Cover all 34 CR values, all 20 levels, and the DMG example encounters. - Domain tests (level validation): Test create/edit with valid levels, invalid levels, and undefined level.
- Integration: Verify indicator appears/hides correctly through component rendering (if existing test patterns support this).