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:
108
packages/domain/src/__tests__/initiative.test.ts
Normal file
108
packages/domain/src/__tests__/initiative.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user