Files
linuxcncWebWASM/web/worker/test_axis_source_execution.mjs
wangdequan bade560372 Align AXIS control flow and fix pause and program rendering
Document source UI gates, explicit native live-plot clearing, and remaining parity boundaries. Fix pause/resume transport continuity, repeated stepping, coordinate previews, and spindle override state. Optimize large program snapshots and rendering, and cover the control flows with native Tcl, WASM, and browser checks.

Validation: 22 native tests and source/browser checks pass. Worker/App regression: 113 of 123 pass; 10 pre-existing failures remain documented with no new failures.
2026-09-08 10:46:27 +08:00

82 lines
4.8 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs";
const bundle = await loadSimulationCase({ path: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", fetchImpl: async (url) => {
try { const source = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url), "utf8"); return { ok: true, text: async () => source }; }
catch { return { ok: false, text: async () => "" }; }
} });
const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) });
const session = new XyzacSession(core, { sessionId: "axis-source-execution" });
let seq = 0;
const send = (kind, payload = {}) => session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, seq: ++seq, operation_id: `source-exec-${seq}`, kind, payload });
const ok = (kind, payload = {}) => {
const response = send(kind, payload);
assert.notEqual(response.kind, "ERROR", `${kind}: ${response.payload?.gate_reason ?? response.payload?.message_key ?? response.payload?.code}`);
return response.payload;
};
const panel = () => ok("SNAPSHOT_REQUEST").panel_snapshot;
const drain = () => { for (let n = 0; n < 2000 && session.wasmMotion.queueDepth() > 0; n++) ok("SIM_STEP", { cycles: 1, response: "motion_delta" }); };
ok("HELLO"); ok("SESSION_ATTACH"); ok("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
ok("PROGRAM_LOAD", { source: "G21 G90\nG1 X1 F600\nG1 X2\nM2\n", path: "source-step.ngc" });
assert.equal(panel().axis_state.source_task_state, "STATE_ESTOP_RESET");
ok("SET_OVERRIDE", { feed: 0.9 });
assert.equal(panel().axis_state.source_task_state, "STATE_ESTOP_RESET");
ok("POWER", { active: true });
assert.equal(panel().ui_gates.run.enabled, true);
assert.equal(panel().ui_gates.step.enabled, true);
assert.equal(send("PROGRAM_RUN").payload.gate_reason, "xyzac_not_all_homed");
ok("HOME", { all: true });
assert.equal(send("HOME", { axis: "x" }).payload.message_key, "home_joint_requires_joint_mode");
ok("PROGRAM_STEP");
assert.equal(panel().program.current_line, 0, "first native Step starts at line zero and pauses");
ok("PROGRAM_STEP");
assert.equal(panel().program.current_line, 1);
ok("PROGRAM_STEP");
assert.equal(panel().program.current_line, 2);
assert.ok(session.wasmMotion.queueDepth() > 0);
ok("PROGRAM_STEP");
assert.equal(panel().motion.stepping, true);
drain();
assert.ok(Math.abs(panel().visual.pose.x - 1) < 1e-8);
assert.equal(panel().program.current_line, 2);
assert.equal(panel().axis_state.operation_state, "PAUSED");
ok("PROGRAM_STEP"); ok("PROGRAM_STEP"); drain();
assert.ok(Math.abs(panel().visual.pose.x - 2) < 1e-8);
ok("PROGRAM_ABORT");
ok("PROGRAM_LOAD", { source: "G21 G90\nG1 X5 F600\nG1 X10\nM2\n", path: "source-waiting.ngc" });
ok("PROGRAM_RUN");
for (let n = 0; n < 20 && panel().program.program_status !== "completed"; n++) ok("SIM_STEP", { cycles: 1 });
assert.equal(panel().axis_state.source_interp_state, "INTERP_WAITING");
ok("PROGRAM_PAUSE");
const paused = panel().visual.pose;
ok("SIM_STEP", { cycles: 2 });
assert.deepEqual(panel().visual.pose, paused);
ok("PROGRAM_RESUME");
assert.equal(panel().program.program_status, "completed", "Resume restores WAITING, not READING");
drain(); ok("SIM_STEP", { cycles: 1 });
assert.ok(Math.abs(panel().visual.pose.x - 10) < 1e-8);
assert.equal(panel().axis_state.source_interp_state, "INTERP_IDLE");
ok("PROGRAM_LOAD", { source: "G21 G90\nM2\n", path: "source-mdi.ngc" });
ok("MDI_EXECUTE", { block: "G1 X12 F600" });
assert.equal(panel().axis_state.source_interp_state, "INTERP_WAITING");
assert.equal(panel().ui_gates.mdi.enabled, true);
assert.equal(panel().ui_gates.jog.enabled, false);
assert.equal(send("MDI_EXECUTE", { block: "G0 X0" }).kind, "ERROR");
ok("PROGRAM_PAUSE"); ok("PROGRAM_RESUME"); drain(); ok("SIM_STEP", { cycles: 1 });
assert.equal(panel().axis_state.source_interp_state, "INTERP_IDLE");
assert.ok(Math.abs(panel().visual.pose.x - 12) < 1e-8);
ok("SPINDLE_COMMAND", { rpm: -600, enabled: true });
ok("SET_SPINDLE_OVERRIDE", { scale: 0 });
assert.equal(panel().devices.spindles[0].direction, -1);
assert.equal(panel().devices.spindles[0].command_rpm, 0);
ok("SET_SPINDLE_OVERRIDE", { scale: 1 });
assert.equal(panel().devices.spindles[0].command_rpm, -600);
ok("SPINDLE_COMMAND", { rpm: 0, enabled: false });
console.log(JSON.stringify({ schema: "axis-source-execution.v1", status: "verified", checks: ["ui_vs_command_gate", "source_off_state", "switchkins_type", "repeated_step", "step_motion", "waiting_pause_resume", "mdi_motion_state", "spindle_zero_override"] }));