import { ReplayTimeline, fnv1a64 } from "./replay.mjs"; import { capabilities, validateLimit } from "./capabilities.mjs"; import { mapCoreError } from "./error-map.mjs"; import { createCheckpoint, recoverSimulation } from "../workspace/recovery.mjs"; import { preflightConfiguration } from "../workspace/configuration.mjs"; import { ConfigurationStartupStateMachine } from "../workspace/configuration.mjs"; import { preflightHalConfiguration } from "../workspace/hal-configuration.mjs"; import { DEVICE_COMMANDS, VirtualDevices } from "./virtual-devices.mjs"; import { PythonBridge } from "./python-bridge.mjs"; import { ProgramRunner } from "./program-runner.mjs"; import { SimTask } from "./sim-task.mjs"; import { SimScheduler, SCHEDULER_PHASES } from "./sim-scheduler.mjs"; import { OperationLedger } from "./operation-ledger.mjs"; import { MotionState } from "./motion-state.mjs"; import { PlcRuntime } from "./plc-runtime.mjs"; import { HalRuntime } from "./hal-runtime.mjs"; import { DeviceIoRuntime } from "./device-io.mjs"; import { IoAckTracker } from "./io-ack.mjs"; import { PythonCallbacks } from "./python-callbacks.mjs"; import { createTrace, validateTrace } from "./trace-export.mjs"; import { ConfigurationBatchRuntime } from "../workspace/configuration-batch.mjs"; import { AuthoritativeRuntime } from "./authoritative-runtime.mjs"; import { axisState } from "./axis-state.mjs"; import { WasmMotionQueue } from "./wasm-motion-queue.mjs"; import { WasmHomeLimit } from "./wasm-home-limit.mjs"; import { axisGate } from "../app/axis-contract.mjs"; const PROTOCOL_SCHEMA = 1; const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"; const OPERATION_KINDS = new Set([ "SESSION_ATTACH", "LOAD_WORKSPACE", "SIM_STEP", "DESTROY", "CHECKPOINT", "RECOVER", "ABORT", "COMMAND", "START", "STOP", "RESET", "PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT", "PROGRAM_RESTART", "JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE", "SPINDLE_COMMAND", "TOOL_PREPARE", "TOOL_CHANGED", "TOOL_PREPARED", "TOOL_CHANGE", "TOOL_ABORT", "TOOL_FAULT", "TOOL_CLEAR_FAULT", "SPINDLE_FAULT", "SPINDLE_INHIBIT", "SPINDLE_ORIENT", "SPINDLE_ORIENTED", "IO_STEP", "IO_RECOVER", "PROBE_CONFIG", "LIMIT_SET", "ESTOP", "HAL_COMMAND", "HAL_SET", "ENCODER_SET", "COOLANT_COMMAND", "COOLANT_FAULT", "DOOR_SET", "INTERLOCK_SET", "INTERLOCK_RESET", "DEVICE_ESTOP", "DEVICE_ACK_ISSUE", "DEVICE_ACK", "DEVICE_ACK_ABORT", "CONFIGURATION_BATCH", "PYTHON_IMPORT", "PYTHON_CALL", "PYTHON_CAPABILITY", "PROGRAM_STEP", "PROGRAM_SEEK", "MDI_EXECUTE", "PLC_CONFIG", "PLC_SET", "PLC_TRIGGER", "FOLLOWING_ERROR", "IO_COMMAND", "IO_ACK", "IO_TIMEOUT", "REMAP_REGISTER", "REMAP_CALL", "VCP_REGISTER", "VCP_UPDATE", "FILTER_EXEC", ]); const PANEL_GATE_ACTIONS = Object.freeze({ PROGRAM_RUN: "run", PROGRAM_STEP: "step", PROGRAM_PAUSE: "pause", PROGRAM_RESUME: "resume", PROGRAM_ABORT: "abort", PROGRAM_RESTART: "restart", JOG_START: "jogStart", JOG_STOP: "jogStop", HOME: "home", SET_OVERRIDE: "override", ESTOP: "estop", IO_RECOVER: "recover", }); function text(value) { return typeof value === "string" && value.length > 0 ? value : null; } function call(core, name, ...args) { if (args.some((value) => typeof value === "string") && typeof core.ccall === "function") { return core.ccall(name, "number", args.map((value) => typeof value === "string" ? "string" : "number"), args); } const direct = core[`_${name}`] ?? core[name]; if (typeof direct === "function") return direct(...args); if (typeof core.cwrap === "function") { const wrapped = core.cwrap(name, "number", args.map((value) => typeof value === "string" ? "string" : "number")); return wrapped(...args); } throw new Error(`missing core export: ${name}`); } function appendCoreEvent(core, seq, tick, kind, payload) { if (typeof core.ccall !== "function") return 0; const bytes = new TextEncoder().encode(canonicalPayload(payload)); return core.ccall("sim_event_append", "number", ["number", "number", "string", "array", "number"], [seq, tick, kind, bytes, bytes.length]); } function canonicalPayload(value) { if (value === null || typeof value !== "object") return JSON.stringify(value); if (Array.isArray(value)) return `[${value.map(canonicalPayload).join(",")}]`; return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalPayload(value[key])}`).join(",")}}`; } export class SimSession { constructor(core, { contextId = CONTEXT_ID, sessionId = "" } = {}) { this.core = core; this.contextId = contextId; this.sessionId = sessionId; this.tick = 0; this.created = false; this.timeline = new ReplayTimeline(); this.operations = new Map(); this.ledger = new OperationLedger(); this.devices = new VirtualDevices(); this.motion = new MotionState(); this.plc = new PlcRuntime(); this.hal = new HalRuntime(); this.deviceIo = new DeviceIoRuntime(); this.configurationBatch = new ConfigurationBatchRuntime({ context_id: contextId }); this.ioAck = new IoAckTracker(); this.callbacks = new PythonCallbacks(); this.python = new PythonBridge(); this.program = new ProgramRunner(core); this.authoritative = new AuthoritativeRuntime(); this.wasmMotion = new WasmMotionQueue(core); this.wasmHomeLimit = new WasmHomeLimit(core); this.motionAuthority = "js"; this.task = new SimTask(core, { contextId, sessionId: sessionId || null }); this.scheduler = new SimScheduler(core); this.configurationState = null; this.halState = null; this.previewState = null; call(core, "sim_protocol_reset"); if (typeof core.ccall === "function") core.ccall("sim_event_reset", null, [], []); } envelope(kind, input, payload, tick = this.tick) { const statePayload = payload && typeof payload === "object" ? payload : {}; const program = statePayload.program ?? (this.program.loaded ? this.program.snapshot() : {}); const task = statePayload.task ?? this.task.export(); const panelSnapshot = this.panelSnapshot({ task, program, input, tick }); return { schema_version: PROTOCOL_SCHEMA, kind, context_id: this.contextId, session_id: this.sessionId, seq: input.seq, ...(text(input.operation_id) ? { operation_id: input.operation_id } : {}), simulation_tick: tick, payload: { ...statePayload, panel_snapshot: panelSnapshot, motion_authority: statePayload.motion_authority ?? this.motionAuthority, task_authority: statePayload.task_authority ?? this.task.authority(), wasm_home_limit: statePayload.wasm_home_limit ?? this.wasmHomeLimit.read(), axis_state: axisState({ task, program, action: input.kind, payload: input.payload ?? {} }), }, }; } panelSnapshot({ task = this.task.export(), program = this.program.loaded ? this.program.snapshot() : null, input = {}, tick = this.tick } = {}) { const motion = this.motion.export(); const wasmMotion = this.wasmMotion.read(); const homeLimit = this.wasmHomeLimit.read(); const devices = this.devices.export(); const deviceIo = this.deviceIo.export(); const halRuntime = this.hal.export(); const authoritative = this.authoritative.export(); const axis = axisState({ task, program: program ?? {}, action: input.kind, payload: input.payload ?? {} }); const gateInput = { task, program: program ?? {}, motion, devices, taskfile: Boolean(this.program.loaded), kinematicsIdentity: this.motionAuthority === "wasm" }; const gateActions = ["load", "run", "step", "pause", "resume", "abort", "restart", "mdi", "jogStart", "jogStop", "home", "estop", "recover", "override", "reset", "spindle", "coolant"]; const gates = Object.fromEntries(gateActions.map((action) => [action, axisGate(action, gateInput)])); const position = this.motionAuthority === "wasm" && wasmMotion.status === "verified" ? wasmMotion.state.position : authoritative.kinematics.pose; const joints = this.motionAuthority === "wasm" && wasmMotion.status === "verified" ? [position.x, position.y, position.z] : authoritative.kinematics.joints; const preview = this.previewState?.status === "verified" ? this.previewState : null; const visual = { schema: "axis-visual.v1", pose: { x: position.x, y: position.y, z: position.z }, joints: Array.isArray(joints) ? [...joints] : [], toolpath: (preview?.segments ?? []).map((segment, index) => ({ id: index + 1, ...segment })), active_segment_id: wasmMotion.status === "verified" ? wasmMotion.state.active_id : authoritative.planner.active_id, source: "wasm_canonical_motion", }; const unsigned = { schema: "axis-panel-snapshot.v1", context_id: this.contextId, session_id: this.sessionId, simulation_tick: tick, task, program, motion, coordinates: { machine: [position.x, position.y, position.z], relative: [position.x, position.y, position.z], commanded: [position.x, position.y, position.z] }, overrides: { feed: motion.feed_override, spindle: [motion.spindle_override] }, wasm_motion: wasmMotion, home_limit: homeLimit, hal: { configuration: this.halState, runtime: halRuntime }, devices, device_io: deviceIo, configuration: this.configurationState, axis_state: axis, gates, visual, authoritative, motion_authority: this.motionAuthority, task_authority: this.task.authority(), }; return { ...unsigned, state_hash: fnv1a64(unsigned) }; } error(input, code, messageKey, retryable = false) { return this.envelope("ERROR", input, { status: "error", code, message_key: messageKey, retryable, operation_id: input.operation_id ?? null, simulation_tick: this.tick, }); } coreError(input, result, source) { const mapped = mapCoreError(result, { operation_id: input.operation_id, simulation_tick: this.tick, source }); return this.envelope("ERROR", input, mapped); } operationState(input) { if (!text(input.operation_id) || !OPERATION_KINDS.has(input.kind)) return null; return this.operations.get(input.operation_id) ?? null; } commandGate(input) { const action = PANEL_GATE_ACTIONS[input.kind]; if (!action) return { action: null, enabled: true, reason: "axis_internal_command" }; const task = this.task.export(); const program = this.program.loaded ? this.program.snapshot() : {}; const motion = this.motion.export(); const devices = this.devices.export(); return axisGate(action, { task, program, motion, devices, taskfile: Boolean(this.program.loaded), kinematicsIdentity: this.motionAuthority === "wasm", }); } stateForCheckpoint() { const state = { context_id: this.contextId, session_id: this.sessionId, tick: this.tick, created: this.created, protocol_seq: this.timeline.events.cursor, devices: this.devices.export(), motion: this.motion.export(), plc: this.plc.export(), hal_runtime: this.hal.export(), device_io: this.deviceIo.export(), configuration_batch: this.configurationBatch.export(), io_ack: this.ioAck.export(), callbacks: this.callbacks.export(), configuration: this.configurationState, hal: this.halState, python: this.python.exportState(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.exportState(), task_authority: this.task.authority(), wasm_home_limit: this.wasmHomeLimit.exportState(), preview: this.previewState, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: this.authoritative.export(), ledger: this.ledger.export(), }; if (this.program.loaded) state.program = this.program.exportState(); return state; } workspacePackageState() { const value = { context_id: this.contextId, session_id: this.sessionId, configuration: this.configurationState, hal: this.halState, devices: this.devices.export(), motion: this.motion.export(), plc: this.plc.export(), hal_runtime: this.hal.export(), device_io: this.deviceIo.export(), configuration_batch: this.configurationBatch.export(), io_ack: this.ioAck.export(), callbacks: this.callbacks.export(), python: this.python.exportState(), tick: this.tick, event_cursor: this.timeline.events.cursor, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: this.authoritative.export(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.exportState(), task_authority: this.task.authority(), wasm_home_limit: this.wasmHomeLimit.exportState(), }; if (this.program.loaded) value.program = this.program.exportState(); return { ...value, state_hash: fnv1a64(value) }; } createCheckpoint() { const latest = this.timeline.snapshots.latest(); if (!latest || latest.seq !== this.timeline.events.cursor || latest.tick !== this.timeline.events.tick) { this.timeline.checkpoint( this.timeline.events.cursor, this.timeline.events.tick, this.stateForCheckpoint(), latest?.snapshot_id ?? null, ); } return createCheckpoint({ contextId: this.contextId, timeline: this.timeline, state: this.stateForCheckpoint(), operations: [...this.operations.values()], }); } exportTrace() { const latest = this.timeline.snapshots.latest(); if (!latest || latest.seq !== this.timeline.events.cursor || latest.tick !== this.timeline.events.tick) { this.timeline.checkpoint(this.timeline.events.cursor, this.timeline.events.tick, this.stateForCheckpoint(), latest?.snapshot_id ?? null); } return createTrace({ contextId: this.contextId, timeline: this.timeline, state: this.stateForCheckpoint(), task: this.task.export(), tick: this.tick }); } importTrace(trace) { const validated = validateTrace(trace, { contextId: this.contextId }); this.timeline = validated.timeline; this.created = Boolean(trace.state?.created); if (trace.state?.ledger?.entries) this.ledger = new OperationLedger(trace.state.ledger.entries); if (trace.state?.task) this.task.restore(trace.state.task); if (trace.state?.scheduler) this.scheduler.restore(trace.state.scheduler); if (trace.state?.authoritative) this.authoritative.restore(trace.state.authoritative); if (trace.state?.motion_authority) this.motionAuthority = trace.state.motion_authority; if (trace.state?.wasm_motion) this.wasmMotion.restoreState(trace.state.wasm_motion); if (trace.state?.wasm_home_limit) this.wasmHomeLimit.restoreState(trace.state.wasm_home_limit); if (trace.state?.motion) this.motion.restore(trace.state.motion); if (trace.state?.program) { this.program = new ProgramRunner(this.core); this.program.restoreState(trace.state.program); } if (trace.state?.configuration !== undefined) this.configurationState = trace.state.configuration; if (trace.state?.hal !== undefined) this.halState = trace.state.hal; if (trace.state?.preview !== undefined) this.previewState = trace.state.preview; if (trace.state?.plc) this.plc.restore(trace.state.plc); if (trace.state?.hal_runtime) this.hal.restore(trace.state.hal_runtime); if (trace.state?.device_io) this.deviceIo.restore(trace.state.device_io); if (trace.state?.configuration_batch) this.configurationBatch.restore(trace.state.configuration_batch); if (trace.state?.io_ack) this.ioAck.restore(trace.state.io_ack); if (trace.state?.callbacks) this.callbacks.restore(trace.state.callbacks); if (trace.state?.python) this.python.restoreState(trace.state.python); if (trace.state?.devices) this.devices = new VirtualDevices({ state: trace.state.devices }); this.tick = trace.tick; return { status: "verified", trace_hash: trace.trace_hash, state_hash: fnv1a64(trace.state), seq: trace.seq, tick: trace.tick }; } restoreCheckpoint(checkpoint, events = null, reconcile = undefined) { const replayMotionAuthority = checkpoint?.state?.motion_authority ?? this.motionAuthority; if (replayMotionAuthority === "wasm" && !checkpoint?.state?.wasm_motion) { return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, checkpoint_hash: checkpoint?.checkpoint_hash ?? null, blocked: [{ reason: "wasm_motion_checkpoint_missing" }] }; } const replayDevices = new VirtualDevices({ state: checkpoint?.state?.devices }); const replayMotion = new MotionState(); if (checkpoint?.state?.motion) replayMotion.restore(checkpoint.state.motion); const replayPlc = new PlcRuntime(); if (checkpoint?.state?.plc) replayPlc.restore(checkpoint.state.plc); const replayHal = new HalRuntime(); if (checkpoint?.state?.hal_runtime) replayHal.restore(checkpoint.state.hal_runtime); const replayDeviceIo = new DeviceIoRuntime(); if (checkpoint?.state?.device_io) replayDeviceIo.restore(checkpoint.state.device_io); const replayConfigurationBatch = new ConfigurationBatchRuntime({ context_id: this.contextId }); if (checkpoint?.state?.configuration_batch) replayConfigurationBatch.restore(checkpoint.state.configuration_batch); const replayIoAck = new IoAckTracker(); if (checkpoint?.state?.io_ack) replayIoAck.restore(checkpoint.state.io_ack); const replayCallbacks = new PythonCallbacks(); if (checkpoint?.state?.callbacks) replayCallbacks.restore(checkpoint.state.callbacks); const replayProgram = new ProgramRunner(this.core); if (checkpoint?.state?.program) replayProgram.restoreState(checkpoint.state.program); const replayPython = new PythonBridge(); if (checkpoint?.state?.python) replayPython.restoreState(checkpoint.state.python); const replayAuthoritative = new AuthoritativeRuntime(); if (checkpoint?.state?.authoritative) replayAuthoritative.restore(checkpoint.state.authoritative); let replayWasmMotion = null; let replayHomeLimit = null; try { if (replayMotionAuthority === "wasm") { replayWasmMotion = new WasmMotionQueue(this.core); replayWasmMotion.restoreState(checkpoint.state.wasm_motion); if (checkpoint.state.wasm_home_limit) { replayHomeLimit = new WasmHomeLimit(this.core); replayHomeLimit.restoreState(checkpoint.state.wasm_home_limit); } } } catch (error) { return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, checkpoint_hash: checkpoint?.checkpoint_hash ?? null, blocked: [{ reason: String(error?.message ?? error) }] }; } const result = recoverSimulation({ checkpoint, contextId: this.contextId, events, reconcile, apply: (state, event) => { if (DEVICE_COMMANDS.has(event.kind)) replayDevices.applyEvent(event.kind, event.payload); if (event.kind === "HOME") { replayMotion.home(event.payload?.axis ?? null); const home = replayHomeLimit?.home(event.seq); if (home?.status === "error") throw new Error(home.code); } if (event.kind === "JOG_START") replayMotion.startJog(event.payload); if (event.kind === "JOG_STOP") replayMotion.stopJog(); if (event.kind === "SET_OVERRIDE") replayMotion.setOverride(event.payload?.feed ?? event.payload?.feed_override, event.payload?.spindle ?? event.payload?.spindle_override); if (event.kind === "LIMIT_SET") replayMotion.setLimit(event.payload?.axis, event.payload?.active === true); if (event.kind === "ESTOP") replayMotion.estop(event.payload?.active === true); if (event.kind === "ESTOP") replayDeviceIo.estop(event.payload?.active === true); if (event.kind === "IO_RECOVER") replayDeviceIo.interlockReset(); if (event.kind === "SIM_STEP") replayMotion.step(event.tick); if (event.kind === "PROGRAM_LOAD") replayProgram.load(event.payload?.source ?? event.payload?.text ?? event.payload?.program_source, event.payload?.path ?? null); if (event.kind === "PROGRAM_RUN") { replayProgram.start(); replayWasmMotion?.setPaused(false); } if (event.kind === "PROGRAM_PAUSE") { replayProgram.pause(); replayWasmMotion?.setPaused(true); } if (event.kind === "PROGRAM_RESUME") { replayProgram.resume(); replayWasmMotion?.setPaused(false); } if (event.kind === "PROGRAM_ABORT") { replayProgram.abort(); replayWasmMotion?.abort(); } if (event.kind === "PROGRAM_RESTART") { replayProgram.restart(); replayWasmMotion?.reset(); } if (event.kind === "SIM_STEP" && replayProgram.loaded) { // PROGRAM_LOAD resets the planner clock, while the session clock is // monotonic. Recovery must start this replay at the checkpoint tick // or one logical step would consume the whole historical interval. if (replayMotionAuthority === "wasm") { if (!replayWasmMotion) throw new Error("wasm_motion_replay_unavailable"); const queueTick = replayWasmMotion.read().state.tick; if (replayProgram.status === "paused" && state.task?.program_status === "running") replayProgram.resume(); const cycles = Number.isSafeInteger(event.payload?.cycles) && event.payload.cycles >= 0 ? event.payload.cycles : 1; for (let cycle = 0; cycle < cycles; cycle += 1) { const replayTick = queueTick + cycle + 1; const steps = replayProgram.tick(1); for (const step of steps) { if (!step.motion) continue; const linearNoop = step.motion.motion_type === 1 && ["x", "y", "z", "a", "b", "c", "u", "v", "w"].every((axis) => step.motion.start[axis] === step.motion.end[axis]); if (!linearNoop) { const enqueue = replayWasmMotion.enqueue(step.motion); if (enqueue.status === "error") throw new Error(enqueue.code); } } const stepped = replayWasmMotion.step(); if (stepped.status === "error" && stepped.code !== "wasm_motion_step_-605") throw new Error(stepped.code); if (stepped.status === "verified") { const projected = replayMotion.applyWasmPosition(stepped.state.position, stepped.state.velocity, replayTick); if (projected.status === "error") throw new Error(projected.code); if (replayHomeLimit?.read().state.homed) { const checked = replayHomeLimit.check([stepped.state.position.x, stepped.state.position.y, stepped.state.position.z]); if (checked.status === "error") throw new Error(checked.code); } } } } else { if (Number.isSafeInteger(state.tick) && replayAuthoritative.state.tick < state.tick) replayAuthoritative.state.tick = state.tick; if (replayProgram.status === "paused" && state.task?.program_status === "running") replayProgram.resume(); for (let replayTick = replayAuthoritative.state.tick + 1; replayTick <= event.tick; replayTick += 1) { replayProgram.tick(1); const synced = replayAuthoritative.syncProgram(replayProgram.snapshot()); if (synced.status === "blocked") throw new Error(synced.code); const stepped = replayAuthoritative.step(replayTick); if (stepped.status === "error") throw new Error(stepped.code); } } } else if (event.kind === "SIM_STEP") { if (replayMotionAuthority === "wasm") { const stepped = replayWasmMotion?.step(); if (stepped?.status === "error" && stepped.code !== "wasm_motion_step_-605") throw new Error(stepped.code); } else { const stepped = replayAuthoritative.step(event.tick); if (stepped.status === "error") throw new Error(stepped.code); } } if (event.kind === "PLC_CONFIG") replayPlc.configure(event.payload); if (event.kind === "PLC_SET") replayPlc.setRegister(event.payload?.register, event.payload?.value); if (event.kind === "PLC_TRIGGER") replayPlc.trigger(); if (event.kind === "SIM_STEP") for (let replayTick = replayPlc.state.tick + 1; replayTick <= event.tick; replayTick += 1) replayPlc.scan(replayTick); if (event.kind === "HAL_COMMAND" && event.payload?.action === "configure") replayHal.configure(event.payload); if (event.kind === "HAL_COMMAND" && event.payload?.action === "thread") replayHal.registerThread(event.payload?.name, event.payload?.period_ticks); if (event.kind === "HAL_COMMAND" && event.payload?.action === "component") replayHal.registerComponent(event.payload?.instance, event.payload?.type, event.payload?.thread); if (event.kind === "HAL_COMMAND" && event.payload?.action === "connect") replayHal.connect(event.payload?.source, event.payload?.target); if (event.kind === "HAL_COMMAND" && event.payload?.action === "failure") replayHal.setFailure(event.payload?.instance, event.payload?.failed === true); if (event.kind === "HAL_SET" && event.payload?.target === "pin") replayHal.setPin(event.payload?.name, event.payload?.value, event.payload?.type); if (event.kind === "HAL_SET" && event.payload?.target === "param") replayHal.setParam(event.payload?.name, event.payload?.value, event.payload?.type); if (event.kind === "SIM_STEP") replayHal.step(event.tick); if (event.kind === "ENCODER_SET") replayDeviceIo.encoderSet(event.payload?.channel, event.payload); if (event.kind === "COOLANT_COMMAND") replayDeviceIo.coolantCommand(event.payload); if (event.kind === "COOLANT_FAULT") replayDeviceIo.coolantFault(event.payload?.fault === true); if (event.kind === "DOOR_SET") replayDeviceIo.doorSet(event.payload?.open === true); if (event.kind === "INTERLOCK_SET") replayDeviceIo.interlockSet(event.payload?.input_ok === true); if (event.kind === "INTERLOCK_RESET") replayDeviceIo.interlockReset(); if (event.kind === "DEVICE_ESTOP") replayDeviceIo.estop(event.payload?.active === true); if (event.kind === "DEVICE_ACK_ISSUE") replayDeviceIo.issueAck(event.operation_id ?? event.payload?.operation_id, event.payload?.timeout_ticks ?? 10); if (event.kind === "DEVICE_ACK") replayDeviceIo.ack(event.payload?.operation_id); if (event.kind === "DEVICE_ACK_ABORT") replayDeviceIo.abortAck(event.payload?.operation_id); if (event.kind === "SIM_STEP") replayDeviceIo.step(event.tick); if (event.kind === "CONFIGURATION_BATCH") replayConfigurationBatch.load(event.payload?.entries ?? [], { context_id: this.contextId }); if (event.kind === "IO_COMMAND") replayIoAck.issue(event.payload?.operation_id ?? event.payload?.command_id, event.payload?.kind ?? "IO", event.tick, event.payload?.timeout_ticks ?? 10); if (event.kind === "IO_COMMAND") replayDeviceIo.issueAck(event.payload?.operation_id ?? event.payload?.command_id, event.payload?.timeout_ticks ?? 10); if (event.kind === "IO_ACK") replayIoAck.ack(event.payload?.operation_id, event.tick); if (event.kind === "IO_ACK") replayDeviceIo.ack(event.payload?.operation_id); if (event.kind === "IO_TIMEOUT") replayIoAck.step(event.tick); if (event.kind === "SIM_STEP") replayIoAck.step(event.tick); if (event.kind === "IO_TIMEOUT") replayDeviceIo.ackStep(event.tick); if (event.kind === "REMAP_REGISTER") replayCallbacks.registerRemap(event.payload?.name, event.payload); if (event.kind === "VCP_REGISTER") replayCallbacks.registerVcp(event.payload?.name, event.payload?.pins ?? []); if (event.kind === "FILTER_EXEC") replayCallbacks.filter(event.payload?.name, event.payload?.source ?? "", event.payload?.args ?? []); if (event.kind === "REMAP_CALL") replayCallbacks.invoke("remap", event.payload?.name, event.payload?.phase ?? "prolog", event.payload?.args ?? []); if (event.kind === "VCP_UPDATE") replayCallbacks.updateVcp(event.payload?.name, event.payload?.pin, event.payload?.value); if (event.kind === "PYTHON_IMPORT") replayPython.importModule(event.payload?.module); if (event.kind === "PYTHON_CALL") replayPython.invoke(event.payload?.module, event.payload?.symbol, event.payload?.args ?? []); if (event.kind === "PYTHON_CAPABILITY") replayPython.checkCapability(event.payload?.capability); if (event.kind === "SIM_STEP") replayCallbacks.setTick(event.tick); if (event.kind === "SIM_STEP") replayPython.setTick(event.tick); const task = { ...(state.task ?? this.task.export()), seq: event.seq, simulation_tick: event.tick }; delete task.state_hash; if (event.kind === "ESTOP" && event.payload?.active === true) { task.state = "ESTOP"; } else if (event.kind === "IO_RECOVER") { task.state = "READY"; } else if (event.kind === "PROGRAM_PAUSE") { task.state = "PAUSED"; task.program_status = "paused"; } else if (event.kind === "PROGRAM_RESUME" || event.kind === "PROGRAM_RUN") { task.state = "RUNNING"; task.program_status = "running"; } else if (event.kind === "PROGRAM_ABORT" || event.kind === "ABORT") { task.state = "STOPPED"; task.program_status = "aborted"; } if (replayProgram.loaded) { const programStatus = replayProgram.status === "completed" ? "completed" : replayProgram.status === "running" ? "running" : replayProgram.status === "paused" ? "paused" : replayProgram.status === "aborted" ? "aborted" : "loaded"; task.current_line = replayProgram.currentLine; task.program_status = programStatus; if (programStatus === "completed" && task.state === "RUNNING" && (replayMotionAuthority !== "wasm" || replayWasmMotion?.read().state.queue_depth === 0)) task.state = "STOPPED"; } const schedulerUnsigned = { schema: "sim_scheduler.v1", budget: state.scheduler?.budget ?? 1000000, phase_trace: [...SCHEDULER_PHASES], tick: event.tick, }; const scheduler = { ...schedulerUnsigned, last_cycles: event.kind === "SIM_STEP" && Number.isSafeInteger(event.payload?.cycles) ? event.payload.cycles : (state.scheduler?.last_cycles ?? 0), state_hash: fnv1a64(schedulerUnsigned), }; return { ...state, tick: event.tick, motion_authority: replayMotionAuthority, task_authority: replayMotionAuthority === "wasm" ? "wasm_task_canonical" : "js_compat", task: { ...task, state_hash: fnv1a64(task) }, scheduler, devices: replayDevices.export(), motion: replayMotion.export(), ...(replayWasmMotion ? { wasm_motion: replayWasmMotion.exportState() } : {}), ...(replayHomeLimit ? { wasm_home_limit: replayHomeLimit.exportState() } : {}), authoritative: replayAuthoritative.export(), plc: replayPlc.export(), hal_runtime: replayHal.export(), device_io: replayDeviceIo.export(), configuration_batch: replayConfigurationBatch.export(), io_ack: replayIoAck.export(), callbacks: replayCallbacks.export(), python: replayPython.exportState(), ...(replayProgram.loaded ? { program: replayProgram.exportState() } : {}) }; }, }); replayWasmMotion?.destroy(); replayHomeLimit?.destroy(); if (result.status === "recovered" || result.status === "aborted") { this.tick = result.simulation_tick; this.motionAuthority = result.state?.motion_authority ?? checkpoint?.state?.motion_authority ?? this.motionAuthority; if (this.motionAuthority === "wasm" && result.state?.wasm_motion) this.wasmMotion.restoreState(result.state.wasm_motion); if (this.motionAuthority === "wasm" && result.state?.wasm_home_limit) this.wasmHomeLimit.restoreState(result.state.wasm_home_limit); if (result.state?.task) this.task.restore(result.state.task); if (result.state?.scheduler) this.scheduler.restore(result.state.scheduler); this.authoritative = new AuthoritativeRuntime(); if (result.state?.authoritative) this.authoritative.restore(result.state.authoritative); this.devices = new VirtualDevices({ state: result.state?.devices ?? checkpoint?.state?.devices }); this.motion = new MotionState(); if (result.state?.motion) this.motion.restore(result.state.motion); if (this.motionAuthority !== "wasm") this.authoritative.applyMotion(this.motion); if (this.motionAuthority === "wasm") { const wasmMotion = this.wasmMotion.read(); if (wasmMotion.status !== "verified" || !result.state?.motion || fnv1a64(result.state.motion.axes) !== fnv1a64(wasmMotion.state.position)) throw new Error("wasm_motion_recovery_projection_mismatch"); } this.plc = new PlcRuntime(); if (result.state?.plc) this.plc.restore(result.state.plc); this.hal = new HalRuntime(); if (result.state?.hal_runtime) this.hal.restore(result.state.hal_runtime); this.deviceIo = new DeviceIoRuntime(); if (result.state?.device_io) this.deviceIo.restore(result.state.device_io); this.configurationBatch = new ConfigurationBatchRuntime({ context_id: this.contextId }); if (result.state?.configuration_batch) this.configurationBatch.restore(result.state.configuration_batch); this.ioAck = new IoAckTracker(); if (result.state?.io_ack) this.ioAck.restore(result.state.io_ack); this.callbacks = new PythonCallbacks(); if (result.state?.callbacks) this.callbacks.restore(result.state.callbacks); this.configurationState = result.state?.configuration ?? checkpoint?.state?.configuration ?? null; this.halState = result.state?.hal ?? checkpoint?.state?.hal ?? null; this.previewState = result.state?.preview ?? checkpoint?.state?.preview ?? null; this.python = new PythonBridge(); if (result.state?.python) this.python.restoreState(result.state.python); this.program = new ProgramRunner(this.core); if (result.state?.program) this.program.restoreState(result.state.program); this.timeline = new ReplayTimeline(checkpoint.timeline); if (events?.schema === "replay-timeline.v1") { for (const event of events.events.events.slice(this.timeline.events.cursor)) this.timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash); } else if (Array.isArray(events)) { for (const event of events) this.timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash); } this.operations = new Map(result.operations.map((operation) => [operation.operation_id, operation])); this.ledger = new OperationLedger(); for (const operation of result.operations) { const entry = { ...operation, phase: operation.phase ?? (operation.status === "verified" ? "committed" : operation.status), side_effect_count: operation.side_effect_count ?? (operation.status === "verified" ? 1 : 0) }; try { this.ledger.importEntry(entry); } catch { /* legacy checkpoint entries remain in operations */ } } } return result; } validateEnvelope(input) { if (!input || typeof input !== "object") return ["protocol_argument", "invalid_envelope"]; if (input.schema_version !== PROTOCOL_SCHEMA) return ["protocol_schema", "unsupported_schema"]; if (input.context_id !== this.contextId) return ["protocol_context", "context_mismatch"]; if (!text(input.kind) || !Number.isSafeInteger(input.seq) || input.seq <= 0) return ["protocol_argument", "invalid_sequence_or_kind"]; if (this.sessionId && input.session_id !== this.sessionId) return ["protocol_session", "session_mismatch"]; if (!this.sessionId && text(input.session_id)) { this.sessionId = input.session_id; this.task.bindSession(this.sessionId); } return null; } accept(input) { const invalid = this.validateEnvelope(input); if (invalid) return this.error(input ?? {}, invalid[0], invalid[1]); const result = call(this.core, "sim_protocol_accept", input.schema_version, input.seq, input.kind, input.operation_id ?? "", input.simulation_tick ?? this.tick); if (result !== 0) return this.coreError(input, result, "sim_protocol_accept"); let ledgerPreparation; try { ledgerPreparation = this.ledger.prepare(input); if (ledgerPreparation.replay && ledgerPreparation.entry?.response) { const replayTick = Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick ? input.simulation_tick : this.tick; this.timeline.appendEvent(input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: ledgerPreparation.entry.seq }); const replayEventResult = appendCoreEvent(this.core, input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: ledgerPreparation.entry.seq }); if (replayEventResult !== 0) return this.error(input, "event_log", "core_event_append_failed"); this.tick = replayTick; try { this.task.setCommand(input.seq, replayTick); } catch (error) { return this.error(input, "task_state", String(error?.message ?? error)); } return { ...ledgerPreparation.entry.response, replayed: true, seq: input.seq, ledger: this.ledger.reconcile(input.operation_id) }; } if (ledgerPreparation.entry) this.ledger.execute(input.operation_id); } catch (error) { const ledgerCode = String(error?.message ?? error); try { this.task.setCommand(input.seq, input.simulation_tick ?? this.tick); this.tick = this.task.state.simulation_tick; } catch { /* protocol has already accepted the command; retain ledger error */ } try { this.timeline.appendEvent(input.seq, this.tick, "COMMAND_REJECTED", { code: ledgerCode, operation_id: input.operation_id ?? null }); appendCoreEvent(this.core, input.seq, this.tick, "COMMAND_REJECTED", { code: ledgerCode, operation_id: input.operation_id ?? null }); } catch { /* preserve the original ledger diagnostic */ } return this.error(input, ledgerCode.startsWith("ledger_") ? ledgerCode : "ledger", ledgerCode); } try { this.task.setCommand(input.seq, input.simulation_tick ?? this.tick); this.tick = this.task.state.simulation_tick; } catch (error) { return this.error(input, "task_state", String(error?.message ?? error)); } const existingOperation = this.operationState(input); if (existingOperation?.response) { if (existingOperation.kind !== input.kind) return this.error(input, "recovery_operation_conflict", "operation_id_conflict"); try { const replayTick = Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick ? input.simulation_tick : this.tick; this.timeline.appendEvent(input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: existingOperation.seq }); const replayEventResult = appendCoreEvent(this.core, input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: existingOperation.seq }); if (replayEventResult !== 0) return this.error(input, "event_log", "core_event_append_failed"); this.tick = replayTick; return { ...existingOperation.response, replayed: true, seq: input.seq }; } catch (error) { return this.error(input, "event_log", String(error?.message ?? error)); } } if (text(input.operation_id) && OPERATION_KINDS.has(input.kind)) { this.operations.set(input.operation_id, { operation_id: input.operation_id, status: "executing", seq: input.seq, kind: input.kind, result_hash: null }); } const gate = this.commandGate(input); const handlerOwnsFault = (input.kind === "JOG_START" || input.kind === "JOG_STOP") && Boolean(this.motion.state.fault); const restartAfterCompletion = input.kind === "PROGRAM_RESTART" && this.program.status === "completed"; if (!gate.enabled && !handlerOwnsFault && !restartAfterCompletion) { if (text(input.operation_id)) this.operations.delete(input.operation_id); const rejectedPayload = { command: input.kind, gate_reason: gate.reason, payload: input.payload ?? {} }; try { this.timeline.appendEvent(input.seq, this.tick, "COMMAND_REJECTED", rejectedPayload); const rejectedEvent = appendCoreEvent(this.core, input.seq, this.tick, "COMMAND_REJECTED", rejectedPayload); if (rejectedEvent !== 0) return this.error(input, "event_log", "core_event_append_failed"); } catch (error) { return this.error(input, "event_log", String(error?.message ?? error)); } return this.envelope("ERROR", input, { status: "blocked", code: "axis_gate", message_key: gate.reason, gate_reason: gate.reason, retryable: false, operation_id: input.operation_id ?? null, }); } if (Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick) this.tick = input.simulation_tick; const eventTick = input.kind === "SIM_STEP" && Number.isSafeInteger(input.payload?.cycles) ? this.tick + input.payload.cycles : this.tick; try { this.timeline.appendEvent(input.seq, eventTick, input.kind, input.payload ?? {}); } catch (error) { if (text(input.operation_id)) this.operations.delete(input.operation_id); return this.error(input, "event_log", String(error?.message ?? error)); } const coreEventResult = appendCoreEvent(this.core, input.seq, eventTick, input.kind, input.payload ?? {}); if (coreEventResult !== 0) { if (text(input.operation_id)) this.operations.delete(input.operation_id); return this.error(input, "event_log", "core_event_append_failed"); } const finish = (response, status = "verified") => { if (text(input.operation_id) && OPERATION_KINDS.has(input.kind)) { const operation = this.operations.get(input.operation_id); if (operation) { operation.status = status; operation.result_hash = fnv1a64(response.payload ?? {}); operation.response = response; } try { this.ledger.verify(input.operation_id, response); this.ledger.commit(input.operation_id, input.seq); } catch (error) { const ledgerCode = String(error?.message ?? error); return this.error(input, ledgerCode.startsWith("ledger_") ? ledgerCode : "ledger", ledgerCode); } } return response; }; const deviceResponse = (kind, payload) => { const result = this.devices.command(kind, payload ?? {}); try { if (kind === "ESTOP" && payload?.active === true) this.task.estop(-904); else if (kind === "IO_RECOVER" && result.status === "verified") this.task.recover(); else if (kind === "TOOL_FAULT" && payload?.fault === true) this.task.fault(-905); } catch (error) { return finish(this.error(input, "task_state", String(error?.message ?? error)), "failed"); } if (kind === "ESTOP") this.deviceIo.estop(payload?.active === true); if (kind === "IO_RECOVER" && result.status === "verified") this.deviceIo.interlockReset(); const responseKind = result.status === "verified" ? "COMMAND_RESULT" : "ERROR"; return finish(this.envelope(responseKind, input, { ...result, command: kind, task: this.task.export(), device_io: this.deviceIo.export(), motion: this.motion.export(), operation_id: input.operation_id ?? null, simulation_tick: this.tick, }), result.status === "verified" ? "verified" : "failed"); }; const deviceIoResponse = (kind, payload = {}) => { let result; if (kind === "ENCODER_SET") result = this.deviceIo.encoderSet(payload.channel, payload); else if (kind === "COOLANT_COMMAND") result = this.deviceIo.coolantCommand(payload); else if (kind === "COOLANT_FAULT") result = this.deviceIo.coolantFault(payload.fault === true); else if (kind === "DOOR_SET") result = this.deviceIo.doorSet(payload.open === true); else if (kind === "INTERLOCK_SET") result = this.deviceIo.interlockSet(payload.input_ok === true); else if (kind === "INTERLOCK_RESET") result = this.deviceIo.interlockReset(); else if (kind === "DEVICE_ESTOP") result = this.deviceIo.estop(payload.active === true); else if (kind === "DEVICE_ACK_ISSUE") result = this.deviceIo.issueAck(input.operation_id ?? payload.operation_id, payload.timeout_ticks ?? 10); else if (kind === "DEVICE_ACK_ABORT") result = this.deviceIo.abortAck(payload.operation_id); else if (kind === "DEVICE_ACK") result = this.deviceIo.ack(payload.operation_id); else result = this.deviceIo.fail("device_io_command_unsupported"); const responseKind = result.status === "verified" ? "COMMAND_RESULT" : "ERROR"; return finish(this.envelope(responseKind, input, { ...result, command: kind, device_io: this.deviceIo.export(), task: this.task.export(), operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); }; switch (input.kind) { case "HELLO": return finish(this.envelope("CAPABILITIES", input, capabilities(this.contextId)), "verified"); case "SESSION_ATTACH": if (!this.created) { const createResult = call(this.core, "sim_create", 0, 0); if (createResult !== 0) return finish(this.coreError(input, createResult, "sim_create"), "failed"); this.created = true; } try { this.task.transition("READY"); } catch (error) { return finish(this.error(input, "task_state", String(error?.message ?? error)), "failed"); } return finish(this.envelope("SESSION_READY", input, { state_hash: this.task.hash(), task: this.task.export(), subscriptions: [] }), "verified"); case "SET_MODE": { const mode = String(input.payload?.mode ?? "").toUpperCase(); if (!["MANUAL", "MDI", "AUTO"].includes(mode)) return finish(this.error(input, "task_mode", "task_mode_invalid"), "failed"); try { this.task.setMode(mode); } catch (error) { return finish(this.error(input, "task_mode", String(error?.message ?? error)), "failed"); } return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, mode, task: this.task.export(), operation_id: input.operation_id ?? null })); } case "RESET": { if (["RUNNING", "PAUSED"].includes(this.task.state.state)) return finish(this.error(input, "task_state", "reset_while_program_active"), "failed"); const result = this.motion.resetFault(); if (result.status !== "verified") return finish(this.envelope("ERROR", input, { ...result, command: input.kind, task: this.task.export() }), "failed"); if (this.task.state.state === "FAULT" || this.task.state.state === "STOPPED") this.task.recover(); return finish(this.envelope("COMMAND_RESULT", input, { ...result, command: input.kind, task: this.task.export(), motion: this.motion.export(), operation_id: input.operation_id ?? null })); } case "LOAD_WORKSPACE": { const rootPath = input.payload?.root_path; if (!text(rootPath) || !rootPath.startsWith("/") || rootPath.includes("..")) return finish(this.error(input, "workspace_path", "invalid_workspace_path"), "failed"); let startupLoaded = false; const configuration = input.payload?.configuration ?? null; if (configuration) { const startupWorkspace = configuration.workspace ?? configuration.files ?? configuration.workspace_files; if (startupWorkspace) { const startup = new ConfigurationStartupStateMachine({ workspace: startupWorkspace, entrypoint: configuration.entrypoint, variables: configuration.variables ?? {}, maxDepth: configuration.maxDepth ?? 32, core: this.core, coreCreated: this.created, task: this.task, halManifest: configuration.halManifest ?? configuration.hal_manifest ?? configuration.manifest ?? [], }); const startupReport = startup.run(); if (startupReport.status !== "ready") { return finish(this.envelope("ERROR", input, { status: "blocked", code: "configuration_startup", message_key: "configuration.startup_blocked", retryable: false, startup: startupReport, }), "failed"); } this.configurationState = startupReport; this.created = true; startupLoaded = true; } else { const preflight = preflightConfiguration(input.payload.configuration); if (preflight.status !== "ready") { return finish(this.envelope("ERROR", input, { status: "blocked", code: "configuration_preflight", message_key: "configuration.preflight_blocked", retryable: false, preflight, }), "failed"); } this.configurationState = { entrypoint: input.payload.configuration.entrypoint ?? null, status: preflight.status, resource_count: preflight.resource_count, state_hash: fnv1a64(preflight), }; } } this.motionAuthority = configuration?.entrypoint?.includes("3axis-tutorial") ? "wasm" : "js"; if (configuration?.home_limits && this.motionAuthority === "wasm") { this.wasmHomeLimit.destroy(); this.wasmHomeLimit = new WasmHomeLimit(this.core, { limits: configuration.home_limits }); } if (input.payload?.hal) { const preflight = preflightHalConfiguration(input.payload.hal); if (preflight.status !== "ready") { return finish(this.envelope("ERROR", input, { status: "blocked", code: "hal_preflight", message_key: "hal.preflight_blocked", retryable: false, preflight, }), "failed"); } this.halState = { status: preflight.status, record_count: preflight.record_count, state_hash: fnv1a64(preflight), }; } const configuredKinematics = input.payload?.kinematics ?? configuration?.kinematics?.type ?? null; if (configuredKinematics) { const kinematics = this.authoritative.setKinematics(configuredKinematics); if (kinematics.status === "blocked") return finish(this.envelope("ERROR", input, { status: "blocked", code: kinematics.code, kinematics: this.authoritative.export() }), "failed"); } const loadResult = startupLoaded ? 0 : call(this.core, "sim_load_workspace", rootPath); return loadResult === 0 ? finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, motion_authority: this.motionAuthority, state_hash: this.task.hash(), task: this.task.export(), ...(this.configurationState?.schema === "configuration-startup-runtime.v1" ? { configuration: this.configurationState } : {}), })) : finish(this.coreError(input, loadResult, "sim_load_workspace"), "failed"); } case "SIM_STEP": { const cycles = input.payload?.cycles; const limit = validateLimit("max_tick_step", cycles); if (limit.status !== "accepted") return finish(this.envelope("ERROR", input, { ...limit, operation_id: input.operation_id ?? null, simulation_tick: this.tick, retryable: false }), "failed"); let program_steps = []; try { this.scheduler.step(cycles, { task: () => { const steps = this.program.tick(1); program_steps.push(...steps); for (const step of steps) { if (!step.motion) continue; const linearNoop = step.motion.motion_type === 1 && ["x", "y", "z", "a", "b", "c", "u", "v", "w"].every((axis) => step.motion.start[axis] === step.motion.end[axis]); if (!linearNoop) { const enqueue = this.wasmMotion.enqueue(step.motion); if (enqueue.status === "error") throw new Error(enqueue.code); } } }, tp: (nextTick) => { if (this.motionAuthority !== "wasm" && this.program.loaded) { const sync = this.authoritative.syncProgram(this.program.snapshot()); if (sync.status === "blocked") throw new Error(sync.code); } if (this.motionAuthority !== "wasm") { const planner = this.authoritative.step(nextTick); if (planner.status === "error") throw new Error(planner.code); } }, kinematics: () => { if (this.motionAuthority !== "wasm" && !["trivkins", "corexy", "tripod", "lineardelta", "rotarydelta"].includes(this.authoritative.state.kinematics.type)) throw new Error("authoritative_kinematics_unsupported"); }, motion: (nextTick) => { const stepResult = call(this.core, "sim_step", 1); if (stepResult !== 0) throw new Error(`core_sim_step_${stepResult}`); this.tick = nextTick; this.task.setTick(nextTick); const motionState = this.motion.step(nextTick); const wasmMotion = this.wasmMotion.step(); if (wasmMotion.status === "error" && wasmMotion.code !== "wasm_motion_step_-605") throw new Error(wasmMotion.code); if (motionState.fault && this.task.state.state === "RUNNING") this.task.fault(-1205); if (this.motionAuthority === "wasm") { if (wasmMotion.status === "verified") { const position = this.motion.applyWasmPosition(wasmMotion.state.position, wasmMotion.state.velocity, nextTick); if (position.status === "error") throw new Error(position.code); } } else if (this.program.loaded) this.authoritative.applyMotion(this.motion); if (this.motionAuthority === "wasm" && this.wasmHomeLimit.read().state.homed && !motionState.fault) { const homeCheck = this.wasmHomeLimit.check(this.motion.state.joints); if (homeCheck.status === "error") throw new Error(homeCheck.code); } const halState = this.hal.step(nextTick); if (halState.status === "error") throw new Error(halState.code); const deviceIoState = this.deviceIo.step(nextTick); if (deviceIoState.status === "blocked") throw new Error(deviceIoState.code); const plcState = this.plc.scan(nextTick); if (plcState.status === "error") throw new Error(plcState.code); const ackState = this.ioAck.step(nextTick); if (ackState.status === "error") throw new Error(ackState.code); const callbackClock = this.callbacks.setTick(nextTick); if (callbackClock.status === "blocked") throw new Error(callbackClock.code); this.python.setTick(nextTick); const protocolTick = call(this.core, "sim_protocol_set_tick", nextTick); if (protocolTick !== 0) throw new Error(`protocol_tick_${protocolTick}`); }, }); } catch (error) { return finish(this.error(input, "scheduler", String(error?.message ?? error)), "failed"); } const program = this.program.loaded ? this.program.snapshot() : null; if (program) { const status = program.program_status === "completed" ? "completed" : program.program_status === "running" ? "running" : program.program_status; this.task.setProgram(status, program.line); if (status === "completed" && this.task.state.state === "RUNNING" && (this.motionAuthority !== "wasm" || this.wasmMotion.read().state.queue_depth === 0)) this.task.transition("STOPPED"); } const snapshotState = { tick: this.tick, cycles, motion_authority: this.motionAuthority, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: this.authoritative.export(), wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), plc: this.plc.export(), hal_runtime: this.hal.export(), device_io: this.deviceIo.export(), configuration_batch: this.configurationBatch.export(), io_ack: this.ioAck.export(), callbacks: this.callbacks.export() }; if (program) snapshotState.program = program; const snapshot = this.timeline.checkpoint(input.seq, this.tick, snapshotState, this.timeline.snapshots.latest()?.snapshot_id ?? null); return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", cycles, program_steps, motion_authority: this.motionAuthority, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: this.authoritative.export(), wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), plc: this.plc.export(), hal_runtime: this.hal.export(), device_io: this.deviceIo.export(), configuration_batch: this.configurationBatch.export(), io_ack: this.ioAck.export(), callbacks: this.callbacks.export(), ...(program ? { program } : {}), state_hash: snapshot.state_hash, snapshot })); } case "PROGRAM_LOAD": { if (["RUNNING", "PAUSED"].includes(this.task.state.state)) return finish(this.error(input, "task_state", "program_load_while_active"), "failed"); const source = input.payload?.source ?? input.payload?.text ?? input.payload?.program_source; const result = this.program.load(source, input.payload?.path ?? null); if (result.status === "verified") { this.authoritative.reset(); this.wasmMotion.reset(); this.wasmMotion.setPaused(false); this.motion.applyWasmPosition(this.wasmMotion.read().state.position, 0, this.tick); this.task.setProgram("loaded", 0); const preview = this.program.preview(); result.preview = preview.preview ?? { status: "error", code: preview.code ?? "program_preview_failed" }; this.previewState = result.preview; } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "PROGRAM_RUN": { const result = this.program.start(); if (result.status === "verified") { this.task.setMode("AUTO"); if (this.task.state.state === "STOPPED") this.task.recover(); if (this.motionAuthority === "wasm") this.wasmMotion.setPaused(false); this.task.transition("RUNNING"); this.task.setProgram("running", this.program.currentLine); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "PROGRAM_PAUSE": case "PROGRAM_RESUME": case "PROGRAM_ABORT": { const action = input.kind === "PROGRAM_PAUSE" ? "pause" : input.kind === "PROGRAM_RESUME" ? "resume" : "abort"; const result = this.program[action](); if (result.status === "verified") { if (action === "pause") { if (this.motionAuthority === "wasm") this.wasmMotion.setPaused(true); this.task.transition("PAUSED"); } else if (action === "resume") { if (this.motionAuthority === "wasm") this.wasmMotion.setPaused(false); this.task.transition("RUNNING"); } else { if (this.motionAuthority === "wasm") this.wasmMotion.abort(); else this.authoritative.abort(); this.task.transition("STOPPED"); this.task.setProgram("aborted", this.program.currentLine); } } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "PROGRAM_RESTART": { const result = this.program.restart(); if (result.status === "verified") { if (["ESTOP", "FAULT"].includes(this.task.state.state)) return finish(this.error(input, "task_state", "program_restart_faulted"), "failed"); if (["RUNNING", "PAUSED"].includes(this.task.state.state)) this.task.transition("STOPPED"); if (this.task.state.state === "STOPPED") this.task.recover(); this.task.setMode("AUTO"); this.authoritative.reset(); this.wasmMotion.reset(); this.wasmMotion.setPaused(false); this.motion.applyWasmPosition(this.wasmMotion.read().state.position, 0, this.tick); this.task.setProgram("loaded", 0); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "PROGRAM_STEP": { if (this.task.state.state === "READY") { this.task.setMode("AUTO"); this.task.transition("RUNNING"); } const result = this.program.step(); if (result.status === "verified") { if (this.motionAuthority === "wasm" && result.motion) { const linearNoop = result.motion.motion_type === 1 && ["x", "y", "z", "a", "b", "c", "u", "v", "w"].every((axis) => result.motion.start[axis] === result.motion.end[axis]); if (!linearNoop) { const enqueue = this.wasmMotion.enqueue(result.motion); if (enqueue.status === "error") return finish(this.error(input, "motion_queue", enqueue.code), "failed"); } this.wasmMotion.setPaused(true); } this.task.setProgram(result.program_status === "completed" ? "completed" : "paused", result.line); if (result.program_status === "completed") { const pending = this.motionAuthority === "wasm" ? this.wasmMotion.read().state.queue_depth > 0 : this.authoritative.state.planner.queue_depth > 0; if (this.task.state.state === "RUNNING") this.task.transition(pending ? "PAUSED" : "STOPPED"); } else if (this.task.state.state === "RUNNING") this.task.transition("PAUSED"); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), motion_authority: this.motionAuthority, wasm_motion: this.wasmMotion.read(), motion: this.motion.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "PROGRAM_SEEK": { const result = this.program.seek(input.payload?.line); if (result.status === "verified") this.task.setProgram("paused", result.line); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "MDI_EXECUTE": { if (["RUNNING", "PAUSED"].includes(this.task.state.state)) return finish(this.error(input, "task_state", "mdi_while_program_active"), "failed"); if (this.task.state.state === "STOPPED") this.task.recover(); const result = this.program.executeBlock(input.payload?.block ?? input.payload?.command); if (result.status === "verified") { this.task.setMode("MDI"); this.task.setProgram("loaded", this.program.currentLine); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null, }), result.status === "verified" ? "verified" : "failed"); } case "HOME": { const result = this.motion.home(input.payload?.axis ?? null); if (result.status === "verified" && this.motionAuthority === "wasm") { const home = this.wasmHomeLimit.home(input.seq); if (home.status === "error") return finish(this.error(input, "home_limit", home.code), "failed"); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, motion: this.motion.export(), task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "JOG_START": case "JOG_STOP": case "SET_OVERRIDE": case "FOLLOWING_ERROR": { let result; if (input.kind === "JOG_START" && this.motionAuthority === "wasm" && !this.wasmHomeLimit.read().state.homed) return finish(this.error(input, "motion_state", "motion_unhomed"), "failed"); if (input.kind === "JOG_START") result = this.motion.startJog(input.payload); else if (input.kind === "JOG_STOP") result = this.motion.stopJog(); else if (input.kind === "SET_OVERRIDE") result = this.motion.setOverride(input.payload?.feed ?? input.payload?.feed_override, input.payload?.spindle ?? input.payload?.spindle_override); else result = this.motion.setFollowingError(input.payload?.value ?? input.payload?.following_error); if (result.status === "error" && result.state?.fault && this.task.state.state === "RUNNING") this.task.fault(-1205); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, motion: this.motion.export(), task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "SPINDLE_COMMAND": case "SPINDLE_FAULT": case "SPINDLE_INHIBIT": case "SPINDLE_ORIENT": case "SPINDLE_ORIENTED": case "IO_STEP": case "TOOL_PREPARE": case "TOOL_PREPARED": case "TOOL_CHANGE": case "TOOL_CHANGED": case "TOOL_ABORT": case "TOOL_FAULT": case "TOOL_CLEAR_FAULT": case "PROBE_CONFIG": case "IO_RECOVER": return deviceResponse(input.kind, input.payload); case "LIMIT_SET": { const result = this.motion.setLimit(input.payload?.axis, input.payload?.active === true); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "PLC_CONFIG": case "PLC_SET": case "PLC_TRIGGER": { const result = input.kind === "PLC_CONFIG" ? this.plc.configure(input.payload) : input.kind === "PLC_SET" ? this.plc.setRegister(input.payload?.register, input.payload?.value) : this.plc.trigger(); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), plc: this.plc.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "CONFIGURATION_BATCH": { const result = this.configurationBatch.load(input.payload?.entries, { context_id: this.contextId }); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, configuration_batch: this.configurationBatch.export(), task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "HAL_COMMAND": case "HAL_SET": { const payload = input.payload ?? {}; let result; if (input.kind === "HAL_SET") { result = payload.target === "param" ? this.hal.setParam(payload.name, payload.value, payload.type) : this.hal.setPin(payload.name, payload.value, payload.type); } else if (payload.action === "configure") { result = this.hal.configure(payload); } else if (payload.action === "thread") { result = this.hal.registerThread(payload.name, payload.period_ticks); } else if (payload.action === "component") { result = this.hal.registerComponent(payload.instance ?? payload.name, payload.type, payload.thread ?? "servo-thread"); } else if (payload.action === "connect") { result = this.hal.connect(payload.source, payload.target); } else if (payload.action === "failure") { result = this.hal.setFailure(payload.instance, payload.failed === true); } else if (payload.action === "step") { result = this.hal.step(payload.tick ?? this.tick); } else { result = this.hal.fail("hal_command_unsupported"); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, task: this.task.export(), hal_runtime: this.hal.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "ENCODER_SET": case "COOLANT_COMMAND": case "COOLANT_FAULT": case "DOOR_SET": case "INTERLOCK_SET": case "INTERLOCK_RESET": case "DEVICE_ESTOP": case "DEVICE_ACK_ISSUE": case "DEVICE_ACK": case "DEVICE_ACK_ABORT": return deviceIoResponse(input.kind, input.payload); case "IO_COMMAND": case "IO_ACK": case "IO_TIMEOUT": { let result; if (input.kind === "IO_COMMAND") { result = this.ioAck.issue(input.operation_id, input.payload?.kind ?? "IO", this.tick, input.payload?.timeout_ticks ?? 10); if (result.status === "verified") this.deviceIo.issueAck(input.operation_id, input.payload?.timeout_ticks ?? 10); } else if (input.kind === "IO_ACK") { result = this.ioAck.ack(input.payload?.operation_id, this.tick); this.deviceIo.ack(input.payload?.operation_id); } else { const targetTick = this.tick + (input.payload?.ticks ?? 0); result = this.ioAck.step(targetTick); this.deviceIo.ackStep(targetTick); } return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, io_ack: this.ioAck.export(), device_io: this.deviceIo.export(), task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "REMAP_REGISTER": case "REMAP_CALL": case "VCP_REGISTER": case "VCP_UPDATE": case "FILTER_EXEC": { const result = input.kind === "REMAP_REGISTER" ? this.callbacks.registerRemap(input.payload?.name, input.payload) : input.kind === "REMAP_CALL" ? this.callbacks.invoke("remap", input.payload?.name, input.payload?.phase ?? "prolog", input.payload?.args ?? []) : input.kind === "VCP_REGISTER" ? this.callbacks.registerVcp(input.payload?.name, input.payload?.pins ?? []) : input.kind === "VCP_UPDATE" ? this.callbacks.updateVcp(input.payload?.name, input.payload?.pin, input.payload?.value) : this.callbacks.filter(input.payload?.name, input.payload?.source ?? "", input.payload?.args ?? []); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, callbacks: this.callbacks.export(), task: this.task.export(), command: input.kind, operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); } case "ESTOP": { const motionResult = this.motion.estop(input.payload?.active === true); const deviceResult = deviceResponse(input.kind, input.payload); if (motionResult.status === "error") return finish(this.envelope("ERROR", input, { ...motionResult, task: this.task.export() }), "failed"); return deviceResult; } case "PYTHON_IMPORT": { const result = this.python.importModule(input.payload?.module); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, result), result.status === "verified" ? "verified" : "failed"); } case "PYTHON_CALL": { const result = this.python.invoke(input.payload?.module, input.payload?.symbol, input.payload?.args ?? []); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, result), result.status === "verified" ? "verified" : "failed"); } case "PYTHON_CAPABILITY": { const result = this.python.checkCapability(input.payload?.capability); return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, result), result.status === "verified" ? "verified" : "failed"); } case "SNAPSHOT_REQUEST": return finish(this.envelope("STATE_SNAPSHOT", input, { snapshot_schema: 1, tick: this.tick, task: this.task.export(), state_hash: this.task.hash(), snapshot: this.timeline.snapshots.latest() })); case "CHECKPOINT": { try { const operation = text(input.operation_id) ? this.operations.get(input.operation_id) : null; if (operation) operation.status = "verified"; const checkpoint = this.createCheckpoint(); return finish(this.envelope("CHECKPOINT_READY", input, { status: "verified", checkpoint, state_hash: checkpoint.checkpoint_hash })); } catch (error) { return finish(this.error(input, "recovery_checkpoint", String(error?.message ?? error)), "failed"); } } case "RECOVER": { const result = this.restoreCheckpoint(input.payload?.checkpoint, input.payload?.events, input.payload?.reconcile); return finish(this.envelope("RECOVERY_STATUS", input, result), result.status === "blocked" ? "failed" : "verified"); } case "ABORT": { const target = input.payload?.operation_id; const operation = text(target) ? this.operations.get(target) : null; if (operation && operation.status !== "verified") operation.status = "aborted"; let ledgerResult = null; if (text(target)) { try { ledgerResult = this.ledger.abort(target); } catch (error) { ledgerResult = { status: "missing", error: String(error?.message ?? error) }; } } if (this.task.state.state !== "DESTROYED") { try { this.task.transition("STOPPED"); } catch { /* target abort can be bookkeeping-only */ } } return finish(this.envelope("COMMAND_RESULT", input, { status: ledgerResult?.status === "too_late" ? "too_late" : operation ? "aborted" : "verified", operation_id: target ?? null, ledger: ledgerResult })); } case "DESTROY": this.program.destroy(); this.task.destroy(); this.scheduler.destroy(); call(this.core, "sim_destroy"); this.created = false; return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", destroyed: true, replay: this.timeline.export() })); default: return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind })); } } } export { CONTEXT_ID, PROTOCOL_SCHEMA };