import { createHash } from "node:crypto"; import { Accessor } from "@gltf-transform/core"; import { KHRMaterialsVariants } from "@gltf-transform/extensions"; import sharp from "sharp"; const BARREN_POLICY_ID = "rom-barren-caves-wretched-tunnel-surface-quality-v2"; const HOTO_POLICY_ID = "rom-hoto-source-rock-detail-v2"; const HOTO_LEGACY_POLICY_ID = "rom-hoto-source-rock-detail-v1"; const BARREN_SOURCE = Object.freeze({ model: "model/dungeon/wretched_grotto/act_world_wretched_grotto.ros", sha256: "d2de5b83f63248b9b6f6d17e13104ee3cddbc4e77903d4eb561ca5731556bab7", material: "runewaker_material_12", texture: "texture/dungeon/wretched_grotto/wretched_tunnel", image: "a6d9c718_wretched_tunnel", imageSha256: "0f05ae62b2486ae90a9836873fac287fd89169abcef962ac7dfacd81fb469a7c", }); const HOTO_SOURCE = Object.freeze({ model: "model/dungeon/echoes_of_the_sea/act_echoes_of_the_sea.ros", sha256: "a0bd77c09045f8bfa1507484bbb2c2ff0cc72d397df4f769fc4e6d5aab14f8a2", }); const HOTO_TARGETS = Object.freeze({ runewaker_material_96: Object.freeze({ nativeMaterial: "runewaker_material_96", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_01", image: "122cc01b_echoes_of_the_sea_01", imageSha256: "ea395afa1d0c65aa57ed7e675a352ca47aab0b1f7a629a3bebdf36e03621a3b1", floorStrength: 0.85, floorBaseStrength: 0, wallStrength: 0.85, legacyWallStrength: 0.45, wallBaseContrast: 0.6, wallDetailVariation: 1, useOwnBaseMean: true, }), runewaker_material_53: Object.freeze({ nativeMaterial: "runewaker_material_53", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_04", image: "5c863840_echoes_of_the_sea_04", imageSha256: "fbb0eb3ac74689cf2e7b6bc043bc15f1c3cbc4638fc15675ff0710037e9666f9", floorStrength: 0.85, floorBaseStrength: 0, wallStrength: 0.85, legacyWallStrength: 0.45, wallBaseContrast: 0.6, wallDetailVariation: 1, useOwnBaseMean: true, }), runewaker_material_26: Object.freeze({ nativeMaterial: "runewaker_material_94", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_02", image: "5672f062_echoes_of_the_sea_02", imageSha256: "bc32a6751da4db246d50f66d2c9fb625f640be652956d7022a4feb649d62a1d2", floorVariantRole: "floor", floorStrength: 0, floorBaseStrength: 1, wallStrength: 0, legacyWallStrength: 0, wallBaseContrast: 1, wallDetailVariation: 0, }), runewaker_material_18: Object.freeze({ nativeMaterial: "runewaker_material_95", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_03", image: "b2ababb9_echoes_of_the_sea_03", imageSha256: "7e20e5f22744d638fa9bd983c13844800bc316e3502ab092bf6d8b6f68a1d018", floorVariantRole: "wall", floorStrength: 0, floorBaseStrength: 1, wallStrength: 0.45, legacyWallStrength: 0.45, wallBaseContrast: 1, wallDetailVariation: 0, }), }); const HOTO_DETAIL = Object.freeze({ floor: Object.freeze({ nativeMaterial: "runewaker_material_94", canonicalMaterial: "runewaker_material_26", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_02", image: "5672f062_echoes_of_the_sea_02", imageSha256: "bc32a6751da4db246d50f66d2c9fb625f640be652956d7022a4feb649d62a1d2", }), wall: Object.freeze({ nativeMaterial: "runewaker_material_95", canonicalMaterial: "runewaker_material_18", texture: "texture/dungeon/echoes_of_the_sea/echoes_of_the_sea_03", image: "b2ababb9_echoes_of_the_sea_03", imageSha256: "7e20e5f22744d638fa9bd983c13844800bc316e3502ab092bf6d8b6f68a1d018", }), }); const HOTO_VARIANTS = Object.freeze({ floor: "rom-detail-hoto-floor", wall: "rom-detail-hoto-wall" }); const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex"); const normalizeResource = (value) => String(value ?? "").replaceAll("\\", "/").replace(/^\/+/, "").toLowerCase(); const accessorHash = (accessor) => { const array = accessor?.getArray(); return array ? sha256(Buffer.from(array.buffer, array.byteOffset, array.byteLength)) : null; }; function verifySourceReport(sourceReport, source) { if (normalizeResource(sourceReport?.sourceModel) !== source.model.toLowerCase()) { throw new Error(`Surface policy source ROS path does not match ${source.model}.`); } if (String(sourceReport?.sourceResourceSha256 ?? "").toLowerCase() !== source.sha256) { throw new Error(`Surface policy source ROS SHA-256 does not match ${source.model}.`); } } function verifyNativeMaterial(sourceReport, nativeName, texturePath) { const rows = (sourceReport?.materials ?? []).filter((row) => row.name === nativeName); if (rows.length !== 1 || normalizeResource(rows[0].sourceTexture) !== texturePath.toLowerCase()) { throw new Error(`Expected exact native material/texture binding ${nativeName} -> ${texturePath}.`); } return rows[0]; } function listVariants(document) { const root = document.getRoot(); const extension = root.listExtensionsUsed().find((item) => item.extensionName === KHRMaterialsVariants.EXTENSION_NAME) ?? document.createExtension(KHRMaterialsVariants).setRequired(false); const byName = new Map(extension.listVariants().map((variant) => [variant.getName(), variant])); const getOrCreate = (name) => byName.get(name) ?? (byName.set(name, extension.createVariant(name)), byName.get(name)); return { extension, getOrCreate }; } function addSurfaceMappings(document, primitive, carrierByName, variantByName, names = [HOTO_VARIANTS.floor, HOTO_VARIANTS.wall]) { if (primitive.getExtension(KHRMaterialsVariants.EXTENSION_NAME)) { throw new Error("Surface-quality policy will not replace an existing KHR_materials_variants mapping."); } const variants = document.getRoot().listExtensionsUsed().find((item) => item.extensionName === KHRMaterialsVariants.EXTENSION_NAME); const mappingList = variants.createMappingList(); for (const name of names) { const carrier = carrierByName.get(name); const variant = variantByName.get(name); if (!carrier || !variant) throw new Error(`Missing surface-detail carrier or variant ${name}.`); mappingList.addMapping(variants.createMapping().setMaterial(carrier).addVariant(variant)); } primitive.setExtension(KHRMaterialsVariants.EXTENSION_NAME, mappingList); } function getOrCreateCarrier(document, sourceTexture, name, extras) { const root = document.getRoot(); const found = root.listMaterials().find((material) => material.getName() === `${name}:source-tile-carrier`); if (found) { if (found.getBaseColorTexture() !== sourceTexture || found.getExtras()?.runewakerSurfaceDetailCarrier?.policyId !== extras.policyId) { throw new Error(`Existing surface-detail carrier ${name} does not match its pinned source.`); } return found; } return document.createMaterial(`${name}:source-tile-carrier`) .setBaseColorTexture(sourceTexture) .setMetallicFactor(0) .setRoughnessFactor(1) .setExtras({ runewakerSurfaceDetailCarrier: extras }); } async function meanLinearFromPng(bytes) { const { data, info } = await sharp(bytes).removeAlpha().raw().toBuffer({ resolveWithObject: true }); if (info.channels < 3 || info.width < 1 || info.height < 1) throw new Error("Surface-detail source image must contain RGB pixels."); const sums = [0, 0, 0]; for (let offset = 0; offset < data.length; offset += info.channels) { for (let channel = 0; channel < 3; channel += 1) { const srgb = data[offset + channel] / 255; sums[channel] += srgb <= 0.04045 ? srgb / 12.92 : ((srgb + 0.055) / 1.055) ** 2.4; } } const count = info.width * info.height; return sums.map((value) => value / count); } function trianglePositions(matrix, position, indices) { return indices.map((index) => { const value = position.getElement(index, [0, 0, 0]); return [ matrix[0] * value[0] + matrix[4] * value[1] + matrix[8] * value[2] + matrix[12], matrix[1] * value[0] + matrix[5] * value[1] + matrix[9] * value[2] + matrix[13], matrix[2] * value[0] + matrix[6] * value[1] + matrix[10] * value[2] + matrix[14], ]; }); } function sub3(left, right) { return [left[0] - right[0], left[1] - right[1], left[2] - right[2]]; } function cross3(left, right) { return [left[1] * right[2] - left[2] * right[1], left[2] * right[0] - left[0] * right[2], left[0] * right[1] - left[1] * right[0]]; } function length3(value) { return Math.hypot(value[0], value[1], value[2]); } const BARREN_PATCHES = Object.freeze([ { key: "u0-v0", col: 0, row: 0, rect: { left: 0, top: 0, width: 256, height: 256 } }, { key: "u3-v0", col: 3, row: 0, rect: { left: 768, top: 0, width: 256, height: 256 } }, { key: "u0-v1", col: 0, row: 1, rect: { left: 0, top: 256, width: 256, height: 256 } }, { key: "u3-v1", col: 3, row: 1, rect: { left: 768, top: 256, width: 256, height: 256 } }, ].map((patch) => Object.freeze({ ...patch, uvRect: { u0: patch.col / 4, u1: (patch.col + 1) / 4, v0: patch.row / 2, v1: (patch.row + 1) / 2 }, }))); const BARREN_WALL_DETAIL_PATCH = Object.freeze({ key: "wall-u1-v0", rect: Object.freeze({ left: 256, top: 0, width: 256, height: 256 }), textureName: "rom-barren-wretched-tunnel-wall-detail-u1-v0", }); function barrenPatchForTriangle(uvs, worldNormalY) { if (worldNormalY < 0.7) return null; const tolerance = 0.0025; const minU = Math.min(...uvs.map((uv) => uv[0])); const maxU = Math.max(...uvs.map((uv) => uv[0])); const minV = Math.min(...uvs.map((uv) => uv[1])); const maxV = Math.max(...uvs.map((uv) => uv[1])); return BARREN_PATCHES.find((patch) => minU >= patch.uvRect.u0 - tolerance && maxU <= patch.uvRect.u1 + tolerance && minV >= patch.uvRect.v0 - tolerance && maxV <= patch.uvRect.v1 + tolerance)?.key ?? null; } async function applyBarrenFloorPolicy(document, sourceReport) { verifySourceReport(sourceReport, BARREN_SOURCE); verifyNativeMaterial(sourceReport, BARREN_SOURCE.material, BARREN_SOURCE.texture); const root = document.getRoot(); const sourceMaterial = root.listMaterials().find((material) => material.getName() === BARREN_SOURCE.material); if (!sourceMaterial) throw new Error("Barren atlas-safe policy cannot find native material 12."); const sourceTexture = sourceMaterial.getBaseColorTexture(); const sourcePng = Buffer.from(sourceTexture?.getImage() ?? []); if (sourceTexture?.getName() !== BARREN_SOURCE.image || sha256(sourcePng) !== BARREN_SOURCE.imageSha256) { throw new Error("Barren material 12 embedded atlas differs from the pinned native prepared PNG."); } const imageMeta = await sharp(sourcePng).metadata(); if (imageMeta.width !== 1024 || imageMeta.height !== 512) throw new Error("Barren atlas dimensions differ from the reviewed 1024×512 source."); const baseMeanLinear = await meanLinearFromPng(sourcePng); // Migrate the previous whole-atlas x4 experiment back to authentic UV0 // before splitting only faces proven to stay inside a native floor island. let legacyRepeatPrimitivesRestored = 0; for (const node of root.listNodes()) { const mesh = node.getMesh(); if (!mesh) continue; for (const primitive of mesh.listPrimitives()) { if (primitive.getExtras()?.romUvAdaptation?.id !== "rom-barren-caves-wretched-tunnel-repeat-x4") continue; const material = primitive.getMaterial(); const uv0 = primitive.getAttribute("TEXCOORD_0"); const uv1 = primitive.getAttribute("TEXCOORD_1"); if (material?.getBaseColorTexture()?.getName() !== BARREN_SOURCE.image || !uv0 || !uv1 || uv0.getCount() !== uv1.getCount()) { throw new Error("Cannot safely restore an earlier Barren whole-atlas x4 primitive."); } for (let index = 0; index < uv0.getCount(); index += 1) { const original = uv0.getElement(index, [0, 0]); const adapted = uv1.getElement(index, [0, 0]); if (Math.abs(adapted[0] - original[0] * 4) > 1e-6 || Math.abs(adapted[1] - original[1] * 4) > 1e-6) { throw new Error("Earlier Barren repeat marker does not contain the expected x4 UV1 values."); } } const extras = { ...(primitive.getExtras() ?? {}) }; delete extras.romUvAdaptation; primitive.setExtras(extras); primitive.setAttribute("TEXCOORD_1", null); primitive.setMaterial(sourceMaterial); legacyRepeatPrimitivesRestored += 1; } } const alreadyApplied = sourceMaterial.getExtras()?.runewakerSurfaceDetail?.policyId === BARREN_POLICY_ID; if (alreadyApplied) { const surfaceDetail = sourceMaterial.getExtras()?.runewakerSurfaceDetail; const cropTextures = root.listTextures().filter((texture) => texture.getExtras()?.romSourcePixelCrop?.policyId === BARREN_POLICY_ID); const cropMaterials = root.listMaterials().filter((material) => material.getExtras()?.romUvAdaptation?.id === BARREN_POLICY_ID); const expectedCropMaterialNames = new Set(BARREN_PATCHES.map((patch) => `${BARREN_SOURCE.material}_${patch.key}_crop_repeat4`)); const actualCropMaterialNames = new Set(cropMaterials.map((material) => material.getName())); const wallTexture = root.listTextures().find((texture) => texture.getName() === BARREN_WALL_DETAIL_PATCH.textureName); const wallCarrier = root.listMaterials().find((material) => material.getName() === "rom-detail-barren-caves-wall:source-tile-carrier"); const wallInfo = wallCarrier?.getBaseColorTextureInfo(); if (surfaceDetail.wallBaseContrast !== 0.25 || surfaceDetail.wallStrength !== 0.85 || surfaceDetail.wallDetailVariation !== 1 || JSON.stringify(surfaceDetail.baseMeanLinear) !== JSON.stringify(baseMeanLinear) || cropTextures.length !== BARREN_PATCHES.length + 1 || !wallTexture || wallCarrier?.getBaseColorTexture() !== wallTexture || wallInfo?.getWrapS() !== 33648 || wallInfo?.getWrapT() !== 33648 || actualCropMaterialNames.size !== BARREN_PATCHES.length || [...expectedCropMaterialNames].some((name) => !actualCropMaterialNames.has(name))) { throw new Error("Barren atlas-safe policy marker is present but its exact floor/wall crop carriers are incomplete."); } return { id: BARREN_POLICY_ID, status: "already-applied", cropTextureCount: cropTextures.length, cropMaterialCount: actualCropMaterialNames.size, cropMaterialInstanceCount: cropMaterials.length, wallDetailTextureName: wallTexture.getName(), wallDetailWrap: "mirrored-repeat", repeatFactor: 4, trianglePartitionChanged: true, sourceModel: BARREN_SOURCE.model, sourceModelSha256: BARREN_SOURCE.sha256, }; } if (root.listTextures().some((texture) => texture.getExtras()?.romSourcePixelCrop?.policyId === BARREN_POLICY_ID)) { throw new Error("Barren source GLB already contains partial atlas-safe floor crops."); } const buffer = root.listBuffers()[0]; if (!buffer) throw new Error("Barren canonical GLB has no binary buffer for UV1."); const cropped = new Map(); for (const patch of BARREN_PATCHES) { const png = await sharp(sourcePng).extract(patch.rect).png().toBuffer(); const rgba = await sharp(png).ensureAlpha().raw().toBuffer(); const meanLinear = await meanLinearFromPng(png); const sourcePixelCrop = { version: 1, sourceImageName: BARREN_SOURCE.image, sourcePreparedPngSha256: BARREN_SOURCE.imageSha256, sourceWidth: 1024, sourceHeight: 512, rect: patch.rect, croppedPngSha256: sha256(png), croppedRgbaSha256: sha256(rgba), policyId: BARREN_POLICY_ID, }; const texture = document.createTexture(`rom-barren-wretched-tunnel-${patch.key}`) .setMimeType("image/png") .setImage(png) .setExtras({ romSourcePixelCrop: sourcePixelCrop }); cropped.set(patch.key, { patch, png, meanLinear, sourcePixelCrop, texture }); } const wallDetailPng = await sharp(sourcePng).extract(BARREN_WALL_DETAIL_PATCH.rect).png().toBuffer(); const wallDetailRgba = await sharp(wallDetailPng).ensureAlpha().raw().toBuffer(); const wallDetailCrop = { version: 1, sourceImageName: BARREN_SOURCE.image, sourcePreparedPngSha256: BARREN_SOURCE.imageSha256, sourceWidth: 1024, sourceHeight: 512, rect: BARREN_WALL_DETAIL_PATCH.rect, croppedPngSha256: sha256(wallDetailPng), croppedRgbaSha256: sha256(wallDetailRgba), policyId: BARREN_POLICY_ID, }; const wallDetailTexture = document.createTexture(BARREN_WALL_DETAIL_PATCH.textureName) .setMimeType("image/png") .setImage(wallDetailPng) .setExtras({ romSourcePixelCrop: wallDetailCrop }); const wallDetailMeanLinear = await meanLinearFromPng(wallDetailPng); const variantInfo = listVariants(document); const variantNames = ["rom-detail-barren-caves-floor", "rom-detail-barren-caves-wall"]; const variantByName = new Map(variantNames.map((name) => [name, variantInfo.getOrCreate(name)])); const baseExtras = sourceMaterial.getExtras() ?? {}; const cropForDetail = cropped.get("u0-v0"); const sourceProof = { sourceModel: BARREN_SOURCE.model, sourceModelSha256: BARREN_SOURCE.sha256, sourceMaterial: BARREN_SOURCE.material, sourceTexture: BARREN_SOURCE.texture, sourceImageName: BARREN_SOURCE.image, sourcePreparedPngSha256: BARREN_SOURCE.imageSha256, sourcePixelCrop: cropForDetail.sourcePixelCrop, baseMeanLinear, wallBaseContrast: 0.25, wallDetailVariation: 1, wallDetailImageName: BARREN_WALL_DETAIL_PATCH.textureName, wallDetailSourcePixelCrop: wallDetailCrop, wallDetailMeanLinear, wallDetailWrap: "mirrored-repeat", }; sourceMaterial.setExtras({ ...baseExtras, runewakerSurfaceDetail: { version: 1, floorVariant: variantNames[0], wallVariant: variantNames[1], periodMeters: 2, floorStrength: 0, wallStrength: 0.85, baseMeanLinear, wallBaseContrast: 0.25, wallDetailVariation: 1, floorMeanLinear: cropForDetail.meanLinear, wallMeanLinear: wallDetailMeanLinear, policyId: BARREN_POLICY_ID, sourceProof, }, }); const carrierExtras = { version: 1, policyId: BARREN_POLICY_ID, sourceImageName: BARREN_SOURCE.image, sourcePixelCrop: cropForDetail.sourcePixelCrop }; const carriers = new Map([ [variantNames[0], getOrCreateCarrier(document, cropForDetail.texture, variantNames[0], { ...carrierExtras, variantName: variantNames[0] })], [variantNames[1], getOrCreateCarrier(document, wallDetailTexture, variantNames[1], { ...carrierExtras, variantName: variantNames[1], sourcePixelCrop: wallDetailCrop, sourceImageName: BARREN_WALL_DETAIL_PATCH.textureName, wrap: "mirrored-repeat", })], ]); carriers.get(variantNames[1]).getBaseColorTextureInfo().setWrapS(33648).setWrapT(33648); const reports = []; let adaptedTriangles = 0; let untouchedTriangles = 0; for (const node of root.listNodes()) { const mesh = node.getMesh(); if (!mesh) continue; const matrix = node.getWorldMatrix(); const originalPrimitives = [...mesh.listPrimitives()]; for (const [primitiveIndex, primitive] of originalPrimitives.entries()) { if (primitive.getMaterial() !== sourceMaterial) continue; if (primitive.getExtension(KHRMaterialsVariants.EXTENSION_NAME)) throw new Error("Barren material 12 already has variant mappings."); const position = primitive.getAttribute("POSITION"); const normal = primitive.getAttribute("NORMAL"); const uv0 = primitive.getAttribute("TEXCOORD_0"); const indices = primitive.getIndices(); if (!position || !normal || !uv0 || !indices || uv0.getType() !== Accessor.Type.VEC2) { throw new Error(`${node.getName()} material 12 primitive ${primitiveIndex} lacks position/normal/index/UV0 data.`); } const indexArray = indices.getArray(); const indicesByCrop = new Map(BARREN_PATCHES.map((patch) => [patch.key, []])); const untouched = []; for (let offset = 0; offset < indexArray.length; offset += 3) { const triangle = [indexArray[offset], indexArray[offset + 1], indexArray[offset + 2]]; const points = trianglePositions(matrix, position, triangle); const cross = cross3(sub3(points[1], points[0]), sub3(points[2], points[0])); const magnitude = length3(cross); const normalY = magnitude > 1e-12 ? Math.abs(cross[1]) / magnitude : 0; const triangleUvs = triangle.map((index) => uv0.getElement(index, [0, 0])); const key = barrenPatchForTriangle(triangleUvs, normalY); (key ? indicesByCrop.get(key) : untouched).push(...triangle); } const activeCrops = [...indicesByCrop].filter(([, group]) => group.length > 0); if (!activeCrops.length) continue; const originalTriangleCount = indexArray.length / 3; const before = accessorHash(indices); const primitiveOutputs = []; if (untouched.length) { primitive.setIndices(document.createAccessor(`${node.getName()}:${BARREN_POLICY_ID}:untouched-indices`) .setType(Accessor.Type.SCALAR).setArray(new indexArray.constructor(untouched)).setBuffer(buffer)); primitive.setExtension(KHRMaterialsVariants.EXTENSION_NAME, (() => { const current = document.getRoot().listExtensionsUsed().find((item) => item.extensionName === KHRMaterialsVariants.EXTENSION_NAME); const mappingList = current.createMappingList(); for (const name of variantNames) mappingList.addMapping(current.createMapping().setMaterial(carriers.get(name)).addVariant(variantByName.get(name))); return mappingList; })()); primitiveOutputs.push({ primitive, triangles: untouched.length / 3, category: "untouched-atlas" }); } else mesh.removePrimitive(primitive); for (const [key, group] of activeCrops) { const { patch, texture, sourcePixelCrop } = cropped.get(key); const adaptedMaterial = sourceMaterial.clone() .setName(`${BARREN_SOURCE.material}_${key}_crop_repeat4`) .setBaseColorTexture(texture); adaptedMaterial.getBaseColorTextureInfo().setTexCoord(1).setWrapS(10497).setWrapT(10497); adaptedMaterial.setExtras({ ...baseExtras, romUvAdaptation: { version: 1, id: BARREN_POLICY_ID, atlasRect: patch.rect, uvRect: patch.uvRect, repeatFactor: 4, nativeBaseTexture: BARREN_SOURCE.image, nativeBaseTexturePngSha256: BARREN_SOURCE.imageSha256, sourcePixelCrop, }, }); const values = new Float32Array(uv0.getCount() * 2); for (let index = 0; index < uv0.getCount(); index += 1) { const uv = uv0.getElement(index, [0, 0]); values[index * 2] = ((uv[0] - patch.uvRect.u0) / (patch.uvRect.u1 - patch.uvRect.u0)) * 4; values[index * 2 + 1] = ((uv[1] - patch.uvRect.v0) / (patch.uvRect.v1 - patch.uvRect.v0)) * 4; } const uv1 = document.createAccessor(`${node.getName()}:${BARREN_POLICY_ID}:${key}:TEXCOORD_1`) .setType(Accessor.Type.VEC2).setArray(values).setBuffer(buffer); const adapted = primitive.clone() .setIndices(document.createAccessor(`${node.getName()}:${BARREN_POLICY_ID}:${key}:indices`) .setType(Accessor.Type.SCALAR).setArray(new indexArray.constructor(group)).setBuffer(buffer)) .setAttribute("TEXCOORD_1", uv1) .setMaterial(adaptedMaterial) .setExtras({ ...(primitive.getExtras() ?? {}), romUvAdaptation: { version: 1, id: BARREN_POLICY_ID, atlasRect: patch.rect, uvRect: patch.uvRect, repeatFactor: 4, sourceMaterial: BARREN_SOURCE.material, sourceTexture: BARREN_SOURCE.image, sourcePngSha256: BARREN_SOURCE.imageSha256, sourcePixelCrop, }, }); const variants = document.getRoot().listExtensionsUsed().find((item) => item.extensionName === KHRMaterialsVariants.EXTENSION_NAME); const mappings = variants.createMappingList(); for (const name of variantNames) mappings.addMapping(variants.createMapping().setMaterial(carriers.get(name)).addVariant(variantByName.get(name))); adapted.setExtension(KHRMaterialsVariants.EXTENSION_NAME, mappings); mesh.addPrimitive(adapted); primitiveOutputs.push({ primitive: adapted, triangles: group.length / 3, category: key }); adaptedTriangles += group.length / 3; } untouchedTriangles += untouched.length / 3; const outputTriangles = primitiveOutputs.reduce((sum, item) => sum + item.triangles, 0); if (outputTriangles !== originalTriangleCount) throw new Error(`Barren triangle partition failed on ${node.getName()} primitive ${primitiveIndex}.`); for (const { primitive: outputPrimitive } of primitiveOutputs) { if (outputPrimitive.getAttribute("POSITION") !== position || outputPrimitive.getAttribute("NORMAL") !== normal || outputPrimitive.getAttribute("TEXCOORD_0") !== uv0) { throw new Error(`Barren UV policy changed source position/normal/UV0 accessors on ${node.getName()}.`); } } reports.push({ node: node.getName(), sourcePrimitiveIndex: primitiveIndex, sourceTriangles: originalTriangleCount, sourceIndexHash: before, sourcePositionHash: accessorHash(position), sourceNormalHash: accessorHash(normal), sourceUv0Hash: accessorHash(uv0), outputGroups: primitiveOutputs.map(({ category, triangles }) => ({ category, triangles })), triangleCoverageExact: true, positionNormalUv0AccessorsShared: true, indexStreamLayoutChanged: true, }); } } if (!reports.length) throw new Error("Barren atlas-safe policy found no floor triangles in the four reviewed source-image quadrants."); const patchReports = [...cropped.values()].map(({ patch, png, sourcePixelCrop, meanLinear }) => ({ key: patch.key, sourcePixelRect: patch.rect, sourceUvRect: patch.uvRect, repeatFactor: 4, croppedPngSha256: sourcePixelCrop.croppedPngSha256, croppedRgbaSha256: sourcePixelCrop.croppedRgbaSha256, bytes: png.length, meanLinear, })); patchReports.push({ key: BARREN_WALL_DETAIL_PATCH.key, role: "wall-detail-carrier", sourcePixelRect: BARREN_WALL_DETAIL_PATCH.rect, repeatMode: "mirrored-repeat", croppedPngSha256: wallDetailCrop.croppedPngSha256, croppedRgbaSha256: wallDetailCrop.croppedRgbaSha256, bytes: wallDetailPng.length, meanLinear: wallDetailMeanLinear, }); return { id: BARREN_POLICY_ID, status: "applied", sourceModel: BARREN_SOURCE.model, sourceModelSha256: BARREN_SOURCE.sha256, sourceMaterial: BARREN_SOURCE.material, sourceTexture: BARREN_SOURCE.texture, sourceImage: BARREN_SOURCE.image, sourceImageSha256: BARREN_SOURCE.imageSha256, sourceImageDimensions: [imageMeta.width, imageMeta.height], repeatFactor: 4, worldNormalYThreshold: 0.7, atlasSeamToleranceUv: 0.0025, trianglePolicy: "floor triangles whose UV0 corners remain inside a reviewed 256x256 floor island receive crop-localized repeat UV1; other faces keep the full native atlas", trianglePartitionChanged: true, legacyRepeatPrimitivesRestored, sourcePositionNormalUv0Preserved: true, untouchedTriangles, adaptedTriangles, primitiveReports: reports, crops: patchReports, wallDetail: { textureName: BARREN_WALL_DETAIL_PATCH.textureName, sourcePixelCrop: wallDetailCrop, meanLinear: wallDetailMeanLinear, samplerWrap: "mirrored-repeat", }, wallBaseContrast: 0.25, wallDetailVariation: 1, baseMeanLinear, variants: variantNames, }; } async function applyHotoDetailPolicy(document, sourceReport) { verifySourceReport(sourceReport, HOTO_SOURCE); for (const target of Object.values(HOTO_TARGETS)) verifyNativeMaterial(sourceReport, target.nativeMaterial, target.texture); const root = document.getRoot(); const sourceTextures = new Map(); const targetMaterials = new Map(); for (const [materialName, target] of Object.entries(HOTO_TARGETS)) { const material = root.listMaterials().find((item) => item.getName() === materialName); if (!material) throw new Error(`HOTO surface policy cannot find canonical material ${materialName}.`); const texture = material.getBaseColorTexture(); const image = Buffer.from(texture?.getImage() ?? []); if (texture?.getName() !== target.image || sha256(image) !== target.imageSha256) { throw new Error(`HOTO ${materialName} base atlas/tile is not the pinned source PNG.`); } if (material.getNormalTexture() || material.getMetallicRoughnessTexture() || material.getEmissiveTexture()) { throw new Error(`HOTO surface policy refuses unexpected auxiliary native texture slots on ${materialName}.`); } targetMaterials.set(materialName, material); sourceTextures.set(materialName, texture); } const detailSources = new Map(); for (const [role, detail] of Object.entries(HOTO_DETAIL)) { const material = targetMaterials.get(detail.canonicalMaterial); const texture = sourceTextures.get(detail.canonicalMaterial); if (texture?.getName() !== detail.image || sha256(texture.getImage() ?? Buffer.alloc(0)) !== detail.imageSha256) { throw new Error(`HOTO ${role} detail tile is not the native, byte-identical image.`); } detailSources.set(role, { ...detail, sourceTexturePath: detail.texture, textureObject: texture, meanLinear: await meanLinearFromPng(Buffer.from(texture.getImage())) }); } const expectedByMaterial = new Map(); const detailProofs = {}; for (const [role, detail] of detailSources) { detailProofs[role] = { nativeMaterial: detail.nativeMaterial, canonicalMaterialName: detail.canonicalMaterial, sourceTexture: detail.sourceTexturePath, sourceImage: detail.image, sourcePreparedPngSha256: detail.imageSha256, meanLinear: detail.meanLinear, }; } for (const [materialName, target] of Object.entries(HOTO_TARGETS)) { const material = targetMaterials.get(materialName); const baseTexture = sourceTextures.get(materialName); const baseMeanLinear = target.useOwnBaseMean ? await meanLinearFromPng(Buffer.from(baseTexture.getImage())) : undefined; const floorRole = target.floorVariantRole ?? "floor"; const sourceProof = { sourceModel: HOTO_SOURCE.model, sourceModelSha256: HOTO_SOURCE.sha256, sourceMaterial: target.nativeMaterial, canonicalMaterialName: materialName, sourceTexture: target.texture, sourceTextureName: target.image, sourcePreparedPngSha256: target.imageSha256, floorDetailSource: detailProofs[floorRole], wallDetailSource: detailProofs.wall, floorVariantRole: floorRole, floorBaseStrength: target.floorBaseStrength, floorStrength: target.floorStrength, wallStrength: target.wallStrength, wallBaseContrast: target.wallBaseContrast, wallDetailVariation: target.wallDetailVariation, }; const metadata = { version: 1, floorVariant: HOTO_VARIANTS[floorRole], wallVariant: HOTO_VARIANTS.wall, periodMeters: 2, floorBaseStrength: target.floorBaseStrength, floorStrength: target.floorStrength, wallStrength: target.wallStrength, floorMeanLinear: detailSources.get(floorRole).meanLinear, wallMeanLinear: detailSources.get("wall").meanLinear, wallBaseContrast: target.wallBaseContrast, wallDetailVariation: target.wallDetailVariation, policyId: HOTO_POLICY_ID, sourceProof, }; if (baseMeanLinear) metadata.baseMeanLinear = baseMeanLinear; expectedByMaterial.set(materialName, metadata); } const carrierNames = new Map(Object.entries(HOTO_VARIANTS).map(([role, variantName]) => [variantName, role])); const existingMaterialPolicies = [...targetMaterials.values()].map((material) => material.getExtras()?.runewakerSurfaceDetail?.policyId ?? null); const hasExistingPolicy = existingMaterialPolicies.some(Boolean); if (hasExistingPolicy) { if (existingMaterialPolicies.some((id) => id !== HOTO_POLICY_ID && id !== HOTO_LEGACY_POLICY_ID)) { throw new Error("HOTO surface metadata contains an unknown or partial policy marker."); } const existingPolicy = existingMaterialPolicies[0]; if (!existingPolicy || existingMaterialPolicies.some((id) => id !== existingPolicy)) { throw new Error("HOTO source-rock detail policy is only partially applied."); } const extension = root.listExtensionsUsed().find((item) => item.extensionName === KHRMaterialsVariants.EXTENSION_NAME); const variantByName = new Map((extension?.listVariants() ?? []).map((variant) => [variant.getName(), variant])); if (!extension || [...carrierNames.keys()].some((name) => !variantByName.has(name))) { throw new Error("HOTO source-rock detail policy marker exists but its material variants are absent."); } const carrierByName = new Map(); for (const [variantName, role] of carrierNames) { const detail = detailSources.get(role); const carrier = root.listMaterials().find((material) => material.getName() === `${variantName}:source-tile-carrier`); const carrierProof = carrier?.getExtras()?.runewakerSurfaceDetailCarrier; if (!carrier || carrier.getBaseColorTexture() !== detail.textureObject || carrierProof?.policyId !== existingPolicy || carrierProof?.sourceResourceSha256 !== HOTO_SOURCE.sha256 || carrierProof?.sourceTextureName !== detail.image || carrierProof?.sourcePreparedPngSha256 !== detail.imageSha256) { throw new Error(`HOTO ${variantName} carrier does not match the pinned source tile/policy.`); } carrierByName.set(variantName, carrier); } let mappedPrimitiveCount = 0; for (const node of root.listNodes()) for (const primitive of node.getMesh()?.listPrimitives() ?? []) { const materialName = primitive.getMaterial()?.getName(); if (!targetMaterials.has(materialName)) continue; const mappingList = primitive.getExtension(KHRMaterialsVariants.EXTENSION_NAME)?.listMappings() ?? []; const mappedNames = new Set(); for (const mapping of mappingList) { if (mapping.listVariants().length !== 1) throw new Error(`HOTO ${materialName} has a bundled or ambiguous surface-detail mapping.`); const variantName = mapping.listVariants()[0].getName(); if (!carrierByName.has(variantName) || mapping.getMaterial() !== carrierByName.get(variantName) || mappedNames.has(variantName)) { throw new Error(`HOTO ${materialName} surface-detail mapping does not point to its verified source carrier.`); } mappedNames.add(variantName); } if (mappedNames.size !== 2 || [...carrierNames.keys()].some((name) => !mappedNames.has(name))) { throw new Error(`HOTO ${materialName} is missing one of the pinned source-detail mappings.`); } mappedPrimitiveCount += 1; } if (mappedPrimitiveCount !== 36) throw new Error(`HOTO source-rock detail mappings are incomplete: expected 36, found ${mappedPrimitiveCount}.`); if (existingPolicy === HOTO_LEGACY_POLICY_ID) { for (const [materialName, target] of Object.entries(HOTO_TARGETS)) { const old = targetMaterials.get(materialName).getExtras()?.runewakerSurfaceDetail; const proof = old?.sourceProof; if (old?.version !== 1 || old.floorVariant !== HOTO_VARIANTS.floor || old.wallVariant !== HOTO_VARIANTS.wall || old.periodMeters !== 2 || old.floorStrength !== 0.65 || old.floorBaseStrength !== undefined && old.floorBaseStrength !== 0 || old.wallStrength !== (target.legacyWallStrength ?? target.wallStrength) || old.wallBaseContrast !== undefined && old.wallBaseContrast !== 1 || old.wallDetailVariation !== undefined && old.wallDetailVariation !== 0 || JSON.stringify(old.floorMeanLinear) !== JSON.stringify(detailSources.get("floor").meanLinear) || JSON.stringify(old.wallMeanLinear) !== JSON.stringify(detailSources.get("wall").meanLinear) || proof?.sourceModelSha256 !== HOTO_SOURCE.sha256 || proof?.canonicalMaterialName !== materialName || proof?.sourceMaterial !== target.nativeMaterial || proof?.sourceTexture !== target.texture || proof?.sourceTextureName !== target.image || proof?.sourcePreparedPngSha256 !== target.imageSha256) { throw new Error(`HOTO legacy v1 metadata on ${materialName} is not the reviewed migration source.`); } } for (const carrier of carrierByName.values()) { const extras = carrier.getExtras() ?? {}; carrier.setExtras({ ...extras, runewakerSurfaceDetailCarrier: { ...extras.runewakerSurfaceDetailCarrier, policyId: HOTO_POLICY_ID } }); } for (const [materialName, metadata] of expectedByMaterial) { const material = targetMaterials.get(materialName); material.setExtras({ ...(material.getExtras() ?? {}), runewakerSurfaceDetail: metadata }); } return { id: HOTO_POLICY_ID, status: "migrated-v1", migratedFrom: HOTO_LEGACY_POLICY_ID, mappedPrimitiveCount, materials: Object.keys(HOTO_TARGETS) }; } for (const [variantName, role] of carrierNames) { const detail = detailSources.get(role); const proof = carrierByName.get(variantName).getExtras()?.runewakerSurfaceDetailCarrier; if (proof?.variantName !== variantName || proof?.sourceMaterial !== detail.nativeMaterial || proof?.canonicalMaterialName !== detail.canonicalMaterial || proof?.sourceTexturePath !== detail.texture) { throw new Error(`HOTO ${variantName} carrier provenance does not match the pinned source.`); } } for (const [materialName, metadata] of expectedByMaterial) { const existing = targetMaterials.get(materialName).getExtras()?.runewakerSurfaceDetail; if (JSON.stringify(existing) !== JSON.stringify(metadata)) throw new Error(`HOTO v2 surface metadata on ${materialName} differs from the reviewed policy.`); } return { id: HOTO_POLICY_ID, status: "already-applied", mappedPrimitiveCount, materials: Object.keys(HOTO_TARGETS) }; } if (root.listMaterials().some((material) => material.getExtras()?.runewakerSurfaceDetailCarrier?.policyId === HOTO_LEGACY_POLICY_ID || material.getExtras()?.runewakerSurfaceDetailCarrier?.policyId === HOTO_POLICY_ID)) { throw new Error("HOTO source-tile carrier markers exist without the complete source-detail policy."); } const { getOrCreate } = listVariants(document); const variantByName = new Map(Object.values(HOTO_VARIANTS).map((name) => [name, getOrCreate(name)])); const carrierByName = new Map(); for (const [role, detail] of detailSources) { const variantName = HOTO_VARIANTS[role]; const extras = { version: 1, variantName, policyId: HOTO_POLICY_ID, sourceResourceSha256: HOTO_SOURCE.sha256, sourceMaterial: detail.nativeMaterial, canonicalMaterialName: detail.canonicalMaterial, sourceTexturePath: detail.sourceTexturePath, sourceTextureName: detail.image, sourcePreparedPngSha256: detail.imageSha256, }; carrierByName.set(variantName, getOrCreateCarrier(document, detail.textureObject, variantName, extras)); } let mappedPrimitiveCount = 0; for (const node of root.listNodes()) { const mesh = node.getMesh(); if (!mesh) continue; for (const primitive of mesh.listPrimitives()) { const materialName = primitive.getMaterial()?.getName(); if (targetMaterials.get(materialName) !== primitive.getMaterial()) continue; addSurfaceMappings(document, primitive, carrierByName, variantByName); mappedPrimitiveCount += 1; } } if (mappedPrimitiveCount !== 36) throw new Error(`HOTO source-rock detail target count changed: expected 36, found ${mappedPrimitiveCount}.`); for (const [materialName, metadata] of expectedByMaterial) { const material = targetMaterials.get(materialName); material.setExtras({ ...(material.getExtras() ?? {}), runewakerSurfaceDetail: metadata }); } return { id: HOTO_POLICY_ID, status: "applied", sourceModel: HOTO_SOURCE.model, sourceModelSha256: HOTO_SOURCE.sha256, detail: { periodMeters: 2, sourceSpace: "browser-adapted world-space detail; native base UV0 retained" }, detailSources: detailProofs, materials: Object.keys(HOTO_TARGETS).map((name) => ({ name, metadata: expectedByMaterial.get(name) })), mappedPrimitiveCount, sourceGeometryChanged: false, nativeBaseTexturesAndMaterialSemanticsPreserved: true, migration: "v1 metadata migrates only when every target material, tile carrier and variant mapping matches the reviewed legacy configuration", }; } /** Source-pinned atlas-safe crops and native source-rock detail carriers. */ export async function applyRunewakerSurfaceQualityPolicies(document, { dungeonId, policyIds = [], sourceReport } = {}) { if (new Set(policyIds).size !== policyIds.length) throw new Error("RoM surface-quality policy IDs must be unique."); const results = []; for (const policyId of policyIds) { if (dungeonId === "barren-caves" && policyId === BARREN_POLICY_ID) results.push(await applyBarrenFloorPolicy(document, sourceReport)); else if (dungeonId === "hoto" && policyId === HOTO_POLICY_ID) results.push(await applyHotoDetailPolicy(document, sourceReport)); else throw new Error(`Unknown or mismatched RoM surface-quality policy ${policyId} for ${dungeonId}.`); } return { schemaVersion: 1, dungeonId, status: policyIds.length ? "applied" : "no-policy", policies: results }; } export const runewakerSurfaceQualityPolicyIds = Object.freeze({ barrenCaves: BARREN_POLICY_ID, hoto: HOTO_POLICY_ID });