Add 2014 DMG encounter difficulty calculation
All checks were successful
CI / check (push) Successful in 2m18s
CI / build-image (push) Successful in 17s

Support the 2014 DMG encounter difficulty as an alternative to the 5.5e
system behind the existing Rules Edition toggle. The 2014 system uses
Easy/Medium/Hard/Deadly thresholds, an encounter multiplier based on
monster count, and party size adjustment (×0.5–×5 range).

- Extract RulesEdition to its own domain module
- Refactor DifficultyTier to abstract numeric values (0–3)
- Restructure DifficultyResult with thresholds array
- Add 2014 XP thresholds table and encounter multiplier logic
- Wire edition from context into difficulty hooks
- Edition-aware labels in indicator and breakdown panel
- Show multiplier, adjusted XP, and party size note for 2014
- Rename settings label from "Conditions" to "Rules Edition"
- Update spec 008 with issue #23 requirements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-04 14:52:23 +02:00
parent 94e1806112
commit 817cfddabc
17 changed files with 892 additions and 257 deletions

View File

@@ -1,6 +1,7 @@
import type {
Combatant,
CreatureId,
DifficultyThreshold,
DifficultyTier,
PlayerCharacter,
} from "@initiative/domain";
@@ -9,6 +10,7 @@ import { useMemo } from "react";
import { useBestiaryContext } from "../contexts/bestiary-context.js";
import { useEncounterContext } from "../contexts/encounter-context.js";
import { usePlayerCharactersContext } from "../contexts/player-characters-context.js";
import { useRulesEditionContext } from "../contexts/rules-edition-context.js";
import { resolveSide } from "./use-difficulty.js";
export interface BreakdownCombatant {
@@ -24,11 +26,10 @@ export interface BreakdownCombatant {
interface DifficultyBreakdown {
readonly tier: DifficultyTier;
readonly totalMonsterXp: number;
readonly partyBudget: {
readonly low: number;
readonly moderate: number;
readonly high: number;
};
readonly thresholds: readonly DifficultyThreshold[];
readonly encounterMultiplier: number | undefined;
readonly adjustedXp: number | undefined;
readonly partySizeAdjusted: boolean | undefined;
readonly pcCount: number;
readonly partyCombatants: readonly BreakdownCombatant[];
readonly enemyCombatants: readonly BreakdownCombatant[];
@@ -38,6 +39,7 @@ export function useDifficultyBreakdown(): DifficultyBreakdown | null {
const { encounter } = useEncounterContext();
const { characters } = usePlayerCharactersContext();
const { getCreature } = useBestiaryContext();
const { edition } = useRulesEditionContext();
return useMemo(() => {
const { partyCombatants, enemyCombatants, descriptors, pcCount } =
@@ -50,7 +52,7 @@ export function useDifficultyBreakdown(): DifficultyBreakdown | null {
if (!hasPartyLevel || !hasCr) return null;
const result = calculateEncounterDifficulty(descriptors);
const result = calculateEncounterDifficulty(descriptors, edition);
return {
...result,
@@ -58,7 +60,7 @@ export function useDifficultyBreakdown(): DifficultyBreakdown | null {
partyCombatants,
enemyCombatants,
};
}, [encounter.combatants, characters, getCreature]);
}, [encounter.combatants, characters, getCreature, edition]);
}
type CreatureInfo = {