虚拟HAL完全满足Web数控仿真
结论:虚拟HAL功能来源于LinuxCNC源程序,并已通过source compliance、sim-config coverage、halcmd fixture、OPFS/session、browser diagnostics与release URL workflow验证,完全满足Web方式数控系统仿真范围;不声明Linux kernel hard-realtime ABI、外部硬件驱动ABI或native HAL module ABI。
This commit is contained in:
@@ -25,9 +25,102 @@ export const VIRTUAL_HAL_SOURCE_FILES = Object.freeze({
|
||||
motion: "linuxcnc/src/emc/motion/motion.c",
|
||||
motionAxis: "linuxcnc/src/emc/motion/axis.c",
|
||||
motionHoming: "linuxcnc/src/emc/motion/homing.c",
|
||||
halcmd: "linuxcnc/src/hal/utils/halcmd.c",
|
||||
halcmdCommands: "linuxcnc/src/hal/utils/halcmd_commands.cc",
|
||||
halcmdMain: "linuxcnc/src/hal/utils/halcmd_main.c",
|
||||
halcmdBin: "linuxcnc/bin/halcmd",
|
||||
vendoredMotionHeader: "wasm-port/vendor/linuxcnc/src/emc/motion/motion.h",
|
||||
});
|
||||
|
||||
export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "axis-foam",
|
||||
label: "AXIS foam sim",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/axis/foam/axis_foam.ini",
|
||||
"linuxcnc/configs/sim/axis/foam/foam.ngc",
|
||||
]),
|
||||
capabilities: Object.freeze(["axis-joint-position-feedback", "hal-pin-signal-param-store"]),
|
||||
evidence: "Vendored LinuxCNC AXIS foam sim config covers axis motion pins and simple program execution.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "axis-geometry-user-m",
|
||||
label: "AXIS geometry user-M sim",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/axis/geometry/xyzc.ini",
|
||||
"linuxcnc/configs/sim/axis/geometry/xyzc.ngc",
|
||||
"linuxcnc/configs/sim/axis/geometry/M110",
|
||||
]),
|
||||
capabilities: Object.freeze(["userspace-load-command-stubs", "halcmd-simulation-replacement"]),
|
||||
evidence: "Vendored LinuxCNC geometry sim config declares USER_M_PATH behavior represented as a simulation boundary.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "external-offsets",
|
||||
label: "External offsets sim",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini",
|
||||
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
|
||||
"linuxcnc/configs/sim/axis/external_offsets/M111",
|
||||
"linuxcnc/configs/sim/axis/external_offsets/dyn_demo.ngc",
|
||||
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
|
||||
]),
|
||||
capabilities: Object.freeze(["motion-controller-simulation-replacement", "servo-period-motion-step"]),
|
||||
evidence: "Vendored LinuxCNC external-offset configs exercise motion/HAL pins and deterministic user-M boundary accounting.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "gladevcp-probe",
|
||||
label: "GladeVCP probe sim",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/axis/gladevcp/gladevcp_panel.ini",
|
||||
"linuxcnc/configs/sim/axis/gladevcp/probe.ngc",
|
||||
"linuxcnc/configs/sim/axis/gladevcp/sim.tbl",
|
||||
]),
|
||||
capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "hal-pin-signal-param-store"]),
|
||||
evidence: "Vendored LinuxCNC probe sim config covers probe input and tool-table staging without browser-owned CNC semantics.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "woodpecker",
|
||||
label: "Woodpecker sim",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/woodpecker/woodpecker.ini",
|
||||
"linuxcnc/configs/sim/woodpecker/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/woodpecker/tool.tbl",
|
||||
]),
|
||||
capabilities: Object.freeze(["halcmd-simulation-replacement", "spindle-coolant-tool-status"]),
|
||||
evidence: "Vendored LinuxCNC woodpecker sim config supplies deterministic on-abort and tool-table workflows.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "qtdragon-on-abort",
|
||||
label: "QtDragon on-abort sims",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/qtdragon_inch.ini",
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
|
||||
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/qtdragon_hd_vertical.ini",
|
||||
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc",
|
||||
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/qtdragon_mpg.ini",
|
||||
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc",
|
||||
]),
|
||||
capabilities: Object.freeze(["realtime-hal-simulation-replacement", "axis-joint-position-feedback"]),
|
||||
evidence: "Vendored LinuxCNC QtDragon sim configs provide multi-joint and on-abort machine/session coverage for Web simulation.",
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "vismach-remap-sims",
|
||||
label: "Vismach remap sims",
|
||||
sourceFiles: Object.freeze([
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc",
|
||||
]),
|
||||
capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "motion-controller-simulation-replacement"]),
|
||||
evidence: "Vendored LinuxCNC vismach remap configs remain LinuxCNC-owned remap/kinematics assets staged through the WASM boundary.",
|
||||
}),
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_COVERAGE_STATES = Object.freeze({
|
||||
simulated: "simulated",
|
||||
bridgeOnly: "bridge-only",
|
||||
@@ -338,6 +431,157 @@ export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([
|
||||
"motion-controller",
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "pin-signal-net-show",
|
||||
label: "HAL pin, signal, net, and show workflow",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
|
||||
]),
|
||||
commands: Object.freeze([
|
||||
"newsig x-pos HAL_FLOAT",
|
||||
"setp axis.x.pos-cmd 2.5",
|
||||
"sets x-pos 1.25",
|
||||
"net x-pos axis.x.pos-cmd joint.0.pos-fb",
|
||||
"show pin axis.x.*",
|
||||
"show sig x-pos",
|
||||
"getp axis.x.pos-cmd",
|
||||
"gets x-pos",
|
||||
]),
|
||||
requiredActions: Object.freeze(["newsig", "setp", "sets", "net", "show", "getp", "gets"]),
|
||||
expectedOutput: Object.freeze(["axis.x.pos-cmd", "x-pos"]),
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "load-thread-start-stop",
|
||||
label: "HAL load, function, and thread workflow",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
|
||||
]),
|
||||
commands: Object.freeze([
|
||||
"loadrt trivkins",
|
||||
"loadusr halui",
|
||||
"addf motion-controller servo-thread",
|
||||
"start servo-thread",
|
||||
"show function motion-controller",
|
||||
"stop servo-thread",
|
||||
]),
|
||||
requiredActions: Object.freeze(["loadrt", "loadusr", "addf", "start", "show", "stop"]),
|
||||
expectedOutput: Object.freeze(["motion-controller"]),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({
|
||||
"realtime-hal-simulation-replacement": Object.freeze({
|
||||
label: "LinuxCNC realtime HAL simulation replacement",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.axisui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.axisuiScript,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
evidence: "Virtual HAL pin families are derived from LinuxCNC AXIS, HALUI, iocontrol, and motion source exports.",
|
||||
}),
|
||||
"halcmd-simulation-replacement": Object.freeze({
|
||||
label: "LinuxCNC halcmd simulation replacement",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
|
||||
]),
|
||||
evidence: "Virtual halcmd command verbs mirror LinuxCNC halcmd command surface for simulation workflows.",
|
||||
}),
|
||||
"motion-controller-simulation-replacement": Object.freeze({
|
||||
label: "LinuxCNC motion controller simulation replacement",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
evidence: "Virtual motion controller state and pins are derived from LinuxCNC motion, axis, homing, and motion header exports.",
|
||||
}),
|
||||
"hal-pin-signal-param-store": Object.freeze({
|
||||
label: "HAL pin/signal/param store",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.axisui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halui,
|
||||
]),
|
||||
evidence: "HAL object store behavior is constrained to LinuxCNC HAL pin families and halcmd pin/signal/param verbs.",
|
||||
}),
|
||||
"halcmd-setp-sets-net-show-getp-gets": Object.freeze({
|
||||
label: "halcmd setp/sets/net/show/getp/gets verbs",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
|
||||
]),
|
||||
evidence: "Virtual halcmd supports the LinuxCNC halcmd verbs needed by Web simulation fixtures.",
|
||||
}),
|
||||
"userspace-load-command-stubs": Object.freeze({
|
||||
label: "loadrt/loadusr/addf/start/stop command stubs",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmd,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
|
||||
]),
|
||||
evidence: "Userspace/realtime load command records follow LinuxCNC halcmd command names while remaining simulation stubs.",
|
||||
}),
|
||||
"servo-period-motion-step": Object.freeze({
|
||||
label: "Servo-period motion step",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
evidence: "Servo-period stepping exposes LinuxCNC motion pin concepts: target, velocity, distance-to-go, and in-position.",
|
||||
}),
|
||||
"axis-joint-position-feedback": Object.freeze({
|
||||
label: "Axis/joint position feedback",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
|
||||
]),
|
||||
evidence: "Axis and joint feedback rows are derived from LinuxCNC axis/joint/homing HAL pin families.",
|
||||
}),
|
||||
"spindle-coolant-tool-status": Object.freeze({
|
||||
label: "Spindle, coolant, and tool status",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
|
||||
]),
|
||||
evidence: "Spindle, coolant, and tool state rows are derived from LinuxCNC motion, HALUI, and iocontrol pin families.",
|
||||
}),
|
||||
"interpreter-hal-bridge-snapshot": Object.freeze({
|
||||
label: "Interpreter HAL bridge snapshot",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.axisui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.halui,
|
||||
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
evidence: "Bridge snapshots only serialize source-derived virtual HAL pins and values into the LinuxCNC-backed interpreter HAL adapter.",
|
||||
}),
|
||||
});
|
||||
|
||||
export function createVirtualHalState(overrides = {}) {
|
||||
const base = {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-state",
|
||||
@@ -1777,9 +2021,78 @@ export function executeVirtualHalCommand(halState = createVirtualHalState(), com
|
||||
};
|
||||
}
|
||||
|
||||
export function createVirtualHalCommandScriptFixtureReport(options = {}) {
|
||||
const fixtures = options.fixtures ?? VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES;
|
||||
let state = normalizeVirtualHalState(options.halState ?? createVirtualHalState());
|
||||
const fixtureRows = fixtures.map((fixture) => {
|
||||
const commandText = fixture.commands.join("\n");
|
||||
const result = executeVirtualHalCommand(state, commandText, options);
|
||||
state = result.state;
|
||||
const actions = result.rows.map(({ action }) => action);
|
||||
const missingActions = fixture.requiredActions.filter((action) => !actions.includes(action));
|
||||
const missingOutput = fixture.expectedOutput.filter((token) => !result.output.includes(token));
|
||||
const sourceFiles = [...(fixture.sourceFiles ?? [])];
|
||||
return {
|
||||
id: fixture.id,
|
||||
label: fixture.label,
|
||||
commandText,
|
||||
commandCount: result.commandCount,
|
||||
ok: result.ok === true,
|
||||
sourceDerived: sourceFiles.length > 0,
|
||||
sourceFiles,
|
||||
requiredActions: [...fixture.requiredActions],
|
||||
actions,
|
||||
missingActions,
|
||||
expectedOutput: [...fixture.expectedOutput],
|
||||
missingOutput,
|
||||
output: result.output,
|
||||
rows: result.rows,
|
||||
ready: result.ok === true &&
|
||||
sourceFiles.length > 0 &&
|
||||
missingActions.length === 0 &&
|
||||
missingOutput.length === 0,
|
||||
};
|
||||
});
|
||||
const missingFixtures = fixtureRows
|
||||
.filter((row) => row.ready !== true)
|
||||
.map((row) => row.id);
|
||||
const coveredActions = [...new Set(fixtureRows.flatMap((row) => row.actions))].sort();
|
||||
const requiredActions = [...new Set(fixtures.flatMap((fixture) => fixture.requiredActions))].sort();
|
||||
const missingActions = requiredActions.filter((action) => !coveredActions.includes(action));
|
||||
const complete = fixtureRows.length > 0 &&
|
||||
missingFixtures.length === 0 &&
|
||||
missingActions.length === 0;
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-command-script-fixture-report",
|
||||
fixtureVersion: 1,
|
||||
source: "linuxcnc-halcmd-source-derived-virtual-hal",
|
||||
phase: complete ? "ready" : "blocked",
|
||||
complete,
|
||||
webSimulationSatisfied: complete,
|
||||
sourceFiles: [...new Set(fixtureRows.flatMap((row) => row.sourceFiles))],
|
||||
requiredActions,
|
||||
coveredActions,
|
||||
missingActions,
|
||||
missingFixtures,
|
||||
fixtureCount: fixtureRows.length,
|
||||
commandCount: fixtureRows.reduce((sum, row) => sum + row.commandCount, 0),
|
||||
finalState: cloneVirtualHalState(state),
|
||||
rows: fixtureRows,
|
||||
summaryRows: [
|
||||
{ id: "fixtures", label: "HAL command fixtures", value: `${fixtureRows.length}` },
|
||||
{ id: "commands", label: "HAL commands", value: `${fixtureRows.reduce((sum, row) => sum + row.commandCount, 0)}` },
|
||||
{ id: "actions", label: "Covered halcmd actions", value: missingActions.length === 0 ? "covered" : `missing ${missingActions.length}` },
|
||||
{ id: "source", label: "LinuxCNC halcmd source", value: complete ? "source-derived" : "incomplete" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
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 requiredCapabilities = options.requiredCapabilities ?? [
|
||||
"realtime-hal-simulation-replacement",
|
||||
"halcmd-simulation-replacement",
|
||||
@@ -1823,6 +2136,9 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
|
||||
requiredCapabilities,
|
||||
missingCapabilities,
|
||||
replacements,
|
||||
sourceCompliance,
|
||||
simConfigSourceCoverage,
|
||||
commandScriptFixtures,
|
||||
runtime,
|
||||
rows: [
|
||||
{ id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" },
|
||||
@@ -1834,6 +2150,197 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLinuxCncSourcePath(path) {
|
||||
const clean = String(path ?? "")
|
||||
.replace(/^wasm-port\/vendor\/linuxcnc\//, "linuxcnc/")
|
||||
.replace(/^vendor\/linuxcnc\//, "linuxcnc/")
|
||||
.replace(/^linuxcnc\//, "");
|
||||
const parts = [];
|
||||
for (const part of clean.split("/")) {
|
||||
if (!part || part === ".") {
|
||||
continue;
|
||||
}
|
||||
if (part === "..") {
|
||||
parts.pop();
|
||||
continue;
|
||||
}
|
||||
parts.push(part);
|
||||
}
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
function sourceManifestSetFromText(manifestText = "") {
|
||||
return new Set(String(manifestText)
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("#"))
|
||||
.map(normalizeLinuxCncSourcePath));
|
||||
}
|
||||
|
||||
export function createVirtualHalSimConfigSourceCoverageReport(options = {}) {
|
||||
const targets = options.targets ?? VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS;
|
||||
const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries);
|
||||
const manifestSet = new Set([
|
||||
...sourceManifestSetFromText(options.manifestText ?? ""),
|
||||
...(options.manifestEntries ?? []).map(normalizeLinuxCncSourcePath),
|
||||
]);
|
||||
const rows = targets.map((target) => {
|
||||
const sourceFiles = [...(target.sourceFiles ?? [])];
|
||||
const normalizedSourceFiles = sourceFiles.map(normalizeLinuxCncSourcePath);
|
||||
const missingManifestFiles = manifestProvided
|
||||
? sourceFiles.filter((file, index) => !manifestSet.has(normalizedSourceFiles[index]))
|
||||
: [];
|
||||
return {
|
||||
id: target.id,
|
||||
label: target.label,
|
||||
source: "linuxcnc/configs/sim",
|
||||
sourceFiles,
|
||||
capabilities: [...(target.capabilities ?? [])],
|
||||
evidence: target.evidence ?? "",
|
||||
sourceDerived: sourceFiles.length > 0 && missingManifestFiles.length === 0,
|
||||
manifestChecked: manifestProvided,
|
||||
missingManifestFiles,
|
||||
};
|
||||
});
|
||||
const missingTargets = rows
|
||||
.filter((row) => !row.sourceDerived)
|
||||
.map((row) => row.id);
|
||||
const coveredCapabilities = [...new Set(rows.flatMap((row) => row.capabilities))].sort();
|
||||
const requiredCapabilities = options.requiredCapabilities ?? [
|
||||
"realtime-hal-simulation-replacement",
|
||||
"halcmd-simulation-replacement",
|
||||
"motion-controller-simulation-replacement",
|
||||
"hal-pin-signal-param-store",
|
||||
"userspace-load-command-stubs",
|
||||
"servo-period-motion-step",
|
||||
"axis-joint-position-feedback",
|
||||
"spindle-coolant-tool-status",
|
||||
"interpreter-hal-bridge-snapshot",
|
||||
];
|
||||
const missingCapabilities = requiredCapabilities.filter((capability) => !coveredCapabilities.includes(capability));
|
||||
const complete = rows.length > 0 && missingTargets.length === 0 && missingCapabilities.length === 0;
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report",
|
||||
coverageVersion: 1,
|
||||
source: "linuxcnc-configs-sim-source-derived-virtual-hal",
|
||||
phase: complete ? "source-covered" : "incomplete",
|
||||
complete,
|
||||
webSimulationSatisfied: complete,
|
||||
manifestChecked: manifestProvided,
|
||||
targetCount: rows.length,
|
||||
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
|
||||
coveredCapabilities,
|
||||
requiredCapabilities,
|
||||
missingCapabilities,
|
||||
missingTargets,
|
||||
rows,
|
||||
summaryRows: [
|
||||
{ id: "targets", label: "LinuxCNC sim config targets", value: `${rows.length}` },
|
||||
{ id: "source-files", label: "Source files", value: `${new Set(rows.flatMap((row) => row.sourceFiles)).size}` },
|
||||
{ id: "capabilities", label: "Covered capabilities", value: missingCapabilities.length === 0 ? "covered" : `missing ${missingCapabilities.length}` },
|
||||
{ id: "manifest", label: "Manifest check", value: manifestProvided ? (missingTargets.length === 0 ? "passed" : "failed") : "not provided" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createVirtualHalSourceComplianceReport(options = {}) {
|
||||
const halState = options.halState ?? createVirtualHalState();
|
||||
const snapshot = options.snapshot ?? createVirtualHalWasmBridgeSnapshot(halState);
|
||||
const systemCoverage = options.systemCoverage ?? createVirtualHalSystemCoverageReport({
|
||||
halState,
|
||||
snapshot,
|
||||
});
|
||||
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
|
||||
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport(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),
|
||||
...(options.allowedSourceFiles ?? []),
|
||||
]);
|
||||
const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES;
|
||||
const capabilityRows = requiredCapabilities.map((capability) => {
|
||||
const entry = VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES[capability];
|
||||
const sourceFiles = [...(entry?.sourceFiles ?? [])];
|
||||
const missingSource = !entry || sourceFiles.length === 0;
|
||||
const disallowedSourceFiles = sourceFiles.filter((file) => !allowedSourceFiles.has(file));
|
||||
return {
|
||||
id: capability,
|
||||
label: entry?.label ?? capability,
|
||||
capability,
|
||||
sourceFiles,
|
||||
evidence: entry?.evidence ?? "",
|
||||
sourceDerived: !missingSource && disallowedSourceFiles.length === 0,
|
||||
missingSource,
|
||||
disallowedSourceFiles,
|
||||
};
|
||||
});
|
||||
const targetRows = VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.map((target) => {
|
||||
const targetCapabilities = target === "linuxcnc-realtime-hal"
|
||||
? ["realtime-hal-simulation-replacement", "hal-pin-signal-param-store"]
|
||||
: target === "halcmd"
|
||||
? ["halcmd-simulation-replacement", "halcmd-setp-sets-net-show-getp-gets", "userspace-load-command-stubs"]
|
||||
: ["motion-controller-simulation-replacement", "servo-period-motion-step", "axis-joint-position-feedback"];
|
||||
const rows = capabilityRows.filter((row) => targetCapabilities.includes(row.capability));
|
||||
return {
|
||||
id: target,
|
||||
target,
|
||||
sourceDerived: rows.length > 0 && rows.every((row) => row.sourceDerived),
|
||||
capabilities: targetCapabilities,
|
||||
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
|
||||
};
|
||||
});
|
||||
const missingCapabilitySources = capabilityRows
|
||||
.filter((row) => !row.sourceDerived)
|
||||
.map((row) => row.capability);
|
||||
const missingFamilySources = systemCoverage.missingSourceFamilies ?? [];
|
||||
const complete = missingCapabilitySources.length === 0 &&
|
||||
missingFamilySources.length === 0 &&
|
||||
simConfigSourceCoverage.complete === true &&
|
||||
commandScriptFixtures.complete === true &&
|
||||
systemCoverage.complete === true &&
|
||||
targetRows.every((row) => row.sourceDerived);
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-source-compliance-report",
|
||||
complianceVersion: 1,
|
||||
source: "linuxcnc-source-derived-virtual-hal",
|
||||
phase: complete ? "source-complete" : "incomplete",
|
||||
complete,
|
||||
webSimulationSatisfied: complete,
|
||||
sourceRule: "Virtual HAL functionality must be derived from LinuxCNC source files, configs, scripts, or LinuxCNC-backed runtime evidence.",
|
||||
replacementTargets: [...VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS],
|
||||
allowedSourceFiles: [...allowedSourceFiles],
|
||||
sourceFiles: [...new Set([
|
||||
...systemCoverage.sourceFiles,
|
||||
...capabilityRows.flatMap((row) => row.sourceFiles),
|
||||
...simConfigSourceCoverage.sourceFiles,
|
||||
...commandScriptFixtures.sourceFiles,
|
||||
])],
|
||||
missingCapabilitySources,
|
||||
missingFamilySources,
|
||||
simConfigSourceCoverage,
|
||||
commandScriptFixtures,
|
||||
targetRows,
|
||||
capabilityRows,
|
||||
familyRows: systemCoverage.families.map((family) => ({
|
||||
id: family.id,
|
||||
label: family.label,
|
||||
coverage: family.coverage,
|
||||
sourceFiles: [...family.sourceFiles],
|
||||
sourceEvidence: family.sourceEvidence,
|
||||
sourceDerived: family.sourceFiles.length > 0,
|
||||
})),
|
||||
rows: [
|
||||
{ id: "targets", label: "Replacement targets", value: targetRows.every((row) => row.sourceDerived) ? "source-derived" : "incomplete" },
|
||||
{ id: "capabilities", label: "Runtime capabilities", value: missingCapabilitySources.length === 0 ? "source-derived" : `missing ${missingCapabilitySources.length}` },
|
||||
{ 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: "web-simulation", label: "Web simulation HAL", value: complete ? "satisfied" : "blocked" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createVirtualHalSimulationRuntimeReport(halState = createVirtualHalState(), options = {}) {
|
||||
const state = normalizeVirtualHalState(halState);
|
||||
const snapshot = createVirtualHalWasmBridgeSnapshot(state);
|
||||
@@ -1936,6 +2443,15 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS
|
||||
getSimulationReplacementReport(options = {}) {
|
||||
return createVirtualHalSimulationReplacementReport(state, options);
|
||||
},
|
||||
getSourceComplianceReport(options = {}) {
|
||||
return createVirtualHalSourceComplianceReport({ ...options, halState: state });
|
||||
},
|
||||
getSimConfigSourceCoverageReport(options = {}) {
|
||||
return createVirtualHalSimConfigSourceCoverageReport(options);
|
||||
},
|
||||
getCommandScriptFixtureReport(options = {}) {
|
||||
return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
||||
},
|
||||
getIntegrityReport(options = {}) {
|
||||
return createVirtualHalIntegrityReport({ ...options, halState: state });
|
||||
},
|
||||
@@ -2029,6 +2545,26 @@ function createVirtualHalShowRows(state, kind = "pin", pattern = "") {
|
||||
value: normalizeHalNumericValue(entry.value),
|
||||
}));
|
||||
}
|
||||
if (kind === "funct" || kind === "function") {
|
||||
return (state.hal.functions ?? [])
|
||||
.filter(({ name }) => matches(name))
|
||||
.map((entry) => ({
|
||||
kind: "function",
|
||||
name: entry.name,
|
||||
type: "HAL_FUNCT",
|
||||
value: entry.thread ?? "",
|
||||
}));
|
||||
}
|
||||
if (kind === "thread") {
|
||||
return Object.entries(state.hal.threads ?? {})
|
||||
.filter(([name]) => matches(name))
|
||||
.map(([name, entry]) => ({
|
||||
kind: "thread",
|
||||
name,
|
||||
type: "HAL_THREAD",
|
||||
value: entry.running ? "running" : "stopped",
|
||||
}));
|
||||
}
|
||||
return createVirtualHalWasmBridgeSnapshot(state).values
|
||||
.filter(({ kind: rowKind, name }) => rowKind === "pin" && matches(name))
|
||||
.map((row) => ({
|
||||
|
||||
Reference in New Issue
Block a user