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>
4.6 KiB
Implementation Plan: Encounter Difficulty Indicator
Branch: 008-encounter-difficulty | Date: 2026-03-27 | Spec: spec.md
Input: Feature specification from /specs/008-encounter-difficulty/spec.md
Summary
Add a live 3-bar encounter difficulty indicator to the top bar, based on the 2024 5.5e XP budget system. The domain layer gains pure lookup tables (CR-to-XP, XP Budget per Character) and a difficulty calculation function. The PlayerCharacter type gains an optional level field (1-20). The UI renders a compact bar indicator that derives difficulty from encounter combatants, player character levels, and bestiary creature CRs.
Technical Context
Language/Version: TypeScript 5.8 (strict mode, verbatimModuleSyntax)
Primary Dependencies: React 19, Vite 6, Tailwind CSS v4, Lucide React (icons)
Storage: localStorage (encounter + player characters), IndexedDB (bestiary cache)
Testing: Vitest (v8 coverage)
Target Platform: Web (mobile-first responsive, desktop side panels)
Project Type: Web application (monorepo: domain → application → web adapter)
Performance Goals: Indicator updates within the same render cycle as combatant changes
Constraints: Offline-capable, local-first, single-user. Max 8 props per component.
Scale/Scope: Single-page app, ~15 components, 3-layer architecture
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| I. Deterministic Domain Core | PASS | Difficulty calculation is a pure function: (partyLevels, monsterCRs) → DifficultyResult. No I/O, no randomness, no clocks. Lookup tables are static data. |
| II. Layered Architecture | PASS | New domain module (encounter-difficulty.ts) has zero imports from application/adapter. UI component composes data from existing contexts. |
| II-A. Context-Based State Flow | PASS | Difficulty indicator reads from existing contexts (encounter, player characters, bestiary). No new props beyond what's needed for the component itself. |
| III. Clarification-First | PASS | All design decisions resolved in spec: optional level, 3 tiers, 5.5e rules only, no multipliers, hidden when insufficient data. |
| IV. Escalation Gates | PASS | Feature scoped to spec. MVP exclusions documented (no custom CR, no 2014 rules, no XP numbers in UI). |
| V. MVP Baseline Language | PASS | Exclusions use "MVP baseline does not include" language. |
| VI. No Gameplay Rules in Constitution | PASS | XP tables and difficulty rules live in the feature spec, not the constitution. |
No violations. Complexity Tracking section not needed.
Project Structure
Documentation (this feature)
specs/008-encounter-difficulty/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
└── tasks.md # Phase 2 output (/speckit.tasks)
Source Code (repository root)
packages/domain/src/
├── encounter-difficulty.ts # NEW: CR-to-XP, XP budget tables, difficulty calc
├── player-character-types.ts # MODIFY: add optional level field
├── create-player-character.ts # MODIFY: add level validation
├── edit-player-character.ts # MODIFY: add level validation + apply
├── __tests__/
│ ├── encounter-difficulty.test.ts # NEW: unit tests for difficulty calc
│ ├── create-player-character.test.ts # MODIFY: add level tests
│ └── edit-player-character.test.ts # MODIFY: add level tests
└── index.ts # MODIFY: export new functions/types
packages/application/src/
├── create-player-character-use-case.ts # MODIFY: pass level through
└── edit-player-character-use-case.ts # MODIFY: pass level through
apps/web/src/
├── components/
│ ├── difficulty-indicator.tsx # NEW: 3-bar indicator component
│ ├── turn-navigation.tsx # MODIFY: add indicator to top bar
│ ├── create-player-modal.tsx # MODIFY: add level field
│ └── player-character-manager.tsx # MODIFY: show level, pass to edit
├── hooks/
│ └── use-difficulty.ts # NEW: hook composing contexts → difficulty result
└── contexts/
└── player-characters-context.tsx # MODIFY: pass level to create/edit
Structure Decision: Follows existing layered architecture. New domain module for difficulty calculation. New UI component + hook at adapter layer. No new contexts needed — the difficulty hook composes existing contexts.