import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; import { fnv1a64 } from "./replay.mjs"; const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)); function base(sessionId) { return { schema_version: 1, context_id: CONTEXT_ID, session_id: sessionId }; } async function createSession(sessionId) { const core = await createModule({ wasmBinary }); const session = new SimSession(core, { sessionId }); const prefix = base(sessionId); assert.equal(session.accept({ ...prefix, kind: "HELLO", seq: 1, payload: {} }).kind, "CAPABILITIES"); const ready = session.accept({ ...prefix, kind: "SESSION_ATTACH", seq: 2, operation_id: `${sessionId}-attach`, payload: {} }); assert.equal(ready.kind, "SESSION_READY"); assert.equal(ready.payload.task.schema, "sim_task.v1"); assert.equal(ready.payload.task.state, "READY"); assert.equal(ready.payload.task.seq, 2); return { core, session, prefix }; } const first = await createSession("task-a"); const { session, prefix } = first; let response = session.accept({ ...prefix, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "load", payload: { root_path: "/workspace" } }); assert.equal(response.payload.task.state, "READY"); response = session.accept({ ...prefix, kind: "PROGRAM_LOAD", seq: 4, operation_id: "program-load", payload: { path: "task.ngc", source: "G21 G90\nG1 X2 F60\nM2\n" } }); assert.equal(response.payload.task.program_status, "loaded"); response = session.accept({ ...prefix, kind: "PROGRAM_RUN", seq: 5, operation_id: "program-run", payload: {} }); assert.equal(response.payload.task.state, "RUNNING"); response = session.accept({ ...prefix, kind: "SIM_STEP", seq: 6, operation_id: "tick-1", payload: { cycles: 1 } }); assert.equal(response.payload.task.simulation_tick, 1); const firstTickHash = response.payload.task.state_hash; response = session.accept({ ...prefix, kind: "PROGRAM_PAUSE", seq: 7, operation_id: "pause", payload: {} }); assert.equal(response.payload.task.state, "PAUSED"); response = session.accept({ ...prefix, kind: "PROGRAM_RESUME", seq: 8, operation_id: "resume", payload: {} }); assert.equal(response.payload.task.state, "RUNNING"); response = session.accept({ ...prefix, kind: "PROGRAM_ABORT", seq: 9, operation_id: "abort", payload: {} }); assert.equal(response.payload.task.state, "STOPPED"); assert.equal(response.payload.task.program_status, "aborted"); const checkpointResponse = session.accept({ ...prefix, kind: "CHECKPOINT", seq: 10, operation_id: "checkpoint", payload: {} }); assert.equal(checkpointResponse.kind, "CHECKPOINT_READY"); assert.equal(checkpointResponse.payload.checkpoint.state.task.schema, "sim_task.v1"); const checkpoint = checkpointResponse.payload.checkpoint; const recovered = session.restoreCheckpoint(checkpoint, null, (operation) => operation); assert.equal(recovered.status, "recovered"); assert.equal(recovered.state.task.state, "STOPPED"); assert.equal(recovered.state.task.state_hash, checkpoint.state.task.state_hash); const faultSession = await createSession("task-fault"); const faultBase = faultSession.prefix; faultSession.session.accept({ ...faultBase, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "fault-load", payload: { root_path: "/workspace" } }); faultSession.session.accept({ ...faultBase, kind: "PROGRAM_LOAD", seq: 4, operation_id: "fault-program", payload: { source: "G21\nG1 X1\n" } }); faultSession.session.accept({ ...faultBase, kind: "PROGRAM_RUN", seq: 5, operation_id: "fault-run", payload: {} }); const estop = faultSession.session.accept({ ...faultBase, kind: "ESTOP", seq: 6, operation_id: "estop", payload: { active: true } }); assert.equal(estop.payload.task.state, "ESTOP"); faultSession.session.accept({ ...faultBase, kind: "ESTOP", seq: 7, operation_id: "estop-clear", payload: { active: false } }); const recoveredIo = faultSession.session.accept({ ...faultBase, kind: "IO_RECOVER", seq: 8, operation_id: "io-recover", payload: {} }); assert.equal(recoveredIo.payload.task.state, "READY"); const second = await createSession("task-a"); second.session.accept({ ...second.prefix, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "load", payload: { root_path: "/workspace" } }); second.session.accept({ ...second.prefix, kind: "PROGRAM_LOAD", seq: 4, operation_id: "program-load", payload: { path: "task.ngc", source: "G21 G90\nG1 X2 F60\nM2\n" } }); second.session.accept({ ...second.prefix, kind: "PROGRAM_RUN", seq: 5, operation_id: "program-run", payload: {} }); const secondTick = second.session.accept({ ...second.prefix, kind: "SIM_STEP", seq: 6, operation_id: "tick-1", payload: { cycles: 1 } }); assert.equal(secondTick.payload.task.state_hash, firstTickHash); console.log(`sim_task=passed state=${recovered.state.task.state} tick_hash=${firstTickHash} recovery_hash=${recovered.state.task.state_hash}`);