Files
healer-man/scripts/runewaker-pipeline/lib/rom-surface-quality-policies.mjs
T
2026-10-06 22:25:34 -04:00

747 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 });