Files
ttrpg_manager/src/lib/dice/check.ts
T
NilsBriggen 9647e6b3d6 Phase 4: interactive dice & sheet rolling
- Dice notation: exploding (Nd6!) and reroll-below (Nd6r2), capped + tested
- Degrees of success / roll-vs-DC (PF2e ±10 + nat 20/1 steps; 5e crit on nat 20/1)
- Global roll tray (shared store + RollTray in layout) with animated result + degree
- Roll from the character sheet: skills, saves, and attack to-hit/damage are
  clickable and post to the tray + history (rollAndShow/rollCheck helpers)
- Saved roll macros per campaign (persisted) on the Dice page

10 new unit tests (notation explode/reroll, degrees), interactive-dice e2e.

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

45 lines
1.5 KiB
TypeScript

import type { SystemId } from '@/lib/rules';
export type Degree = 'critical-success' | 'success' | 'failure' | 'critical-failure';
export const DEGREE_LABEL: Record<Degree, string> = {
'critical-success': 'Critical Success',
success: 'Success',
failure: 'Failure',
'critical-failure': 'Critical Failure',
};
export const DEGREE_COLOR: Record<Degree, string> = {
'critical-success': 'text-success',
success: 'text-success',
failure: 'text-warning',
'critical-failure': 'text-danger',
};
/**
* Evaluate a d20 result against a DC.
* - PF2e: ≥DC+10 crit success, ≥DC success, ≤DC-10 crit failure; a natural 20
* improves the degree one step and a natural 1 worsens it one step.
* - 5e: success/failure vs DC, with natural 20/1 mapped to critical results.
*/
export function degreeOfSuccess(input: {
total: number;
dc: number;
system: SystemId;
/** the natural face of the d20, if known */
natural?: number | undefined;
}): Degree {
const { total, dc, system, natural } = input;
if (system === 'pf2e') {
let step = total >= dc + 10 ? 2 : total >= dc ? 1 : total <= dc - 10 ? -1 : 0;
if (natural === 20) step += 1;
else if (natural === 1) step -= 1;
step = Math.max(-1, Math.min(2, step));
return step === 2 ? 'critical-success' : step === 1 ? 'success' : step === 0 ? 'failure' : 'critical-failure';
}
// 5e
if (natural === 20) return 'critical-success';
if (natural === 1) return 'critical-failure';
return total >= dc ? 'success' : 'failure';
}