Files
healer-man/scripts/runewaker-pipeline/sand-vortex/extract-native-terrain.test.mjs
T
2026-10-06 22:25:34 -04:00

189 lines
6.9 KiB
JavaScript

import assert from "node:assert/strict";
import { deflateSync } from "node:zlib";
import test from "node:test";
import {
dxt1ToRgba,
encodeChunkKey,
intersectingChunkIndices,
parseTerrainChunk,
readDataStore,
wrapDxt1AsDds,
} from "./extract-native-terrain.mjs";
function i32(value) {
const buffer = Buffer.alloc(4);
buffer.writeInt32LE(value);
return buffer;
}
function u32(value) {
const buffer = Buffer.alloc(4);
buffer.writeUInt32LE(value);
return buffer;
}
function f32(value) {
const buffer = Buffer.alloc(4);
buffer.writeFloatLE(value);
return buffer;
}
function makeChunk({ dimension = 2, visibleVertices = null, waterLayerCount = 0, waterPayload = Buffer.alloc(0) } = {}) {
const count = (dimension + 1) ** 2 + (dimension + 1) * 4;
const classGuid = Buffer.alloc(16);
const heights = Buffer.concat(Array.from({ length: count }, (_, index) => f32(index / 10)));
const vertexData = Buffer.concat(Array.from({ length: count }, (_, index) => {
const visible = visibleVertices === null || visibleVertices.has(index);
const alpha = visible ? 255 : 0;
return u32(((alpha << 24) | (191 << 16) | (128 << 8) | 255) >>> 0);
}));
const bounds = Buffer.concat([
f32(-960), f32(-2), f32(-960),
f32(-945), f32(2), f32(-945),
]);
const mapDxt1 = Buffer.alloc(8);
mapDxt1.writeUInt16LE(0xffff, 0);
mapDxt1.writeUInt16LE(0x0000, 2);
const layerIds = Buffer.concat([u32(1).subarray(0, 2), u32(3).subarray(0, 2), u32(9).subarray(0, 2)]);
const bytes = Buffer.concat([
classGuid,
i32(4), // chunk iteration
i32(dimension),
f32(7.5),
i32(4),
bounds,
f32(-960), f32(-960),
heights,
vertexData,
i32(128), // splat resolution
classGuid,
i32(1), // compressed-alpha map type
i32(4), // splat map dimension
i32(3),
layerIds,
i32(1), // alpha maps
i32(1), // mip count
u32(4), u32(4),
mapDxt1,
i32(0), // doodad clusters
i32(waterLayerCount), // water layers
waterPayload,
]);
return deflateSync(bytes);
}
function makeDataStoreFixture() {
const tablePosition = 28;
const recordHeaderPosition = tablePosition + 272;
const recordPayloadPosition = recordHeaderPosition + 16;
const payload = Buffer.from([1, 2, 3, 4]);
const buffer = Buffer.alloc(recordPayloadPosition + payload.length);
buffer.writeInt32LE(tablePosition, 16);
buffer.writeInt32LE(1, 20);
buffer.writeInt32LE(32, 24);
buffer.write("header", tablePosition, "ascii");
buffer.writeInt32LE(4, tablePosition + 256);
buffer.writeInt32LE(recordHeaderPosition, tablePosition + 260);
buffer.writeInt32LE(1, tablePosition + 264);
buffer.writeInt32LE(64, tablePosition + 268);
buffer.writeUInt32LE(0, recordHeaderPosition);
buffer.writeInt32LE(recordPayloadPosition, recordHeaderPosition + 4);
buffer.writeInt32LE(payload.length, recordHeaderPosition + 8);
buffer.writeInt32LE(64, recordHeaderPosition + 12);
payload.copy(buffer, recordPayloadPosition);
return buffer;
}
test("terrain chunk parser preserves source geometry, visibility, normals, and splat layer IDs", () => {
const parsed = parseTerrainChunk(makeChunk({ visibleVertices: new Set([0]) }), "test");
assert.equal(parsed.dimension, 2);
assert.equal(parsed.localPositions.length, 27);
assert.deepEqual([...parsed.normalizedUvs.slice(0, 6)], [0, 0, 0.5, 0, 1, 0]);
assert.deepEqual([...parsed.layerIds], [1, 3, 9]);
assert.deepEqual([...parsed.triangleVisibility], [1, 1, 0, 0, 0, 0, 0, 0]);
assert.deepEqual([...parsed.indices], [0, 3, 4, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual([...parsed.cellVisibility], [1, 0, 0, 0]);
const expectedNormal = [
(191 / 255 - 0.5) * 2,
(128 / 255 - 0.5) * 2,
1,
];
for (let axis = 0; axis < 3; axis += 1) {
assert.ok(Math.abs(parsed.nativeNormals[axis] - expectedNormal[axis]) < 1e-6);
}
assert.equal(parsed.splatMaps[0].mipmaps[0].bytes.length, 8);
assert.equal(parsed.doodadClusterCount, 0);
assert.equal(parsed.waterLayerCount, 0);
assert.equal(parsed.unconsumedBytes, 0);
});
test("terrain topology retains every face slot and degenerates fully masked triangles", () => {
const parsed = parseTerrainChunk(makeChunk({ visibleVertices: new Set() }), "masked-test");
assert.equal(parsed.indices.length / 3, 8);
assert.equal(parsed.triangleVisibility.reduce((sum, value) => sum + value, 0), 0);
assert.ok(parsed.indices.every((value) => value === 0));
assert.ok(parsed.cellVisibility.every((value) => value === 0));
});
test("water-bearing WDB chunks can export their unchanged ground heightfield when explicitly enabled", () => {
const source = makeChunk({ waterLayerCount: 1, waterPayload: Buffer.from([0x51, 0x52, 0x53, 0x54]) });
assert.throws(() => parseTerrainChunk(source, "wet-fixture"), /allowUnparsedWaterPayload/);
const parsed = parseTerrainChunk(source, "wet-fixture", { allowUnparsedWaterPayload: true });
assert.equal(parsed.waterLayerCount, 1);
assert.equal(parsed.heights[0], 0);
assert.deepEqual([...parsed.triangleVisibility], [1, 1, 1, 1, 1, 1, 1, 1]);
assert.equal(parsed.unconsumedBytes, 4);
});
test("Rune disk datastore table and record headers are read using native offsets", () => {
const datastore = readDataStore(makeDataStoreFixture());
const header = datastore.tables.get("header");
const record = header.records.get("0");
assert.equal(datastore.tableCount, 1);
assert.equal(header.keyLength, 4);
assert.equal(header.count, 1);
assert.equal(record.position, 316);
assert.deepEqual([...record.key], [0, 0, 0, 0]);
});
test("native terrain chunk selection and DXT1 standalone DDS wrapper are deterministic", () => {
const selected = intersectingChunkIndices({
origin: [-7680, 0, -7680],
chunkDimension: 64,
gridSize: 7.5,
numXChunks: 32,
numZChunks: 32,
}, { min: [-664.98, 0, -716.26], max: [180.76, 0, 117.06] });
const dxt1 = Buffer.alloc(8);
dxt1.writeUInt16LE(0xffff, 0);
const rgba = dxt1ToRgba(dxt1, 4, 4);
const dds = wrapDxt1AsDds(4, 4, dxt1);
assert.equal(selected.length, 9);
assert.equal(selected[0].x, 14);
assert.equal(selected[0].z, 14);
assert.equal(encodeChunkKey(14, 14), 917518);
assert.deepEqual([...rgba.subarray(0, 4)], [255, 255, 255, 255]);
assert.equal(dds.toString("ascii", 0, 4), "DDS ");
assert.equal(dds.readUInt32LE(12), 4);
assert.equal(dds.readUInt32LE(16), 4);
assert.equal(dds.toString("ascii", 84, 88), "DXT1");
assert.deepEqual(dds.subarray(128), dxt1);
const rowPattern = Buffer.alloc(8);
rowPattern.writeUInt16LE(0xf800, 0); // red endpoint
rowPattern.writeUInt16LE(0x001f, 2); // blue endpoint
let packed = 0;
for (let pixel = 0; pixel < 16; pixel += 1) {
packed = (packed | ((pixel >>> 2) << (pixel * 2))) >>> 0;
}
rowPattern.writeUInt32LE(packed, 4);
const rowPixels = dxt1ToRgba(rowPattern, 4, 4);
assert.deepEqual([...rowPixels.subarray(0, 4)], [255, 0, 0, 255]);
assert.deepEqual([...rowPixels.subarray(4 * 4, 4 * 4 + 4)], [0, 0, 255, 255]);
});