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>
82 lines
4.6 KiB
Markdown
82 lines
4.6 KiB
Markdown
# Implementation Plan: Encounter Difficulty Indicator
|
|
|
|
**Branch**: `008-encounter-difficulty` | **Date**: 2026-03-27 | **Spec**: [spec.md](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)
|
|
|
|
```text
|
|
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)
|
|
|
|
```text
|
|
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.
|