Files
Lukas ef76b9c90b
All checks were successful
CI / check (push) Successful in 1m13s
CI / build-image (push) Successful in 16s
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>
2026-03-27 22:55:48 +01:00

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.