Files
ttrpg_manager/src/lib/combat/budget.test.ts
T
NilsBriggen 3e5bdc06e2 Phase 3: combat depth
- Condition durations + auto-expiry: timed conditions tick down on the affected
  combatant's turn and drop at 0 (engine tickConditions; UI rounds field + (Nr) chip)
- Initiative: Roll-all (d20 + per-combatant bonus, re-sort anchored) + group/quantity add
- Combat log: per-round event feed (turns, round changes, damage/heal, expiries,
  removals) + in-memory Undo of the last change
- Encounter difficulty budget: 5e XP thresholds + PF2e level budget (pure lib +
  tests); live difficulty/XP-award readout from monster combatants vs the party
- Combatants carry initBonus + cr/level; compendium add-to-combat passes them

18 new unit tests (durations, applyInitiatives, budget), new combat-depth e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 01:23:13 +02:00

46 lines
1.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { computeBudget } from './budget';
describe('5e encounter budget', () => {
it('rates a single CR 1/4 vs a level-1 party of 4 as easy-ish', () => {
const r = computeBudget('5e', [1, 1, 1, 1], [{ cr: 0.25 }]);
expect(r.totalXp).toBe(50);
expect(r.ratingXp).toBe(50); // single monster, x1 multiplier
expect(['trivial', 'easy']).toContain(r.difficulty);
});
it('applies the count multiplier to the rating but not the award', () => {
const r = computeBudget('5e', [3, 3, 3, 3], [{ cr: 1 }, { cr: 1 }, { cr: 1 }]);
expect(r.totalXp).toBe(600); // 3 * 200
expect(r.ratingXp).toBe(1200); // x2 for 3 monsters
expect(r.awardPerCharacter).toBe(150); // 600 / 4
});
it('flags a deadly fight', () => {
const r = computeBudget('5e', [1, 1, 1, 1], [{ cr: 3 }, { cr: 3 }]);
expect(r.difficulty).toBe('deadly');
});
});
describe('pf2e encounter budget', () => {
it('a same-level creature is worth 40 XP', () => {
const r = computeBudget('pf2e', [3, 3, 3, 3], [{ level: 3 }]);
expect(r.totalXp).toBe(40);
expect(r.awardPerCharacter).toBe(40);
});
it('rates a moderate encounter', () => {
const r = computeBudget('pf2e', [5, 5, 5, 5], [{ level: 5 }, { level: 5 }]);
expect(r.totalXp).toBe(80);
expect(r.difficulty).toBe('moderate');
});
it('adjusts thresholds for party size', () => {
const four = computeBudget('pf2e', [1, 1, 1, 1], []);
const five = computeBudget('pf2e', [1, 1, 1, 1, 1], []);
const sevFour = four.thresholds.find((t) => t.label === 'Severe')!.value;
const sevFive = five.thresholds.find((t) => t.label === 'Severe')!.value;
expect(sevFive - sevFour).toBe(30);
});
});