import test from "node:test"; import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { assertMatchingRootDigests, auditWmoAsset, documentedFixColorVertexAlpha, mapExactObjMocv, parseGroupWmo, parseRootWmo, sourceToObjPoint, } from "./wow-native-wmo-audit.mjs"; function reversedChunk(id, payload) { const header = Buffer.alloc(8); header.write(id.split("").reverse().join(""), 0, "ascii"); header.writeUInt32LE(payload.length, 4); return Buffer.concat([header, payload]); } function floats(values) { const result = Buffer.alloc(values.length * 4); values.forEach((value, index) => result.writeFloatLE(value, index * 4)); return result; } function makeLightRow() { const row = Buffer.alloc(48); row.set([0, 1, 1, 1, 230, 160, 90, 255], 0); [2, 3, 4].forEach((value, index) => row.writeFloatLE(value, 8 + index * 4)); row.writeFloatLE(0.65, 20); row.writeFloatLE(0.5, 24); row.writeFloatLE(8, 28); return row; } function makeRoot({ flags = 0, lightPayload = makeLightRow(), ambient = [20, 30, 40, 255] } = {}) { const mver = Buffer.alloc(4); mver.writeUInt32LE(17); const mohd = Buffer.alloc(64); mohd.writeUInt32LE(1, 4); // one group mohd.writeUInt32LE(lightPayload.length / 48, 12); mohd.set(ambient, 28); mohd.writeUInt32LE(723, 32); floats([-10, -20, -30, 10, 20, 30]).copy(mohd, 36); mohd.writeUInt16LE(flags, 60); const mogi = Buffer.alloc(32); mogi.writeUInt32LE(0x2000, 0); floats([-1, -1, -1, 1, 1, 1]).copy(mogi, 4); mogi.writeInt32LE(1, 28); return Buffer.concat([ reversedChunk("MVER", mver), reversedChunk("MOHD", mohd), reversedChunk("MOGN", Buffer.from("\0JailRoom\0Interior\0", "latin1")), reversedChunk("MOGI", mogi), reversedChunk("MOLT", lightPayload), ]); } function makeGroup({ flags = 0x2204, colors = [[100, 90, 80, 0], [110, 100, 90, 0], [120, 110, 100, 0]], refs = [0], batchLastVertex = 0, } = {}) { const positions = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const normals = [[0, 1, 0], [0, 0, 1], [1, 0, 0]]; const uvs = [[0.25, 0.75], [0.5, 0.25], [0.125, 0.875]]; const header = Buffer.alloc(68); header.writeUInt32LE(1, 0); header.writeUInt32LE(10, 4); header.writeUInt32LE(flags, 8); floats([-10, -20, -30, 10, 20, 30]).copy(header, 12); header.writeUInt16LE(1, 40); // batch A header.writeUInt16LE(1, 42); // interior batch header.writeUInt16LE(0, 44); // exterior batch const nested = [ reversedChunk("MOVT", floats(positions.flat())), reversedChunk("MONR", floats(normals.flat())), reversedChunk("MOTV", floats(uvs.flat())), reversedChunk("MOLR", Buffer.from(refs.flatMap((value) => { const row = Buffer.alloc(2); row.writeInt16LE(value); return [...row]; }))), reversedChunk("MOCV", Buffer.from(colors.flat())), reversedChunk("MOBA", (() => { const batch = Buffer.alloc(24); batch.writeUInt16LE(batchLastVertex, 20); return batch; })()), ]; const version = Buffer.alloc(4); version.writeUInt32LE(17); return { bytes: Buffer.concat([reversedChunk("MVER", version), reversedChunk("MOGP", Buffer.concat([header, ...nested]))]), positions, normals, uvs, colors, }; } function makeObj({ positions, normals, uvs }) { const lines = []; for (const [x, y, z] of positions) lines.push(`v ${x} ${z} ${-y}`); for (const [x, y, z] of normals) lines.push(`vn ${x} ${z} ${-y}`); for (const [u, v] of uvs) lines.push(`vt ${u} ${1 - v}`); return `${lines.join("\n")}\n`; } function digest(bytes) { return createHash("sha256").update(bytes).digest("hex"); } test("WMO v17 parsers decode MOLT rows, MOGP flags, batches, MOLR, and raw MOCV bytes", () => { const rootBytes = makeRoot(); const group = makeGroup(); const root = parseRootWmo(rootBytes); const parsedGroup = parseGroupWmo(group.bytes, 0); assert.equal(root.header.groupCount, 1); assert.equal(root.header.lightCount, 1); assert.deepEqual(root.header.ambientColorBytes, [20, 30, 40, 255]); assert.deepEqual(root.lights[0], { index: 0, lightType: 0, secondaryType: 1, useAttenuation: 1, padding: 1, colorBytes: [230, 160, 90, 255], position: [2, 3, 4], intensity: Math.fround(0.65), attenuationStart: 0.5, attenuationEnd: 8, unknownFloats: [0, 0, 0, 0], }); assert.equal(parsedGroup.header.flags, 0x2204); assert.deepEqual(parsedGroup.header.batchCounts, { unknownA: 1, interior: 1, exterior: 0, unknownD: 0 }); assert.equal(parsedGroup.hasLightFlag, true); assert.equal(parsedGroup.indoorFlag, true); assert.deepEqual(parsedGroup.lightRefs, [0]); assert.deepEqual(parsedGroup.vertices[0].mocvBytes, [100, 90, 80, 0]); }); test("source coordinate basis is exact WMO (x,y,z) to OBJ (x,z,-y)", () => { assert.deepEqual(sourceToObjPoint([2, 3, 4]), [2, 4, -3]); }); test("exact MOCV mapping uses full position, normal, and UV signatures and preserves raw BGRA", () => { const group = makeGroup(); const obj = makeObj(group); const mapping = mapExactObjMocv(parseRootWmo(makeRoot()), [parseGroupWmo(group.bytes)], obj); assert.equal(mapping.exactCompleteMapping, true); assert.equal(mapping.mappedVertexCount, 3); assert.equal(mapping.ambiguousVertexCount, 0); assert.equal(mapping.unmatchedVertexCount, 0); assert.equal(mapping.rawMocvByteOrder, "rawColorBytesByObjVertex preserves BGRA storage bytes exactly; alpha semantics are deliberately not applied"); assert.deepEqual([...mapping.rawColorBytesByObjVertex], group.colors.flat()); assert.equal(mapping.documentedFixColorAlphaMapping.exactCompleteMapping, true); assert.deepEqual([...mapping.documentedFixColorAlphaMapping.colorsByObjVertex].slice(3, 4), [0]); assert.deepEqual(mapping.rawAlphaHistogram, { 0: 3 }); }); test("documented MOCV fixup treats alpha as lighting data and rewrites indoor/outdoor alpha, never opacity", () => { const root = parseRootWmo(makeRoot()); const colors = [[110, 90, 70, 128], [100, 80, 60, 64], [40, 50, 60, 0]]; const indoor = parseGroupWmo(makeGroup({ flags: 0x2204, colors }).bytes); const outdoor = parseGroupWmo(makeGroup({ flags: 0x220c, colors }).bytes); const indoorResult = documentedFixColorVertexAlpha(root, indoor); const outdoorResult = documentedFixColorVertexAlpha(root, outdoor); assert.equal(indoorResult.secondBatchVertexStart, 1); assert.deepEqual([...indoorResult.rawColorBytes], colors.flat()); assert.deepEqual([...indoorResult.adaptedColorBytes], [22, 14, 7, 128, 90, 65, 40, 0, 10, 10, 10, 0]); assert.deepEqual([...outdoorResult.adaptedColorBytes], [22, 14, 7, 128, 90, 65, 40, 255, 10, 10, 10, 255]); assert.match(indoorResult.alphaSemantics, /not opacity/u); assert.equal(indoorResult.productionStatus.includes("diagnostic-only"), true); }); test("documented MOHD 0x0008 path is marked disputed and does not claim production colors", () => { const root = parseRootWmo(makeRoot({ flags: 0x8 })); const group = parseGroupWmo(makeGroup({ flags: 0x2204, colors: [[100, 80, 60, 64], [50, 40, 30, 32], [20, 10, 5, 7]] }).bytes); const result = documentedFixColorVertexAlpha(root, group); assert.equal(result.fixupPath, "documented-special-mohd-0x0008-disputed"); assert.deepEqual([...result.adaptedColorBytes], [100, 80, 60, 64, 50, 40, 30, 0, 20, 10, 5, 0]); assert.match(result.productionStatus, /diagnostic-only/u); }); test("exact mapping fails closed on changed geometry instead of nearest-vertex guessing", () => { const group = makeGroup(); const obj = makeObj(group).replace("v 1 3 -2", "v 1.0001 3 -2"); const mapping = mapExactObjMocv(parseRootWmo(makeRoot()), [parseGroupWmo(group.bytes)], obj); assert.equal(mapping.exactCompleteMapping, false); assert.equal(mapping.unmatchedVertexCount, 1); }); test("group data is rejected unless it is paired with the byte-identical expected root", () => { const rootBytes = makeRoot(); const expected = digest(rootBytes); assert.deepEqual(assertMatchingRootDigests({ rootBytes, groupCacheRootBytes: Buffer.from(rootBytes), expectedRootSha256: expected, }), { rootSha256: expected, groupCacheRootSha256: expected }); const differentRoot = Buffer.from(rootBytes); differentRoot[differentRoot.length - 1] ^= 1; assert.throws(() => assertMatchingRootDigests({ rootBytes, groupCacheRootBytes: differentRoot, expectedRootSha256: expected, }), /not byte-identical/u); assert.throws(() => assertMatchingRootDigests({ rootBytes, groupCacheRootBytes: rootBytes, expectedRootSha256: "", }), /expected 64-character/u); }); test("MOLT references are eligible only with HasLight and without DoNotUseLocalLighting", () => { const rootBytes = makeRoot(); const group = makeGroup({ flags: 0x2244 }); const result = auditWmoAsset({ rootBytes, groupCacheRootBytes: rootBytes, expectedRootSha256: digest(rootBytes), groupBuffers: [group.bytes], objText: makeObj(group), }); assert.equal(result.lighting.totalMoltRows, 1); assert.equal(result.lighting.groupCountWithHasLightAndMolr, 0); assert.equal(result.lighting.eligibleMolrReferenceCount, 0); assert.deepEqual(result.lighting.eligibleUniqueMoltRows, []); assert.match(result.lighting.runtimeLightClaim, /no Three\.js dynamic lights/u); assert.match(result.sourceGeometry.mocv.alphaSemantics, /not glTF opacity/u); }); test("malformed MOLT rows are rejected", () => { assert.throws(() => parseRootWmo(makeRoot({ lightPayload: Buffer.alloc(47) })), /not divisible/u); });