feat(web): finish RE 5000PBR lesson video and fix non-ASCII GLB import
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

- 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>
This commit is contained in:
mes123456
2026-11-12 21:49:24 +08:00
parent a078648297
commit d5dd647881
276 changed files with 9632 additions and 314 deletions

View File

@@ -268,3 +268,68 @@ test("M10-07 keeps texture-fed Mix blocked with a visible issue", () => {
assert.equal(report.status, "BLOCKED");
assert.ok(report.issues.some((entry) => entry.message.includes("Mix color inputs")));
});
test("RE 5000PBR graph collapses REROUTE, ignores FRAME, keeps DISPLACEMENT/HEIGHT and applies Value-driven Mapping", () => {
const report = compiler.compileMaterialGraph(material({
nodes: [
{ id: "uv", type: "TEX_COORD", name: "UV" },
{ id: "mapping", type: "MAPPING", name: "Mapping" },
{ id: "reroute", type: "REROUTE", name: "Reroute" },
{ id: "frame", type: "FRAME", name: "Frame" },
{ id: "value", type: "VALUE", name: "Value", defaultValue: [2] },
{ id: "base", type: "IMAGE_TEXTURE", name: "Base", imageId: "image:Base" },
{ id: "rough", type: "IMAGE_TEXTURE", name: "Rough", imageId: "image:Rough" },
{ id: "height", type: "IMAGE_TEXTURE", name: "Height", imageId: "image:Height" },
{ id: "displacement", type: "DISPLACEMENT", name: "Displacement" },
{ id: "principled", type: "PRINCIPLED", name: "Principled" },
{ id: "output", type: "OUTPUT", name: "Output" },
],
links: [
{ fromNodeId: "uv", fromSocket: "UV", toNodeId: "mapping", toSocket: "Vector" },
{ fromNodeId: "value", fromSocket: "Value", toNodeId: "mapping", toSocket: "Scale" },
{ fromNodeId: "mapping", fromSocket: "Vector", toNodeId: "reroute", toSocket: "Input" },
{ fromNodeId: "reroute", fromSocket: "Output", toNodeId: "base", toSocket: "Vector" },
{ fromNodeId: "reroute", fromSocket: "Output", toNodeId: "rough", toSocket: "Vector" },
{ fromNodeId: "reroute", fromSocket: "Output", toNodeId: "height", toSocket: "Vector" },
{ fromNodeId: "base", fromSocket: "Color", toNodeId: "principled", toSocket: "Base Color" },
{ fromNodeId: "rough", fromSocket: "Color", toNodeId: "principled", toSocket: "Roughness" },
{ fromNodeId: "height", fromSocket: "Color", toNodeId: "displacement", toSocket: "Height" },
{ fromNodeId: "displacement", fromSocket: "Displacement", toNodeId: "output", toSocket: "Displacement" },
{ fromNodeId: "principled", fromSocket: "BSDF", toNodeId: "output", toSocket: "Surface" },
],
}), { imageIds: new Set(["image:Base", "image:Rough", "image:Height"]) });
assert.equal(report.status, "COMPILED");
assert.ok(report.compiledNodeTypes.every((type) => !["REROUTE", "FRAME"].includes(type)));
assert.ok(report.compiledNodeTypes.includes("DISPLACEMENT"));
const base = report.textureBindings.find((binding) => binding.usage === "BASE_COLOR");
assert.deepEqual(base.uvTransform?.scale, [2, 2]);
assert.ok(report.textureBindings.some((binding) => binding.usage === "ROUGHNESS"));
const height = report.textureBindings.find((binding) => binding.usage === "HEIGHT");
assert.equal(height.imageId, "image:Height");
assert.equal(height.scale, 0.01);
assert.equal(height.midlevel, 0.5);
assert.deepEqual(height.uvTransform?.scale, [2, 2]);
assert.equal(report.material.displacementImageId, "image:Height");
assert.equal(report.material.displacementScale, 0.01);
assert.equal(report.material.displacementMidlevel, 0.5);
assert.equal(report.material.roughness, 1);
});
test("RE 5000PBR roughness/metallic can be read through Separate Color", () => {
const report = compiler.compileMaterialGraph(material({
nodes: [
{ id: "image", type: "IMAGE_TEXTURE", name: "Mask", imageId: "image:Mask" },
{ id: "separate", type: "SEPARATE_COLOR", name: "Separate" },
{ id: "principled", type: "PRINCIPLED", name: "Principled" },
{ id: "output", type: "OUTPUT", name: "Output" },
],
links: [
{ fromNodeId: "image", fromSocket: "Color", toNodeId: "separate", toSocket: "Color" },
{ fromNodeId: "separate", fromSocket: "Blue", toNodeId: "principled", toSocket: "Metallic" },
{ fromNodeId: "principled", fromSocket: "BSDF", toNodeId: "output", toSocket: "Surface" },
],
}), { imageIds: new Set(["image:Mask"]) });
assert.equal(report.status, "COMPILED");
assert.ok(report.textureBindings.some((binding) => binding.usage === "METALLIC" && binding.imageId === "image:Mask"));
assert.equal(report.material.metallic, 1);
});