diff --git a/text17.txt b/text17.txt index 4288943..5044162 100644 --- a/text17.txt +++ b/text17.txt @@ -757,3 +757,112 @@ browser_real_simulation_page_smoke=ok 让用户可直接保存和恢复完整 Web CNC 仿真会话。 3. 将 browser diagnostics artifact 的 URL workflow 接入 ini-panel workflow overview 页面, 让 release readiness 页面能显示 browser diagnostics validation rows。 + +### 八、2026-06-17 Virtual HAL Motion Controller Matrix Report + +继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL +完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 motion controller +仿真测试矩阵提升为 SDK/API/report/gate 能力,使多轴 target、servo period、distance-to-go、 +in-position 与 HAL pin feedback 的关系可由 LinuxCNC motion source evidence 验证。 + +新增实质能力: + +- 新增 `VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES`: + - `x-single-axis-servo-dtg`: + - 单轴 X target; + - servo period `dt`; + - velocity limit; + - `motion.distance-to-go`; + - `motion.in-position`; + - `axis.x.pos-cmd`; + - `joint.0.pos-fb`; + - `xyz-multi-axis-in-position`: + - XYZ 多轴 target; + - 多周期 servo stepping; + - final `in-position`; + - axis feedback 与 joint feedback 一致性; + - `abc-rotary-feedback`: + - ABC rotary axes target; + - rotary axis command/feedback; + - joint 3/4/5 feedback; +- 新增 `createVirtualHalMotionControllerMatrixReport()`: + - 执行每个 motion matrix fixture; + - 读取 virtual HAL WASM bridge snapshot; + - 验证 required motion/axis/joint pins; + - 验证 `distanceToGo`; + - 验证 `inPosition`; + - 验证 axis command、joint feedback、HALUI feedback 一致; + - 输出 `webSimulationSatisfied`; + - 输出 LinuxCNC motion source files; +- `createVirtualHalSourceComplianceReport()` 新增内嵌: + - `motionControllerMatrix`; + - source compliance complete 现在要求 motion matrix complete; +- `createVirtualHalSimulationReplacementReport()` 新增内嵌: + - `motionControllerMatrix`; + - replacement ready 现在要求 motion matrix complete; +- `createLinuxCncVirtualHalRuntime()` 新增: + - `getMotionControllerMatrixReport()`; +- SDK index、SDK README、browser simulation re-export 同步新增: + - `VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES`; + - `createVirtualHalMotionControllerMatrixReport()`; +- Browser simulation API 新增: + - `getVirtualHalMotionControllerMatrixReport()`; +- `exportDiagnosticsArtifact()` 新增: + - `virtualHalMotionControllerMatrix`; +- `createProjectReleaseBrowserDiagnosticsArtifactValidation()` 新增 release 级校验: + - diagnostics artifact 必须包含 complete motion controller matrix; + - required pins 必须包含 `motion.distance-to-go` 与 `motion.in-position`; + - source evidence 必须包含 `linuxcnc/src/emc/motion/motion.c` 与 + `linuxcnc/src/emc/motion/axis.c`。 + +新增/扩展验证: + +- `verify_sdk_surface.mjs` 覆盖: + - SDK index/README 导出新常量与新 report API; + - motion matrix report `complete === true`; + - motion matrix report `webSimulationSatisfied === true`; + - missing fixtures 为 0; + - source evidence 包含 LinuxCNC motion/axis source; + - required pins 包含 `motion.distance-to-go` 与 `motion.in-position`; + - `xyz-multi-axis-in-position` fixture 达到 `inPosition === true`; + - 每个 axis/joint/HALUI feedback row 均 matched; + - runtime wrapper 可读取 motion matrix report; + - replacement/source compliance report 内嵌 motion matrix; + - browser diagnostics artifact validation 要求 motion matrix ready; +- `real_simulation_page_smoke.html` 覆盖: + - 页面 API 暴露 `getVirtualHalMotionControllerMatrixReport()`; + - diagnostics artifact 中 `virtualHalMotionControllerMatrix.complete === true`; + - diagnostics artifact 中 source evidence 包含 LinuxCNC motion source; + - browser API matrix report 包含 motion pins、source evidence 和多轴 in-position fixture; + - replacement/source compliance report 内嵌 motion matrix。 + +关键验证: + +```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 +project_release_readiness_artifact_node_smoke=ok +git diff --check ok +``` + +语义边界: + +- 本批没有新增 G-code/interpreter/planner/kinematics 语义; +- motion matrix 只验证 Web virtual HAL motion controller 的仿真替代状态与 HAL pin + feedback,不把 browser virtual HAL 声称为 Linux kernel hard-realtime ABI; +- `stepVirtualHalMotionController()` 仍是 Web 仿真范围内的 deterministic servo-period + stepping,不替代外部硬件驱动 ABI 或 native HAL module ABI; +- G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed WASM。 + +下一步工作内容: + +1. 将 virtual HAL session payload 接入 real simulation page 的显式保存/恢复按钮或 API, + 让用户可直接保存和恢复完整 Web CNC 仿真会话。 +2. 将 browser diagnostics artifact 的 URL workflow 接入 ini-panel workflow overview 页面, + 让 release readiness 页面能显示 browser diagnostics validation rows。 +3. 扩展 motion matrix 与 LinuxCNC sim configs 的关联,把 external-offsets、QtDragon + multi-joint、vismach remap sims 的 motion source evidence 显式映射到 matrix fixture rows。 diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index d3f14de..bf70d45 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -68,6 +68,7 @@ import { createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, + VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, @@ -83,6 +84,7 @@ import { createLinuxCncInterpSdk, createLinuxCncVirtualHalRuntime, createVirtualHalCommandScriptFixtureReport, + createVirtualHalMotionControllerMatrixReport, createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, @@ -303,6 +305,8 @@ private page state. `VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES`, `executeVirtualHalCommand()`, `executeVirtualHalcmd()`, `stepVirtualHalMotion()`, `stepVirtualHalMotionController()`, + `VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES`, + `createVirtualHalMotionControllerMatrixReport()`, `createVirtualHalSimulationRuntimeReport()`, and `createVirtualHalSimulationReplacementReport()` expose a simulation-grade replacement for the host LinuxCNC realtime HAL, `halcmd`, and motion diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index 2c804a5..72b91a9 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -5,6 +5,7 @@ export { VIRTUAL_HAL_AXISUI_PINS, VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, + VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, @@ -19,6 +20,7 @@ export { cloneVirtualHalState, createLinuxCncVirtualHalRuntime, createVirtualHalCommandScriptFixtureReport, + createVirtualHalMotionControllerMatrixReport, createVirtualHalBridgeActionPlan, createVirtualHalBridgeReadiness, createVirtualHalDroState, diff --git a/wasm-port/runtime/sdk/src/linuxcnc-hal.js b/wasm-port/runtime/sdk/src/linuxcnc-hal.js index 3bf8f70..a3c4481 100644 --- a/wasm-port/runtime/sdk/src/linuxcnc-hal.js +++ b/wasm-port/runtime/sdk/src/linuxcnc-hal.js @@ -476,6 +476,97 @@ export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([ }), ]); +export const VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES = Object.freeze([ + Object.freeze({ + id: "x-single-axis-servo-dtg", + label: "Single-axis servo period target stepping", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + initialPosition: Object.freeze({ x: 0, y: 0, z: 0 }), + target: Object.freeze({ x: 1 }), + maxVelocity: 10, + dt: 0.05, + cycleCount: 1, + requiredPins: Object.freeze([ + "motion.distance-to-go", + "motion.in-position", + "axis.x.pos-cmd", + "joint.0.pos-fb", + ]), + expected: Object.freeze({ + moving: true, + inPosition: false, + distanceToGo: 0.5, + axisFeedback: Object.freeze({ x: 0.5 }), + }), + }), + Object.freeze({ + id: "xyz-multi-axis-in-position", + label: "Multi-axis target reaches in-position", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.motionHoming, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + initialPosition: Object.freeze({ x: 0, y: 0, z: 0 }), + target: Object.freeze({ x: 1, y: -0.5, z: 0.25 }), + maxVelocity: 10, + dt: 0.1, + cycleCount: 4, + requiredPins: Object.freeze([ + "motion.distance-to-go", + "motion.in-position", + "axis.x.pos-cmd", + "axis.y.pos-cmd", + "axis.z.pos-cmd", + "halui.axis.x.pos-feedback", + "joint.0.pos-fb", + "joint.1.pos-fb", + "joint.2.pos-fb", + ]), + expected: Object.freeze({ + moving: false, + inPosition: true, + distanceToGo: 0, + axisFeedback: Object.freeze({ x: 1, y: -0.5, z: 0.25 }), + }), + }), + Object.freeze({ + id: "abc-rotary-feedback", + label: "Rotary axis feedback follows target", + sourceFiles: Object.freeze([ + VIRTUAL_HAL_SOURCE_FILES.motion, + VIRTUAL_HAL_SOURCE_FILES.motionAxis, + VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader, + ]), + initialPosition: Object.freeze({ a: 0, b: 0, c: 0 }), + target: Object.freeze({ a: 0.4, b: -0.2, c: 0.1 }), + maxVelocity: 8, + dt: 0.1, + cycleCount: 2, + requiredPins: Object.freeze([ + "motion.current-vel", + "motion.requested-vel", + "axis.a.pos-cmd", + "axis.b.pos-cmd", + "axis.c.pos-cmd", + "joint.3.pos-fb", + "joint.4.pos-fb", + "joint.5.pos-fb", + ]), + expected: Object.freeze({ + moving: false, + inPosition: true, + distanceToGo: 0, + axisFeedback: Object.freeze({ a: 0.4, b: -0.2, c: 0.1 }), + }), + }), +]); + export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({ "realtime-hal-simulation-replacement": Object.freeze({ label: "LinuxCNC realtime HAL simulation replacement", @@ -2087,12 +2178,126 @@ export function createVirtualHalCommandScriptFixtureReport(options = {}) { }; } +function readVirtualHalSnapshotValue(snapshot, name) { + return snapshot.values.find((entry) => entry.name === name)?.value; +} + +function valuesClose(actual, expected, tolerance = 0.000001) { + return Math.abs(Number(actual) - Number(expected)) <= tolerance; +} + +export function createVirtualHalMotionControllerMatrixReport(options = {}) { + const fixtures = options.motionFixtures ?? options.fixtures ?? VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES; + const tolerance = clampNumber(options.tolerance ?? 0.000001, 0, 1); + const rows = fixtures.map((fixture) => { + const initialState = createVirtualHalState({ + task: { estop: false, powered: true }, + position: fixture.initialPosition ?? {}, + motion: { + requestedVel: fixture.maxVelocity, + target: fixture.initialPosition ?? {}, + servoPeriod: fixture.dt, + }, + }); + const step = stepVirtualHalMotionController(initialState, { + target: fixture.target, + maxVelocity: fixture.maxVelocity, + dt: fixture.dt, + cycleCount: fixture.cycleCount, + stopOnInPosition: fixture.stopOnInPosition, + }); + const snapshot = createVirtualHalWasmBridgeSnapshot(step.state); + const missingPins = (fixture.requiredPins ?? []) + .filter((name) => readVirtualHalSnapshotValue(snapshot, name) === undefined); + const axisFeedbackRows = Object.entries(fixture.expected?.axisFeedback ?? {}).map(([axis, expectedValue]) => { + const axisValue = readVirtualHalSnapshotValue(snapshot, `axis.${axis}.pos-cmd`); + const jointIndex = VIRTUAL_HAL_AXES.indexOf(axis); + const jointValue = jointIndex >= 0 + ? readVirtualHalSnapshotValue(snapshot, `joint.${jointIndex}.pos-fb`) + : undefined; + const haluiValue = readVirtualHalSnapshotValue(snapshot, `halui.axis.${axis}.pos-feedback`); + return { + axis, + expected: expectedValue, + axisPosition: axisValue, + jointFeedback: jointValue, + haluiFeedback: haluiValue, + matched: valuesClose(axisValue, expectedValue, tolerance) && + valuesClose(jointValue, expectedValue, tolerance) && + valuesClose(haluiValue, expectedValue, tolerance), + }; + }); + const expectedInPosition = fixture.expected?.inPosition; + const expectedDistanceToGo = fixture.expected?.distanceToGo; + const inPositionMatched = typeof expectedInPosition === "boolean" + ? step.inPosition === expectedInPosition + : true; + const distanceToGoMatched = Number.isFinite(Number(expectedDistanceToGo)) + ? valuesClose(step.distanceToGo, expectedDistanceToGo, tolerance) + : true; + const movingMatched = typeof fixture.expected?.moving === "boolean" + ? (step.frames.some((frame) => frame.motion.motionType === 1) === fixture.expected.moving) + : true; + const complete = step.ok === true && + missingPins.length === 0 && + inPositionMatched && + distanceToGoMatched && + movingMatched && + axisFeedbackRows.every((row) => row.matched); + return { + id: fixture.id, + label: fixture.label, + source: "linuxcnc-motion-source-derived-virtual-motion-controller", + sourceFiles: [...(fixture.sourceFiles ?? [])], + complete, + target: { ...(fixture.target ?? {}) }, + initialPosition: { ...(fixture.initialPosition ?? {}) }, + maxVelocity: fixture.maxVelocity, + dt: fixture.dt, + cycleCount: step.cycleCount, + inPosition: step.inPosition, + distanceToGo: step.distanceToGo, + currentVel: step.state.motion.currentVel, + requestedVel: step.state.motion.requestedVel, + servoPeriod: step.state.motion.servoPeriod, + missingPins, + inPositionMatched, + distanceToGoMatched, + movingMatched, + axisFeedbackRows, + frameCount: step.frames.length, + }; + }); + const missingFixtures = rows.filter((row) => !row.complete).map((row) => row.id); + const complete = rows.length > 0 && missingFixtures.length === 0; + return { + apiName: "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report", + matrixVersion: 1, + source: "linuxcnc-motion-source-derived-virtual-hal", + phase: complete ? "ready" : "blocked", + complete, + webSimulationSatisfied: complete, + sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))], + requiredPins: [...new Set(fixtures.flatMap((fixture) => fixture.requiredPins ?? []))], + fixtureCount: rows.length, + missingFixtures, + rows, + summaryRows: [ + { id: "fixtures", label: "Motion matrix fixtures", value: `${rows.length}` }, + { id: "source", label: "LinuxCNC motion source", value: complete ? "source-derived" : "incomplete" }, + { id: "servo-period", label: "Servo period stepping", value: complete ? "covered" : "blocked" }, + { id: "axis-joint-feedback", label: "Axis/joint feedback", value: complete ? "covered" : "blocked" }, + ], + }; +} + export function createVirtualHalSimulationReplacementReport(halState = createVirtualHalState(), options = {}) { const state = normalizeVirtualHalState(halState); const runtime = createVirtualHalSimulationRuntimeReport(state, options); const sourceCompliance = createVirtualHalSourceComplianceReport({ halState: state }); const simConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(options); const commandScriptFixtures = createVirtualHalCommandScriptFixtureReport({ ...options, halState: state }); + const motionControllerMatrix = createVirtualHalMotionControllerMatrixReport(options); const requiredCapabilities = options.requiredCapabilities ?? [ "realtime-hal-simulation-replacement", "halcmd-simulation-replacement", @@ -2122,7 +2327,8 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir }, }; const ready = missingCapabilities.length === 0 && - Object.values(replacements).every((item) => item.ready === true); + Object.values(replacements).every((item) => item.ready === true) && + motionControllerMatrix.complete === true; return { apiName: "linuxcnc-wasm-virtual-hal-simulation-replacement-report", replacementVersion: 1, @@ -2139,6 +2345,7 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir sourceCompliance, simConfigSourceCoverage, commandScriptFixtures, + motionControllerMatrix, runtime, rows: [ { id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" }, @@ -2252,10 +2459,12 @@ export function createVirtualHalSourceComplianceReport(options = {}) { }); const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options); const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport(options); + const motionControllerMatrix = options.motionControllerMatrix ?? createVirtualHalMotionControllerMatrixReport(options); const allowedSourceFiles = new Set([ ...Object.values(VIRTUAL_HAL_SOURCE_FILES), ...VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles), ...VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.flatMap((fixture) => fixture.sourceFiles), + ...VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES.flatMap((fixture) => fixture.sourceFiles), ...(options.allowedSourceFiles ?? []), ]); const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES; @@ -2298,6 +2507,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) { missingFamilySources.length === 0 && simConfigSourceCoverage.complete === true && commandScriptFixtures.complete === true && + motionControllerMatrix.complete === true && systemCoverage.complete === true && targetRows.every((row) => row.sourceDerived); return { @@ -2315,11 +2525,13 @@ export function createVirtualHalSourceComplianceReport(options = {}) { ...capabilityRows.flatMap((row) => row.sourceFiles), ...simConfigSourceCoverage.sourceFiles, ...commandScriptFixtures.sourceFiles, + ...motionControllerMatrix.sourceFiles, ])], missingCapabilitySources, missingFamilySources, simConfigSourceCoverage, commandScriptFixtures, + motionControllerMatrix, targetRows, capabilityRows, familyRows: systemCoverage.families.map((family) => ({ @@ -2336,6 +2548,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) { { id: "pin-families", label: "HAL pin families", value: missingFamilySources.length === 0 ? "source-derived" : `missing ${missingFamilySources.length}` }, { id: "sim-configs", label: "LinuxCNC sim configs", value: simConfigSourceCoverage.complete ? "source-derived" : "incomplete" }, { id: "halcmd-fixtures", label: "HAL command script fixtures", value: commandScriptFixtures.complete ? "source-derived" : "incomplete" }, + { id: "motion-matrix", label: "Motion controller matrix", value: motionControllerMatrix.complete ? "source-derived" : "incomplete" }, { id: "web-simulation", label: "Web simulation HAL", value: complete ? "satisfied" : "blocked" }, ], }; @@ -2452,6 +2665,9 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS getCommandScriptFixtureReport(options = {}) { return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state }); }, + getMotionControllerMatrixReport(options = {}) { + return createVirtualHalMotionControllerMatrixReport(options); + }, getIntegrityReport(options = {}) { return createVirtualHalIntegrityReport({ ...options, halState: state }); }, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index 3644b92..69e8f27 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -104,6 +104,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance); const simConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage); const commandScriptFixtures = objectOrEmpty(artifactObject.virtualHalCommandScriptFixtures); + const motionControllerMatrix = objectOrEmpty(artifactObject.virtualHalMotionControllerMatrix); const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement); const virtualHal = objectOrEmpty(artifactObject.virtualHal); const sourceComplianceReady = sourceCompliance.complete === true && @@ -115,9 +116,16 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac arrayOrEmpty(commandScriptFixtures.coveredActions).includes("loadusr") && arrayOrEmpty(commandScriptFixtures.coveredActions).includes("stop") && arrayOrEmpty(commandScriptFixtures.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc"); + const motionControllerMatrixReady = motionControllerMatrix.complete === true && + motionControllerMatrix.webSimulationSatisfied === true && + arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.distance-to-go") && + 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/axis.c"); const replacementReady = replacement.ready === true && replacement.replacesHostRuntimeForSimulation === true && - objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true; + objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true && + objectOrEmpty(replacement.motionControllerMatrix).complete === true; const virtualHalReady = virtualHal.source === "browser-virtual-hal"; const missing = [ ...(artifactObject.apiName === "real-browser-simulation-diagnostics-artifact" ? [] : ["apiName"]), @@ -126,6 +134,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac ...(sourceComplianceReady ? [] : ["virtualHalSourceCompliance"]), ...(simConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]), ...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]), + ...(motionControllerMatrixReady ? [] : ["virtualHalMotionControllerMatrix"]), ]; return { apiName: "project-release-browser-diagnostics-artifact-validation", @@ -139,10 +148,12 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac sourceComplianceReady, simConfigSourceCoverageReady, commandScriptFixturesReady, + motionControllerMatrixReady, sourceFiles: [...new Set([ ...arrayOrEmpty(sourceCompliance.sourceFiles), ...arrayOrEmpty(simConfigSourceCoverage.sourceFiles), ...arrayOrEmpty(commandScriptFixtures.sourceFiles), + ...arrayOrEmpty(motionControllerMatrix.sourceFiles), ])], rows: [ { @@ -170,6 +181,11 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac label: "Virtual HAL command script fixtures", value: commandScriptFixturesReady ? "ready" : "missing", }, + { + id: "motion-controller-matrix", + label: "Virtual HAL motion controller matrix", + value: motionControllerMatrixReady ? "ready" : "missing", + }, { id: "missing", label: "Missing diagnostics evidence", diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html index 42b087f..4977f6e 100644 --- a/wasm-port/runtime/ui/simulation/index.html +++ b/wasm-port/runtime/ui/simulation/index.html @@ -1802,6 +1802,7 @@ M2 createVirtualHalIntegrityReport as createSharedVirtualHalIntegrityReport, createVirtualHalLimitsHomeState as createSharedVirtualHalLimitsHomeState, createVirtualHalMachineStatusState as createSharedVirtualHalMachineStatusState, + createVirtualHalMotionControllerMatrixReport as createSharedVirtualHalMotionControllerMatrixReport, createVirtualHalPinRegistry as createSharedVirtualHalPinRegistry, createVirtualHalSimConfigSourceCoverageReport as createSharedVirtualHalSimConfigSourceCoverageReport, createVirtualHalSimulationReplacementReport as createSharedVirtualHalSimulationReplacementReport, @@ -2496,6 +2497,10 @@ M2 return createSharedVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }); } + function createVirtualHalMotionControllerMatrixReport() { + return createSharedVirtualHalMotionControllerMatrixReport(); + } + function renderMachineStatusDom(machineStatus) { const statusMap = { "spindle-state": machineStatus.spindle.state, @@ -2585,6 +2590,7 @@ M2 virtualHalSourceCompliance: createVirtualHalSourceComplianceReport(), virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(), virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(), + virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(), statusHistory: getStatusHistory(), mdiHistory: getMdiHistory(), recentPrograms: getRecentPrograms(), @@ -3890,6 +3896,7 @@ M2 getVirtualHalSourceComplianceReport: () => createVirtualHalSourceComplianceReport(), getVirtualHalSimConfigSourceCoverageReport: () => createVirtualHalSimConfigSourceCoverageReport(), getVirtualHalCommandScriptFixtureReport: () => createVirtualHalCommandScriptFixtureReport(), + getVirtualHalMotionControllerMatrixReport: () => createVirtualHalMotionControllerMatrixReport(), getVirtualRealtimeHalRuntimeReport: () => createVirtualHalSimulationReplacementReport(), getVirtualHalDroState: () => createVirtualHalDroState(), getVirtualHalLimitsHomeState: () => createVirtualHalLimitsHomeState(), diff --git a/wasm-port/runtime/ui/simulation/simulation-app.js b/wasm-port/runtime/ui/simulation/simulation-app.js index 065f677..bc6fede 100644 --- a/wasm-port/runtime/ui/simulation/simulation-app.js +++ b/wasm-port/runtime/ui/simulation/simulation-app.js @@ -18,6 +18,7 @@ export { export { VIRTUAL_HAL_AXES, VIRTUAL_HAL_AXISUI_PINS, + VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS, VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES, applyVirtualHalAction, @@ -25,6 +26,7 @@ export { cloneVirtualHalState, createLinuxCncVirtualHalRuntime, createVirtualHalCommandScriptFixtureReport, + createVirtualHalMotionControllerMatrixReport, createVirtualHalDroState, createVirtualHalIntegrityReport, createVirtualHalLimitsHomeState, diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index 61392f9..2dbfabb 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -244,6 +244,7 @@ !api?.getVirtualHalSourceComplianceReport || !api?.getVirtualHalSimConfigSourceCoverageReport || !api?.getVirtualHalCommandScriptFixtureReport || + !api?.getVirtualHalMotionControllerMatrixReport || !api?.getVirtualRealtimeHalRuntimeReport ) { throw new Error("simulation API missing browser virtual HAL controls"); @@ -409,6 +410,8 @@ !diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") || diagnosticsArtifact.virtualHalCommandScriptFixtures?.complete !== true || !diagnosticsArtifact.virtualHalCommandScriptFixtures?.coveredActions?.includes("loadusr") || + diagnosticsArtifact.virtualHalMotionControllerMatrix?.complete !== true || + !diagnosticsArtifact.virtualHalMotionControllerMatrix?.sourceFiles?.includes("linuxcnc/src/emc/motion/motion.c") || diagnosticsArtifact.limitsHome.source !== "browser-virtual-hal" || diagnosticsArtifact.preview.renderer !== "threejs" || diagnosticsArtifact.toolTable.state !== "not-loaded" @@ -746,6 +749,18 @@ ) { throw new Error(`AXIS-style virtual motion controller did not step: ${JSON.stringify(motionControllerStep)}`); } + const motionControllerMatrix = api.getVirtualHalMotionControllerMatrixReport(); + if ( + motionControllerMatrix.apiName !== "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report" || + motionControllerMatrix.complete !== true || + motionControllerMatrix.webSimulationSatisfied !== true || + !motionControllerMatrix.requiredPins.includes("motion.distance-to-go") || + !motionControllerMatrix.requiredPins.includes("motion.in-position") || + !motionControllerMatrix.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c") || + !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)}`); + } const virtualHalReplacement = api.getVirtualHalSimulationReplacementReport(); if ( virtualHalReplacement.apiName !== "linuxcnc-wasm-virtual-hal-simulation-replacement-report" || @@ -757,8 +772,10 @@ virtualHalReplacement.sourceCompliance?.webSimulationSatisfied !== true || virtualHalReplacement.simConfigSourceCoverage?.complete !== true || virtualHalReplacement.commandScriptFixtures?.complete !== true || + virtualHalReplacement.motionControllerMatrix?.complete !== true || api.getVirtualHalSourceComplianceReport().complete !== true || api.getVirtualHalSourceComplianceReport().commandScriptFixtures?.complete !== true || + api.getVirtualHalSourceComplianceReport().motionControllerMatrix?.complete !== true || !api.getVirtualHalSourceComplianceReport().sourceFiles.includes("linuxcnc/src/emc/motion/motion.c") || api.getVirtualHalSimConfigSourceCoverageReport().complete !== true || !api.getVirtualHalSimConfigSourceCoverageReport().sourceFiles.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc") || diff --git a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs index 515d688..c39f7ef 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.mjs @@ -8,6 +8,7 @@ import { createProjectReleaseReadinessReport, createVirtualHalSimConfigSourceCoverageReport, createVirtualHalCommandScriptFixtureReport, + createVirtualHalMotionControllerMatrixReport, createVirtualHalSimulationReplacementReport, createVirtualHalSourceComplianceReport, createVirtualHalState, @@ -25,6 +26,7 @@ const browserDiagnosticsArtifact = { virtualHalSourceCompliance: createVirtualHalSourceComplianceReport({ halState: virtualHalState }), virtualHalSimConfigSourceCoverage, virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }), + virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(), }; assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation(browserDiagnosticsArtifact).ready, true); const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({ diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 9416503..4f8b1b1 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -34,6 +34,7 @@ import { createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel, VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES, VIRTUAL_HAL_COVERAGE_STATES, + VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES, VIRTUAL_HAL_PROJECT_PIN_GROUPS, VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS, VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES, @@ -50,6 +51,7 @@ import { createLinuxCncInterpSdk, createLinuxCncVirtualHalRuntime, createVirtualHalIntegrityReport, + createVirtualHalMotionControllerMatrixReport, createMachineSessionSnapshotPayload, createVirtualHalSessionPayload, createMachineSessionPersistenceDisplayViewModel, @@ -233,6 +235,7 @@ const requiredExports = [ ["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness], ["createVirtualHalCommandScriptFixtureReport", createVirtualHalCommandScriptFixtureReport], ["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport], + ["createVirtualHalMotionControllerMatrixReport", createVirtualHalMotionControllerMatrixReport], ["createVirtualHalPinInventory", createVirtualHalPinInventory], ["createVirtualHalPinRegistry", createVirtualHalPinRegistry], ["createVirtualHalProjectReport", createVirtualHalProjectReport], @@ -501,6 +504,8 @@ assert.match(sdkIndexText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES\b/); +assert.match(sdkIndexText, /\bVIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES\b/); +assert.match(sdkReadmeText, /\bVIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/); assert.match(sdkReadmeText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/); assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS\b/); @@ -519,6 +524,8 @@ assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/); assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/); assert.match(sdkIndexText, /\bcreateVirtualHalCommandScriptFixtureReport\b/); assert.match(sdkReadmeText, /\bcreateVirtualHalCommandScriptFixtureReport\b/); +assert.match(sdkIndexText, /\bcreateVirtualHalMotionControllerMatrixReport\b/); +assert.match(sdkReadmeText, /\bcreateVirtualHalMotionControllerMatrixReport\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/); assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/); assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/); @@ -644,6 +651,19 @@ for (const action of ["setp", "sets", "net", "show", "getp", "gets", "loadrt", " assert.equal(commandScriptFixtureReport.coveredActions.includes(action), true); } assert.equal(virtualHalRuntime.getCommandScriptFixtureReport().complete, true); +assert.equal(VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES.length >= 3, true); +const motionControllerMatrixReport = createVirtualHalMotionControllerMatrixReport(); +assert.equal(motionControllerMatrixReport.apiName, "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report"); +assert.equal(motionControllerMatrixReport.complete, true); +assert.equal(motionControllerMatrixReport.webSimulationSatisfied, true); +assert.equal(motionControllerMatrixReport.missingFixtures.length, 0); +assert.equal(motionControllerMatrixReport.sourceFiles.includes("linuxcnc/src/emc/motion/motion.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.in-position"), true); +assert.equal(motionControllerMatrixReport.rows.some(({ id, inPosition }) => id === "xyz-multi-axis-in-position" && inPosition === true), true); +assert.equal(motionControllerMatrixReport.rows.every(({ axisFeedbackRows }) => axisFeedbackRows.every(({ matched }) => matched)), true); +assert.equal(virtualHalRuntime.getMotionControllerMatrixReport().complete, true); const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), { target: { x: 3 }, maxVelocity: 10, @@ -688,6 +708,7 @@ assert.equal(replacementReport.sourceCompliance.complete, true); assert.equal(replacementReport.sourceCompliance.webSimulationSatisfied, true); assert.equal(replacementReport.simConfigSourceCoverage.complete, true); assert.equal(replacementReport.commandScriptFixtures.complete, true); +assert.equal(replacementReport.motionControllerMatrix.complete, true); assert.equal(virtualHalRuntime.getSimulationReplacementReport().ready, true); const simConfigSourceCoverageReport = createVirtualHalSimConfigSourceCoverageReport({ manifestText: sourceManifestText }); assert.equal(simConfigSourceCoverageReport.apiName, "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report"); @@ -715,6 +736,7 @@ assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/src/emc/motio assert.equal(sourceComplianceReport.sourceFiles.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini"), true); assert.equal(sourceComplianceReport.simConfigSourceCoverage.complete, true); assert.equal(sourceComplianceReport.commandScriptFixtures.complete, true); +assert.equal(sourceComplianceReport.motionControllerMatrix.complete, true); assert.equal(virtualHalRuntime.getSourceComplianceReport().complete, true); const virtualHalSessionPayload = createVirtualHalSessionPayload(virtualHalRuntime.getState(), { simConfigSourceCoverage: simConfigSourceCoverageReport, @@ -744,13 +766,16 @@ const browserDiagnosticsArtifactValidation = createProjectReleaseBrowserDiagnost virtualHalSourceCompliance: sourceComplianceReport, virtualHalSimConfigSourceCoverage: simConfigSourceCoverageReport, virtualHalCommandScriptFixtures: commandScriptFixtureReport, + virtualHalMotionControllerMatrix: motionControllerMatrixReport, }); assert.equal(browserDiagnosticsArtifactValidation.apiName, "project-release-browser-diagnostics-artifact-validation"); assert.equal(browserDiagnosticsArtifactValidation.ready, true); assert.equal(browserDiagnosticsArtifactValidation.sourceComplianceReady, true); assert.equal(browserDiagnosticsArtifactValidation.simConfigSourceCoverageReady, true); assert.equal(browserDiagnosticsArtifactValidation.commandScriptFixturesReady, 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/emc/motion/axis.c"), true); const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }); assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true); assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);