# 04 RUN status loop 完成记录与下一步建议

生成时间：2026-06-22

## 1. 本轮完成范围

本轮完成 `03-run-step1-preconditions-complete-next-steps.md` 建议中的连续 feedback 工作，但命名从 `pump` 调整为更明确的：

```text
taskHalStatusLoop
```

这里的 loop 不生成 CNC 状态，不解析 G-code，也不自增行号。它只负责在 `RUN` 之后持续调度：

```text
taskHalRuntime.runCycles(...)
taskHalRuntime.readStatus()
dispatch TASK_HAL_STATUS_APPLIED
```

`activeLine`、`axisPose`、`currentVelocity`、`switchkinsType`、DRO 和 runtime feedback 仍来自同一个 task/HAL/motion status snapshot。

## 2. 已完成改动

### 2.1 store 增加 taskHalStatusLoop

更新：

```text
app/src/state/store.js
```

新增状态：

```text
taskHalStatusLoop:
  active
  sequence
  profileId
  iniPath
  kinematicsModuleId
  tickCount
  batchSize
  intervalMs
  taskPeriodNs
  servoPeriodNs
  lastStatusAt
  lastError
  stopReason
```

并新增：

```text
programRuntimeFeedbackHistory
```

最多保留 100 条 task/HAL/motion runtime feedback。

### 2.2 RUN 改为连续读取 task/HAL status

`RUN` 当前流程：

```text
1. validateRunPreconditions(..., requireTaskHalSession=false)
2. 初始化或复用当前 machine-file task/HAL session
3. validateRunPreconditions(..., requireTaskHalSession=true)
4. 发送:
   EMC_TASK_SET_STATE ON
   EMC_TASK_SET_MODE AUTO
   EMC_TASK_PLAN_RUN
5. runCycles/readStatus 一次，应用首帧 status
6. 如果 status 仍处于 READING，则启动 taskHalStatusLoop
7. loop 持续 runCycles/readStatus，并通过 TASK_HAL_STATUS_APPLIED 更新 UI 状态
```

停止条件：

```text
interpState=IDLE 且 nextProgramLine >= openedLineCount -> complete
interpState=PAUSED 或 motion.paused=true -> paused
motion.aborted=true -> stopped
```

### 2.3 STOP / ABORT / PAUSE / RESUME / STEP

更新行为：

```text
STOP/ABORT:
  先停止 taskHalStatusLoop，再发送 EMC_TASK_ABORT，并读取一次 status。

PAUSE:
  先停止 taskHalStatusLoop，再发送 EMC_TASK_PLAN_PAUSE，并读取一次 paused status。

RESUME:
  发送 EMC_TASK_PLAN_RESUME，若 status 回到 READING，则重新启动 taskHalStatusLoop。

STEP:
  task/HAL runtime loaded 时不走 fixture playback。
  改为明确 runCycles({ taskCycles: 1 }) 后 readStatus 一次。
```

### 2.4 新增验证

新增：

```text
tests/node/verify_run_feedback_loop.mjs
```

覆盖：

```text
1. 加载 xyzac-trt INI。
2. 加载 xyzac-trt kinematics WASM。
3. 加载 task/HAL runtime。
4. stage machine files。
5. 打开 xyzac_switchkins_test_1.ngc。
6. POWER/HOME/AUTO/RUN。
7. 等待 programRuntimeFeedbackHistory 至少 3 条。
8. 断言每条 feedback sourceMode 都是 linuxcnc-task-motion-hal-wasm。
9. 断言 semanticBoundary 是 linuxcnc_task_motion_hal_wasm_simulation_runtime。
10. 断言 taskCycle 和 servoCycle 单调递增。
11. 断言 STOP 后 taskHalStatusLoop.active=false。
```

`app/package.json` 的 `smoke:node` 已加入：

```text
node ../tests/node/verify_run_feedback_loop.mjs
```

## 3. 已运行验证

通过：

```bash
node tests/node/verify_run_feedback_loop.mjs
node tests/node/verify_linuxcnc_task_hal_runtime.mjs
node tests/node/verify_run_preconditions.mjs
node tests/node/verify_rtcp_store.mjs
npm --prefix app run build
npm --prefix app run smoke:node
```

关键输出：

```text
run_feedback_status_loop_smoke=ok
linuxcnc_task_hal_runtime_smoke=ok
run_preconditions_ini_profile_smoke=ok
rtcp_store_smoke=ok
gmoccapy_static_build=ok
```

本轮 `npm --prefix app run smoke:node` 已完整通过，包括 native task/HAL audit。

## 4. 当前 RUN 状态

当前 `RUN` 已经从：

```text
发送 task command -> runCycles({ taskCycles: 5 }) -> readStatus() 一次
```

推进为：

```text
发送 task command
-> runCycles/readStatus 首帧
-> taskHalStatusLoop 持续 runCycles/readStatus
-> TASK_HAL_STATUS_APPLIED 持续消费同一 task/HAL/motion status 链
```

重要边界仍保持：

```text
不引入 JS G-code parser。
不在 UI 自增 activeLine。
不在 UI 自造 axisPose。
不把 canonical timing estimate 当作真实 task/HAL runtime。
status 内容必须来自 task/HAL/motion runtime。
```

## 5. 下一步工作建议

下一步建议做浏览器端证据采集：

```text
1. 扩展 qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs。
2. 新增 07-run-preconditions-and-feedback case。
3. 浏览器中执行 POWER/HOME/AUTO/RUN。
4. 采集 t=0.2s / 0.5s / 1.0s / 2.0s state。
5. 断言:
   - taskHalStatusLoop.tickCount 增长
   - programRuntimeFeedbackHistory 增长
   - activeLine 等于 UI 高亮行
   - DRO 读取 state.programRuntimeFeedback.axisPose
   - RTCP canvas 状态与 state.rtcpState 一致
6. 输出 JSON 和截图证据。
```

随后再考虑：

```text
1. 把 M428/M429 switchkins 状态变化纳入 RUN loop 浏览器断言。
2. 增加 xyzbc-trt 的 RUN status loop 对称测试。
3. 如果需要更接近真实 UI 实时性，再评估是否把 loop 下沉到 worker push status，而不是 store setTimeout polling。
```
