import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { compareEndpoints, createEndpoint, endpointHash } from "./endpoint-replay.mjs"; import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; const CASE = "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.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 wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)); async function create(id) { const core = await createModule({ wasmBinary }); const session = new SimSession(core, { sessionId: id }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: id }; let seq = 0; const send = (kind, operationId, payload = {}) => session.accept({ ...base, kind, seq: ++seq, operation_id: operationId, payload }); send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); const loaded = send("LOAD_WORKSPACE", "load", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); assert.equal(loaded.payload.configuration.state, "TASK_READY"); send("PROGRAM_LOAD", "program", { path: "persist.ngc", source: "G21 G90\nG0 X0 Y0 Z0\nG1 X2 Y1 F60\nG1 Z-1\nM2\n" }); send("PROGRAM_RUN", "run"); return { session, send }; } const continuous = await create("axis3-tutorial-persistence"); const checkpoint = continuous.send("CHECKPOINT", "checkpoint").payload.checkpoint; continuous.send("SIM_STEP", "step-a", { cycles: 3 }); const continuousEndpoint = createEndpoint({ session: continuous.session }); const recovered = await create("axis3-tutorial-persistence"); const recovery = recovered.session.restoreCheckpoint(checkpoint, continuous.session.timeline.export(), (operation) => operation); assert.equal(recovery.status, "recovered"); const recoveredEndpoint = createEndpoint({ session: recovered.session }); const parity = compareEndpoints(continuousEndpoint, recoveredEndpoint); assert.equal(parity.equal, true); const bad = structuredClone(checkpoint); bad.state_hash = "fnv1a64:bad"; const blocked = recovered.session.restoreCheckpoint(bad, null, (operation) => operation); assert.equal(blocked.status, "blocked"); const continuousHash = endpointHash(continuousEndpoint); const recoveredHash = endpointHash(recoveredEndpoint); console.log(JSON.stringify({ schema: "axis3-tutorial-persistence.v1", status: "verified", case: CASE, exact_runtime_files: ["3axis-tutorial_mm.var", "tool.tbl", "position_mm.txt"], checkpoint_hash: checkpoint.checkpoint_hash, continuous_hash: continuousHash, recovered_hash: recoveredHash, endpoint_equal: parity.equal, blocked_bad_checkpoint: blocked.status, event_cursor: checkpoint.event_cursor }));