chore: checkpoint web Blender migration source, tools and docs
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

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:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -2,6 +2,7 @@ import type { ErrorCode } from "./error";
import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } from "./capability-gates";
export const NLA_PROTOCOL_SCHEMA = 1 as const;
export const NLA_OBJECT_ID_TYPE = 0x424f; // DNA_ID_enums.h: MAKE_ID2('O', 'B'); assignment stays native.
export const NLA_STACK_BUDGET = Object.freeze({
maxTracks: 4_096,
maxStripsPerTrack: 16_384,
@@ -10,12 +11,13 @@ export const NLA_STACK_BUDGET = Object.freeze({
maxNameBytes: 1_024,
maxUnsupportedReasonBytes: 4_096,
});
export type NlaBlendMode = "REPLACE" | "ADD" | "MULTIPLY" | "COMBINE";
export type NlaBlendMode = "REPLACE" | "ADD" | "SUBTRACT" | "MULTIPLY" | "COMBINE";
export type NlaExtrapolation = "NOTHING" | "HOLD" | "HOLD_FORWARD";
export interface NlaStripIR {
id: string;
actionId: string;
sourceActionId?: string;
frameStart: number;
frameEnd: number;
actionFrameStart: number;
@@ -31,6 +33,12 @@ export interface NlaStripIR {
selected: boolean;
reverse?: boolean;
useTimeWarp?: boolean;
useSyncLength?: boolean;
useAnimatedInfluence?: boolean;
useAutoBlend?: boolean;
actionSlotHandle?: number;
actionSlotIdentifier?: string;
actionSlotAssigned?: boolean;
stripType?: "CLIP" | "TRANSITION" | "META" | "SOUND" | "UNKNOWN";
unsupportedReason?: string;
}
@@ -108,13 +116,15 @@ function finite(value: unknown, path: string, maximumMagnitude = 1_000_000): num
function parseStrip(value: unknown, path: string): NlaStripIR {
if (!record(value)) throw new NlaValidationError("NLA_INVALID_STACK", `${path} must be an object`, path);
exactKeys(value, [
"id", "actionId", "frameStart", "frameEnd", "actionFrameStart", "actionFrameEnd",
"id", "actionId", "sourceActionId", "frameStart", "frameEnd", "actionFrameStart", "actionFrameEnd",
"scale", "repeat", "blendIn", "blendOut", "influence", "blendMode", "extrapolation",
"muted", "selected", "reverse", "useTimeWarp", "stripType", "unsupportedReason",
"muted", "selected", "reverse", "useTimeWarp", "useSyncLength", "useAnimatedInfluence", "useAutoBlend", "actionSlotHandle", "actionSlotIdentifier", "actionSlotAssigned", "stripType", "unsupportedReason",
], path);
boundedText(value.id, `${path}.id`, NLA_STACK_BUDGET.maxIdentifierBytes);
boundedText(value.actionId, `${path}.actionId`, NLA_STACK_BUDGET.maxIdentifierBytes);
if (value.sourceActionId !== undefined) boundedText(value.sourceActionId, `${path}.sourceActionId`, NLA_STACK_BUDGET.maxIdentifierBytes);
const strip = value as Record<string, unknown>;
const actionClip = strip.stripType === undefined || strip.stripType === "CLIP";
const frameStart = finite(strip.frameStart, `${path}.frameStart`);
const frameEnd = finite(strip.frameEnd, `${path}.frameEnd`);
const actionFrameStart = finite(strip.actionFrameStart, `${path}.actionFrameStart`);
@@ -124,12 +134,19 @@ function parseStrip(value: unknown, path: string): NlaStripIR {
const blendIn = finite(strip.blendIn, `${path}.blendIn`);
const blendOut = finite(strip.blendOut, `${path}.blendOut`);
const influence = finite(strip.influence, `${path}.influence`);
if (frameEnd <= frameStart || actionFrameEnd <= actionFrameStart || scale <= 0 || repeat <= 0 || blendIn < 0 || blendOut < 0 || influence < 0 || influence > 1) throw new NlaValidationError("NLA_INVALID_STACK", `${path} has an invalid range or influence`, path);
if (!["REPLACE", "ADD", "MULTIPLY", "COMBINE"].includes(strip.blendMode as string)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.blendMode is invalid`, `${path}.blendMode`);
if (frameEnd <= frameStart || (actionClip && (actionFrameEnd < actionFrameStart || scale <= 0 || repeat <= 0)) || blendIn < 0 || blendOut < 0 || influence < 0 || influence > 1) throw new NlaValidationError("NLA_INVALID_STACK", `${path} has an invalid range or influence`, path);
if (!["REPLACE", "ADD", "SUBTRACT", "MULTIPLY", "COMBINE"].includes(strip.blendMode as string)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.blendMode is invalid`, `${path}.blendMode`);
if (!["NOTHING", "HOLD", "HOLD_FORWARD"].includes(strip.extrapolation as string)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.extrapolation is invalid`, `${path}.extrapolation`);
if (typeof strip.muted !== "boolean" || typeof strip.selected !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.muted/selected must be boolean`, path);
if (strip.reverse !== undefined && typeof strip.reverse !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.reverse must be boolean`, `${path}.reverse`);
if (strip.useTimeWarp !== undefined && typeof strip.useTimeWarp !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.useTimeWarp must be boolean`, `${path}.useTimeWarp`);
if (strip.useSyncLength !== undefined && typeof strip.useSyncLength !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.useSyncLength must be boolean`, `${path}.useSyncLength`);
if (strip.useAnimatedInfluence !== undefined && typeof strip.useAnimatedInfluence !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.useAnimatedInfluence must be boolean`, `${path}.useAnimatedInfluence`);
if (strip.useAutoBlend !== undefined && typeof strip.useAutoBlend !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.useAutoBlend must be boolean`, `${path}.useAutoBlend`);
const actionSlotHandle = strip.actionSlotHandle;
if (actionSlotHandle !== undefined && actionSlotHandle !== null && (typeof actionSlotHandle !== "number" || !Number.isSafeInteger(actionSlotHandle) || actionSlotHandle < 0)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.actionSlotHandle must be a non-negative integer`, `${path}.actionSlotHandle`);
if (strip.actionSlotIdentifier !== undefined) boundedText(strip.actionSlotIdentifier, `${path}.actionSlotIdentifier`, NLA_STACK_BUDGET.maxIdentifierBytes);
if (strip.actionSlotAssigned !== undefined && typeof strip.actionSlotAssigned !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.actionSlotAssigned must be boolean`, `${path}.actionSlotAssigned`);
if (strip.stripType !== undefined && !["CLIP", "TRANSITION", "META", "SOUND", "UNKNOWN"].includes(strip.stripType as string)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.stripType is invalid`, `${path}.stripType`);
if (strip.unsupportedReason !== undefined) boundedText(strip.unsupportedReason, `${path}.unsupportedReason`, NLA_STACK_BUDGET.maxUnsupportedReasonBytes);
return value as unknown as NlaStripIR;
@@ -185,8 +202,8 @@ export function validateNlaTracks(tracks: readonly NlaTrackIR[], context: NlaVal
stripIds.add(strip.id);
if (!context.actionIds.has(strip.actionId)) issues.push({ code: "NLA_ACTION_MISSING", message: `NLA strip references missing Action: ${strip.actionId}`, path });
if (strip.useTimeWarp) issues.push({ code: "NLA_TIME_WARP_UNSUPPORTED", message: "time warp is outside the finite NLA subset", path });
if (strip.stripType !== undefined && strip.stripType !== "CLIP") issues.push({ code: "NLA_STRIP_UNSUPPORTED", message: `${strip.stripType} strips are outside the finite Action Clip subset`, path });
if (strip.unsupportedReason) issues.push({ code: "NLA_STRIP_UNSUPPORTED", message: strip.unsupportedReason, path });
const unsupportedReason = strip.unsupportedReason ?? (strip.stripType !== undefined && strip.stripType !== "CLIP" ? `${strip.stripType} strips are outside the finite Action Clip subset` : undefined);
if (unsupportedReason) issues.push({ code: "NLA_STRIP_UNSUPPORTED", message: unsupportedReason, path });
const expectedDuration = (strip.actionFrameEnd - strip.actionFrameStart) * strip.scale * strip.repeat;
if (Math.abs(expectedDuration - (strip.frameEnd - strip.frameStart)) > 1e-4) issues.push({ code: "NLA_INVALID_STACK", message: "NLA strip time mapping is inconsistent with Action range, scale and repeat", path });
if (strip.blendIn + strip.blendOut > strip.frameEnd - strip.frameStart) issues.push({ code: "NLA_INVALID_STACK", message: "NLA strip blend ranges exceed its duration", path });