import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; // Diagnostic lossless append: preserve existing images/geometry byte-for-byte. // Do not run solid-texture pruning on RGB weight carriers stored through AO. const [input, bundleDirectory, output] = process.argv.slice(2); if (!input || !bundleDirectory || !output) throw new Error("Provide shipping GLB, source bundle directory, and diagnostic output GLB."); const hash = bytes => createHash("sha256").update(bytes).digest("hex"); const original = await readFile(input); if (original.toString("ascii", 0, 4) !== "glTF" || original.readUInt32LE(4) !== 2 || original.readUInt32LE(8) !== original.length) throw new Error("Invalid GLB."); const jsonLength = original.readUInt32LE(12); const json = JSON.parse(original.subarray(20, 20 + jsonLength).toString().trim()); const binaryHeader = 20 + jsonLength; if (original.readUInt32LE(binaryHeader + 4) !== 0x004e4942 || json.buffers.length !== 1) throw new Error("Expected one embedded BIN buffer."); const originalBinary = original.subarray(binaryHeader + 8, binaryHeader + 8 + original.readUInt32LE(binaryHeader)); const manifest = JSON.parse(await readFile(path.join(bundleDirectory, "terrain-materials.json"), "utf8")); if (manifest.sourceAdt.sha256 !== "8ad75da6b9485d61537ee3b451c08b79e25b1b1b2c7d4e91c8e57ad0a65ff75b" || manifest.sourceAdt.clientBuild !== "3.3.5a-Ascension" || hash(await readFile(path.join(bundleDirectory, "source/tanarisinstance_30_29.adt"))) !== manifest.sourceAdt.sha256 || hash(original) !== "a45ad3ad072fce73d657fb668cab691fc87059a8a0701630465249ce8670d8f0") { throw new Error("Pilot inputs must match the verified Ascension source tile and reviewed shipping asset."); } const targetNodes = json.nodes.filter(node => node.name === manifest.sourceGeometry.node); if (targetNodes.length !== 1 || targetNodes[0].children?.length) throw new Error("Ambiguous or nested target terrain node."); const target = targetNodes[0]; const oldMesh = target.mesh; const oldPrimitives = json.meshes[oldMesh]?.primitives; if (oldPrimitives?.length !== 1 || !/^ADT_Terrain(?:\.\d+)?$/.test(json.materials[oldPrimitives[0].material]?.name ?? "") || json.accessors[oldPrimitives[0].indices].count !== manifest.sourceGeometry.indexCount || json.nodes.filter(node => node.mesh === oldMesh).length !== 1) throw new Error("Target is not the verified single terrain primitive."); if (json.nodes.some(node => node.children?.includes(json.nodes.indexOf(target)))) throw new Error("Expected terrain at scene root."); // This pilot supports the verified no-hole tile. Other tiles must be recovered independently. if (manifest.chunks.length !== 256 || manifest.chunks.some(chunk => chunk.sourceHoleMaskHex !== "0000")) throw new Error("Unverified tile topology."); const parts = [originalBinary]; let byteLength = originalBinary.length; function append(bytes, targetKind) { const padding = (4 - byteLength % 4) % 4; if (padding) { parts.push(Buffer.alloc(padding)); byteLength += padding; } const view = json.bufferViews.length; json.bufferViews.push({ buffer: 0, byteOffset: byteLength, byteLength: bytes.length, ...(targetKind ? { target: targetKind } : {}) }); parts.push(bytes); byteLength += bytes.length; return view; } async function verified(file, expected) { const absolute = path.resolve(bundleDirectory, file); if (!absolute.startsWith(`${path.resolve(bundleDirectory)}${path.sep}`)) throw new Error("Source path escapes bundle."); const bytes = await readFile(absolute); if (hash(bytes) !== expected) throw new Error(`Source hash mismatch: ${file}`); return bytes; } json.textures ??= []; json.samplers ??= []; json.images ??= []; const repeat = json.samplers.push({ wrapS: 10497, wrapT: 10497, minFilter: 9987, magFilter: 9729 }) - 1; const clamp = json.samplers.push({ wrapS: 33071, wrapT: 33071, minFilter: 9987, magFilter: 9729 }) - 1; json.extensions ??= {}; json.extensions.KHR_materials_variants ??= { variants: [] }; json.extensionsUsed = [...new Set([...(json.extensionsUsed ?? []), "KHR_materials_variants"])]; const variants = json.extensions.KHR_materials_variants.variants; const carriers = new Map(); async function carrier(name, file, sha256, weight) { if (carriers.has(name)) throw new Error("Duplicate carrier."); const bytes = await verified(file, sha256); const image = json.images.push({ name, mimeType: "image/png", bufferView: append(bytes) }) - 1; const texture = json.textures.push({ source: image, sampler: weight ? clamp : repeat }) - 1; const material = json.materials.push({ name, pbrMetallicRoughness: { metallicFactor: 0, roughnessFactor: 1, ...(!weight ? { baseColorTexture: { index: texture } } : {}) }, ...(weight ? { occlusionTexture: { index: texture } } : {}) }) - 1; const variant = variants.push({ name }) - 1; carriers.set(name, { material, variants: [variant] }); } for (const texture of manifest.textures) await carrier(texture.variant, texture.decodedPng, texture.decodedPngSha256, false); for (const weight of manifest.weightCarriers) await carrier(weight.variant, weight.image, weight.sha256, true); json.meshes.splice(oldMesh, 1); for (const node of json.nodes) if (node.mesh > oldMesh) node.mesh -= 1; delete target.mesh; delete target.translation; delete target.rotation; delete target.scale; target.matrix = manifest.sourceGeometry.nodeWorldMatrix; target.children = []; const definitions = new Map(manifest.materials.map(material => [material.name, material])); for (const chunk of manifest.chunks) { const definition = definitions.get(chunk.material); const material = json.materials.push({ name: definition.name, doubleSided: true, pbrMetallicRoughness: { metallicFactor: 0, roughnessFactor: 1 }, extras: { wowTerrain: definition.wowTerrain, sourceChunk: { indexX: chunk.indexX, indexY: chunk.indexY } } }) - 1; async function accessor(record, type, componentType, targetKind, bounds = false) { const bytes = await verified(record.file, record.sha256); if (bytes.length !== record.bytes) throw new Error("Attribute size mismatch."); const arity = type === "SCALAR" ? 1 : type === "VEC2" ? 2 : 3; const value = { bufferView: append(bytes, targetKind), componentType, type, count: record.count / arity }; if (bounds) { value.min = Array(arity).fill(Infinity); value.max = Array(arity).fill(-Infinity); for (let i = 0; i < record.count; i++) { const v = bytes.readFloatLE(i * 4); const k = i % arity; value.min[k] = Math.min(value.min[k], v); value.max[k] = Math.max(value.max[k], v); } } return json.accessors.push(value) - 1; } const geometry = chunk.geometry; const attributes = { POSITION: await accessor(geometry.position, "VEC3", 5126, 34962, true), NORMAL: await accessor(geometry.normal, "VEC3", 5126, 34962), TEXCOORD_0: await accessor(geometry.uv0, "VEC2", 5126, 34962), }; const mappings = [...definition.wowTerrain.layerVariants, ...definition.wowTerrain.weightVariants].map(name => { const mapping = carriers.get(name); if (!mapping) throw new Error(`Missing ${name}`); return mapping; }); const mesh = json.meshes.push({ name: definition.name, primitives: [{ attributes, material, indices: await accessor(geometry.indices, "SCALAR", 5123, 34963), extensions: { KHR_materials_variants: { mappings } } }] }) - 1; target.children.push(json.nodes.push({ name: definition.name, mesh }) - 1); } json.buffers[0].byteLength = byteLength; const binary = Buffer.concat(parts); const binaryPadding = (4 - binary.length % 4) % 4; const jsonBytes = Buffer.from(JSON.stringify(json)); const jsonPadding = (4 - jsonBytes.length % 4) % 4; const result = Buffer.alloc(28 + jsonBytes.length + jsonPadding + binary.length + binaryPadding); result.write("glTF"); result.writeUInt32LE(2, 4); result.writeUInt32LE(result.length, 8); result.writeUInt32LE(jsonBytes.length + jsonPadding, 12); result.writeUInt32LE(0x4e4f534a, 16); jsonBytes.copy(result, 20); result.fill(0x20, 20 + jsonBytes.length, 20 + jsonBytes.length + jsonPadding); const offset = 20 + jsonBytes.length + jsonPadding; result.writeUInt32LE(binary.length + binaryPadding, offset); result.writeUInt32LE(0x004e4942, offset + 4); binary.copy(result, offset + 8); await writeFile(output, result); const report = { status: "diagnostic-only", input: { path: input, sha256: hash(original), bytes: original.length }, output: { path: output, sha256: hash(result), bytes: result.length }, sourceAdt: manifest.sourceAdt, sourceBundle: bundleDirectory, chunkCount: manifest.chunks.length, recoveredTriangleCount: manifest.sourceGeometry.indexCount / 3, originalBinaryPrefixUnchanged: binary.subarray(0, originalBinary.length).equals(originalBinary), originalImagesUnchanged: true, sourceTextures: manifest.textures.length, weightImages: manifest.weightCarriers.length, compression: "original payloads retained; new source textures are lossless PNG and disjoint geometry is float32/uint16", limits: "One source-backed tile. No original-client color/shadow reconstruction or fleet publication." }; await writeFile(`${output}.report.json`, `${JSON.stringify(report, null, 2)}\n`); console.log(JSON.stringify(report, null, 2));