chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -26,10 +26,11 @@ export const PHYSICS_FAMILIES = [
|
||||
|
||||
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 type PhysicsSettingValue = boolean | number | string | null | [number, number, number];
|
||||
|
||||
/** Typed Main-authoring payloads for the physics families exposed by WebEngine. */
|
||||
export interface PhysicsRigidBodySettings {
|
||||
substepsPerFrame?: number;
|
||||
type?: number;
|
||||
mass?: number;
|
||||
friction?: number;
|
||||
@@ -44,6 +45,7 @@ export interface PhysicsRigidBodySettings {
|
||||
}
|
||||
|
||||
export interface PhysicsClothSettings {
|
||||
pinVertexGroup?: string;
|
||||
mass?: number;
|
||||
airDamping?: number;
|
||||
tensionStiffness?: number;
|
||||
@@ -62,13 +64,64 @@ export interface PhysicsFluidSettings {
|
||||
resolution?: number;
|
||||
timeScale?: number;
|
||||
viscosity?: number;
|
||||
gravity?: number;
|
||||
gravity?: number | [number, number, number];
|
||||
cacheFrameStart?: number;
|
||||
cacheFrameEnd?: number;
|
||||
cacheType?: number;
|
||||
}
|
||||
|
||||
export interface PhysicsSoftBodySettings {
|
||||
cacheFrameStart?: number;
|
||||
cacheFrameEnd?: number;
|
||||
cacheFrameStep?: number;
|
||||
cacheUseDisk?: boolean;
|
||||
effectorCollectionId?: string | null;
|
||||
effectorGravity?: number;
|
||||
effectorAll?: number;
|
||||
effectorForce?: number;
|
||||
effectorVortex?: number;
|
||||
effectorMagnetic?: number;
|
||||
effectorWind?: number;
|
||||
effectorCurveGuide?: number;
|
||||
effectorTexture?: number;
|
||||
effectorHarmonic?: number;
|
||||
effectorCharge?: number;
|
||||
effectorLennardJones?: number;
|
||||
effectorBoid?: number;
|
||||
effectorTurbulence?: number;
|
||||
effectorDrag?: number;
|
||||
effectorSmokeFlow?: number;
|
||||
collisionCollectionId?: string | null;
|
||||
useSelfCollision?: boolean;
|
||||
useEdgeCollision?: boolean;
|
||||
useFaceCollision?: boolean;
|
||||
collisionType?: 0 | 1 | 2 | 3 | 4;
|
||||
ballSize?: number;
|
||||
ballStiffness?: number;
|
||||
ballDamping?: number;
|
||||
errorThreshold?: number;
|
||||
stepMin?: number;
|
||||
stepMax?: number;
|
||||
choke?: number;
|
||||
fuzzy?: number;
|
||||
useEdges?: boolean;
|
||||
useStiffQuads?: boolean;
|
||||
pull?: number;
|
||||
push?: number;
|
||||
damping?: number;
|
||||
bending?: number;
|
||||
shear?: number;
|
||||
plasticity?: number;
|
||||
springLength?: number;
|
||||
aero?: number;
|
||||
aerodynamicsType?: 0 | 1;
|
||||
springVertexGroup?: string;
|
||||
useGoal?: boolean;
|
||||
goalDefault?: number;
|
||||
goalMin?: number;
|
||||
goalMax?: number;
|
||||
massVertexGroup?: string;
|
||||
goalVertexGroup?: string;
|
||||
nodeMass?: number;
|
||||
gravity?: number;
|
||||
mediaFriction?: number;
|
||||
@@ -118,6 +171,23 @@ export interface PhysicsCacheBindingIR {
|
||||
status: "COMPLETE" | "PARTIAL";
|
||||
}
|
||||
|
||||
export interface PhysicsNativeCacheTargetIR {
|
||||
ownerSessionUid: number;
|
||||
modifierUid: number;
|
||||
slotIndex: number;
|
||||
}
|
||||
export interface PhysicsNativeCacheSlotsIR {
|
||||
count: number;
|
||||
items: Array<{ index: number; name: string; baked: boolean }>;
|
||||
}
|
||||
export interface PhysicsNativeCacheStateIR {
|
||||
slots?: PhysicsNativeCacheSlotsIR;
|
||||
target?: PhysicsNativeCacheTargetIR;
|
||||
baked: boolean;
|
||||
baking: boolean;
|
||||
outdated: boolean;
|
||||
framesSkipped: boolean;
|
||||
}
|
||||
export interface PhysicsSystemIR {
|
||||
id: string;
|
||||
family: PhysicsFamily;
|
||||
@@ -126,6 +196,7 @@ export interface PhysicsSystemIR {
|
||||
settings: Record<string, PhysicsSettingValue>;
|
||||
dependencyIds: string[];
|
||||
cache?: PhysicsCacheBindingIR;
|
||||
nativeCache?: PhysicsNativeCacheStateIR;
|
||||
}
|
||||
|
||||
export interface PhysicsSimulationManifestIR {
|
||||
@@ -248,13 +319,15 @@ function parseSettings(value: unknown, systemIndex: number): Record<string, Phys
|
||||
const settings: Record<string, PhysicsSettingValue> = {};
|
||||
for (const [name, item] of entries) {
|
||||
if (name.length === 0 || name.length > 128 || !/^[A-Za-z0-9_.:-]+$/.test(name) ||
|
||||
!(item === null || typeof item === "boolean" || typeof item === "string" || (typeof item === "number" && Number.isFinite(item)))) {
|
||||
!((name === "gravity" && Array.isArray(item) && item.length === 3 && item.every(value => typeof value === "number" && Number.isFinite(value))) || item === null || typeof item === "boolean" || typeof item === "string" || (typeof item === "number" && Number.isFinite(item)))) {
|
||||
throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${systemIndex}].settings.${name} is invalid`);
|
||||
}
|
||||
if (typeof item === "string" && item.length > 1_024) {
|
||||
throw new PhysicsSimulationValidationError("PHYSICS_BUDGET_EXCEEDED", `systems[${systemIndex}].settings.${name} exceeds the string budget`);
|
||||
}
|
||||
settings[name] = item;
|
||||
settings[name] = name === "gravity" && Array.isArray(item)
|
||||
? [item[0] as number, item[1] as number, item[2] as number]
|
||||
: item as PhysicsSettingValue;
|
||||
}
|
||||
if (new TextEncoder().encode(JSON.stringify(settings)).byteLength > PHYSICS_SIMULATION_BUDGET.maxSettingsBytes) {
|
||||
throw new PhysicsSimulationValidationError("PHYSICS_BUDGET_EXCEEDED", `systems[${systemIndex}].settings exceeds the byte budget`);
|
||||
@@ -357,6 +430,30 @@ function parseCache(
|
||||
};
|
||||
}
|
||||
|
||||
function parseNativeCacheTarget(value: unknown, index: number): PhysicsNativeCacheTargetIR | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (!record(value) || Object.keys(value).length !== 3 || !["ownerSessionUid", "modifierUid", "slotIndex"].every(key => typeof value[key] === "number" && Number.isInteger(value[key]) && value[key] >= -2147483648 && value[key] <= 2147483647) || value.ownerSessionUid === 0 || (value.slotIndex as number) < 0) throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}].nativeCache.target is invalid`);
|
||||
return { ownerSessionUid: value.ownerSessionUid as number, modifierUid: value.modifierUid as number, slotIndex: value.slotIndex as number };
|
||||
}
|
||||
function parseNativeCacheSlots(value: unknown, index: number): PhysicsNativeCacheSlotsIR | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (!record(value) || Object.keys(value).length !== 2 || typeof value.count !== "number" || !Number.isInteger(value.count) || value.count < 1 || value.count > 2147483647 || !Array.isArray(value.items) || value.items.length === 0 || value.items.length > Math.min(128, value.count)) throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}].nativeCache.slots is invalid`);
|
||||
const seen = new Set<number>();
|
||||
const count = value.count;
|
||||
const items = value.items.map(item => {
|
||||
if (!record(item) || Object.keys(item).length !== 3 || typeof item.index !== "number" || !Number.isInteger(item.index) || item.index < 0 || item.index >= count || seen.has(item.index) || typeof item.name !== "string" || item.name.length > 63 || item.name.includes("\0") || typeof item.baked !== "boolean") throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}].nativeCache.slots item is invalid`);
|
||||
seen.add(item.index); return { index: item.index, name: item.name, baked: item.baked };
|
||||
});
|
||||
return { count, items };
|
||||
}
|
||||
function parseNativeCacheState(value: unknown, index: number): PhysicsNativeCacheStateIR | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (!record(value) || Object.keys(value).some(key => !["baked", "baking", "outdated", "framesSkipped", "target", "slots"].includes(key)) || !["baked", "baking", "outdated", "framesSkipped"].every(key => typeof value[key] === "boolean")) throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}].nativeCache is invalid`);
|
||||
const target = parseNativeCacheTarget(value.target, index);
|
||||
const slots = parseNativeCacheSlots(value.slots, index);
|
||||
if (target && slots && !slots.items.some(item => item.index === target.slotIndex)) throw new PhysicsSimulationValidationError("PHYSICS_MANIFEST_INVALID", `systems[${index}].nativeCache active slot is absent`);
|
||||
return { baked: value.baked as boolean, baking: value.baking as boolean, outdated: value.outdated as boolean, framesSkipped: value.framesSkipped as boolean, ...(target ? { target } : {}), ...(slots ? { slots } : {}) };
|
||||
}
|
||||
export function parsePhysicsSimulationManifest(value: unknown): PhysicsSimulationManifestIR {
|
||||
if (!record(value) || value.schemaVersion !== PHYSICS_SIMULATION_SCHEMA || !Array.isArray(value.systems)) {
|
||||
throw new PhysicsSimulationValidationError("PROTOCOL_MISMATCH", "Unsupported PhysicsSimulation manifest schema");
|
||||
@@ -390,6 +487,7 @@ export function parsePhysicsSimulationManifest(value: unknown): PhysicsSimulatio
|
||||
settings: parseSettings(item.settings, index),
|
||||
dependencyIds,
|
||||
cache: parseCache(item.cache, settingsHash, family, index),
|
||||
nativeCache: parseNativeCacheState(item.nativeCache, index),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user