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.9 KiB
3.9 KiB
Data Model: Encounter Difficulty Indicator
Date: 2026-03-27 | Feature: 008-encounter-difficulty
Entities
PlayerCharacter (modified)
Existing entity from spec 005. Adding one optional field.
| Field | Type | Required | Notes |
|---|---|---|---|
| id | PlayerCharacterId | yes | Existing — branded string |
| name | string | yes | Existing |
| ac | number | yes | Existing |
| maxHp | number | yes | Existing |
| color | PlayerColor | no | Existing |
| icon | PlayerIcon | no | Existing |
| level | number | no | NEW — integer 1-20. Used for XP budget calculation. PCs without level are excluded from difficulty calc. |
Validation rules for level:
- If provided, must be an integer
- If provided, must be >= 1 and <= 20
- If omitted/undefined, PC is excluded from difficulty budget
DifficultyTier (new)
Enumeration of encounter difficulty categories.
| Value | Display Label | Visual |
|---|---|---|
"trivial" |
Trivial | 3 empty bars |
"low" |
Low | 1 green bar |
"moderate" |
Moderate | 2 yellow bars |
"high" |
High | 3 red bars |
DifficultyResult (new)
Output of the difficulty calculation. Pure data object.
| Field | Type | Notes |
|---|---|---|
| tier | DifficultyTier | The determined difficulty category |
| totalMonsterXp | number | Sum of XP for all bestiary-linked combatants |
| partyBudget | { low: number; moderate: number; high: number } | XP thresholds for the party |
XP Budget per Character (static lookup)
Maps character level to XP thresholds. Data from 2024 5.5e DMG.
| Level | Low | Moderate | High |
|---|---|---|---|
| 1 | 50 | 75 | 100 |
| 2 | 100 | 150 | 200 |
| 3 | 150 | 225 | 400 |
| 4 | 250 | 375 | 500 |
| 5 | 500 | 750 | 1,100 |
| 6 | 600 | 1,000 | 1,400 |
| 7 | 750 | 1,300 | 1,700 |
| 8 | 1,000 | 1,700 | 2,100 |
| 9 | 1,300 | 2,000 | 2,600 |
| 10 | 1,600 | 2,300 | 3,100 |
| 11 | 1,900 | 2,900 | 4,100 |
| 12 | 2,200 | 3,700 | 4,700 |
| 13 | 2,600 | 4,200 | 5,400 |
| 14 | 2,900 | 4,900 | 6,200 |
| 15 | 3,300 | 5,400 | 7,800 |
| 16 | 3,800 | 6,100 | 9,800 |
| 17 | 4,500 | 7,200 | 11,700 |
| 18 | 5,000 | 8,700 | 14,200 |
| 19 | 5,500 | 10,700 | 17,200 |
| 20 | 6,400 | 13,200 | 22,000 |
CR-to-XP (static lookup)
Maps challenge rating strings to XP values. Standard 5e values.
| CR | XP |
|---|---|
| 0 | 0 |
| 1/8 | 25 |
| 1/4 | 50 |
| 1/2 | 100 |
| 1 | 200 |
| 2 | 450 |
| 3 | 700 |
| 4 | 1,100 |
| 5 | 1,800 |
| 6 | 2,300 |
| 7 | 2,900 |
| 8 | 3,900 |
| 9 | 5,000 |
| 10 | 5,900 |
| 11 | 7,200 |
| 12 | 8,400 |
| 13 | 10,000 |
| 14 | 11,500 |
| 15 | 13,000 |
| 16 | 15,000 |
| 17 | 18,000 |
| 18 | 20,000 |
| 19 | 22,000 |
| 20 | 25,000 |
| 21 | 33,000 |
| 22 | 41,000 |
| 23 | 50,000 |
| 24 | 62,000 |
| 25 | 75,000 |
| 26 | 90,000 |
| 27 | 105,000 |
| 28 | 120,000 |
| 29 | 135,000 |
| 30 | 155,000 |
Relationships
PlayerCharacter (has optional level)
│
▼ linked via playerCharacterId
Combatant (in Encounter)
│
▼ linked via creatureId
Creature (has cr string)
│
▼ lookup via CR_TO_XP table
XP value (number)
Party levels ──► XP_BUDGET_TABLE ──► { low, moderate, high } thresholds
Monster XP total ──► compare against thresholds ──► DifficultyTier
State Transitions
The difficulty calculation is stateless — it's a pure derivation from current encounter state. No state machine or transitions to model.
Input derivation (at adapter layer):
- For each combatant with
playerCharacterId→ look upPlayerCharacter.level→ collect non-undefined levels - For each combatant with
creatureId→ look upCreature.cr→ collect CR strings - Pass
(levels[], crs[])to domain function
Pure calculation (domain layer):
- Sum XP budget per level →
partyBudget.{low, moderate, high} - Convert each CR to XP → sum →
totalMonsterXp - Compare
totalMonsterXpagainst thresholds →DifficultyTier