import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { fnv1a64 } from "./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)); const source = "G21 G90\nG0 X0 Y0 Z0\nG1 X40 Y20 F120\nG1 Z-2 F60\nG0 X0 Y0 Z0\nM2\n"; 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", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); assert.equal(loaded.payload.motion_authority, "wasm"); send("PROGRAM_LOAD", "program", { path: "checkpoint.ngc", source }); send("PROGRAM_RUN", "run"); return { session, send }; } const continuous = await create("wasm-motion-checkpoint"); const before = continuous.send("SIM_STEP", "before", { cycles: 3 }); assert.ok(before.payload.wasm_motion.state.queue_depth > 0); const checkpoint = continuous.send("CHECKPOINT", "checkpoint").payload.checkpoint; assert.equal(checkpoint.state.wasm_motion.schema, "sim_motion_queue_state.v1"); assert.equal(checkpoint.state.motion_authority, "wasm"); assert.equal(checkpoint.state_hash, fnv1a64(checkpoint.state)); continuous.send("SIM_STEP", "after", { cycles: 4 }); const continuousState = continuous.session.workspacePackageState(); const recovered = await create("wasm-motion-checkpoint"); for (const method of ["syncProgram", "step", "applyMotion"]) { recovered.session.authoritative[method] = () => { throw new Error(`js_motion_authority_called:${method}`); }; } const recovery = recovered.session.restoreCheckpoint(checkpoint, continuous.session.timeline.export(), (operation) => operation); assert.equal(recovery.status, "recovered"); const recoveredState = recovered.session.workspacePackageState(); assert.equal(fnv1a64(continuousState), fnv1a64(recoveredState)); assert.equal(recoveredState.wasm_motion.state.queue_depth, continuousState.wasm_motion.state.queue_depth); assert.deepEqual(recoveredState.wasm_motion.state.position, continuousState.wasm_motion.state.position); const trace = continuous.session.exportTrace(); const traced = await create("wasm-motion-checkpoint"); const traceImport = traced.session.importTrace(trace); assert.equal(traceImport.status, "verified"); assert.equal(traced.session.workspacePackageState().wasm_motion.state_hash, continuousState.wasm_motion.state_hash); assert.deepEqual(traced.session.workspacePackageState().wasm_motion.state.position, continuousState.wasm_motion.state.position); const damaged = structuredClone(checkpoint); damaged.state.wasm_motion.bytes = `${damaged.state.wasm_motion.bytes.startsWith("00") ? "ff" : "00"}${damaged.state.wasm_motion.bytes.slice(2)}`; damaged.state_hash = fnv1a64(damaged.state); const blocked = recovered.session.restoreCheckpoint(damaged, null, (operation) => operation); assert.equal(blocked.status, "blocked"); console.log(JSON.stringify({ schema: "wasm-motion-checkpoint-t3.v1", status: "verified", case: CASE, checkpoint_hash: checkpoint.checkpoint_hash, continuous_hash: fnv1a64(continuousState), recovered_hash: fnv1a64(recoveredState), endpoint_equal: fnv1a64(continuousState) === fnv1a64(recoveredState), trace_import: traceImport.status, checkpoint_queue_depth: checkpoint.state.wasm_motion.state.queue_depth, blocked_damaged_checkpoint: blocked.status, }));