import { closeSync, openSync, readSync } from "node:fs"; import path from "node:path"; import { MeshStandardMaterial } from "three"; import { describe, expect, it } from "vitest"; import { DUNGEON_DEFINITIONS } from "../game/dungeonRegistry"; import { BITE_SIZED_STOCKADES_DUNGEON_ID } from "../game/dungeonContentSource"; import { prepareDungeonMaterial } from "./productionDungeonMaterials"; interface GltfMaterial { name?: string; alphaMode?: string; alphaCutoff?: number; pbrMetallicRoughness?: { baseColorFactor?: number[] }; } function shippedMaterials(file: string): GltfMaterial[] { const descriptor = openSync(file, "r"); try { const header = Buffer.alloc(20); expect(readSync(descriptor, header, 0, 20, 0)).toBe(20); expect(header.toString("utf8", 0, 4)).toBe("glTF"); const json = Buffer.alloc(header.readUInt32LE(12)); expect(readSync(descriptor, json, 0, json.length, 20)).toBe(json.length); return (JSON.parse(json.toString("utf8")) as { materials: GltfMaterial[] }).materials; } finally { closeSync(descriptor); } } const wowDungeons = DUNGEON_DEFINITIONS.filter((definition) => ( definition.expansion !== "runewaker" && definition.id !== BITE_SIZED_STOCKADES_DUNGEON_ID )); describe("shipped WoW dungeon material policies", () => { it("covers all 54 campaign dungeons", () => { expect(wowDungeons).toHaveLength(54); }); it.each(wowDungeons)("retains authored opacity and effect roles in $title", (definition) => { let opaqueCount = 0; const roles = definition.presentation.materials; const matches = (name: string, patterns: readonly string[], names: readonly string[]) => ( names.some((candidate) => candidate.toLowerCase() === name.toLowerCase()) || patterns.some((pattern) => new RegExp(pattern, "i").test(name)) ); for (const chunk of definition.assets.visual) { const file = chunk.url ? path.join(process.cwd(), "public", chunk.url) : path.join(process.cwd(), "src/assets/game/dungeons", definition.id, chunk.fileName); for (const raw of shippedMaterials(file)) { const name = raw.name ?? ""; const source = new MeshStandardMaterial({ name, transparent: raw.alphaMode === "BLEND", alphaTest: raw.alphaMode === "MASK" ? raw.alphaCutoff ?? 0.5 : 0, opacity: raw.pbrMetallicRoughness?.baseColorFactor?.[3] ?? 1, }); const prepared = prepareDungeonMaterial(source, roles, definition.environmentMode); const cutout = matches(name, roles.cutoutPatterns, roles.cutoutNames); const blended = !cutout && (matches(name, roles.blendPatterns, roles.blendNames) || matches(name, roles.additivePatterns, roles.additiveNames)); expect(prepared.transparent, name).toBe(blended); expect(prepared.depthWrite, name).toBe(!blended); if (!cutout && !blended) { opaqueCount += 1; expect(prepared.opacity, name).toBe(1); expect(prepared.alphaTest, name).toBe(0); } prepared.dispose(); source.dispose(); } } expect(opaqueCount).toBeGreaterThan(0); }); });