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

@@ -1,5 +1,353 @@
# XYZAC TRT 面板按钮对标测试报告
## 2026-09-08 10:33 恢复原生清图方式(北京时间,当前结论)
按最新明确要求“按原生的方法如何清除的,web 也同样方式清除”,先更新
流程文件“原生清图方式(10:14)”,再修改实现。本节取代下方历史版本中
自动换程序清图的说明,不再保留该扩展行为。
源码路径:`axis.tcl` 工具栏 Clear Live Plot / Ctrl-K →
`axis.py::clear_live_plot` → `LivePlotter.clear` → `positionlogger.clear`。
`open_file_guts`、`reload_file` 创建/替换程序预览,但不调用这一清图路径。
当前 Web 行为:
| 操作 | 加工轨迹 Live Plot | 程序预览 |
| --- | --- | --- |
| Open、同文件 Reload、编辑后 Reload、加载 Chips | 保留 | 替换 |
| Run、Pause、Resume、Stop、正常结束 | 保留 | 保留 |
| 工具栏 Clear Live Plot / Ctrl+K | 清除,运行时继续累积后续运动 | 保留 |
实现仅移除五个 Web 加载入口的自动清图标志,以及 Worker PROGRAM_LOAD
中的自动清图分支。旧调用即使附带退役的 `clear_live_plot_on_replace` 参数,
也不会额外清图。显式 VISUAL_CLEAR、暂停/恢复通信修复和 Chips 性能优化保留。
验证结果:
- Worker 验证换文件、同文件、编辑后、解析失败与 gate 拒绝均不改变
live epoch;显式 Clear 才推进 epoch,且 task/motion/program 哈希不变。
- 浏览器 Open 和 Reload 保留轨迹;工具栏 Clear 清为 0、静态预览保留。
- Ctrl+K 将 41 个实际运动轨迹点清为 0,12 段静态预览完整保留。
- 默认程序与 Chips 的重复暂停/清图/倍率/继续、暂停中 Step 后继续和
移动端清图/继续均通过;185 条桌面有序命令、4 项预期 task 拒绝,无通信错误。
- 源码契约、TypeScript/生产构建通过;桌面/移动 canvas 检查通过。
- 全量 Worker/App:123 项,113 通过、10 项原有失败;与上一轮失败集合
相同,没有新增失败。既有完整 AXIS 能力边界不因此改变。
原始记录:`/tmp/axis-native-clear-browser.json`、
`/tmp/axis-native-clear-pause.json`、`/tmp/axis-native-clear-regression.log`。
本地预览:`http://127.0.0.1:5175/`,刷新后使用新版本;未进行远程部署。
## 2026-09-08 09:57 暂停/恢复一致性修复(北京时间,当前结论)
遵循“先核对源码、更新流程文档,再修改代码”的顺序。本次修复仅涉及已连接
通信层对 PAUSED 的处理,以及 React 对 task 拒绝的采样处理;既有轨迹、
G-code、TP 步长、进给、程序切换和 UI 控件集合继续使用原实现。
### 已复现的原因
| 命令 | 发送 seq | 旧通信层 last_seq | 结果 |
| --- | --- | --- | --- |
| PROGRAM_PAUSE | 9 | 9 | 成功暂停 |
| VISUAL_CLEAR | 10 | 9 | protocol.invalid_state |
| PROGRAM_RESUME | 11 | 9 | protocol.sequence_gap |
| PROGRAM_ABORT | 12 | 9 | protocol.sequence_gap |
旧 `sim_protocol.c` 在 PAUSED 使用另一份命令白名单,拒绝了 Tcl 仍允许的
Clear、Step、主轴倍率等操作,并且不消费 seq。随后继续和停止都无法恢复
通信连续性。该问题独立于 TP 是否能恢复,也不能通过改写 task 状态解决。
源依据是 `axis.py::task_pause/task_resume/task_pauseresume` 和
`emctaskmain.cc` 的 PAUSED 命令分支、`interpResumeState` 保存/恢复:暂停
保留队列和恢复状态,NML 通道继续接受命令,再由 task 判断执行是否合法。
### 修复及保持不变的行为
- READY/PAUSED 均接收顺序正确的合法通信帧;Worker task gate 继续负责拒绝
非法机床操作,拒绝事件按该 seq 入账,后续 Resume/Stop 不再断档。
- schema、握手、operation_id、真正乱序和 tick 回退校验继续生效;不放宽
CNC gate、不丢弃错误、不自动重发副作用、不用 Reload/清 TP 实现恢复。
- React 遇到 `axis_gate` 拒绝时保留 canonical 状态。若 task 仍运行则继续
原有单请求采样;真正 FAULT/OFF/ESTOP、恢复或其他错误继续停止采样。
- 原生协议单测加入 `-UNDEBUG`,确保 Release 构建确实执行断言;补充暂停中
通信接收和真正坏帧拒绝。旧 MDI 通信拒绝断言改由真实 Worker task 拒绝
用例覆盖,未删除机床拒绝验证。
### 验证结果
| 检查 | 结果 |
| --- | --- |
| 原生构建 / CTest | 22/22 通过,协议断言已启用 |
| `test_xyzac_pause_resume_consistency.mjs` | 153 条真实 WASM 命令,通信 seq 与事件 cursor 一致;重复暂停、清图/倍率、非法命令、单步后继续、WAITING 完成、MDI 暂停/继续、最后停止均通过 |
| `test_xyzac_pause_resume_browser.mjs` | 默认程序与 Chips 各三轮暂停/清图/倍率/继续;重复命令拒绝不终止后续运动;暂停中单步后继续、移动端暂停/清图/继续/停止通过;桌面段 194 条有序命令、4 个预期 task 拒绝,无通信错误 |
| 既有按钮浏览器检查 | 20/20 组通过,浏览器错误 0 |
| 既有轨迹生命周期 | 换新文件清图、同源重复执行保留、Pause/Resume/Stop 保留、显式 Clear 通过 |
| 源码公式/清单 | Tcl 7488 条断言、源码 hash 和 82 个控件 ID 通过 |
| TypeScript / Vite | 通过;保留既有 bundle 大小提示 |
| 全量 Worker/App | **123 项,113 通过,10 项旧失败**;与上一轮失败集合相同,新增失败 0 |
剩余旧失败仍为三轴 adapter/完成状态/工具 IO 和配置数量问题,详见下方
历史记录;不能把本次专项通过写成完整 LinuxCNC 发布或全仓测试通过。
原始记录:`/tmp/axis-pause-resume-build.log`、
`/tmp/axis-pause-resume-browser.json`、`/tmp/axis-pause-resume-regression.log`、
`/tmp/axis-pause-buttons-regression.json`、`/tmp/axis-pause-plot-regression.json`。
桌面/移动截图:`/tmp/axis-pause-resume-desktop.png`、
`/tmp/axis-pause-resume-mobile.png`。
本地生产预览为 `http://127.0.0.1:5175/`。刷新页面才会启动新 WASM Worker;
本次未远程部署,也不会在旧页面已损坏的通信序号上静默重放程序。
## 2026-09-08 换程序清图与 Chips 性能(当前结论)
本次按“核对 AXIS 源码 → 先完善流程文件 → 修改实现 → 复现验收”执行。
对应流程见 `linuxcnc-axis-web-control-panel-source-logic.md` 的“换程序轨迹与
长程序性能”及更新后的“Toolpath display lifecycle”。
### 清图语义
`axis.py::open_file_guts` 替换静态 AxisCanon,不调用 LivePlotter.clear;
原生保留历史加工轨迹。因此这不是原生 Open 的缺陷。本次按用户明确要求,
Web 文件替换发送 `clear_live_plot_on_replace:true`,在新文件/新文本解析
成功后,通过与 VISUAL_CLEAR 共用的 `clearLivePlot()` 递增可视 epoch,
一次发布新预览与新 epoch。这是已记录的 `WEB_EXTENSION`,不冒充原生行为。
默认 PROGRAM_LOAD 保留轨迹;同文件同文本 Reload、Run/Pause/Resume/Stop
保留轨迹。取消、门控拒绝或解析失败不自动清图。加载期间也不再用 null
快照造成假 epoch=0;画布只按真正的 Worker epoch 清除历史和待播放样本。
### 性能原因与测量
修改前 CPU profile 中,82.9% 样本位于 JS `fnv1a64`,主要来自每次 program
snapshot 对 200509 字节源程序重复计算 hash。此外,全部 canonical 线段
都有 samples,旧绘图分支将其各建成独立 Three.js Line,导致约 4684 次
静态 draw call,而非代码注释中声称的批量绘图。
修正为源文本 hash 按不可变文本缓存,保留 WASM 动态状态的实时读取;
同一 servo 批次只读取一次不变的 program paused 状态;相邻 canonical
顶点合并成 rapid/feed 两个 LineSegments;Live Plot 使用增量更新的 GPU
缓冲,不再每几帧复制整段历史。未修改 G-code、F 值、倍率、TP 或 tick 步长。
同一主机、相同输入、两次 Chips 加载,200 task ticks(2 秒仿真)测试:
| 指标 | 修改前 | 修改后 |
| --- | --- | --- |
| 第一次 Chips 加载 | 18.904 s | 1.822 s |
| 再次 Chips 加载 | 18.394 s | 1.688 s |
| 推进 2 秒仿真(第一次) | 10.376 s CPU/墙钟 | 0.325 s CPU/墙钟 |
| 推进 2 秒仿真(再次) | 10.268 s | 0.296 s |
| 200 次长程序 snapshot | 755 ms | 40 ms |
| 5-tick 请求 p95(第一次) | 287 ms | 13.4 ms |
两个尝试的 source hash、preview hash、preview 段数、末端位置、速度、队列、
进给倍率、执行行号在修改前后逐项完全一致;清图 epoch 是唯一预期的可视
生命周期差异。以上是计算吞吐提升,不是放大机床速度。浏览器仍按 1 倍
仿真时间推进,最多一个采样请求在途。
### 完整浏览器场景
在 `http://127.0.0.1:5175/` 运行实际配置的第一份 `xyzac_switchkins.ngc`
直到完成,然后点击加载 `linuxcnc/nc_files/3D_Chips.ngc`,再运行/暂停/
恢复/停止,结果:
| 项目 | 实际结果 |
| --- | --- |
| 第一份程序完成时 Live Plot | 4349 点 |
| 第二份加载完成后旧 Live Plot | 0 点,新 epoch=2 |
| Chips 浏览器加载 | 2624 ms |
| 静态预览 | 4683 段,2 次 draw call;旧程序结束在 Z10,首个 G0 Z10 是正确消去的零位移 |
| 运行 5.020 s 墙钟 | 推进 5.050 s 仿真,比例 1.006 |
| Worker 请求 p95 | 18.2 ms |
| 暂停/停止响应 | 149 / 151 ms |
| 浏览器错误 | 0 |
另外验证同源 Reload 保留轨迹、显式 Clear 只清 Live Plot、不清静态预览;
默认 Worker PROGRAM_LOAD 的原生保留路径及成功/失败/拒绝 Web 替换均通过。
移动端长程序编辑区的自动高度曾扩展到数万像素,已改为固定 grid 高度和
内部滚动。390/768/1440 宽度下页面高度为 2520/2427/1559,编辑器可滚动
81957 像素但只渲染 30/30/37 行;移动端运行比 0.999,错误 0。桌面/移动端
canvas 非空,Chips 实体刀路正常显示,截图已检查。
### 复现与回归
- `scripts/benchmark_xyzac_program_replacement.mjs`:实际 WASM 前后基准。
- `scripts/test_xyzac_second_program_browser.mjs`:完整首程序完成后换 Chips。
- `scripts/test_xyzac_chips_layout_browser.mjs`:三种尺寸、长编辑器滚动及移动运行。
- `scripts/test_xyzac_live_path_lifecycle_browser.mjs`:暂停/恢复/停止、换文件、重复运行、显式清图。
- 源码契约、82 个控件 ID、TypeScript 和生产构建通过。
- 针对性 Worker/React 回归 9/9;全量 Worker/App **122 项,112 通过、10 项原有失败**,与上轮失败集合相同。
原始输出:`/tmp/axis-chips-before.json`、`/tmp/axis-chips-after.json`、
`/tmp/axis-chips-browser.json`、`/tmp/axis-chips-layout.json`、
`/tmp/axis-chips-lifecycle-browser.log`、`/tmp/axis-chips-full-regression.log`。
截图:`/tmp/axis-chips-replacement-1440.png`、`/tmp/axis-chips-replacement-390.png`。
本次仅修正上述换程序和性能问题,保持既有完整 AXIS/所有配置的能力边界,
未进行远程部署;线上页面不会因本地代码修改自动更新。
## 2026-09-08 第二次源码复核(当前结论)
按要求先复核并修正流程文件,再修改 Worker/WASM 和 Web。当前流程文件的
“源码复核修正”和“尚未取得完整等价证据”两节是本次审核入口;早期的
SOURCE_ADAPTER 标签不得用来掩盖其中列出的功能缺口。
### 源码证据
新增 `scripts/test_axis_source_ui_oracle.mjs`,直接执行锁定的
`axis.tcl::update_state`,对 **576 种状态组合、13 条控件公式作 7488 条断言**,
结果通过。源码 Python/Tcl hash 检查和 **82 个控件 ID** 清单检查也通过。
该测试不运行桌面 AXIS,更不替代完整 motion 数值 oracle;它验证原始 Tcl
公式与 Web 的 `ui_gates` 投影一致。
### 本次修正
| 项目 | 源码定义与实现结果 |
| --- | --- |
| UI 与命令门控 | `ui_gates` 对标 Tcl,`gates` 对标命令接收。Run/Step 不再用未回零状态改变原生 UI 公式;MDI UI 可用时,handler 仍可按 manual_ok 拒绝并保留输入 |
| OFF 状态 | `STATE_ESTOP_RESET` 在 AXIS 显示为 OFF;不再根据 last_error 猜测机器状态,也不再将 FAULT 标签直接投影成 ESTOP |
| Step | 初次从 0 行进入暂停;后续可连续解析;已有暂停运动通过 C/WASM 的 TP 执行 ID 边界单步。原生 C 测试验证两条排队运动分两次完成,不能一次排空 |
| WAITING | 程序读完但运动未结束可暂停;恢复原来的完成/等待状态,不重新开始解释;MDI 运动期间正确投影 WAITING 并保持互斥 |
| 模式恢复 | ON+IDLE 的 AUTO 边沿及文件加载后按选中标签发送 SET_MODE;在消息处理时排队,避免延迟 effect 覆盖后续 Run |
| Home Axis | switchkins 始终为 KINEMATICS_BOTH。世界轴字母回零正确拒绝;Worker 独立支持 `HOME {joint:0..4}` 的原生数字 joint 入口,未伪装为世界轴按钮 |
| 主轴覆盖率 | 独立保留 requested_rpm/direction;覆盖率 0 后恢复原转速与方向;React 不再按输出 RPM 的正负推断按钮方向 |
| 手动/MDI 控件组 | 轴单选框及 MDI 历史列表加入各自的源控件组门控;c/i/I 切换步长;Escape 可调用原生 task_stop,中止手动或 MDI 运动 |
| 预览 | Rotate 只切换鼠标 Drag/Rotate;取消自动旋转。limits 读取 C/WASM XYZ 软限位;offsets 读取 G5x;velocity/extents 现在确实改变对应显示。按 glnav.py 修正五个正交视图、Z2 的平面旋转及短程序过大的标记 |
### 验收结果
| 检查 | 结果 |
| --- | --- |
| 原始 Tcl 公式对照 | 7488/7488 通过 |
| 原生 CTest | 22/22 通过;包含双段 TP 单步和方向限位 |
| 实际 WASM 控制链 | `test_axis_source_execution.mjs` 的 UI/命令门控、Step、WAITING/MDI、回零类型、主轴零覆盖率均通过 |
| 生产构建浏览器按钮 | 20/20 组通过 |
| 生产构建事件/显示流程 | 12/12 组通过;包含重复 Step、模式恢复、旋转不自转、真实图层、1440×1000 和 390×844 画布像素检查 |
| 控件清单 | 82 个已登记 ID/家族;初始 DOM 80 个控件,About/历史为动态控件 |
| 前端投影测试 | 4/4 通过 |
| TypeScript / Vite | 通过;既有 bundle 大小提示仍存在 |
| 全部 Worker/App 测试 | **122 项,112 通过,10 失败** |
剩余 10 项均为上轮已在修改前源码复现的旧三轴/配置问题:
`test_axis3_selected_case`、`test_axis3_tutorial_motion`、`test_axis3_tutorial_program`、
`test_axis3_tutorial_sequence`、`test_axis3_tutorial_tool_io`、`test_axis3_visible_completion`、
`test_panel_gate_matrix`、`test_panel_snapshot_projection`、`test_sim_case_matrix_s8_06`、
`test_wasm_canonical_state`(均为 `web/worker/*.mjs`)。没有把这些失败计为通过。
`test_xyzac_manual_control` 已使用真实 joint 编号回零、等待 MDI 队列排空后再
加载程序;原测试的 Z180 超过选定 INI 的 Z 最大值 120,正常路径改为 Z18,
并继续保留越界拒绝和活动 MDI 拒绝断言。ESTOP_RESET 断言改为检查 source
state;界面显示 OFF。首次 Step 及暂停恢复测试已按源 task 分支更新。
日志:`/tmp/axis-source-round2-final-regression.log`、
`/tmp/axis-source-round2-camera-final.log`、`/tmp/axis-source-round2-browser-buttons2.log`。
截图:`/tmp/axis-source-flow-1440.png`、`/tmp/axis-source-flow-390.png`。
本地生产预览:`http://127.0.0.1:5175/`,未执行远程部署。
### 仍不属于完整对标
完整 joint/world 模式、Run 边界警告对话框、独立角速度和物理单位滑块、
原生 task/NML 指令级 Step 细分、全部 G92/旋转/刀具补偿图层、optional stop、
block delete、rapid override、Tool Touch Off 等仍有明确缺口。TP 执行 ID 在
伺服采样后变化,单步边界允许原生单周期内的微小推进,未伪造精确端点。
新 IO 状态布局由 `sim_io_size` 读取;旧布局 checkpoint 会明确拒绝,不能
把旧二进制状态直接套入新结构。
因此本轮结论是**指定控制路径经源码验证并修正,仍有公开的源功能缺口**;
不是“全部 LinuxCNC/AXIS 功能已经完全等价”。`release_status: STOP_RELEASE`。
## 2026-09-08 源流程文件驱动验收
本次按“核对 AXIS 源码 → 完善流程文件 → 修改 WASM/Worker → 修改 Web → 验收”执行。
当前规范为 [`linuxcnc-axis-web-control-panel-source-logic.md`](../docs/linuxcnc-axis-web-control-panel-source-logic.md),
覆盖 **82 个控件 ID/动态家族**,并同步了 DOT 与 PNG 流程图。
本节是当前结论;后面的旧版本记录仅用于历史追溯,不能替代本节或当前流程文件。
状态:`VERIFIED_WITH_SCOPE_GAPS`;发布边界继续为 `STOP_RELEASE`。
本地生产构建预览:`http://127.0.0.1:5175/`。
### 按流程修正
| 流程 | 当前行为与验证 |
| --- | --- |
| 文件打开/重载 | 统一 Worker 加载门控;运行、暂停、活动 TP 队列和读取中的互斥;异步读取后再次检查门控 |
| 按住点动 | 一次 `JOG_START`,C/WASM 选择方向限位;浏览器只发 `SIM_STEP`;释放/取消/移出/失焦/隐藏页只发送一次 `JOG_STOP` |
| 增量点动 | 松开后继续采样直到 WASM 队列排空;线性轴按显示单位转换,A/C 保持角度 |
| MDI | trim、清空输入、相邻历史去重、1000 条历史上限、单击选取、双击一次执行、Up/Down 循环选择 |
| 主轴/冷却/Stop | 区分原生命令接收条件与 Tcl 控件启用条件;主轴指令自行切 MANUAL,F9/F10 先停转分支保留;冷却复选框只在 ON+IDLE 可用,Stop 按钮只在 ON+非 IDLE 可用 |
| 覆盖率 | Feed/Jog/Max/Spindle 设置在 OFF/ESTOP 仍可提交;快照回显数值,设置本身不得使主轴启动 |
| 对刀 | G10 不改变机器位置;X/Y/Z 单位转换与 A/C 角度分开;G54..G59.3 偏置保留到 Reload,重新生成预览并更新 relative 显示 |
| 程序完成 | M2/EOF 后等待最后 TP 段排空;验收同时检查队列排空前 Run 禁用、排空后任务 STOPPED |
| 快捷键/弹窗 | F1 在文本输入中仍有效;大小写和 Ctrl/Alt/Meta 不误映射机器命令;About 焦点、Escape 和菜单关闭按流程处理 |
| 显示 | 修复菜单弹层裁切、文件栏按钮被挤压和窄屏数值裁切;减少画布调试文本遮挡 |
### 实际结果
| 检查 | 结果 |
| --- | --- |
| `scripts/build-wasm.sh` | WASM 构建及内置样例通过;native CTest **22/22** 通过 |
| 源码契约/控件清单 | Python/Tcl 源文件 SHA-256 与锁定值一致;**82** 个控件 ID 已登记 |
| `test_xyzac_panel_buttons_browser.mjs` | 最新生产构建 **20/20** 组通过,浏览器错误 0;含菜单、案例、急停和断电恢复 |
| `test_axis_panel_source_flow_browser.mjs` | **11/11** 组通过;实际统计 Worker 命令,验证五轴双向、快速释放、失焦、键盘按住、MDI 历史、对刀与相对坐标 |
| 浏览器控件清单 | 初始 80 个控件加动态 About,历史家族另在专项生成并验证;无未登记或非法重复 ID |
| 桌面/移动端 | 1440×1000、390×844;画布非空、视图改变像素、资源加载正常、无页面横向溢出 |
| `web/react/src/test_*.mjs` | **4/4** 投影测试通过 |
| TypeScript/Vite | typecheck/build 通过;保留既有大 bundle 提示 |
| 全部 `web/worker/test_*.mjs` + `web/app/test_*.mjs` | **121 项,110 通过,11 失败**;不能标记全仓通过 |
原始输出:`/tmp/axis-panel-core-build-final.log`、
`/tmp/axis-panel-browser-production.log`、`/tmp/axis-panel-source-flow-acceptance.log`、
`/tmp/axis-panel-worker-acceptance.log`。
截图:`/tmp/axis-source-flow-1440.png`、`/tmp/axis-source-flow-390.png`。
### 修改前复现与保留失败
使用主仓库提交 `845075f4a37baca145ecec572d93d8aade17ba32` 的独立源码目录
`/tmp/axis-source-baseline.mK3EZO` 重建原始 WASM,复用相同 LinuxCNC 资源,
对 13 个相关用例复测:1 通过、12 失败。
其中 `test_xyzac_hal_tool_alarm.mjs` 的 MDI 空闲态主轴拒绝已修正;
最终剩余 11 项均属于该基线失败集合,**新增失败数为 0**。
复现输出:`/tmp/axis-panel-baseline-failures.log`。
| 保留用例(均位于 `web/worker/`) | 失败原因 |
| --- | --- |
| `test_axis3_selected_case.mjs`、`test_axis3_tutorial_motion.mjs`、`test_axis3_tutorial_sequence.mjs`、`test_panel_gate_matrix.mjs`、`test_panel_snapshot_projection.mjs`、`test_wasm_canonical_state.mjs` | 旧三轴 motion adapter 缺少 `wasmMotion.jog` |
| `test_axis3_tutorial_program.mjs`、`test_axis3_visible_completion.mjs` | 旧三轴任务完成状态仍为 RUNNING |
| `test_axis3_tutorial_tool_io.mjs` | 旧三轴工具/IO 路径被门控拒绝 |
| `test_sim_case_matrix_s8_06.mjs` | 资源矩阵实际数量 236 与旧断言 235 不一致 |
| `test_xyzac_manual_control.mjs` | 旧用例要求在 MDI 运动队列未排空时接受 `PROGRAM_LOAD`,与当前文档的加载互斥相冲突;保留拒绝行为 |
`test_xyzac_machine_sequence.mjs` 已按源码补充 M2 后的有限队列排空检查,
继续断言 TP 队列非空时 Run 禁用、最终 task STOPPED、queue=0;输出记录实际
`drain_ticks`,不再把固定 8000 个采样等同于完成。
原先关于“Spindle 必须预先 MANUAL”及“OFF/ESTOP 禁止覆盖率”的断言已按
AXIS handler/Tcl 原公式修正,并增加实际 WASM 状态断言。
### 明确边界
Rapid Override、独立 Angular Jog Speed、Override Limits、Block Delete、
Optional Stop、完整 Tool Touch Off/工具偏置 ABI,以及桌面工具编辑器/Quit
仍按流程记录为 `SOURCE_BLOCKED` 或 host boundary。禁用功能不计入已实现
源码对标;当前证据也不代表完整原生 free/teleop 减速、全部坐标旋转/G92/
刀具补偿、所有 LinuxCNC 配置或完整 TP 数值 oracle 已闭合。
复现本次验收:
```bash
bash scripts/build-wasm.sh
node scripts/test_axis_control_contract.mjs
node scripts/test_axis_control_inventory.mjs
node --experimental-strip-types web/worker/test_xyzac_source_widget_gates.mjs
node --experimental-strip-types web/worker/test_xyzac_touch_off_source_flow.mjs
npm run build --prefix web/react
# 本地预览运行后,向这两个脚本传入 PLAYWRIGHT_MODULE 和 XYZAC_PANEL_URL
node scripts/test_xyzac_panel_buttons_browser.mjs
node scripts/test_axis_panel_source_flow_browser.mjs
node --experimental-strip-types --test --test-concurrency=4 web/worker/test_*.mjs web/app/test_*.mjs
```
## 历史记录(不作为当前验收结论)
## 结论
本报告针对当前选定案例