Implement the 025-display-initiative feature that adds initiative modifier and passive initiative display to creature stat blocks, calculated as DEX modifier + (proficiency multiplier × proficiency bonus) from bestiary data, shown in MM 2024 format on the AC line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 11:27:46 +01:00
parent c6349928eb
commit 5b0bac880d
14 changed files with 650 additions and 1 deletions

View File

@@ -0,0 +1,108 @@
import { describe, expect, it } from "vitest";
import {
calculateInitiative,
formatInitiativeModifier,
} from "../initiative.js";
describe("calculateInitiative", () => {
it("returns positive modifier for creature with expertise (Aboleth: DEX 9, CR 10, proficiency 2)", () => {
const result = calculateInitiative({
dexScore: 9,
cr: "10",
initiativeProficiency: 2,
});
// DEX mod = floor((9-10)/2) = -1, PB for CR 10 = 4, -1 + 2*4 = +7
expect(result.modifier).toBe(7);
expect(result.passive).toBe(17);
});
it("returns negative modifier for low DEX with no proficiency", () => {
const result = calculateInitiative({
dexScore: 8,
cr: "1",
initiativeProficiency: 0,
});
// DEX mod = floor((8-10)/2) = -1, 0 * PB = 0, -1 + 0 = -1
expect(result.modifier).toBe(-1);
expect(result.passive).toBe(9);
});
it("returns zero modifier for DEX 10 with no proficiency", () => {
const result = calculateInitiative({
dexScore: 10,
cr: "1",
initiativeProficiency: 0,
});
expect(result.modifier).toBe(0);
expect(result.passive).toBe(10);
});
it("adds single proficiency bonus (multiplier 1)", () => {
const result = calculateInitiative({
dexScore: 14,
cr: "5",
initiativeProficiency: 1,
});
// DEX mod = +2, PB for CR 5 = 3, 2 + 1*3 = 5
expect(result.modifier).toBe(5);
expect(result.passive).toBe(15);
});
it("adds double proficiency bonus (multiplier 2 / expertise)", () => {
const result = calculateInitiative({
dexScore: 14,
cr: "5",
initiativeProficiency: 2,
});
// DEX mod = +2, PB for CR 5 = 3, 2 + 2*3 = 8
expect(result.modifier).toBe(8);
expect(result.passive).toBe(18);
});
it("handles no proficiency (multiplier 0) — reduces to raw DEX modifier", () => {
const result = calculateInitiative({
dexScore: 14,
cr: "5",
initiativeProficiency: 0,
});
// DEX mod = +2, 0 * PB = 0, 2 + 0 = 2
expect(result.modifier).toBe(2);
expect(result.passive).toBe(12);
});
it("handles negative result even with proficiency (very low DEX)", () => {
const result = calculateInitiative({
dexScore: 3,
cr: "0",
initiativeProficiency: 1,
});
// DEX mod = floor((3-10)/2) = -4, PB for CR 0 = 2, -4 + 1*2 = -2
expect(result.modifier).toBe(-2);
expect(result.passive).toBe(8);
});
it("handles fractional CR values", () => {
const result = calculateInitiative({
dexScore: 12,
cr: "1/4",
initiativeProficiency: 1,
});
// DEX mod = +1, PB for CR 1/4 = 2, 1 + 1*2 = 3
expect(result.modifier).toBe(3);
expect(result.passive).toBe(13);
});
});
describe("formatInitiativeModifier", () => {
it("formats positive modifier with plus sign", () => {
expect(formatInitiativeModifier(7)).toBe("+7");
});
it("formats negative modifier with U+2212 minus sign", () => {
expect(formatInitiativeModifier(-1)).toBe("\u22121");
});
it("formats zero modifier with plus sign", () => {
expect(formatInitiativeModifier(0)).toBe("+0");
});
});