Files
healer-man/scripts/combat-effects/package-rom-particles.test.mjs
T
2026-09-25 18:46:09 -04:00

100 lines
5.0 KiB
JavaScript

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, 170);
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(Boolean(graph.partialRendererReady), result.partialRendererReady);
if (result.rendererReady) {
assert.equal(result.partialRendererReady, false);
assert.equal(result.errors.length, 0);
assert.ok(result.spriteControllers > 0);
assert.ok(result.unsupported.every((type) => type === "CRuController_EventTrigger"));
}
if (verifiedRuneWakerParticleModels.has(result.model) && !result.partialRendererReady) {
assert.equal(result.rendererReady, true, result.model);
}
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, 668);
assert.ok(textureReferences >= 139);
});
test("Rising Tide ships three original renderable phases and Urgent Heal declares its partial target layers", () => {
for (const model of [
"model/fx/skill/ice/act_icecone01_c_hand.ros",
"model/fx/skill/ice/act_icecone01_f.ros",
"model/fx/skill/ice/act_riptide_t_freeze_hit.ros",
]) {
const result = index.results.find((entry) => entry.model === model);
assert.equal(result?.rendererReady, true, model);
assert.equal(result?.partialRendererReady, false, model);
}
const urgent = index.results.find((entry) =>
entry.model === "model/fx/skill/holy/act_benedictory_heal_t_down.ros");
assert.equal(urgent?.rendererReady, false);
assert.equal(urgent?.partialRendererReady, true);
assert.equal(urgent?.spriteControllers, 2);
const graph = JSON.parse(fs.readFileSync(assetFile(urgent.graphUrl), "utf8"));
assert.equal(graph.visualFidelity, "partial-original");
assert.deepEqual(graph.omittedVisuals, ["CRuEntity_Decal:terrain-projection"]);
assert.equal(graph.runeMesh.sourceModel, "model/fx/skill/holy/_polygon/rune_cylinder.ros");
assert.equal(graph.runeMesh.positions.length, 72 * 9);
assert.equal(graph.runeMesh.uvs.length, 72 * 6);
assert.ok(graph.textures.some((texture) => texture.url === graph.runeMesh.textureAssetUrl));
const impactControllers = graph.motions.find((motion) => motion.name === "explode").controllers;
assert.ok(impactControllers.some((controller) => controller.type === "entity-visualizer"));
const decal = impactControllers.find((controller) => controller.type === "decal");
assert.ok(decal);
assert.ok(graph.textures.some((texture) => texture.url === decal.textureAssetUrl));
assert.ok(impactControllers.some((controller) => controller.type === "global-color"));
assert.ok(impactControllers.some((controller) => controller.type === "global-alpha"));
});