125 lines
5.9 KiB
JavaScript
125 lines
5.9 KiB
JavaScript
const normalizeResource = (value) => String(value ?? "")
|
|
.replaceAll("\\", "/")
|
|
.replace(/^\/+/, "")
|
|
.toLowerCase();
|
|
|
|
/**
|
|
* The Windows native audit tool currently emits its input/output path strings
|
|
* directly into JSON. Forward slashes are accepted by the filesystem and
|
|
* prevent unescaped backslashes from making that report invalid JSON.
|
|
*/
|
|
export function buildNativeCollisionAuditArguments({ resourceRoot, sourceModel, reportPath }) {
|
|
const normalizePath = (value, label) => {
|
|
const normalized = String(value ?? "").replaceAll("\\", "/");
|
|
if (!normalized) throw new Error(`${label} is required for native collision audit.`);
|
|
return normalized;
|
|
};
|
|
return [
|
|
normalizePath(resourceRoot, "Resource root"),
|
|
normalizePath(sourceModel, "Source ROS path"),
|
|
normalizePath(reportPath, "Native report path"),
|
|
"respect-options",
|
|
];
|
|
}
|
|
|
|
function requireHash(value, label) {
|
|
const hash = String(value ?? "").toLowerCase();
|
|
if (!/^[a-f0-9]{64}$/.test(hash)) throw new Error(`${label} must be a SHA-256 hash.`);
|
|
return hash;
|
|
}
|
|
|
|
function requireEqual(actual, expected, label) {
|
|
if (actual !== expected) throw new Error(`${label} does not match.`);
|
|
}
|
|
|
|
function samePlacement(actual, expected) {
|
|
if (!actual || !expected) return false;
|
|
if (String(actual.guid ?? "").toLowerCase() !== String(expected.guid ?? "").toLowerCase()) return false;
|
|
if (normalizeResource(actual.resource) !== normalizeResource(expected.resource)) return false;
|
|
for (const field of ["translation", "rotation", "scale"]) {
|
|
if (!Array.isArray(actual[field]) || !Array.isArray(expected[field]) || actual[field].length !== expected[field].length) return false;
|
|
for (let index = 0; index < actual[field].length; index += 1) {
|
|
if (Math.abs(Number(actual[field][index]) - Number(expected[field][index])) > 1e-5) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Verifies that native-core collision candidates are bound to one exact
|
|
* recipe, primary ROS/WDB source cache, import metadata, WDB placement, and
|
|
* strict engine-query geometry report before any candidate files are emitted.
|
|
*/
|
|
export function validateNativeCoreCandidateProvenance(input) {
|
|
const {
|
|
slug,
|
|
recipe,
|
|
recipeSha256,
|
|
importMetadata,
|
|
actualSourceModelSha256,
|
|
actualSourceWdbSha256,
|
|
wdbReport,
|
|
wdbReportSha256,
|
|
exportReport,
|
|
exportReportSha256,
|
|
conversionReportSha256,
|
|
nativeCollisionReport,
|
|
nativeCollisionReportSha256,
|
|
nativeCollisionObjSha256,
|
|
nativeCollisionFlagsSha256,
|
|
primaryPlacement,
|
|
} = input;
|
|
|
|
requireEqual(recipe?.slug, slug, "Native-core candidate recipe slug");
|
|
requireEqual(importMetadata?.dungeonId, slug, "Native-core import metadata dungeon ID");
|
|
|
|
const recipeModel = normalizeResource(recipe?.source?.model);
|
|
const recipeWdb = normalizeResource(recipe?.source?.wdb);
|
|
requireEqual(normalizeResource(importMetadata.source?.model), recipeModel, "Imported source ROS path");
|
|
requireEqual(normalizeResource(importMetadata.source?.wdb), recipeWdb, "Imported source WDB path");
|
|
|
|
const sourceModelSha256 = requireHash(actualSourceModelSha256, "Current source ROS SHA-256");
|
|
const sourceWdbSha256 = requireHash(actualSourceWdbSha256, "Current source WDB SHA-256");
|
|
requireEqual(requireHash(importMetadata.source?.modelSha256, "Imported source ROS SHA-256"), sourceModelSha256, "Imported source ROS hash");
|
|
requireEqual(requireHash(importMetadata.source?.wdbSha256, "Imported source WDB SHA-256"), sourceWdbSha256, "Imported source WDB hash");
|
|
|
|
const recipeHash = requireHash(recipeSha256, "Recipe file SHA-256");
|
|
const wdbReportHash = requireHash(wdbReportSha256, "WDB descriptor report SHA-256");
|
|
const exportReportHash = requireHash(exportReportSha256, "Native export report SHA-256");
|
|
const conversionHash = requireHash(conversionReportSha256, "Converted collision report SHA-256");
|
|
const nativeReportHash = requireHash(nativeCollisionReportSha256, "Native collision query report SHA-256");
|
|
const nativeObjHash = requireHash(nativeCollisionObjSha256, "Native collision query OBJ SHA-256");
|
|
const nativeFlagsHash = requireHash(nativeCollisionFlagsSha256, "Native collision query flags SHA-256");
|
|
const primaryDescriptor = wdbReport?.descriptors?.find((descriptor) => samePlacement(descriptor, primaryPlacement));
|
|
if (!primaryDescriptor || normalizeResource(primaryDescriptor.resource) !== recipeModel) {
|
|
throw new Error("Native-core primary WDB descriptor does not match the resolved placement and recipe ROS.");
|
|
}
|
|
requireEqual(normalizeResource(exportReport?.sourceModel), recipeModel, "Native exporter source ROS path");
|
|
|
|
requireEqual(normalizeResource(nativeCollisionReport?.sourceModel), recipeModel, "Native collision query source ROS path");
|
|
requireEqual(nativeCollisionReport?.collisionMode, "respect-options", "Native collision query mode");
|
|
requireEqual(nativeCollisionReport?.queryMasks?.ignoreEntityCollisionOption, false, "Native collision entity-option gate policy");
|
|
requireEqual(nativeCollisionReport?.renderMeshFallbackTriangleCount, 0, "Native collision render-mesh fallback count");
|
|
if (
|
|
!Number.isInteger(nativeCollisionReport?.nativeCollisionTriangleCount)
|
|
|| nativeCollisionReport.nativeCollisionTriangleCount <= 0
|
|
|| nativeCollisionReport.nativeCollisionTriangleCount !== nativeCollisionReport.enabledNativeCollisionTriangleCount
|
|
) {
|
|
throw new Error("Options-aware collision query count must equal the enabled serialized native triangle count.");
|
|
}
|
|
|
|
return Object.freeze({
|
|
recipeSha256: recipeHash,
|
|
sourceModelSha256,
|
|
sourceWdbSha256,
|
|
wdbReportSha256: wdbReportHash,
|
|
exportReportSha256: exportReportHash,
|
|
conversionReportSha256: conversionHash,
|
|
nativeCollisionReportSha256: nativeReportHash,
|
|
nativeCollisionObjSha256: nativeObjHash,
|
|
nativeCollisionFlagsSha256: nativeFlagsHash,
|
|
nativeEnabledTriangleCount: nativeCollisionReport.nativeCollisionTriangleCount,
|
|
primaryPlacementGuid: primaryDescriptor.guid,
|
|
});
|
|
}
|