# 02 RUN 前置条件、详细测试与编写步骤

生成时间：2026-06-22

## 1. 核心原则

`RUN` 不是孤立按钮逻辑。对五轴机床，必须先确定 LinuxCNC 机床上下文，再执行程序。

正确顺序是：

```text
明确 INI 文件
-> 确定机床类型、坐标轴、joint、HAL、remap、tool-table
-> 确定五轴运动学算法和 switchkins 映射
-> 验证 kinematics WASM 和 task/HAL runtime 已处在同一机床上下文
-> 加载并打开 G-code
-> RUN 发送 LinuxCNC task command
-> 持续 runCycles/readStatus 或 worker push feedback
-> UI 只消费 task/HAL/motion feedback
```

不能先写一个通用 `RUN`，再猜测当前机床是什么。否则 `activeLine`、DRO、RTCP、A/B/C 旋转轴、TCP 补偿和 switchkins 状态都可能来自不同来源。

## 2. 必须先确认的机床上下文

当前项目内可作为 `RUN` 上下文的五轴 profile 只有两个：

| profile | LinuxCNC INI | 机床类型 | 坐标 | kinematics module |
| --- | --- | --- | --- | --- |
| `xyzac-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | table rotary tilting | `XYZAC` | `xyzac-trt` |
| `xyzbc-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` | table rotary tilting | `XYZBC` | `xyzbc-trt` |

源码落点：

```text
app/src/profiles/index.js
app/src/profiles/xyzac-trt.js
app/src/profiles/xyzbc-trt.js
app/src/runtime/linuxcnc-ini-runtime.js
app/src/runtime/linuxcnc-kinematics-runtime.js
wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
```

### 2.1 `xyzac-trt`

必须确认：

```text
profile id: xyzac-trt
INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini
[EMC] MACHINE: sim-xyzac-trt-kins (switchkins)
[TRAJ] COORDINATES: XYZAC
[KINS] KINEMATICS: xyzac-trt-kins sparm=identityfirst
WASM module id: xyzac-trt
WASM file: linuxcnc_xyzac_trt_kinematics.wasm
LinuxCNC source: src/emc/kinematics/xyzac-trt-kins.c
shared source: src/emc/kinematics/trtfuncs.c
switchkins source: src/emc/kinematics/switchkins.c
```

switchkins 映射：

```text
M429 -> switchkins type 0 -> identity
M428 -> switchkins type 1 -> tcp-xyzac
M430 -> switchkins type 2 -> userk
HAL link: motion.analog-out-03 => motion.switchkins-type
```

### 2.2 `xyzbc-trt`

必须确认：

```text
profile id: xyzbc-trt
INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
[EMC] MACHINE: sim-xyzbc-trt-kins (switchkins)
[TRAJ] COORDINATES: XYZBC
[KINS] KINEMATICS: xyzbc-trt-kins sparm=identityfirst
WASM module id: xyzbc-trt
WASM file: linuxcnc_xyzbc_trt_kinematics.wasm
LinuxCNC source: src/emc/kinematics/xyzbc-trt-kins.c
shared source: src/emc/kinematics/trtfuncs.c
switchkins source: src/emc/kinematics/switchkins.c
```

switchkins 映射：

```text
M429 -> switchkins type 0 -> identity
M428 -> switchkins type 1 -> tcp-xyzbc
M430 -> switchkins type 2 -> userk
HAL link: motion.analog-out-03 => motion.switchkins-type
```

## 3. INI definition contract

`RUN` 只能基于一个完整、可验证的 LinuxCNC INI 上下文启动。INI 不是展示信息，而是机床契约来源。`parseLinuxCncIni()` 和 `applyIniConfigToProfile()` 必须把下面字段转成结构化状态，RUN gate 只读取结构化状态，不在 RUN action 中重新猜测字符串。

### 3.1 必须存在的 INI sections

当前五轴 `xyzac-trt` / `xyzbc-trt` profile 至少要求这些 section：

| section | 是否必填 | RUN gate 用途 |
| --- | --- | --- |
| `[EMC]` | 必填 | 识别 machine name，确认是 switchkins 五轴 profile |
| `[DISPLAY]` | 必填 | 提供默认打开程序、程序目录、UI 坐标提示；不能作为运动学真值 |
| `[RS274NGC]` | 必填 | 提供 remap、subroutine path、HAL pin vars、parameter file |
| `[KINS]` | 必填 | 提供 kinematics module 和 joint 数，是 kinematics/runtime 匹配依据 |
| `[HAL]` | 必填 | 提供 HAL 文件、HALUI、POSTGUI_HALFILE、switchkins HALCMD |
| `[HALUI]` | 必填 | 提供 M428/M429/M430 operator command 顺序和语义提示 |
| `[TRAJ]` | 必填 | 提供 COORDINATES、单位、速度/加速度上限 |
| `[EMCMOT]` | 必填 | 提供 motmod、SERVO_PERIOD、COMM_TIMEOUT |
| `[TASK]` | 必填 | 提供 task module 和 CYCLE_TIME |
| `[EMCIO]` | 必填 | 提供 tool table |
| `[AXIS_*]` | 必填 | 提供各坐标轴 limit/velocity/acceleration |
| `[JOINT_*]` | 必填 | 提供 joint 类型、home、limit、home sequence |

不满足这些 section 时，INI readiness 必须是 not ready，RUN 必须阻断。

### 3.2 必填字段与 gate 用途

`[EMC]`：

```text
VERSION
MACHINE
```

要求：

```text
xyzac-trt: MACHINE = sim-xyzac-trt-kins (switchkins)
xyzbc-trt: MACHINE = sim-xyzbc-trt-kins (switchkins)
```

用途：

```text
1. 推导 profileId。
2. 确认当前 profile 是 switchkins 五轴仿真机床。
3. 作为 task/HAL session metadata，写入 feedback 证据。
```

`[DISPLAY]`：

```text
GEOMETRY
OPEN_FILE
JOG_AXES
DISPLAY
PROGRAM_PREFIX
POSITION_OFFSET
POSITION_FEEDBACK
MAX_LINEAR_VELOCITY
MAX_ANGULAR_VELOCITY
```

用途：

```text
1. OPEN_FILE 只能作为默认程序候选，RUN 实际程序必须来自 staged selected G-code。
2. PROGRAM_PREFIX 用于解析 machine-file staging 的相对路径。
3. GEOMETRY/JOG_AXES 用于 UI 呈现，不得覆盖 [TRAJ] COORDINATES。
```

`[RS274NGC]`：

```text
SUBROUTINE_PATH
HAL_PIN_VARS = 1
REMAP = M428 modalgroup=10 ngc=428remap
REMAP = M429 modalgroup=10 ngc=429remap
REMAP = M430 modalgroup=10 ngc=430remap
PARAMETER_FILE
```

用途：

```text
1. HAL_PIN_VARS=1 是 remap 子程序读取 motion.switchkins-type 的前提。
2. M428/M429/M430 remap 文件必须被 stage 到同一 machine context。
3. PARAMETER_FILE 必须随 profile 区分，不能 xyzac/xyzbc 混用。
```

`[KINS]`：

```text
KINEMATICS
JOINTS
```

要求：

```text
xyzac-trt: KINEMATICS = xyzac-trt-kins sparm=identityfirst
xyzbc-trt: KINEMATICS = xyzbc-trt-kins sparm=identityfirst
JOINTS = 5
```

用途：

```text
1. 从 KINEMATICS 推导 kinematicsModuleId。
2. 校验 kinematics WASM moduleId。
3. 校验 joint count 与 [JOINT_0]...[JOINT_4] 完整性。
4. sparm=identityfirst 决定 switchkins type 0 是 identity，不能按默认 kins 猜测。
```

`[HAL]`：

```text
HALUI = halui
HALFILE = LIB:basic_sim.tcl
POSTGUI_HALFILE = switchkins_postgui.hal
HALCMD = net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
```

用途：

```text
1. HALUI 存在时，UI command 与 LinuxCNC halui 行为对齐。
2. HALFILE/POSTGUI_HALFILE 必须 stage，task/HAL runtime 才能构造同一机床上下文。
3. switchkins HALCMD 是 M428/M429/M430 影响 motion.switchkins-type 的证据。
```

`[HALUI]`：

```text
MDI_COMMAND = M429
MDI_COMMAND = M428
MDI_COMMAND = M430
```

用途：

```text
1. 记录 operator 命令入口。
2. 与 [RS274NGC] REMAP 和 [HAL] switchkins HALCMD 交叉校验。
3. 不直接作为 RUN 自动插入命令；RUN 程序内的 M428/M429/M430 由 interpreter/remap 处理。
```

`[TRAJ]`：

```text
COORDINATES
LINEAR_UNITS
ANGULAR_UNITS
DEFAULT_LINEAR_VELOCITY
MAX_LINEAR_VELOCITY
MAX_LINEAR_ACCELERATION
DEFAULT_LINEAR_ACCELERATION
```

要求：

```text
xyzac-trt: COORDINATES = XYZAC
xyzbc-trt: COORDINATES = XYZBC
LINEAR_UNITS = mm
ANGULAR_UNITS = deg
```

用途：

```text
1. COORDINATES 是 UI 轴、G-code 轴、runtime axisPose 的主契约。
2. 单位决定 DRO 和 velocity 显示，不能由前端默认值覆盖。
3. velocity/acceleration 是 planner/runtime limit 的来源。
```

`[EMCMOT]` 与 `[TASK]`：

```text
[EMCMOT]
EMCMOT = motmod
SERVO_PERIOD = 1000000
COMM_TIMEOUT = 1

[TASK]
TASK = milltask
CYCLE_TIME = 0.010
```

用途：

```text
1. SERVO_PERIOD 用于 taskHalStatusLoop 的 servo tick 语义。
2. CYCLE_TIME 用于 task cycle 语义和测试断言。
3. runtime readiness 必须能报告 task/motion/HAL 三者已按这些参数初始化。
```

`[EMCIO]`：

```text
TOOL_TABLE
```

用途：

```text
1. tool table 必须 stage 到 machine files。
2. G43/tool offset 相关状态不能用空表静默替代。
```

`[AXIS_*]`：

```text
MIN_LIMIT
MAX_LIMIT
MAX_VELOCITY
MAX_ACCELERATION
```

要求：

```text
xyzac-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_A] [AXIS_C]
xyzbc-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_B] [AXIS_C]
```

用途：

```text
1. 校验 profile coordinates 对应的 axis section 完整。
2. 约束 UI limit、DRO 范围、preview bounds。
3. 防止 xyzac/xyzbc 轴表混用。
```

`[JOINT_*]`：

```text
TYPE
HOME
MIN_LIMIT
MAX_LIMIT
MAX_VELOCITY
MAX_ACCELERATION
HOME_SEARCH_VEL
HOME_SEQUENCE
```

要求：

```text
JOINT_0..JOINT_4 必须完整。
JOINT_0..JOINT_2 TYPE = LINEAR。
xyzac-trt: JOINT_3/JOINT_4 分别对应 A/C rotary。
xyzbc-trt: JOINT_3/JOINT_4 分别对应 B/C rotary。
```

用途：

```text
1. 校验 [KINS] JOINTS=5 与 joint section 数量一致。
2. home/allHomed gate 的配置来源。
3. task/HAL runtime 初始化 joint status 的来源。
```

### 3.3 INI readiness 输出

`parseLinuxCncIni()` 应输出或间接形成这些结构化字段：

```js
{
  profileId: "xyzac-trt",
  iniPath: "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
  machineName: "sim-xyzac-trt-kins (switchkins)",
  traj: {
    coordinates: "XYZAC",
    linearUnits: "mm",
    angularUnits: "deg"
  },
  kinematics: {
    name: "xyzac-trt-kins",
    moduleId: "xyzac-trt",
    switchkins: true,
    identityFirst: true,
    joints: 5
  },
  rs274ngc: {
    halPinVars: true,
    remaps: ["M428", "M429", "M430"],
    subroutinePath: "./remap_subs",
    parameterFile: "xyzac.var"
  },
  hal: {
    halui: "halui",
    halFiles: ["LIB:basic_sim.tcl"],
    postguiHalFiles: ["switchkins_postgui.hal"],
    switchkinsSignal: "motion.analog-out-03 => motion.switchkins-type"
  },
  emcmot: {
    module: "motmod",
    servoPeriodNs: 1000000
  },
  task: {
    module: "milltask",
    cycleTimeSeconds: 0.010
  },
  emcio: {
    toolTable: "xyzac-trt.tbl"
  },
  axes: {
    X: { minLimit: -200, maxLimit: 200 },
    Y: { minLimit: -100, maxLimit: 100 },
    Z: { minLimit: -120, maxLimit: 120 },
    A: { minLimit: -100, maxLimit: 50 },
    C: { minLimit: -36000, maxLimit: 36000 }
  },
  joints: [
    { index: 0, type: "LINEAR", axis: "X" },
    { index: 1, type: "LINEAR", axis: "Y" },
    { index: 2, type: "LINEAR", axis: "Z" },
    { index: 3, type: "ANGULAR", axis: "A" },
    { index: 4, type: "ANGULAR", axis: "C" }
  ],
  validation: {
    ready: true,
    errors: []
  }
}
```

`xyzbc-trt` 的输出必须只在 profileId、machineName、coordinates、kinematics module、parameter/tool table、rotary axis B/C 等字段上变化，不能沿用 xyzac 的 A/C axis contract。

### 3.4 INI contract 失败时的阻断

这些情况必须让 `state.iniConfigReadiness.ready=false`，并阻断 RUN：

```text
缺少 [EMC] MACHINE。
缺少 [TRAJ] COORDINATES。
COORDINATES 不是 XYZAC 或 XYZBC。
[KINS] KINEMATICS 不是 xyzac-trt-kins 或 xyzbc-trt-kins。
[KINS] JOINTS 不是 5。
缺少 M428/M429/M430 remap。
HAL_PIN_VARS 不是 1。
缺少 motion.analog-out-03 => motion.switchkins-type HALCMD。
缺少 [EMCMOT] SERVO_PERIOD。
缺少 [TASK] CYCLE_TIME。
缺少 TOOL_TABLE。
axis section 与 COORDINATES 不一致。
joint section 数量与 [KINS] JOINTS 不一致。
xyzac INI 指向 xyzbc tool table/parameter file，或反向混用。
```

阻断消息建议：

```text
run blocked: LinuxCNC INI contract invalid
run blocked: INI missing required section [KINS]
run blocked: INI coordinates do not match selected profile
run blocked: INI switchkins remap incomplete
run blocked: INI HAL switchkins signal missing
run blocked: INI joint/axis contract mismatch
```

### 3.5 与 RUN gate 的关系

INI contract 通过只是 RUN 的第一层 gate。RUN 仍必须继续检查：

```text
1. kinematics WASM 已按 INI [KINS] moduleId 加载。
2. machine files 已按同一个 INI stage。
3. task/HAL runtime 已按同一个 INI/session 初始化。
4. selected G-code 已通过 task plan open 打开。
5. task status 的 opened file 与 selected staged G-code 一致。
6. machine ON、AUTO、homed/no_force_homing 满足。
```

所以不能把 `parseLinuxCncIni().validation.ready=true` 等同于 `RUN` ready。

## 4. RUN 前置状态检查

`RUN` action 进入真正执行前，应要求这些状态全部明确：

```text
state.profile.id 是 xyzac-trt 或 xyzbc-trt
state.profile.iniPath 非空且已加载
state.iniConfigReadiness.loaded === true
state.iniConfigReadiness.ready === true
state.iniConfigReadiness.coordinates 与 profile.traj.coordinates 一致
state.profile.kinematicsModuleId 与 INI [KINS] KINEMATICS 推导结果一致
state.kinematicsRuntimeReadiness.loaded === true
state.kinematicsRuntimeReadiness.moduleId === state.profile.kinematicsModuleId
state.rtcpFrame.sourceMode === source-derived-kinematics-wasm
state.machineFileStaging.status === staged
state.taskHalRuntimeReadiness.taskRuntimeReady === true
state.taskHalRuntimeReadiness.motionRuntimeReady === true
state.taskHalRuntimeReadiness.halRuntimeReady === true
state.taskHalSession.programPath 指向当前选中的 G-code
state.taskHalSession.openProgram === true
state.taskHalStatus.task.file 或 openedProgram.path 指向同一个 staged G-code
state.taskHalStatus.task.state 是 ON
state.taskHalStatus.task.mode 是 AUTO，或 RUN 前可切换到 AUTO
state.taskHalStatus.motion.allHomed === true，或当前仿真 profile 明确 no_force_homing
```

如果任一项不满足，`RUN` 应返回明确 operatorMessage，不应进入 fixture line playback 后伪装成真实运行。

建议阻断消息：

```text
run blocked: LinuxCNC INI not loaded
run blocked: machine profile and INI coordinates mismatch
run blocked: LinuxCNC kinematics runtime not ready
run blocked: task/HAL runtime not ready
run blocked: no machine-file G-code opened for task/HAL session
run blocked: machine is not on
run blocked: machine is not homed
run blocked: task mode is not AUTO
```

## 5. LinuxCNC 源码对照结论

`RUN` 命令修改方向总体合适，但必须严格贴近 LinuxCNC 的 task 语义：

```text
RUN 不是前端 sample playback。
RUN 不是直接解析 G-code 后自增 activeLine。
RUN 是在已打开程序、机床可运行、AUTO 模式下发送 EMC_TASK_PLAN_RUN，
随后由 task 主循环持续 read/execute interpreter，并从 motion/HAL 状态读取反馈。
```

源码证据：

```text
/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/halui.cc:1096
  sendProgramRun(int line)
  - updateStatus()
  - 如果 emcStatus->task.file 为空，直接返回 -1
  - 保存 programStartLine
  - 设置 EMC_TASK_PLAN_RUN.line
  - sendAuto()
  - emcCommandSend(EMC_TASK_PLAN_RUN)

/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/shcom.cc:750
  sendProgramOpen(program)
  - 发送 EMC_TASK_PLAN_OPEN
  - 本地进程发送文件名
  - remote process 通过 remote_buffer 分块发送文件内容

/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/shcom.cc:814
  sendProgramRun(int line)
  - status 为 AUTO update 时先 updateStatus()
  - task.file 为空时尝试重新打开 lastProgramFile
  - 再发送 EMC_TASK_PLAN_RUN

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2164
  EMC_TASK_PLAN_OPEN
  - 接收 remote 文件或打开本地文件
  - 调用 emcTaskPlanOpen(open_msg->file)
  - 成功后写入 emcStatus->task.file

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2318
  EMC_TASK_PLAN_RUN
  - 未 homed 且 no_force_homing=false 时拒绝运行
  - 清 single stepping
  - 必要时重新 emcTaskPlanOpen(emcStatus->task.file)
  - 保存 programStartLine
  - 设置 interpState=READING
  - 清 task_paused

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2337
  EMC_TASK_PLAN_PAUSE
  - emcTrajPause()
  - interpState=PAUSED
  - task_paused=1

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2369
  EMC_TASK_PLAN_RESUME
  - emcTrajResume()
  - 恢复 interpState
  - 清 task_paused

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctask.cc:543
  emcTaskPlanOpen(file)
  - 清 motionLine/currentLine/readLine
  - interp.open(file)
  - taskplanopen=1

/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctask.cc:566
  emcTaskPlanRead()
  - interp.read()
  - 文件未打开时用 emcStatus->task.file 重新 open/read
```

对当前 web runtime 的约束：

```text
1. initializeTaskHalSession({ openProgram: true }) 必须等价于先完成 EMC_TASK_PLAN_OPEN。
2. RUN gate 必须确认 opened program 与当前 selected G-code 是同一个 staged machine-file。
3. RUN 发送 EMC_TASK_PLAN_RUN 后，UI 不能用 canonical timing 或 fixture sample 冒充 task 反馈。
4. 连续反馈必须来自 taskHalRuntime.runCycles/readStatus，且 status 的 task/motion/HAL 字段必须同源。
5. PAUSE/RESUME/ABORT 要按 LinuxCNC task command 修改状态循环，而不是只改前端 runState。
```

## 6. 详细测试计划

### 6.1 INI/profile 解析测试

目标：证明 `RUN` 使用的不是手写机床参数，而是明确 LinuxCNC INI。

测试文件建议：

```text
tests/node/verify_run_preconditions.mjs
```

断言：

```text
1. 读取 xyzac-trt.ini，parseLinuxCncIni() 返回:
   profileId=xyzac-trt
   machineName=sim-xyzac-trt-kins (switchkins)
   traj.coordinates=XYZAC
   kinematics.name=xyzac-trt-kins
   kinematicsModuleId=xyzac-trt
   validation.ready=true

2. 读取 xyzbc-trt.ini，parseLinuxCncIni() 返回:
   profileId=xyzbc-trt
   machineName=sim-xyzbc-trt-kins (switchkins)
   traj.coordinates=XYZBC
   kinematics.name=xyzbc-trt-kins
   kinematicsModuleId=xyzbc-trt
   validation.ready=true

3. applyIniConfigToProfile() 后:
   profile.traj.coordinates 与 INI 一致
   profile.kinematicsModuleId 与 INI 推导结果一致
   axisLimits 和 jointConfig 来自 INI
```

命令：

```bash
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
```

通过标准：

```text
run_preconditions_ini_profile_smoke=ok
```

### 6.2 运动学模块确认测试

目标：证明当前机床的五轴运动学算法已经按 INI/profile 加载。

断言：

```text
1. createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }) 成功。
2. readiness.moduleId === "xyzac-trt"。
3. wasmFile === "linuxcnc_xyzac_trt_kinematics.wasm"。
4. frameForJoints([10,20,30,25,40]) 返回 moduleId=xyzac-trt。
5. switchKinematics(1) 后 switchkinsType=1，frame source 仍是 linuxcnc_kinematics_wasm_c_abi。

6. 对 xyzbc-trt 重复同样测试。
```

已有相关测试：

```bash
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
```

新增 RUN 前置测试应把这个结果纳入 `RUN` gate，而不是只作为独立 smoke。

### 6.3 machine-file staging 测试

目标：证明 `RUN` 打开的程序、INI、remap、tool table 是同一个 LinuxCNC 机床目录上下文。

断言：

```text
1. stageProfileMachineFiles(profile) 成功。
2. save.files 中包含:
   - 当前 profile 的 INI
   - 当前 profile 的 tool table
   - remap_subs/428remap.ngc
   - remap_subs/429remap.ngc
   - remap_subs/430remap.ngc
   - 当前 profile 的 demo G-code
3. buildTaskHalSessionFromMachineFiles() 生成:
   profileId 与 state.profile.id 一致
   iniPath 与 profile.iniPath 一致
   programPath 指向当前 selectedGcodeSourceRel
```

已有相关测试：

```bash
node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs
```

### 6.4 RUN 连续 feedback 测试

目标：证明点击 `RUN` 后，不是只推进一次 `taskCycles=5`，而是持续从 task/HAL/motion 获取反馈。

测试程序：

```ngc
G90 G21
M429
G0 X0 Y0 Z5 A0 C0
M428
G1 X10 Y0 Z5 A10 C20 F600
G1 X10 Y10 Z0 A20 C30 F300
M429
G1 X0 Y0 Z5 A0 C0 F600
M30
```

对 `xyzbc-trt` 时把 A 轴替换为 B 轴：

```ngc
G90 G21
M429
G0 X0 Y0 Z5 B0 C0
M428
G1 X10 Y0 Z5 B10 C20 F600
G1 X10 Y10 Z0 B20 C30 F300
M429
G1 X0 Y0 Z5 B0 C0 F600
M30
```

采集至少 8 个 feedback tick，断言：

```text
1. 每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。
2. taskCycle 或 servoCycle 单调递增。
3. activeLine 随 feedback 推进，不停留在 RUN 后第一帧。
4. axisPose 来自同一 tick 的 task/HAL status.ui.axisPose。
5. currentVelocityMmPerMin 来自同一 tick 的 motion currentVel。
6. M428 后 kinsType 切到 tcp-xyzac 或 tcp-xyzbc。
7. M429 后 kinsType 回到 identity。
8. UI 当前高亮行、DRO、runtime feedback 文本读取的是同一个 feedback snapshot。
9. 程序完成后 runState 变为 complete 或 idle，不继续显示 running。
```

失败判定：

```text
RUN 后 servoCycle 只增加一次，然后 0.5s/2s/5s 不变。
activeLine 固定在某一行不再推进。
axisPose 从 HOME 位姿被 task-local 0 值覆盖。
programRuntimeFeedback.sourceMode 不是 linuxcnc-task-motion-hal-wasm。
RTCP 状态与当前 G-code 的 M428/M429 不一致。
```

### 6.5 浏览器端测试

建议扩展：

```text
qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
```

新增 case：

```text
07-run-preconditions-and-feedback
```

浏览器断言：

```text
1. 首屏等待 state.iniConfigReadiness.loaded === true。
2. 选择 xyzac-trt，确认 titlebar/profile/INI/kinematics module 都是 xyzac-trt。
3. 点击 POWER、HOME、AUTO。
4. 加载上述短 G-code。
5. 点击 RUN。
6. 采集 t=0.2s、0.5s、1.0s、2.0s、5.0s 状态。
7. 断言 task/servo cycle 单调递增。
8. 断言 UI `.gcode-row.active` 的 data-program-line 等于 state.activeLine。
9. 断言 DRO X/Y/Z/A/C 等于 state.programRuntimeFeedback.axisPose。
10. 断言 canvas dataset 的 RTCP 状态与 state.rtcpState 一致。
```

输出证据：

```text
qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json
qa/web-rtcp-5axis-site-test/screenshots/run-preconditions-feedback/*.png
```

## 7. 编写 RUN 程序的实现步骤

### Step 1: 增加 `validateRunPreconditions(state)`

位置建议：

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

职责：

```text
1. 检查 INI 已加载。
2. 检查 profile 与 INI 坐标一致。
3. 检查 profile.kinematicsModuleId 与 kinematics runtime moduleId 一致。
4. 检查 kinematics runtime ready。
5. 检查 task/HAL runtime ready。
6. 检查 machine file staging ready。
7. 检查 taskHalSession.programPath 指向当前 G-code。
8. 检查 task status 的 opened file 与当前 staged G-code 一致。
9. 检查 machine ON、AUTO mode、homed/no_force_homing。
```

返回结构：

```js
{
  ok: true,
  profileId: "xyzac-trt",
  iniPath: ".../xyzac-trt.ini",
  coordinates: "XYZAC",
  kinematicsModuleId: "xyzac-trt"
}
```

失败时：

```js
{
  ok: false,
  operatorMessage: "run blocked: LinuxCNC INI not loaded"
}
```

### Step 2: `RUN` action 先调用前置检查

当前 `RUN` 不能直接进入：

```text
runTaskHalCommandSequence([...], { taskCycles: 5 })
```

应先：

```text
1. gateLinuxCncTaskAction(state, action)
2. validateRunPreconditions(state)
3. initializeTaskHalSession({ openProgram: true }) 如需要
4. 确认 readStatus() 的 openProgram/programPath 与 state.activeProgram 对齐
5. 如果 task mode 不是 AUTO，先发送 EMC_TASK_SET_MODE AUTO 并等待 status 确认
6. 如果 task state 不是 ON，先发送 EMC_TASK_SET_STATE ON 并等待 status 确认
```

未满足前置条件时，只设置 `operatorMessage`，不推进任何 line/sample。

### Step 3: 拆分 task command 与 taskHalStatusLoop

`RUN` 应只发送 LinuxCNC task 命令：

```text
EMC_TASK_SET_STATE ON
EMC_TASK_SET_MODE AUTO
EMC_TASK_PLAN_RUN line = linuxcncStartLine
```

说明：

```text
linuxcncStartLine 应按 LinuxCNC 的 program run line 语义保存。
普通从头运行传 0。
run-from-line 才传明确 line，并需要单独处理 start line、previous modal state 和安全性。
不要用 UI activeLine 自行推导 programStartLine。
```

然后启动 `taskHalStatusLoop`：

```text
while running:
  taskHalRuntime.runCycles({ taskCycles: batchSize })
  status = taskHalRuntime.readStatus()
  dispatch TASK_HAL_STATUS_APPLIED(status)
  append feedback history
  if complete/paused/aborted/stopped:
    stop taskHalStatusLoop
```

说明：

```text
这里的 taskHalStatusLoop 可以在 store 主线程调度，也可以放到 worker 内部 postMessage 推送。
关键不是 timer 本身，而是每个 tick 都必须由 task/HAL/motion runtime 的 runCycles/readStatus 产生反馈。
```

### Step 4: 增加 RUN status loop 状态

建议 state 增加：

```js
taskHalStatusLoop: {
  active: false,
  sequence: 0,
  profileId: null,
  iniPath: null,
  kinematicsModuleId: null,
  tickCount: 0,
  lastStatusAt: null,
  lastError: null
}
```

同时增加：

```js
programRuntimeFeedbackHistory: []
```

历史最多保留 100 到 500 条，避免浏览器长程序内存无限增长。

### Step 5: `TASK_HAL_STATUS_APPLIED` 只消费 status

`applyTaskHalStatusPatch()` 应继续负责：

```text
activeLine
axisPose
kinsType
rtcpState
runState
feed.currentVelocity
programRuntimeFeedback
```

但需要补充：

```text
1. programRuntimeFeedbackHistory append 当前 feedback。
2. 记录 feedback.profileId、iniPath、kinematicsModuleId。
3. 对 ui.axisPoseFrame 做严格处理，避免 task-local 0 值覆盖 HOME/work pose。
4. 运行态 axisPose、velocity、DTG、activeLine 必须来自同一个 status snapshot。
```

### Step 6: STOP/ABORT/PAUSE/RESUME 控制 taskHalStatusLoop

行为要求：

```text
STOP/ABORT:
  send EMC_TASK_ABORT
  stop taskHalStatusLoop
  read final status

PAUSE:
  send EMC_TASK_PLAN_PAUSE
  taskHalStatusLoop 可停止或降频读取 paused status
  runState=paused

RESUME:
  send EMC_TASK_PLAN_RESUME
  restart taskHalStatusLoop

STEP:
  不走 fixture sample playback
  发送/推进一个明确的 task cycle batch
  readStatus 一次
  runState=stepping 或 paused
```

### Step 7: UI 显示执行历史

在 `app/src/ui/gmoccapy-shell.js` 的 info/sidebar 区域增加：

```text
Last feedback:
  sourceMode / sample / line / taskCycle / servoCycle / velocity / kinsType

Run history:
  最近 10 条 line/tick
```

UI 只显示 `programRuntimeFeedbackHistory`，不自行推导行号或坐标。

### Step 8: 移除真实 RUN 的 fixture fallback 混淆

保留 fallback 可以用于开发，但必须明确标记：

```text
programExecutionSourceMode=fixture-line-playback
operatorMessage=LinuxCNC task/HAL unavailable; fixture playback only
fullLinuxCncProgramExecutionReady=false
```

在真实 `taskHalRuntime.loaded === true` 且前置条件失败时，不应退回 fixture playback。

## 8. 开发验收命令

基础检查：

```bash
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
npm --prefix web-rtcp-5axis-sim-plan/app run build
```

完整 node smoke：

```bash
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
```

浏览器 smoke：

```bash
bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
```

QA 证据采集：

```bash
node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
```

## 9. 完成标准

`RUN` 可以判定为完善，必须同时满足：

```text
1. 每次 RUN 都能追溯到明确 INI。
2. 每次 RUN 都能说明机床类型和坐标轴。
3. 每次 RUN 都能说明使用哪个 LinuxCNC 五轴 kinematics module。
4. M428/M429/M430 的 switchkins 状态来自 LinuxCNC remap/HAL/task feedback 链。
5. activeLine、DRO、速度、DTG、程序时间来自同一个 task/HAL/motion feedback snapshot。
6. RUN 后 taskCycle/servoCycle 随时间持续推进，直到 paused/stopped/complete。
7. UI 高亮行和执行历史能复现逐行执行过程。
8. fallback playback 不能冒充 LinuxCNC task/HAL runtime。
9. RUN 失败时能明确区分 INI/profile、open program、machine ON、AUTO、homed、runtime readiness 哪一项未满足。
```

未满足以上条件时，状态应继续标记为：

```text
fullLinuxCncProgramExecutionReady=false
hardwareDrive=false
hostRealtimeKernel=false
externalUserMProcessReady=false
```
