182 lines
9.6 KiB
TypeScript
182 lines
9.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { DUNGEON_DEFINITIONS, prepareDungeonSessionDefinition, requireDungeonDefinition } from "./dungeonRegistry";
|
|
import { FORSAKEN_ABBEY_DEFINITION } from "./dungeons/forsakenAbbey";
|
|
import { WOW_DUNGEON_LIGHTING, withWowDungeonEnvironment, wowDungeonLightingProfile } from "./wowDungeonEnvironment";
|
|
import { materializeBiteSizedLayout } from "./biteSizedLayoutRuntime";
|
|
import stairs from "./generated/wowDungeonStairs.generated.json";
|
|
import stockadesOverride from "../../dungeon-pipeline/runtime-overrides/stormwind-stockade.json";
|
|
import { BITE_SIZED_DEFAULT_CONFIG, selectBiteSizedRun } from "../../shared/bite-sized-layouts.mjs";
|
|
import { DUNGEON_REGISTRY } from "./dungeonRegistry";
|
|
import { dungeonContentSourceId, isBiteSizedDungeonId } from "./dungeonContentSource";
|
|
|
|
describe("fleet-wide WoW dungeon environment policy", () => {
|
|
const wowDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => (
|
|
dungeon.expansion !== "runewaker" && !isBiteSizedDungeonId(dungeon.id)
|
|
));
|
|
const wowBiteSizedDungeons = DUNGEON_DEFINITIONS.filter((dungeon) => (
|
|
dungeon.expansion !== "runewaker" && isBiteSizedDungeonId(dungeon.id)
|
|
));
|
|
|
|
it("gives every WoW dungeon readable ambient and hemisphere fill without changing fog draw distance or material roles", () => {
|
|
expect(wowDungeons).toHaveLength(54);
|
|
for (const dungeon of wowDungeons) {
|
|
const profile = WOW_DUNGEON_LIGHTING[wowDungeonLightingProfile(dungeon.id)];
|
|
expect(dungeon.presentation.ambientLight).toEqual(profile.ambientLight);
|
|
expect(dungeon.presentation.hemisphereLight).toEqual(profile.hemisphereLight);
|
|
expect(dungeon.presentation.directionalLight).toEqual(profile.directionalLight);
|
|
expect(dungeon.presentation.background).toBe(profile.background);
|
|
expect(dungeon.presentation.fog.color).toBe(profile.fog.color);
|
|
expect(dungeon.presentation.fog.near).toBe(profile.fog.near);
|
|
expect(dungeon.presentation.fog.far).toBeGreaterThan(dungeon.presentation.fog.near);
|
|
const updated = withWowDungeonEnvironment(dungeon);
|
|
expect(updated.presentation.materials).toBe(dungeon.presentation.materials);
|
|
expect(updated.assets).toBe(dungeon.assets);
|
|
expect(updated.validation).toBe(dungeon.validation);
|
|
}
|
|
});
|
|
|
|
it("preserves adapted source lighting on every WoW Bite-Sized catalog and prepared definition", () => {
|
|
expect(wowBiteSizedDungeons.map((dungeon) => dungeon.id).sort()).toEqual([
|
|
"bite-sized-deadmines-ship",
|
|
"bite-sized-stormwind-stockade",
|
|
"bite-sized-utgarde-pinnacle-skadi",
|
|
"bite-sized-wailing-caverns",
|
|
"randomized-bite-sized-stormwind-stockade",
|
|
"randomized-bite-sized-stormwind-stockade-available",
|
|
]);
|
|
|
|
const presentationLighting = (definition: typeof wowBiteSizedDungeons[number]) => ({
|
|
background: definition.presentation.background,
|
|
fog: {
|
|
color: definition.presentation.fog.color,
|
|
near: definition.presentation.fog.near,
|
|
far: definition.presentation.fog.far,
|
|
},
|
|
ambientLight: definition.presentation.ambientLight,
|
|
hemisphereLight: definition.presentation.hemisphereLight,
|
|
directionalLight: definition.presentation.directionalLight,
|
|
});
|
|
|
|
for (const catalogDefinition of wowBiteSizedDungeons) {
|
|
const source = requireDungeonDefinition(dungeonContentSourceId(catalogDefinition.id));
|
|
const expectedLighting = presentationLighting(source);
|
|
expect(presentationLighting(catalogDefinition), catalogDefinition.id).toEqual(expectedLighting);
|
|
|
|
const prepared = prepareDungeonSessionDefinition(catalogDefinition.id, `lighting-${catalogDefinition.id}`);
|
|
expect(prepared, catalogDefinition.id).not.toBeNull();
|
|
const preparedSource = requireDungeonDefinition(
|
|
prepared!.contentSourceDungeonId ?? dungeonContentSourceId(catalogDefinition.id),
|
|
);
|
|
expect(prepared!.expansion, `${catalogDefinition.id} prepared expansion`).toBe(preparedSource.expansion);
|
|
const sourceSlice = wowBiteSizedDungeons.find((definition) => (
|
|
!definition.id.startsWith("randomized-")
|
|
&& dungeonContentSourceId(definition.id) === preparedSource.id
|
|
)) ?? DUNGEON_DEFINITIONS.find((definition) => (
|
|
definition.expansion === preparedSource.expansion
|
|
&& isBiteSizedDungeonId(definition.id)
|
|
&& !definition.id.startsWith("randomized-")
|
|
&& dungeonContentSourceId(definition.id) === preparedSource.id
|
|
));
|
|
expect(sourceSlice, `${catalogDefinition.id} source slice ${preparedSource.id}`).toBeDefined();
|
|
expect(presentationLighting(prepared!), `${catalogDefinition.id} prepared from ${preparedSource.id}`)
|
|
.toEqual(presentationLighting(sourceSlice!));
|
|
if (preparedSource.id === dungeonContentSourceId(catalogDefinition.id)) {
|
|
expect(presentationLighting(prepared!), `${catalogDefinition.id} prepared`).toEqual(expectedLighting);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("uses the selected source lighting and point lights when a custom layout baseline belongs elsewhere", () => {
|
|
const source = requireDungeonDefinition("wailing-caverns");
|
|
const pointLights = [{ position: [3, 4, 5] as const, color: "#80d8ff", intensity: 2, distance: 12 }];
|
|
const customSource = {
|
|
...source,
|
|
presentation: { ...source.presentation, pointLights },
|
|
};
|
|
const baseline = requireDungeonDefinition("bite-sized-paspers-shrine-hafiz");
|
|
const selection = selectBiteSizedRun(
|
|
BITE_SIZED_DEFAULT_CONFIG,
|
|
"bite-sized-wailing-caverns",
|
|
"cross-source-environment-test",
|
|
);
|
|
const materialized = materializeBiteSizedLayout(
|
|
customSource,
|
|
baseline,
|
|
selection,
|
|
DUNGEON_REGISTRY,
|
|
);
|
|
|
|
expect(materialized.presentation.background).toBe(source.presentation.background);
|
|
expect(materialized.presentation.fog).toEqual(source.presentation.fog);
|
|
expect(materialized.presentation.ambientLight).toEqual(source.presentation.ambientLight);
|
|
expect(materialized.presentation.hemisphereLight).toEqual(source.presentation.hemisphereLight);
|
|
expect(materialized.presentation.directionalLight).toEqual(source.presentation.directionalLight);
|
|
expect(materialized.presentation.pointLights).toEqual(pointLights);
|
|
expect(materialized.presentation.theme).toBe("Bite-Sized Dungeon");
|
|
});
|
|
|
|
it("keeps tuned lighting from a custom baseline whose explicit source matches", () => {
|
|
const source = requireDungeonDefinition("wailing-caverns");
|
|
const selection = selectBiteSizedRun(
|
|
BITE_SIZED_DEFAULT_CONFIG,
|
|
"bite-sized-wailing-caverns",
|
|
"explicit-baseline-source-test",
|
|
);
|
|
const tunedPresentation = {
|
|
...source.presentation,
|
|
background: "#101820",
|
|
ambientLight: { color: "#c1d2c2", intensity: 0.91 },
|
|
hemisphereLight: { skyColor: "#abc4b5", groundColor: "#1f2a23", intensity: 1.23 },
|
|
directionalLight: { ...source.presentation.directionalLight, color: "#d9ecc8", intensity: 1.34 },
|
|
pointLights: [{ position: [6, 7, 8] as const, color: "#b8f1ff", intensity: 1.8, distance: 14 }],
|
|
};
|
|
const customBaseline = {
|
|
...requireDungeonDefinition("bite-sized-paspers-shrine-hafiz"),
|
|
id: "custom-lighting-baseline",
|
|
contentSourceDungeonId: source.id,
|
|
presentation: tunedPresentation,
|
|
};
|
|
const materialized = materializeBiteSizedLayout(
|
|
source,
|
|
customBaseline,
|
|
selection,
|
|
DUNGEON_REGISTRY,
|
|
);
|
|
|
|
expect(materialized.presentation.background).toBe(tunedPresentation.background);
|
|
expect(materialized.presentation.ambientLight).toEqual(tunedPresentation.ambientLight);
|
|
expect(materialized.presentation.hemisphereLight).toEqual(tunedPresentation.hemisphereLight);
|
|
expect(materialized.presentation.directionalLight).toEqual(tunedPresentation.directionalLight);
|
|
expect(materialized.presentation.pointLights).toEqual(tunedPresentation.pointLights);
|
|
expect(materialized.presentation.location).toContain(selection.spot.name);
|
|
});
|
|
|
|
it("keeps Stockades' reviewed lighting, ramps and navigation links intact", () => {
|
|
const stockades = requireDungeonDefinition("stormwind-stockade");
|
|
// The existing campaign compiler stores package vectors at four decimals.
|
|
const compiled = (value: unknown) => JSON.parse(JSON.stringify(value, (_key, item) => typeof item === "number" ? Number(item.toFixed(4)) : item));
|
|
expect(stockades.traversalRamps).toEqual(compiled(stockadesOverride.traversalRamps));
|
|
expect(stockades.navigationLinks).toEqual(compiled(stockadesOverride.navigationLinks));
|
|
expect(stockades.presentation.directionalLight).toEqual(stockadesOverride.presentation.directionalLight);
|
|
});
|
|
|
|
it("does not apply WoW changes to RuneWaker environments", () => {
|
|
expect(withWowDungeonEnvironment(FORSAKEN_ABBEY_DEFINITION)).toBe(FORSAKEN_ABBEY_DEFINITION);
|
|
});
|
|
|
|
it("records a scan for every shipped WoW dungeon and installs only package-matched proxies", () => {
|
|
expect(Object.keys(stairs.dungeons).sort()).toEqual(wowDungeons.map((dungeon) => dungeon.id).sort());
|
|
for (const dungeon of wowDungeons) {
|
|
const record = (stairs.dungeons as Record<string, { ramps: unknown[] }>)[dungeon.id];
|
|
if (record.ramps.length) expect(dungeon.traversalRamps).toEqual(record.ramps);
|
|
}
|
|
});
|
|
|
|
it("rejects stale ramp coordinates when collision content is replaced", () => {
|
|
const dungeon = wowDungeons.find((entry) => entry.id !== "stormwind-stockade" && entry.traversalRamps?.length)!;
|
|
expect(dungeon).toBeDefined();
|
|
const replaced = { ...dungeon, traversalRamps: undefined, assets: { ...dungeon.assets, collision: dungeon.assets.collision.map((asset) => ({ ...asset, checksum: "replacement" })) } };
|
|
expect(withWowDungeonEnvironment(replaced).traversalRamps).toBeUndefined();
|
|
});
|
|
});
|