import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs"; import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs"; const bundle = await loadSimulationCase({ path: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", fetchImpl: async (url) => { try { const source = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url), "utf8"); return { ok: true, text: async () => source }; } catch { return { ok: false, text: async () => "" }; } } }); const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) }); const session = new XyzacSession(core, { sessionId: "axis-source-execution" }); let seq = 0; const send = (kind, payload = {}) => session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, seq: ++seq, operation_id: `source-exec-${seq}`, kind, payload }); const ok = (kind, payload = {}) => { const response = send(kind, payload); assert.notEqual(response.kind, "ERROR", `${kind}: ${response.payload?.gate_reason ?? response.payload?.message_key ?? response.payload?.code}`); return response.payload; }; const panel = () => ok("SNAPSHOT_REQUEST").panel_snapshot; const drain = () => { for (let n = 0; n < 2000 && session.wasmMotion.queueDepth() > 0; n++) ok("SIM_STEP", { cycles: 1, response: "motion_delta" }); }; ok("HELLO"); ok("SESSION_ATTACH"); ok("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); ok("PROGRAM_LOAD", { source: "G21 G90\nG1 X1 F600\nG1 X2\nM2\n", path: "source-step.ngc" }); assert.equal(panel().axis_state.source_task_state, "STATE_ESTOP_RESET"); ok("SET_OVERRIDE", { feed: 0.9 }); assert.equal(panel().axis_state.source_task_state, "STATE_ESTOP_RESET"); ok("POWER", { active: true }); assert.equal(panel().ui_gates.run.enabled, true); assert.equal(panel().ui_gates.step.enabled, true); assert.equal(send("PROGRAM_RUN").payload.gate_reason, "xyzac_not_all_homed"); ok("HOME", { all: true }); ok("SIM_STEP", { cycles: 1 }); assert.equal(send("HOME", { axis: "x" }).payload.message_key, "home_joint_requires_joint_mode"); ok("PROGRAM_STEP"); assert.equal(panel().program.current_line, 0, "first native Step starts at line zero and pauses"); ok("PROGRAM_STEP"); assert.equal(panel().program.current_line, 1); ok("PROGRAM_STEP"); assert.equal(panel().program.current_line, 2); assert.ok(session.wasmMotion.queueDepth() > 0); ok("PROGRAM_STEP"); assert.equal(panel().motion.stepping, true); drain(); assert.ok(Math.abs(panel().visual.pose.x - 1) < 1e-8); assert.equal(panel().program.current_line, 2); assert.equal(panel().axis_state.operation_state, "PAUSED"); ok("PROGRAM_STEP"); ok("PROGRAM_STEP"); drain(); assert.ok(Math.abs(panel().visual.pose.x - 2) < 1e-8); ok("PROGRAM_ABORT"); ok("PROGRAM_LOAD", { source: "G21 G90\nG1 X5 F600\nG1 X10\nM2\n", path: "source-waiting.ngc" }); ok("PROGRAM_RUN"); for (let n = 0; n < 20 && panel().program.program_status !== "completed"; n++) ok("SIM_STEP", { cycles: 1 }); assert.equal(panel().axis_state.source_interp_state, "INTERP_WAITING"); ok("PROGRAM_PAUSE"); const paused = panel().visual.pose; ok("SIM_STEP", { cycles: 2 }); assert.deepEqual(panel().visual.pose, paused); ok("PROGRAM_RESUME"); assert.equal(panel().program.program_status, "completed", "Resume restores WAITING, not READING"); drain(); ok("SIM_STEP", { cycles: 1 }); assert.ok(Math.abs(panel().visual.pose.x - 10) < 1e-8); assert.equal(panel().axis_state.source_interp_state, "INTERP_IDLE"); ok("PROGRAM_LOAD", { source: "G21 G90\nM2\n", path: "source-mdi.ngc" }); ok("MDI_EXECUTE", { block: "G1 X12 F600" }); assert.equal(panel().axis_state.source_interp_state, "INTERP_WAITING"); assert.equal(panel().ui_gates.mdi.enabled, true); assert.equal(panel().ui_gates.jog.enabled, false); assert.equal(send("MDI_EXECUTE", { block: "G0 X0" }).kind, "ERROR"); ok("PROGRAM_PAUSE"); ok("PROGRAM_RESUME"); drain(); ok("SIM_STEP", { cycles: 1 }); assert.equal(panel().axis_state.source_interp_state, "INTERP_IDLE"); assert.ok(Math.abs(panel().visual.pose.x - 12) < 1e-8); ok("SPINDLE_COMMAND", { rpm: -600, enabled: true }); ok("SET_SPINDLE_OVERRIDE", { scale: 0 }); assert.equal(panel().devices.spindles[0].direction, -1); assert.equal(panel().devices.spindles[0].command_rpm, 0); ok("SET_SPINDLE_OVERRIDE", { scale: 1 }); assert.equal(panel().devices.spindles[0].command_rpm, -600); ok("SPINDLE_COMMAND", { rpm: 0, enabled: false }); console.log(JSON.stringify({ schema: "axis-source-execution.v1", status: "verified", checks: ["ui_vs_command_gate", "source_off_state", "switchkins_type", "repeated_step", "step_motion", "waiting_pause_resume", "mdi_motion_state", "spindle_zero_override"] }));