Files
healer-man/src/scene/dungeonMaterialAudit.test.ts
T
2026-09-20 13:40:23 -04:00

76 lines
3.1 KiB
TypeScript

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);
});
});