diff --git a/wasm-port/runtime/opfs/snapshot-store.js b/wasm-port/runtime/opfs/snapshot-store.js
index 0344247..01b83ed 100644
--- a/wasm-port/runtime/opfs/snapshot-store.js
+++ b/wasm-port/runtime/opfs/snapshot-store.js
@@ -12,8 +12,10 @@ import {
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
+ VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY,
cloneVirtualHalState,
createVirtualHalCommandScriptFixtureReport,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigEvidenceExpansionReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
@@ -74,6 +76,7 @@ function validateVirtualHalSessionPayload(virtualHal) {
assertPlainObject(virtualHal.sourceCompliance, "virtual HAL session source compliance");
assertPlainObject(virtualHal.simConfigSourceCoverage, "virtual HAL session sim-config source coverage");
assertPlainObject(virtualHal.simConfigPromotionCandidates, "virtual HAL session sim-config promotion candidates");
+ assertPlainObject(virtualHal.millturnUserMProcessBoundary, "virtual HAL session millturn user-M process boundary");
assertPlainObject(virtualHal.simConfigMacroLoadFixtures, "virtual HAL session sim-config macro/load fixtures");
assertPlainObject(virtualHal.commandScriptFixtures, "virtual HAL session command script fixtures");
assertPlainObject(virtualHal.motionControllerMatrix, "virtual HAL session motion controller matrix");
@@ -89,6 +92,9 @@ function validateVirtualHalSessionPayload(virtualHal) {
if (virtualHal.simConfigPromotionCandidates.complete !== true) {
throw new Error("virtual HAL sim-config promotion candidates are incomplete.");
}
+ if (virtualHal.millturnUserMProcessBoundary.complete !== true) {
+ throw new Error("virtual HAL millturn user-M process boundary is incomplete.");
+ }
if (virtualHal.simConfigEvidenceExpansion.complete !== true) {
throw new Error("virtual HAL sim-config evidence expansion candidates are incomplete.");
}
@@ -116,6 +122,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
...VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES.flatMap((candidate) => candidate.sourceFiles ?? []),
...VIRTUAL_HAL_SIM_CONFIG_EVIDENCE_EXPANSION_CANDIDATES.flatMap((candidate) => candidate.sourceFiles ?? []),
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.flatMap((fixture) => fixture.sourceFiles ?? []),
+ ...VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
],
}
: {}
@@ -137,6 +144,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
commandScriptFixtures,
motionControllerMatrix,
});
+ const millturnUserMProcessBoundary = options.millturnUserMProcessBoundary ?? createVirtualHalMillturnUserMProcessBoundaryReport(reportOptions);
const sourceCompliance = options.sourceCompliance ?? createVirtualHalSourceComplianceReport({
...reportOptions,
halState: state,
@@ -161,6 +169,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
simConfigSourceCoverage,
simConfigPromotionCandidates,
simConfigEvidenceExpansion,
+ millturnUserMProcessBoundary,
simConfigMacroLoadFixtures,
commandScriptFixtures,
motionControllerMatrix,
diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md
index c3c6593..64cea05 100644
--- a/wasm-port/runtime/sdk/README.md
+++ b/wasm-port/runtime/sdk/README.md
@@ -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
diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js
index ebc1c39..bf2c68b 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_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,
diff --git a/wasm-port/runtime/sdk/src/linuxcnc-hal.js b/wasm-port/runtime/sdk/src/linuxcnc-hal.js
index 51efbdc..77832d1 100644
--- a/wasm-port/runtime/sdk/src/linuxcnc-hal.js
+++ b/wasm-port/runtime/sdk/src/linuxcnc-hal.js
@@ -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";
}
diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js
index f54a4f6..795907f 100644
--- a/wasm-port/runtime/sdk/src/project-release-readiness.js
+++ b/wasm-port/runtime/sdk/src/project-release-readiness.js
@@ -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",
diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html
index 98fd681..1d36fdb 100644
--- a/wasm-port/runtime/ui/simulation/index.html
+++ b/wasm-port/runtime/ui/simulation/index.html
@@ -1732,6 +1732,12 @@ M2
contract-only | callable locked | generator locked | state locked
Python Promotion
execution disabled | promotion locked
+ User-M Process
+ L4-USER-M-PROCESS | axis/vismach/millturn/example.ngc | ready
+ Millturn Mode
+ none | remap none | mode none | switchkins none
+ User-M Promotion
+ execution disabled | promotion locked
Limits/Home
source unavailable | X home no limit off/off fault none
Overrides
@@ -1851,7 +1857,9 @@ M2
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES as SHARED_VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES as SHARED_VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS as SHARED_VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
+ VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY as SHARED_VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY,
applyVirtualHalAction as applySharedVirtualHalAction,
+ applyVirtualHalMillturnUserMProcessState as applySharedVirtualHalMillturnUserMProcessState,
applyVirtualHalPinUpdates as applySharedVirtualHalPinUpdates,
cloneVirtualHalState as cloneSharedVirtualHalState,
createVirtualHalCommandScriptFixtureReport as createSharedVirtualHalCommandScriptFixtureReport,
@@ -1859,6 +1867,7 @@ M2
createVirtualHalIntegrityReport as createSharedVirtualHalIntegrityReport,
createVirtualHalLimitsHomeState as createSharedVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState as createSharedVirtualHalMachineStatusState,
+ createVirtualHalMillturnUserMProcessBoundaryReport as createSharedVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport as createSharedVirtualHalMotionControllerMatrixReport,
createVirtualHalPinRegistry as createSharedVirtualHalPinRegistry,
createVirtualHalSimConfigEvidenceExpansionReport as createSharedVirtualHalSimConfigEvidenceExpansionReport,
@@ -2036,7 +2045,22 @@ M2
const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = SHARED_VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES;
const VIRTUAL_HAL_SIM_CONFIG_EVIDENCE_EXPANSION_CANDIDATES = SHARED_VIRTUAL_HAL_SIM_CONFIG_EVIDENCE_EXPANSION_CANDIDATES;
const VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES = SHARED_VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []);
+ const VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY = SHARED_VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY;
let virtualHalState = createVirtualHalState();
+ let millturnUserMProcessState = {
+ apiName: "real-browser-simulation-millturn-user-m-process-state",
+ stateVersion: 1,
+ phase: "ready",
+ boundaryClass: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass,
+ path: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.path,
+ activeUserMCode: "none",
+ activeRemapCode: "none",
+ activeMode: "none",
+ switchkinsTarget: "none",
+ executionEnabled: false,
+ promotionAllowed: false,
+ processExecutionReady: false,
+ };
function setAllText(selector, value) {
document.querySelectorAll(selector).forEach((node) => {
@@ -2562,6 +2586,67 @@ M2
return createSharedVirtualHalSimulationReplacementReport(virtualHalState);
}
+ function createVirtualHalMillturnUserMProcessBoundaryReport() {
+ return createSharedVirtualHalMillturnUserMProcessBoundaryReport({
+ manifestEntries: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
+ });
+ }
+
+ function renderMillturnUserMProcessState(state = millturnUserMProcessState) {
+ const nextState = {
+ apiName: "real-browser-simulation-millturn-user-m-process-state",
+ stateVersion: 1,
+ phase: state.phase ?? "ready",
+ boundaryClass: state.boundaryClass ?? VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass,
+ path: state.path ?? VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.path,
+ activeUserMCode: state.activeUserMCode ?? "none",
+ activeRemapCode: state.activeRemapCode ?? "none",
+ activeMode: state.activeMode ?? "none",
+ switchkinsTarget: state.switchkinsTarget ?? "none",
+ executionEnabled: state.executionEnabled === true,
+ promotionAllowed: state.promotionAllowed === true,
+ processExecutionReady: state.processExecutionReady === true,
+ };
+ millturnUserMProcessState = nextState;
+ document.body.dataset.userMProcessPhase = nextState.phase;
+ document.body.dataset.userMProcessActiveCode = nextState.activeUserMCode;
+ document.body.dataset.userMProcessActiveMode = nextState.activeMode;
+ setAllText("[data-user-m-process=\"boundary\"]", nextState.boundaryClass);
+ setAllText("[data-user-m-process=\"path\"]", nextState.path);
+ setAllText("[data-user-m-process=\"phase\"]", nextState.phase);
+ setAllText("[data-user-m-process=\"active-code\"]", nextState.activeUserMCode);
+ setAllText("[data-user-m-process=\"active-remap\"]", nextState.activeRemapCode);
+ setAllText("[data-user-m-process=\"active-mode\"]", nextState.activeMode);
+ setAllText("[data-user-m-process=\"switchkins\"]", String(nextState.switchkinsTarget));
+ setAllText("[data-user-m-process=\"execution\"]", nextState.executionEnabled ? "enabled" : "disabled");
+ setAllText("[data-user-m-process=\"promotion\"]", nextState.promotionAllowed ? "allowed" : "locked");
+ return { ...nextState };
+ }
+
+ function applyMillturnUserMProcessState(codeOrOptions = "M128", options = {}) {
+ const result = applySharedVirtualHalMillturnUserMProcessState(virtualHalState, codeOrOptions, {
+ manifestEntries: VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
+ ...options,
+ });
+ virtualHalState = result.state;
+ renderVirtualHalState();
+ renderMillturnUserMProcessState({
+ phase: "applied",
+ boundaryClass: result.boundaryClass,
+ path: result.path,
+ activeUserMCode: result.userMCode,
+ activeRemapCode: result.remapCode,
+ activeMode: result.stateMode,
+ switchkinsTarget: result.switchkinsTarget,
+ executionEnabled: result.executionEnabled,
+ promotionAllowed: result.promotionAllowed,
+ processExecutionReady: result.processExecutionReady,
+ });
+ pushStatusHistory("user-m-process", `${result.remapCode}->${result.userMCode} ${result.stateMode}`);
+ renderAxisStatusbar();
+ return result;
+ }
+
function createVirtualHalSourceComplianceReport() {
return createSharedVirtualHalSourceComplianceReport({ halState: virtualHalState });
}
@@ -2741,6 +2826,7 @@ M2
renderVirtualHalDro();
}
renderLimitsHomeState(createVirtualHalLimitsHomeState());
+ renderMillturnUserMProcessState();
const promotionCandidateReport = createVirtualHalSimConfigPromotionCandidateReport();
renderPromotionFamilyRows(promotionCandidateReport);
renderPromotionCandidateSummary(createVirtualHalPromotionCandidateSummary(promotionCandidateReport));
@@ -2793,6 +2879,8 @@ M2
virtualHal: cloneVirtualHalState(),
virtualHalSimulationReplacement: createVirtualHalSimulationReplacementReport(),
virtualHalSourceCompliance: createVirtualHalSourceComplianceReport(),
+ virtualHalMillturnUserMProcessBoundary: createVirtualHalMillturnUserMProcessBoundaryReport(),
+ virtualHalMillturnUserMProcessState: renderMillturnUserMProcessState(),
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalSimConfigPromotionCandidates: createVirtualHalSimConfigPromotionCandidateReport(),
virtualHalSimConfigEvidenceExpansion: createVirtualHalSimConfigEvidenceExpansionReport(),
@@ -2833,6 +2921,7 @@ M2
virtualHal: virtualHal.state,
virtualHalSimulationReplacement: virtualHal.diagnostics,
virtualHalSourceCompliance: virtualHal.sourceCompliance,
+ virtualHalMillturnUserMProcessBoundary: virtualHal.millturnUserMProcessBoundary,
virtualHalSimConfigSourceCoverage: virtualHal.simConfigSourceCoverage,
virtualHalSimConfigPromotionCandidates: virtualHal.simConfigPromotionCandidates,
virtualHalSimConfigEvidenceExpansion: virtualHal.simConfigEvidenceExpansion,
@@ -3773,6 +3862,7 @@ M2
...VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES.flatMap((candidate) => candidate.sourceFiles ?? []),
...VIRTUAL_HAL_SIM_CONFIG_EVIDENCE_EXPANSION_CANDIDATES.flatMap((candidate) => candidate.sourceFiles ?? []),
...VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_FIXTURES.flatMap((fixture) => fixture.sourceFiles ?? []),
+ ...VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.sourceFiles,
],
});
const payload = createMachineSessionSnapshotPayload(session.machineId, {
@@ -4236,6 +4326,9 @@ M2
stepVirtualHalMotionController: (options = {}) => stepVirtualHalMotionController(options),
getVirtualHalSimulationRuntimeReport: () => createVirtualHalSimulationRuntimeReport(),
getVirtualHalSimulationReplacementReport: () => createVirtualHalSimulationReplacementReport(),
+ getVirtualHalMillturnUserMProcessBoundaryReport: () => createVirtualHalMillturnUserMProcessBoundaryReport(),
+ getMillturnUserMProcessState: () => renderMillturnUserMProcessState(),
+ applyMillturnUserMProcessState: (codeOrOptions = "M128", options = {}) => applyMillturnUserMProcessState(codeOrOptions, options),
getVirtualHalSourceComplianceReport: () => createVirtualHalSourceComplianceReport(),
getVirtualHalSimConfigSourceCoverageReport: () => createVirtualHalSimConfigSourceCoverageReport(),
getVirtualHalSimConfigPromotionCandidateReport: () => createVirtualHalSimConfigPromotionCandidateReport(),
diff --git a/wasm-port/runtime/ui/simulation/simulation-app.js b/wasm-port/runtime/ui/simulation/simulation-app.js
index afcb44b..ad78e12 100644
--- a/wasm-port/runtime/ui/simulation/simulation-app.js
+++ b/wasm-port/runtime/ui/simulation/simulation-app.js
@@ -22,6 +22,7 @@ export {
export {
VIRTUAL_HAL_AXES,
VIRTUAL_HAL_AXISUI_PINS,
+ VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_BLOCKED_FIXTURES,
@@ -32,6 +33,7 @@ export {
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
applyVirtualHalAction,
+ applyVirtualHalMillturnUserMProcessState,
applyVirtualHalPinUpdates,
cloneVirtualHalState,
createLinuxCncVirtualHalRuntime,
@@ -41,6 +43,7 @@ export {
createVirtualHalIntegrityReport,
createVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalPinRegistry,
createVirtualHalSimConfigEvidenceExpansionReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
diff --git a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html
index 052c5ce..e9a5923 100644
--- a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html
+++ b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html
@@ -10,6 +10,7 @@
import {
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
createVirtualHalCommandScriptFixtureReport,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
createVirtualHalSimConfigEvidenceExpansionReport,
@@ -433,6 +434,16 @@
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
],
}),
+ virtualHalMillturnUserMProcessBoundary: createVirtualHalMillturnUserMProcessBoundaryReport({
+ manifestEntries: [
+ "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",
+ ],
+ }),
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: diagnosticsVirtualHalState }),
virtualHalMotionControllerMatrix: diagnosticsMotionControllerMatrix,
};
diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html
index 1a06ce3..b76b208 100644
--- a/wasm-port/tests/browser/real_simulation_page_smoke.html
+++ b/wasm-port/tests/browser/real_simulation_page_smoke.html
@@ -241,6 +241,9 @@
!api?.stepVirtualHalMotionController ||
!api?.getVirtualHalSimulationRuntimeReport ||
!api?.getVirtualHalSimulationReplacementReport ||
+ !api?.getVirtualHalMillturnUserMProcessBoundaryReport ||
+ !api?.getMillturnUserMProcessState ||
+ !api?.applyMillturnUserMProcessState ||
!api?.getVirtualHalSourceComplianceReport ||
!api?.getVirtualHalSimConfigSourceCoverageReport ||
!api?.getVirtualHalSimConfigPromotionCandidateReport ||
@@ -442,7 +445,66 @@
) {
throw new Error(`simulation virtual HAL registry/integrity drift: ${JSON.stringify(initialVirtualHalRegistry)}`);
}
- const diagnosticsArtifact = api.exportDiagnosticsArtifact();
+ let diagnosticsArtifact = api.exportDiagnosticsArtifact();
+ const millturnUserMBoundary = api.getVirtualHalMillturnUserMProcessBoundaryReport();
+ if (
+ millturnUserMBoundary.apiName !== "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report" ||
+ millturnUserMBoundary.boundaryClass !== "L4-USER-M-PROCESS" ||
+ millturnUserMBoundary.complete !== true ||
+ millturnUserMBoundary.webSimulationSatisfied !== true ||
+ millturnUserMBoundary.executionEnabled !== false ||
+ millturnUserMBoundary.promotionAllowed !== false ||
+ millturnUserMBoundary.processExecutionReady !== false ||
+ millturnUserMBoundary.rows.length !== 2 ||
+ !millturnUserMBoundary.rows.some(({ userMCode, remapCode, stateTargetCount }) => userMCode === "M128" && remapCode === "M428" && stateTargetCount === 12) ||
+ !millturnUserMBoundary.rows.some(({ userMCode, remapCode, stateTargetCount }) => userMCode === "M129" && remapCode === "M429" && stateTargetCount === 12) ||
+ doc.body.dataset.userMProcessPhase !== "ready" ||
+ doc.querySelector('[data-user-m-process="boundary"]')?.textContent !== "L4-USER-M-PROCESS" ||
+ doc.querySelector('[data-user-m-process="path"]')?.textContent !== "axis/vismach/millturn/example.ngc" ||
+ doc.querySelector('[data-user-m-process="execution"]')?.textContent !== "disabled" ||
+ doc.querySelector('[data-user-m-process="promotion"]')?.textContent !== "locked"
+ ) {
+ throw new Error(`simulation millturn user-M boundary drift: ${JSON.stringify(millturnUserMBoundary)}`);
+ }
+ const millturnM128 = api.applyMillturnUserMProcessState("M128");
+ if (
+ millturnM128.apiName !== "linuxcnc-wasm-virtual-hal-millturn-user-m-process-state-result" ||
+ millturnM128.userMCode !== "M128" ||
+ millturnM128.remapCode !== "M428" ||
+ millturnM128.stateMode !== "mill" ||
+ api.readVirtualHalPin("motion.switchkins-type")?.value !== 0 ||
+ api.readVirtualHalPin("ini.x.min_limit")?.value !== -300 ||
+ api.readVirtualHalPin("ini.z.max_limit")?.value !== 0 ||
+ doc.body.dataset.userMProcessPhase !== "applied" ||
+ doc.body.dataset.userMProcessActiveCode !== "M128" ||
+ doc.body.dataset.userMProcessActiveMode !== "mill" ||
+ doc.querySelector('[data-user-m-process="active-code"]')?.textContent !== "M128" ||
+ doc.querySelector('[data-user-m-process="active-remap"]')?.textContent !== "M428" ||
+ doc.querySelector('[data-user-m-process="active-mode"]')?.textContent !== "mill" ||
+ doc.querySelector('[data-user-m-process="switchkins"]')?.textContent !== "0"
+ ) {
+ throw new Error(`simulation M128 user-M state did not apply: ${JSON.stringify(millturnM128)}`);
+ }
+ const millturnM129 = api.applyMillturnUserMProcessState("M129");
+ if (
+ millturnM129.userMCode !== "M129" ||
+ millturnM129.remapCode !== "M429" ||
+ millturnM129.stateMode !== "turn" ||
+ api.readVirtualHalPin("motion.switchkins-type")?.value !== 1 ||
+ api.readVirtualHalPin("motion.analog-out-03")?.value !== 1 ||
+ api.readVirtualHalPin("kinstype.is-0")?.value !== 0 ||
+ api.readVirtualHalPin("kinstype.is-1")?.value !== 1 ||
+ api.readVirtualHalPin("ini.x.max_limit")?.value !== 0 ||
+ api.readVirtualHalPin("ini.z.max_limit")?.value !== 300 ||
+ api.getMillturnUserMProcessState().activeMode !== "turn" ||
+ doc.querySelector('[data-user-m-process="active-code"]')?.textContent !== "M129" ||
+ doc.querySelector('[data-user-m-process="active-remap"]')?.textContent !== "M429" ||
+ doc.querySelector('[data-user-m-process="active-mode"]')?.textContent !== "turn" ||
+ doc.querySelector('[data-user-m-process="switchkins"]')?.textContent !== "1"
+ ) {
+ throw new Error(`simulation M129 user-M state did not apply: ${JSON.stringify(millturnM129)}`);
+ }
+ diagnosticsArtifact = api.exportDiagnosticsArtifact();
const promotionCandidateSummary = api.getVirtualHalPromotionCandidateSummary();
const expectedPromotionCandidates = [
[
@@ -542,7 +604,14 @@
diagnosticsArtifact.virtualHalSimulationReplacement?.sourceCompliance?.webSimulationSatisfied !== true ||
diagnosticsArtifact.virtualHalSourceCompliance?.complete !== true ||
diagnosticsArtifact.virtualHalSourceCompliance?.webSimulationSatisfied !== true ||
+ !diagnosticsArtifact.virtualHalSourceCompliance?.sourceFiles?.includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128") ||
!diagnosticsArtifact.virtualHalSourceCompliance?.sourceFiles?.includes("linuxcnc/src/hal/utils/halcmd_commands.cc") ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessBoundary?.complete !== true ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessBoundary?.boundaryClass !== "L4-USER-M-PROCESS" ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessState?.activeUserMCode !== "M129" ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessState?.activeMode !== "turn" ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessState?.executionEnabled !== false ||
+ diagnosticsArtifact.virtualHalMillturnUserMProcessState?.promotionAllowed !== false ||
diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.complete !== true ||
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") ||
!diagnosticsArtifact.virtualHalSimConfigSourceCoverage?.sourceFiles?.includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz45/qtdragon_xyza.ini") ||
@@ -976,6 +1045,8 @@
savedVirtualHalSession.sessionDiagnostics?.ready !== true ||
savedVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
savedVirtualHalSession.sessionDiagnostics?.validation?.motionControllerMatrixReady !== true ||
+ savedVirtualHalSession.snapshot?.payload?.virtualHal?.millturnUserMProcessBoundary?.complete !== true ||
+ savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMillturnUserMProcessBoundary?.complete !== true ||
savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMotionControllerMatrix?.complete !== true ||
doc.body.dataset.virtualHalSessionPhase !== "saved" ||
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Saved HAL session")
@@ -991,7 +1062,8 @@
savedSessionDiagnosticsArtifact.validation?.ready !== true ||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.ready !== true ||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.validation?.ready !== true ||
- diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalSourceCompliance?.complete !== true
+ diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalSourceCompliance?.complete !== true ||
+ diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalMillturnUserMProcessBoundary?.complete !== true
) {
throw new Error(`AXIS-style virtual HAL session diagnostics did not become release-ready: ${JSON.stringify(diagnosticsWithSavedSession.virtualHalSessionDiagnostics)}`);
}
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 2f7e87a..8e8dd6e 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
@@ -13,6 +13,7 @@ import {
createVirtualHalSimConfigPromotionCandidateReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
createVirtualHalCommandScriptFixtureReport,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigEvidenceExpansionReport,
createVirtualHalSimulationReplacementReport,
@@ -57,6 +58,9 @@ const virtualHalSimConfigEvidenceExpansion = createVirtualHalSimConfigEvidenceEx
const virtualHalSimConfigMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixtureReport({
manifestText: sourceManifestText,
});
+const virtualHalMillturnUserMProcessBoundary = createVirtualHalMillturnUserMProcessBoundaryReport({
+ manifestText: sourceManifestText,
+});
const virtualHalState = createVirtualHalState();
const pythonRemapRuntimeProof = {
nativeLifecycleReady: true,
@@ -97,6 +101,7 @@ const browserDiagnosticsArtifact = {
inventoryBaseline: "executed=82 passed=82 skipped=77 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures,
+ virtualHalMillturnUserMProcessBoundary,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: virtualHalState }),
virtualHalMotionControllerMatrix,
};
diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs
index 61b5fed..e34fe50 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_MILLTURN_USER_M_PROCESS_BOUNDARY,
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_MACRO_LOAD_AUDIT_CANDIDATES,
@@ -51,6 +52,7 @@ import {
VIRTUAL_HAL_SYSTEM_PIN_FAMILIES,
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
applyVirtualHalAction,
+ applyVirtualHalMillturnUserMProcessState,
applyVirtualHalPinUpdates,
applyVirtualHalToInterpSdk,
createVirtualHalSimulationRuntimeReport,
@@ -58,6 +60,7 @@ import {
createLinuxCncInterpSdk,
createLinuxCncVirtualHalRuntime,
createVirtualHalIntegrityReport,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport,
createMachineSessionSnapshotPayload,
createVirtualHalSessionPayload,
@@ -356,6 +359,7 @@ const requiredExports = [
["createProjectReleaseReadinessReport", createProjectReleaseReadinessReport],
["createProjectReleaseReadinessSummaryViewModel", createProjectReleaseReadinessSummaryViewModel],
["applyVirtualHalAction", applyVirtualHalAction],
+ ["applyVirtualHalMillturnUserMProcessState", applyVirtualHalMillturnUserMProcessState],
["applyVirtualHalPinUpdates", applyVirtualHalPinUpdates],
["applyVirtualHalToInterpSdk", applyVirtualHalToInterpSdk],
["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime],
@@ -363,6 +367,7 @@ const requiredExports = [
["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness],
["createVirtualHalCommandScriptFixtureReport", createVirtualHalCommandScriptFixtureReport],
["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport],
+ ["createVirtualHalMillturnUserMProcessBoundaryReport", createVirtualHalMillturnUserMProcessBoundaryReport],
["createVirtualHalMotionControllerMatrixReport", createVirtualHalMotionControllerMatrixReport],
["createVirtualHalPinInventory", createVirtualHalPinInventory],
["createVirtualHalPinRegistry", createVirtualHalPinRegistry],
@@ -648,6 +653,11 @@ assert.equal(VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS
.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/M111"), true);
assert.equal(VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES["halcmd-simulation-replacement"].sourceFiles.includes("linuxcnc/src/hal/utils/halcmd_commands.cc"), true);
assert.equal(VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES["motion-controller-simulation-replacement"].sourceFiles.includes("linuxcnc/src/emc/motion/motion.c"), true);
+assert.equal(VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES["controlled-user-m-process-state-boundary"].sourceFiles.includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128"), true);
+assert.equal(VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.boundaryClass, "L4-USER-M-PROCESS");
+assert.equal(VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.cases.length, 2);
+assert.equal(VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.cases.some(({ userMCode }) => userMCode === "M128"), true);
+assert.equal(VIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY.cases.some(({ userMCode }) => userMCode === "M129"), true);
assert.equal(VIRTUAL_HAL_COVERAGE_STATES.runtimeBoundary, "runtime-boundary");
assert.equal(VIRTUAL_HAL_SOURCE_FILES.halui, "linuxcnc/src/emc/usr_intf/halui.cc");
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "iocontrol"), true);
@@ -656,6 +666,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_MILLTURN_USER_M_PROCESS_BOUNDARY\b/);
+assert.match(sdkReadmeText, /\bVIRTUAL_HAL_MILLTURN_USER_M_PROCESS_BOUNDARY\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/);
@@ -672,6 +684,10 @@ assert.match(sdkIndexText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
assert.match(sdkIndexText, /\bexecuteVirtualHalCommand\b/);
assert.match(sdkReadmeText, /\bexecuteVirtualHalCommand\b/);
+assert.match(sdkIndexText, /\bapplyVirtualHalMillturnUserMProcessState\b/);
+assert.match(sdkReadmeText, /\bapplyVirtualHalMillturnUserMProcessState\b/);
+assert.match(sdkIndexText, /\bcreateVirtualHalMillturnUserMProcessBoundaryReport\b/);
+assert.match(sdkReadmeText, /\bcreateVirtualHalMillturnUserMProcessBoundaryReport\b/);
assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalCommandScriptFixtureReport\b/);
@@ -781,6 +797,38 @@ assert.equal(virtualHalReport.pinRegistry.byName["spindle.0.speed-out"].value, 1
assert.equal(virtualHalReport.integrity.complete, true);
const virtualHalRuntime = createLinuxCncVirtualHalRuntime(sdkVirtualHal);
assert.equal(virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }).ready, true);
+const millturnUserMBoundaryReport = createVirtualHalMillturnUserMProcessBoundaryReport({ manifestText: sourceManifestText });
+assert.equal(millturnUserMBoundaryReport.apiName, "linuxcnc-wasm-virtual-hal-millturn-user-m-process-boundary-report");
+assert.equal(millturnUserMBoundaryReport.boundaryClass, "L4-USER-M-PROCESS");
+assert.equal(millturnUserMBoundaryReport.complete, true);
+assert.equal(millturnUserMBoundaryReport.webSimulationSatisfied, true);
+assert.equal(millturnUserMBoundaryReport.executionEnabled, false);
+assert.equal(millturnUserMBoundaryReport.promotionAllowed, false);
+assert.equal(millturnUserMBoundaryReport.processExecutionReady, false);
+assert.equal(millturnUserMBoundaryReport.inventoryBaselineUnchanged, true);
+assert.equal(millturnUserMBoundaryReport.missingManifestFiles.length, 0);
+assert.equal(millturnUserMBoundaryReport.rows.some(({ userMCode, remapCode }) => userMCode === "M128" && remapCode === "M428"), true);
+assert.equal(millturnUserMBoundaryReport.rows.some(({ userMCode, remapCode }) => userMCode === "M129" && remapCode === "M429"), true);
+assert.equal(millturnUserMBoundaryReport.rows.every(({ stateTargetCount }) => stateTargetCount === 12), true);
+assert.equal(millturnUserMBoundaryReport.rows.every(({ executionEnabled, promotionAllowed }) => executionEnabled === false && promotionAllowed === false), true);
+let millturnStateResult = applyVirtualHalMillturnUserMProcessState(virtualHalRuntime.getState(), "M128", { manifestText: sourceManifestText });
+assert.equal(millturnStateResult.apiName, "linuxcnc-wasm-virtual-hal-millturn-user-m-process-state-result");
+assert.equal(millturnStateResult.ok, true);
+assert.equal(millturnStateResult.userMCode, "M128");
+assert.equal(millturnStateResult.executionEnabled, false);
+assert.equal(millturnStateResult.promotionAllowed, false);
+assert.equal(readVirtualHalPin(millturnStateResult.state, "motion.switchkins-type").value, 0);
+assert.equal(readVirtualHalPin(millturnStateResult.state, "ini.x.min_limit").value, -300);
+assert.equal(readVirtualHalPin(millturnStateResult.state, "ini.z.max_limit").value, 0);
+millturnStateResult = virtualHalRuntime.applyMillturnUserMProcessState("M129", { manifestText: sourceManifestText });
+assert.equal(millturnStateResult.userMCode, "M129");
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "motion.switchkins-type").value, 1);
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "motion.analog-out-03").value, 1);
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "kinstype.is-0").value, 0);
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "kinstype.is-1").value, 1);
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "ini.x.max_limit").value, 0);
+assert.equal(readVirtualHalPin(virtualHalRuntime.getState(), "ini.z.max_limit").value, 300);
+assert.equal(virtualHalRuntime.getMillturnUserMProcessBoundaryReport({ manifestText: sourceManifestText }).complete, true);
const virtualHalcmdResult = virtualHalRuntime.executeHalCommand(`
loadrt trivkins
addf motion-controller servo-thread
@@ -1365,6 +1413,8 @@ assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true);
assert.equal(virtualHalSessionPayload.simConfigPromotionCandidates.complete, true);
assert.equal(virtualHalSessionPayload.simConfigEvidenceExpansion.complete, true);
assert.equal(virtualHalSessionPayload.simConfigMacroLoadFixtures.complete, true);
+assert.equal(virtualHalSessionPayload.millturnUserMProcessBoundary.complete, true);
+assert.equal(virtualHalSessionPayload.millturnUserMProcessBoundary.boundaryClass, "L4-USER-M-PROCESS");
assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true);
assert.equal(virtualHalSessionPayload.motionControllerMatrix.complete, true);
const virtualHalSessionSnapshot = createSessionSnapshot(
@@ -1404,6 +1454,7 @@ const browserDiagnosticsArtifact = {
inventoryBaseline: "executed=82 passed=82 skipped=77 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: simConfigMacroLoadFixtureReport,
+ virtualHalMillturnUserMProcessBoundary: millturnUserMBoundaryReport,
virtualHalCommandScriptFixtures: commandScriptFixtureReport,
virtualHalMotionControllerMatrix: manifestCheckedMotionControllerMatrixReport,
};
@@ -1435,6 +1486,8 @@ assert.equal(browserDiagnosticsArtifactValidation.promotionCandidateSummaryReady
assert.equal(browserDiagnosticsArtifactValidation.hasPromotionCandidateSummary, true);
assert.equal(browserDiagnosticsArtifactValidation.macroLoadFixturesReady, true);
assert.equal(browserDiagnosticsArtifactValidation.hasMacroLoadFixtures, true);
+assert.equal(browserDiagnosticsArtifactValidation.millturnUserMProcessBoundaryReady, true);
+assert.equal(browserDiagnosticsArtifactValidation.hasMillturnUserMProcessBoundary, true);
assert.equal(browserDiagnosticsArtifactValidation.commandScriptFixturesReady, true);
assert.equal(browserDiagnosticsArtifactValidation.motionControllerMatrixReady, true);
assert.equal(browserDiagnosticsArtifactValidation.hasSessionDiagnostics, true);
@@ -1453,6 +1506,19 @@ assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/rose_engine/rcone.ngc"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini"), true);
assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc"), true);
+assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M128"), true);
+assert.equal(browserDiagnosticsArtifactValidation.sourceFiles.includes("linuxcnc/configs/sim/axis/vismach/millturn/mcodes/M129"), true);
+assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
+ ...browserDiagnosticsArtifact,
+ virtualHalMillturnUserMProcessBoundary: undefined,
+}).missing.includes("virtualHalMillturnUserMProcessBoundary"), true);
+assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
+ ...browserDiagnosticsArtifact,
+ virtualHalMillturnUserMProcessBoundary: {
+ ...millturnUserMBoundaryReport,
+ promotionAllowed: true,
+ },
+}).millturnUserMProcessBoundaryReady, false);
assert.equal(createProjectReleaseBrowserDiagnosticsArtifactValidation({
...browserDiagnosticsArtifact,
virtualHalPromotionCandidateSummary: undefined,
diff --git a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs
index 9f9cdff..611de44 100644
--- a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs
+++ b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs
@@ -11,6 +11,7 @@ import {
import {
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
createVirtualHalCommandScriptFixtureReport,
+ createVirtualHalMillturnUserMProcessBoundaryReport,
createVirtualHalMotionControllerMatrixReport,
createVirtualHalSimConfigMacroLoadFixtureReport,
createVirtualHalSimConfigEvidenceExpansionReport,
@@ -1473,6 +1474,16 @@ const releaseReadinessMacroLoadFixtures = createVirtualHalSimConfigMacroLoadFixt
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
],
});
+const releaseReadinessMillturnUserMProcessBoundary = createVirtualHalMillturnUserMProcessBoundaryReport({
+ manifestEntries: [
+ "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",
+ ],
+});
const pythonRemapRuntimeProof = {
nativeLifecycleReady: true,
nativeProbeStatus: "runtime_lifecycle_probe_passed",
@@ -1524,6 +1535,7 @@ const browserDiagnosticsArtifact = {
inventoryBaseline: "executed=82 passed=82 skipped=77 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
+ virtualHalMillturnUserMProcessBoundary: releaseReadinessMillturnUserMProcessBoundary,
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport({ halState: browserDiagnosticsVirtualHalState }),
virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix,
};
@@ -1957,6 +1969,8 @@ assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.hasPromotionCandid
assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.promotionCandidatesReady, true);
assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.hasMacroLoadFixtures, true);
assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.macroLoadFixturesReady, true);
+assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.hasMillturnUserMProcessBoundary, true);
+assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.millturnUserMProcessBoundaryReady, true);
assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.hasSessionDiagnostics, true);
assert.equal(releaseArtifactUrlWorkflow.diagnosticsValidation.sessionDiagnosticsReady, true);
assert.equal(
@@ -2111,7 +2125,7 @@ assert.equal(
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value,
- "L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test2.ngc",
+ "L4-USER-M-PROCESS:axis/vismach/millturn/example.ngc",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
@@ -2841,7 +2855,7 @@ const releaseArtifactUrlWorkflowMountResult =
rowsNode: releaseArtifactUrlWorkflowRowsNode,
});
assert.equal(releaseArtifactUrlWorkflowRenderResult.rendered, true);
-assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 57);
+assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 53);
assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("browser-diagnostics"),
true,
@@ -2891,7 +2905,7 @@ assert.equal(
true,
);
assert.equal(releaseArtifactUrlWorkflowMountResult.ready, true);
-assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 57);
+assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 53);
const releaseArtifactUrlWorkflowMissingDom = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({