import { describe, expect, it } from "vitest"; import { DUNGEON_REGISTRY } from "./dungeonRegistry"; import { createDungeonEncounterSnapshot, reduceDungeonEncounter, resetDungeonEncounterSnapshot, validateDungeonEncounterDefinition, } from "./dungeonEncounterRuntime"; import { WOW_PROGRESSION_OBJECT_REVIEWS } from "./wowProgressionObjectContentCatalog"; function encounter(dungeonId: string, suffix: string) { const definition = DUNGEON_REGISTRY[dungeonId]; const result = definition.encounters?.find((candidate) => candidate.id === `${dungeonId}:${suffix}`); if (!result) throw new Error(`Missing ${dungeonId}:${suffix}`); return result; } function actorId(dungeonId: string, entry: number): string { const definition = DUNGEON_REGISTRY[dungeonId]; const staticSpawn = definition.staticSpawns.find( (spawn) => definition.entities[spawn.entityId]?.combat?.serverEntry === entry, ); if (staticSpawn) return staticSpawn.id; for (const pack of definition.roamingPacks) { const member = pack.members.find( (candidate) => definition.entities[candidate.entityId]?.combat?.serverEntry === entry, ); if (member) return `${pack.id}:${member.id}`; } throw new Error(`${dungeonId} is missing entry ${entry}`); } describe("source-backed progression objects", () => { it("pins source hashes and keeps every adaptation and remaining gap explicit", () => { expect(WOW_PROGRESSION_OBJECT_REVIEWS.map((review) => review.dungeonId)).toEqual([ "deadmines", "shadowfang-keep", "maraudon", "mechanar", "steamvault", "shadow-labyrinth", "scarlet-monastery", ]); for (const review of WOW_PROGRESSION_OBJECT_REVIEWS) { expect(review.files.length).toBeGreaterThan(0); expect(review.files.every((file) => /sha256 [a-f0-9]{64}/.test(file))).toBe(true); expect(review.adaptations.length).toBeGreaterThan(0); expect(review.remainingGaps.length).toBeGreaterThan(0); const runtime = DUNGEON_REGISTRY[review.dungeonId].encounters?.find( (candidate) => candidate.source?.reviewedBehavior === review.reviewedBehavior, ); expect(runtime).toBeDefined(); validateDungeonEncounterDefinition(runtime!); } }); it("runs Deadmines factory, foundry, and cannon breach transitions", () => { const dungeon = DUNGEON_REGISTRY.deadmines; const definition = encounter("deadmines", "progression-objects"); const factory = dungeon.serverGameObjects.find((object) => object.source?.templateId === 13_965)!; const foundry = dungeon.serverGameObjects.find((object) => object.source?.templateId === 16_399)!; const ironClad = dungeon.serverGameObjects.find((object) => object.source?.templateId === 16_397)!; const foundryLever = dungeon.serverGameObjects.find((object) => object.source?.templateId === 101_834)!; const cannon = dungeon.serverGameObjects.find((object) => object.source?.templateId === 16_398)!; let snapshot = createDungeonEncounterSnapshot(definition); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("deadmines", 644), now: 1, }).snapshot; expect(snapshot.worldObjectStates[factory.id]).toBe("open"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "interact", objectId: foundryLever.id, now: 2, }).snapshot; expect(snapshot.worldObjectStates[foundry.id]).toBe("open"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "interact", objectId: cannon.id, now: 3, }).snapshot; expect(snapshot.worldObjectStates[ironClad.id]).toBe("open"); expect(snapshot.worldObjectStates[cannon.id]).toBe("used"); expect(snapshot.activeSpawnGroupIds).toContain("deadmines:cannon-reinforcements"); expect(dungeon.encounterSpawnGroups?.find((group) => group.id === "deadmines:cannon-reinforcements")?.spawns).toHaveLength(2); const reset = resetDungeonEncounterSnapshot(definition, snapshot, "boss-wipe", 4); expect(reset.worldObjectStates[factory.id]).toBe("open"); expect(reset.worldObjectStates[foundry.id]).toBe("open"); expect(reset.worldObjectStates[ironClad.id]).toBe("open"); expect(reset.activeSpawnGroupIds).not.toContain("deadmines:cannon-reinforcements"); }); it("opens each Shadowfang Keep progression door at its source milestone", () => { const dungeon = DUNGEON_REGISTRY["shadowfang-keep"]; const definition = encounter("shadowfang-keep", "progression-objects"); const courtyard = dungeon.serverGameObjects.find((object) => object.source?.templateId === 18_895)!; const sorcerer = dungeon.serverGameObjects.find((object) => object.source?.templateId === 18_972)!; const arugal = dungeon.serverGameObjects.find((object) => object.source?.templateId === 18_971)!; let snapshot = createDungeonEncounterSnapshot(definition); for (const [entry, objectId, now] of [ [3_914, courtyard.id, 1], [4_274, sorcerer.id, 2], [3_927, arugal.id, 3], ] as const) { snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("shadowfang-keep", entry), now, }).snapshot; expect(snapshot.worldObjectStates[objectId]).toBe("open"); } const reset = resetDungeonEncounterSnapshot(definition, snapshot, "checkpoint", 4); expect(reset.worldObjectStates[courtyard.id]).toBe("open"); expect(reset.worldObjectStates[sorcerer.id]).toBe("open"); expect(reset.worldObjectStates[arugal.id]).toBe("open"); }); it("opens Maraudon's Corruption Spewer when Noxxion dies", () => { const dungeon = DUNGEON_REGISTRY.maraudon; const definition = encounter("maraudon", "progression-objects"); const spewer = dungeon.serverGameObjects.find((object) => object.source?.templateId === 178_570)!; let snapshot = createDungeonEncounterSnapshot(definition); expect(snapshot.worldObjectStates[spewer.id]).toBe("closed"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("maraudon", 13_282), now: 1, }).snapshot; expect(snapshot.worldObjectStates[spewer.id]).toBe("open"); expect(resetDungeonEncounterSnapshot(definition, snapshot, "boss-wipe", 2).worldObjectStates[spewer.id]).toBe("open"); }); it("opens Mechanar passage doors and resets Sepethrea's room door safely", () => { const dungeon = DUNGEON_REGISTRY.mechanar; const definition = encounter("mechanar", "progression-objects"); let snapshot = createDungeonEncounterSnapshot(definition); const ironDoor = dungeon.serverGameObjects.find((object) => object.source?.templateId === 184_632)!; const gyroDoor = dungeon.serverGameObjects.find((object) => object.source?.templateId === 184_322)!; const sepethreaDoor = dungeon.serverGameObjects.find((object) => object.source?.templateId === 184_449)!; expect(ironDoor.states.find((state) => state.id === "closed")?.blocksMovement).toBe(true); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("mechanar", 19_710), now: 1, }).snapshot; expect(snapshot.worldObjectStates[ironDoor.id]).toBe("open"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("mechanar", 19_218), now: 2, }).snapshot; expect(snapshot.worldObjectStates[gyroDoor.id]).toBe("open"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-engaged", entityId: actorId("mechanar", 19_221), now: 3, }).snapshot; expect(snapshot.worldObjectStates[sepethreaDoor.id]).toBe("closed"); snapshot = resetDungeonEncounterSnapshot(definition, snapshot, "boss-wipe", 4); expect(snapshot.worldObjectStates[ironDoor.id]).toBe("open"); expect(snapshot.worldObjectStates[gyroDoor.id]).toBe("open"); expect(snapshot.worldObjectStates[sepethreaDoor.id]).toBe("open"); }); it("requires both Steamvault boss panels before opening the main chamber", () => { const dungeon = DUNGEON_REGISTRY.steamvault; const definition = encounter("steamvault", "access-panels"); let snapshot = createDungeonEncounterSnapshot(definition); const hydro = dungeon.serverGameObjects.find((object) => object.source?.templateId === 184_125)!; const mek = dungeon.serverGameObjects.find((object) => object.source?.templateId === 184_126)!; const door = dungeon.serverGameObjects.find((object) => object.source?.templateId === 183_049)!; snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("steamvault", 17_797), now: 1, }).snapshot; expect(snapshot.worldObjectStates[hydro.id]).toBe("unlocked"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "interact", objectId: hydro.id, now: 2 }).snapshot; snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "tick", now: 3 }).snapshot; expect(snapshot.worldObjectStates[door.id]).toBe("closed"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "entity-death", entityId: actorId("steamvault", 17_796), now: 4, }).snapshot; expect(snapshot.worldObjectStates[mek.id]).toBe("unlocked"); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "interact", objectId: mek.id, now: 5 }).snapshot; snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "tick", now: 6 }).snapshot; expect(snapshot.worldObjectStates[door.id]).toBe("open"); const reset = resetDungeonEncounterSnapshot(definition, snapshot, "boss-wipe", 7); expect(reset.worldObjectStates[door.id]).toBe("open"); expect(reset.worldObjectStates[hydro.id]).toBe("used"); expect(reset.worldObjectStates[mek.id]).toBe("used"); }); it("holds Hellmaw out of static population until all common ritualists die", () => { const dungeon = DUNGEON_REGISTRY["shadow-labyrinth"]; const definition = encounter("shadow-labyrinth", "progression-objects"); const ritualists = dungeon.staticSpawns .filter((spawn) => dungeon.entities[spawn.entityId]?.combat?.serverEntry === 18_794 && (spawn.spawnMask ?? 3) === 3) .map((spawn) => spawn.id); const hellmawGroup = dungeon.encounterSpawnGroups?.find( (group) => group.id === "shadow-labyrinth:hellmaw", ); expect(ritualists).toHaveLength(18); expect(dungeon.staticSpawns.some((spawn) => dungeon.entities[spawn.entityId]?.combat?.serverEntry === 18_731)).toBe(false); expect(hellmawGroup?.spawns).toHaveLength(1); const result = reduceDungeonEncounter(definition, createDungeonEncounterSnapshot(definition), { kind: "tick", now: 1, facts: { registeredEntityIds: ritualists, deadEntityIds: ritualists }, }); expect(result.snapshot.activeSpawnGroupIds).toContain("shadow-labyrinth:hellmaw"); }); it("gates Whitemane behind Mograine and restores her attempt after a wipe", () => { const dungeon = DUNGEON_REGISTRY["scarlet-monastery"]; const definition = encounter("scarlet-monastery", "cathedral-pair"); const groupId = "scarlet-monastery:whitemane"; expect(dungeon.staticSpawns.some((spawn) => dungeon.entities[spawn.entityId]?.combat?.serverEntry === 3_977)).toBe(false); expect(dungeon.encounterSpawnGroups?.find((group) => group.id === groupId)?.spawns).toHaveLength(1); let snapshot = reduceDungeonEncounter(definition, createDungeonEncounterSnapshot(definition), { kind: "entity-death", entityId: actorId("scarlet-monastery", 3_976), now: 1, }).snapshot; expect(snapshot.activeSpawnGroupIds).toContain(groupId); snapshot = resetDungeonEncounterSnapshot(definition, snapshot, "boss-wipe", 2); expect(snapshot.activeSpawnGroupIds).not.toContain(groupId); snapshot = reduceDungeonEncounter(definition, snapshot, { kind: "tick", now: 3 }).snapshot; expect(snapshot.activeSpawnGroupIds).toContain(groupId); }); });