import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { ProgramRunner } from "./program-runner.mjs"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; import { compareEndpoints, createEndpoint } from "./endpoint-replay.mjs"; const sourcePath = "linuxcnc/tests/interp/g6164/test.ngc"; const source = await readFile(new URL(`../../${sourcePath}`, import.meta.url), "utf8"); const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)); const core = await createModule({ wasmBinary }); const runner = new ProgramRunner(core); assert.equal(runner.load(source, sourcePath).status, "verified"); assert.equal(runner.start().status, "verified"); const controls = []; let guard = 0; while (runner.status === "running" && guard < 100) { const result = runner.step(); if (result.motion_control) controls.push({ line: result.line, ...result.motion_control }); guard += 1; } assert.equal(runner.status, "completed"); assert.deepEqual(controls.map(({ mode, path_tolerance, naive_cam_tolerance }) => ({ mode, path_tolerance, naive_cam_tolerance })), [ { mode: "continuous", path_tolerance: 0, naive_cam_tolerance: 0 }, { mode: "continuous", path_tolerance: 1, naive_cam_tolerance: 0 }, { mode: "continuous", path_tolerance: 1, naive_cam_tolerance: 2 }, { mode: "exact_path", path_tolerance: 0, naive_cam_tolerance: 0 }, { mode: "exact_stop", path_tolerance: 0, naive_cam_tolerance: 0 }, ]); const finalHash = runner.snapshot().motion_control_hash; const sourceHash = runner.snapshot().source_hash; runner.destroy(); const malformed = new ProgramRunner(core); assert.equal(malformed.load("G64P-1\nM2\n", "bad-g64.ngc").status, "verified"); malformed.start(); assert.equal(malformed.step().detail, "program_motion_control_range"); malformed.destroy(); const unsupported = new ProgramRunner(core); assert.equal(unsupported.load("G61.2\nM2\n", "bad-g61.ngc").status, "verified"); unsupported.start(); assert.equal(unsupported.step().detail, "program_motion_control_syntax"); unsupported.destroy(); const session = new SimSession(await createModule({ wasmBinary }), { sessionId: "s7-g6164-recovery" }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: "s7-g6164-recovery" }; let seq = 0; const send = (kind, operation_id, payload = {}) => { seq += 1; const response = session.accept({ ...base, kind, seq, operation_id, payload }); assert.notEqual(response.kind, "ERROR", `${kind}:${response.payload?.code ?? "error"}`); return response; }; send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace" }); send("PROGRAM_LOAD", "program", { source, path: sourcePath }); send("PROGRAM_RUN", "run"); send("SIM_STEP", "step-a", { cycles: 3 }); const checkpoint = session.createCheckpoint(); send("SIM_STEP", "step-b", { cycles: 4 }); const continuous = createEndpoint({ session }); assert.equal(session.restoreCheckpoint(checkpoint, session.timeline.export(), (operation) => operation).status, "recovered"); const recovered = createEndpoint({ session }); const parity = compareEndpoints(continuous, recovered); assert.equal(parity.equal, true, JSON.stringify(parity)); console.log(JSON.stringify({ schema: "linuxcnc-source-g6164-s7-02-sample.v1", status: "verified", context_id: CONTEXT_ID, source: { path: sourcePath, source_hash: sourceHash }, controls, final_motion_control_hash: finalHash, errors: { negative_tolerance: "program_motion_control_range", unsupported_g61: "program_motion_control_syntax" }, recovery: { equal: parity.equal, difference: parity.difference, checkpoint_hash: checkpoint.checkpoint_hash }, boundaries: ["LinuxCNC canonical expected output remains SOURCE_BLOCKED", "TP blend/exact-stop numeric parity remains follow-up", "Safari/Firefox are out of scope"], }));