Govern task context and advance execution pointer
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-20 06:02:43 -04:00
parent 380cbed4ff
commit 10640aeb3c
984 changed files with 543475 additions and 327 deletions

View File

@@ -11,6 +11,12 @@ export interface InputModalState {
mainCommitCount: number;
}
export interface InputModalEvent {
type: "pointerdown" | "pointerup" | "pointercancel";
pointerType: "touch" | "pen";
pointerId: number;
}
export function createInputModalState(): InputModalState {
return { schemaVersion: 1, kind: "NONE", activePointerIds: [], cancelled: false, navigationRevision: 0, mainCommitCount: 0 };
}
@@ -39,3 +45,15 @@ export function commitPenStroke(state: InputModalState, pointerId: number): Inpu
if (state.kind !== "PEN_STROKE" || !state.activePointerIds.includes(pointerId) || state.cancelled) return state;
return { ...state, kind: "NONE", activePointerIds: [], mainCommitCount: state.mainCommitCount + 1 };
}
export function reduceInputModal(state: InputModalState, event: InputModalEvent): InputModalState {
if (!Number.isSafeInteger(event.pointerId) || event.pointerId < 0) throw new Error("POINTER_ID_INVALID");
if (event.pointerType === "touch") {
if (event.type === "pointerdown") return beginTouch(state, event.pointerId);
if (event.type === "pointerup") return endTouch(state, event.pointerId);
return cancelInputModal(state);
}
if (event.type === "pointerdown") return beginPenStroke(state, event.pointerId);
if (event.type === "pointerup") return commitPenStroke(state, event.pointerId);
return cancelInputModal(state);
}

View File

@@ -377,6 +377,68 @@ export interface VFontResourceIR {
sha256?: string;
}
export interface SoundResourceIR {
id: string;
name: string;
sourcePath: string;
packed: boolean;
packedByteLength?: number;
volume: number;
pitch: number;
audioChannels: number;
sampleRate: number;
}
export interface SpeakerResourceIR {
id: string;
name: string;
soundId?: string;
volumeMax: number;
volumeMin: number;
distanceMax: number;
distanceReference: number;
attenuation: number;
coneAngleOuter: number;
coneAngleInner: number;
coneVolumeOuter: number;
volume: number;
pitch: number;
}
export interface TextResourceIR {
id: string;
name: string;
source: string;
sourceSha256: string;
byteLength: number;
lineCount: number;
internal: boolean;
sourcePath?: string;
isDirty: boolean;
useModule: boolean;
}
export interface TextureResourceIR {
id: string;
name: string;
type: number;
noiseScale: number;
noiseDepth: number;
intensity: number;
contrast: number;
saturation: number;
}
export interface WindowManagerResourceIR {
id: string;
name: string;
presetName: string;
windowCount: number;
interfaceLocked: boolean;
}
export interface WorkspaceResourceIR { id: string; name: string; screenCount: number }
export interface NonMeshVolumePropertiesIR {
displayDensity: number;
interpolation: "NEAREST" | "LINEAR";
@@ -488,6 +550,9 @@ export interface SceneSnapshotIR {
lights: LightIR[];
worlds: WorldIR[];
images: ImageIR[];
brushes?: BrushIR[];
lineStyles?: FreestyleLineStyleIR[];
lattices?: LatticeIR[];
nonMeshData?: NonMeshDataIR[];
vfonts?: VFontResourceIR[];
greasePencils?: GreasePencilDataIR[];
@@ -499,6 +564,13 @@ export interface SceneSnapshotIR {
scriptSources?: ScriptSourceInventoryIR;
scriptSourceStatus?: "AVAILABLE" | "BLOCKED";
physicsSimulation?: PhysicsSimulationManifestIR;
particleSettings?: ParticleSettingsIR[];
sounds?: SoundResourceIR[];
speakers?: SpeakerResourceIR[];
texts?: TextResourceIR[];
textures?: TextureResourceIR[];
windowManager?: WindowManagerResourceIR;
workspaces?: WorkspaceResourceIR[];
geometryNodeGraphs?: GeometryNodeGraphIR[];
libraries?: Array<{
id: string;
@@ -520,6 +592,66 @@ export interface SceneSnapshotIR {
frame: { current: number; start: number; end: number };
}
export interface ParticleSettingsIR {
id: string;
name: string;
type: number;
from: number;
distribution: number;
physicsType: number;
totalParticles: number;
start: number;
end: number;
lifetime: number;
size: number;
drawSize: number;
}
export interface BrushIR {
id: string;
name: string;
size: number;
alpha: number;
hardness: number;
spacing: number;
jitter: number;
sculptTool: number;
}
export interface FreestyleLineStyleIR {
id: string;
name: string;
color: [number, number, number];
alpha: number;
thickness: number;
chaining: number;
caps: number;
dashed: boolean;
modifierCounts: { color: number; alpha: number; thickness: number; geometry: number };
}
export type LatticeInterpolationIR = "KEY_LINEAR" | "KEY_CARDINAL" | "KEY_BSPLINE" | "KEY_CATMULL_ROM";
export interface LatticePointIR {
coDeform: [number, number, number];
weight: number;
selected: boolean;
}
export interface LatticeIR {
id: string;
name: string;
dimensions: [number, number, number];
pointCount: number;
interpolation: [LatticeInterpolationIR, LatticeInterpolationIR, LatticeInterpolationIR];
useOutside: boolean;
activePoint: number;
vertexGroup: string;
status: "AVAILABLE" | "BLOCKED";
points?: LatticePointIR[];
errorCode?: "LATTICE_DIMENSIONS_INVALID" | "LATTICE_POINTS_UNREADABLE";
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -564,7 +696,218 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
requireArray(value.lights, "lights");
requireArray(value.worlds, "worlds");
requireArray(value.images, "images");
if (value.brushes !== undefined) {
const brushes = requireArray(value.brushes, "brushes");
for (const [index, brush] of brushes.entries()) {
if (!isRecord(brush)) throw new Error(`SceneIR.brushes[${index}] must be an object`);
requireString(brush.id, `brushes[${index}].id`);
requireString(brush.name, `brushes[${index}].name`);
const size = requireNumber(brush.size, `brushes[${index}].size`);
const alpha = requireNumber(brush.alpha, `brushes[${index}].alpha`);
const hardness = requireNumber(brush.hardness, `brushes[${index}].hardness`);
const spacing = requireNumber(brush.spacing, `brushes[${index}].spacing`);
const jitter = requireNumber(brush.jitter, `brushes[${index}].jitter`);
const sculptTool = requireNumber(brush.sculptTool, `brushes[${index}].sculptTool`);
if (!Number.isSafeInteger(size) || size < 0 || size > 100000 || alpha < 0 || alpha > 1 || hardness < 0 || hardness > 1 || !Number.isSafeInteger(spacing) || spacing < 1 || spacing > 1000 || jitter < 0 || jitter > 1 || !Number.isSafeInteger(sculptTool) || sculptTool < 0) {
throw new Error(`SceneIR.brushes[${index}] is outside the bounded brush contract`);
}
}
}
if (value.lineStyles !== undefined) {
const lineStyles = requireArray(value.lineStyles, "lineStyles");
for (const [index, lineStyle] of lineStyles.entries()) {
if (!isRecord(lineStyle)) throw new Error(`SceneIR.lineStyles[${index}] must be an object`);
requireString(lineStyle.id, `lineStyles[${index}].id`);
requireString(lineStyle.name, `lineStyles[${index}].name`);
requireTuple(lineStyle.color, 3, `lineStyles[${index}].color`);
const alpha = requireNumber(lineStyle.alpha, `lineStyles[${index}].alpha`);
const thickness = requireNumber(lineStyle.thickness, `lineStyles[${index}].thickness`);
requireNumber(lineStyle.chaining, `lineStyles[${index}].chaining`);
requireNumber(lineStyle.caps, `lineStyles[${index}].caps`);
requireBoolean(lineStyle.dashed, `lineStyles[${index}].dashed`);
if (!isRecord(lineStyle.modifierCounts)) throw new Error(`SceneIR.lineStyles[${index}].modifierCounts must be an object`);
for (const field of ["color", "alpha", "thickness", "geometry"] as const) {
const count = requireNumber(lineStyle.modifierCounts[field], `lineStyles[${index}].modifierCounts.${field}`);
if (!Number.isSafeInteger(count) || count < 0 || count > 1024) throw new Error(`SceneIR.lineStyles[${index}].modifierCounts.${field} is outside budget`);
}
if (alpha < 0 || alpha > 1 || thickness < 0 || thickness > 10000) throw new Error(`SceneIR.lineStyles[${index}] is outside bounds`);
}
}
if (value.lattices !== undefined) {
const lattices = requireArray(value.lattices, "lattices");
if (lattices.length > 4096) throw new Error("SceneIR.lattices exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, lattice] of lattices.entries()) {
if (!isRecord(lattice)) throw new Error(`SceneIR.lattices[${index}] must be an object`);
const id = requireString(lattice.id, `lattices[${index}].id`);
if (!id.startsWith("lattice:") || ids.has(id)) throw new Error(`SceneIR.lattices[${index}].id is invalid`);
ids.add(id);
requireString(lattice.name, `lattices[${index}].name`);
if (!Array.isArray(lattice.dimensions) || lattice.dimensions.length !== 3 || lattice.dimensions.some((item) => !Number.isSafeInteger(item) || item < 1 || item > 64)) throw new Error(`SceneIR.lattices[${index}].dimensions is invalid`);
const pointCount = requireNumber(lattice.pointCount, `lattices[${index}].pointCount`);
const expectedCount = (lattice.dimensions as number[]).reduce((product, item) => product * item, 1);
if (!Number.isSafeInteger(pointCount) || pointCount !== expectedCount || pointCount > 64 * 64 * 64) throw new Error(`SceneIR.lattices[${index}].pointCount is invalid`);
const interpolation = ["KEY_LINEAR", "KEY_CARDINAL", "KEY_BSPLINE", "KEY_CATMULL_ROM"];
if (!Array.isArray(lattice.interpolation) || lattice.interpolation.length !== 3 || lattice.interpolation.some((item) => !interpolation.includes(item as string))) throw new Error(`SceneIR.lattices[${index}].interpolation is invalid`);
requireBoolean(lattice.useOutside, `lattices[${index}].useOutside`);
const activePoint = requireNumber(lattice.activePoint, `lattices[${index}].activePoint`);
if (!Number.isSafeInteger(activePoint) || activePoint < -1 || activePoint >= pointCount) throw new Error(`SceneIR.lattices[${index}].activePoint is invalid`);
requireString(lattice.vertexGroup, `lattices[${index}].vertexGroup`);
if (lattice.status !== "AVAILABLE" && lattice.status !== "BLOCKED") throw new Error(`SceneIR.lattices[${index}].status is invalid`);
if (lattice.status === "AVAILABLE") {
if (!Array.isArray(lattice.points) || lattice.points.length !== pointCount) throw new Error(`SceneIR.lattices[${index}].points is incomplete`);
for (const [pointIndex, point] of lattice.points.entries()) {
if (!isRecord(point)) throw new Error(`SceneIR.lattices[${index}].points[${pointIndex}] is invalid`);
requireTuple(point.coDeform, 3, `lattices[${index}].points[${pointIndex}].coDeform`);
const weight = requireNumber(point.weight, `lattices[${index}].points[${pointIndex}].weight`);
if (weight < 0.01 || weight > 100) throw new Error(`SceneIR.lattices[${index}].points[${pointIndex}].weight is invalid`);
requireBoolean(point.selected, `lattices[${index}].points[${pointIndex}].selected`);
}
if (lattice.errorCode !== undefined) throw new Error(`SceneIR.lattices[${index}] available data cannot contain errorCode`);
}
else if (!lattice.errorCode || lattice.points !== undefined) {
throw new Error(`SceneIR.lattices[${index}] blocked data is incomplete`);
}
}
}
if (value.nonMeshData !== undefined) requireArray(value.nonMeshData, "nonMeshData");
if (value.particleSettings !== undefined) {
const settings = requireArray(value.particleSettings, "particleSettings");
if (settings.length > 4096) throw new Error("SceneIR.particleSettings exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, setting] of settings.entries()) {
if (!isRecord(setting)) throw new Error(`SceneIR.particleSettings[${index}] must be an object`);
const id = requireString(setting.id, `particleSettings[${index}].id`);
if (!id.startsWith("particle-settings:") || ids.has(id)) throw new Error(`SceneIR.particleSettings[${index}].id is invalid`);
ids.add(id);
requireString(setting.name, `particleSettings[${index}].name`);
for (const field of ["type", "from", "distribution", "physicsType", "totalParticles"] as const) {
const number = requireNumber(setting[field], `particleSettings[${index}].${field}`);
if (!Number.isSafeInteger(number) || number < 0) throw new Error(`SceneIR.particleSettings[${index}].${field} is invalid`);
}
for (const field of ["start", "end", "lifetime", "size", "drawSize"] as const) {
const number = requireNumber(setting[field], `particleSettings[${index}].${field}`);
if (number < 0 || number > 1_000_000) throw new Error(`SceneIR.particleSettings[${index}].${field} is invalid`);
}
}
}
if (value.sounds !== undefined) {
const sounds = requireArray(value.sounds, "sounds");
if (sounds.length > 4096) throw new Error("SceneIR.sounds exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, sound] of sounds.entries()) {
if (!isRecord(sound)) throw new Error(`SceneIR.sounds[${index}] must be an object`);
const id = requireString(sound.id, `sounds[${index}].id`);
if (!id.startsWith("sound:") || ids.has(id)) throw new Error(`SceneIR.sounds[${index}].id is invalid`);
ids.add(id);
requireString(sound.name, `sounds[${index}].name`);
requireString(sound.sourcePath, `sounds[${index}].sourcePath`);
requireBoolean(sound.packed, `sounds[${index}].packed`);
for (const field of ["volume", "pitch"] as const) {
const number = requireNumber(sound[field], `sounds[${index}].${field}`);
if (number < 0 || number > 1000) throw new Error(`SceneIR.sounds[${index}].${field} is outside bounds`);
}
for (const field of ["audioChannels", "sampleRate"] as const) {
const number = requireNumber(sound[field], `sounds[${index}].${field}`);
if (!Number.isSafeInteger(number) || number < 0 || number > 1_000_000) throw new Error(`SceneIR.sounds[${index}].${field} is invalid`);
}
if (sound.packedByteLength !== undefined && (!Number.isSafeInteger(sound.packedByteLength) || (sound.packedByteLength as number) < 0)) throw new Error(`SceneIR.sounds[${index}].packedByteLength is invalid`);
if (sound.packed && sound.packedByteLength === undefined) throw new Error(`SceneIR.sounds[${index}] packed identity is incomplete`);
}
}
if (value.speakers !== undefined) {
const speakers = requireArray(value.speakers, "speakers");
if (speakers.length > 4096) throw new Error("SceneIR.speakers exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, speaker] of speakers.entries()) {
if (!isRecord(speaker)) throw new Error(`SceneIR.speakers[${index}] must be an object`);
const id = requireString(speaker.id, `speakers[${index}].id`);
if (!id.startsWith("speaker:") || ids.has(id)) throw new Error(`SceneIR.speakers[${index}].id is invalid`);
ids.add(id);
requireString(speaker.name, `speakers[${index}].name`);
if (speaker.soundId !== undefined && (!Array.isArray(value.sounds) || typeof speaker.soundId !== "string" || !speaker.soundId.startsWith("sound:"))) {
throw new Error(`SceneIR.speakers[${index}].soundId is invalid`);
}
for (const field of ["volumeMax", "volumeMin", "distanceMax", "distanceReference", "attenuation"] as const) {
const number = requireNumber(speaker[field], `speakers[${index}].${field}`);
if (number < 0 || number > 1_000_000_000) throw new Error(`SceneIR.speakers[${index}].${field} is outside bounds`);
}
for (const field of ["coneAngleOuter", "coneAngleInner"] as const) {
const number = requireNumber(speaker[field], `speakers[${index}].${field}`);
if (number < 0 || number > 360) throw new Error(`SceneIR.speakers[${index}].${field} is outside bounds`);
}
const coneVolumeOuter = requireNumber(speaker.coneVolumeOuter, `speakers[${index}].coneVolumeOuter`);
if (coneVolumeOuter < 0 || coneVolumeOuter > 1) throw new Error(`SceneIR.speakers[${index}].coneVolumeOuter is outside bounds`);
for (const field of ["volume", "pitch"] as const) {
const number = requireNumber(speaker[field], `speakers[${index}].${field}`);
if (number < 0 || number > 1000) throw new Error(`SceneIR.speakers[${index}].${field} is outside bounds`);
}
}
}
if (value.texts !== undefined) {
const texts = requireArray(value.texts, "texts");
if (texts.length > 4096) throw new Error("SceneIR.texts exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, text] of texts.entries()) {
if (!isRecord(text)) throw new Error(`SceneIR.texts[${index}] must be an object`);
const id = requireString(text.id, `texts[${index}].id`);
if (!id.startsWith("text:") || ids.has(id)) throw new Error(`SceneIR.texts[${index}].id is invalid`);
ids.add(id);
requireString(text.name, `texts[${index}].name`);
const source = requireString(text.source, `texts[${index}].source`);
const sourceSha256 = requireString(text.sourceSha256, `texts[${index}].sourceSha256`);
if (!/^[a-f0-9]{64}$/.test(sourceSha256)) throw new Error(`SceneIR.texts[${index}].sourceSha256 is invalid`);
const byteLength = requireNumber(text.byteLength, `texts[${index}].byteLength`);
const lineCount = requireNumber(text.lineCount, `texts[${index}].lineCount`);
if (!Number.isSafeInteger(byteLength) || byteLength !== new TextEncoder().encode(source).byteLength || byteLength > 1_048_576) throw new Error(`SceneIR.texts[${index}].byteLength is invalid`);
if (!Number.isSafeInteger(lineCount) || lineCount < 1 || lineCount > 65_536) throw new Error(`SceneIR.texts[${index}].lineCount is invalid`);
requireBoolean(text.internal, `texts[${index}].internal`);
if (text.sourcePath !== undefined) requireString(text.sourcePath, `texts[${index}].sourcePath`);
requireBoolean(text.isDirty, `texts[${index}].isDirty`);
requireBoolean(text.useModule, `texts[${index}].useModule`);
}
}
if (value.textures !== undefined) {
const textures = requireArray(value.textures, "textures");
if (textures.length > 4096) throw new Error("SceneIR.textures exceeds the data-block budget");
const ids = new Set<string>();
for (const [index, texture] of textures.entries()) {
if (!isRecord(texture)) throw new Error(`SceneIR.textures[${index}] must be an object`);
const id = requireString(texture.id, `textures[${index}].id`);
if (!id.startsWith("texture:") || ids.has(id)) throw new Error(`SceneIR.textures[${index}].id is invalid`);
ids.add(id);
requireString(texture.name, `textures[${index}].name`);
const type = requireNumber(texture.type, `textures[${index}].type`);
const noiseDepth = requireNumber(texture.noiseDepth, `textures[${index}].noiseDepth`);
if (!Number.isSafeInteger(type) || type < 0 || !Number.isSafeInteger(noiseDepth) || noiseDepth < 0 || noiseDepth > 30) throw new Error(`SceneIR.textures[${index}] integer fields are invalid`);
for (const field of ["noiseScale", "intensity", "contrast", "saturation"] as const) {
const number = requireNumber(texture[field], `textures[${index}].${field}`);
if (number < 0 || number > 1000) throw new Error(`SceneIR.textures[${index}].${field} is outside bounds`);
}
}
}
if (value.windowManager !== undefined) {
if (!isRecord(value.windowManager)) throw new Error("SceneIR.windowManager must be an object");
const manager = value.windowManager;
const id = requireString(manager.id, "windowManager.id");
if (id !== "window-manager:WinMan") throw new Error("SceneIR.windowManager.id is invalid");
requireString(manager.name, "windowManager.name");
requireString(manager.presetName, "windowManager.presetName");
const windowCount = requireNumber(manager.windowCount, "windowManager.windowCount");
if (!Number.isSafeInteger(windowCount) || windowCount < 0 || windowCount > 1024) throw new Error("SceneIR.windowManager.windowCount is invalid");
requireBoolean(manager.interfaceLocked, "windowManager.interfaceLocked");
}
if (value.workspaces !== undefined) {
const workspaces = requireArray(value.workspaces, "workspaces");
const ids = new Set<string>();
for (const [index, workspace] of workspaces.entries()) {
if (!isRecord(workspace)) throw new Error(`SceneIR.workspaces[${index}] must be an object`);
const id = requireString(workspace.id, `workspaces[${index}].id`);
if (!id.startsWith("workspace:") || ids.has(id)) throw new Error(`SceneIR.workspaces[${index}].id is invalid`); ids.add(id);
requireString(workspace.name, `workspaces[${index}].name`); const count = requireNumber(workspace.screenCount, `workspaces[${index}].screenCount`);
if (!Number.isSafeInteger(count) || count < 0 || count > 64) throw new Error(`SceneIR.workspaces[${index}].screenCount is invalid`);
}
}
if (value.greasePencils !== undefined) requireArray(value.greasePencils, "greasePencils");
if (value.libraries !== undefined) requireArray(value.libraries, "libraries");
requireArray(value.animations, "animations");