Skip to main content

Encounters and Combat

Encounters are campaign-level prep objects. Combat is the live tracker state for running an encounter or ad-hoc fight.

Backend

Primary files:

  • server/app/routers/encounters.py
  • server/app/routers/combat.py
  • server/app/lib/combat.py
  • server/app/lib/encounter_xp.py
  • server/app/lib/encounter_suggest.py
  • server/app/lib/statblock.py
  • server/app/lib/statblock_ref.py
  • server/app/lib/tools/get_stat_block.py

Encounters contain monster rows and optional session links. Running an encounter expands monster counts into combatants, seeds party rows, and resets combat round/turn state. Combat routes mutate current round, active turn, combatants, ordering, and generated loot.

Frontend

Primary files:

  • web/src/components/SessionEncounters.tsx
  • web/src/components/CombatTracker.tsx
  • web/src/components/MonsterAutocomplete.tsx
  • web/src/components/StatBlock.tsx
  • web/src/components/LootModal.tsx
  • web/src/lib/encounterDifficulty.ts

The frontend mirrors difficulty math for live editing, but the server remains the source of truth for saved encounter responses.

Difficulty Math

Encounter difficulty uses deterministic 2024-style XP budgets from party member levels. The AI can suggest monster choices and flavor, but tier calculation, XP totals, CR-to-XP mapping, and final labels come from code.

Monster Sources

Monster lookup can draw from:

  • Campaign-extracted monsters.
  • Core-rule extracted monsters.
  • SRD proxy data.

Stat-block references use stable locators such as campaign/core/SRD sources so cards can be rehydrated in chat and combat.

AI Suggestion Boundary

AI encounter suggestion is DM-only and AI-gated. The model picks a coherent set from eligible candidates; code validates party levels, scopes visible monsters, calculates difficulty, and serializes the result. Each suggested monster row carries its statBlockRef locator (campaign:<id> / core:<id>), so encounters saved from a suggestion open stat blocks in the combat tracker just like autocomplete-picked monsters — numbered combatant names ("Goblin 2") can't be resolved by name alone.

Change Checklist

  • Keep server and frontend difficulty math aligned.
  • Enforce DM-only access for encounter/combat mutations.
  • Preserve run semantics: templates do not mutate when run.
  • Check stat-block reference behavior when monster DTOs change.
  • Add tests for edge cases around party levels, CR, and active combat conflicts.