# 05 RUN 功能程序实现详细实施步骤 生成时间:2026-06-22 ## 1. 实施目标 本文件把 `working_run` 下 01-04 文档中的原则、前置条件、源码对照和测试结论,整理成可直接执行的程序实现步骤。 目标不是做一个前端 G-code 播放器,而是实现这个链路: ```text LinuxCNC INI contract -> profile 与 INI 一致 -> kinematics WASM 与 INI [KINS] 一致 -> machine files 按同一个 INI stage -> task/HAL runtime 按同一个 machine context 初始化 -> selected G-code 通过 task plan open 打开 -> RUN 发送 LinuxCNC task command -> taskHalStatusLoop 持续 runCycles/readStatus -> UI 只消费 task/HAL/motion feedback ``` ## 2. 实施边界 必须保持的边界: ```text 1. 不在 UI/store 自行解析 G-code 来推进 activeLine。 2. 不在 UI/store 自造 axisPose、velocity、DTG。 3. 不把 canonical timing estimate 当成真实 task/HAL runtime。 4. 不在真实 RUN 前置失败时退回 fixture playback。 5. RUN、PAUSE、RESUME、STOP 必须映射为 LinuxCNC task command 语义。 6. activeLine、DRO、RTCP、velocity、runtime feedback 必须来自同一个 task/HAL/motion status snapshot。 ``` 允许保留的 fallback: ```text fixture-line-playback 只能作为开发演示 fallback。 一旦 taskHalRuntime.loaded === true,真实 RUN 失败必须明确 blocked,不能 fallback 冒充 LinuxCNC runtime。 ``` ## 3. 主要代码落点 需要实现或调整的文件: ```text app/src/runtime/linuxcnc-ini-runtime.js app/src/profiles/index.js app/src/profiles/xyzac-trt.js app/src/profiles/xyzbc-trt.js app/src/runtime/linuxcnc-kinematics-runtime.js app/src/runtime/linuxcnc-machine-file-staging.js app/src/runtime/linuxcnc-task-hal-runtime.js app/src/runtime/linuxcnc-task-hal-worker-client.js app/src/runtime/linuxcnc-task-hal-worker.js app/src/state/store.js app/src/state/linuxcnc-task-policy.js app/src/runtime/full-execution-boundary.js app/src/ui/gmoccapy-shell.js app/package.json ``` 需要新增或扩展的测试: ```text tests/node/verify_run_preconditions.mjs tests/node/verify_machine_file_staging.mjs tests/node/verify_linuxcnc_ini_runtime.mjs tests/node/verify_linuxcnc_kinematics_runtime.mjs tests/node/verify_linuxcnc_task_hal_runtime.mjs tests/node/verify_run_feedback_loop.mjs tests/node/verify_rtcp_store.mjs qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs ``` ## 4. 阶段 0:建立当前状态基线 先确认当前代码和文档一致,避免在错误前提上继续实现。 执行: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.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 ``` 基线记录: ```text 1. 哪些测试已通过。 2. 哪些测试失败,失败是否来自外部 linuxcnc source 缺文件、runtime 缺功能,还是 RUN 逻辑本身。 3. 当前 store 中是否已有 validateRunPreconditions、initializeTaskHalSession、runValidatedTaskHalProgramRun、taskHalStatusLoop。 ``` 通过标准: ```text 已知 smoke 能稳定复现。 失败项有明确归因。 不在没有基线的情况下改 RUN 主流程。 ``` ## 5. 阶段 1:实现 INI definition contract 目标:让 `RUN` 依赖结构化 INI readiness,而不是散落字符串和 profile 默认值。 ### 5.1 扩展 INI parser 文件: ```text app/src/runtime/linuxcnc-ini-runtime.js ``` 实现内容: ```text 1. parseLinuxCncIni(text, options) 必须解析这些 section: [EMC] [DISPLAY] [RS274NGC] [KINS] [HAL] [HALUI] [TRAJ] [EMCMOT] [TASK] [EMCIO] [AXIS_*] [JOINT_*] 2. 返回结构必须包含: machineName profileId kinematics.name kinematicsModuleId kinematicsParameters.sparm kinematicsParameters.joints traj.coordinates traj.linearUnits traj.angularUnits rs274ngc.halPinVars rs274ngc.remaps rs274ngc.subroutinePath rs274ngc.parameterFile hal.halui hal.halFiles hal.postguiHalFiles hal.halcmd hal.switchkinsSignal halui.mdiCommands emcmot.module emcmot.servoPeriodNs task.module task.cycleTimeSeconds emcio.toolTable axisLimits jointConfig validation.ready validation.errors 3. validateIniConfig() 必须校验: required sections 存在 COORDINATES 是 XYZAC 或 XYZBC KINEMATICS 与 profile 一致 JOINTS = 5 JOINT_0..JOINT_4 完整 AXIS_* 与 COORDINATES 一致 M428/M429/M430 remap 完整 HAL_PIN_VARS = 1 motion.analog-out-03 => motion.switchkins-type HALCMD 存在 SERVO_PERIOD 存在 CYCLE_TIME 存在 TOOL_TABLE 存在 ``` 注意: ```text [DISPLAY] GEOMETRY 和 JOG_AXES 只能用于 UI 显示,不能覆盖 [TRAJ] COORDINATES。 [KINS] sparm=identityfirst 决定 switchkins type 0 是 identity。 xyzac-trt 和 xyzbc-trt 的 axis/joint contract 不能混用。 ``` ### 5.2 应用 INI 到 profile 文件: ```text app/src/runtime/linuxcnc-ini-runtime.js app/src/profiles/index.js app/src/profiles/xyzac-trt.js app/src/profiles/xyzbc-trt.js ``` 实现内容: ```text 1. applyIniConfigToProfile(profile, iniConfig) 只接受 validation.ready=true 的 INI。 2. profile.machineName 来自 [EMC] MACHINE。 3. profile.traj 来自 [TRAJ]。 4. profile.kinematicsModuleId 来自 [KINS] KINEMATICS 推导结果。 5. profile.axisLimits 来自 [AXIS_*]。 6. profile.jointConfig 来自 [JOINT_*]。 7. profile.rs274ngc 来自 [RS274NGC]。 8. profile.hal 来自 [HAL]。 9. profile.emcmot/task/emcio 保存到 profile 或 linuxCncIniConfig,供 runtime/session 使用。 ``` 阻断行为: ```text INI validation 不 ready 时: state.iniConfigReadiness.ready=false operatorMessage=LinuxCNC INI contract invalid RUN gate 必须返回 run blocked: LinuxCNC INI contract invalid ``` ### 5.3 测试 扩展: ```text tests/node/verify_linuxcnc_ini_runtime.mjs tests/node/verify_run_preconditions.mjs ``` 断言: ```text 1. xyzac-trt.ini 解析出 XYZAC、xyzac-trt-kins、JOINTS=5、AXIS_X/Y/Z/A/C。 2. xyzbc-trt.ini 解析出 XYZBC、xyzbc-trt-kins、JOINTS=5、AXIS_X/Y/Z/B/C。 3. M428/M429/M430 remap 完整。 4. HAL switchkins signal 完整。 5. 删除任一必填 section 后 validation.ready=false。 6. 删除 HAL_PIN_VARS 后 RUN 被阻断。 7. xyzac 使用 xyzbc tool table 或 parameter file 时 validation.ready=false。 ``` ## 6. 阶段 2:绑定 kinematics runtime 目标:运动学 runtime 必须来自 INI [KINS],不能只靠当前 UI profile 猜测。 文件: ```text app/src/runtime/linuxcnc-kinematics-runtime.js wasm-port/runtime/sdk/src/linuxcnc-kinematics.js app/src/state/store.js ``` 实现内容: ```text 1. load kinematics runtime 时只接受 iniConfig.kinematicsModuleId。 2. state.kinematicsRuntimeReadiness.moduleId 必须等于 profile.kinematicsModuleId。 3. switchKinematics(type) 后 readback 的 switchkinsType 必须进入 rtcpFrame metadata。 4. rtcpFrame.sourceMode 必须是 source-derived-kinematics-wasm。 5. frame metadata 必须记录: profileId iniPath kinematicsModuleId switchkinsType coordinates ``` RUN gate 增加检查: ```text kinematicsRuntimeReadiness.loaded === true kinematicsRuntimeReadiness.moduleId === profile.kinematicsModuleId rtcpFrame.sourceMode === source-derived-kinematics-wasm ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs ``` ## 7. 阶段 3:实现 machine-file staging 目标:task/HAL runtime 看到的 INI、HAL、remap、tool table、G-code 必须来自同一个 LinuxCNC machine context。 文件: ```text app/src/runtime/linuxcnc-machine-file-staging.js app/src/state/store.js ``` 实现内容: ```text 1. stageProfileMachineFiles(profile) 使用 profile.iniPath 和当前 iniText 生成 plan。 2. plan 必须包含: profileId iniPath wasmDir wasmIniPath wasmProgramPath files[] taskHalSession 3. save 必须包含: files[] gcodeSources[] gcodeFiles[] summary storageMode storageCapability 4. files[] 至少包含: 当前 profile INI 当前 profile tool table remap_subs/428remap.ngc remap_subs/429remap.ngc remap_subs/430remap.ngc 当前 profile demos 下的 G-code 5. selectMachineFileProgram(plan, save, sourceRel) 只能选择 LinuxCNC vendored five-axis demo G-code。 6. 选择 G-code 后,machineFileStaging.selectedGcodeSourceRel 和 plan.wasmProgramPath 必须同步更新。 ``` 状态更新: ```text MACHINE_FILE_STAGING_STARTED MACHINE_FILE_STAGING_COMPLETE MACHINE_FILE_STAGING_FAILED LOAD_LINUXCNC_GCODE_SOURCE ``` 失败阻断: ```text run blocked: LinuxCNC machine files not staged run blocked: no machine-file G-code opened for task/HAL session ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs ``` 断言: ```text 1. xyzac-trt stage 后 save.summary.kinds.remap >= 3。 2. gcodeSources 包含 xyzac_switchkins_test_1.ngc。 3. selectedGcodeSourceRel 为空时 RUN 被阻断。 4. LOAD_LINUXCNC_GCODE_SOURCE 后 wasmProgramPath 指向 selected source。 5. xyzac/xyzbc 的 INI、tool table、parameter file 不混用。 ``` ## 8. 阶段 4:初始化 task/HAL session 目标:在 RUN 前完成等价于 LinuxCNC `EMC_TASK_PLAN_OPEN` 的程序打开动作。 文件: ```text app/src/runtime/linuxcnc-task-hal-runtime.js app/src/runtime/linuxcnc-task-hal-worker-client.js app/src/runtime/linuxcnc-task-hal-worker.js app/src/state/store.js ``` 实现内容: ```text 1. attach taskHalRuntime 后写入 taskHalRuntimeReadiness。 2. initializeTaskHalSession({ openProgram: true }) 执行: buildTaskHalSessionFromMachineFiles(...) taskHalRuntime.resetSession() taskHalRuntime.initSession({ profileId, iniPath, iniText, programPath }) taskHalRuntime.stageFiles(session.files) taskHalRuntime.openProgram(session.programPath) dispatch TASK_HAL_SESSION_READY readStatus() dispatch TASK_HAL_STATUS_APPLIED 3. taskHalSession 必须记录: profileId iniPath programPath programSourceRel files semanticBoundary 4. readStatus() 必须能返回: task.file 或 openedProgram.path task.mode task.state task.interpState task.nextProgramLine task.openedLineCount motionStatus.motion ui.axisPose ui.activeLine ui.currentVelocity ui.switchkinsType ``` RUN gate 增加检查: ```text taskHalRuntime.loaded === true taskHalRuntimeReadiness.taskRuntimeReady === true taskHalRuntimeReadiness.motionRuntimeReady === true taskHalRuntimeReadiness.halRuntimeReady === true taskHalSession.programPath === expectedTaskHalProgramPathForState(state) task status opened file === taskHalSession.programPath ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs ``` ## 9. 阶段 5:实现 RUN preconditions gate 目标:所有 RUN 入口都先经过统一 gate。 文件: ```text app/src/state/store.js app/src/state/linuxcnc-task-policy.js ``` 函数: ```text validateRunPreconditions(state, options) expectedTaskHalProgramPathForState(state) gateLinuxCncTaskAction(state, action) ``` 实现顺序: ```text 1. 检查 supported profile: xyzac-trt / xyzbc-trt。 2. 检查 INI loaded + ready。 3. 检查 INI path 与 profile.iniPath 一致。 4. 检查 [TRAJ] COORDINATES 与 profile.traj.coordinates 一致。 5. 检查 [KINS] moduleId 与 profile.kinematicsModuleId 一致。 6. 检查 kinematics runtime loaded/moduleId/frame source。 7. 检查 machine files staged。 8. 检查 selectedGcodeSourceRel 存在。 9. 检查 task/HAL runtime readiness。 10. requireTaskHalSession=true 时检查 taskHalSession.programPath 与 expected program path 一致。 11. 检查 task status: task state ON 或 RUN 前可 set ON task mode AUTO 或 RUN 前可 set AUTO homed/no_force_homing 满足 opened file 与 session programPath 一致 ``` 返回结构: ```js { apiName: "web-rtcp-5axis-run-preconditions", ok: true, profileId, iniPath, coordinates, kinematicsModuleId, selectedGcodeSourceRel, programPath, sourceMode: "linuxcnc-task-motion-hal-wasm", semanticBoundary: "linuxcnc_ini_profile_kinematics_task_hal_run_preconditions" } ``` 失败返回: ```js { ok: false, operatorMessage: "run blocked: ...", profileId, iniPath, coordinates, kinematicsModuleId } ``` 必须覆盖的阻断消息: ```text run blocked: LinuxCNC INI not loaded run blocked: LinuxCNC INI contract invalid run blocked: machine profile and INI path mismatch run blocked: machine profile and INI coordinates mismatch run blocked: machine profile and INI kinematics mismatch run blocked: LinuxCNC kinematics runtime not ready run blocked: LinuxCNC kinematics module mismatch run blocked: LinuxCNC kinematics frame not ready run blocked: LinuxCNC machine files not staged run blocked: no machine-file G-code opened for task/HAL session run blocked: task/HAL runtime not ready run blocked: task/HAL session program mismatch run blocked: machine is not on run blocked: machine is not homed run blocked: task mode is not AUTO ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs ``` ## 10. 阶段 6:实现 RUN command sequence 目标:RUN 按 LinuxCNC task command 发送,不直接播放 fixture。 文件: ```text app/src/state/store.js ``` 函数: ```text runValidatedTaskHalProgramRun() runTaskHalCommandSequence(commands, options) ``` 实现步骤: ```text 1. preflight = validateRunPreconditions(state, { requireTaskHalSession: false })。 2. preflight 失败时只设置 operatorMessage,不改 activeLine、不启动 loop。 3. expectedProgramPath = expectedTaskHalProgramPathForState(state)。 4. 如果 taskHalSession 不存在,或 programPath 不等于 expectedProgramPath: initializeTaskHalSession({ openProgram: true }) 5. ready = validateRunPreconditions(state, { requireTaskHalSession: true })。 6. ready 失败时只设置 operatorMessage,不启动 loop。 7. stopTaskHalStatusLoop("restarted")。 8. 发送: EMC_TASK_SET_STATE ON EMC_TASK_SET_MODE AUTO EMC_TASK_PLAN_RUN line=linuxcncStartLine 9. runCycles({ taskCycles: initialBatch }) 10. readStatus() 11. dispatch TASK_HAL_STATUS_APPLIED 12. 如果 status 仍在 READING,启动 taskHalStatusLoop。 ``` 重要修正: ```text 普通 RUN 从头运行时 linuxcncStartLine=0。 不要用 activeLine - programStartLine 自动推导 run-from-line。 run-from-line 必须作为独立功能实现,并单独处理 previous modal state、安全提示和 LinuxCNC restart semantics。 ``` `runTaskHalCommandSequence()` 要求: ```text 1. allowFixtureSession=false 时绝不创建 fixture task/HAL session。 2. 每条 command 都通过 taskHalRuntime.sendCommand(command)。 3. command 后必须 runCycles/readStatus 一次。 4. status 通过 TASK_HAL_STATUS_APPLIED 统一落入 store。 5. 失败派发 TASK_HAL_COMMAND_FAILED。 ``` ## 11. 阶段 7:实现 taskHalStatusLoop 目标:RUN 后持续推进 task/HAL runtime,并持续消费真实 status。 文件: ```text app/src/state/store.js ``` 状态: ```js taskHalStatusLoop: { active, sequence, profileId, iniPath, kinematicsModuleId, tickCount, batchSize, intervalMs, taskPeriodNs, servoPeriodNs, lastStatusAt, lastError, stopReason } ``` 函数: ```text startTaskHalStatusLoop(options) runTaskHalStatusLoopTick(sequence) stopTaskHalStatusLoop(reason, options) shouldContinueTaskHalStatusLoop(state, status) ``` 实现步骤: ```text 1. start 时先 stopTaskHalStatusLoop("restarted", notify=false)。 2. sequence 自增,写入 TASK_HAL_STATUS_LOOP_STARTED。 3. setTimeout 调度 runTaskHalStatusLoopTick(sequence)。 4. tick 内校验 loop.active、sequence、taskHalRuntime.loaded。 5. 调用 taskHalRuntime.runCycles({ taskPeriodNs, servoPeriodNs, taskCycles: batchSize })。 6. 调用 taskHalRuntime.readStatus()。 7. 如果 sequence 未过期,dispatch TASK_HAL_STATUS_APPLIED(status, loopSequence)。 8. shouldContinueTaskHalStatusLoop 为 true 时继续 setTimeout。 9. paused/complete/stopped/error 时 stopTaskHalStatusLoop。 ``` 停止条件: ```text motion.aborted === true -> stopped interpState === paused 或 motion.paused === true -> paused interpState === idle 且 nextProgramLine >= openedLineCount -> complete sequence 不匹配 -> ignore stale tick runtime missing -> stopped ``` 参数来源: ```text taskPeriodNs 默认来自 [TASK] CYCLE_TIME。 servoPeriodNs 默认来自 [EMCMOT] SERVO_PERIOD。 batchSize 初始可为 5。 intervalMs 初始可为 25。 ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs ``` 断言: ```text 1. RUN 后 programRuntimeFeedbackHistory 至少 3 条。 2. 每条 sourceMode=linuxcnc-task-motion-hal-wasm。 3. semanticBoundary=linuxcnc_task_motion_hal_wasm_simulation_runtime。 4. taskCycle/servoCycle 单调递增。 5. STOP 后 taskHalStatusLoop.active=false。 ``` ## 12. 阶段 8:统一 TASK_HAL_STATUS_APPLIED 目标:所有 task/HAL 状态进入 UI 前只走一个 reducer patch。 文件: ```text app/src/state/store.js ``` 函数: ```text applyTaskHalStatusPatch(state, status, operatorMessage, options) createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) resolveTaskHalAxisPose(state, status) resolveTaskHalKinsType(state, status, activeLine) rtcpStateFromKinsType(kinsType) ``` 必须更新的 state: ```text taskHalStatus taskHalExecutionPending activeLine axisPose kinsType rtcpState programExecutionSourceMode machine.powerOn machine.taskState machine.mode machine.interpState machine.taskPaused runState feed.currentVelocity programRuntimeFeedback programRuntimeFeedbackHistory taskHalStatusLoop.tickCount taskHalStatusLoop.lastStatusAt ``` 规则: ```text 1. activeLine 来自 status.ui.activeLine,不从 timer 或 sample index 推导。 2. axisPose 优先来自 status.ui.axisPose。 3. velocity 来自 status.ui.currentVelocity 或 motion current velocity。 4. switchkinsType 来自 status.ui.switchkinsType/HAL/motion,不从 G-code 文本猜测。 5. rtcpState 由 switchkinsType 映射。 6. runtime feedback history 最多保留 100 条。 7. axisPose、velocity、activeLine、DTG、kinsType 必须来自同一个 status。 ``` 需要重点防止: ```text task-local 0 值覆盖 HOME/work pose。 canonical preview pose 覆盖 runtime feedback pose。 fixture playback sourceMode 混入 task/HAL history。 ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs ``` ## 13. 阶段 9:实现 STOP / ABORT / PAUSE / RESUME / STEP 目标:控制按钮与 LinuxCNC task command 对齐。 文件: ```text app/src/state/store.js app/src/ui/gmoccapy-shell.js ``` 行为: ```text STOP/ABORT: stopTaskHalStatusLoop("stopped") send EMC_TASK_ABORT runCycles/readStatus 一次 TASK_HAL_STATUS_APPLIED PAUSE: stopTaskHalStatusLoop("paused") send EMC_TASK_PLAN_PAUSE runCycles/readStatus 一次 runState=paused RESUME: send EMC_TASK_PLAN_RESUME runCycles/readStatus 一次 如果 interpState 回到 reading,restart taskHalStatusLoop STEP: task/HAL runtime loaded 时不走 fixture sample playback runCycles({ taskCycles: 1 }) readStatus 一次 TASK_HAL_STATUS_APPLIED ``` 测试: ```text 1. RUN 后 PAUSE,loop active=false,runState=paused。 2. RESUME 后 loop 重新 active。 3. STOP 后 loop active=false,motion.aborted/stopped 状态进入 UI。 4. STEP 在 task/HAL loaded 时 sourceMode 仍是 linuxcnc-task-motion-hal-wasm。 ``` ## 14. 阶段 10:UI 显示与交互 目标:UI 只展示 store 中的 LinuxCNC-owned status,不推导运行状态。 文件: ```text app/src/ui/gmoccapy-shell.js app/src/styles/gmoccapy.css ``` 需要显示: ```text 1. INI readiness: loaded/ready/path kinematics coordinates jointCount/axisCount 2. Machine files: staged/error file count gcode source selected storage mode 3. Task/HAL readiness: taskRuntimeReady motionRuntimeReady halRuntimeReady profileId iniPath 4. Runtime feedback: sourceMode line sample taskCycle servoCycle velocity DTG switchkinsType 5. Run history: 最近 10 条 programRuntimeFeedbackHistory。 ``` 交互要求: ```text 1. 选择 G-code 后必须 initializeTaskHalSession({ openProgram: true }) 或标记 session stale。 2. RUN button 只 dispatch RUN_MACHINE_FILE_PROGRAM / RUN action,不直接操作 line。 3. RUN blocked 时显示 operatorMessage。 4. UI 高亮行读取 state.activeLine。 5. DRO 读取 state.axisPose 或 state.programRuntimeFeedback.axisPose。 6. RTCP canvas 状态读取 state.rtcpState。 ``` ## 15. 阶段 11:full execution boundary 目标:让系统明确声明当前是不是完整 LinuxCNC task/HAL run,而不是含糊显示 ready。 文件: ```text app/src/runtime/full-execution-boundary.js app/src/runtime/native-task-hal-audit.js app/src/state/store.js ``` 实现内容: ```text 1. fullLinuxCncProgramExecutionReady 只在这些条件都满足时为 true: INI contract ready profile/INI coordinates match kinematics runtime ready machine files staged task runtime ready motion runtime ready HAL runtime ready selected G-code opened 2. hardwareDrive=false。 3. hostRealtimeKernel=false。 4. externalUserMProcessReady=false。 5. semanticBoundary 明确标注: linuxcnc_task_motion_hal_wasm_simulation_runtime ``` 测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_full_execution_boundary.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_native_task_hal_audit.mjs ``` ## 16. 阶段 12:Node smoke 集成 目标:所有关键链路进入 `npm run smoke:node`。 文件: ```text app/package.json ``` 加入或确认: ```json { "scripts": { "smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_run_preconditions.mjs && node ../tests/node/verify_run_feedback_loop.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs" } } ``` 实际脚本可以保留现有顺序,但必须覆盖: ```text INI parser kinematics runtime machine-file staging task/HAL runtime RUN preconditions RUN feedback loop store status patch full boundary ``` 执行: ```bash npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node ``` ## 17. 阶段 13:浏览器证据采集 目标:证明浏览器 UI 中 RUN 不是一次性 status,也不是 fixture playback。 文件: ```text qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs ``` 新增 case: ```text 07-run-preconditions-and-feedback ``` 浏览器步骤: ```text 1. 打开 app。 2. 等待 state.iniConfigReadiness.loaded === true。 3. 选择 xyzac-trt。 4. 确认 INI/profile/kinematics module 都是 xyzac-trt。 5. 选择 xyzac_switchkins_test_1.ngc。 6. 点击 POWER。 7. 点击 HOME。 8. 点击 AUTO。 9. 点击 RUN。 10. 采集 t=0.2s / 0.5s / 1.0s / 2.0s / 5.0s state。 11. 截图。 ``` 断言: ```text 1. taskHalStatusLoop.tickCount 增长。 2. programRuntimeFeedbackHistory.length 增长。 3. 每条 feedback sourceMode 是 linuxcnc-task-motion-hal-wasm。 4. activeLine 等于 UI 高亮行。 5. DRO 等于 state.programRuntimeFeedback.axisPose。 6. RTCP canvas 状态与 state.rtcpState 一致。 7. runtime feedback text 显示 taskCycle/servoCycle/line/velocity。 8. 没有 fixture-line-playback sourceMode。 ``` 输出: ```text qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json qa/web-rtcp-5axis-site-test/screenshots/run-preconditions-feedback/*.png ``` ## 18. 最终验收命令 基础: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_ini_runtime.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_run_preconditions.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 node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs npm --prefix web-rtcp-5axis-sim-plan/app run build ``` 完整: ```bash npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node 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 node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs ``` ## 19. 完成标准 功能完成必须同时满足: ```text 1. RUN 前能明确追溯到一个 LinuxCNC INI。 2. INI definition contract 完整通过。 3. profile/INI/kinematics runtime/moduleId/coordinates 一致。 4. machine-file staging 使用同一 profile 的 INI、HAL、remap、tool table、G-code。 5. selected G-code 已通过 taskHalRuntime.openProgram 打开。 6. machine ON、AUTO、homed/no_force_homing gate 明确。 7. RUN 发送 EMC_TASK_SET_STATE / EMC_TASK_SET_MODE / EMC_TASK_PLAN_RUN。 8. RUN 后 taskHalStatusLoop 持续推进 runCycles/readStatus。 9. activeLine、DRO、velocity、DTG、switchkinsType、RTCP 来自同一个 status snapshot。 10. PAUSE/RESUME/STOP/STEP 行为与 LinuxCNC task command 对齐。 11. Node smoke 通过。 12. 浏览器 JSON/截图证据证明 UI 正在消费 task/HAL/motion feedback。 13. fallback playback 不会冒充 LinuxCNC task/HAL runtime。 ``` ## 20. 建议实施顺序 推荐实际开发顺序: ```text 1. 补强 INI parser/validation。 2. 补 run_preconditions 测试。 3. 补 machine-file staging 对 xyzac/xyzbc 混用的断言。 4. 补 taskHalSession opened program status 字段。 5. 修正 RUN line=0 与 run-from-line 分离。 6. 扩展 validateRunPreconditions 的 ON/AUTO/homed/opened-file gate。 7. 确认 taskHalStatusLoop 使用 INI CYCLE_TIME/SERVO_PERIOD。 8. 扩展 TASK_HAL_STATUS_APPLIED 的 same-snapshot 断言。 9. 补 PAUSE/RESUME/STOP/STEP 测试。 10. 更新 UI runtime feedback/history。 11. 跑 smoke:node。 12. 补浏览器证据采集。 ``` 每完成一个阶段都先跑该阶段对应 node 测试,再进入下一阶段。