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

@@ -2,12 +2,16 @@ import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } fr
import type { ErrorCode } from "./error";
export const PHYSICS_SIMULATION_SCHEMA = 1 as const;
export const PHYSICS_CACHE_SCHEMA = 1 as const;
export const PHYSICS_CACHE_BLENDER_VERSION_PREFIX = "5.2." as const;
export const PHYSICS_SIMULATION_BUDGET = {
maxSystems: 4_096,
maxDependenciesPerSystem: 1_024,
maxSettings: 256,
maxSettingsBytes: 64 * 1024,
maxFrames: 100_000,
maxFrameBytes: 512 * 1024 * 1024,
maxCacheBytes: 16 * 1024 * 1024 * 1024,
} as const;
export const PHYSICS_FAMILIES = [
@@ -24,16 +28,27 @@ export type PhysicsFamily = typeof PHYSICS_FAMILIES[number];
export type PhysicsExecutionRequest = "METADATA" | "CACHE_MANIFEST" | "CACHE_PLAYBACK" | "LOCAL_SOLVER" | "SERVER_JOB";
export type PhysicsSettingValue = boolean | number | string | null;
export interface PhysicsCacheFrameIR {
frame: number;
byteOffset: number;
byteLength: number;
sha256: string;
}
export interface PhysicsCacheBindingIR {
schemaVersion: typeof PHYSICS_CACHE_SCHEMA;
cacheKey: string;
source: "BLENDER_DESKTOP_BAKE";
family: PhysicsFamily;
source: "BLENDER_DESKTOP_BAKE" | "BLENDER_SERVER_BAKE";
blenderVersion: string;
sourceBlendSha256: string;
settingsHash: string;
inputHash: string;
cacheSha256: string;
frameStart: number;
frameEnd: number;
cachedFrames: number[];
byteLength: number;
frames: PhysicsCacheFrameIR[];
status: "COMPLETE" | "PARTIAL";
}
@@ -57,10 +72,42 @@ export interface PhysicsFamilyCapabilityIR {
metadata: "LOCAL_BOUNDED";
cacheManifest: "LOCAL_BOUNDED";
cachePlayback: "BLOCKED";
localSolver: "BLOCKED";
localSolver: "READY" | "BLOCKED";
solverProbe: PhysicsSolverProbeStatus;
unsupportedRoute: "DESKTOP_SERVER_BAKE";
serverJob: "BLOCKED";
}
export type PhysicsSolverProbeStatus =
| "READY"
| "EXPORT_UNAVAILABLE"
| "INITIALIZATION_FAILED"
| "THREADS_UNAVAILABLE"
| "MEMORY_UNAVAILABLE"
| "INVALID_RESULT";
export interface PhysicsSolverProbeEnvironmentIR {
threadMode: "SINGLE" | "PTHREAD";
memoryLimitBytes: number;
}
export interface PhysicsSolverInitializationIR {
initialized: boolean;
requiredThreadMode: "SINGLE" | "PTHREAD";
requiredMemoryBytes: number;
}
export interface PhysicsSolverRuntimeProbe {
hasFamilyExport(family: PhysicsFamily): boolean;
initializeFamily(family: PhysicsFamily): PhysicsSolverInitializationIR | Promise<PhysicsSolverInitializationIR>;
}
export interface PhysicsExecutionRouteIR {
family: PhysicsFamily;
mode: "LOCAL_SOLVER" | "DESKTOP_SERVER_BAKE";
probe: PhysicsSolverProbeStatus;
}
export interface BrowserTransformCacheObjectIR {
objectId: string;
translation: [number, number, number];
@@ -105,6 +152,20 @@ function digest(value: unknown, name: string): string {
return value;
}
function integer(value: unknown, name: string, minimum = 0, maximum = Number.MAX_SAFE_INTEGER): number {
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `${name} is outside its integer range`);
}
return value;
}
function exactKeys(value: Record<string, unknown>, allowed: readonly string[], name: string): void {
const allowedSet = new Set(allowed);
if (Object.keys(value).some((key) => !allowedSet.has(key))) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `${name} contains undeclared fields`);
}
}
function frame(value: unknown, name: string): number {
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < -1_000_000 || value > 1_000_000) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `${name} is outside the supported frame range`);
@@ -135,23 +196,77 @@ function parseSettings(value: unknown, systemIndex: number): Record<string, Phys
return settings;
}
function parseCache(value: unknown, settingsHash: string, systemIndex: number): PhysicsCacheBindingIR | undefined {
function parseCache(
value: unknown,
settingsHash: string,
family: PhysicsFamily,
systemIndex: number,
): PhysicsCacheBindingIR | undefined {
if (value === undefined) return undefined;
if (!record(value) || value.source !== "BLENDER_DESKTOP_BAKE" || !CACHE_KEY.test(String(value.cacheKey ?? "")) ||
if (!record(value)) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].cache is invalid`);
}
exactKeys(value, [
"schemaVersion", "cacheKey", "family", "source", "blenderVersion", "sourceBlendSha256",
"settingsHash", "inputHash", "cacheSha256", "frameStart", "frameEnd", "byteLength",
"frames", "status",
], `systems[${systemIndex}].cache`);
if (value.schemaVersion !== PHYSICS_CACHE_SCHEMA) {
throw new PhysicsSimulationValidationError("PROTOCOL_MISMATCH", `systems[${systemIndex}].cache has an unsupported schema`);
}
if (value.family !== family ||
(value.source !== "BLENDER_DESKTOP_BAKE" && value.source !== "BLENDER_SERVER_BAKE") ||
!CACHE_KEY.test(String(value.cacheKey ?? "")) ||
(value.status !== "COMPLETE" && value.status !== "PARTIAL")) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].cache is invalid`);
}
const blenderVersion = text(value.blenderVersion, `systems[${systemIndex}].cache.blenderVersion`);
if (!blenderVersion.startsWith(PHYSICS_CACHE_BLENDER_VERSION_PREFIX)) {
throw new PhysicsSimulationValidationError("PROTOCOL_MISMATCH", `Physics cache requires Blender ${PHYSICS_CACHE_BLENDER_VERSION_PREFIX}x`);
}
const frameStart = frame(value.frameStart, `systems[${systemIndex}].cache.frameStart`);
const frameEnd = frame(value.frameEnd, `systems[${systemIndex}].cache.frameEnd`);
if (frameEnd < frameStart || frameEnd - frameStart + 1 > PHYSICS_SIMULATION_BUDGET.maxFrames || !Array.isArray(value.cachedFrames)) {
const byteLength = integer(value.byteLength, `systems[${systemIndex}].cache.byteLength`, 1);
if (byteLength > PHYSICS_SIMULATION_BUDGET.maxCacheBytes) {
throw new PhysicsSimulationValidationError("PHYSICS_BUDGET_EXCEEDED", `systems[${systemIndex}].cache exceeds the byte budget`);
}
if (frameEnd < frameStart || frameEnd - frameStart + 1 > PHYSICS_SIMULATION_BUDGET.maxFrames || !Array.isArray(value.frames)) {
throw new PhysicsSimulationValidationError("PHYSICS_BUDGET_EXCEEDED", `systems[${systemIndex}].cache frame range exceeds the budget`);
}
const cachedFrames = value.cachedFrames.map((item, frameIndex) => frame(item, `systems[${systemIndex}].cache.cachedFrames[${frameIndex}]`));
if (cachedFrames.length === 0 || cachedFrames.length > PHYSICS_SIMULATION_BUDGET.maxFrames ||
cachedFrames.some((item, index) => item < frameStart || item > frameEnd || (index > 0 && item <= cachedFrames[index - 1]))) {
if (value.frames.length === 0 || value.frames.length > PHYSICS_SIMULATION_BUDGET.maxFrames) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].cache frames are invalid`);
}
let nextOffset = 0;
const frames = value.frames.map((item, frameIndex): PhysicsCacheFrameIR => {
if (!record(item)) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].cache.frames[${frameIndex}] is invalid`);
}
exactKeys(item, ["frame", "byteOffset", "byteLength", "sha256"], `systems[${systemIndex}].cache.frames[${frameIndex}]`);
const frameNumber = frame(item.frame, `systems[${systemIndex}].cache.frames[${frameIndex}].frame`);
const byteOffset = integer(item.byteOffset, `systems[${systemIndex}].cache.frames[${frameIndex}].byteOffset`);
const frameByteLength = integer(item.byteLength, `systems[${systemIndex}].cache.frames[${frameIndex}].byteLength`, 1);
if (frameByteLength > PHYSICS_SIMULATION_BUDGET.maxFrameBytes) {
throw new PhysicsSimulationValidationError("PHYSICS_BUDGET_EXCEEDED", `systems[${systemIndex}].cache.frames[${frameIndex}] exceeds the byte budget`);
}
if (frameNumber < frameStart || frameNumber > frameEnd ||
byteOffset !== nextOffset || byteOffset > byteLength - frameByteLength) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_FRAME_MISMATCH", `systems[${systemIndex}].cache.frames[${frameIndex}] is not ordered or contiguous`);
}
nextOffset += frameByteLength;
return {
frame: frameNumber,
byteOffset,
byteLength: frameByteLength,
sha256: digest(item.sha256, `systems[${systemIndex}].cache.frames[${frameIndex}].sha256`),
};
});
if (nextOffset !== byteLength) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_FRAME_MISMATCH", `systems[${systemIndex}].cache frame ranges do not cover the payload`);
}
if (frames.some((item, index) => index > 0 && item.frame <= frames[index - 1].frame)) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].cache frames must be unique, ordered and in range`);
}
if (value.status === "COMPLETE" && (cachedFrames.length !== frameEnd - frameStart + 1 || cachedFrames.some((item, index) => item !== frameStart + index))) {
if (value.status === "COMPLETE" && (frames.length !== frameEnd - frameStart + 1 || frames.some((item, index) => item.frame !== frameStart + index))) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_FRAME_MISMATCH", `systems[${systemIndex}] declares an incomplete cache as COMPLETE`);
}
const cacheSettingsHash = digest(value.settingsHash, `systems[${systemIndex}].cache.settingsHash`);
@@ -159,15 +274,19 @@ function parseCache(value: unknown, settingsHash: string, systemIndex: number):
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_FRAME_MISMATCH", `systems[${systemIndex}] cache settings do not match the current system`);
}
return {
schemaVersion: PHYSICS_CACHE_SCHEMA,
cacheKey: value.cacheKey as string,
source: "BLENDER_DESKTOP_BAKE",
family,
source: value.source,
blenderVersion,
sourceBlendSha256: digest(value.sourceBlendSha256, `systems[${systemIndex}].cache.sourceBlendSha256`),
settingsHash: cacheSettingsHash,
inputHash: digest(value.inputHash, `systems[${systemIndex}].cache.inputHash`),
cacheSha256: digest(value.cacheSha256, `systems[${systemIndex}].cache.cacheSha256`),
frameStart,
frameEnd,
cachedFrames,
byteLength,
frames,
status: value.status,
};
}
@@ -195,15 +314,16 @@ export function parsePhysicsSimulationManifest(value: unknown): PhysicsSimulatio
if (new Set(dependencyIds).size !== dependencyIds.length) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}] contains duplicate dependencies`);
}
const family = item.family as PhysicsFamily;
const settingsHash = digest(item.settingsHash, `systems[${index}].settingsHash`);
return {
id,
family: item.family as PhysicsFamily,
family,
ownerObjectId: text(item.ownerObjectId, `systems[${index}].ownerObjectId`, "object:"),
settingsHash,
settings: parseSettings(item.settings, index),
dependencyIds,
cache: parseCache(item.cache, settingsHash, index),
cache: parseCache(item.cache, settingsHash, family, index),
};
});
@@ -229,28 +349,146 @@ export function physicsCapabilityInventory(): PhysicsFamilyCapabilityIR[] {
cacheManifest: "LOCAL_BOUNDED",
cachePlayback: "BLOCKED",
localSolver: "BLOCKED",
solverProbe: "EXPORT_UNAVAILABLE",
unsupportedRoute: "DESKTOP_SERVER_BAKE",
serverJob: "BLOCKED",
}));
}
export function gatePhysicsExecution(family: PhysicsFamily, request: PhysicsExecutionRequest): CapabilityGateResult {
function solverCapability(family: PhysicsFamily, status: PhysicsSolverProbeStatus): PhysicsFamilyCapabilityIR {
return {
family,
metadata: "LOCAL_BOUNDED",
cacheManifest: "LOCAL_BOUNDED",
cachePlayback: "BLOCKED",
localSolver: status === "READY" ? "READY" : "BLOCKED",
solverProbe: status,
unsupportedRoute: "DESKTOP_SERVER_BAKE",
serverJob: "BLOCKED",
};
}
function validProbeEnvironment(value: PhysicsSolverProbeEnvironmentIR): boolean {
return (value.threadMode === "SINGLE" || value.threadMode === "PTHREAD") &&
Number.isSafeInteger(value.memoryLimitBytes) && value.memoryLimitBytes > 0 && value.memoryLimitBytes <= 2_147_483_648;
}
function validInitialization(value: unknown): value is PhysicsSolverInitializationIR {
return record(value) && typeof value.initialized === "boolean" &&
(value.requiredThreadMode === "SINGLE" || value.requiredThreadMode === "PTHREAD") &&
typeof value.requiredMemoryBytes === "number" && Number.isSafeInteger(value.requiredMemoryBytes) &&
value.requiredMemoryBytes > 0 && value.requiredMemoryBytes <= 2_147_483_648;
}
export async function probePhysicsSolverCapabilities(
runtime: PhysicsSolverRuntimeProbe | undefined,
environment: PhysicsSolverProbeEnvironmentIR,
): Promise<PhysicsFamilyCapabilityIR[]> {
if (!validProbeEnvironment(environment)) {
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", "Physics solver probe environment is invalid");
}
const capabilities: PhysicsFamilyCapabilityIR[] = [];
for (const family of PHYSICS_FAMILIES) {
if (!runtime) {
capabilities.push(solverCapability(family, "EXPORT_UNAVAILABLE"));
continue;
}
let hasExport = false;
try { hasExport = runtime.hasFamilyExport(family) === true; }
catch { /* A failed symbol lookup is unavailable, not implicit support. */ }
if (!hasExport) {
capabilities.push(solverCapability(family, "EXPORT_UNAVAILABLE"));
continue;
}
let initialized: unknown;
try { initialized = await runtime.initializeFamily(family); }
catch {
capabilities.push(solverCapability(family, "INITIALIZATION_FAILED"));
continue;
}
if (!validInitialization(initialized)) {
capabilities.push(solverCapability(family, "INVALID_RESULT"));
continue;
}
if (!initialized.initialized) {
capabilities.push(solverCapability(family, "INITIALIZATION_FAILED"));
continue;
}
if (initialized.requiredThreadMode === "PTHREAD" && environment.threadMode !== "PTHREAD") {
capabilities.push(solverCapability(family, "THREADS_UNAVAILABLE"));
continue;
}
if (initialized.requiredMemoryBytes > environment.memoryLimitBytes) {
capabilities.push(solverCapability(family, "MEMORY_UNAVAILABLE"));
continue;
}
capabilities.push(solverCapability(family, "READY"));
}
return capabilities;
}
export function selectPhysicsExecutionRoute(
family: PhysicsFamily,
capabilities: readonly PhysicsFamilyCapabilityIR[],
): PhysicsExecutionRouteIR {
const capability = capabilities.find((entry) => entry.family === family);
if (!capability || capability.localSolver !== "READY" || capability.solverProbe !== "READY") {
return { family, mode: "DESKTOP_SERVER_BAKE", probe: capability?.solverProbe ?? "EXPORT_UNAVAILABLE" };
}
return { family, mode: "LOCAL_SOLVER", probe: "READY" };
}
export function gatePhysicsExecution(
family: PhysicsFamily,
request: PhysicsExecutionRequest,
capabilities: readonly PhysicsFamilyCapabilityIR[] = physicsCapabilityInventory(),
): CapabilityGateResult {
if (request === "METADATA" || request === "CACHE_MANIFEST") return readyGate("N-018", `${family}_${request}`);
const route = selectPhysicsExecutionRoute(family, capabilities);
if (request === "LOCAL_SOLVER" && route.mode === "LOCAL_SOLVER") return readyGate("N-018", `${family}_${request}`);
const issue = request === "CACHE_PLAYBACK" ?
capabilityIssue("PHYSICS_CACHE_PLAYBACK_UNAVAILABLE", `${family} cache playback is not connected to frame evaluation`) :
request === "LOCAL_SOLVER" ?
capabilityIssue("PHYSICS_SOLVER_UNAVAILABLE", `${family} has no verified local WASM solver`) :
capabilityIssue("PHYSICS_SOLVER_UNAVAILABLE", `${family} local WASM solver probe is ${route.probe}; use a verified desktop/server bake`) :
capabilityIssue("PHYSICS_SERVER_UNAVAILABLE", `${family} server job execution is not configured`);
return blockedGate("N-018", `${family}_${request}`, [issue]);
}
export function selectPhysicsCacheFrame(system: PhysicsSystemIR, requestedFrame: number): { cacheKey: string; frame: number } {
const cache = system.cache;
if (!Number.isSafeInteger(requestedFrame) || !cache || !cache.cachedFrames.includes(requestedFrame)) {
if (!Number.isSafeInteger(requestedFrame) || !cache || !cache.frames.some((item) => item.frame === requestedFrame)) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_FRAME_MISMATCH", `Physics cache has no verified frame ${requestedFrame}`);
}
return { cacheKey: cache.cacheKey, frame: requestedFrame };
}
async function sha256(value: ArrayBuffer): Promise<string> {
const hash = await crypto.subtle.digest("SHA-256", value);
return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
}
export async function verifyPhysicsCachePayload(
system: PhysicsSystemIR,
sourceBlend: ArrayBuffer,
payload: ArrayBuffer,
): Promise<PhysicsCacheBindingIR> {
const cache = parseCache(system.cache, system.settingsHash, system.family, 0);
if (!cache) throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `Physics system ${system.id} has no cache`);
if (!(sourceBlend instanceof ArrayBuffer) || await sha256(sourceBlend) !== cache.sourceBlendSha256) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_SOURCE_MISMATCH", `Physics ${cache.family} cache source does not match the current blend`);
}
if (!(payload instanceof ArrayBuffer) || payload.byteLength !== cache.byteLength || await sha256(payload) !== cache.cacheSha256) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_HASH_MISMATCH", `Physics ${cache.family} cache payload failed SHA-256 verification`);
}
for (const cacheFrame of cache.frames) {
const bytes = payload.slice(cacheFrame.byteOffset, cacheFrame.byteOffset + cacheFrame.byteLength);
if (await sha256(bytes) !== cacheFrame.sha256) {
throw new PhysicsSimulationValidationError("PHYSICS_CACHE_HASH_MISMATCH", `Physics ${cache.family} frame ${cacheFrame.frame} failed SHA-256 verification`);
}
}
return cache;
}
const BROWSER_TRANSFORM_CACHE_MAGIC = 0x31465442; // BTF1
const BROWSER_TRANSFORM_CACHE_HEADER_BYTES = 16;
const BROWSER_TRANSFORM_CACHE_OBJECT_BYTES = 72;