import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { verifiedRuneWakerParticleModels } from "./rom-particle-readiness.mjs"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const publicRoot = path.join(root, "public"); const index = JSON.parse(fs.readFileSync(path.join(publicRoot, "assets/game/combat-effects/original/runewaker/manifest.json"), "utf8")); function assetFile(url) { assert.match(url, /^\/assets\/game\/combat-effects\/original\/runewaker\/[\w/.-]+\.(?:json|png|hlsl)$/); const file = path.resolve(publicRoot, url.slice(1)); assert.ok(file.startsWith(`${publicRoot}${path.sep}`)); return file; } test("primary-five RuneWaker particle archive resolves its decoded graphs and textures", () => { assert.equal(index.requestedModels, 123); assert.equal(index.processedModels, index.requestedModels); assert.equal(index.failedModels, 0); assert.equal(index.results.length, index.requestedModels); let spriteControllers = 0; let textureReferences = 0; for (const result of index.results) { assert.ok(result.graphUrl, result.model); const graph = JSON.parse(fs.readFileSync(assetFile(result.graphUrl), "utf8")); assert.equal(graph.kind, "runewaker-ros-particle-archive"); assert.equal(graph.sourceModel, result.model); assert.equal(graph.rendererReady, result.rendererReady); assert.equal(result.rendererReady, verifiedRuneWakerParticleModels.has(result.model) && result.errors.length === 0 && result.unsupported.every((type) => type === "CRuController_EventTrigger")); assert.equal(graph.shader.name, "ruSHADER_PARTICLE_SPRITEEX"); for (const [stage, signature] of [["vertex", "VS_OUTPUT main"], ["pixel", "PS_OUTPUT main"]]) { const shader = fs.readFileSync(assetFile(graph.shader[`${stage}SourceUrl`]), "utf8"); assert.ok(shader.includes(signature)); assert.ok(shader.endsWith("\n")); assert.ok(!shader.trimEnd().endsWith("\\n\\")); } assert.equal(graph.textures.length, result.textures); spriteControllers += result.spriteControllers; for (const texture of graph.textures) { const png = fs.readFileSync(assetFile(texture.url)); assert.equal(png.subarray(0, 8).toString("hex"), "89504e470d0a1a0a"); textureReferences++; } for (const motion of graph.motions) for (const controller of motion.controllers) { if (controller.type === "sprite" && controller.textureAssetUrl) { assert.ok(graph.textures.some((texture) => texture.url === controller.textureAssetUrl)); assert.equal(controller.shader, "ruSHADER_PARTICLE_SPRITEEX"); assert.ok(Array.isArray(controller.tileSizePixels)); } if (controller.type === "emitter") assert.equal(controller.tracks.length, 27); } } assert.equal(spriteControllers, 510); assert.ok(textureReferences >= 103); });