import test from "node:test"; import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { Document, NodeIO } from "@gltf-transform/core"; import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; import { MeshoptDecoder } from "meshoptimizer"; import { applyRunewakerDerivedSupportStyling, listRunewakerDerivedSupportStylingPolicies } from "./rom-derived-support-styling.mjs"; import { applyRunewakerDerivedSupportStylingToFile } from "./rom-derived-support-styling-file.mjs"; const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); const mysticVisual = path.join(repositoryRoot, "runewaker-export-work/mystic-altar/converted/mystic-altar-visual.glb"); const mysticReport = path.join(repositoryRoot, "runewaker-export-work/mystic-altar/prepared/export-report.json"); const mysticMetadata = path.join(repositoryRoot, "src/assets/game/dungeons/mystic-altar/import-metadata.json"); const mysticDds = path.join(repositoryRoot, "runewaker-export-work/mystic-altar/raw/textures/caa8475c_mystic_altar_floor.dds"); const mysticRos = path.resolve(repositoryRoot, "../rom-pvt-server-backups/Runewaker/Resource/model/dungeon/mystic_altar/act_mystic_altar_instance.ros"); const sourceSha256 = (bytes) => createHash("sha256").update(bytes).digest("hex"); function accessorSnapshot(document) { return document.getRoot().listAccessors().map((accessor) => { const array = accessor.getArray(); return JSON.stringify({ name: accessor.getName(), type: accessor.getType(), count: accessor.getCount(), hash: array ? sourceSha256(Buffer.from(array.buffer, array.byteOffset, array.byteLength)) : null, }); }).sort(); } function imageSnapshot(document) { return document.getRoot().listTextures().map((texture) => JSON.stringify({ name: texture.getName(), hash: sourceSha256(Buffer.from(texture.getImage() ?? [])), })).sort(); } test("unlisted packages do not read or mutate a converted source", async () => { assert.deepEqual(await applyRunewakerDerivedSupportStylingToFile("missing-source.glb", {}), { status: "not-requested", geometryChanged: false, }); }); test("the base Demon support policy refuses the level125 variant before mutation", () => { const document = new Document(); assert.throws(() => applyRunewakerDerivedSupportStyling(document, { dungeonId: "demon-stronghold-125", policyId: listRunewakerDerivedSupportStylingPolicies()[0].id, }), /cannot run/); assert.equal(document.getRoot().listMaterials().length, 0); }); test("the reviewed floor cannot be substituted with mismatched source DDS content", () => { const policy = listRunewakerDerivedSupportStylingPolicies()[0]; const document = new Document(); assert.throws(() => applyRunewakerDerivedSupportStyling(document, { dungeonId: policy.dungeonId, policyId: policy.id, sourceReport: { sourceModel: policy.sourceModel, sourceResourceSha256: policy.sourceModelSha256, materials: [{ name: policy.sourceMaterial, sourceTexture: policy.sourceTexture, stagedTexture: policy.stagedTexture }] }, sourceDdsBytes: Buffer.from("unreviewed floor"), }), /source DDS hash/); assert.equal(document.getRoot().listMaterials().length, 0); }); test("Mystic derived pads use the source floor UV mapping without changing native accessors or images", { skip: ![mysticVisual, mysticReport, mysticMetadata, mysticDds, mysticRos].every(existsSync), }, async () => { const io = new NodeIO().registerExtensions(ALL_EXTENSIONS).registerDependencies({ "meshopt.decoder": MeshoptDecoder }); await MeshoptDecoder.ready; const document = await io.read(mysticVisual); const policy = listRunewakerDerivedSupportStylingPolicies().find(({ dungeonId }) => dungeonId === "mystic-altar"); assert.ok(policy); const sourceReport = JSON.parse(await readFile(mysticReport, "utf8")); const metadata = JSON.parse(await readFile(mysticMetadata, "utf8")); sourceReport.sourceResourceSha256 = metadata.source.modelSha256; const sourceDdsBytes = await readFile(mysticDds); assert.equal(sourceSha256(await readFile(mysticRos)), policy.sourceModelSha256); const originalAccessors = accessorSnapshot(document); const originalImages = imageSnapshot(document); const floorMaterial = document.getRoot().listMaterials().find((material) => material.getName() === policy.sourceMaterial); const floorUv = floorMaterial?.getBaseColorTextureInfo()?.getTexCoord(); const floorImageHash = sourceSha256(Buffer.from(floorMaterial?.getBaseColorTexture()?.getImage() ?? [])); const result = applyRunewakerDerivedSupportStyling(document, { dungeonId: "mystic-altar", policyId: policy.id, sourceReport, sourceDdsBytes, }); assert.equal(result.status, "applied"); assert.equal(result.padCount, 250); assert.equal(result.triangleCount, 3000); assert.deepEqual(result.sourceFloorReference.anchorWorld, policy.anchorWorld); assert.deepEqual(result.sourceFloorReference.anchorUv, policy.anchorUv); assert.equal(result.sourceFloorReference.densityPixelsPerMeter, 22.176030799610068); assert.ok(Object.values(result.sourceFloorReference.fullRankFaceDeterminants).every((value) => Math.abs(value) > 1e-8)); const support = document.getRoot().listNodes().find((node) => node.getName() === policy.supportNode); const supportPrimitive = support?.getMesh()?.listPrimitives()[0]; assert.ok(supportPrimitive?.getAttribute("TEXCOORD_1")); assert.equal(supportPrimitive.getAttribute("TEXCOORD_0"), null); assert.equal(supportPrimitive.getExtras().romDerivedSupportStyling.sourcePositionHash, result.policy.sourcePositionHash); assert.equal(supportPrimitive.getExtras().romDerivedSupportStyling.sourceIndexHash, result.policy.sourceIndexHash); const styledAccessors = accessorSnapshot(document); for (const original of originalAccessors) assert.ok(styledAccessors.includes(original), `missing original accessor ${original}`); assert.deepEqual(imageSnapshot(document), originalImages); const unchangedFloorMaterial = document.getRoot().listMaterials().find((material) => material.getName() === policy.sourceMaterial); assert.equal(unchangedFloorMaterial?.getBaseColorTextureInfo()?.getTexCoord(), floorUv); assert.equal(sourceSha256(Buffer.from(unchangedFloorMaterial?.getBaseColorTexture()?.getImage() ?? [])), floorImageHash); });