From d0e55ba11e839f03f36c17238ce4249e3ccde040 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sat, 20 Jun 2026 06:33:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E7=BB=AD=E4=B8=8A=E4=B8=80=E8=BD=AE?= =?UTF-8?q?=EF=BC=8C=E5=85=88=E5=81=9A=20L4-USER-M-PROCESS=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=85=A8=E6=8E=A5=E5=85=A5=E4=BB=BF=E7=9C=9F=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:已将 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 解锁。 --- wasm-port/runtime/opfs/snapshot-store.js | 9 + wasm-port/runtime/sdk/README.md | 16 +- wasm-port/runtime/sdk/src/index.js | 3 + wasm-port/runtime/sdk/src/linuxcnc-hal.js | 333 +++++++++++++++++- .../sdk/src/project-release-readiness.js | 48 +++ wasm-port/runtime/ui/simulation/index.html | 93 +++++ .../runtime/ui/simulation/simulation-app.js | 3 + .../ini_panel_workflow_overview_smoke.html | 11 + .../browser/real_simulation_page_smoke.html | 76 +++- ..._project_release_artifact_url_workflow.mjs | 5 + .../tests/sdk/node/verify_sdk_surface.mjs | 66 ++++ .../ui/node/verify_ini_panel_ui_shell.mjs | 20 +- 12 files changed, 676 insertions(+), 7 deletions(-) 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 () => ({