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/); }); });