Advance M8-M11 parity workflows
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

This commit is contained in:
mes123456
2026-08-17 04:37:07 -04:00
parent 7c16b279ae
commit 0fe8d2bb56
324 changed files with 31920 additions and 863 deletions

View File

@@ -87,8 +87,8 @@ test("migrates IndexedDB metadata and creates a validated OPFS project layout",
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
worker.postMessage({ requestId: infoId, command: { type: "info" } });
}));
expect(result.schemaVersion).toBe(6);
expect(result.stores).toEqual(expect.arrayContaining(["project", "asset", "snapshot", "operation_log", "operation_quarantine", "setting", "lod_manifest", "simulation_manifest", "migration"]));
expect(result.schemaVersion).toBe(7);
expect(result.stores).toEqual(expect.arrayContaining(["project", "asset", "snapshot", "operation_log", "operation_quarantine", "setting", "lod_manifest", "simulation_manifest", "simulation_quarantine", "migration"]));
expect(result.projectPath).toBe("projects/layout-e2e/scene.blend");
});
@@ -454,8 +454,8 @@ test("renders a bounded previous/next N-016 Grease Pencil onion-skin preview", a
await page.goto("/");
const result = await page.evaluate(async () => {
const { createGreasePencilObject } = await import("/src/three-adapter/grease-pencil.ts");
const point = (x: number) => ({ position: [x, 0, 0] as [number, number, number], radius: 0.1, opacity: 1, vertexColor: [0.2, 0.4, 0.8, 1] as [number, number, number, number] });
const drawing = (id: string, x: number) => ({ id, strokeCount: 1, pointCount: 2, strokes: [{ cyclic: false, pointCount: 2, materialIndex: 0, points: [point(x), point(x + 1)] }] });
const point = (drawingId: string, x: number, index: number) => ({ id: `grease-pencil-point:onion:${drawingId}:${index}`, position: [x, 0, 0] as [number, number, number], radius: 0.1, opacity: 1, vertexColor: [0.2, 0.4, 0.8, 1] as [number, number, number, number] });
const drawing = (id: string, x: number) => ({ id, strokeCount: 1, pointCount: 2, strokes: [{ id: `grease-pencil-stroke:onion:${id}`, cyclic: false, pointCount: 2, materialIndex: 0, points: [point(id, x, 0), point(id, x + 1, 1)] }] });
const object = createGreasePencilObject({
id: "grease-pencil:onion",
name: "Onion",
@@ -464,7 +464,8 @@ test("renders a bounded previous/next N-016 Grease Pencil onion-skin preview", a
frameCount: 3,
strokeCount: 3,
pointCount: 6,
layers: [{ id: "layer:1", name: "Lines", visible: true, locked: false, opacity: 1, onionSkinning: true, frames: [
activeLayerId: "grease-pencil-layer:onion:1",
layers: [{ id: "grease-pencil-layer:onion:1", name: "Lines", visible: true, locked: false, opacity: 1, onionSkinning: true, frames: [
{ frame: 1, drawing: drawing("drawing:1", -2) },
{ frame: 5, drawing: drawing("drawing:5", 0) },
{ frame: 9, drawing: drawing("drawing:9", 2) },
@@ -531,7 +532,7 @@ test("raycasts and highlights N-016 Grease Pencil points with stable drawing ide
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
worker.postMessage({});
}));
expect(result.hit).toEqual({ dataId: "grease-pencil:Viewport", layerId: "grease-pencil-layer:Viewport", frame: 1, strokeIndex: 0, pointIndex: 1 });
expect(result.hit).toEqual({ dataId: "grease-pencil:Viewport", layerId: "grease-pencil-layer:Viewport", frame: 1, drawingId: "grease-pencil-drawing:Viewport", strokeId: "grease-pencil-stroke:Viewport:0:0", pointId: "grease-pencil-point:Viewport:0:0:1", strokeIndex: 0, pointIndex: 1 });
expect(result.selectedColor).toEqual([expect.closeTo(1), expect.closeTo(0.38), expect.closeTo(0.08)]);
expect(result.preview).toEqual([[0.5, 2, -1], [0.5, 2, -1]]);
expect(result.restored).toEqual([[0, 0, -0], [0, 0, -0]]);
@@ -849,7 +850,7 @@ test("executes the bounded N-020 CPU compositor and preserves unsupported nodes
expect(result.cache).toEqual([false, true, false, true, true, 0.25, 2, 256]);
});
test("executes the N-020 Exposure and Invert chain read from a real Blender 5.2 graph", async ({ page }) => {
test("blocks the N-020 Main graph when it also preserves an unsupported Blender node", async ({ page }) => {
await page.goto("/");
const bytes = await import("node:fs").then((fs) => fs.readFileSync(compositorBlend));
const result = await page.evaluate(async (input) => {
@@ -860,19 +861,24 @@ test("executes the N-020 Exposure and Invert chain read from a real Blender 5.2
const opened = await client.openBlend(input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength));
const scene = opened.snapshot.scenes.find((candidate) => candidate.name === "CompositorScene");
if (!scene?.compositorGraph) throw new Error("Real compositor graph is missing");
const execution = executeCompositorGraph(scene.compositorGraph, new Map(), { width: 1, height: 1 });
const before = JSON.stringify(scene.compositorGraph);
let executionCode = "";
try { executeCompositorGraph(scene.compositorGraph, new Map(), { width: 1, height: 1 }); }
catch (error) { executionCode = (error as { code?: string }).code ?? String(error); }
return {
status: scene.compositorStatus,
pixel: Array.from(execution.composite.data),
evaluated: execution.evaluatedNodeIds.map((id) => scene.compositorGraph!.nodes.find((node) => node.id === id)?.name),
executionCode,
preserved: JSON.stringify(scene.compositorGraph) === before,
unsupported: scene.compositorGraph.nodes.find((node) => node.type === "UNSUPPORTED")?.blenderType,
gate: gateCompositorGraph(scene.compositorGraph, new Set()).issues[0]?.code,
};
}
finally { client.terminate(); }
}, new Uint8Array(bytes));
expect(result.status).toBe("AVAILABLE");
expect(result.pixel).toEqual([0.75, 0.5, 0, 0.75]);
expect(result.evaluated).toEqual(["WebConstantColor", "WebExposure", "WebInvert", "WebComposite"]);
expect(result.executionCode).toBe("COMPOSITOR_NODE_UNSUPPORTED");
expect(result.preserved).toBe(true);
expect(result.unsupported).toBe("CompositorNodeGlare");
expect(result.gate).toBe("COMPOSITOR_NODE_UNSUPPORTED");
});
@@ -1121,8 +1127,7 @@ for (const offscreen of [false, true]) test(`previews an N-015 Curve handle drag
const { PerspectiveCamera, Vector3 } = await import("/src/vendor/three/three.module.js");
const bounds = canvas.getBoundingClientRect();
const camera = new PerspectiveCamera(45, bounds.width / bounds.height, 0.01, 1000);
if (input.offscreen) camera.position.set(7 * Math.cos(0.55) * Math.cos(-Math.PI / 4), 7 * Math.cos(0.55) * Math.sin(-Math.PI / 4), 7 * Math.sin(0.55));
else camera.position.set(4.5, -4.5, 3.5);
camera.position.set(4.219781, -4.219781, 3.658811);
camera.lookAt(0, 0, 0);
camera.updateMatrixWorld(true);
camera.updateProjectionMatrix();
@@ -1383,7 +1388,15 @@ test("recovers NanoVDB paging from network, Worker and WebGPU device faults", as
expect(gpu.paging.pageCount).toBeGreaterThan(1);
expect(gpu.paging.residentPageCount).toBe(gpu.paging.pageCount);
expect(gpu.deviceLoss.recoveredGeneration).toBe(gpu.deviceLoss.firstGeneration + 1);
expect(gpu.deviceLoss.recoveredDemandWords).toEqual(gpu.demandPaging.expectedWords.slice(0, 2));
expect(gpu.deviceLoss.residentBeforeReplay).toEqual([]);
expect(gpu.deviceLoss.visiblePageIds).toEqual([0, 1, 2]);
expect(gpu.deviceLoss.replayedPageIds).toEqual([0, 1]);
expect(gpu.deviceLoss.skippedPageIds).toEqual([2]);
expect(gpu.deviceLoss.recoveredResidentPages).toEqual([0, 1]);
expect(gpu.deviceLoss.recoveredResidentPages).not.toContain(gpu.deviceLoss.oldNonVisiblePage);
expect(gpu.deviceLoss.recoveredPageTable.slice(0, 2)).toEqual([0, 1]);
expect(gpu.deviceLoss.recoveredPageTable.slice(2).every((slot: number) => slot === 0xffffffff)).toBe(true);
expect(gpu.deviceLoss.recoveredDemandWords).toEqual(gpu.deviceLoss.expectedDemandWords);
expect(gpu.samplesStable).toBe(true);
const interrupted = await page.evaluate(() => new Promise<any>((resolve, reject) => {
@@ -1549,7 +1562,7 @@ test("retains bounded snapshots and returns a validated operation replay plan",
await client.appendOperation("op-r5", projectId, 5, { type: "setFrame", frame: 5 });
await client.appendOperation("op-r4", projectId, 4, { type: "setFrame", frame: 4 });
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open("blender-web-metadata", 6);
const request = indexedDB.open("blender-web-metadata", 7);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
@@ -1559,18 +1572,50 @@ test("retains bounded snapshots and returns a validated operation replay plan",
db.close();
const snapshots = await client.listSnapshots(projectId);
const latest = await client.readSnapshot(projectId, snapshots.snapshots[0].revision);
const snapshotRows = await new Promise<Array<{ backend?: string; buffer?: ArrayBuffer }>>((resolve, reject) => {
const request = indexedDB.open("blender-web-metadata", 7);
request.onsuccess = () => {
const snapshotDb = request.result;
const transaction = snapshotDb.transaction("snapshot", "readonly");
const rows = transaction.objectStore("snapshot").getAll();
rows.onsuccess = () => resolve((rows.result as Array<{ projectId: string; backend?: string; buffer?: ArrayBuffer }>).filter((row) => row.projectId === projectId));
rows.onerror = () => reject(rows.error);
transaction.oncomplete = () => snapshotDb.close();
};
request.onerror = () => reject(request.error);
});
const root = await navigator.storage.getDirectory();
const projects = await root.getDirectoryHandle("projects");
const project = await projects.getDirectoryHandle(projectId);
const snapshotDirectory = await project.getDirectoryHandle("snapshots");
const snapshotFiles: string[] = [];
for await (const [name, handle] of (snapshotDirectory as unknown as { entries: () => AsyncIterable<[string, FileSystemHandle]> }).entries()) {
if (handle.kind === "file") snapshotFiles.push(name);
}
const replay = await client.listOperations(projectId, 3);
const pruned = await client.pruneOperations(projectId, 4);
client.terminate();
return {
snapshotRevisions: snapshots.snapshots.map((item) => item.revision),
latest: Array.from(new Uint8Array(latest.buffer)),
snapshotBackends: snapshotRows.map((row) => row.backend).sort(),
inlineSnapshotBuffers: snapshotRows.filter((row) => row.buffer).length,
snapshotFiles: snapshotFiles.sort(),
replayRevisions: replay.operations.map((item) => item.revision),
quarantined: replay.quarantined,
pruned: pruned.removed,
};
});
expect(result).toEqual({ snapshotRevisions: [4, 3], latest: [4, 5], replayRevisions: [4, 5], quarantined: 1, pruned: 2 });
expect(result).toEqual({
snapshotRevisions: [4, 3],
latest: [4, 5],
snapshotBackends: ["opfs", "opfs"],
inlineSnapshotBuffers: 0,
snapshotFiles: ["3.blend", "4.blend"],
replayRevisions: [4, 5],
quarantined: 1,
pruned: 2,
});
});
test("reports quota exhaustion without replacing the committed project", async ({ page }) => {
@@ -1654,16 +1699,26 @@ test("persists and revalidates content-addressed Simulation caches across Worker
payloadBytes.set(new Uint8Array(frameTwo), frameOne.byteLength);
const payload = payloadBytes.buffer;
const fixedHash = await digest(Uint8Array.from([1, 2, 3]).buffer);
const manifest = {
schemaVersion: 1 as const,
const binding = {
graphId: "geometry-node-tree:simulation-e2e",
graphHash: fixedHash,
sourceBlendSha256: await digest(sourceBlend),
sourceRevision: 1,
inputHash: await digest(Uint8Array.from([4, 5, 6]).buffer),
cacheSha256: await digest(payload),
blenderVersion: "5.2.0",
frameStart: 1,
frameEnd: 2,
};
const revisionHash = await digest(new TextEncoder().encode(JSON.stringify([
"blender-web-simulation-cache-revision-v2", binding.graphId, binding.graphHash,
binding.sourceBlendSha256, String(binding.sourceRevision), binding.inputHash,
binding.blenderVersion, String(binding.frameStart), String(binding.frameEnd),
])).buffer);
const manifest = {
schemaVersion: 2 as const,
...binding,
revisionHash,
cacheSha256: await digest(payload),
byteLength: payload.byteLength,
frames: [
{ frame: 1, byteOffset: 0, byteLength: frameOne.byteLength, sha256: await digest(frameOne) },
@@ -1683,7 +1738,7 @@ test("persists and revalidates content-addressed Simulation caches across Worker
const scene = { schemaVersion: 1 as const, revision: 1, sceneId: "scene:Cache", source: { kind: "mock" as const }, coordinateSystem: { upAxis: "Z" as const, forwardAxis: "-Y" as const, handedness: "RIGHT" as const, unitSystem: 0, unitScale: 1 }, activeObjectId: "object:CacheTarget", frame: { current: 1, start: 1, end: 2 }, nodes: [{ id: "object:CacheTarget", name: "CacheTarget", type: "MESH" as const, parentId: null, dataId: "mesh:CacheTarget", visible: true, selectable: true, localMatrix: identity, worldMatrix: identity, transform: { translation: [0, 0, 0] as [number, number, number], rotationEuler: [0, 0, 0] as [number, number, number], scale: [1, 1, 1] as [number, number, number], rotationMode: 1 } }], meshes: [], materials: [], cameras: [], lights: [], worlds: [], images: [], animations: [], collections: [], scenes: [] };
let publishedFrame = 0;
let publishedTranslation: number[] = [];
const playback = new BrowserTransformCachePlaybackSession(scene, { frameStart: 1, frameEnd: 2, readFrame: async (frame) => (await restarted.readSimulationCacheFrame(projectId, stored.cacheKey, frame)).data }, (preview) => { publishedFrame = preview.frame.current; publishedTranslation = preview.nodes[0].transform.translation; });
const playback = new BrowserTransformCachePlaybackSession(scene, { frameStart: 1, frameEnd: 2, readFrame: async (frame, signal) => (await restarted.readSimulationCacheFrame(projectId, stored.cacheKey, frame, signal)).data }, (preview) => { publishedFrame = preview.frame.current; publishedTranslation = preview.nodes[0].transform.translation; });
await playback.seek(2);
let missingFrameCode = "";
try {
@@ -1715,7 +1770,7 @@ test("persists and revalidates content-addressed Simulation caches across Worker
corruptCode,
};
});
expect(result.cacheKey).toMatch(/^[a-f0-9]{16}-[a-f0-9]{16}-[a-f0-9]{16}-1-2$/);
expect(result.cacheKey).toMatch(/^sim2-[a-f0-9]{64}$/);
expect(result.path).toMatch(/^projects\/simulation-e2e-[0-9]+\/assets\/sha256\/[a-f0-9]{2}\/[a-f0-9]{64}$/);
expect(result.listed).toContain(result.cacheKey);
expect(result.bytes).toBe(176);