import { describe, expect, it } from "vitest"; import { gunzipSync, strFromU8 } from "fflate"; import { createDungeonEncounterSnapshot, reduceDungeonEncounter, resetDungeonEncounterSnapshot, validateDungeonEncounterDefinition, } from "./dungeonEncounterRuntime"; import type { DungeonDefinition, DungeonEncounterSpawnGroupDefinition } from "./dungeonTypes"; import type { DungeonEncounterDefinition, DungeonEncounterSnapshot } from "./dungeonEncounterTypes"; import { validateDungeonWorldObject } from "./dungeonWorldObjects"; import compressedCampaign from "./generated/dungeonCampaignCatalog.compressed"; import { WOW_CLASSIC_ENCOUNTER_CONTENT_REVIEWS, withWowClassicEncounterContent, } from "./wowClassicEncounterContentCatalog"; function decodeBase64(value: string): Uint8Array { const decoded = atob(value); const bytes = new Uint8Array(decoded.length); for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index); return bytes; } const campaign = JSON.parse(strFromU8(gunzipSync(decodeBase64(compressedCampaign)))) as { readonly definitions: readonly DungeonDefinition[]; }; function adapted(id: string): DungeonDefinition { const definition = campaign.definitions.find((candidate) => candidate.id === id); if (!definition) throw new Error(`Missing generated dungeon ${id}`); return withWowClassicEncounterContent(definition); } function classicEncounter(definition: DungeonDefinition): DungeonEncounterDefinition { const encounter = definition.encounters?.find((value) => value.id === `${definition.id}:classic-instance`); if (!encounter) throw new Error(`Missing Classic encounter for ${definition.id}`); return encounter; } function group(definition: DungeonDefinition, id: string): DungeonEncounterSpawnGroupDefinition { const value = definition.encounterSpawnGroups?.find((candidate) => candidate.id === id); if (!value) throw new Error(`Missing spawn group ${id}`); return value; } function objectByTemplate(definition: DungeonDefinition, templateId: number) { const value = definition.serverGameObjects.find((object) => object.source?.templateId === templateId); if (!value) throw new Error(`Missing gameobject template ${templateId}`); return value; } function killGroup( encounter: DungeonEncounterDefinition, snapshot: DungeonEncounterSnapshot, spawnGroup: DungeonEncounterSpawnGroupDefinition, now: number, ) { const ids = spawnGroup.spawns.map((spawn) => spawn.id); return reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now, facts: { registeredEntityIds: ids, deadEntityIds: ids }, }); } function staticEntries(definition: DungeonDefinition): readonly number[] { return definition.staticSpawns.flatMap((spawn) => { const entry = definition.entities[spawn.entityId]?.combat?.serverEntry; return entry === undefined ? [] : [entry]; }); } describe("Classic source-backed dungeon encounter content", () => { it("pins source reviews, validates references, suppresses event bosses, and is idempotent", () => { const cases = [ ["blackfathom-deeps", []], ["uldaman", [2_748]], ["razorfen-downs", [7_355, 7_356]], ["zulfarrak", [7_273]], ] as const; expect(WOW_CLASSIC_ENCOUNTER_CONTENT_REVIEWS.map((review) => review.dungeonId)).toEqual(cases.map(([id]) => id)); for (const review of WOW_CLASSIC_ENCOUNTER_CONTENT_REVIEWS) { expect(review.fidelity).toBe("source-backed-adapted"); expect(review.sourceFiles.length).toBeGreaterThan(0); for (const sourceFile of review.sourceFiles) { expect(sourceFile.sha256).toMatch(/^[a-f0-9]{64}$/); expect(sourceFile.anchors.length).toBeGreaterThan(0); expect(sourceFile.anchors.every((anchor) => /lines? \d+/.test(anchor))).toBe(true); } expect(review.adaptations.length).toBeGreaterThan(0); expect(review.remainingGaps.length).toBeGreaterThan(0); } for (const [id, suppressedEntries] of cases) { const definition = adapted(id); const encounter = classicEncounter(definition); validateDungeonEncounterDefinition(encounter); expect(withWowClassicEncounterContent(definition)).toBe(definition); if (suppressedEntries.length > 0) { expect(staticEntries(definition)).not.toEqual(expect.arrayContaining([...suppressedEntries])); } const spawnGroupIds = new Set(definition.encounterSpawnGroups?.map((spawnGroup) => spawnGroup.id)); const objectIds = new Set(definition.serverGameObjects.map((object) => object.id)); const objectiveIds = new Set(definition.bosses.map((boss) => boss.id)); for (const spawnGroup of definition.encounterSpawnGroups ?? []) { for (const spawn of spawnGroup.spawns) { expect(definition.entities[spawn.entityId], `${id}:${spawnGroup.id}:${spawn.id}`).toBeDefined(); } } for (const rule of encounter.rules) { if (rule.trigger.kind === "interact") expect(objectIds.has(rule.trigger.objectId), `${id}:${rule.id}`).toBe(true); for (const action of rule.actions) { if (action.kind === "activate-spawn-group" || action.kind === "deactivate-spawn-group") { expect(spawnGroupIds.has(action.spawnGroupId), `${id}:${rule.id}:${action.id}`).toBe(true); } if (action.kind === "set-world-object-state") { expect(objectIds.has(action.objectId), `${id}:${rule.id}:${action.id}`).toBe(true); } if (action.kind === "complete-objective") { expect(objectiveIds.has(action.objectiveId), `${id}:${rule.id}:${action.id}`).toBe(true); } } } for (const object of definition.serverGameObjects.filter((candidate) => ( encounter.initialState?.worldObjectStates?.[candidate.id] !== undefined ))) { validateDungeonWorldObject(object); expect(object.visual?.hidden).not.toBe(true); } for (const entry of suppressedEntries) { const entityIds = new Set(Object.entries(definition.entities) .filter(([, entity]) => entity.combat?.serverEntry === entry) .map(([entityId]) => entityId)); const objective = definition.bosses.find((boss) => definition.encounterSpawnGroups?.some( (spawnGroup) => spawnGroup.spawns.some((spawn) => spawn.id === boss.id && entityIds.has(spawn.entityId)), )); expect(objective, `${id}:${entry}`).toMatchObject({ eventOnly: true }); } } }); it("runs all four Blackfathom fires, opens the physical portal, and persists its checkpoint", () => { const definition = adapted("blackfathom-deeps"); const encounter = classicEncounter(definition); const fires = [21_118, 21_119, 21_120, 21_121].map((entry) => objectByTemplate(definition, entry)); let snapshot = createDungeonEncounterSnapshot(encounter); fires.forEach((fire, index) => { snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: fire.id, now: index * 10, }).snapshot; const spawnGroup = group(definition, `blackfathom-deeps:fire-${index + 1}-pack`); expect(snapshot.activeSpawnGroupIds).toContain(spawnGroup.id); snapshot = killGroup(encounter, snapshot, spawnGroup, index * 10 + 1).snapshot; expect(snapshot.worldObjectStates[fire.id]).toBe("lit"); }); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 100 }).snapshot; const portal = objectByTemplate(definition, 21_117); expect(snapshot).toMatchObject({ counters: { "completed-fires": 4 }, checkpointId: "blackfathom-fire-event" }); expect(snapshot.worldObjectStates[portal.id]).toBe("open"); const reset = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 200); expect(reset.worldObjectStates[portal.id]).toBe("open"); expect(fires.every((fire) => reset.worldObjectStates[fire.id] === "lit")).toBe(true); expect(reset.activeSpawnGroupIds).toEqual([]); }); it("opens Uldaman's seal, awakens four keepers with wipe recovery, and source-gates Archaedas", () => { const definition = adapted("uldaman"); const encounter = classicEncounter(definition); const keystone = objectByTemplate(definition, 124_371); const seal = objectByTemplate(definition, 124_372); const keeperAltar = objectByTemplate(definition, 130_511); const keeperDoor = objectByTemplate(definition, 124_367); const archaedasAltar = objectByTemplate(definition, 133_234); const archaedasRoom = objectByTemplate(definition, 141_869); const vault = objectByTemplate(definition, 124_369); let snapshot = reduceDungeonEncounter(encounter, createDungeonEncounterSnapshot(encounter), { kind: "interact", objectId: keystone.id, now: 0, }).snapshot; expect(snapshot.worldObjectStates[seal.id]).toBe("open"); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: keeperAltar.id, now: 1 }).snapshot; snapshot = killGroup(encounter, snapshot, group(definition, "uldaman:stone-keeper-1"), 2).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: keeperAltar.id, now: 3 }).snapshot; expect(snapshot.activeSpawnGroupIds).toEqual(["uldaman:stone-keeper-2"]); snapshot = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 4); expect(snapshot).toMatchObject({ phase: "active", counters: { "keepers-defeated": 1 } }); expect(snapshot.activeSpawnGroupIds).toEqual([]); for (let number = 2; number <= 4; number += 1) { snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: keeperAltar.id, now: number * 10, }).snapshot; snapshot = killGroup(encounter, snapshot, group(definition, `uldaman:stone-keeper-${number}`), number * 10 + 1).snapshot; } expect(snapshot).toMatchObject({ phase: "active", counters: { "keepers-defeated": 4 }, checkpointId: "uldaman-stone-keepers" }); expect(snapshot.worldObjectStates[keeperDoor.id]).toBe("open"); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: archaedasAltar.id, now: 50 }).snapshot; expect(snapshot).toMatchObject({ phase: "archaedas-active", activeSpawnGroupIds: ["uldaman:archaedas"] }); expect(snapshot.worldObjectStates[archaedasRoom.id]).toBe("closed"); snapshot = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 51); expect(snapshot).toMatchObject({ phase: "active", activeSpawnGroupIds: [] }); expect(snapshot.worldObjectStates[archaedasRoom.id]).toBe("open"); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: archaedasAltar.id, now: 52 }).snapshot; snapshot = killGroup(encounter, snapshot, group(definition, "uldaman:archaedas"), 53).snapshot; const objective = definition.bosses.find((boss) => boss.name === "Archaedas")!; expect(snapshot.completedObjectiveIds).toContain(objective.id); expect(snapshot.worldObjectStates[vault.id]).toBe("open"); expect(snapshot.worldObjectStates[archaedasRoom.id]).toBe("open"); }); it("recovers Razorfen's gong and runs the full timed Belnistrasz event", () => { const definition = adapted("razorfen-downs"); const encounter = classicEncounter(definition); const gong = objectByTemplate(definition, 148_917); const gongGroupIds = [ "razorfen-downs:gong-fiends", "razorfen-downs:gong-reavers", "razorfen-downs:tutenkash", ]; let snapshot = reduceDungeonEncounter(encounter, createDungeonEncounterSnapshot(encounter), { kind: "interact", objectId: gong.id, now: 0, }).snapshot; expect(snapshot.activeSpawnGroupIds).toEqual([gongGroupIds[0]]); snapshot = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 1); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 1 }).snapshot; expect(snapshot).toMatchObject({ phase: "idle", counters: { "gong-round": 0 } }); for (let index = 0; index < gongGroupIds.length; index += 1) { snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: gong.id, now: 10 + index * 2, }).snapshot; snapshot = killGroup(encounter, snapshot, group(definition, gongGroupIds[index]), 11 + index * 2).snapshot; } const tutenkash = definition.bosses.find((boss) => boss.name === "Tuten'kash")!; expect(snapshot).toMatchObject({ counters: { "gong-round": 3 } }); expect(snapshot.completedObjectiveIds).toContain(tutenkash.id); expect(snapshot.worldObjectStates[gong.id]).toBe("complete"); const belnistrasz = definition.serverGameObjects.find((object) => object.id === "razorfen-downs:belnistrasz")!; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: belnistrasz.id, now: 100 }).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 8_100 }).snapshot; expect(snapshot.activeSpawnGroupIds).toContain("razorfen-downs:idol-wave-1"); const wiped = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 8_101); const recovered = reduceDungeonEncounter(encounter, wiped, { kind: "tick", now: 8_101 }).snapshot; expect(recovered).toMatchObject({ phase: "idle", activeSpawnGroupIds: [] }); expect(recovered.worldObjectStates[belnistrasz.id]).toBe("ready"); snapshot = reduceDungeonEncounter(encounter, recovered, { kind: "interact", objectId: belnistrasz.id, now: 9_000 }).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 17_000 }).snapshot; for (let wave = 2; wave <= 8; wave += 1) { snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: snapshot.phaseStartedAt + 35_000, }).snapshot; expect(snapshot.activeSpawnGroupIds).toContain(`razorfen-downs:idol-wave-${wave}`); } const plaguemawGroup = group(definition, "razorfen-downs:idol-wave-8"); snapshot = killGroup(encounter, snapshot, plaguemawGroup, snapshot.phaseStartedAt + 1).snapshot; const plaguemaw = definition.bosses.find((boss) => boss.name === "Plaguemaw the Rotting")!; expect(snapshot.completedObjectiveIds).toContain(plaguemaw.id); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: snapshot.phaseStartedAt + 55_000, }).snapshot; expect(snapshot).toMatchObject({ phase: "idol-complete", flags: { "idol-extinguished": true } }); const fires = definition.serverGameObjects.filter((object) => [151_951, 151_952, 151_973].includes(object.source?.templateId ?? -1)); expect(fires.every((fire) => snapshot.worldObjectStates[fire.id] === "extinguished")).toBe(true); const resetComplete = resetDungeonEncounterSnapshot(encounter, snapshot, "checkpoint", snapshot.phaseStartedAt + 1); const resumedComplete = reduceDungeonEncounter(encounter, resetComplete, { kind: "tick", now: snapshot.phaseStartedAt + 1 }).snapshot; expect(resumedComplete).toMatchObject({ phase: "idol-complete", flags: { "idol-extinguished": true } }); }); it("runs Zul'Farrak's exact 23/24/7 pyramid and independently recovers the Gahz'rilla gong", () => { const definition = adapted("zulfarrak"); const encounter = classicEncounter(definition); const groups = [1, 2, 3].map((wave) => group(definition, `zulfarrak:pyramid-wave-${wave}`)); expect(groups.map((spawnGroup) => spawnGroup.spawns.length)).toEqual([23, 24, 7]); const cages = definition.serverGameObjects.filter((object) => object.name === "Troll Cage"); const endDoor = objectByTemplate(definition, 146_084); const weegli = definition.serverGameObjects.find((object) => object.id === "zulfarrak:weegli-gate-charge")!; let snapshot = reduceDungeonEncounter(encounter, createDungeonEncounterSnapshot(encounter), { kind: "interact", objectId: cages[0].id, now: 0, }).snapshot; expect(cages.every((cage) => snapshot.worldObjectStates[cage.id] === "open")).toBe(true); snapshot = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 1); expect(snapshot).toMatchObject({ phase: "idle", activeSpawnGroupIds: [] }); expect(cages.every((cage) => snapshot.worldObjectStates[cage.id] === "closed")).toBe(true); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: cages[2].id, now: 2 }).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 5_002 }).snapshot; expect(snapshot.activeSpawnGroupIds).toEqual([groups[0].id]); snapshot = killGroup(encounter, snapshot, groups[0], 5_003).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 15_003 }).snapshot; expect(snapshot.activeSpawnGroupIds).toEqual([groups[1].id]); snapshot = killGroup(encounter, snapshot, groups[1], 15_004).snapshot; expect(snapshot.activeSpawnGroupIds).toEqual([groups[2].id]); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 20_004 }).snapshot; snapshot = killGroup(encounter, snapshot, groups[2], 20_005).snapshot; expect(snapshot).toMatchObject({ phase: "pyramid-aftermath", activeSpawnGroupIds: [] }); expect(snapshot.worldObjectStates[weegli.id]).toBe("ready"); snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "interact", objectId: weegli.id, now: 20_006 }).snapshot; snapshot = reduceDungeonEncounter(encounter, snapshot, { kind: "tick", now: 24_006 }).snapshot; expect(snapshot).toMatchObject({ phase: "pyramid-complete", flags: { "pyramid-complete": true }, checkpointId: "zulfarrak-pyramid" }); expect(snapshot.worldObjectStates[endDoor.id]).toBe("open"); const reset = resetDungeonEncounterSnapshot(encounter, snapshot, "checkpoint", 24_007); expect(reset.worldObjectStates[endDoor.id]).toBe("open"); expect(cages.every((cage) => reset.worldObjectStates[cage.id] === "open")).toBe(true); const gong = objectByTemplate(definition, 141_832); snapshot = reduceDungeonEncounter(encounter, reset, { kind: "interact", objectId: gong.id, now: 25_000 }).snapshot; const gahzrillaGroup = group(definition, "zulfarrak:gahzrilla"); expect(snapshot.activeSpawnGroupIds).toEqual([gahzrillaGroup.id]); const gongWipe = resetDungeonEncounterSnapshot(encounter, snapshot, "boss-wipe", 25_001); expect(gongWipe.worldObjectStates[gong.id]).toBe("ready"); snapshot = reduceDungeonEncounter(encounter, gongWipe, { kind: "interact", objectId: gong.id, now: 25_002 }).snapshot; snapshot = killGroup(encounter, snapshot, gahzrillaGroup, 25_003).snapshot; const gahzrilla = definition.bosses.find((boss) => boss.name === "Ghaz'rilla")!; expect(snapshot.completedObjectiveIds).toContain(gahzrilla.id); expect(snapshot.worldObjectStates[gong.id]).toBe("complete"); }); });