115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { AVAILABLE_BOSS_IDS, BOSS_DEFINITIONS } from "./bossCatalog";
|
|
import {
|
|
RUN_BUFF_ORDER,
|
|
RUN_BUFFS,
|
|
bossHealthMultiplier,
|
|
compileRunModifiers,
|
|
effectiveRunBuffRank,
|
|
formatRunBuffEffect,
|
|
increaseRunBuffRank,
|
|
selectRandomBossPair,
|
|
selectRogueTrialsBosses,
|
|
selectUnseenBosses,
|
|
selectRunBuffDraft,
|
|
} from "./roguelike";
|
|
import type { RunBuffRanks } from "./types";
|
|
|
|
describe("roguelike progression", () => {
|
|
it("adds 10% base boss HP per completed round", () => {
|
|
expect(bossHealthMultiplier(1)).toBe(1);
|
|
expect(bossHealthMultiplier(2)).toBe(1.1);
|
|
expect(bossHealthMultiplier(5)).toBe(1.4);
|
|
});
|
|
|
|
it("defines 18 unique infusion-eligible ability buffs without legacy ids", () => {
|
|
expect(RUN_BUFF_ORDER).toHaveLength(18);
|
|
expect(new Set(RUN_BUFF_ORDER)).toHaveLength(18);
|
|
expect(RUN_BUFF_ORDER.every((id) => RUN_BUFFS[id].infusionEligible)).toBe(true);
|
|
expect(RUN_BUFF_ORDER).not.toContain("vital-bloom");
|
|
expect(RUN_BUFF_ORDER).not.toContain("deep-wells");
|
|
expect(RUN_BUFF_ORDER).not.toContain("restoring-grace");
|
|
});
|
|
|
|
it("compiles capped ranks and overlays one passive infusion rank", () => {
|
|
const ranks: RunBuffRanks = {
|
|
"mend-echo": 9,
|
|
"mend-efficiency": 2,
|
|
"renew-duration": 2,
|
|
"shield-guard": 3,
|
|
"purify-renew": 1,
|
|
"radiance-shield": 2,
|
|
"barrier-regen": 3,
|
|
};
|
|
const modifiers = compileRunModifiers(ranks, "mend-efficiency");
|
|
|
|
expect(modifiers.mendExtraTargets).toBe(3);
|
|
expect(modifiers.mendManaMultiplier).toBeCloseTo(0.75 ** 3);
|
|
expect(modifiers.renewDurationBonus).toBe(4);
|
|
expect(modifiers.shieldDamageTakenMultiplier).toBeCloseTo(0.76);
|
|
expect(modifiers.purifyAppliesRenew).toBe(true);
|
|
expect(modifiers.radianceAbsorb).toBe(18);
|
|
expect(modifiers.barrierHealingPerSecond).toBe(9);
|
|
expect(effectiveRunBuffRank(ranks, "mend-efficiency", "mend-efficiency")).toBe(3);
|
|
expect(formatRunBuffEffect("mend-efficiency", 3)).toBe("58% less Mend mana cost");
|
|
});
|
|
|
|
it("increments earned ranks without exceeding each buff cap", () => {
|
|
const first = increaseRunBuffRank({}, "purify-renew");
|
|
const capped = increaseRunBuffRank(first, "purify-renew");
|
|
expect(first["purify-renew"]).toBe(1);
|
|
expect(capped["purify-renew"]).toBe(1);
|
|
});
|
|
|
|
it("draws unique eligible choices and handles one or zero remaining buffs", () => {
|
|
expect(selectRunBuffDraft({}, null, () => 0)).toEqual(RUN_BUFF_ORDER.slice(0, 3));
|
|
const maxed = Object.fromEntries(RUN_BUFF_ORDER.map((id) => [id, RUN_BUFFS[id].maxRank])) as RunBuffRanks;
|
|
maxed["mend-efficiency"] = 2;
|
|
expect(selectRunBuffDraft(maxed, null, () => 0)).toEqual(["mend-efficiency"]);
|
|
expect(selectRunBuffDraft(maxed, "mend-efficiency", () => 0)).toEqual([]);
|
|
});
|
|
|
|
it("selects a distinct pair that excludes both bosses from the prior round", () => {
|
|
const values = [0, 0];
|
|
const pair = selectRandomBossPair(["bulldrome", "broodfang-spider"], () => values.shift() ?? 0);
|
|
|
|
expect(pair).toEqual(["sandglass-scorpion", "cragclaw-crab"]);
|
|
expect(new Set(pair)).toHaveLength(2);
|
|
expect(pair).not.toContain("bulldrome");
|
|
expect(pair).not.toContain("broodfang-spider");
|
|
});
|
|
|
|
it("selects three distinct unseen bosses for Rogue Trials round five", () => {
|
|
const seen = [
|
|
"bulldrome",
|
|
"sandglass-scorpion",
|
|
"cragclaw-crab",
|
|
"mournveil-ghost",
|
|
"crownshard-golem",
|
|
"crystal-bat-matriarch",
|
|
"stormwool-alpaca",
|
|
"cluckhorn-colossus",
|
|
] as const;
|
|
const trio = selectRogueTrialsBosses(5, seen, () => 0);
|
|
|
|
expect(trio).toEqual(["ashwing-demon", "riftclaw-demon", "tempestscale-dragon"]);
|
|
expect(new Set(trio)).toHaveLength(3);
|
|
expect(trio.every((bossId) => !seen.includes(bossId as typeof seen[number]))).toBe(true);
|
|
});
|
|
|
|
it("never selects two Memory Sequence bosses in one pair or trio", () => {
|
|
const pair = selectRandomBossPair([], () => 0.04);
|
|
const trio = selectUnseenBosses(3, [], () => 0);
|
|
const memoryBossCount = (bossIds: readonly (typeof AVAILABLE_BOSS_IDS)[number][]) => bossIds
|
|
.filter((bossId) => BOSS_DEFINITIONS[bossId].mechanicIds.includes("memory-sequence"))
|
|
.length;
|
|
|
|
expect(memoryBossCount(pair)).toBeLessThanOrEqual(1);
|
|
expect(memoryBossCount(trio)).toBeLessThanOrEqual(1);
|
|
});
|
|
|
|
it("fails instead of silently reusing seen bosses when unseen pool is too small", () => {
|
|
expect(() => selectUnseenBosses(3, AVAILABLE_BOSS_IDS.slice(0, -2))).toThrow(/Cannot select 3 unseen bosses/);
|
|
});
|
|
});
|