import { afterEach, describe, expect, it } from "vitest"; import { buildBossTestingCatalog, BOSS_TESTING_CATALOG, createBossTestingRoom, randomTestingBoss } from "./bossTesting"; import { clearBossTestingDungeons, DUNGEON_DEFINITIONS, registerBossTestingDungeon, requireDungeonDefinition } from "./dungeonRegistry"; import { activateDungeonSession, restartActiveDungeonSession } from "./dungeonSession"; import { useGameStore } from "./store"; import { useCombatStore } from "./combatStore"; import { bossTestIsDefeated } from "./BossTestingBridge"; import type { DungeonDefinition } from "./dungeonTypes"; import { dungeonEnemyScaling } from "./dungeonBossScaling"; import { creatureRuntimeBaseStats } from "./azerothCoreReference"; import { bossDifficultyMultipliers } from "./dungeonDifficulty"; import { finalDungeonBoss } from "./dungeonRunStore"; afterEach(() => { useGameStore.getState().activateDungeon("wailing-caverns"); clearBossTestingDungeons(); }); describe("Boss Testing", () => { const boss = BOSS_TESTING_CATALOG.find((entry) => entry.dungeonId === "wailing-caverns" && entry.name === "Lady Anacondra")!; const source = requireDungeonDefinition(boss.dungeonId); it("includes unready and event-only bosses and isolates catalog changes from shipped content", () => { const blocked: DungeonDefinition = { ...source, availability: "blocked", staticSpawns: [], roamingPacks: [], encounterSpawnGroups: [{ id: "event", spawns: [boss.spawn] }], entities: { [boss.entityId]: source.entities[boss.entityId] } }; expect(buildBossTestingCatalog([blocked])).toHaveLength(1); const count = DUNGEON_DEFINITIONS.length; const room = createBossTestingRoom(source, boss); registerBossTestingDungeon(room.definition, source.id); expect(requireDungeonDefinition(source.id)).toBe(source); expect(DUNGEON_DEFINITIONS).toHaveLength(count); expect(room.definition.staticSpawns).toHaveLength(1); expect(room.definition.roamingPacks).toEqual([]); expect(room.definition.entities[boss.entityId].combat).toEqual(source.entities[boss.entityId].combat); expect(room.definition.staticSpawns[0].position).toEqual(boss.spawn.position); expect(room.definition.assets).toEqual({ visual: [], collision: [] }); }); it("preserves boss phases and scripted summons without spawning unrelated dungeon packs", () => { const fixture: DungeonDefinition = { ...source, encounters: [ { id: "boss-mechanics", dungeonId: source.id, initialPhase: "idle", rules: [{ id: "adds", trigger: { kind: "entity-health", entityId: boss.spawn.id, atOrBelowPercent: 50 }, actions: [{ id: "summon", kind: "activate-spawn-group", spawnGroupId: "boss-adds" }, { id: "loot", kind: "grant-loot", lootTableId: "test" }] }] }, { id: "other-boss", dungeonId: source.id, initialPhase: "idle", rules: [{ id: "other", trigger: { kind: "entity-engaged", entityId: "unrelated" }, actions: [] }] }, ], encounterSpawnGroups: [{ id: "boss-adds", spawns: [{ ...boss.spawn, id: "add", entityId: boss.entityId }] }, { id: "trash", spawns: [] }] }; const room = createBossTestingRoom(fixture, boss); expect(room.definition.encounters?.map((entry) => entry.id)).toEqual([`${room.definition.id}:boss-mechanics`]); expect(room.definition.encounters?.[0].dungeonId).toBe(room.definition.id); expect(room.definition.encounters?.[0].rules[0].actions).toEqual([{ id: "summon", kind: "activate-spawn-group", spawnGroupId: "boss-adds" }]); expect(room.definition.encounterSpawnGroups?.map((entry) => entry.id)).toEqual(["boss-adds"]); expect(room.definition.staticSpawns).toHaveLength(1); }); it("starts an unready boss room, survives scene retries, and retains testing mode when restarted", () => { const room = createBossTestingRoom(source, boss); registerBossTestingDungeon(room.definition, source.id); expect(activateDungeonSession(room.definition.id)).toBe(false); expect(activateDungeonSession(room.definition.id, undefined, { gmTestMode: true, bossTest: room.session })).toBe(true); expect(restartActiveDungeonSession()).toBe(true); useGameStore.getState().resetAtEntrance(); expect(useGameStore.getState()).toMatchObject({ gmTestMode: true, bossTest: room.session, overlay: null }); }); it("uses Celebras's native health at the player's scaled level without campaign health floors", () => { const celebras = BOSS_TESTING_CATALOG.find((entry) => entry.name === "Celebras the Cursed")!; const maraudon = requireDungeonDefinition(celebras.dungeonId); const room = createBossTestingRoom(maraudon, celebras); const entity = room.definition.entities[celebras.entityId]; const scale = (level: number, difficulty = 1) => dungeonEnemyScaling(room.definition, entity, celebras.spawn.id, level, room.definition.defaultDifficultyId, difficulty); const nativeHealth = creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health * entity.combat!.healthMultiplier!; expect(scale(1)).toMatchObject({ level: 2, maxHealth: nativeHealth }); expect(Math.round(scale(1).maxHealth)).toBe(575); expect(scale(8).maxHealth).toBeGreaterThan(scale(1).maxHealth); expect(scale(20).maxHealth).toBeGreaterThan(scale(8).maxHealth); expect(scale(1, 10).maxHealth).toBe(nativeHealth * bossDifficultyMultipliers(10).health); const campaign = dungeonEnemyScaling(maraudon, maraudon.entities[celebras.entityId], celebras.spawn.id, 1, maraudon.defaultDifficultyId, 1); expect(campaign.maxHealth).toBe(900); expect(scale(1).damageMultiplier).toBe(campaign.damageMultiplier); expect(entity.combat?.attacks).toEqual(maraudon.entities[celebras.entityId].combat?.attacks); }); it("keeps low extrapolated boss health alive long enough to test mechanics", () => { const zereketh = BOSS_TESTING_CATALOG.find((entry) => entry.name === "Zereketh the Unbound")!; const arcatraz = requireDungeonDefinition(zereketh.dungeonId); const room = createBossTestingRoom(arcatraz, zereketh); const entity = room.definition.entities[zereketh.entityId]; const nativeHealth = creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health * entity.combat!.healthMultiplier!; const testing = dungeonEnemyScaling( room.definition, entity, zereketh.spawn.id, 1, room.definition.defaultDifficultyId, 1, ); expect(nativeHealth).toBeLessThan(100); expect(testing).toMatchObject({ level: 2, maxHealth: 540 }); }); it("omits the campaign finale bonus even when testing the source dungeon's final boss", () => { const final = finalDungeonBoss(source)!; const finalBoss = BOSS_TESTING_CATALOG.find((entry) => entry.dungeonId === source.id && entry.spawn.id === final.id)!; const room = createBossTestingRoom(source, finalBoss); const entity = room.definition.entities[finalBoss.entityId]; const testing = dungeonEnemyScaling(room.definition, entity, finalBoss.spawn.id, 1, source.defaultDifficultyId, 1); const campaign = dungeonEnemyScaling(source, entity, finalBoss.spawn.id, 1, source.defaultDifficultyId, 1); expect(testing.maxHealth).toBe(Math.max( creatureRuntimeBaseStats(entity.combat!.serverEntry!, 2)!.health * (entity.combat?.healthMultiplier ?? 1), 540, )); expect(campaign.maxHealth).toBe(Math.max(testing.maxHealth, 900 * 1.8)); }); it("recognizes actual boss deaths and awards no progression or collectible rewards", () => { const room = createBossTestingRoom(source, boss); registerBossTestingDungeon(room.definition, source.id); useGameStore.getState().activateDungeon(room.definition.id, undefined, { bossTest: room.session, gmTestMode: true }); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 20 }); expect(bossTestIsDefeated()).toBe(false); useCombatStore.getState().registerMob(boss.spawn.id, { name: boss.name, boss: true, maxHealth: 10, xpReward: 500, hasLoot: true, lootSourceId: boss.entityId }); const xp = useCombatStore.getState().xp; useCombatStore.getState().damageMob(boss.spawn.id, 100000, Date.now()); expect(bossTestIsDefeated()).toBe(true); const combat = useCombatStore.getState(); expect(combat.xp).toBe(xp); expect(combat.mobs[boss.spawn.id]).toMatchObject({ hasLoot: false, pendingLoot: [] }); expect(combat.mobs[boss.spawn.id].bossCardDrop).toBeFalsy(); }); it("draws a boss only from the selected filters and handles empty queues", () => { expect(randomTestingBoss([], () => 0.5)).toBeNull(); const candidates = BOSS_TESTING_CATALOG.slice(0, 2); expect(randomTestingBoss(candidates, () => 0)).toBe(candidates[0]); expect(randomTestingBoss(candidates, () => 1)).toBe(candidates[1]); expect(randomTestingBoss(candidates, () => Number.NaN)).toBe(candidates[0]); }); });