# 3axis Tutorial Web 控制面板源程序对标实施方案 ## 目标 为固定案例 `linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini` 建立与 LinuxCNC AXIS 控制面板可观察行为一致的 Web 控制面板。 唯一运行时数据来源是 LinuxCNC 源码编译得到的 WASM core,以及由 Dedicated Worker 投影的 authoritative snapshot/event。React 只能发送命令、显示状态和呈现诊断;不得在浏览器端重新推导任务状态、运动轨迹、DRO、工具状态、门禁或错误结果。 本方案是固定案例的控制面板实施合同,不宣称全量 LinuxCNC、实时硬件或 release candidate。机器可读的源语义规则见 [`coverage/linuxcnc-source-semantics-contract.json`](../coverage/linuxcnc-source-semantics-contract.json),共享实现见 [`web/workspace/linuxcnc-source-semantics.mjs`](../web/workspace/linuxcnc-source-semantics.mjs)。 ## 硬约束和功能矩阵 以下约束是所有 PANEL 任务的共同通过条件: 1. 网页控制面板的唯一数据来源是 LinuxCNC WASM;React 不得生成、修正或覆盖 canonical state。 2. 状态机严格对标 LinuxCNC 源程序;状态数值、迁移顺序、错误码和 gate reason 必须有 source line 和 WASM/Worker evidence。 3. 所有按钮逻辑制约必须与 LinuxCNC AXIS 源程序一致;按钮 disabled/enabled、模式切换、ESTOP/limit/fault gate 不允许由 UI 自行猜测。 4. 刀具路径预览必须来自 WASM canonical motion events,并与原生 LinuxCNC 的 segment 顺序、source line、端点、圆弧中心/法向、turn、units 和最终 hash 一致;没有 WASM event 时必须 fail-closed。 | # | 功能 | LinuxCNC 原生 | WASM 支持 | Web 实现状态 | 优先级 | 主要任务 | |---:|---|:---:|:---:|---|:---:|---| | 1 | 加载数控程序 | ✅ | ✅ | `PANEL-07` | P0 | `PROGRAM_LOAD`、exact source/hash、diagnostic | | 2 | 刀具路径预览 | ✅ | ✅ | `PANEL-11` | P0 | WASM motion event、segment golden、no JS preview | | 3 | 取消急停 | ✅ | ✅ | `PANEL-05` | P0 | `ESTOP_RESET`/recover source state transition | | 4 | 上电 | ✅ | ✅ | `PANEL-05` | P0 | `STATE_OFF -> STATE_ON` and gate | | 5 | 复位 | ✅ | ✅ | `PANEL-05`/`PANEL-12` | P0 | task reset、fault clear、checkpoint reconciliation | | 6 | HOME 回零 | ✅ | ✅ | `PANEL-06` | P0 | individual/all home、home sequence、home-limit ABI | | 7 | 执行 G 代码 | ✅ | ✅ | `PANEL-07` | P0 | run/pause/resume/step/abort/restart | | 8 | 实时路径显示 | ✅ | ✅ | `PANEL-10` | P0 | authoritative pose/toolpath -> Three.js | | 9 | 实时坐标显示 | ✅ | ✅ | `PANEL-08` | P0 | commanded/actual/relative DRO、units | | 10 | MDI 功能 | ✅ | ✅ | `PANEL-07` | P1 | MDI mode、queue limit、active program rejection | | 11 | JOG 功能 | ✅ | ✅ | `PANEL-06` | P1 | manual mode、continuous/incremental、stop | | 12 | 按钮逻辑制约 | ✅ | ✅ | `PANEL-03` | P0 | complete AXIS gate matrix and error reasons | 表中的“Web 实现状态”在任务完成前只能是 `queued`、`in_progress` 或 `blocked`;现有 selected-case smoke/Worker evidence 不得自动把矩阵项标记为 `verified`。 ### 功能项统一验收证据 每个矩阵项必须同时保存:LinuxCNC source path/line/hash、WASM export/state field、Worker command/event、React control ID、normal/boundary/error/ESTOP/recovery sample、native/WASM/Worker logical tick、输入/输出/state/event hash 和 self-hashed report。P0 项缺任一项即保持 `blocked`;P1 项不能阻塞 P0 实施,但不能被宣称完成。 ## 源程序映射 | Web 控制面板区域 | LinuxCNC 源输入 | WASM/Worker 唯一来源 | React 允许行为 | 禁止行为 | |---|---|---|---|---| | 电源、急停、状态灯 | `axis.py` 状态更新、`linuxcnc` `STATE_*`/`MODE_*`/`INTERP_*` | `sim_task` canonical + `axis_state` | 显示、发送命令、显示 gate reason | 本地切换 ON/ESTOP | | File/Program | `axis.py` `open_file_guts`, `reload_file`, `task_run` | WASM interpreter events + Worker program state | 编辑器输入、发送 LOAD/RUN/RESTART | JS 解析 G-code 或伪造当前行 | | MDI | `axis.py` `ensure_mode`, `send_mdi_command` | WASM interpreter/task mode/queue | 提交文本、显示拒绝 | 本地判断 MDI 成功 | | Manual/Jog/Home | `axis.py` `jog_plus`, `jog_minus`, `home_all_joints`, `home_joint` | WASM motion queue + home/limit ABI | 按钮、方向、速度输入 | JS 插值、修改 position/home | | DRO/active codes | `axis.py` `update`, `task_modename`, `motion_modename`, `interp_statename` | WASM task/interpreter/motion snapshot | 格式化显示 | 从命令文本推导状态 | | Feed/spindle override | `axis.py` `set_feedrate`, `set_spindlerate` | WASM/Worker device canonical | slider/number input | 直接修改 React store | | Spindle/coolant/tool/probe | `axis.py` manual controls、案例 HAL、`sim_io` | WASM IO/device canonical | 显示、发送命令、fault recovery | mock device response | | Preview/3D | `axis.py` preview/canon、案例 Vismach executable | WASM canonical motion + Worker visual snapshot | Three.js 渲染 | JS G-code preview/几何 authority | | 配置诊断 | `linuxcnc-source-semantics-contract.v1` | Worker startup snapshot | 显示 `TASK_READY`/`BLOCKED_*`、source/line | basename 替换、隐藏缺失资源 | 源代码锚点: - AXIS startup/open-file:`linuxcnc/src/emc/usr_intf/axis/scripts/axis.py` 约 3991-3998 行。 - AXIS status projection:同文件 `update`、`task_modename`、`motion_modename`、`interp_statename`。 - AXIS command gates:同文件 `ensure_mode`、`task_run`、`task_pause`、`task_resume`、`task_stop`、`jog_*`、`home_*`、`estop_clicked`。 - 参数文件和程序前缀:`linuxcnc/src/emc/rs274ngc/rs274ngc_pre.cc`、`linuxcnc/src/emc/task/emctask.cc`。 - 案例 UI/HAL/Vismach:`linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini`、`.hal` 和 `3axis-tutorial`。 ## 控制面板数据契约 Worker 每次响应必须能追溯到同一个 `context_id`、`session_id`、`logical_tick` 和 `state_hash`。控制面板消费以下投影字段: ```yaml snapshot: task: wasm sim_task canonical state program: WASM interpreter/program state motion: WASM motion queue state home_limit: WASM kinematics home/limit state hal: static/virtual HAL projection with source diagnostics devices: WASM sim_io/tool/spindle/coolant/probe state configuration: startup state, exact resources, optional_missing, runtime_fixtures axis_state: additive AXIS state/mode/interpreter view visual: pose, joints, toolpath and Vismach pose projection state_hash: hash of the canonical projection ``` 每个命令必须携带 `operation_id`,并产生 pending/result/error event。UI gate 只读取上一份 authoritative snapshot;命令被拒绝时显示 Worker 返回的 stable error code、message key、source/line 和 gate reason。错误、ESTOP、limit、following-error、资源缺失和能力边界必须可见,不能变成静默 disabled。 ### 分层接口责任 | 层 | 必须提供 | 允许缓存 | 不得提供 | |---|---|---|---| | LinuxCNC source/native recorder | 常量、状态迁移、命令顺序、错误/拒绝语义、segment golden | stdout/log/screenshot hash | 浏览器状态 | | `wasm-core` | canonical task/interpreter/motion/home-limit/HAL/IO state、fixed tick、canonical event | raw ABI bytes、read-only sample | UI 文案或按钮 gate | | Dedicated Worker | command ledger、session lifecycle、snapshot/event 转发、diagnostic/source line、checkpoint/trace | 序列化协议和 operation trace | JS authority mirror、JS G-code 解析 | | React/Web panel | command input、snapshot projection、accessibility、visual rendering | 最近一份 snapshot 用于显示 | 改写 state、推导成功、推导轨迹 | 任何层违反责任都必须在测试中以 `authority_violation` 失败;“结果看起来一致”不能替代唯一来源证明。 ### 面板命令 envelope 所有 P0/P1 命令使用已有 `protocol.v1` envelope,不为 UI 单独发明协议: ```json { "schema_version": 1, "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "session_id": "axis3-panel", "seq": 42, "operation_id": "axis-program-run-000042", "kind": "PROGRAM_RUN", "payload": {} } ``` 响应至少包含 `kind`、`seq`、`operation_id`、`logical_tick`、`payload.task`、`payload.program`、`payload.motion`、`payload.axis_state`、`payload.state_hash`。错误响应必须包含 `code`、`message_key`、`source`/`line`(若有)、`gate_reason` 和拒绝时的完整 snapshot。 ## 严格状态机和按钮真值表 源常量和 AXIS 视图必须使用 [`docs/linuxcnc-web-wasm-3axis-tutorial-state-machine.md`](linuxcnc-web-wasm-3axis-tutorial-state-machine.md) 中的数值,不得只使用同名字符串: | AXIS 状态 | LinuxCNC source state | 允许的面板操作 | 必须拒绝 | |---|---|---|---| | `ESTOP` | `STATE_ESTOP` | cancel ESTOP、显示诊断 | power/home/run/MDI/JOG | | `ESTOP_RESET` | `STATE_ESTOP_RESET` | power on、ESTOP | run/home/MDI/JOG | | `OFF` | `STATE_OFF` | power/reset、ESTOP | run/home/MDI/JOG | | `ON`/`IDLE` | `STATE_ON + INTERP_IDLE` | load/run/MDI/manual/home/jog/device | 与当前 mode/limit/fault 冲突的操作 | | `HOME` | source home transient | 等待 home result、ESTOP | run/MDI/重复 home | | `RUNNING` | `INTERP_READING/WAITING` | pause/abort/ESTOP | home/jog/MDI/load | | `PAUSED` | `INTERP_PAUSED` | resume/abort/ESTOP/step(按源 gate) | home/jog/普通 MDI | 按钮 gate 的可执行真值样本必须至少包含: | 操作 | 正常条件 | 拒绝样本 | |---|---|---| | 加载程序 | `ON + INTERP_IDLE` 或源允许的 manual load | `OFF`、`RUNNING`、资源缺失 | | 预览 | WASM motion events 已存在 | WASM event 缺失、解析错误 | | 取消急停 | `ESTOP`/`ESTOP_RESET` 按源状态转换 | snapshot 过期、seq/tick 错误 | | 上电 | `ESTOP_RESET -> ON` 或源定义 reset 流程 | `ESTOP` 未 reset、fault latch | | 复位 | abort/clear/recover 的源顺序 | active motion、坏 checkpoint | | HOME | `ON + MANUAL + INTERP_IDLE` | `OFF`、AUTO/MDI、limit/fault、未满足 previous home | | G-code run | `ON + AUTO + IDLE + taskfile` | 无程序、MDI、paused、ESTOP | | 实时路径/DRO | snapshot tick/hash 可验证 | snapshot stale、state hash mismatch | | MDI | `ON + MDI` 且 queue 未满 | AUTO active、queue full、ESTOP | | JOG | `ON + MANUAL + IDLE` | AUTO/MDI、未 home、limit/fault | ## 刀具路径预览一致性算法 预览和执行都必须使用 WASM interpreter canonical motion event;Web 不得将原始 G-code 再解析一遍。每个 segment 的比较字段固定为: ```yaml segment: source_line: integer motion_type: rapid|linear|arc start: pose(x,y,z,a,b,c,u,v,w) end: pose(x,y,z,a,b,c,u,v,w) center: pose_or_null normal: vector_or_null turn: integer velocity: finite_number units: mm|inch plane: G17|G18|G19 sequence: integer ``` 验收顺序: 1. native recorder 使用固定程序和固定 units/modal 初始状态生成 segment JSONL。 2. WASM `sim_interpreter_read_motion` 生成同字段事件;Worker 只排序/封装,不补字段。 3. 比较 segment count、source line、顺序、字段值和 canonical hash;浮点比较使用 source/ABI 规定的 exact或明确 tolerance,不能用截图相似度替代。 4. Three.js 只渲染已比较的 `visual` projection;canvas 非空是必要条件,不是轨迹正确性的充分条件。 5. 任意字段首差必须记录 `first_divergent_segment`、`source_line`、`logical_tick`、`field`、`native`、`wasm`,并阻止 preview/execute 继续宣称一致。 禁止条件:WASM event buffer 为空时调用 JS `previewGcode`;用当前 DRO 终点反推完整路径;用 native screenshot 通过 Web segment hash;用墙钟时间推进路径。 ## 功能矩阵报告模板 每个功能项使用同一结构,便于控制面板任务和后续自动聚合: ```yaml feature_id: PANEL-07 matrix_item: execute_gcode priority: P0 status: queued|in_progress|verified|blocked source: paths: [] lines: [] sha256: [] authority: wasm_exports: [] canonical_fields: [] worker: commands: [] events: [] react: control_ids: [] projection_fields: [] samples: normal: [] boundary: [] error: [] estop_or_fault: [] recovery: [] comparison: native_hash: null wasm_hash: null worker_hash: null first_divergent_tick: null stop_reason: null report_sha256: null ``` `verified` 只允许在 `source`、`authority`、`worker`、`react`、5 类 samples、comparison 和 self-hash 全部存在时使用。任何 `REAL-EXCLUDED`、`SOURCE_BLOCKED`、`WASM_CAPABILITY_BLOCKED` 都必须保持 `blocked`,并带 failure sample。 ## 面板布局和交互范围 桌面和移动布局都必须保持以下固定区域,不因动态文字改变尺寸: 1. 顶栏:案例名、WASM capability、连接/session、`TASK_READY`/`BLOCKED`、ESTOP 和 recover。 2. 左侧状态:machine power、task mode、interp state、program status、current line、logical tick、state hash。 3. 中央机床视图:Three.js Vismach 3-axis model、tool pose、joints、limits、toolpath、axis labels。 4. 右侧 DRO:commanded/actual/relative 的 X/Y/Z、unit、feed/spindle override、active G/M codes。 5. 下方 tabs:Manual、MDI、Program、Diagnostics;每个 tab 的控件 gate 来自 Worker snapshot。 6. 操作条:Home all/individual、jog +/-/stop、Run/Pause/Resume/Step/Abort/Restart、spindle/coolant/tool/probe/ESTOP。 7. 诊断面板:按事件顺序显示 source path、line、code、reason、first divergent tick、recovery status。 熟悉符号使用 AXIS 源图标或 lucide icon;按钮必须有 accessible name、disabled reason 和 keyboard path。控件不能仅通过颜色表示 fault/ESTOP。 ## 功能缺口移植流程 当 WASM/Worker 当前能力不满足 AXIS 行为时,严格按以下顺序处理: 1. 从 LinuxCNC 源文件定位函数、状态常量、命令顺序和错误语义,记录 source path、line、source hash。 2. 判断是否属于纯仿真可移植行为。实时内核、真实硬件、宿主动态 loader、宿主 GUI/Python 进程只能登记 `REAL-EXCLUDED`/`HOST_EXCLUDED`,不能伪造 WASM parity。 3. 先增加 native source sample 和失败测试,再在 `wasm-core` 增加 C ABI/状态实现;ABI 变更必须建立新 context,不能就地改锁定 ABI。 4. 增加真实 WASM module 测试,再接 `web/worker` adapter;Worker 只投影 WASM canonical,不保留 JS authority 镜像。 5. 最后接 React 命令/渲染,补 normal、boundary、error、non-finite、capacity、pause/resume、ESTOP、recovery 样本。 6. 记录首个 divergent phase/tick、native/WASM/Worker hash、checkpoint/trace/recovery hash,更新 self-hashed report。 ## 详细实施任务 ### P0/P1 执行顺序 先完成 P0 的 authority、状态机、按钮 gate、程序加载/执行、preview、实时路径和实时坐标,再实现 P1 的 MDI/JOG 扩展。P0 任务之间按 `PANEL-01 -> PANEL-02 -> PANEL-03 -> PANEL-05 -> PANEL-06/PANEL-07 -> PANEL-08/PANEL-10/PANEL-11 -> PANEL-14 -> PANEL-15` 执行;P1 的 MDI/JOG 只能复用同一 WASM/Worker authority,不能另建一套 UI state。 P0 完成的硬条件:12 项矩阵中的 1-9、12 有 native source reference、真实 WASM module、Worker sequence 和 React evidence;刀具路径 preview 与 native canonical segments 的 hash/端点一致;所有按钮 gate 在正常、运行、暂停、MDI、ESTOP、limit、fault、未 home 和 queue-full 样本中一致。P1 完成的硬条件:10-11 的 MDI/JOG 与 P0 状态机、queue、home/limit、ESTOP gate 共用同一 snapshot,不能产生 divergent tick。 | ID | 任务 | 主要实现 | 直接源输入 | 验收输出 | 依赖 | |---|---|---|---|---|---| | `PANEL-01` | 源控件 inventory | 把 AXIS 菜单、toolbar、快捷键、Manual/MDI/Auto、DRO、诊断控件登记为稳定 IDs | `axis.py`、AXIS Tcl UI、案例 INI | source inventory + control ID contract | 无 | | `PANEL-02` | snapshot projection contract | 固定 task/program/motion/home/hal/devices/configuration/axis_state/visual 字段、tick/hash 和 event envelope | `sim-session.mjs`、T4/T3 reports | schema-compatible projection test | 01 | | `PANEL-03` | source gate matrix | 逐命令复刻 AXIS `ensure_mode`、状态和 gate;禁止 React 自判定 | `axis.py` command functions、`axis-contract.mjs` | gate matrix normal/error/ESTOP test | 01/02 | | `PANEL-04` | startup/config panel | 显示 exact resources、optional `OPEN_FILE`/graphic、HAL/resource/INI diagnostics 和 runtime fixtures | source semantics contract、案例 INI/HAL | `TASK_READY`/`BLOCKED_*` UI test | 02 | | `PANEL-05` | power/ESTOP/recover | 对齐 `STATE_OFF/ON/ESTOP/ESTOP_RESET` 和 recover transition | `axis.py`、`sim_task`、`sim_io` | transition/event/hash report | 02/03 | | `PANEL-06` | Manual/Home/Jog | 对齐 individual/all home、continuous/incremental jog、stop、joint/teleop gate | `axis.py` `jog_*`/`home_*`、T4 home-limit | native/WASM/Worker sequence report | 03/05 | | `PANEL-07` | Program/MDI | 对齐 open/load/reload/run/pause/resume/step/abort/restart/MDI queue and active-program rejection | `axis.py`、WASM interpreter/task | program control and current-line report | 03/05 | | `PANEL-08` | DRO/active codes | 对齐 actual/commanded/relative、units、feed/spindle、active G/M/modal display | AXIS `update`/stat mixins、WASM snapshot | DRO golden and unit switch test | 02/06/07 | | `PANEL-09` | device panels | 对齐 spindle ramp/at-speed, coolant, tool prepare/change, probe, limit/FERROR faults |案例 HAL、`sim_io`、tool-IO report | device fault/recovery report | 05/06/07 | | `PANEL-10` | Vismach/3D | 按 `3axis-tutorial` Python model 和 HAL pins 实现 geometry/pose/tool continuity | 案例 executable、Vismach source、Three.js view | canvas pixel/nonblank/pose report | 02/06/08/09 | | `PANEL-11` | editor/preview | Worker WASM preview、source line highlighting、active G-code 和 diagnostics | `sim_interpreter`、AXIS editor/preview | no-JS-preview test + source-line parity | 07/08 | | `PANEL-12` | persistence/recovery UI | checkpoint, journal, trace import, `.var/.bak/position/tool.tbl` restart and fail-closed recovery | T4/T6 reports、workspace recovery | recovered endpoint and no duplicate side effects | 05/07/09 | | `PANEL-13` | keyboard/accessibility/layout | AXIS shortcut mapping、focus order、ARIA、desktop/mobile no-overlap | AXIS shortcut table、React source | DOM/accessibility/visual report | 01/03/08/10/11 | | `PANEL-14` | native/Worker/UI recorder | 同一 operation sequence、same logical ticks、first divergence and hash comparison | native runner、Dedicated Worker、React | sequence ledger and diff report | 05..13 | | `PANEL-15` | selected-case acceptance | 汇总所有面板区域、normal/boundary/error/recovery 和 release boundaries | all PANEL evidence | self-hashed closure report | 14 | ## 验收和停止条件 通过条件:每个 PANEL row 有 source reference、WASM authority、Worker test、UI evidence、state/event hash 和 recovery sample;同一操作序列 native/WASM/Worker/UI 的首个差异可定位;控制面板无 JS authority、无 JS G-code preview、无 basename 替换。 保持 `blocked`:缺 source/oracle/resource、WASM ABI 不足、首个 divergent tick 未定位、Worker snapshot/hash 不稳定、恢复重复副作用、只在 Chromium 可见但 browser minimum 未锁定,或只能靠 mock/墙钟/宿主能力通过。 停止发布:不把固定案例 verified 写成全量 LinuxCNC parity;Firefox/Safari、OPFS 物理崩溃、实时硬件、官方 oracle、许可证和 owner release gate 继续保持 `STOP_RELEASE`。 ## 证据命令 ```bash python3 scripts/validate_baseline.py python3 scripts/validate_task_pointer.py nextTask1.md --lock context-lock.json python3 scripts/test_source_semantics_contract.py node web/workspace/test_source_semantics.mjs node web/workspace/test_sim_case_loader.mjs node web/worker/test_sim_case_matrix_s8_06.mjs node web/worker/test_wasm_canonical_state.mjs node web/worker/test_wasm_canonical_state_checkpoint.mjs node web/worker/test_axis3_selected_case.mjs node web/worker/test_axis3_visible_completion.mjs npm --prefix web/react run typecheck npm --prefix web/react run build ``` 每个 PANEL 任务必须增加专属测试和 self-hashed report;未完成的任务保留 `queued`/`blocked`,不能从 UI smoke 直接升级为 parity。