# 02-项目程序开发详细步骤 ## 阶段 0:基线固定 1. 记录当前实现与 LinuxCNC 主循环差距。 2. 固定当前通过的 smoke: - `./tools/verify_task_hal_source_manifest.sh` - `./tests/wasm/node/verify_motion_hal_sync.sh` - `./tests/wasm/node/verify_task_hal_wasm.sh` - `./tests/wasm/node/verify_task_hal_sdk.sh` 3. 记录当前已知阻塞和 readiness: - `./tests/native/verify_task_hal_phase0.sh` 曾因上游运行目录缺少 `xyzac-trt_cmds.hal` 失败,T-016 已通过运行目录 machine overlay 修复该 probe 输入完整性。 - task-hal source manifest 仍显示 `task_hal_runtime_promoted=0`,不代表 full native task/HAL runtime 已 promoted。 ## 阶段 1:建立 task 周期内 motion snapshot 1. 在 `linuxcnc_task_hal_wasm.cpp` 中新增 task runtime 内部 motion snapshot。 2. 在 `linuxcnc_motion_runtime.h` 中定义 `LcmotStatusSnapshot`,字段至少覆盖: - cycle、program_line、motion_id、status、in_position、paused、stepping、aborted; - queue_count、queue_capacity、queue_full、active_depth; - axis/joint command/feedback; - requested/current velocity; - switchkins/analog outputs。 3. 从 `linuxcnc_motion_runtime.c` 暴露结构化读取函数,避免 task 周期内解析 JSON。 4. 在 `linuxcnc_task_hal_wasm.cpp` 中新增 `wasm_emcMotionUpdate()`,把 `LcmotStatusSnapshot` 映射到 task 持有的 `EMC_STAT.motion` 等价结构。 5. 在每个 `lctask_run_cycles()` task 周期调用 motion snapshot update。 6. 将 status JSON 改为读取 task 内已更新的 snapshot,而不是独立拼接最新 motion JSON。 7. 新增测试: - 队列中 motion 状态变化必须在 `runCycles()` 后反映到 task status。 - 不调用 `runCycles()` 时,仅调用 status API 不应推进 task 周期语义。 - 直接调用 low-level `lcmot_step_servo()` 后,task status 不应自动更新,直到下一次 `lctask_run_cycles()`。 ## 阶段 2:重排 `lctask_run_cycles()` 周期顺序 目标顺序: 1. task heartbeat / cycle 自增。 2. command read:从 staged command queue 或 C ABI command slot 取命令。 3. plan:处理模式、run、pause、resume、mdi、home、abort 等 task plan 行为。 4. execute:根据 task exec state 向 motion/io 发命令或等待 motion/io 完成。 5. motion update:读取 motion runtime snapshot 到 task 状态。 6. subordinate sync:处理 estop、motion error、io error、soft limit、abort cleanup 等。 7. task update:刷新 task-specific status。 8. status write:更新 C ABI 可读 status snapshot。 9. servo step:按 standalone WASM 边界推进 deterministic servo cycles。 注意:上游 LinuxCNC 是 native process/realtime 拓扑,WASM 内的 servo step 位置允许作为 standalone runtime 边界记录在决策里,但 task 对外语义必须说明与上游差异。 建议拆成内部函数,避免把所有逻辑堆回 `lctask_run_cycles()`: ```cpp static void task_cycle_begin(TaskRuntime &state); static bool task_read_command(TaskRuntime &state); static int wasm_emcTaskPlan(TaskRuntime &state); static int wasm_emcTaskExecute(TaskRuntime &state); static int wasm_emcMotionUpdate(TaskRuntime &state); static void sync_subordinate_states(TaskRuntime &state); static void wasm_emcTaskUpdate(TaskRuntime &state); static void update_top_level_status(TaskRuntime &state); static void write_status_snapshot(TaskRuntime &state); ``` 每个函数必须在注释或文档中标明对应的上游 `emctaskmain.cc` 行为。 ## 阶段 3:引入 `EMC_STAT` 等价状态容器 1. 对齐 `EMC_STAT.task`、`EMC_STAT.motion`、`EMC_STAT.io` 中本阶段需要的字段。 2. 识别上游真实定义来源: - enum 和函数声明主要在 `emc.hh`; - `EMC_STAT`、`EMC_TASK_STAT`、`EMC_MOTION_STAT`、`EMC_TRAJ_STAT` 真实字段在 `emc_nml.hh`。 3. 优先复用 vendored `emc_nml.hh`/`emc.hh` 类型;如依赖过重,建立字段同名、语义同向的窄 `StandaloneEmcStatus`,并记录差异。 4. 把 `TaskRuntime` 中重复字段逐步映射到 `EMC_STAT.task`。 5. 把 `lcmot` motion status 映射到 `EMC_STAT.motion`。 6. 初始 IO 可用 shim 固定 `DONE`,但必须具备 `status`、`aux.estop`、`fault`、`reason` 字段。 7. 确保 JSON status 只是 `EMC_STAT` 或等价 status buffer 的导出视图。 ## 阶段 4:迁移 wait/execute 语义 1. 对标 `emcTaskExecute()` 的关键状态: - `DONE` - `WAITING_FOR_MOTION_QUEUE` - `WAITING_FOR_MOTION` - `WAITING_FOR_IO` - `WAITING_FOR_MOTION_AND_IO` - `WAITING_FOR_DELAY` - `WAITING_FOR_SYSTEM_CMD` - `ERROR` 2. 先迁移 motion 相关状态,不急于实现 system command/process。 3. `queueFull`、`motion.status`、`io.status` 的判断必须从周期 snapshot 读取。 4. pause/step/resume 的状态变化必须覆盖现有 smoke。 ## 阶段 5:逐步替换自有状态机 1. 把当前 JSON command 分派收缩为 host/C ABI 边界。 2. `lctask_send_command_json()` 不直接修改 `state/mode/interp/exec`,而是入队 command envelope。 3. 在 command read 阶段消费 envelope,生成 LinuxCNC task command 等价物。 4. 把 task plan/execute 语义迁向 vendored/upstream `emctask.cc`、`taskintf.cc`、`emccanon.cc`。 5. 为 native-only/runtime-edge 功能建立 shim: - NML transport - file IO - process spawning - IO process - realtime HAL/thread scheduling 6. 每替换一段,补一条 matrix 任务和验收证据。 具体源码替换顺序见 `08-上游task源码替换分解.md`。实施时按以下顺序推进: 1. `taskintf.cc` minimal bridge:先替换 motion init/update/abort、traj pause/step/resume/linear move。 2. `emctask.cc` state/update/abort:替换 task state/mode/abort/update。 3. `emctask.cc` plan wrapper:接 interpreter open/read/execute/synch。 4. `emccanon.cc` straight motion:接 `INIT_CANON()`、`FINISH()`、`STRAIGHT_TRAVERSE()`、`STRAIGHT_FEED()`。 5. `emctaskmain.cc` execute 分支:用 `interp_list` 和 taskintf issue command。 6. `emccanon.cc` spindle/tool/io 子集:dwell、switchkins、tool/spindle command。 ## 阶段 6:readiness 和文档收口 1. 修正文档、代码、测试中的 readiness 口径。 2. `nativeTaskReady` 只有在真实 vendored/native task 周期被采用后才能为 true。 3. `nativeHalSyncReady` 只有在 HAL 同步达到对应验收后才能为 true。 4. `fullLinuxCncProgramExecutionReady` 只有在完整 program execution 语义闭合后才能为 true。 5. 更新 `docs/source-reuse-map.md`,避免文档声称与实现不一致。 ## 阶段 7:后续 WASM 核心状态机完善 本阶段用于后续新增任务,不改变 T-001 到 T-049 的闭合状态。实施前必须先在 `04-任务矩阵.md` 新增任务编号、状态、验收标准和 gate。 1. 坚持 WASM/C++ 为核心状态机实现: - task/motion 状态机、周期调度、状态聚合、命令执行进入 C/C++。 - `StandaloneEmcStatus` 或后续 `EMC_STAT` 等价容器是 status 事实源。 - `LcmotStatusSnapshot` 是 task 周期读取 motion 的结构化输入。 2. 保持 JS/SDK 为 host 边界: - JS/SDK 可以加载 WASM、写入文件、入队 command、读取 status。 - JS/SDK 不实现 G-code、task、motion、planner 或 canon 语义。 3. 保持 JSON 为通信格式: - command JSON 只描述 host command envelope。 - status JSON 只导出最后一次 status write。 - 不允许新增依赖 JSON 文本推进 task cycle 的路径。 - 新增 status 字段进入 `emcStatus` 对标对象,不再提供旧字段兼容视图。 4. 每次迁移新语义时优先查找 vendored LinuxCNC source: - 能接入 source-anchored subset 时接入 subset。 - 不能直接接入时建立窄 shim,并在 `06-决策记录.md` 写明原因。 5. 每次新增状态字段时同步更新: - C/C++ status 结构体。 - status JSON 导出。 - WASM smoke。 - SDK smoke。 - state matrix。 - source reuse/drift docs gate。 详细边界和检查清单见 `11-WASM核心状态机边界与后续完善路线.md`;status JSON 字段映射、禁止链路和后续 SJ 批次见 `12-status-json-LinuxCNC对标方案.md`。