import { describe, expect, it } from "vitest"; import campaign from "../../dungeon-pipeline/dungeon-campaign.json"; import epochInstances from "../../dungeon-pipeline/epoch-five-player-instances.json"; import gameObjectAssetManifest from "../../dungeon-pipeline/WOW_GAMEOBJECT_ASSET_MANIFEST.json"; import generated from "./generated/dungeonCampaignCatalog.json"; import manastormPackages from "./generated/manastormAssetPackages.json"; import { DUNGEON_DEFINITIONS, requireDungeonDefinition } from "./dungeonRegistry"; import { createDungeonEncounterSnapshot, reduceDungeonEncounter, resetDungeonEncounterSnapshot, validateDungeonEncounterDefinition, } from "./dungeonEncounterRuntime"; import type { DungeonEncounterDefinition } from "./dungeonEncounterTypes"; import { validateDungeonWorldObject } from "./dungeonWorldObjects"; const SOURCE_NATIVE_DUNGEON_IDS = [ "ahnkahet-old-kingdom", "arcatraz", "auchenai-crypts", "azjol-nerub", "black-morass", "blackfathom-deeps", "blackrock-depths", "blackrock-spire", "blood-furnace", "botanica", "culling-of-stratholme", "draktharon-keep", "escape-from-durnholde", "forge-of-souls", "gnomeregan", "gundrak", "halls-of-lightning", "halls-of-reflection", "halls-of-stone", "magisters-terrace", "mana-tombs", "maraudon", "mechanar", "pit-of-saron", "ragefire-chasm", "scarlet-monastery", "sethekk-halls", "shadow-labyrinth", "shadowfang-keep", "shattered-halls", "slave-pens", "steamvault", "stormwind-stockade", "sunken-temple", "the-nexus", "the-oculus", "trial-of-the-champion", "uldaman", "underbog", "utgarde-keep", "utgarde-pinnacle", "violet-hold", "zulfarrak", ] as const; const WDT_PLACEMENT_DUNGEON_IDS = [ "dire-maul", "hellfire-ramparts", "razorfen-downs", "razorfen-kraul", "scholomance", "stratholme", ] as const; describe("generated dungeon campaign catalog", () => { it("covers only reviewed five-player instances with loadable assets and combat content", () => { expect(generated.definitions).toHaveLength(54); expect(DUNGEON_DEFINITIONS).toHaveLength(86); expect(generated.definitions.map((dungeon) => dungeon.id)) .toEqual(campaign.dungeons.map((dungeon) => dungeon.slug)); for (const dungeon of generated.definitions) { expect(dungeon.assets.visual.length, dungeon.id).toBeGreaterThan(0); expect(dungeon.assets.collision.length, dungeon.id).toBeGreaterThan(0); expect(dungeon.assets.navigation.url, dungeon.id) .toMatch(/^\/assets\/game\/(?:dungeons|manastorm)\//); expect(Object.keys(dungeon.entities).length, dungeon.id).toBeGreaterThan(0); expect(dungeon.staticSpawns.length + dungeon.roamingPacks.length, dungeon.id).toBeGreaterThan(0); expect(dungeon.objectiveOrder.length, dungeon.id).toBeGreaterThan(0); } }); it("never exposes raw database gameobject rows to the live world-object renderer", () => { for (const dungeon of DUNGEON_DEFINITIONS) { for (const object of dungeon.serverGameObjects) { expect(() => validateDungeonWorldObject(object), `${dungeon.id}:${object.id}`).not.toThrow(); } } }); it("gives every WoW dungeon executable persisted boss progression", () => { const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => ( campaign.dungeons.some((entry) => entry.slug === dungeon.id) )); expect(wowDungeons).toHaveLength(54); for (const dungeon of wowDungeons) { const objectiveIds = dungeon.objectiveOrder.map((objective) => objective.id); expect(new Set(objectiveIds).size, `${dungeon.id}:duplicate-objective-id`).toBe(objectiveIds.length); const encounters = dungeon.encounters ?? []; for (const encounter of encounters) { expect(() => validateDungeonEncounterDefinition(encounter), `${dungeon.id}:${encounter.id}`).not.toThrow(); } const allCompletedObjectiveIds = new Set(encounters.flatMap((encounter) => ( encounter.rules.flatMap((rule) => rule.actions.flatMap((action) => ( action.kind === "complete-objective" ? [action.objectiveId] : [] ))) ))); for (const objective of dungeon.objectiveOrder) { expect(allCompletedObjectiveIds.has(objective.id), `${dungeon.id}:${objective.name}`).toBe(true); } const progression = dungeon.encounters?.find( (encounter) => encounter.source?.scope === "boss-progression", ) as DungeonEncounterDefinition | undefined; const instanceCompletion = encounters.find((encounter) => ( encounter.source?.scope === "instance-orchestration" && encounter.rules.some((rule) => rule.actions.some((action) => ( action.kind === "set-flag" && action.flagId === "dungeon-complete" && action.value ))) )); const authority = progression ?? instanceCompletion; expect(authority, dungeon.id).toBeDefined(); expect(authority!.source?.fidelity, dungeon.id).toBe( dungeon.id === "tinkertech-showdown" ? "healer-man-authored" : "source-backed-adapted", ); if (!progression) { expect(instanceCompletion?.source?.scope, dungeon.id).toBe("instance-orchestration"); const completedObjectiveIds = new Set(instanceCompletion!.rules.flatMap((rule) => ( rule.actions.flatMap((action) => ( action.kind === "complete-objective" ? [action.objectiveId] : [] )) ))); for (const objective of dungeon.objectiveOrder.filter((candidate) => !candidate.optional)) { expect(completedObjectiveIds.has(objective.id), `${dungeon.id}:${objective.name}`).toBe(true); } continue; } const progressionObjectiveIds = new Set(progression.rules.flatMap((rule) => ( rule.actions.flatMap((action) => ( action.kind === "complete-objective" ? [action.objectiveId] : [] )) ))); const runtimeBosses = dungeon.objectiveOrder.filter((objective) => ( progressionObjectiveIds.has(objective.id) )); let snapshot = createDungeonEncounterSnapshot(progression, 1_000); const deadEntityIds: string[] = []; for (const boss of runtimeBosses) { deadEntityIds.push(boss.id); snapshot = reduceDungeonEncounter(progression, snapshot, { kind: "tick", now: 1_000 + deadEntityIds.length * 100, facts: { registeredEntityIds: runtimeBosses.map((objective) => objective.id), deadEntityIds, }, }).snapshot; expect(snapshot.completedObjectiveIds, `${dungeon.id}:${boss.id}`).toContain(boss.id); } const checkpoint = resetDungeonEncounterSnapshot(progression, snapshot, "checkpoint", 5_000); expect(checkpoint.completedObjectiveIds, dungeon.id) .toEqual(expect.arrayContaining(runtimeBosses.map((boss) => boss.id))); const completesFromObjectivesAlone = progression.rules.some((rule) => ( rule.actions.some((action) => ( action.kind === "set-flag" && action.flagId === "dungeon-complete" && action.value )) && (rule.guards ?? []).every((guard) => guard.kind === "objective-complete") )); if (!completesFromObjectivesAlone) { // A full source adapter may own completion inside a separate encounter; // its focused lifecycle suite exercises that path. expect(snapshot.flags["dungeon-complete"], dungeon.id).toBe(false); } else { expect(snapshot.flags["dungeon-complete"], dungeon.id).toBe(true); expect(snapshot.phase, dungeon.id).toBe("complete"); expect(checkpoint.flags["dungeon-complete"], dungeon.id).toBe(true); } } }); it("has an instance-level source review for every campaign dungeon", () => { const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => ( campaign.dungeons.some((entry) => entry.slug === dungeon.id) )); const verifiedNoOps: string[] = []; for (const dungeon of wowDungeons) { const reviewed = (dungeon.encounters ?? []).filter( (encounter) => encounter.source?.scope === "instance-orchestration", ); expect(reviewed.length, dungeon.id).toBeGreaterThan(0); for (const encounter of reviewed) { expect(encounter.source?.files.length, `${dungeon.id}:${encounter.id}:files`).toBeGreaterThan(0); expect(encounter.source?.reviewedBehavior.trim(), `${dungeon.id}:${encounter.id}:review`).not.toBe(""); if (!encounter.rules.length) { expect(encounter.source?.fidelity, `${dungeon.id}:${encounter.id}`).toBe("source-exact"); expect(encounter.source?.adaptations, `${dungeon.id}:${encounter.id}`).toEqual([]); expect(encounter.source?.remainingGaps, `${dungeon.id}:${encounter.id}`).toEqual([]); verifiedNoOps.push(dungeon.id); } } } expect(wowDungeons).toHaveLength(54); expect(verifiedNoOps.sort()).toEqual([ "ragefire-chasm", "razorfen-kraul", "stormwind-stockade", ]); }); it("gates every required event-only objective behind an instance completion authority", () => { const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => ( campaign.dungeons.some((entry) => entry.slug === dungeon.id) )); for (const dungeon of wowDungeons) { const requiredEvents = dungeon.objectiveOrder.filter( (objective) => objective.eventOnly && !objective.optional, ); if (!requiredEvents.length) continue; const progression = dungeon.encounters?.find( (encounter) => encounter.source?.scope === "boss-progression", ); const baselineGatedObjectiveIds = new Set((progression?.rules ?? []).flatMap((rule) => ( rule.actions.some((action) => ( action.kind === "set-flag" && action.flagId === "dungeon-complete" && action.value )) ? (rule.guards ?? []).flatMap((guard) => ( guard.kind === "objective-complete" && guard.equals ? [guard.objectiveId] : [] )) : [] ))); const completionAuthorities = (dungeon.encounters ?? []).filter((encounter) => ( encounter.source?.scope === "instance-orchestration" && encounter.rules.some((rule) => rule.actions.some((action) => ( action.kind === "set-flag" && action.flagId === "dungeon-complete" && action.value ))) )); for (const objective of requiredEvents) { expect( baselineGatedObjectiveIds.has(objective.id) || completionAuthorities.length > 0, `${dungeon.id}:${objective.name}`, ).toBe(true); if (progression && completionAuthorities.length) { const completionRules = completionAuthorities.flatMap((encounter) => ( encounter.rules.filter((rule) => rule.actions.some((action) => ( action.kind === "set-flag" && action.flagId === "dungeon-complete" && action.value ))) )); expect(completionRules.length, `${dungeon.id}:completion-authority`).toBeGreaterThan(0); expect(completionRules.every((rule) => ( (rule.guards ?? []).some((guard) => ( guard.kind === "objective-complete" && guard.objectiveId === objective.id && guard.equals )) || rule.actions.some((action) => ( action.kind === "complete-objective" && action.objectiveId === objective.id )) )), `${dungeon.id}:${objective.name}:premature-completion`).toBe(true); } } } }); it("only uses difficulty guards that match declared dungeon variants", () => { const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => ( campaign.dungeons.some((entry) => entry.slug === dungeon.id) )); for (const dungeon of wowDungeons) { const difficultyIds = new Set(dungeon.difficultyVariants.map((variant) => variant.id)); for (const encounter of dungeon.encounters ?? []) { for (const rule of encounter.rules) { for (const guard of rule.guards ?? []) { if (guard.kind !== "difficulty") continue; expect(guard.difficultyIds.length, `${dungeon.id}:${rule.id}`).toBeGreaterThan(0); for (const difficultyId of guard.difficultyIds) { expect(difficultyIds.has(difficultyId), `${dungeon.id}:${rule.id}:${difficultyId}`).toBe(true); } } } } } }); it("completes ordinary dungeons regardless of required boss kill order", () => { const dungeon = requireDungeonDefinition("deadmines"); const progression = dungeon.encounters?.find( (encounter) => encounter.id === "deadmines:boss-progression", ); expect(progression).toBeDefined(); const required = dungeon.objectiveOrder.filter((objective) => !objective.optional && !objective.eventOnly); expect(required.length).toBeGreaterThan(1); const killOrder = [required.at(-1)!, ...required.slice(0, -1)]; const deadEntityIds: string[] = []; let snapshot = createDungeonEncounterSnapshot(progression!, 1_000); for (const boss of killOrder) { deadEntityIds.push(boss.id); snapshot = reduceDungeonEncounter(progression!, snapshot, { kind: "tick", now: 1_000 + deadEntityIds.length * 100, facts: { registeredEntityIds: required.map((objective) => objective.id), deadEntityIds, }, }).snapshot; } expect(snapshot.flags["dungeon-complete"]).toBe(true); expect(snapshot.phase).toBe("complete"); }); it("compiles every sourced WoW gameobject row with stable source provenance", () => { const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => ( campaign.dungeons.some((entry) => entry.slug === dungeon.id) )); const objects = wowDungeons.flatMap((dungeon) => dungeon.serverGameObjects); const sourceRows = objects.filter((object) => ( object.source?.templateId !== undefined && object.source?.spawnId !== undefined )); expect(wowDungeons).toHaveLength(54); expect(sourceRows).toHaveLength(3_943); expect(sourceRows.filter((object) => object.source?.clientDisplayId)).toHaveLength(3_905); expect(sourceRows.filter((object) => object.source?.clientModelPath)).toHaveLength(3_837); expect(sourceRows.filter((object) => object.source?.clientBounds)).toHaveLength(3_547); for (const dungeon of wowDungeons) { expect(new Set(dungeon.serverGameObjects.map((object) => object.id)).size) .toBe(dungeon.serverGameObjects.length); for (const object of dungeon.serverGameObjects) { expect(object.source?.fidelity).toBe("source-backed-adapted"); expect(object.source?.files.length).toBeGreaterThan(0); if (object.source?.spawnId !== undefined) { expect(object.source.templateId).toBeGreaterThan(0); expect(object.source.spawnId).toBeGreaterThan(0); expect(object.source.gameObjectType).toBeGreaterThanOrEqual(0); expect(object.source.gameObjectType).toBeLessThanOrEqual(35); } if (object.visual?.hidden) { expect(object.interaction).toBeUndefined(); expect(object.states.every((state) => !state.blocksMovement && !state.blocksSight)).toBe(true); } } } }); it("publishes a deduplicated export manifest for source-backed gameobject visuals", () => { expect(gameObjectAssetManifest.summary).toEqual({ typedInstances: 3_943, visualDisplayInstances: 3_905, nonVisualInstances: 38, resolvedModelInstances: 3_837, boundedInstances: 3_547, uniqueAssets: 458, unresolvedModelInstances: 68, unresolvedBoundInstances: 358, unresolvedModelDisplayIds: 9, unresolvedBoundDisplayIds: 68, }); expect(gameObjectAssetManifest.assets).toHaveLength(458); expect(new Set(gameObjectAssetManifest.assets.map( (asset) => asset.sourceModelPath.toLowerCase(), )).size).toBe(gameObjectAssetManifest.assets.length); expect(gameObjectAssetManifest.assets.every((asset) => ( ["mdl", "mdx"].includes(asset.sourceType) && asset.targetUrl.startsWith("/assets/game/world-objects/") && asset.targetUrl.endsWith(".glb") && asset.instances > 0 && asset.displayIds.length > 0 && asset.dungeons.length > 0 ))).toBe(true); expect(gameObjectAssetManifest.assets.reduce((total, asset) => total + asset.instances, 0)) .toBe(gameObjectAssetManifest.summary.resolvedModelInstances); expect(gameObjectAssetManifest.nonVisualObjects.reduce( (total, object) => total + object.instances, 0, )).toBe(gameObjectAssetManifest.summary.nonVisualInstances); expect(gameObjectAssetManifest.unresolvedModels.map((entry) => entry.displayId)).toEqual([ 9158, 9159, 9160, 9161, 9162, 9163, 9236, 9467, 9468, ]); }); it("runs Wailing Caverns with the complete server population and authored presentation", () => { const wailing = requireDungeonDefinition("wailing-caverns"); const runtimePopulation = wailing.staticSpawns.length + wailing.roamingPacks.reduce((total, pack) => total + pack.members.length, 0); expect(runtimePopulation).toBeGreaterThan(250); expect(wailing.staticSpawns.some((spawn) => spawn.id === "creature-18678")).toBe(true); expect(wailing.staticSpawns.some((spawn) => spawn.id === "lady-anacondra-spawn")).toBe(true); expect(wailing.roamingPacks.some((pack) => pack.id === "kresh-patrol")).toBe(true); expect(wailing.entities["entry-3636"]).toMatchObject({ name: "Deviate Ravager", combat: { serverEntry: 3636 }, visual: { model: { url: "/assets/creatures/wailing-caverns/deviate-ravager-animated.glb" }, }, }); }); it("never replaces a generated dungeon with a smaller live population", () => { for (const source of generated.definitions) { const runtime = requireDungeonDefinition(source.id); const sourcePopulation = source.staticSpawns.length + source.roamingPacks.reduce((total, pack) => total + pack.members.length, 0); const runtimePopulation = runtime.staticSpawns.length + runtime.roamingPacks.reduce((total, pack) => total + pack.members.length, 0) + new Set((runtime.encounterSpawnGroups ?? []) .flatMap((group) => group.spawns.map((spawn) => spawn.id)) .filter((id) => !runtime.staticSpawns.some((spawn) => spawn.id === id))).size; if (source.id === "frozen-reach") { expect(runtime.staticSpawns.some((spawn) => spawn.id.startsWith("generated-trash-"))).toBe(false); expect(runtime.validation.warnings.join(" ")).toMatch(/recovered Alva\/Arktos/i); } else { expect(runtimePopulation, source.id).toBeGreaterThanOrEqual(sourcePopulation); } } }); it("preserves source-side random rare spawns without making rare bosses mandatory", () => { const chanceSpawns = generated.definitions.flatMap((dungeon) => [ ...dungeon.staticSpawns.filter((spawn) => "spawnChance" in spawn), ...dungeon.roamingPacks.filter((pack) => "spawnChance" in pack), ]) as unknown as ReadonlyArray<{ readonly spawnChance: number }>; const optionalBosses = generated.definitions.flatMap((dungeon) => ( dungeon.objectiveOrder.filter((objective) => { if (!("optional" in objective) || !objective.optional) return false; const spawn = dungeon.staticSpawns.find((candidate) => candidate.id === objective.id); return Boolean(spawn && "spawnChance" in spawn); }) )); expect(chanceSpawns).toHaveLength(24); expect(chanceSpawns.every((spawn) => spawn.spawnChance > 0 && spawn.spawnChance < 1)).toBe(true); expect(optionalBosses.map((boss) => boss.name)).toEqual([ "Earthcaller Halmgar", "Ragglesnout", "Crystal Fang", "Spirestone Battle Lord", "Spirestone Butcher", "Meshlok the Harvester", ]); }); it("reuses each complete optimized map package for dungeons and Manastorms", () => { const packagesByMap = new Map( manastormPackages.packages.map((assetPackage) => [assetPackage.mapId, assetPackage]), ); const identity = (descriptor: { readonly url: string; readonly checksum: string }) => ({ url: descriptor.url, checksum: descriptor.checksum, }); for (const dungeon of generated.definitions) { const assetPackage = packagesByMap.get(dungeon.mapId); expect(assetPackage, dungeon.id).toBeTruthy(); expect(dungeon.assets.visual.map(identity), dungeon.id).toEqual(assetPackage!.visual.map(identity)); expect(dungeon.assets.collision.map(identity), dungeon.id) .toEqual(assetPackage!.collision.map(identity)); expect(identity(dungeon.assets.navigation), dungeon.id).toEqual(identity(assetPackage!.navigation)); } const shadowfang = generated.definitions.find((dungeon) => dungeon.id === "shadowfang-keep")!; expect(shadowfang.assets.navigation.url) .toMatch(/^\/assets\/game\/manastorm\/33-shadowfang\//); expect(Object.values(shadowfang.entities).every((entity) => entity.visual.model)).toBe(true); expect( Object.values(shadowfang.entities).every( (entity) => ( entity.visual.model?.url.includes("/dungeons/shadowfang-keep/creatures/") || entity.visual.model?.url.includes("/game/creatures/shared/") ), ), ).toBe(true); }); it("ships every selected Epoch map as a true normal/heroic instanced dungeon", () => { expect(epochInstances.dungeons).toHaveLength(18); for (const source of epochInstances.dungeons) { const dungeon = generated.definitions.find((candidate) => candidate.id === source.slug); expect(dungeon, source.slug).toBeTruthy(); expect(dungeon!.mapId, source.slug).toBe(source.mapId); expect(dungeon!.provenance.clientBuild, source.slug).toBe("3.3.5a-Epoch"); expect(dungeon!.difficultyVariants.map((variant) => variant.id), source.slug) .toEqual(["normal", "heroic"]); expect(dungeon!.objectiveOrder.length, source.slug).toBeGreaterThan(0); expect(dungeon!.staticSpawns.length + dungeon!.roamingPacks.length, source.slug) .toBeGreaterThan(0); const entities = Object.values(dungeon!.entities) as ReadonlyArray<{ readonly id: string; readonly name: string; readonly visual: { readonly model?: { readonly url: string } }; }>; const eventOnlyNames = new Set(( dungeon!.objectiveOrder as unknown as ReadonlyArray<{ readonly name: string; readonly eventOnly?: boolean; }> ).filter((objective) => objective.eventOnly).map((objective) => objective.name)); for (const entity of entities) { if (entity.visual.model) { expect(entity.visual.model.url, `${source.slug}:${entity.name}`) .toMatch(new RegExp( `^/assets/(?:game/creatures/shared|game/dungeons/${source.slug}/creatures)/.+\\.glb$`, )); } else { expect( eventOnlyNames.has(entity.name) || /^entry-8\d{8}$/.test(entity.id), `${source.slug}:${entity.name}`, ).toBe(true); } } } }); it("compiles exact scripted boss abilities before generic fallbacks", () => { const forge = generated.definitions.find((dungeon) => dungeon.id === "forge-of-souls"); const culling = generated.definitions.find((dungeon) => dungeon.id === "culling-of-stratholme"); expect(forge).toBeTruthy(); expect(culling).toBeTruthy(); const attackNames = ( dungeon: NonNullable, bossName: string, ): string[] => { const entity = Object.values(dungeon.entities).find((candidate) => candidate.name === bossName); return entity?.combat.attacks.map((attack: { readonly name: string }) => attack.name) ?? []; }; expect(attackNames(forge!, "Bronjahm")).toEqual([ "Basic Attack", "Shadow Bolt", "Fear", "Magic's Bane", "Corrupt Soul", "Soulstorm", ]); expect(attackNames(culling!, "Mal'Ganis")).toEqual([ "Basic Attack", "Carrion Swarm", "Mind Blast", "Sleep", "Vampiric Touch", ]); const attackFor = ( dungeonId: string, bossName: string, attackName: string, ): { readonly damageMultiplier: number; readonly effect?: unknown } | undefined => { const dungeon = generated.definitions.find((candidate) => candidate.id === dungeonId); const entity = Object.values(dungeon?.entities ?? {}).find( (candidate) => candidate.name === bossName, ) as undefined | { readonly combat: { readonly attacks: ReadonlyArray<{ readonly name: string; readonly damageMultiplier: number; readonly effect?: unknown; }>; }; }; return entity?.combat.attacks.find((attack) => attack.name === attackName); }; expect(attackFor("forge-of-souls", "Bronjahm", "Fear")).toMatchObject({ effect: { kind: "control", mechanic: "fear", durationMs: 3_000 }, }); expect(attackFor("culling-of-stratholme", "Mal'Ganis", "Sleep")).toMatchObject({ effect: { kind: "control", mechanic: "sleep", durationMs: 10_000 }, }); expect(attackFor("blackrock-depths", "Warder Stilgiss", "Frostbolt")).toMatchObject({ damageMultiplier: 0, effect: { kind: "control", mechanic: "slow", durationMs: 8_000, damageMultiplier: 1.35, }, }); expect(attackFor("blackrock-depths", "Warder Stilgiss", "Frost Nova")).toMatchObject({ damageMultiplier: 0, effect: { kind: "control", mechanic: "root", durationMs: 8_000, damageMultiplier: 0.82, }, }); expect(attackFor("razorfen-kraul", "Aggem Thorncurse", "Summon Boar Spirit")) .toMatchObject({ damageMultiplier: 0, effect: { kind: "summon", creatureEntry: 6021, count: 1 }, }); expect(attackFor("blackrock-depths", "Bael'Gar", "Summon Spawn of Bael'Gar")) .toMatchObject({ effect: { kind: "composite", effects: [ { kind: "damage", multiplier: 1.35 }, { kind: "summon", creatureEntry: 9436, count: 1 }, ], }, }); expect(attackFor("shattered-halls", "Grand Warlock Nethekurse", "Death Coil")) .toMatchObject({ effect: { kind: "composite", effects: [ { kind: "leech", damageMultiplier: 1.35, healMultiplier: 1.35 }, { kind: "control", mechanic: "fear", durationMs: 4_000 }, ], }, }); expect(attackFor("magisters-terrace", "Kael'thas Sunstrider", "Shock Barrier")) .toMatchObject({ effect: { kind: "aura", absorbMultiplier: 2.5, durationMs: 10_000 }, }); expect(attackFor("blackrock-spire", "General Drakkisath", "Pierce Armor")) .toMatchObject({ effect: { kind: "target-aura", damageTakenMultiplier: 1.2, damageSchool: "physical", }, }); expect(attackFor("culling-of-stratholme", "Mal'Ganis", "Vampiric Touch")) .toMatchObject({ effect: { kind: "aura", lifeStealPercent: 0.5, durationMs: 30_000 }, }); expect(attackFor("underbog", "The Black Stalker", "Summon Player")) .toMatchObject({ effect: { kind: "pull", destination: "source" } }); const effectRegressionSpellIds = new Set([ 246, 6_016, 7_645, 8_286, 8_398, 8_988, 9_080, 10_061, 10_180, 11_086, 11_700, 12_097, 12_479, 12_480, 12_556, 12_642, 13_323, 13_747, 13_895, 14_515, 14_907, 15_041, 15_044, 15_487, 15_530, 15_572, 15_859, 16_097, 17_235, 17_238, 17_279, 17_503, 17_773, 18_099, 18_327, 20_279, 21_707, 22_714, 30_500, 31_692, 32_358, 32_364, 32_365, 32_371, 32_424, 34_309, 34_557, 35_032, 35_280, 36_931, 36_932, 37_162, 38_245, 44_219, 44_294, 46_165, 49_037, 52_723, 53_030, 53_418, 66_043, 67_534, 67_705, 67_715, ]); const observedEffectSpellIds = new Set(); for (const dungeon of generated.definitions) { for (const entity of Object.values(dungeon.entities)) { if (entity.kind !== "boss") continue; for (const attack of entity.combat.attacks) { if (!effectRegressionSpellIds.has(attack.spellId ?? 0)) continue; expect(attack.effect, `${dungeon.id}:${entity.name}:${attack.name}`).toBeDefined(); observedEffectSpellIds.add(attack.spellId!); } } } expect([...observedEffectSpellIds].sort((left, right) => left - right)) .toEqual([...effectRegressionSpellIds].sort((left, right) => left - right)); const auxiliarySpellIds = new Set([21_708, 47_669, 54_888]); for (const dungeon of generated.definitions) { for (const entity of Object.values(dungeon.entities)) { if (entity.kind !== "boss") continue; expect( entity.combat.attacks.some((attack: { readonly spellId?: number }) => ( auxiliarySpellIds.has(attack.spellId ?? 0) )), `${dungeon.id}:${entity.name}:auxiliary-cast`, ).toBe(false); } } expect( attackFor("escape-from-durnholde", "Captain Skarloc", "Holy Light"), ).toMatchObject({ effect: { kind: "heal", multiplier: 1.2 }, }); }); it("compiles Archaedas's timed and health-based encounter mechanics", () => { const uldaman = generated.definitions.find((dungeon) => dungeon.id === "uldaman")!; const archaedas = Object.values(uldaman.entities).find( (entity) => entity.name === "Archaedas", ) as unknown as { readonly combat: { readonly mechanicIds: readonly string[]; readonly attacks: ReadonlyArray<{ readonly id: string; readonly initialCooldownMs?: readonly [number, number]; readonly repeatCooldownMs?: readonly [number, number]; readonly condition?: { readonly kind: string; readonly maximum?: number }; readonly effect?: { readonly kind: string; readonly creatureEntry?: number; readonly count?: number }; readonly telegraph?: { readonly shape: string; readonly anchor: string; readonly radius?: number }; }>; }; }; expect(archaedas.combat.mechanicIds).toEqual([ "archaedas-awaken-earthen-custodian", "archaedas-ground-tremor", "archaedas-awaken-earthen-guardians", "archaedas-awaken-vault-warders", ]); expect(archaedas.combat.attacks.find((attack) => attack.id === "archaedas-ground-tremor")) .toMatchObject({ initialCooldownMs: [0, 10_000], repeatCooldownMs: [14_000, 20_000], telegraph: { shape: "circle", anchor: "source", radius: 18 }, }); expect(archaedas.combat.attacks.find( (attack) => attack.id === "archaedas-awaken-earthen-guardians", )).toMatchObject({ condition: { kind: "self-health-percent", maximum: 70 }, effect: { kind: "summon", creatureEntry: 7076, count: 6 }, }); expect(archaedas.combat.attacks.find( (attack) => attack.id === "archaedas-awaken-vault-warders", )).toMatchObject({ condition: { kind: "self-health-percent", maximum: 40 }, effect: { kind: "summon", creatureEntry: 10120, count: 2 }, }); }); it("does not leak outdoor zones, arenas, or raids into the dungeon browser", () => { const ids = new Set(generated.definitions.map((dungeon) => dungeon.id)); for (const excluded of [ "ashamanes-fall", "redridge-mountains", "westfall-outdoor", "tanaris-outdoor", "nagrand-outdoor", "black-rook-hold-arena", "molten-core", "karazhan", ]) { expect(ids.has(excluded), excluded).toBe(false); } }); it("gives every generated creature a complete animation-driving attack kit", () => { for (const dungeon of generated.definitions) { for (const entity of Object.values(dungeon.entities)) { expect(entity.combat.attacks.length, `${dungeon.id}:${entity.id}`).toBeGreaterThan(0); for (const attack of entity.combat.attacks) { expect(["attack", "cast"], `${dungeon.id}:${entity.id}:${attack.id}`) .toContain(attack.animation); expect(attack.cooldownMs, `${dungeon.id}:${entity.id}:${attack.id}`).toBeGreaterThan(0); if (attack.target === "self") { expect(attack.range, `${dungeon.id}:${entity.id}:${attack.id}`).toBeGreaterThanOrEqual(0); } else { expect(attack.range, `${dungeon.id}:${entity.id}:${attack.id}`).toBeGreaterThan(0); } } } } }); it("binds every boss objective to an actual boss spawn", () => { for (const dungeon of generated.definitions) { const spawns = new Map(dungeon.staticSpawns.map((spawn) => [spawn.id, spawn])); const entities = dungeon.entities as unknown as Readonly>; for (const boss of dungeon.objectiveOrder) { const spawn = spawns.get(boss.id); if ("eventOnly" in boss && boss.eventOnly) { expect(spawn, `${dungeon.id}:${boss.name}`).toBeUndefined(); expect( Object.values(dungeon.entities).some((entity) => ( entity.kind === "boss" && entity.name === boss.name )), `${dungeon.id}:${boss.name}`, ).toBe(true); continue; } expect(spawn, `${dungeon.id}:${boss.name}`).toBeTruthy(); expect(entities[spawn!.entityId]?.kind, `${dungeon.id}:${boss.name}`).toBe("boss"); } } }); it("makes every required boss objective available in every declared difficulty", () => { for (const dungeon of generated.definitions) { const spawns = new Map(dungeon.staticSpawns.map((spawn) => [spawn.id, spawn])); for (const boss of dungeon.objectiveOrder) { if (("eventOnly" in boss && boss.eventOnly) || ("optional" in boss && boss.optional)) continue; const spawn = spawns.get(boss.id); expect(spawn, `${dungeon.id}:${boss.name}`).toBeTruthy(); for (const difficulty of dungeon.difficultyVariants) { expect( (spawn!.spawnMask & difficulty.spawnMask) !== 0, `${dungeon.id}:${boss.name}:${difficulty.id}`, ).toBe(true); } } } }); it("preserves heroic-only Epoch bosses without gating normal completion", () => { for (const [dungeonId, bossName] of [ ["the-nexus", "Frozen Commander"], ["ahnkahet-old-kingdom", "Amanitar"], ["gundrak", "Eck the Ferocious"], ] as const) { const dungeon = generated.definitions.find((entry) => entry.id === dungeonId)!; const boss = dungeon.objectiveOrder.find((objective) => objective.name === bossName)!; const spawn = dungeon.staticSpawns.find((entry) => entry.id === boss.id)!; expect("optional" in boss && boss.optional, `${dungeonId}:${bossName}`).toBe(true); expect(spawn.spawnMask, `${dungeonId}:${bossName}`).toBe(2); } const blackMorass = generated.definitions.find((dungeon) => dungeon.id === "black-morass")!; for (const boss of blackMorass.objectiveOrder) { const spawn = blackMorass.staticSpawns.find((entry) => entry.id === boss.id)!; expect("optional" in boss && boss.optional, boss.name).not.toBe(true); expect(spawn.spawnMask, boss.name).toBe(3); } }); it("aligns the Deadmines source population to its packaged interior route", () => { const deadmines = requireDungeonDefinition("deadmines"); const firstPack = deadmines.staticSpawns.find( (spawn) => spawn.id === "creature-79127", ); expect(deadmines.environmentMode).toBe("adt-hybrid"); expect(deadmines.provenance.coordinateTransform) .toBe("three(x,y,z)=(wow.y,wow.z,wow.x)"); expect(deadmines.entrance.footPosition).toEqual([-383.0017, 61.9028, -16.4018]); expect(firstPack?.position).toEqual([-401.1297, 55.8039, -50.2669]); expect(deadmines.objectiveOrder[0]?.position) .toEqual([-448.1427, 54.5567, -192.9168]); expect(deadmines.objectiveOrder.at(-1)?.name).toBe("Edwin VanCleef"); expect(deadmines.objectiveOrder.at(-1)?.position) .toEqual([-819.8267, 39.4232, -87.3708]); }); it("keeps Stockades creatures in the package's native world coordinates", () => { const stockades = requireDungeonDefinition("stormwind-stockade"); const firstSpawn = stockades.staticSpawns.find( (spawn) => spawn.id === "creature-79035", ); const targorr = stockades.staticSpawns.find( (spawn) => spawn.id === "creature-84027", ); expect(stockades.environmentMode).toBe("global-wmo"); expect(stockades.provenance.coordinateTransform) .toBe("three(x,y,z)=draft(x,y,z)"); expect(stockades.entrance.footPosition).toEqual([-54.23, -18.34, 0.28]); expect(firstSpawn?.position).toEqual([-82.3245, -26.4396, -10.6106]); expect(targorr?.position).toEqual([-159.582, -25.6062, 1.2531]); }); it("starts Ragefire Chasm in the dungeon instead of the Orgrimmar portal annex", () => { const ragefire = requireDungeonDefinition("ragefire-chasm"); const firstSpawn = ragefire.staticSpawns.find( (spawn) => spawn.id === "creature-38041", ); const jergosh = ragefire.staticSpawns.find( (spawn) => spawn.id === "creature-48740", ); expect(ragefire.environmentMode).toBe("global-wmo"); expect(ragefire.provenance.coordinateTransform) .toBe("three(x,y,z)=draft(x,y,z)"); expect(ragefire.entrance.footPosition).toEqual([-3.81, -17.84, -14.82]); expect(firstSpawn?.position).toEqual([45.0298, -21.2917, -27.7645]); expect(jergosh?.position).toEqual([376.811, -21.801, 209.224]); }); it("keeps every navigation-audited dungeon in its native source coordinates", () => { const sourceAligned = generated.definitions .filter((dungeon) => dungeon.provenance.coordinateAlignment === "source") .map((dungeon) => dungeon.id) .sort(); expect(sourceAligned).toEqual([...SOURCE_NATIVE_DUNGEON_IDS].sort()); for (const dungeonId of SOURCE_NATIVE_DUNGEON_IDS) { const dungeon = requireDungeonDefinition(dungeonId); expect(dungeon.provenance.coordinateAlignment, dungeonId).toBe("source"); expect(dungeon.provenance.coordinateTransform, dungeonId) .not.toContain("package-anchor-translation"); expect( dungeon.validation.warnings.some((warning) => ( warning.includes("native world coordinates") )), dungeonId, ).toBe(true); } }); it("pins representative global-WMO and ADT entrances to their source basis", () => { const expectedEntrances = { "blackfathom-deeps": [151.89, -39.87, 106.96], "blackrock-depths": [-456.929, -68.0896, 34.0923], "blood-furnace": [3.9967, -44.8009, 14.6363], "the-nexus": [-145.87, -16.636, -10.554], "violet-hold": [803.93, 44.364, 1808.82], "black-morass": [7034.7, 32.5619, -1496.24], "culling-of-stratholme": [556.92, 36.69, 1431.1], } as const; for (const [dungeonId, expected] of Object.entries(expectedEntrances)) { expect(requireDungeonDefinition(dungeonId).entrance.footPosition, dungeonId) .toEqual(expected); } }); it("keeps scripted Trial of the Champion objectives on the native arena floor", () => { const trial = requireDungeonDefinition("trial-of-the-champion"); const [entranceX, , entranceZ] = trial.entrance.footPosition; expect(trial.objectiveOrder).toHaveLength(5); for (const objective of trial.objectiveOrder) { expect(objective.position[1], objective.name).toBeGreaterThanOrEqual(411); expect(objective.position[1], objective.name).toBeLessThanOrEqual(413); expect( Math.hypot(objective.position[0] - entranceX, objective.position[2] - entranceZ), objective.name, ).toBeLessThan(150); } }); it("uses client WDT placement records for rotated or translated root WMOs", () => { for (const dungeonId of WDT_PLACEMENT_DUNGEON_IDS) { expect( requireDungeonDefinition(dungeonId).provenance.coordinateAlignment, dungeonId, ).toBe("source-transform"); expect( requireDungeonDefinition(dungeonId).provenance.coordinateTransform, dungeonId, ).toContain("rotate-y"); expect( requireDungeonDefinition(dungeonId).provenance.coordinateTransform, dungeonId, ).not.toContain("package-anchor-translation"); } }); it("pins every map found by the portal-map coordinate audit to its dungeon entrance", () => { const expectedEntrances = { arcatraz: [1.2316, -0.2043, 0.0143], "dire-maul": [44.4499, -2.712, 154.822], "hellfire-ramparts": [5.7204, 129.1393, -94.4661], "razorfen-downs": [-190.2691, -10.71, -100.8219], "razorfen-kraul": [-37.901, -5.684, 22.7908], "scarlet-monastery": [1053.48, 18.6775, 1688.99], scholomance: [-55.0407, -13.1583, 20.4141], "shadowfang-keep": [2109.18, 76.8898, -229.135], stratholme: [-143.4892, -5.8848, -266.8634], zulfarrak: [841.59, 8.93, 1213.52], } as const; for (const [dungeonId, expected] of Object.entries(expectedEntrances)) { expect(requireDungeonDefinition(dungeonId).entrance.footPosition, dungeonId) .toEqual(expected); } }); it("starts Razorfen Kraul inside the portal and maps its first pack into the WMO", () => { const razorfenKraul = requireDungeonDefinition("razorfen-kraul"); const firstPackSpawn = razorfenKraul.staticSpawns.find( (spawn) => spawn.id === "creature-87284", ); expect(razorfenKraul.entrance.footPosition).toEqual([-37.901, -5.684, 22.7908]); expect(firstPackSpawn).toBeTruthy(); expect(firstPackSpawn!.position[0]).toBeGreaterThan(razorfenKraul.entrance.footPosition[0]); expect(firstPackSpawn!.position[0]).toBeLessThan(100); expect(razorfenKraul.provenance.coordinateTransform) .toBe("three(x,y,z)=rotate-y(draft(x,y,z)-[-1952.1803,87.684,1587.8923],-133deg)"); }); });