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.
This commit is contained in:
2026-09-08 10:46:27 +08:00
parent 845075f4a3
commit bade560372
55 changed files with 2495 additions and 286 deletions

View File

@@ -0,0 +1,56 @@
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 "../web/workspace/sim-case-loader.mjs";
import { XyzacSession, CONTEXT_ID } from "../web/worker/xyzac-session.mjs";
const root = new URL("../", import.meta.url);
const read = (path) => readFile(new URL(path, root), "utf8");
const bundle = await loadSimulationCase({ path: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", fetchImpl: async (url) => {
try { const body = await read(String(url).replace(/^\//, "")); return { ok: true, text: async () => body }; }
catch { return { ok: false, text: async () => "" }; }
} });
const core = await createModule({ wasmBinary: await readFile(new URL("build-wasm/linuxcnc-sim-core.wasm", root)) });
const session = new XyzacSession(core, { sessionId: "replacement-benchmark" });
let seq = 0;
const send = (kind, payload = {}) => {
const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, payload, seq: ++seq, operation_id: `bench-${seq}` });
assert.notEqual(result.kind, "ERROR", `${kind}: ${result.payload.gate_reason ?? result.payload.message_key}`);
return result.payload;
};
send("HELLO"); send("SESSION_ATTACH"); send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
send("PROGRAM_LOAD", { source: "G21 G90\nG0 X0 Y0 Z1\nG1 X1 F600\nG1 X0\nM2\n", path: "first.ngc" });
send("POWER", { active: true }); send("HOME", { all: true }); send("PROGRAM_RUN");
for (let n = 0; n < 1000 && session.task.state.state !== "STOPPED"; n++) send("SIM_STEP", { cycles: 5, response: "motion_delta" });
assert.equal(session.task.state.state, "STOPPED");
const path = "linuxcnc/nc_files/3D_Chips.ngc", source = await read(path);
const ticks = Number(process.env.BENCH_TICKS ?? 200);
const rows = [];
for (let attempt = 1; attempt <= 2; attempt++) {
const start = performance.now();
const loaded = send("PROGRAM_LOAD", { source, path });
const loadMs = performance.now() - start;
const snapshotStart = performance.now();
for (let sample = 0; sample < 200; sample++) session.xyzacProgram.snapshot();
const snapshotMs = performance.now() - snapshotStart;
send("PROGRAM_RUN");
const runStart = performance.now();
let result;
const latencies = [];
for (let tick = 0; tick < ticks; tick += 5) {
const requestStart = performance.now();
result = send("SIM_STEP", { cycles: 5, response: "motion_delta" });
latencies.push(performance.now() - requestStart);
}
const runMs = performance.now() - runStart;
const state = session.wasmMotion.read().state;
rows.push({ attempt, load_ms: loadMs, snapshot_200_ms: snapshotMs, run_ms: runMs, task_ticks: ticks, simulated_ms: ticks * 10, p95_step_ms: latencies.sort((a, b) => a - b)[Math.floor(latencies.length * .95)], source_hash: loaded.program.source_hash, preview_hash: loaded.preview.preview_hash, preview_segments: loaded.preview.segments.length, final_line: result.program.current_line, pose: state.position, queue: state.queue_depth, feed: state.feed_override, velocity: state.velocity, live_epoch: loaded.panel_snapshot.visual.live_path_epoch });
send("PROGRAM_ABORT");
if (attempt === 1) {
// Reset the machine position through the interpreter, preserving source
// semantics, so the repeated load has the same physical starting point.
send("MDI_EXECUTE", { block: "G90 G0 X0 Y0 Z1" });
for (let tick = 0; tick < 2000 && session.wasmMotion.read().state.queue_depth; tick++) send("SIM_STEP", { cycles: 5, response: "motion_delta" });
}
}
console.log(JSON.stringify({ schema: "xyzac-replacement-benchmark.v1", rows }));