import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs"; import { XyzacCommandLedger } from "./xyzac-command-ledger.mjs"; import { XYZAC_SCHEDULER_PHASES } from "./xyzac-scheduler.mjs"; import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.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 }); assert.equal(bundle.status, "ready"); 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-rtcp-01" }); 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 }); const hello = send("HELLO", "hello"); assert.equal(hello.kind, "CAPABILITIES"); assert.equal(hello.payload.xyzac_session.scope, "selected_xyzac_simulation"); assert.equal(hello.payload.xyzac_session.machine.joint_count, 5); assert.deepEqual(hello.payload.xyzac_scheduler.phase_trace, XYZAC_SCHEDULER_PHASES); assert.equal(hello.payload.xyzac_ledger.pending_count, 0); const attached = send("SESSION_ATTACH", "attach"); assert.equal(attached.kind, "SESSION_READY"); const loaded = send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); assert.equal(loaded.payload.status, "verified"); assert.equal(loaded.payload.xyzac_session.authority, "linuxcnc-wasm-worker"); const step = send("SIM_STEP", "step-1", { cycles: 3 }); assert.equal(step.payload.status, "verified"); assert.equal(step.payload.xyzac_scheduler.tick, 3); assert.equal(step.payload.xyzac_scheduler.clock, "logical_tick"); assert.equal(session.ledger.reconcile("step-1").status, "committed"); const replay = session.accept({ ...base, kind: "SIM_STEP", seq: ++seq, operation_id: "step-1", payload: { cycles: 99 } }); assert.equal(replay.replayed, true); assert.equal(session.scheduler.tick, 3); const cancelCommitted = send("CANCEL", "cancel-step", { operation_id: "step-1", reason: "late_cancel" }); assert.equal(cancelCommitted.payload.retry_allowed, false); assert.equal(cancelCommitted.payload.ledger.status, "too_late"); const gap = session.accept({ ...base, kind: "SNAPSHOT_REQUEST", seq: ++seq + 1, payload: {} }); assert.equal(gap.kind, "ERROR"); assert.equal(gap.payload.code, "protocol_sequence"); const ledger = new XyzacCommandLedger(); ledger.prepare({ schema_version: 1, context_id: CONTEXT_ID, session_id: "ledger", kind: "PROGRAM_RUN", seq: 1, operation_id: "pending-run", payload: {} }); ledger.execute("pending-run"); assert.equal(ledger.pending().length, 1); assert.equal(ledger.reconcileAll()[0].status, "executing"); assert.equal(ledger.timeoutReconcile("pending-run").retry_allowed, false); assert.equal(ledger.abort("pending-run").status, "aborted"); assert.equal(ledger.pending().length, 0); assert.equal(ledger.audit().scope, "selected_xyzac_simulation"); console.log(JSON.stringify({ schema: "xyzac-session-rtcp-01.v1", status: "verified", case: CASE, authority: session.authority(), tick: session.scheduler.tick, replayed: replay.replayed === true, committed: session.ledger.reconcile("step-1").status, pending_after_abort: ledger.pending().length, state_hash: step.payload.xyzac_scheduler.state_hash, }));