Files
workinf_Blender_Wasm/tools/web/check-re-pbr-limits.mjs
mes123456 d5dd647881
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): finish RE 5000PBR lesson video and fix non-ASCII GLB import
- Add 11:07 Chinese-narrated lesson video, VTT, poster and 13 chapter screenshots.
- Regenerate 20-render WebGL2 matrix and 15 operation captures on RTX 4060.
- Fix WASM staging for non-ASCII import filenames (猴头.glb); rebuild web_engine.
- Harden lesson video recording pipeline; fix stale lesson-link E2E selector.
- Add RE PBR derived libraries, lesson page, evidence and status report.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-12 21:49:24 +08:00

100 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
/** Validate the RE 5000PBR limitation fixes: displacement, diagnostics, HQ, GPU and camera. */
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
const REPO = path.resolve(import.meta.dirname, "../..");
const LIMITS = path.join(REPO, "tests/golden/RE-PBR-LIMITS-001");
const RENDER = path.join(REPO, "tests/golden/RE-PBR-RENDER-001");
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const exists = (file, label) => assert.ok(fs.existsSync(file), `missing ${label}: ${path.relative(REPO, file)}`);
const displacement = readJson(path.join(LIMITS, "displacement/report.json"));
assert.equal(displacement.status, "PASS");
assert.equal(displacement.nativeBinding.usage, "HEIGHT");
assert.ok(Math.abs(displacement.nativeBinding.scale - 0.001) < 1e-6);
assert.ok(Math.abs(displacement.nativeBinding.midlevel - 0.5) < 1e-6);
assert.equal(displacement.subdivision.sourceTriangles, 968);
assert.equal(displacement.subdivision.renderTriangles, 61952);
for (const key of ["1x", "100x"]) {
const render = displacement.renders[key];
const file = path.join(REPO, render.path);
exists(file, `displacement ${key}`);
assert.equal(sha256(file), render.sha256, `displacement ${key} SHA`);
assert.ok(render.mean > 1 && render.mean < 254);
}
assert.notEqual(displacement.renders["1x"].sha256, displacement.renders["100x"].sha256);
assert.ok(displacement.meanDelta > 0.01, "displacement 1x/100x mean delta too small");
const diagnostics = readJson(path.join(LIMITS, "diagnostics/report.json"));
assert.equal(diagnostics.status, "PASS");
assert.equal(diagnostics.views.length, 3);
for (const view of diagnostics.views) {
const file = path.join(REPO, view.path);
exists(file, `diagnostic ${view.mode}`);
assert.equal(sha256(file), view.sha256, `diagnostic ${view.mode} SHA`);
assert.ok(view.mean > 1 && view.mean < 254);
}
assert.equal(diagnostics.exposure.result, "PASS");
assert.equal(new Set(diagnostics.views.map((view) => view.sha256)).size, 3, "diagnostic views must differ");
const hq = readJson(path.join(LIMITS, "hq/report.json"));
assert.equal(hq.status, "PASS");
exists(path.join(REPO, hq.library.path), "HQ library");
assert.equal(sha256(path.join(REPO, hq.library.path)), hq.library.sha256);
assert.equal(hq.budget.loaded, 5);
assert.equal(hq.budget.rejected, 0);
assert.ok(hq.budget.gpuDecodedBytes < 512 * 1024 * 1024, "HQ exceeds WebGL2 decoded budget");
exists(path.join(REPO, hq.render.path), "HQ render");
assert.equal(sha256(path.join(REPO, hq.render.path)), hq.render.sha256);
const gpu = readJson(path.join(LIMITS, "gpu/batch0-gpu-enable.json"));
assert.equal(gpu.status, "PASS");
assert.match(gpu.webgl.renderer, /NVIDIA|AMD|Intel|ANGLE/);
assert.doesNotMatch(gpu.webgl.renderer, /SwiftShader|llvmpipe|software/i);
const hardwareRender = gpu.render.hardware;
const hardwareFile = path.join(REPO, hardwareRender.path);
exists(hardwareFile, "GPU hardware render");
assert.equal(sha256(hardwareFile), hardwareRender.sha256);
assert.match(gpu.render.swiftshader.sha256, /^[a-f0-9]{64}$/);
assert.ok(gpu.render.meanStd.swiftshader.mean > 1 && gpu.render.meanStd.swiftshader.mean < 254);
const renderReport = readJson(path.join(RENDER, "report.json"));
assert.equal(renderReport.status, "PASS");
assert.equal(renderReport.renders.length, 20);
assert.equal(new Set(renderReport.renders.map((render) => render.sha256)).size, 20, "20 renders must be unique");
assert.match(renderReport.webgl.renderer, /NVIDIA|AMD|Intel|ANGLE/);
const last = "material-black_worn_wall_brick";
const scene = renderReport.renders.find((render) => render.name === `${last}-1024-scene`);
const viewport = renderReport.renders.find((render) => render.name === `${last}-1024-viewport`);
assert.ok(scene && viewport);
assert.notEqual(scene.sha256, viewport.sha256, "SCENE and COMPARISON renders must differ");
const reopen = renderReport.renders.find((render) => render.name.endsWith("-reopen-1024-scene"));
assert.ok(reopen);
assert.match(reopen.textureStatus, /^ready:([1-9]\d*):0:/);
const lessonHtml = path.join(REPO, "web/app/public/tutorial/pbr/render-lesson.html");
exists(lessonHtml, "lesson HTML");
const html = fs.readFileSync(lessonHtml, "utf8");
for (const required of ["7. 进阶", "位移与细分", "诊断视图", "高清 2048", "RTX 4060"]) {
assert.ok(html.includes(required), `lesson misses ${required}`);
}
for (const source of [...html.matchAll(/src="([^"]+)"/g)].map((match) => match[1])) {
if (!source.startsWith("/")) continue;
exists(path.join(REPO, "web/app/public", source.slice(1)), `lesson asset ${source}`);
}
console.log(JSON.stringify({
status: "PASS",
displacement: { scale: displacement.nativeBinding.scale, triangles: displacement.subdivision.renderTriangles, meanDelta: displacement.meanDelta },
diagnostics: diagnostics.views.length,
hq: { bytes: hq.library.bytes, gpuDecodedBytes: hq.budget.gpuDecodedBytes },
gpu: gpu.webgl.renderer,
renders: renderReport.renders.length,
uniqueRenders: 20,
reopen: reopen.textureStatus,
}, null, 2));