Files
workinf_Blender_Wasm/web/tests/unit/physics-cache-family.test.mjs
mes123456 0fe8d2bb56
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
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

122 lines
5.0 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "physics-cache-family-unit-"));
function transpile(sourceName, outputName, replacements = []) {
const sourcePath = path.join(root, "web/protocol", sourceName);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const output = replacements.reduce((source, [from, to]) => source.replaceAll(from, to), transpiled.outputText);
fs.writeFileSync(path.join(temporary, outputName), output);
}
transpile("capability-gates.ts", "capability-gates.mjs");
transpile("physics-simulation.ts", "physics-simulation.mjs", [
['from "./capability-gates"', 'from "./capability-gates.mjs"'],
]);
const physics = await import(pathToFileURL(path.join(temporary, "physics-simulation.mjs")));
const digest = async (value) => Array.from(
new Uint8Array(await crypto.subtle.digest("SHA-256", value)),
(byte) => byte.toString(16).padStart(2, "0"),
).join("");
async function cachedSystem(family, index = 0, cachePatch = {}) {
const source = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, index]).buffer;
const payload = Uint8Array.from([index + 1, 2, index + 3, 4]).buffer;
const first = payload.slice(0, 2);
const second = payload.slice(2, 4);
const settingsHash = await digest(Uint8Array.from([index + 11]).buffer);
const cache = {
schemaVersion: 1,
cacheKey: `physics-${family.toLowerCase()}-1-2`,
family,
source: index % 2 === 0 ? "BLENDER_DESKTOP_BAKE" : "BLENDER_SERVER_BAKE",
blenderVersion: "5.2.0",
sourceBlendSha256: await digest(source),
settingsHash,
inputHash: await digest(Uint8Array.from([index + 21]).buffer),
cacheSha256: await digest(payload),
frameStart: 1,
frameEnd: 2,
byteLength: payload.byteLength,
frames: [
{ frame: 1, byteOffset: 0, byteLength: 2, sha256: await digest(first) },
{ frame: 2, byteOffset: 2, byteLength: 2, sha256: await digest(second) },
],
status: "COMPLETE",
...cachePatch,
};
const manifest = {
schemaVersion: 1,
systems: [{
id: `physics:${family.toLowerCase()}`,
family,
ownerObjectId: `object:${family}`,
settingsHash,
settings: { enabled: true },
dependencyIds: [],
cache,
}],
};
return { manifest, source, payload };
}
test("M10-14 verifies source, payload and frame hashes for every Physics family", async () => {
for (const [index, family] of physics.PHYSICS_FAMILIES.entries()) {
const value = await cachedSystem(family, index);
const parsed = physics.parsePhysicsSimulationManifest(value.manifest);
const cache = await physics.verifyPhysicsCachePayload(parsed.systems[0], value.source, value.payload);
assert.equal(cache.family, family);
assert.equal(cache.source, index % 2 === 0 ? "BLENDER_DESKTOP_BAKE" : "BLENDER_SERVER_BAKE");
assert.equal(cache.frames.length, 2);
assert.deepEqual(physics.selectPhysicsCacheFrame(parsed.systems[0], 2), { cacheKey: cache.cacheKey, frame: 2 });
}
});
test("M10-14 rejects source and cache byte drift before playback", async () => {
const value = await cachedSystem("CLOTH");
const parsed = physics.parsePhysicsSimulationManifest(value.manifest);
await assert.rejects(
physics.verifyPhysicsCachePayload(parsed.systems[0], Uint8Array.from([1]).buffer, value.payload),
{ code: "PHYSICS_CACHE_SOURCE_MISMATCH" },
);
await assert.rejects(
physics.verifyPhysicsCachePayload(parsed.systems[0], value.source, Uint8Array.from([9, 9, 9, 9]).buffer),
{ code: "PHYSICS_CACHE_HASH_MISMATCH" },
);
});
test("M10-14 rejects family, version, range, byte budget and undeclared cache fields", async () => {
const value = await cachedSystem("FLUID");
const cache = value.manifest.systems[0].cache;
const invalid = [
[{ ...cache, schemaVersion: 2 }, "PROTOCOL_MISMATCH"],
[{ ...cache, blenderVersion: "5.3.0" }, "PROTOCOL_MISMATCH"],
[{ ...cache, family: "CLOTH" }, "PHYSICS_MANIFEST_INVALID"],
[{ ...cache, byteLength: physics.PHYSICS_SIMULATION_BUDGET.maxCacheBytes + 1 }, "PHYSICS_BUDGET_EXCEEDED"],
[{ ...cache, frames: [{ ...cache.frames[0], byteOffset: 1 }, cache.frames[1]] }, "PHYSICS_CACHE_FRAME_MISMATCH"],
[{ ...cache, frames: [cache.frames[0]] }, "PHYSICS_CACHE_FRAME_MISMATCH"],
[{ ...cache, proxySuccess: true }, "PHYSICS_MANIFEST_INVALID"],
];
for (const [candidate, code] of invalid) {
assert.throws(() => physics.parsePhysicsSimulationManifest({
...value.manifest,
systems: [{ ...value.manifest.systems[0], cache: candidate }],
}), { code });
}
});
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));