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"; import { axisGate, xyzacGateMatrix } from "../app/axis-contract.mjs"; const CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini"; const fetchImpl = async (url) => { try { const body = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url)); return { ok: true, text: async () => body.toString("utf8") }; } catch { return { ok: false, text: async () => "" }; } }; const bundle = await loadSimulationCase({ path: CASE, fetchImpl }); const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) }); const session = new XyzacSession(core, { sessionId: "xyzac-gate-matrix" }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId }; let seq = 0; const send = (kind, operation_id, payload = {}) => session.accept({ ...base, kind, seq: ++seq, operation_id, payload }); send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); const loaded = send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); assert.equal(loaded.payload.status, "verified"); const initial = loaded.payload.panel_snapshot; assert.equal(initial.xyzac.coordinates, "XYZAC"); assert.equal(initial.xyzac.joint_count, 5); assert.equal(initial.gates.run.enabled, false); assert.equal(initial.gates.run.reason, "xyzac_machine_off"); assert.equal(initial.gates.runLine.reason, "xyzac_machine_off"); assert.equal(initial.gates.switchkinsIdentity.enabled, false); assert.equal(initial.gates.switchkinsXyzac.enabled, false); assert.equal(initial.gates.switchkinsUserk.enabled, false); assert.equal(initial.gates.vismachClear.visual_only, true); assert.equal(send("POWER", "power-on", { active: true }).payload.panel_snapshot.axis_state.machine_state, "ON"); assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const powered = send("SNAPSHOT_REQUEST", "powered-snapshot").payload.panel_snapshot; assert.equal(powered.gates.switchkinsIdentity.enabled, true); assert.equal(powered.gates.switchkinsXyzac.enabled, true); assert.equal(powered.gates.switchkinsUserk.enabled, true); const noFile = axisGate("runLine", { task: { state: "READY", mode: "MANUAL" }, program: {}, motion: {}, taskfile: false, highlight_line: null }); assert.equal(noFile.enabled, false); assert.equal(noFile.reason, "axis_taskfile_missing"); const highlightMissing = axisGate("runLine", { task: { state: "READY", mode: "MANUAL" }, program: { source_hash: "fnv1a64:file" }, motion: {}, taskfile: true, highlight_line: null }); assert.equal(highlightMissing.reason, "axis_highlight_missing"); const invalidSwitch = xyzacGateMatrix({ task: { state: "READY", mode: "MANUAL" }, program: {}, motion: {}, taskfile: false, xyzac: { coordinates: "XYZ", joint_count: 3, hal_pin_vars: false, switchkins_net: false, finite_offsets: true, userk_capability: false } }); assert.equal(invalidSwitch.switchkinsXyzac.enabled, false); assert.equal(invalidSwitch.switchkinsXyzac.reason, "xyzac_hal_pin_vars_missing"); send("PROGRAM_LOAD", "program-load", { path: "gate.ngc", source: "G21 G90\nG1 X10 A20 C30 F100\nM2\n" }); const running = send("PROGRAM_RUN", "program-run"); assert.equal(running.payload.panel_snapshot.gates.pause.enabled, true); assert.equal(running.payload.panel_snapshot.gates.abort.enabled, true); assert.equal(running.payload.panel_snapshot.gates.resume.enabled, false); assert.equal(running.payload.panel_snapshot.gates.switchkinsXyzac.enabled, false); send("PROGRAM_PAUSE", "pause"); const paused = send("SNAPSHOT_REQUEST", "paused-snapshot"); assert.equal(paused.payload.panel_snapshot.gates.resume.enabled, true); assert.equal(paused.payload.panel_snapshot.gates.pause.enabled, false); send("PROGRAM_ABORT", "abort"); const estopGates = xyzacGateMatrix({ task: { state: "ESTOP", mode: "MANUAL" }, program: { source_hash: "fnv1a64:file" }, motion: {}, taskfile: true, xyzac: { coordinates: "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true } }); assert.equal(estopGates.switchkinsXyzac.enabled, false); assert.equal(estopGates.switchkinsXyzac.reason, "xyzac_estop_active"); send("ESTOP", "estop-on", { active: true }); assert.equal(send("ESTOP", "estop-reset", { active: false }).payload.panel_snapshot.axis_state.source_task_state, "STATE_ESTOP_RESET"); send("POWER", "power-on-again", { active: true }); const recovered = send("SNAPSHOT_REQUEST", "recovered-snapshot"); assert.equal(typeof recovered.payload.panel_snapshot.state_hash, "string"); assert.equal(recovered.payload.panel_snapshot.gates.vismachClear.visual_only, true); console.log(JSON.stringify({ schema: "xyzac-gate-matrix-rtcp-03.v1", status: "verified", case: CASE, normal: ["runLine", "switchkinsIdentity", "switchkinsXyzac", "switchkinsUserk"], blocked: [noFile.reason, highlightMissing.reason, invalidSwitch.switchkinsXyzac.reason, estopGates.switchkinsXyzac.reason], running: { pause: true, abort: true }, paused: { resume: true }, estop: { switchkins_enabled: estopGates.switchkinsXyzac.enabled }, final_state_hash: recovered.payload.panel_snapshot.state_hash }));