import assert from "node:assert/strict"; import test from "node:test"; import { decodeWotlkM2Effects, parseDbc, parseRunewakerImageRecords } from "./generate-manifest.mjs"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; function tinyDbc() { const strings = Buffer.from("\0hello\0", "utf8"); const buffer = Buffer.alloc(20 + 8 + strings.length); buffer.write("WDBC", 0, "ascii"); buffer.writeUInt32LE(1, 4); buffer.writeUInt32LE(2, 8); buffer.writeUInt32LE(8, 12); buffer.writeUInt32LE(strings.length, 16); buffer.writeUInt32LE(42, 20); buffer.writeUInt32LE(1, 24); strings.copy(buffer, 28); return buffer; } test("parses numeric and string DBC fields", () => { const table = parseDbc(tinyDbc(), "fixture"); assert.equal(table.row(0).uint(0), 42); assert.equal(table.row(0).string(1), "hello"); }); test("classifies paths from fixed RuneWaker ImageObject records", () => { const buffer = Buffer.alloc(140 + 3712); buffer.writeInt32LE(1, 132); buffer.writeInt32LE(573727, 140); Buffer.from("model\\fx\\skill\\fire\\act_fireball01_f_i.ros\0", "ascii").copy(buffer, 140 + 300); const records = parseRunewakerImageRecords(buffer, [573727]); assert.deepEqual(records.get(573727), ["model/fx/skill/fire/act_fireball01_f_i.ros"]); }); test("decodes supported WotLK M2 particle and ribbon records", () => { const particleOffset = 304; const buffer = Buffer.alloc(particleOffset + 476); buffer.write("MD20", 0, "ascii"); buffer.writeUInt32LE(1, 296); buffer.writeUInt32LE(particleOffset, 300); buffer.writeUInt32LE(17, particleOffset); buffer.writeUInt16LE(4, particleOffset + 20); buffer.writeUInt16LE(9, particleOffset + 22); buffer.writeUInt8(2, particleOffset + 40); buffer.writeUInt8(1, particleOffset + 41); buffer.writeUInt16LE(4, particleOffset + 48); buffer.writeUInt16LE(8, particleOffset + 50); buffer.writeUInt16LE(1, particleOffset + 56); buffer.writeUInt32LE(3, particleOffset + 56 + 12); const decoded = decodeWotlkM2Effects(buffer, "fixture"); assert.equal(decoded.particles[0].id, 17); assert.equal(decoded.particles[0].textureIndex, 9); assert.equal(decoded.particles[0].blendMode, 2); assert.equal(decoded.particles[0].textureColumns, 8); assert.equal(decoded.particles[0].tracks.emissionSpeed.interpolation, 1); assert.equal(decoded.particles[0].tracks.emissionSpeed.keyCount, 3); }); test("generated coverage packages every referenced source sound", () => { const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); const manifest = JSON.parse(fs.readFileSync(path.join(root, "public", "assets", "game", "combat-effects", "manifest.json"), "utf8")); assert.equal(manifest.coverage.requested, manifest.coverage.authentic + manifest.coverage.fallback); assert.deepEqual(manifest.coverage.audio.missing, []); const sounds = manifest.entries.flatMap((entry) => entry.sounds ?? []); assert.ok(sounds.length > 0); for (const sound of sounds) { assert.ok(sound.url?.startsWith("/assets/game/combat-effects/audio/")); assert.ok(fs.existsSync(path.join(root, "public", sound.url))); } });