接续上一轮,先做 L4-USER-M-PROCESS,完全接入仿真系统

结论:已将 L4-USER-M-PROCESS 的 millturn M128/M129 受控状态转换接入 virtual HAL、真实 browser simulation、OPFS 会话 payload、SDK API 和 release diagnostics validation;外部 user-M process execution 与 promotion 仍保持 locked,不伪装为 native process 解锁。
This commit is contained in:
2026-06-20 06:33:00 +08:00
parent 15b98c3ecf
commit d0e55ba11e
12 changed files with 676 additions and 7 deletions

View File

@@ -318,7 +318,9 @@ private page state.
`executeHalcmd()`, `stepMotion()`, `stepMotionController()`,
`getSimulationRuntimeReport()`, `getSimulationReplacementReport()`,
`getSourceComplianceReport()`, `getSimConfigPromotionCandidateReport()`,
`getSimConfigMacroLoadFixtureReport()`, `getIntegrityReport()`,
`getSimConfigMacroLoadFixtureReport()`,
`getMillturnUserMProcessBoundaryReport()`,
`applyMillturnUserMProcessState()`, `getIntegrityReport()`,
`getPinInventory()`, `getProjectReport()`, and `applyToInterpSdk()`.
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`,
`VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`,
@@ -339,6 +341,18 @@ private page state.
`manifestEntries`, the matrix report checks those associated sim-config
source files against the vendored LinuxCNC manifest and exposes
`manifestChecked` plus `missingManifestFiles`.
- `VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY`,
`createVirtualHalMillturnUserMProcessBoundaryReport()`, and
`applyVirtualHalMillturnUserMProcessState()` expose the controlled
millturn `M128`/`M129` user-M process boundary for browser/Node simulation.
The report names the LinuxCNC `millturn.ini`, `example.ngc`, Tcl M-code
scripts, and remap callers, then lists the source-derived
`motion.switchkins-type`, `kinstype.is-*`, and `ini.[xyz].*` HAL state
targets. `applyVirtualHalMillturnUserMProcessState()` can apply that
controlled state to the virtual HAL for simulation display. It keeps
`executionEnabled === false`, `promotionAllowed === false`, and
`processExecutionReady === false`; it does not execute arbitrary external
user-M processes or unlock `L4-USER-M-PROCESS`.
- `VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES` and
`createVirtualHalSimConfigPromotionCandidateReport()` expose the current
source-derived sim-config promotion candidate set. The first candidates keep

View File

@@ -5,6 +5,7 @@ export {
VIRTUAL_HAL_AXISUI_PINS,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
@@ -23,6 +24,7 @@ export {
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
applyVirtualHalToInterpSdk,
applyVirtualHalAction,
applyVirtualHalMillturnUserMProcessState,
applyVirtualHalPinUpdates,
cloneVirtualHalState,
createLinuxCncVirtualHalRuntime,
@@ -34,6 +36,7 @@ export {
createVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState,
createVirtualHalIntegrityReport,
createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalPinInventory,
createVirtualHalPinRegistry,
createVirtualHalProjectReport,

View File

@@ -32,6 +32,84 @@ export const VIRTUAL_HAL_SOURCE_FILES = Object.freeze({
vendoredMotionHeader: "wasm-port/vendor/linuxcnc/src/emc/motion/motion.h",
});
export const VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY = Object.freeze({
id: "millturn-user-m-process",
label: "Millturn M128/M129 user-M process boundary",
boundaryClass: "L4-USER-M-PROCESS",
path: "axis/vismach/millturn/example.ngc",
ini: "axis/vismach/millturn/millturn.ini",
requiredRuntime: "linuxcnc_task_hal_tcl_user_m_process",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/vismach/millturn/millturn.ini",
"linuxcnc/configs/sim/axis/vismach/millturn/example.ngc",
"linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128",
"linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M129",
"linuxcnc/configs/sim/axis/vismach/millturn/remap_subs/428remap.ngc",
"linuxcnc/configs/sim/axis/vismach/millturn/remap_subs/429remap.ngc",
]),
manifestSourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/vismach/millturn/millturn.ini",
"linuxcnc/configs/sim/axis/vismach/millturn/example.ngc",
"linuxcnc/configs/sim/axis/vismach/millturn/remap_subs/428remap.ngc",
"linuxcnc/configs/sim/axis/vismach/millturn/remap_subs/429remap.ngc",
]),
nativeProbe: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
cases: Object.freeze([
Object.freeze({
userMCode: "M128",
remapCode: "M428",
stateMode: "mill",
guardPin: "kinstype.is-0",
switchkinsOutputPin: "motion.analog-out-03",
switchkinsTarget: 0,
activeG5x: "G59.1",
workOffsetPocket: "P7",
workOffsetWords: "X-290 Y0 Z-160 A0",
sourceField: "MIN_LIMIT/MAX_LIMIT",
stateTargets: Object.freeze([
Object.freeze({ pin: "ini.x.min_limit", value: -300, sourceSection: "AXIS_X", sourceField: "MIN_LIMIT" }),
Object.freeze({ pin: "ini.x.max_limit", value: 300, sourceSection: "AXIS_X", sourceField: "MAX_LIMIT" }),
Object.freeze({ pin: "ini.x.min_velocity", value: 60.0, sourceSection: "AXIS_X", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.x.max_acceleration", value: 400.0, sourceSection: "AXIS_X", sourceField: "MAX_ACCELERATION" }),
Object.freeze({ pin: "ini.y.min_limit", value: -100, sourceSection: "AXIS_Y", sourceField: "MIN_LIMIT" }),
Object.freeze({ pin: "ini.y.max_limit", value: 100, sourceSection: "AXIS_Y", sourceField: "MAX_LIMIT" }),
Object.freeze({ pin: "ini.y.min_velocity", value: 60.0, sourceSection: "AXIS_Y", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.y.max_acceleration", value: 400.0, sourceSection: "AXIS_Y", sourceField: "MAX_ACCELERATION" }),
Object.freeze({ pin: "ini.z.min_limit", value: -240, sourceSection: "AXIS_Z", sourceField: "MIN_LIMIT" }),
Object.freeze({ pin: "ini.z.max_limit", value: 0, sourceSection: "AXIS_Z", sourceField: "MAX_LIMIT" }),
Object.freeze({ pin: "ini.z.min_velocity", value: 60.0, sourceSection: "AXIS_Z", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.z.max_acceleration", value: 400.0, sourceSection: "AXIS_Z", sourceField: "MAX_ACCELERATION" }),
]),
}),
Object.freeze({
userMCode: "M129",
remapCode: "M429",
stateMode: "turn",
guardPin: "kinstype.is-1",
switchkinsOutputPin: "motion.analog-out-03",
switchkinsTarget: 1,
activeG5x: "G59.2",
workOffsetPocket: "P8",
workOffsetWords: "X-160 Y0 Z-290 A0",
sourceField: "MIN_LIMIT_TURN/MAX_LIMIT_TURN",
stateTargets: Object.freeze([
Object.freeze({ pin: "ini.x.min_limit", value: -240, sourceSection: "AXIS_X", sourceField: "MIN_LIMIT_TURN" }),
Object.freeze({ pin: "ini.x.max_limit", value: 0, sourceSection: "AXIS_X", sourceField: "MAX_LIMIT_TURN" }),
Object.freeze({ pin: "ini.x.min_velocity", value: 60.0, sourceSection: "AXIS_X", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.x.max_acceleration", value: 400.0, sourceSection: "AXIS_X", sourceField: "MAX_ACCELERATION" }),
Object.freeze({ pin: "ini.y.min_limit", value: -100, sourceSection: "AXIS_Y", sourceField: "MIN_LIMIT_TURN" }),
Object.freeze({ pin: "ini.y.max_limit", value: 100, sourceSection: "AXIS_Y", sourceField: "MAX_LIMIT_TURN" }),
Object.freeze({ pin: "ini.y.min_velocity", value: 60.0, sourceSection: "AXIS_Y", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.y.max_acceleration", value: 400.0, sourceSection: "AXIS_Y", sourceField: "MAX_ACCELERATION" }),
Object.freeze({ pin: "ini.z.min_limit", value: -300, sourceSection: "AXIS_Z", sourceField: "MIN_LIMIT_TURN" }),
Object.freeze({ pin: "ini.z.max_limit", value: 300, sourceSection: "AXIS_Z", sourceField: "MAX_LIMIT_TURN" }),
Object.freeze({ pin: "ini.z.min_velocity", value: 60.0, sourceSection: "AXIS_Z", sourceField: "MAX_VELOCITY" }),
Object.freeze({ pin: "ini.z.max_acceleration", value: 400.0, sourceSection: "AXIS_Z", sourceField: "MAX_ACCELERATION" }),
]),
}),
]),
});
export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
Object.freeze({
id: "axis-foam",
@@ -740,6 +818,31 @@ export const VIRTUAL_HAL_SYSTEM_PIN_FAMILIES = Object.freeze([
{ name: "motion.switchkins-type", type: "HAL_FLOAT", direction: "HAL_IN", conditional: "switchable kinematics" },
]),
},
{
id: "millturn-user-m-process",
component: "millturn",
label: "Millturn user-M process boundary pins",
sourceFiles: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
sourceEvidence: "LinuxCNC millturn M128/M129 Tcl scripts and remap callers set switchkins guard state and ini.[xyz] HAL pins.",
coverage: VIRTUAL_HAL_COVERAGE_STATES.runtimeBoundary,
pins: Object.freeze([
{ name: "motion.analog-out-03", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "kinstype.is-0", type: "HAL_BIT", direction: "HAL_OUT" },
{ name: "kinstype.is-1", type: "HAL_BIT", direction: "HAL_OUT" },
{ name: "ini.x.min_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.x.max_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.x.min_velocity", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.x.max_acceleration", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.y.min_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.y.max_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.y.min_velocity", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.y.max_acceleration", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.z.min_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.z.max_limit", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.z.min_velocity", type: "HAL_FLOAT", direction: "HAL_OUT" },
{ name: "ini.z.max_acceleration", type: "HAL_FLOAT", direction: "HAL_OUT" },
]),
},
{
id: "axis-motion",
component: "axis",
@@ -866,6 +969,11 @@ export const VIRTUAL_HAL_PROJECT_PIN_GROUPS = Object.freeze([
label: "Tool pins",
description: "Tool status, tool change, and motion tool-offset pins from HALUI, iocontrol, and motion.",
},
{
id: "user-m-process",
label: "User-M process boundary pins",
description: "Controlled millturn M128/M129 state pins sourced from LinuxCNC Tcl user-M scripts and remap callers.",
},
]);
export const VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES = Object.freeze([
@@ -879,6 +987,7 @@ export const VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES = Object.freeze([
"axis-joint-position-feedback",
"spindle-coolant-tool-status",
"interpreter-hal-bridge-snapshot",
"controlled-user-m-process-state-boundary",
]);
export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([
@@ -887,6 +996,24 @@ export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([
"motion-controller",
]);
const VIRTUAL_HAL_DEFAULT_MILLTURN_USER_M_EXTERNAL_PINS = Object.freeze({
"motion.analog-out-03": Object.freeze({ kind: "pin", name: "motion.analog-out-03", type: "HAL_FLOAT", value: 0, connected: true }),
"kinstype.is-0": Object.freeze({ kind: "pin", name: "kinstype.is-0", type: "HAL_BIT", value: 0, connected: true }),
"kinstype.is-1": Object.freeze({ kind: "pin", name: "kinstype.is-1", type: "HAL_BIT", value: 0, connected: true }),
"ini.x.min_limit": Object.freeze({ kind: "pin", name: "ini.x.min_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.x.max_limit": Object.freeze({ kind: "pin", name: "ini.x.max_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.x.min_velocity": Object.freeze({ kind: "pin", name: "ini.x.min_velocity", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.x.max_acceleration": Object.freeze({ kind: "pin", name: "ini.x.max_acceleration", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.y.min_limit": Object.freeze({ kind: "pin", name: "ini.y.min_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.y.max_limit": Object.freeze({ kind: "pin", name: "ini.y.max_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.y.min_velocity": Object.freeze({ kind: "pin", name: "ini.y.min_velocity", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.y.max_acceleration": Object.freeze({ kind: "pin", name: "ini.y.max_acceleration", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.z.min_limit": Object.freeze({ kind: "pin", name: "ini.z.min_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.z.max_limit": Object.freeze({ kind: "pin", name: "ini.z.max_limit", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.z.min_velocity": Object.freeze({ kind: "pin", name: "ini.z.min_velocity", type: "HAL_FLOAT", value: 0, connected: true }),
"ini.z.max_acceleration": Object.freeze({ kind: "pin", name: "ini.z.max_acceleration", type: "HAL_FLOAT", value: 0, connected: true }),
});
export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([
Object.freeze({
id: "pin-signal-net-show",
@@ -1130,6 +1257,11 @@ export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({
]),
evidence: "Bridge snapshots only serialize source-derived virtual HAL pins and values into the LinuxCNC-backed interpreter HAL adapter.",
}),
"controlled-user-m-process-state-boundary": Object.freeze({
label: "Controlled user-M process state boundary",
sourceFiles: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
evidence: "Millturn M128/M129 simulation exposes LinuxCNC source-derived Tcl/HAL state targets while keeping external process execution and promotion locked.",
}),
});
export function createVirtualHalState(overrides = {}) {
@@ -1150,7 +1282,7 @@ export function createVirtualHalState(overrides = {}) {
mode: "manual",
},
pins: Object.fromEntries(VIRTUAL_HAL_AXISUI_PINS.pins.map((pin) => [pin.name, pin.type === "HAL_FLOAT" ? 0 : false])),
externalPins: {},
externalPins: Object.fromEntries(Object.entries(VIRTUAL_HAL_DEFAULT_MILLTURN_USER_M_EXTERNAL_PINS).map(([name, entry]) => [name, { ...entry }])),
position: Object.fromEntries(VIRTUAL_HAL_AXES.map((axis) => [axis, 0])),
homed: Object.fromEntries(VIRTUAL_HAL_AXES.map((axis) => [axis, false])),
limits: Object.fromEntries(VIRTUAL_HAL_AXES.map((axis) => [
@@ -2210,6 +2342,12 @@ export function createVirtualHalBridgeReadiness(options = {}) {
"halui.estop.is-activated",
"spindle.0.speed-out",
"iocontrol.0.coolant-flood",
"motion.switchkins-type",
"motion.analog-out-03",
"kinstype.is-0",
"kinstype.is-1",
"ini.x.min_limit",
"ini.z.max_limit",
];
const snapshotPins = new Set((snapshot.values ?? []).map(({ name }) => name));
const missingPins = requiredPins.filter((name) => !snapshotPins.has(name));
@@ -2337,6 +2475,7 @@ export function createVirtualHalProjectReport(options = {}) {
registry: pinRegistry,
systemCoverage,
});
const userMProcessBoundary = createVirtualHalMillturnUserMProcessBoundaryReport(options);
return {
apiName: "linuxcnc-wasm-virtual-hal-project-report",
reportVersion: 1,
@@ -2353,6 +2492,7 @@ export function createVirtualHalProjectReport(options = {}) {
integrity,
systemCoverage,
simulationRuntime: createVirtualHalSimulationRuntimeReport(state),
userMProcessBoundary,
wasmBridgeSnapshot: snapshot,
bridgeReadiness: readiness,
bridgeActionPlan: actionPlan,
@@ -2387,6 +2527,11 @@ export function createVirtualHalProjectReport(options = {}) {
label: "Simulation HAL runtime",
value: "ready",
},
{
id: "user-m-process-boundary",
label: "User-M process boundary",
value: userMProcessBoundary.phase,
},
],
};
}
@@ -3407,6 +3552,177 @@ export function createVirtualHalSimConfigMacroLoadFixtureReport(options = {}) {
};
}
function virtualHalMillturnUserMCaseForCode(code) {
const normalized = String(code ?? "").trim().toUpperCase();
return VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.cases
.find((entry) => entry.userMCode === normalized || entry.remapCode === normalized) ?? null;
}
function createMillturnUserMStateTargetRows(boundaryCase) {
return boundaryCase.stateTargets.map((target) => ({
path: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.path,
ini: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.ini,
blocked: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass,
userMCode: boundaryCase.userMCode,
targetPin: target.pin,
sourceSection: target.sourceSection,
sourceField: target.sourceField,
expectedValue: target.value,
executionEnabled: false,
promotionAllowed: false,
proofStatus: "pending_native_runtime_probe",
notes: "source_derived_virtual_hal_state_target_no_external_process_execution",
}));
}
export function createVirtualHalMillturnUserMProcessBoundaryReport(options = {}) {
const boundary = VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY;
const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries);
const manifestSet = new Set([
...sourceManifestSetFromText(options.manifestText ?? ""),
...(options.manifestEntries ?? []).map(normalizeLinuxCncSourcePath),
]);
const sourceFiles = [...boundary.sourceFiles];
const manifestSourceFiles = [...boundary.manifestSourceFiles];
const missingManifestFiles = manifestProvided
? manifestSourceFiles.filter((file) => !manifestSet.has(normalizeLinuxCncSourcePath(file)))
: [];
const rows = boundary.cases.map((entry) => {
const stateTargetRows = createMillturnUserMStateTargetRows(entry);
return {
path: boundary.path,
ini: boundary.ini,
blocked: boundary.boundaryClass,
remapCode: entry.remapCode,
userMCode: entry.userMCode,
stateMode: entry.stateMode,
guardPin: entry.guardPin,
switchkinsOutputPin: entry.switchkinsOutputPin,
switchkinsTarget: entry.switchkinsTarget,
activeG5x: entry.activeG5x,
workOffsetPocket: entry.workOffsetPocket,
workOffsetWords: entry.workOffsetWords,
requiredRuntime: boundary.requiredRuntime,
expectedStatePins: stateTargetRows.map((row) => row.targetPin),
expectedStateValues: stateTargetRows.map((row) => `${row.targetPin}=${row.expectedValue}`),
stateTargetCount: stateTargetRows.length,
stateTargetRows,
sourceFiles,
manifestSourceFiles,
sourceDerived: sourceFiles.length > 0,
executionEnabled: false,
promotionAllowed: false,
proofStatus: "pending_native_runtime_probe",
nativeProbe: boundary.nativeProbe,
complete: sourceFiles.length > 0 && missingManifestFiles.length === 0,
notes: "controlled_virtual_hal_state_boundary_keep_user_m_process_blocked",
};
});
const complete = rows.length > 0 &&
rows.every((row) => row.complete) &&
missingManifestFiles.length === 0;
return {
apiName: "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report",
reportVersion: 1,
source: "linuxcnc-configs-sim-source-derived-user-m-process-boundary",
phase: complete ? "simulation-boundary-ready" : "blocked",
complete,
webSimulationSatisfied: complete,
boundaryClass: boundary.boundaryClass,
path: boundary.path,
ini: boundary.ini,
requiredRuntime: boundary.requiredRuntime,
nativeProbe: boundary.nativeProbe,
nativeRuntimeRequired: true,
processExecutionReady: false,
executionEnabled: false,
promotionAllowed: false,
inventoryBaselineUnchanged: true,
jsCncSemantics: false,
manifestChecked: manifestProvided,
missingManifestFiles,
sourceFiles,
manifestSourceFiles,
caseCount: rows.length,
stateTargetCount: rows.reduce((sum, row) => sum + row.stateTargetCount, 0),
rows,
summaryRows: [
{ id: "boundary", label: "User-M boundary", value: boundary.boundaryClass },
{ id: "cases", label: "M-code cases", value: rows.map((row) => `${row.remapCode}->${row.userMCode}`).join(", ") },
{ id: "state-targets", label: "HAL state targets", value: `${rows.reduce((sum, row) => sum + row.stateTargetCount, 0)}` },
{ id: "execution", label: "External process execution", value: "locked" },
{ id: "promotion", label: "Promotion allowed", value: "0" },
],
};
}
export function applyVirtualHalMillturnUserMProcessState(halState = createVirtualHalState(), codeOrOptions = "M128", options = {}) {
const code = typeof codeOrOptions === "object" && codeOrOptions !== null
? codeOrOptions.code ?? codeOrOptions.userMCode ?? codeOrOptions.remapCode
: codeOrOptions;
const boundaryCase = virtualHalMillturnUserMCaseForCode(code);
if (!boundaryCase) {
throw new Error(`unknown millturn user-M process code: ${code}`);
}
const report = createVirtualHalMillturnUserMProcessBoundaryReport(options);
const row = report.rows.find((entry) => entry.userMCode === boundaryCase.userMCode);
if (!row?.complete) {
throw new Error(`millturn user-M process boundary is not source-complete for ${boundaryCase.userMCode}`);
}
const guardUpdates = VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.cases.map((entry) => ({
name: entry.guardPin,
type: "HAL_BIT",
value: entry.userMCode === boundaryCase.userMCode ? 1 : 0,
}));
const updates = [
{ name: "motion.switchkins-type", type: "HAL_FLOAT", value: boundaryCase.switchkinsTarget },
{ name: boundaryCase.switchkinsOutputPin, type: "HAL_FLOAT", value: boundaryCase.switchkinsTarget },
...guardUpdates,
...boundaryCase.stateTargets.map((target) => ({
name: target.pin,
type: "HAL_FLOAT",
value: target.value,
})),
];
const next = applyVirtualHalPinUpdates(halState, updates, { allowExternal: true });
next.hal.commandLog = [
...(next.hal.commandLog ?? []),
{
source: "virtual-user-m-process-boundary",
action: "millturn-user-m-state",
userMCode: boundaryCase.userMCode,
remapCode: boundaryCase.remapCode,
stateMode: boundaryCase.stateMode,
executionEnabled: false,
promotionAllowed: false,
ok: true,
},
].slice(-200);
next.revision += 1;
return {
apiName: "linuxcnc-wasm-virtual-hal-millturn-user-m-process-state-result",
resultVersion: 1,
source: "linuxcnc-configs-sim-source-derived-user-m-process-state",
ok: true,
boundaryClass: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass,
path: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.path,
ini: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.ini,
userMCode: boundaryCase.userMCode,
remapCode: boundaryCase.remapCode,
stateMode: boundaryCase.stateMode,
switchkinsTarget: boundaryCase.switchkinsTarget,
activeG5x: boundaryCase.activeG5x,
workOffsetPocket: boundaryCase.workOffsetPocket,
appliedPins: updates.map((update) => update.name),
executionEnabled: false,
promotionAllowed: false,
processExecutionReady: false,
state: normalizeVirtualHalState(next),
report,
};
}
export function createVirtualHalSourceComplianceReport(options = {}) {
const halState = options.halState ?? createVirtualHalState();
const snapshot = options.snapshot ?? createVirtualHalWasmBridgeSnapshot(halState);
@@ -3422,6 +3738,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
...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),
...VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
...(options.allowedSourceFiles ?? []),
]);
const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES;
@@ -3629,6 +3946,14 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS
getSimConfigMacroLoadFixtureReport(options = {}) {
return createVirtualHalSimConfigMacroLoadFixtureReport(options);
},
getMillturnUserMProcessBoundaryReport(options = {}) {
return createVirtualHalMillturnUserMProcessBoundaryReport(options);
},
applyMillturnUserMProcessState(codeOrOptions = "M128", options = {}) {
const result = applyVirtualHalMillturnUserMProcessState(state, codeOrOptions, options);
state = result.state;
return result;
},
getCommandScriptFixtureReport(options = {}) {
return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
},
@@ -3826,6 +4151,9 @@ function isVirtualHalPinWritable(name) {
if (/^(axis|joint)\./.test(name) || name.startsWith("spindle.")) {
return true;
}
if (name.startsWith("ini.") || name.startsWith("kinstype.")) {
return true;
}
return false;
}
@@ -4014,6 +4342,9 @@ function applyAxisPinWrite(state, name, numeric, enabled) {
}
function classifyVirtualHalPinGroup(name) {
if (name.startsWith("ini.") || name.startsWith("kinstype.") || name === "motion.analog-out-03") {
return "user-m-process";
}
if (name.startsWith("axisui.") || name.startsWith("jog.") || name.startsWith("notifications-") || name === "error" || name === "abort") {
return "axisui";
}

View File

@@ -702,6 +702,38 @@ function isVirtualHalPromotionCandidateSummaryReady(summary) {
arrayOrEmpty(summaryObject.blockedCandidateIds).length === 0;
}
function isVirtualHalMillturnUserMProcessBoundaryReady(report) {
const reportObject = objectOrEmpty(report);
const rows = arrayOrEmpty(reportObject.rows);
return reportObject.apiName === "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report" &&
reportObject.complete === true &&
reportObject.webSimulationSatisfied === true &&
reportObject.boundaryClass === "L4-USER-M-PROCESS" &&
reportObject.path === "axis/vismach/millturn/example.ngc" &&
reportObject.executionEnabled === false &&
reportObject.promotionAllowed === false &&
reportObject.processExecutionReady === false &&
reportObject.stateTargetCount === 24 &&
arrayOrEmpty(reportObject.sourceFiles).includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128") &&
arrayOrEmpty(reportObject.sourceFiles).includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M129") &&
rows.some((row) =>
row.userMCode === "M128" &&
row.remapCode === "M428" &&
row.stateMode === "mill" &&
row.stateTargetCount === 12 &&
row.executionEnabled === false &&
row.promotionAllowed === false
) &&
rows.some((row) =>
row.userMCode === "M129" &&
row.remapCode === "M429" &&
row.stateMode === "turn" &&
row.stateTargetCount === 12 &&
row.executionEnabled === false &&
row.promotionAllowed === false
);
}
export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact);
const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance);
@@ -712,6 +744,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
const evidenceExpansion = objectOrEmpty(artifactObject.virtualHalSimConfigEvidenceExpansion);
const promotionCandidateSummary = objectOrEmpty(artifactObject.virtualHalPromotionCandidateSummary);
const macroLoadFixtures = objectOrEmpty(artifactObject.virtualHalSimConfigMacroLoadFixtures);
const millturnUserMProcessBoundary = objectOrEmpty(artifactObject.virtualHalMillturnUserMProcessBoundary);
const sessionDiagnostics = objectOrEmpty(artifactObject.virtualHalSessionDiagnostics);
const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement);
const virtualHal = objectOrEmpty(artifactObject.virtualHal);
@@ -737,6 +770,9 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
const hasMacroLoadFixtures = artifactObject.virtualHalSimConfigMacroLoadFixtures !== undefined;
const macroLoadFixturesReady = hasMacroLoadFixtures &&
isVirtualHalSimConfigMacroLoadFixtureReportReady(macroLoadFixtures);
const hasMillturnUserMProcessBoundary = artifactObject.virtualHalMillturnUserMProcessBoundary !== undefined;
const millturnUserMProcessBoundaryReady = hasMillturnUserMProcessBoundary &&
isVirtualHalMillturnUserMProcessBoundaryReady(millturnUserMProcessBoundary);
const replacementReady = replacement.ready === true &&
replacement.replacesHostRuntimeForSimulation === true &&
objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true &&
@@ -750,6 +786,9 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
objectOrEmpty(sessionDiagnostics.validation).ready === true &&
objectOrEmpty(sessionDiagnostics.diagnosticsArtifact).apiName === "real-browser-simulation-diagnostics-artifact" &&
objectOrEmpty(sessionDiagnostics.diagnosticsArtifact?.virtualHalSourceCompliance).complete === true &&
isVirtualHalMillturnUserMProcessBoundaryReady(
objectOrEmpty(sessionDiagnostics.diagnosticsArtifact?.virtualHalMillturnUserMProcessBoundary),
) &&
objectOrEmpty(sessionDiagnostics.diagnosticsArtifact?.virtualHalMotionControllerMatrix).manifestChecked === true
);
const missing = [
@@ -762,6 +801,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
...(evidenceExpansionReady ? [] : ["virtualHalSimConfigEvidenceExpansion"]),
...(promotionCandidateSummaryReady ? [] : ["virtualHalPromotionCandidateSummary"]),
...(macroLoadFixturesReady ? [] : ["virtualHalSimConfigMacroLoadFixtures"]),
...(millturnUserMProcessBoundaryReady ? [] : ["virtualHalMillturnUserMProcessBoundary"]),
...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]),
...(motionControllerMatrixReady ? [] : ["virtualHalMotionControllerMatrix"]),
...(sessionDiagnosticsReady ? [] : ["virtualHalSessionDiagnostics"]),
@@ -785,6 +825,8 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
hasPromotionCandidateSummary,
macroLoadFixturesReady,
hasMacroLoadFixtures,
millturnUserMProcessBoundaryReady,
hasMillturnUserMProcessBoundary,
commandScriptFixturesReady,
motionControllerMatrixReady,
sessionDiagnosticsReady,
@@ -795,6 +837,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
...arrayOrEmpty(promotionCandidates.sourceFiles),
...arrayOrEmpty(evidenceExpansion.sourceFiles),
...arrayOrEmpty(macroLoadFixtures.sourceFiles),
...arrayOrEmpty(millturnUserMProcessBoundary.sourceFiles),
...arrayOrEmpty(commandScriptFixtures.sourceFiles),
...arrayOrEmpty(motionControllerMatrix.sourceFiles),
...arrayOrEmpty(motionControllerMatrix.simConfigSourceFiles),
@@ -840,6 +883,11 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
label: "Virtual HAL sim-config macro/load fixtures",
value: hasMacroLoadFixtures ? (macroLoadFixturesReady ? "ready" : "missing") : "not provided",
},
{
id: "millturn-user-m-process-boundary",
label: "Millturn user-M process boundary",
value: hasMillturnUserMProcessBoundary ? (millturnUserMProcessBoundaryReady ? "ready" : "missing") : "not provided",
},
{
id: "command-script-fixtures",
label: "Virtual HAL command script fixtures",