Files
healer-man/scripts/runewaker-pipeline/promote-native-water-screen-effects.mjs
T
2026-10-06 22:25:34 -04:00

66 lines
3.8 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/water-screen-effects";
const proof = JSON.parse(await readFile(`${stage}/proof.json`, "utf8"));
const browser = JSON.parse(await readFile(`${stage}/browser-proof.json`, "utf8"));
assert.ok(browser.some(row => row.staged && !row.errors.length && row.water.some(layer => layer.screenEffectsReady === 1)));
const sha = bytes => createHash("sha256").update(bytes).digest("hex");
const writeJson = (file, data) => writeFile(file, `${JSON.stringify(data, null, 2)}\n`);
const canonical = proof.files.find(row => row.kind === "canonical");
const shipping = proof.files.find(row => row.kind === "shipping");
const backup = `${stage}/pre-promotion-backup`;
const directories = ["src/assets/game/dungeons/the-origin", "runewaker-export-work/the-origin/converted"];
const edits = [];
for (const directory of directories) {
for (const name of ["import-metadata.json", "optimization-report.json", "conversion-report.json"]) {
const file = `${directory}/${name}`;
const bytes = await readFile(file);
const data = JSON.parse(bytes);
if (name === "import-metadata.json") {
const visual = data.assets.find(row => row.role === "visual");
assert.equal(visual.sha256, shipping.previousSha256);
visual.sha256 = shipping.sha256;
visual.size = shipping.bytes;
data.nativeMaterialSemantics.assembledSourceSha256 = canonical.sha256;
data.continuationReview.waterScreenEffects = proof;
} else if (name === "optimization-report.json") {
assert.equal(data.source.checksum, canonical.previousSha256);
assert.equal(data.optimized.checksum, shipping.previousSha256);
data.source.checksum = canonical.sha256;
data.source.size = canonical.bytes;
data.optimized.checksum = shipping.sha256;
data.optimized.size = shipping.bytes;
data.waterScreenEffects = proof;
} else {
data.continuationIntegrityProof.waterScreenEffects = proof;
}
edits.push({ file, bytes, data });
}
}
const nativeFile = "src/assets/game/dungeons/the-origin/native-water-report.json";
const nativeBytes = await readFile(nativeFile);
const native = JSON.parse(nativeBytes);
native.waterScreenEffects = proof;
native.nativeMaterialSemantics.viewportMaterial = "Source-enabled browser reflection/refraction with native split planes, .025 bump perturbation and Fresnel .02037/.97963 coefficients. Three.js lighting replaces native SH; reflect-class filtering is unsupported.";
native.limitations = native.limitations.filter(row => !row.includes("projected screen reflection/refraction paths"));
native.limitations.push("Browser screen effects use up to four 512px plane pairs per asset and at most ten two-pass captures per second. Additional planes and below-plane views retain the lit animated fallback. Native SH and reflect-class filtering remain unsupported.");
edits.push({ file: nativeFile, bytes: nativeBytes, data: native });
// Verify every expected old/new identity before any canonical mutation.
for (const row of proof.files) {
assert.equal(sha(await readFile(row.file)), row.previousSha256);
assert.equal(sha(await readFile(row.staged)), row.sha256);
}
for (const row of [...proof.files, ...edits]) {
const destination = path.join(backup, row.file);
await mkdir(path.dirname(destination), { recursive: true });
await copyFile(row.file, destination, constants.COPYFILE_EXCL);
}
for (const row of proof.files) await copyFile(row.staged, row.file);
for (const row of edits) await writeJson(row.file, row.data);
console.log(JSON.stringify({ promoted: proof.files.map(row => ({ file: row.file, sha256: row.sha256 })), backup }));