Files
healer-man/scripts/runewaker-pipeline/lib/origin-native-material-provenance.mjs
T
2026-10-06 22:25:34 -04:00

137 lines
6.5 KiB
JavaScript

const SHA256 = /^[a-f0-9]{64}$/i;
function resourceKey(value) {
return String(value ?? "").replaceAll("/", "\\").toLowerCase();
}
function requireObject(value, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${label} must be an object.`);
}
return value;
}
export function buildOriginNativeMaterialProvenance({ metadata, assembly, semantics, conversionReports }) {
requireObject(metadata, "Import metadata");
requireObject(assembly, "Assembly evidence");
requireObject(semantics, "Native semantics report");
if (!Array.isArray(assembly.stagedOverrides) || assembly.stagedOverrides.length < 1) {
throw new Error("Assembly evidence must contain one or more staged overrides.");
}
if (!Array.isArray(assembly.resources)) {
throw new Error("Assembly evidence is missing its authoritative source resource rows.");
}
if (!Array.isArray(semantics.khrMaterialsUnlit) || !Array.isArray(semantics.nativeAdditiveMaterials)) {
throw new Error("Native semantics report is missing its verified unlit/additive material lists.");
}
if (!SHA256.test(assembly.sourceWdbSha256 ?? "") || !SHA256.test(assembly.assembledVisual?.sha256 ?? "")) {
throw new Error("Assembly evidence is missing a valid source WDB or assembled-source SHA-256.");
}
if (assembly.sourceWdbReportSha256 != null && !SHA256.test(assembly.sourceWdbReportSha256)) {
throw new Error("Assembly evidence has an invalid source WDB report SHA-256.");
}
const reportEntries = conversionReports instanceof Map
? conversionReports
: new Map(Object.entries(conversionReports ?? {}).map(([key, value]) => [resourceKey(key), value]));
const nativeAlphaResources = [];
const metadataResources = [];
const seenResources = new Set();
const authoritativeHashes = new Map();
for (const override of assembly.stagedOverrides) {
const key = resourceKey(override.resource);
if (!key || seenResources.has(key)) throw new Error(`Staged override resource is missing or duplicated: ${override.resource}.`);
seenResources.add(key);
const sourceRows = assembly.resources.filter((row) => resourceKey(row.resource) === key);
if (sourceRows.length !== 1) throw new Error(`Expected one authoritative assembly resource row for ${override.resource}; found ${sourceRows.length}.`);
const sourceRow = sourceRows[0];
if (!SHA256.test(sourceRow.sourceSha256 ?? "")) throw new Error(`Invalid assembly source SHA-256 for ${override.resource}.`);
if (Number(sourceRow.placementCount) !== Number(override.placements)) {
throw new Error(`Placement count mismatch for ${override.resource}.`);
}
const conversion = reportEntries.get(key);
const nativeAlpha = conversion?.visual?.materials?.nativeAlpha;
if (!nativeAlpha || !Array.isArray(nativeAlpha.materials)) {
throw new Error(`Missing native alpha material evidence for ${override.resource}.`);
}
const sourceSha256 = nativeAlpha.sourceResourceSha256;
if (!SHA256.test(sourceSha256 ?? "")) {
throw new Error(`Invalid native source hash for ${override.resource}.`);
}
if (resourceKey(nativeAlpha.sourceModel) !== key) {
throw new Error(`Source model identity mismatch for ${override.resource}: ${nativeAlpha.sourceModel}.`);
}
if (resourceKey(conversion.source?.model) !== key || conversion.source?.resourceSha256 !== sourceSha256) {
throw new Error(`Conversion source provenance mismatch for ${override.resource}.`);
}
if (sourceRow.sourceSha256 !== sourceSha256) {
throw new Error(`Conversion source SHA-256 does not match the authoritative assembly row for ${override.resource}.`);
}
if (nativeAlpha.materials.some((material) =>
resourceKey(material.sourceModel) !== key || material.sourceResourceSha256 !== sourceSha256)) {
throw new Error(`Material-level native identity does not match ${override.resource}.`);
}
authoritativeHashes.set(key, sourceSha256);
nativeAlphaResources.push({
resource: override.resource,
placements: override.placements,
sourceResourceSha256: sourceSha256,
nativeAlpha,
});
metadataResources.push({
resource: override.resource,
placements: override.placements,
sourceResourceSha256: sourceSha256,
});
}
for (const semanticMaterial of [...semantics.khrMaterialsUnlit, ...semantics.nativeAdditiveMaterials]) {
const key = resourceKey(semanticMaterial.sourceModel);
if (!key || !authoritativeHashes.has(key) || semanticMaterial.sourceResourceSha256 !== authoritativeHashes.get(key)) {
throw new Error(`Unlit/additive material has no exact staged source identity: ${semanticMaterial.sourceModel ?? "<missing>"}.`);
}
}
const assemblyProvenance = {
sourceWdbSha256: assembly.sourceWdbSha256,
sourceWdbReportSha256: assembly.sourceWdbReportSha256 ?? null,
assembledSourceSha256: assembly.assembledVisual.sha256,
staticPlacements: assembly.staticPlacementCount,
uniqueResources: assembly.uniqueResourceCount,
dynamicPlacements: assembly.dynamicPlacementCount,
stagedOverrideCount: assembly.stagedOverrides.length,
primaryResource: assembly.primaryResource,
primarySourceSha256: assembly.primarySourceSha256,
};
const alphaReport = {
schemaVersion: 2,
source: "native material conversion reports joined by exact ROS resource identity and source SHA-256",
assembly: assemblyProvenance,
resources: nativeAlphaResources,
};
const semanticsReport = {
schemaVersion: 1,
source: "native material semantics annotated before gltf-transform optimization",
assembly: assemblyProvenance,
materialCounts: semantics.materialCounts ?? null,
sharedMapLightingPair: semantics.sharedMapLightingPair ?? null,
khrMaterialsUnlit: semantics.khrMaterialsUnlit,
nativeAdditiveMaterials: semantics.nativeAdditiveMaterials,
limitation: "This report records source-proven material semantics for the listed staged overrides; it does not certify every surface as visually correct.",
};
const updatedMetadata = structuredClone(metadata);
updatedMetadata.nativeMaterialAlpha = {
report: "native-material-alpha-report.json",
resources: metadataResources,
};
updatedMetadata.nativeMaterialSemantics = {
report: "native-material-semantics-report.json",
stagedOverrideCount: assembly.stagedOverrides.length,
additiveMaterialCount: semantics.nativeAdditiveMaterials.length,
unlitMaterialCount: semantics.khrMaterialsUnlit.length,
assembledSourceSha256: assembly.assembledVisual.sha256,
};
return { metadata: updatedMetadata, alphaReport, semanticsReport };
}