From 8b7fc5221bc53ac2b9c4fa56acaa81429d1937ba Mon Sep 17 00:00:00 2001 From: wangdequan Date: Wed, 2 Sep 2026 13:55:45 +0800 Subject: [PATCH] Clear stale toolpaths on program replacement --- .../xyzac-panel-button-parity.report.json | 15 +++++-- .../xyzac-panel-button-parity-test-report.md | 19 +++++++++ web/worker/sim-session.mjs | 5 +++ web/worker/test_xyzac_program_replace.mjs | 39 +++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 web/worker/test_xyzac_program_replace.mjs diff --git a/coverage/xyzac-panel-button-parity.report.json b/coverage/xyzac-panel-button-parity.report.json index 1af893c..e5bcb6c 100644 --- a/coverage/xyzac-panel-button-parity.report.json +++ b/coverage/xyzac-panel-button-parity.report.json @@ -32,6 +32,15 @@ ], "status": "verified" }, + "program_preview_replacement": { + "test": "node --experimental-strip-types web/worker/test_xyzac_program_replace.mjs", + "first_program_segments": 1, + "failed_program_segments": 0, + "second_program_segments": 1, + "old_preview_retained_after_failure": false, + "browser_screenshot_sha256": "c3e0a5b992381e7b6cb26f98a30338935df5239c875755727fc678911452ac24", + "status": "verified" + }, "switchkins_direct_matrix": { "command": "node --experimental-strip-types web/worker/test_xyzac_trt_case.mjs", "sequence": [0, 1, 2, 0, 1, 2, 0], @@ -75,7 +84,7 @@ }, "published_browser_smoke": { "url": "https://49.232.250.35:58010/", - "release": "20260902-linuxcnc-wasm-live-parameters-r14", + "release": "20260902-linuxcnc-wasm-program-replace-r15", "status": "verified", "startup_pause_resume_stop": "verified", "pause_tick": 140, @@ -120,8 +129,8 @@ "switchkins": "920b85ad7fd6451dfb134255ffdcc4f03a7aa615ebf486c63cde8582e17af195" }, "resource_sha256": { - "index-CDC5yZJC.js": "c3a6aa009e091d5fcb2098c7254313d987229abc1e4765052edb15b465ca0fc4", - "sim-worker-entry-BQKHHWpv.js": "e72d80d9ae0fd679d92d3efbad4dba1eef55f9ce7b54bcd06e4a0d644c3cfb1a", + "index-DUB3ldSh.js": "4d71a2eb0b162608d7a47b63274cf274a57b1c2f2620b69af139b12c2ba22cce", + "sim-worker-entry-yfjiq6MN.js": "a0cf75522ddc9c6712126de86937ec89f24574bf0e0dad3dbddc6d807fb352b7", "linuxcnc-sim-core-fuIvKetC.wasm": "ad4e17ecbf73afbad0d9f069ab20ba3c66b6cc4d75ecdb2b3182673490ee916d" }, "authority_dom": { diff --git a/doc-rtcp/xyzac-panel-button-parity-test-report.md b/doc-rtcp/xyzac-panel-button-parity-test-report.md index 11b1aae..7e29197 100644 --- a/doc-rtcp/xyzac-panel-button-parity-test-report.md +++ b/doc-rtcp/xyzac-panel-button-parity-test-report.md @@ -55,6 +55,8 @@ AXIS 源控件清单:菜单 80、工具栏 20、快捷键 10,共 110 条, 实时参数区显示:主轴实际转速 `speed_rpm`、主轴指令转速 `command_rpm`、G-code `S` 值、TP 实际进给 `velocity_mm_min`、G-code `F` 值、TP 速度/上限、F/S override、队列深度、活动段和当前执行行。所有值来自 WASM program/TP/IO 快照,参数区的 `data-xyzac-parameter-source` 为 `linuxcnc-wasm`;未取得快照时显示 `-`。 +程序预览替换规则:`PROGRAM_LOAD` 开始时先清空旧的 canonical preview;如果 WASM 解析或子程序加载失败,错误响应中的路径仍为空,不会把旧程序刀路重新投影出来。新的 WASM preview 成功后才替换为新程序路径。 + 在 WASM 快照尚未到达前,界面显示 `WASM PENDING` 并禁用运动操作;不会使用 JS 默认轴值、任务状态或 override 值。浏览器定时器只发送 `SIM_STEP` 采样请求,不执行 G-code、轨迹、运动学或设备状态计算。React/Three 中的定时器、相机、网格、刀具网格和文本格式化均为显示层,不产生或修改任何 CNC 状态。 ## 按钮/控件矩阵 @@ -225,6 +227,22 @@ screenshot: /tmp/published-live-parameters-r14.png screenshot_sha256: 7f5307c913d8bc4e60c2a31c29aacc9afd1b0d91fac4a4f22e8a64fc2ea37a1e ``` +发布站点 r15(程序预览原子替换)验证: + +```text +url: https://49.232.250.35:58010/ +release: 20260902-linuxcnc-wasm-program-replace-r15 +initial preview: 29 segments +invalid PROGRAM_LOAD: preview 0 segments, error shown, old path not retained +valid PROGRAM_LOAD: preview 1 new segment +browser_errors: 0 +screenshot: /tmp/published-program-replace-r15.png +screenshot_sha256: c3e0a5b992381e7b6cb26f98a30338935df5239c875755727fc678911452ac24 +resource_sha256: index-DUB3ldSh.js 4d71a2eb0b162608d7a47b63274cf274a57b1c2f2620b69af139b12c2ba22cce + sim-worker-entry-yfjiq6MN.js a0cf75522ddc9c6712126de86937ec89f24574bf0e0dad3dbddc6d807fb352b7 + linuxcnc-sim-core-fuIvKetC.wasm ad4e17ecbf73afbad0d9f069ab20ba3c66b6cc4d75ecdb2b3182673490ee916d +``` + 发布站点 r10 连续性验证: ```text @@ -304,6 +322,7 @@ switchkins_direct_test_output 24e6cb8ec1195a97f71294b49217db0cbc0d7c3ecfa4bc8aa2 panel_r10_output 9bddcd8b0219925e00598e081dc7a4ed0f4868e8822a21f0ccd08d8e9d355d45 pause_resume_browser_screenshot 9d1660fd043dd0628ff2989a3e139a3239acd6386d141657a5834c9d2f1bc5a7 live_parameters_browser_screenshot 7f5307c913d8bc4e60c2a31c29aacc9afd1b0d91fac4a4f22e8a64fc2ea37a1e +program_replace_browser_screenshot c3e0a5b992381e7b6cb26f98a30338935df5239c875755727fc678911452ac24 ``` 报告对应提交前工作树保留了既有 coverage 大文件和 LinuxCNC 子模块改动;本轮提交包含 WASM TP/IO/HOME authority、无 JS JOG 运动计算、前端 pending 投影、长程序快照性能修正、聚合测试和本报告。 diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index dd6198a..07fdee4 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -1926,6 +1926,11 @@ export class SimSession { case "PROGRAM_LOAD": { if (["RUNNING", "PAUSED"].includes(this.task.state.state)) return finish(this.error(input, "task_state", "program_load_while_active"), "failed"); const source = input.payload?.source ?? input.payload?.text ?? input.payload?.program_source; + // PROGRAM_LOAD is a replacement transaction. Clear the previous + // canonical preview before asking the WASM interpreter to parse the + // new source, so a parse/subroutine error cannot re-publish the old + // toolpath as if it belonged to the requested file. + this.previewState = null; if (this.xyzacProgram) { this.pendingXyzacMotion = null; this.pendingProgramControl = 0; diff --git a/web/worker/test_xyzac_program_replace.mjs b/web/worker/test_xyzac_program_replace.mjs new file mode 100644 index 0000000..573dc2f --- /dev/null +++ b/web/worker/test_xyzac_program_replace.mjs @@ -0,0 +1,39 @@ +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 { SimSession, CONTEXT_ID } from "./sim-session.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 }); +const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) }); +const session = new SimSession(core, { sessionId: "xyzac-program-replace" }); +const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId }; +let seq = 0; +const send = (kind, payload = {}) => session.accept({ ...base, kind, seq: ++seq, operation_id: `replace-${seq}`, payload }); +send("HELLO"); send("SESSION_ATTACH"); +assert.equal(send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); +const first = send("PROGRAM_LOAD", { path: "first.ngc", source: "G21\nG1 X10 Y5 Z2 F100\nM2\n" }); +assert.equal(first.payload.preview.segments.length, 1); +assert.equal(first.payload.panel_snapshot.visual.toolpath.length, 1); +const failed = send("PROGRAM_LOAD", { path: "invalid.ngc", source: "G999\n" }); +assert.equal(failed.kind, "ERROR"); +assert.equal(failed.payload.panel_snapshot.visual.toolpath.length, 0); +assert.equal(session.previewState, null); +const second = send("PROGRAM_LOAD", { path: "second.ngc", source: "G21\nG1 X1 Y2 Z3 F100\nM2\n" }); +assert.equal(second.payload.program.path, "second.ngc"); +assert.notEqual(second.payload.program.source_hash, first.payload.program.source_hash); +assert.equal(second.payload.panel_snapshot.visual.toolpath.length, 1); + +console.log(JSON.stringify({ + schema: "xyzac-program-replacement.v1", + status: "verified", + first_segments: first.payload.panel_snapshot.visual.toolpath.length, + failed_segments: failed.payload.panel_snapshot.visual.toolpath.length, + second_segments: second.payload.panel_snapshot.visual.toolpath.length, + old_preview_retained_after_failure: false, +}));