虚拟HAL完全满足Web数控仿真

结论:motion controller matrix 已显式关联 external-offsets、QtDragon multi-joint、vismach remap sims 的 LinuxCNC configs/sim source evidence,并纳入 SDK、browser diagnostics artifact 与 release validation;虚拟 HAL 仍只作为 Web 仿真替代层,不声明 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native HAL module ABI。
This commit is contained in:
2026-06-17 23:00:25 +08:00
parent 71c60d0ea9
commit 7449ff7f4e
6 changed files with 137 additions and 3 deletions

View File

@@ -1033,3 +1033,83 @@ git diff --check ok
做联动,使保存的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。 做联动,使保存的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。
3. 将 ini-panel workflow overview 的 diagnostics URL workflow 接入固定页面表单/按钮, 3. 将 ini-panel workflow overview 的 diagnostics URL workflow 接入固定页面表单/按钮,
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。 让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
### 十一、2026-06-17 Motion Matrix Sim Config Source Evidence
继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL
完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 motion
controller matrix 与 LinuxCNC `configs/sim` 证据建立显式关联,使 external-offsets、
QtDragon multi-joint、vismach remap sims 的 source evidence 可在 SDK、browser
diagnostics artifact 和 release validation 中被直接验证。
新增实质能力:
- `VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES` 新增 `simConfigTargets`
- `x-single-axis-servo-dtg` 映射 `external-offsets`
- `xyz-multi-axis-in-position` 映射 `qtdragon-on-abort`
- `abc-rotary-feedback` 映射 `vismach-remap-sims`
- `createVirtualHalMotionControllerMatrixReport()` 新增:
- `simConfigTargets`
- `simConfigEvidenceRows`
- `simConfigSourceFiles`
- `missingSimConfigTargets`
- motion matrix complete 判定现在同时要求:
- fixture 本身 motion/pin feedback 完整;
- fixture 映射的 LinuxCNC sim config source target 存在;
- `missingSimConfigTargets.length === 0`
- `createVirtualHalSourceComplianceReport()` 的 `sourceFiles` 汇总 now 包含 motion
matrix 关联的 sim config source files
- `createProjectReleaseBrowserDiagnosticsArtifactValidation()` 加严:
- 必须包含 `external-offsets`、`qtdragon-on-abort`、`vismach-remap-sims` targets
- 必须包含 `dynamic_offsets.ini`、`qtdragon_xyyz.ini`、`puma.ini` 具体
LinuxCNC sim config source files
- SDK README 同步说明 motion controller matrix rows 已携带 LinuxCNC `configs/sim`
evidence。
新增/扩展验证:
- `verify_sdk_surface.mjs` 覆盖:
- matrix report 包含三个 sim config targets
- matrix report 包含 external offsets、QtDragon multi-joint、vismach puma source
files
- `missingSimConfigTargets.length === 0`
- fixture row 与 sim config target 的映射存在;
- browser diagnostics artifact validation 的 source files 包含三类 sim config evidence
- `real_simulation_page_smoke.html` 覆盖:
- browser API `getVirtualHalMotionControllerMatrixReport()` 输出三类 sim config
targets
- diagnostics/matrix 输出三类具体 LinuxCNC sim config source files
- release diagnostics validation 现在不能只靠 target 名称通过,必须携带具体 LinuxCNC
sim config source evidence。
关键验证:
```text
sdk_surface_node_smoke=ok
project_release_gate_manifest_node_smoke=ok
project_release_artifact_url_workflow_node_smoke=ok
project_batch_acceptance_workflow_node_smoke=ok
project_batch_acceptance_artifact_node_smoke=ok
browser_real_simulation_page_smoke=ok
git diff --check ok
```
语义边界:
- 本批没有新增 G-code/interpreter/planner/kinematics/remap 语义;
- external-offsets、QtDragon multi-joint、vismach remap sims 只作为 LinuxCNC-owned
sim config source evidence 映射到 virtual HAL motion matrix
- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native
HAL module ABI
- G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed WASM
- Web 方式数控系统仿真所需 HAL、halcmd、motion controller 替代层继续由
source-derived virtual HAL 满足。
下一步工作内容:
1. 将 real simulation page 的 virtual HAL session snapshot 与 release diagnostics artifact
做联动,使保存的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。
2. 将 ini-panel workflow overview 的 diagnostics URL workflow 接入固定页面表单/按钮,
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
3. 为 motion matrix 增加 manifest-backed source check把 matrix 关联的 sim config
source files 与 vendored LinuxCNC manifest 做显式一致性验证。

View File

@@ -313,7 +313,9 @@ private page state.
controller runtime. It supports HAL pin/signal/param storage, `setp`, controller runtime. It supports HAL pin/signal/param storage, `setp`,
`sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs, `sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs,
`addf`, thread start/stop, and servo-period motion stepping for browser `addf`, thread start/stop, and servo-period motion stepping for browser
simulation and Node dashboards. simulation and Node dashboards. The motion controller matrix rows also carry
LinuxCNC `configs/sim` evidence for external offsets, QtDragon multi-joint,
and vismach remap simulation coverage.
- `createVirtualHalSourceComplianceReport()` is the machine-readable gate for - `createVirtualHalSourceComplianceReport()` is the machine-readable gate for
the virtual HAL source rule. It maps replacement targets, runtime the virtual HAL source rule. It maps replacement targets, runtime
capabilities, and HAL pin families back to LinuxCNC source files such as capabilities, and HAL pin families back to LinuxCNC source files such as

View File

@@ -496,6 +496,7 @@ export const VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES = Object.freeze([
"axis.x.pos-cmd", "axis.x.pos-cmd",
"joint.0.pos-fb", "joint.0.pos-fb",
]), ]),
simConfigTargets: Object.freeze(["external-offsets"]),
expected: Object.freeze({ expected: Object.freeze({
moving: true, moving: true,
inPosition: false, inPosition: false,
@@ -528,6 +529,7 @@ export const VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES = Object.freeze([
"joint.1.pos-fb", "joint.1.pos-fb",
"joint.2.pos-fb", "joint.2.pos-fb",
]), ]),
simConfigTargets: Object.freeze(["qtdragon-on-abort"]),
expected: Object.freeze({ expected: Object.freeze({
moving: false, moving: false,
inPosition: true, inPosition: true,
@@ -558,6 +560,7 @@ export const VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES = Object.freeze([
"joint.4.pos-fb", "joint.4.pos-fb",
"joint.5.pos-fb", "joint.5.pos-fb",
]), ]),
simConfigTargets: Object.freeze(["vismach-remap-sims"]),
expected: Object.freeze({ expected: Object.freeze({
moving: false, moving: false,
inPosition: true, inPosition: true,
@@ -2186,6 +2189,11 @@ function valuesClose(actual, expected, tolerance = 0.000001) {
return Math.abs(Number(actual) - Number(expected)) <= tolerance; return Math.abs(Number(actual) - Number(expected)) <= tolerance;
} }
function virtualHalSimConfigTargetsById(targetIds = []) {
const ids = new Set(targetIds);
return VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.filter((target) => ids.has(target.id));
}
export function createVirtualHalMotionControllerMatrixReport(options = {}) { export function createVirtualHalMotionControllerMatrixReport(options = {}) {
const fixtures = options.motionFixtures ?? options.fixtures ?? VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES; const fixtures = options.motionFixtures ?? options.fixtures ?? VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES;
const tolerance = clampNumber(options.tolerance ?? 0.000001, 0, 1); const tolerance = clampNumber(options.tolerance ?? 0.000001, 0, 1);
@@ -2244,11 +2252,25 @@ export function createVirtualHalMotionControllerMatrixReport(options = {}) {
distanceToGoMatched && distanceToGoMatched &&
movingMatched && movingMatched &&
axisFeedbackRows.every((row) => row.matched); axisFeedbackRows.every((row) => row.matched);
const simConfigEvidenceRows = virtualHalSimConfigTargetsById(fixture.simConfigTargets ?? [])
.map((target) => ({
id: target.id,
label: target.label,
sourceFiles: [...target.sourceFiles],
capabilities: [...target.capabilities],
evidence: target.evidence,
}));
const missingSimConfigTargets = (fixture.simConfigTargets ?? [])
.filter((targetId) => !simConfigEvidenceRows.some((target) => target.id === targetId));
return { return {
id: fixture.id, id: fixture.id,
label: fixture.label, label: fixture.label,
source: "linuxcnc-motion-source-derived-virtual-motion-controller", source: "linuxcnc-motion-source-derived-virtual-motion-controller",
sourceFiles: [...(fixture.sourceFiles ?? [])], sourceFiles: [...(fixture.sourceFiles ?? [])],
simConfigTargets: [...(fixture.simConfigTargets ?? [])],
simConfigEvidenceRows,
simConfigSourceFiles: [...new Set(simConfigEvidenceRows.flatMap((target) => target.sourceFiles))],
missingSimConfigTargets,
complete, complete,
target: { ...(fixture.target ?? {}) }, target: { ...(fixture.target ?? {}) },
initialPosition: { ...(fixture.initialPosition ?? {}) }, initialPosition: { ...(fixture.initialPosition ?? {}) },
@@ -2269,7 +2291,8 @@ export function createVirtualHalMotionControllerMatrixReport(options = {}) {
}; };
}); });
const missingFixtures = rows.filter((row) => !row.complete).map((row) => row.id); const missingFixtures = rows.filter((row) => !row.complete).map((row) => row.id);
const complete = rows.length > 0 && missingFixtures.length === 0; const missingSimConfigTargets = rows.flatMap((row) => row.missingSimConfigTargets);
const complete = rows.length > 0 && missingFixtures.length === 0 && missingSimConfigTargets.length === 0;
return { return {
apiName: "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report", apiName: "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report",
matrixVersion: 1, matrixVersion: 1,
@@ -2278,13 +2301,17 @@ export function createVirtualHalMotionControllerMatrixReport(options = {}) {
complete, complete,
webSimulationSatisfied: complete, webSimulationSatisfied: complete,
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))], sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
simConfigSourceFiles: [...new Set(rows.flatMap((row) => row.simConfigSourceFiles))],
simConfigTargets: [...new Set(rows.flatMap((row) => row.simConfigTargets))],
requiredPins: [...new Set(fixtures.flatMap((fixture) => fixture.requiredPins ?? []))], requiredPins: [...new Set(fixtures.flatMap((fixture) => fixture.requiredPins ?? []))],
fixtureCount: rows.length, fixtureCount: rows.length,
missingFixtures, missingFixtures,
missingSimConfigTargets,
rows, rows,
summaryRows: [ summaryRows: [
{ id: "fixtures", label: "Motion matrix fixtures", value: `${rows.length}` }, { id: "fixtures", label: "Motion matrix fixtures", value: `${rows.length}` },
{ id: "source", label: "LinuxCNC motion source", value: complete ? "source-derived" : "incomplete" }, { id: "source", label: "LinuxCNC motion source", value: complete ? "source-derived" : "incomplete" },
{ id: "sim-config-source", label: "LinuxCNC sim config source", value: missingSimConfigTargets.length === 0 ? "source-derived" : `missing ${missingSimConfigTargets.length}` },
{ id: "servo-period", label: "Servo period stepping", value: complete ? "covered" : "blocked" }, { id: "servo-period", label: "Servo period stepping", value: complete ? "covered" : "blocked" },
{ id: "axis-joint-feedback", label: "Axis/joint feedback", value: complete ? "covered" : "blocked" }, { id: "axis-joint-feedback", label: "Axis/joint feedback", value: complete ? "covered" : "blocked" },
], ],
@@ -2526,6 +2553,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
...simConfigSourceCoverage.sourceFiles, ...simConfigSourceCoverage.sourceFiles,
...commandScriptFixtures.sourceFiles, ...commandScriptFixtures.sourceFiles,
...motionControllerMatrix.sourceFiles, ...motionControllerMatrix.sourceFiles,
...motionControllerMatrix.simConfigSourceFiles,
])], ])],
missingCapabilitySources, missingCapabilitySources,
missingFamilySources, missingFamilySources,

View File

@@ -121,7 +121,13 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.distance-to-go") && arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.distance-to-go") &&
arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.in-position") && arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.in-position") &&
arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/motion.c") && arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/motion.c") &&
arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/axis.c"); arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/axis.c") &&
arrayOrEmpty(motionControllerMatrix.simConfigTargets).includes("external-offsets") &&
arrayOrEmpty(motionControllerMatrix.simConfigTargets).includes("qtdragon-on-abort") &&
arrayOrEmpty(motionControllerMatrix.simConfigTargets).includes("vismach-remap-sims") &&
arrayOrEmpty(motionControllerMatrix.simConfigSourceFiles).includes("linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini") &&
arrayOrEmpty(motionControllerMatrix.simConfigSourceFiles).includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini") &&
arrayOrEmpty(motionControllerMatrix.simConfigSourceFiles).includes("linuxcnc/configs/sim/axis/vismach/puma/puma.ini");
const replacementReady = replacement.ready === true && const replacementReady = replacement.ready === true &&
replacement.replacesHostRuntimeForSimulation === true && replacement.replacesHostRuntimeForSimulation === true &&
objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true && objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true &&
@@ -154,6 +160,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
...arrayOrEmpty(simConfigSourceCoverage.sourceFiles), ...arrayOrEmpty(simConfigSourceCoverage.sourceFiles),
...arrayOrEmpty(commandScriptFixtures.sourceFiles), ...arrayOrEmpty(commandScriptFixtures.sourceFiles),
...arrayOrEmpty(motionControllerMatrix.sourceFiles), ...arrayOrEmpty(motionControllerMatrix.sourceFiles),
...arrayOrEmpty(motionControllerMatrix.simConfigSourceFiles),
])], ])],
rows: [ rows: [
{ {

View File

@@ -802,6 +802,12 @@
!motionControllerMatrix.requiredPins.includes("motion.distance-to-go") || !motionControllerMatrix.requiredPins.includes("motion.distance-to-go") ||
!motionControllerMatrix.requiredPins.includes("motion.in-position") || !motionControllerMatrix.requiredPins.includes("motion.in-position") ||
!motionControllerMatrix.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c") || !motionControllerMatrix.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c") ||
!motionControllerMatrix.simConfigTargets.includes("external-offsets") ||
!motionControllerMatrix.simConfigTargets.includes("qtdragon-on-abort") ||
!motionControllerMatrix.simConfigTargets.includes("vismach-remap-sims") ||
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini") ||
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini") ||
!motionControllerMatrix.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/vismach/puma/puma.ini") ||
!motionControllerMatrix.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true) !motionControllerMatrix.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true)
) { ) {
throw new Error(`AXIS-style virtual motion controller matrix did not pass: ${JSON.stringify(motionControllerMatrix)}`); throw new Error(`AXIS-style virtual motion controller matrix did not pass: ${JSON.stringify(motionControllerMatrix)}`);

View File

@@ -661,7 +661,15 @@ assert.equal(motionControllerMatrixReport.sourceFiles.includes("linuxcnc/src/emc
assert.equal(motionControllerMatrixReport.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c"), true); assert.equal(motionControllerMatrixReport.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c"), true);
assert.equal(motionControllerMatrixReport.requiredPins.includes("motion.distance-to-go"), true); assert.equal(motionControllerMatrixReport.requiredPins.includes("motion.distance-to-go"), true);
assert.equal(motionControllerMatrixReport.requiredPins.includes("motion.in-position"), true); assert.equal(motionControllerMatrixReport.requiredPins.includes("motion.in-position"), true);
assert.equal(motionControllerMatrixReport.simConfigTargets.includes("external-offsets"), true);
assert.equal(motionControllerMatrixReport.simConfigTargets.includes("qtdragon-on-abort"), true);
assert.equal(motionControllerMatrixReport.simConfigTargets.includes("vismach-remap-sims"), true);
assert.equal(motionControllerMatrixReport.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini"), true);
assert.equal(motionControllerMatrixReport.simConfigSourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"), true);
assert.equal(motionControllerMatrixReport.simConfigSourceFiles.includes("linuxcnc/configs/sim/axis/vismach/puma/puma.ini"), true);
assert.equal(motionControllerMatrixReport.missingSimConfigTargets.length, 0);
assert.equal(motionControllerMatrixReport.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true), true); assert.equal(motionControllerMatrixReport.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true), true);
assert.equal(motionControllerMatrixReport.rows.some(({ id, simConfigTargets }) => id === "x-single-axis-servo-dtg" && simConfigTargets.includes("external-offsets")), true);
assert.equal(motionControllerMatrixReport.rows.every(({ axisFeedbackRows }) => axisFeedbackRows.every(({ matched }) => matched)), true); assert.equal(motionControllerMatrixReport.rows.every(({ axisFeedbackRows }) => axisFeedbackRows.every(({ matched }) => matched)), true);
assert.equal(virtualHalRuntime.getMotionControllerMatrixReport().complete, true); assert.equal(virtualHalRuntime.getMotionControllerMatrixReport().complete, true);
const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), { const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), {
@@ -777,6 +785,9 @@ assert.equal(browserDiagnosticsArtifactValidation.commandScriptFixturesReady, tr
assert.equal(browserDiagnosticsArtifactValidation.motionControllerMatrixReady, true); assert.equal(browserDiagnosticsArtifactValidation.motionControllerMatrixReady, true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true); assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c"), true); assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/vismach/puma/puma.ini"), true);
const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }); const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS });
assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true); assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true);
assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0); assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);