20 KiB
LinuxCNC Web/WASM 完整仿真机床实施蓝图
文档状态:实施规范 v1.0
适用基线:LinuxCNC 2.10.0~pre0,提交由 context-lock.json 冻结
用途:把“完整移植 LinuxCNC 仿真机床”分解为可实现、可回归、可恢复和可发布的工程闭环
运动学主线:运动学完整移植计划
上位路线:技术路线
覆盖依据:仿真全功能覆盖矩阵
源程序依据:功能与源程序关联目录
通信契约:前端与通信详细设计
执行总控:仿真移植总控执行手册
本文件是实施层的补充规范。运动学实现族、正逆解契约和 K0-K8 里程碑以运动学完整移植计划为专项细则。它不替代来源清单、协议 schema 或覆盖矩阵;当这些文件发生冲突时,context-lock.json、协议 schema 和逐文件覆盖记录优先。本文件解决的是:一个 LinuxCNC 仿真入口如何启动,经过哪些运行时层,如何产生确定性结果,如何在浏览器中恢复,以及怎样证明“完整”而不是“演示可用”。
1. 完整移植的工程定义
完整移植必须同时闭合六条链。任何一条链缺失,都只能称为部分移植或演示版本:
| 闭合链 | 必须回答的问题 | 发布前证据 |
|---|---|---|
| 来源闭合 | LinuxCNC 文档、源码、符号、配置资源和测试是否全部登记? | source-index、manifest、依赖闭包、零孤儿报告 |
| 构建闭合 | 可移植代码、适配层、静态组件和 Python 运行时是否可重建? | 编译锁、构建日志、产物 SHA-256、许可证清单 |
| 启动闭合 | 任一纳入范围的 configs/sim 入口能否解析并进入 READY 或给出可诊断错误? |
配置资源图、启动日志、错误文件/行号 |
| 行为闭合 | 解释器、TP、运动、HAL、IO、PLC 和 Python 的可观察结果是否与原生 oracle 一致? | JSONL golden、首个 divergent tick 报告、容差说明 |
| 工作流闭合 | 编辑、MDI、运行、暂停、单步、急停、jog、home、工具、报警和恢复是否能从 UI 完成? | 浏览器 E2E、键盘/触摸、故障和恢复场景 |
| 交付闭合 | 离线、迁移、配额、崩溃恢复、回滚、兼容性和许可证是否可审计? | 发布清单、离线包、迁移/回滚演练、兼容报告 |
“全覆盖”只统计仿真可观察能力。真实硬件、硬实时内核和安全认证属于 REAL-EXCLUDED;但它们在仿真配置中产生的信号必须由数字孪生提供,不能以“不支持”跳过主轴、编码器、探针、限位、换刀、冷却或 IO 的仿真行为。
2. 当前起点和硬门禁
当前仓库的实际起点如下,不能用文档中的目标状态替代代码证据:
| 项目 | 当前状态 | 实施含义 |
|---|---|---|
| M0 来源/上下文 | 已生成,锁定 8,026 个来源条目和 context lock | 任何来源变化先重建并审查哈希 |
| M1 ABI 骨架 | 已有生命周期、固定 tick、快照和导入导出骨架 | 只能作为 ABI 起点,不代表 LinuxCNC 核心已接入 |
| posemath/解释器/运动切片 | 已有仓库内适配报告和原生测试证据 | 仍需接入统一 sim_task、HAL 和 Worker |
| kinematics 节点 | 旧 M2-CORE-04 blocked,当前转入 K0 来源审计 |
锁定来源缺少声明的 linuxcnc/unit_tests/kinematics/**,不得静默改用其他目录 |
| Python、完整 HAL、UI、OPFS 恢复 | 尚未完成全量闭环 | 不得以 mock、刷新后重建或单一三轴页面宣称完成 |
每个实施任务开始前运行基线门禁:
python3 scripts/validate_baseline.py
python3 scripts/validate_download_index.py
python3 -m unittest discover -s scripts -p 'test_*.py' -v
python3 scripts/validate_task_pointer.py
若锁定提交、输入目录、schema 版本或生成器版本发生变化,先创建新的 context_id;不能在旧 context 中覆盖索引或降低校验条件。
3. 运行时分层和依赖规则
完整仿真运行时固定为单 Dedicated Worker 单写者模型。主线程只能通过版本化协议读取状态和提交命令,不能读取 WASM 指针、修改 HAL pin 或直接写 OPFS。
UI / Three.js / 编辑器
│ protocol.v1
Worker: CommandRouter → TaskLedger → SimScheduler
│ ├─ SimTask / rs274ngc / canonical
│ ├─ TP / posemath / kinematics
│ ├─ SimMotion / homing / limits
│ ├─ SimHAL / components / ClassicLadder
│ ├─ SimIO / digital-twin devices
│ └─ PythonRuntime / remap / VCP
├─ SnapshotPublisher / EventBus / TraceRecorder
└─ WorkspaceRuntime → WasmFS/POSIX → OPFS journal/checkpoint
依赖必须单向流动:
- 几何、解释器和运动核心不能依赖 DOM、OPFS、Python 或 UI。
- HAL 组件只能通过注册的 pin/signal/parameter 接口访问运行时;组件不得调用宿主路径、网络或动态加载。
- Python 只能访问白名单虚拟模块、
/workspace和虚拟时钟;超出能力返回稳定错误码。 - UI 只能消费快照、事件和流;派生显示状态不得回写 authoritative state。
- OPFS 只承接事务和恢复,不参与每个仿真 tick 的运动计算。
WASM 核心以独立 CMake/Emscripten 目标构建。LinuxCNC 原生 meson.build 继续用于 oracle 和原生测试,不作为 WASM 的直接构建入口。动态库、fork/exec、NML/CMS 进程边界和共享内存改为静态注册、进程内对象和版本化快照。
4. 一个仿真入口的启动闭环
所有 configs/sim/** 入口都必须经过相同的启动状态机;配置专属差异只能来自已登记能力和资源,不得在 UI 中写隐式特例。
NEW
→ WORKSPACE_MOUNTED
→ INI_PARSED
→ RESOURCES_RESOLVED
→ CORE_CREATED
→ HAL_TWOPASS_DONE
→ DEVICES_BOUND
→ TASK_READY
→ RUNNING / PAUSED / ESTOP
→ STOPPED → DESTROYED
启动步骤及其产物:
HANDSHAKE:协商context_id、ABI/protocol/snapshot 版本和能力注册表;版本不兼容直接拒绝。MOUNT:将工程挂载到虚拟/workspace,规范化大小写、路径和编码,拒绝..、绝对宿主路径和符号链接。PARSE_INI:解析 section、include、变量替换、轴/关节/主轴数量、周期和入口文件;错误必须包含文件、行和字段。RESOLVE:构建.hal、.tbl、.var、.ngc、.py、.xml/.glade/.ui、.clp、.halscope/.halshow等资源图,报告缺失、循环和宿主依赖。CREATE_CORE:按配置静态选择 kinematics、解释器、TP、motion、IO 和工具服务,记录模块版本与 source SHA。HAL_TWOPASS:完成loadrt、组件实例、addf、setp、net、sets、link*的确定性顺序;任何未注册组件阻断启动。BIND_DEVICES:绑定轴、编码器、主轴、探针、限位、回零、换刀、冷却和 PLC 数字孪生,并应用故障模型。START_TASK:加载程序、参数、工具表和 Python/remap;运行一次零周期初始化,生成初始状态哈希。READY:发布完整快照、能力和资源诊断。UI 只有收到READY才能开放运行按钮。
任一步骤失败时,必须撤销尚未提交的资源绑定并保留结构化诊断;不能留下半初始化会话,也不能通过“忽略失败文件”进入 READY。
5. 固定 tick 和状态更新顺序
sim_step(cycles) 是唯一改变仿真结果的入口。每个逻辑周期按下列顺序执行,顺序属于协议和 golden 的一部分:
- 读取本周期已确认的命令,按
seq和operation_id去重;未确认的命令不能重复产生副作用。 - 执行 Task/解释器预算,产生 canonical 队列、当前行、模态和错误。
- 运行 TP 前瞻、blend、速度/加速度/jerk 限制,生成本周期目标轨迹。
- 调用 kinematics
inverse,检查奇异姿态、轴/关节限制和 NaN/Inf。 - 更新 motion、homing、软/硬限位、following error、急停和 jog 状态。
- 按静态线程清单运行 HAL function、虚拟轴/编码器/主轴/探针/换刀和 ClassicLadder scan。
- 处理 IO 完成、输入等待、超时、报警和 Python 回调;回调不能绕过当前 tick。
- 生成事件、采样和状态哈希;先发布内存快照,再按 checkpoint/journal 策略持久化。
暂停只停止后续运动和解释,不丢弃已入队 canonical;急停清空或冻结哪些队列必须由状态机明确规定。快速模式、1x、单步、暂停恢复和回放在相同逻辑终点必须得到相同状态哈希;墙钟只用于节流和让出 Worker 事件循环。
6. 各域实施契约
6.1 配置、工作区和启动资源
实现顺序为“路径规则 → 文件 API → 解析器 → 资源图 → 启动器”。至少提供 open/read/write/stat/readdir/rename/unlink/fsync,并保留 LinuxCNC 相对路径和 include 语义。
| 输入类型 | 处理方式 | 最小验收 |
|---|---|---|
.ini/.inc |
兼容 section、变量、include、相对路径和循环检测 | 逐文件行号诊断、重复加载结果一致 |
.hal/.comp |
解析 twopass、实例、函数顺序和 pin 类型;组件构建期静态注册 | HAL graph 与 native 对齐 |
.tbl/.var |
工具、pocket、offset、参数和 wear 的版本化服务 | 加载、编辑、提交、恢复一致 |
.ngc/.nc |
主程序、子程序、O-word、M-code 文件和过滤器 | 相同入口解析到相同程序图 |
.py |
受控 Python runtime;能力清单和资源限额 | 正常、异常、超时、禁止 API |
.xml/.glade/.ui |
转换为 Web Component descriptor;保留 pin、属性、回调 | 样例面板可加载且权限可审计 |
.clp/.halscope/.halshow |
解析 PLC、采样、触发和回放描述 | 逻辑/波形结果可回放 |
资源图必须记录 input_sha、解析器版本、依赖、输出能力和错误。配置加载不成功时,错误报告必须能反向定位到 manifest 条目。
6.2 解释器、Task、TP、motion 和 kinematics
移植以原生 oracle 为先:先让 native runner 输出解释器状态、canonical、每 tick 运动和错误,再接入 WASM。核心边界只允许使用固定宽度类型和稳定错误码。
实施顺序:
- posemath 和数值策略:固定浮点编译选项、角度/单位换算、NaN/Inf 检查。
- rs274ngc:词法/语法、模态、参数、O-code、循环、补偿、G/M/O 和错误位置。
- canonical/TP:直线、圆弧、球面弧、样条、NURBS、前瞻、blend、exact stop 和同步运动。
- Task/NML 兼容:命令、状态、错误、模式转换、MDI、单步、暂停、恢复、abort 和队列清空。
- motion:轴/关节、jog、home、软硬限位、following error、外部偏置和故障注入。
- kinematics:trivkins、gantry、CoreXY、5/6 轴、Delta、机器人、foam 等静态模块;每个模块提供
forward/inverse/limits/capabilities。
M2-CORE-04 当前因锁定输入缺少 unit_tests/kinematics/** 而 blocked。处理方式只有两种:补充并重新锁定确实存在的官方测试输入,或由契约变更明确批准替代 oracle 并登记差异;不能把 tests/matrixkins/** 直接伪装成原声明输入。
6.3 HAL、组件、PLC 和数字孪生
HAL 运行时必须先建立类型化对象图,再执行组件。组件来源按三类登记:可移植核心、构建期 .comp 组件、仿真专用数字孪生。每个组件 manifest 至少包含名称、版本、输入/输出 pin、参数、线程要求、依赖、权限、测试和状态。
固定调度顺序为:base/servo(如配置启用)→运动反馈→主轴/编码器→通用 HAL function→PLC scan→IO 完成→采样发布。缺少实时墙钟不改变这个逻辑顺序。
数字孪生必须能脚本化以下状态:主轴加减速/不到速/定向失败,编码器计数和丢脉冲,探针触发/未触发,限位/回零,换刀成功/超时/失败,冷却和润滑,模拟/数字 IO 延迟、抖动和故障。故障注入本身也是可回放输入,不能通过随机数产生不可重复结果。
ClassicLadder 需要覆盖工程加载、寄存器、触点/线圈、定时器、计数器、顺序控制、HAL 绑定、扫描周期、暂停/恢复和诊断 UI;真实 Modbus transport 排除,但虚拟寄存器、延迟和错误状态必须可配置。
6.4 Python、remap、VCP 和插件
Python runtime 在 Worker 内启动,固定 Pyodide 版本、包索引和 artifact hash。桥接模块只提供 linuxcnc、hal、gstat、INI、工具表、虚拟时间和工作区 API;subprocess、socket、宿主线程、设备文件和任意路径必须返回稳定的能力拒绝错误。
remap/filter/VCP 每次调用都记录入口、参数摘要、tick、返回值、异常、耗时和资源消耗。Python 异常必须回到解释器/Task 错误语义,不能只写 console。VCP descriptor 转换为带版本和权限的 Web Component schema,插件崩溃不得破坏核心会话。
6.5 协议、快照、事件和持久化
命令必须执行 prepare → execute → verify → commit;operation_id 幂等,回答丢失时先查账本。快照至少分为配置、解释器、Task、TP、motion、kinematics、HAL、IO、Python、工作区和恢复元数据,并带 schema_version/context_id/simulation_tick/state_hash。
OPFS 只保存已提交工程、journal、checkpoint、日志和可删除缓存。推荐恢复流程:读取最近有效 checkpoint → 校验哈希 → 按序重放已提交 journal → 对未确认操作逐项对账 → 发布 RECOVERED 或明确 RECOVERY_BLOCKED。不能以“重新加载 INI 并从头运行”替代状态恢复。
7. 原生 oracle 和全量回归
原生 runner 与 WASM runner 必须使用同一工程输入和同一命令序列。每个样本至少输出:
{
"context_id": "...",
"entry_source_id": "...",
"test_id": "...",
"tick": 1234,
"interpreter": {"line": 12, "modal_hash": "...", "parameters_hash": "..."},
"canonical": [{"kind": "straight_feed", "target": [0, 0, 0]}],
"motion": {"axis": [], "joint": [], "following_error": []},
"hal": {"pins_hash": "...", "signals_hash": "..."},
"io": {"spindle": {}, "tool": {}, "probe": {}},
"events": [],
"state_hash": "..."
}
差异报告必须给出首个 divergent tick、字段路径、原生值、WASM 值、单位、容差、来源文件和 manifest ID。浮点容差只能用于数值字段,不能掩盖状态转换、错误码、事件顺序、当前行、工具号或 HAL 拓扑差异。
每个入口至少执行以下维度:正常运行、边界数值、语法/配置错误、暂停/单步/abort、急停/限位/设备故障、导出导入、Worker 重启/OPFS 恢复、离线启动;有 UI 或插件的入口再加键盘/触摸、权限和背压维度。
发布前必须满足:
source-index、manifest、依赖闭包和配置资源图无孤儿,所有条目已分配 owner/feature/domain。- 所有可计入条目为
accepted;不得残留planned、needs_owner、无 oracle 或无测试维度条目。 - 同一输入在至少三次运行、1x/快速/单步/恢复路径下的最终
state_hash一致。 - 所有事件按
seq/tick单调发布,重放不重复产生已提交副作用。 - 配额不足、schema 不兼容、缺失资源、禁止宿主能力和恢复无法对账均有明确错误,不静默降级。
- 浏览器矩阵、性能、内存、离线、许可证、迁移和回滚报告齐全;性能优化不能改变 golden 结果。
8. 分阶段实施和产物
| 阶段 | 实施重点 | 必须产物 | 退出条件 |
|---|---|---|---|
| M0 基线闭合 | 来源、符号、配置、测试、依赖和 context lock | 索引、manifest、oracle schema、影响图 | 零孤儿、哈希可重建、缺字段会失败 |
| M1 会话闭环 | C ABI、Worker 队列、虚拟工作区、固定 tick、最小恢复 | WASM/原生 ABI 测试、协议 schema、journal/checkpoint | create/load/step/snapshot/export/import/recover 通过 |
| M2 核心行为 | 解释器、canonical、TP、Task、motion、kinematics、工具服务 | 每个源组的适配报告、golden、差异报告 | 受影响核心条目 verified;blocked 输入已处理 |
| M3 仿真系统 | HAL、.comp、ClassicLadder、IO、数字孪生、Python/remap |
组件 manifest、HAL graph、故障矩阵、Python 能力报告 | 配置启动和 S1-S6 场景通过 |
| M4 Web 工作流 | 工程、编辑器、MDI、控制台、HAL/PLC、VCP、Three.js、导入导出 | 浏览器 E2E、截图/像素、性能和恢复证据 | 所有真实 Worker 工作流通过,mock 退出 |
| M5 发布闭合 | 全量 configs/tests/unit_tests、浏览器、离线、迁移、许可证 | 兼容报告、排除项、离线包、回滚包 | G0-G5 全绿,所有计入条目 accepted |
每个阶段都按“输入冻结 → 失败测试 → 最小实现 → native/WASM 对照 → 故障/恢复 → 文档和 manifest → 门禁”执行。阶段间不得用“先做 UI、后补核心”绕过依赖。
9. 实施任务的最小交付包
每个子任务只能有一个主要结果,并在提交中同时更新:
- 受影响 source/feature/task/test/oracle 的 ID 和 source SHA。
- C ABI、协议、快照或工作区 schema 的版本和兼容策略(若有变化)。
- 正常、边界、错误、恢复和离线测试命令及结果路径。
- native/WASM 差异报告、性能/内存/配额影响和已知限制。
- 回滚点、未完成输入、下一任务和
nextTask.md指针。
示例任务标题应具体到单一结果,例如:M3-RT-03b:限位与换刀故障注入可回放,而不是“完善仿真设备”。任务若同时修改两个独立契约,先拆成契约任务和实现任务。
10. 停止线和风险处置
- 来源或测试输入缺失:停止并记录
blocked,由新的契约变更决定是否补充输入;不删样本、不改目录级状态。 - 原生/WASM 首个差异未定位:冻结受影响源组和 golden,不进入下一层集成。
- 协议/ABI/schema 破坏性变化:新建版本、迁移和双向测试;旧版本不能静默接受。
- Python、组件或 VCP 需要宿主能力:改为虚拟能力或明确
REAL-EXCLUDED,并测试拒绝路径。 - OPFS journal/checkpoint 损坏、配额不足或确认丢失:保留最近已提交版本,进入恢复或导出,不覆盖旧数据。
- 非确定性、随机故障或浏览器墙钟改变结果:禁止合并,固定种子、顺序、tick 和输入后重跑。
- 性能优化导致 golden 变化:按行为回归处理,不能以“更快”豁免差异。
11. 当前立即执行顺序
- 按
nextTask.md执行K0-INV-01,处理旧M2-CORE-04的 kinematics 输入阻塞,先确认锁定提交中不存在声明路径,并保留报告。 - 若要采用替代 oracle,先创建可审查的契约变更和新的 context lock;否则保持 blocked,不推进 HAL 节点。
- 在 kinematics 契约闭合后,完成静态模块注册、
forward/inverse/limitsABI、确定性状态哈希和失败路径测试。 - 继续
M2-CORE-05前,先让 native runner、WASM runner 和sim_step共享同一 tick/golden schema。 - 只有 M2 核心条目达到
verified,才进入 HAL/数字孪生/OPFS 恢复;只有 M3 通过,才把 mock UI 替换成真实 Worker 工作流。
本文件完成的是实施规则和证据闭环,不改变当前任务指针,也不把尚未实现的 LinuxCNC 功能标为完成。