forked from phenom/healer-man
113 lines
7.1 KiB
JavaScript
113 lines
7.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { constants } from "node:fs";
|
|
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
const stage = "artifacts/playtest/rom-deep-review/uv-stretch-review";
|
|
const manifestFile = process.argv[2] ?? `${stage}/final-surface-policy-stage-manifest.json`;
|
|
const manifest = JSON.parse(await readFile(manifestFile, "utf8"));
|
|
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
|
|
const json = async file => JSON.parse(await readFile(file, "utf8"));
|
|
assert.deepEqual(manifest.dungeons.map(row => row.dungeonId).sort(), ["barren-caves", "hoto"]);
|
|
const copies = [], edits = [], reviews = [];
|
|
for (const row of manifest.dungeons) {
|
|
const id = row.dungeonId;
|
|
const canonical = await readFile(row.canonical.file);
|
|
const optimized = await readFile(row.optimized.file);
|
|
const shipping = await readFile(row.shipping.file);
|
|
assert.equal(digest(canonical), row.canonical.oldSha256);
|
|
assert.equal(digest(optimized), row.optimized.oldSha256);
|
|
assert.equal(digest(shipping), row.shipping.oldSha256);
|
|
// Historical converted copies can predate later shipping compression. Guard
|
|
// each baseline independently; promotion gives both roles one reviewed output.
|
|
for (const asset of [row.canonical, row.optimized]) {
|
|
const bytes = await readFile(asset.candidate);
|
|
assert.equal(digest(bytes), asset.candidateSha256);
|
|
assert.equal(bytes.length, asset.candidateBytes);
|
|
}
|
|
const layout = await json(row.sourceLayoutProof);
|
|
assert.equal(digest(await readFile(layout.beforeFile)), layout.beforeSha256);
|
|
assert.equal(layout.afterSha256, row.canonical.candidateSha256);
|
|
assert.equal(layout.hierarchyAndTransformsPreserved, true);
|
|
assert.equal(layout.orderedSourceTriangleCornersPreserved, true);
|
|
assert.equal(layout.allSourceAttributesExceptAdaptedUv1Preserved, true);
|
|
assert.equal(layout.originalSourceImagesPreserved, true);
|
|
const policy = await json(row.policyReport);
|
|
assert.equal(policy.sourceCanonicalSha256, row.canonical.oldSha256);
|
|
assert.equal(policy.outputSha256, row.canonical.candidateSha256);
|
|
assert.equal(digest(await readFile(policy.sourceRos)), policy.sourceResourceSha256);
|
|
assert.equal(policy.policyReport.surfaceQuality.status, "applied");
|
|
const quality = await json(row.textureQualityReport);
|
|
assert.deepEqual(quality.errors, []);
|
|
assert.equal(quality.policy.preservesSourceDimensionsAndFullMipChains, true);
|
|
const codec = (await json(row.codecProof)).results.find(result => result.id === id);
|
|
assert.ok(codec && codec.textures.length >= (id === "hoto" ? 4 : 6));
|
|
assert.equal(codec.glbSha256, row.optimized.candidateSha256);
|
|
assert.ok(codec.textures.every(texture => texture.maeAlpha === 0));
|
|
const visual = await json(row.visualProof);
|
|
assert.equal(visual.status, "passed");
|
|
const views = visual.results.filter(result => result.dungeonId === id);
|
|
assert.ok(views.filter(result => result.stage === "candidate").length >= 2);
|
|
for (const view of views) {
|
|
assert.deepEqual(view.errors, []);
|
|
assert.equal(view.glbSha256, view.stage === "candidate" ? row.optimized.candidateSha256 : row.shipping.oldSha256);
|
|
assert.ok((await readFile(view.screenshot)).length > 1000);
|
|
if (view.stage === "candidate") assert.ok(view.candidateResponses.some(response =>
|
|
response.status === 200 && response.bodySha256 === row.optimized.candidateSha256));
|
|
}
|
|
const review = { manifest: manifestFile, policyReport: row.policyReport, sourceLayoutProof: row.sourceLayoutProof,
|
|
textureQualityReport: row.textureQualityReport, codecProof: row.codecProof, visualProof: row.visualProof,
|
|
canonicalBefore: row.canonical.oldSha256, canonicalAfter: row.canonical.candidateSha256,
|
|
shippingBefore: row.shipping.oldSha256, shippingAfter: row.optimized.candidateSha256 };
|
|
reviews.push({ dungeonId: id, ...review });
|
|
copies.push({ file: row.canonical.file, candidate: row.canonical.candidate },
|
|
{ file: row.optimized.file, candidate: row.optimized.candidate },
|
|
{ file: row.shipping.file, candidate: row.optimized.candidate });
|
|
for (const directory of [`src/assets/game/dungeons/${id}`, `runewaker-export-work/${id}/converted`]) {
|
|
for (const name of ["import-metadata.json", "optimization-report.json", "conversion-report.json"]) {
|
|
const file = `${directory}/${name}`;
|
|
const data = await json(file).catch(error => {
|
|
if (error.code === "ENOENT" && directory.startsWith("runewaker-export-work") && name === "import-metadata.json") return null;
|
|
throw error;
|
|
});
|
|
if (!data) continue;
|
|
if (name === "import-metadata.json") {
|
|
const asset = data.assets.find(asset => asset.role === "visual");
|
|
assert.equal(asset.sha256, row.shipping.oldSha256);
|
|
asset.sha256 = row.optimized.candidateSha256;
|
|
asset.size = row.optimized.candidateBytes;
|
|
if (data.nativeMaterialSemantics?.assembledSourceSha256) data.nativeMaterialSemantics.assembledSourceSha256 = row.canonical.candidateSha256;
|
|
data.continuationReview = { ...data.continuationReview, surfaceQuality: review };
|
|
} else if (name === "optimization-report.json") {
|
|
if (directory.startsWith("src/")) assert.equal(data.optimized.checksum, row.shipping.oldSha256);
|
|
data.source = { ...data.source, checksum: row.canonical.candidateSha256, size: row.canonical.candidateBytes };
|
|
data.optimized = { ...data.optimized, checksum: row.optimized.candidateSha256, size: row.optimized.candidateBytes };
|
|
data.compression = "meshopt+ktx2";
|
|
data.textureQuality = quality;
|
|
data.supersededVisualUvAdaptations ??= data.visualUvAdaptations;
|
|
data.visualUvAdaptations = policy.policyReport;
|
|
data.commands = [
|
|
"applyRunewakerVisualUvAdaptationsToFile with the source-pinned visualSurfaceQualityPolicies recipe opt-in",
|
|
"gltf-transform optimize --compress meshopt --meshopt-level high --instance true --instance-min 3 --simplify false --texture-compress false --join false --prune false --prune-attributes false",
|
|
"node scripts/runewaker-pipeline/compress-environment-textures.mjs <optimized.glb> --concurrency 1 --jobs 2",
|
|
];
|
|
data.surfaceQuality = review;
|
|
} else data.continuationIntegrityProof = { ...data.continuationIntegrityProof, surfaceQuality: review };
|
|
edits.push({ file, data });
|
|
}
|
|
}
|
|
}
|
|
// Validate every current/candidate identity before the first mutation. Exclusive
|
|
// backups preserve the complete previous assets and prevent accidental replays.
|
|
const backup = `${stage}/pre-surface-promotion-backup`;
|
|
for (const row of [...copies, ...edits]) {
|
|
const target = path.join(backup, row.file);
|
|
await mkdir(path.dirname(target), { recursive: true });
|
|
await copyFile(row.file, target, constants.COPYFILE_EXCL);
|
|
}
|
|
for (const row of copies) await copyFile(row.candidate, row.file);
|
|
for (const row of edits) await writeFile(row.file, `${JSON.stringify(row.data, null, 2)}\n`);
|
|
await writeFile(`${stage}/surface-promotion-proof.json`, `${JSON.stringify({ manifest: manifestFile, backup, reviews, copies }, null, 2)}\n`);
|
|
console.log(JSON.stringify({ promoted: reviews.map(row => row.dungeonId), backup }));
|