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

结论:real simulation page 已将保存的 virtual HAL session snapshot 联动到 release diagnostics artifact,保存或恢复后的 Web CNC 仿真会话可直接输出 release-ready virtual HAL source、sim-config、halcmd fixture 与 motion matrix evidence;该能力仍限于 Web 仿真替代层,不声明 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native HAL module ABI。
This commit is contained in:
2026-06-17 23:07:11 +08:00
parent 7449ff7f4e
commit 208992c42e
5 changed files with 154 additions and 2 deletions

View File

@@ -1113,3 +1113,80 @@ git diff --check ok
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
3. 为 motion matrix 增加 manifest-backed source check把 matrix 关联的 sim config
source files 与 vendored LinuxCNC manifest 做显式一致性验证。
### 十二、2026-06-17 Virtual HAL Session Diagnostics Artifact Linkage
继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL
完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 real
simulation page 保存的 virtual HAL session snapshot 与 release diagnostics artifact 做联动,
使保存后的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。
新增实质能力:
- real simulation page 新增 session diagnostics artifact builder
- `createVirtualHalSessionDiagnosticsArtifact(snapshot)`
- 从保存的 session snapshot 读取 `payload.virtualHal`
- 复用 snapshot 中的 virtual HAL state、simulation replacement、source compliance、
sim-config coverage、halcmd fixtures、motion controller matrix
- 使用 `createProjectReleaseBrowserDiagnosticsArtifactValidation()` 对 snapshot
派生 diagnostics artifact 做 release 级验证;
- `exportDiagnosticsArtifact()` 新增:
- `virtualHalSessionDiagnostics`
- 未保存 session 时返回 `phase: "not-saved"` 和 missing reason
- 已保存或恢复 session 后返回 `phase: "release-ready"`
- Browser simulation API 新增:
- `exportVirtualHalSessionDiagnosticsArtifact()`
- `saveVirtualHalSessionSnapshot()` 返回值新增:
- `releaseDiagnosticsReady`
- `sessionDiagnostics`
- `restoreVirtualHalSessionSnapshot()` 返回值新增:
- `releaseDiagnosticsReady`
- `sessionDiagnostics`
- AXIS simulation docs 同步公开
`exportVirtualHalSessionDiagnosticsArtifact()`,并说明 saved virtual HAL session
snapshot 会进入 browser diagnostics artifact形成 release-ready evidence。
新增/扩展验证:
- `real_simulation_page_smoke.html` 覆盖:
- 页面 API 暴露 `exportVirtualHalSessionDiagnosticsArtifact()`
- 保存 virtual HAL session 后 `releaseDiagnosticsReady === true`
- 保存返回的 `sessionDiagnostics.validation.ready === true`
- 保存返回的 session diagnostics 包含 complete motion controller matrix
- `exportVirtualHalSessionDiagnosticsArtifact()` 输出 `phase === "release-ready"`
- `exportDiagnosticsArtifact()` 内嵌 `virtualHalSessionDiagnostics.ready === true`
- restore 后 session diagnostics 仍保持 release-ready
- `verify_axis_style_simulation_docs.mjs` 覆盖新 API 文档契约;
- `verify_sdk_surface.sh` 继续覆盖 release diagnostics validation 与 virtual HAL source
evidence。
关键验证:
```text
axis_style_simulation_docs_node_smoke=ok
browser_real_simulation_page_smoke=ok
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
git diff --check ok
```
语义边界:
- 本批没有新增 G-code/interpreter/planner/kinematics/remap 语义;
- session diagnostics artifact 只复用保存时已有的 source-derived virtual HAL reports
- release validation 只验证 Web 仿真所需 HAL、halcmd、motion controller evidence
- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native
HAL module ABI
- G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed WASM。
下一步工作内容:
1. 将 ini-panel workflow overview 的 diagnostics URL workflow 接入固定页面表单/按钮,
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
2. 为 motion matrix 增加 manifest-backed source check把 matrix 关联的 sim config
source files 与 vendored LinuxCNC manifest 做显式一致性验证。
3. 将 virtual HAL session diagnostics artifact 纳入 release artifact URL workflow 的示例
fixtures使 URL workflow 同时覆盖 live diagnostics 与 saved-session diagnostics。

View File

@@ -373,7 +373,11 @@ Current implemented behavior:
state;
- browser API exposes `getDiagnosticsState()`, `getStatusHistory()`,
`getToolTableSummary()`, `getLimitsHomeState()`,
`exportDiagnosticsArtifact()`, and `handleAxisShortcut(event)`;
`exportDiagnosticsArtifact()`, `exportVirtualHalSessionDiagnosticsArtifact()`,
and `handleAxisShortcut(event)`;
- saved virtual HAL session snapshots are linked back into the browser
diagnostics artifact as release-ready virtual HAL source, sim-config,
halcmd-fixture, and motion-matrix evidence;
- browser smoke covers run and play/pause keyboard shortcuts after waiting for
their async UI/API side effects;
- browser smoke verifies blocked readiness before OPFS files exist, then writes
@@ -458,6 +462,7 @@ Required methods:
- `getToolTableSummary()`;
- `getLimitsHomeState()`;
- `exportDiagnosticsArtifact()`;
- `exportVirtualHalSessionDiagnosticsArtifact()`;
- `handleAxisShortcut(event)`;
- `applyAxisViewFromUrl(search)`;

View File

@@ -1800,6 +1800,7 @@ M2
import {
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
} from "../../sdk/src/index.js";
import {
DEFAULT_SIMULATION_PROGRAM_ID,
@@ -1921,6 +1922,7 @@ M2
status: "Virtual HAL session not saved",
revision: 0,
restored: false,
releaseDiagnosticsReady: false,
};
let machineReadinessState = {
machineId: "axis-browser-sim",
@@ -2613,6 +2615,7 @@ M2
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(),
virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(),
virtualHalSessionDiagnostics: createVirtualHalSessionDiagnosticsArtifact(),
statusHistory: getStatusHistory(),
mdiHistory: getMdiHistory(),
recentPrograms: getRecentPrograms(),
@@ -2627,6 +2630,44 @@ M2
};
}
function createVirtualHalSessionDiagnosticsArtifact(snapshot = virtualHalSessionState.snapshot ?? null) {
if (!snapshot?.payload?.virtualHal) {
return {
apiName: "real-browser-simulation-virtual-hal-session-diagnostics",
artifactVersion: 1,
ready: false,
phase: "not-saved",
missing: ["virtualHalSessionSnapshot"],
};
}
const virtualHal = snapshot.payload.virtualHal;
const diagnosticsArtifact = {
apiName: "real-browser-simulation-diagnostics-artifact",
artifactVersion: 1,
virtualHal: virtualHal.state,
virtualHalSimulationReplacement: virtualHal.diagnostics,
virtualHalSourceCompliance: virtualHal.sourceCompliance,
virtualHalSimConfigSourceCoverage: virtualHal.simConfigSourceCoverage,
virtualHalCommandScriptFixtures: virtualHal.commandScriptFixtures,
virtualHalMotionControllerMatrix: virtualHal.motionControllerMatrix,
};
const validation = createProjectReleaseBrowserDiagnosticsArtifactValidation(diagnosticsArtifact);
return {
apiName: "real-browser-simulation-virtual-hal-session-diagnostics",
artifactVersion: 1,
ready: validation.ready === true,
phase: validation.ready === true ? "release-ready" : "blocked",
sessionId: snapshot.sessionId,
machineId: snapshot.payload.machineId,
createdAt: snapshot.createdAt,
metadata: { ...(snapshot.metadata ?? {}) },
files: { ...(snapshot.payload.files ?? {}) },
virtualHalRevision: virtualHal.state.revision,
validation,
diagnosticsArtifact,
};
}
function recordError(error, context = "runtime") {
const message = `${context}: ${error?.message ?? error}`;
renderDiagnostics({ lastError: message });
@@ -3549,6 +3590,7 @@ M2
virtualHalRevision: virtualHal.state.revision,
},
});
const sessionDiagnostics = createVirtualHalSessionDiagnosticsArtifact(snapshot);
pushStatusHistory("hal-session", `saved ${session.sessionId}/${session.filename}`);
return setVirtualHalSessionState({
ready: true,
@@ -3559,6 +3601,8 @@ M2
status: `Saved HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
revision: virtualHal.state.revision,
restored: false,
releaseDiagnosticsReady: sessionDiagnostics.ready === true,
sessionDiagnostics,
snapshot,
});
}
@@ -3571,6 +3615,7 @@ M2
virtualHalState = restoreVirtualHalStateFromSessionSnapshot(snapshot);
renderVirtualHalState();
renderAxisStatusbar();
const sessionDiagnostics = createVirtualHalSessionDiagnosticsArtifact(snapshot);
pushStatusHistory("hal-session", `restored ${session.sessionId}/${session.filename}`);
return setVirtualHalSessionState({
ready: true,
@@ -3581,6 +3626,8 @@ M2
status: `Restored HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
revision: virtualHalState.revision,
restored: true,
releaseDiagnosticsReady: sessionDiagnostics.ready === true,
sessionDiagnostics,
snapshot,
});
}
@@ -3975,6 +4022,7 @@ M2
getVirtualHalSessionState: () => ({ ...virtualHalSessionState }),
saveVirtualHalSessionSnapshot,
restoreVirtualHalSessionSnapshot,
exportVirtualHalSessionDiagnosticsArtifact: () => createVirtualHalSessionDiagnosticsArtifact(),
getRunMode: () => syncRunMode(),
getRunControlState: () => renderRunControlState(),
getAxisStatusbarState: () => renderAxisStatusbar(),

View File

@@ -252,7 +252,7 @@
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
throw new Error("simulation API missing AXIS machine/session load controls");
}
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot) {
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot || !api?.exportVirtualHalSessionDiagnosticsArtifact) {
throw new Error("simulation API missing virtual HAL session persistence controls");
}
if (!api?.getRunMode || !api?.getRunControlState || !api?.setUseLoadedSession || !api?.getRunSummary) {
@@ -747,11 +747,29 @@
savedVirtualHalSession.revision < virtualHalcmdResult.state.revision ||
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.sourceCompliance?.complete ||
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.motionControllerMatrix?.complete ||
savedVirtualHalSession.releaseDiagnosticsReady !== true ||
savedVirtualHalSession.sessionDiagnostics?.ready !== true ||
savedVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
savedVirtualHalSession.sessionDiagnostics?.validation?.motionControllerMatrixReady !== true ||
savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMotionControllerMatrix?.complete !== true ||
doc.body.dataset.virtualHalSessionPhase !== "saved" ||
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Saved HAL session")
) {
throw new Error(`AXIS-style virtual HAL session save failed: ${JSON.stringify(savedVirtualHalSession)}`);
}
const savedSessionDiagnosticsArtifact = api.exportVirtualHalSessionDiagnosticsArtifact();
const diagnosticsWithSavedSession = api.exportDiagnosticsArtifact();
if (
savedSessionDiagnosticsArtifact.ready !== true ||
savedSessionDiagnosticsArtifact.phase !== "release-ready" ||
savedSessionDiagnosticsArtifact.sessionId !== "axis-browser-virtual-hal-session" ||
savedSessionDiagnosticsArtifact.validation?.ready !== true ||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.ready !== true ||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.validation?.ready !== true ||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalSourceCompliance?.complete !== true
) {
throw new Error(`AXIS-style virtual HAL session diagnostics did not become release-ready: ${JSON.stringify(diagnosticsWithSavedSession.virtualHalSessionDiagnostics)}`);
}
api.executeVirtualHalcmd("setp axis.x.pos-cmd 9.5\nloadusr post-save-component");
if (api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 9.5) {
throw new Error("AXIS-style virtual HAL pre-restore mutation did not apply");
@@ -766,6 +784,9 @@
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75 ||
api.getVirtualHalState().hal.loadedComponents.some(({ component }) => component === "post-save-component") ||
!api.getVirtualHalState().hal.functions.some(({ name }) => name === "motion-controller") ||
restoredVirtualHalSession.releaseDiagnosticsReady !== true ||
restoredVirtualHalSession.sessionDiagnostics?.ready !== true ||
restoredVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
doc.body.dataset.virtualHalSessionPhase !== "restored" ||
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Restored HAL session")
) {

View File

@@ -22,6 +22,7 @@ for (const required of [
"getRecentPrograms()",
"Three.js `^0.183.2`",
"exportDiagnosticsArtifact()",
"exportVirtualHalSessionDiagnosticsArtifact()",
"getLimitsHomeState()",
"getAxisStatusbarState()",
"applyAxisViewFromUrl(search)",