import { describe, expect, it } from "vitest"; import { createDungeonEncounterSnapshot, reduceDungeonEncounter, validateDungeonEncounterDefinition, } from "./dungeonEncounterRuntime"; import { BITE_SIZED_STOCKADES_DUNGEON_ID, STORMWIND_STOCKADE_DUNGEON_ID } from "./dungeonContentSource"; import { DUNGEON_REGISTRY, dungeonDefinitionById } from "./dungeonRegistry"; import { activateDungeonSession, restartActiveDungeonSession } from "./dungeonSession"; import { useDungeonRunStore } from "./dungeonRunStore"; import { BITE_SIZED_STOCKADES_BOSS_CANDIDATES, createBiteSizedStockadesDefinition, } from "./dungeons/biteSizedStockades"; import { validateDungeonWorldObject } from "./dungeonWorldObjects"; const source = DUNGEON_REGISTRY[STORMWIND_STOCKADE_DUNGEON_ID]; describe("bite-sized Stormwind Stockades", () => { it("creates a separate central-corridor dungeon without changing Stockades", () => { const originalBossIds = source.bosses.map((boss) => boss.id); const originalSpawnCount = source.staticSpawns.length; const biteSized = createBiteSizedStockadesDefinition(source, "run-a"); expect(biteSized.id).toBe(BITE_SIZED_STOCKADES_DUNGEON_ID); expect(biteSized).not.toBe(source); expect(source.bosses.map((boss) => boss.id)).toEqual(originalBossIds); expect(source.staticSpawns).toHaveLength(originalSpawnCount); expect(source.bosses).toHaveLength(5); expect(source.serverGameObjects.some((object) => object.id.startsWith("bite-sized-"))).toBe(false); const bossId = biteSized.bosses[0].id; expect(BITE_SIZED_STOCKADES_BOSS_CANDIDATES.map((boss) => boss.sourceSpawnId)).toContain(bossId); expect(biteSized.objectiveOrder).toEqual(biteSized.bosses); expect(biteSized.staticSpawns.filter((spawn) => spawn.id === bossId)).toHaveLength(1); expect(biteSized.staticSpawns.every((spawn) => Math.abs(spawn.position[2]) <= 15)).toBe(true); expect(biteSized.navigationLinks.map((link) => link.id)).toEqual(["entrance-to-central-stairs"]); expect(biteSized.traversalRamps?.map((ramp) => ramp.id)).toEqual(["entrance-stair-ramp"]); }); it("seals both side wings with visible movement and sight blockers", () => { const biteSized = createBiteSizedStockadesDefinition(source, "run-b"); const walls = biteSized.serverGameObjects.filter((object) => object.id.startsWith("bite-sized-stockades-")); expect(walls.map((wall) => wall.id).sort()).toEqual([ "bite-sized-stockades-north-wall", "bite-sized-stockades-south-wall", ]); for (const wall of walls) { expect(() => validateDungeonWorldObject(wall)).not.toThrow(); expect(wall.states).toEqual([expect.objectContaining({ id: "sealed", visible: true, blocksMovement: true, blocksSight: true, })]); } }); it("chooses all five source bosses deterministically across fresh run ids", () => { const selected = new Set(); for (let index = 0; index < 1_000; index += 1) { selected.add(createBiteSizedStockadesDefinition(source, `fresh-run-${index}`).bosses[0].name); } expect([...selected].sort()).toEqual(BITE_SIZED_STOCKADES_BOSS_CANDIDATES.map((boss) => boss.name).sort()); const first = createBiteSizedStockadesDefinition(source, "same-run"); const second = createBiteSizedStockadesDefinition(source, "same-run"); expect(second.bosses[0]).toEqual(first.bosses[0]); expect(second.staticSpawns.find((spawn) => spawn.id === second.bosses[0].id)?.entityId) .toBe(first.staticSpawns.find((spawn) => spawn.id === first.bosses[0].id)?.entityId); }); it("uses the run identity for fresh, restarted, and shared sessions", () => { expect(activateDungeonSession(BITE_SIZED_STOCKADES_DUNGEON_ID, undefined, { runId: "run-a" })).toBe(true); const firstBoss = dungeonDefinitionById(BITE_SIZED_STOCKADES_DUNGEON_ID)!.bosses[0].name; expect(useDungeonRunStore.getState().progress?.id).toBe("run-a"); expect(restartActiveDungeonSession()).toBe(true); expect(dungeonDefinitionById(BITE_SIZED_STOCKADES_DUNGEON_ID)!.bosses[0].name).toBe(firstBoss); expect(activateDungeonSession(BITE_SIZED_STOCKADES_DUNGEON_ID, undefined, { runId: "run-b" })).toBe(true); expect(useDungeonRunStore.getState().progress?.id).toBe("run-b"); expect(dungeonDefinitionById(BITE_SIZED_STOCKADES_DUNGEON_ID)!.bosses[0].name).not.toBe(firstBoss); }); it("completes the one-boss run only through its stable objective", () => { const biteSized = createBiteSizedStockadesDefinition(source, "completion-run"); const bossId = biteSized.bosses[0].id; const encounter = biteSized.encounters![0]; expect(() => validateDungeonEncounterDefinition(encounter)).not.toThrow(); const snapshot = createDungeonEncounterSnapshot(encounter, 1_000); const reduced = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 2_000, facts: { registeredEntityIds: [bossId], deadEntityIds: [bossId], }, }).snapshot; expect(reduced.completedObjectiveIds).toEqual([bossId]); expect(reduced.flags["dungeon-complete"]).toBe(true); expect(reduced.phase).toBe("complete"); }); });