Files
cnc_wams/wasm-port/tests/browser/interp_smoke.html

9795 lines
374 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Interpreter Browser Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
import {
analyzeIniRuntimeBoundaries,
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
planIniFileContextStaging,
planSimConfigStaging,
} from "../../runtime/sdk/src/index.js";
import {
saveMachineParametersToOpfs,
} from "../../runtime/opfs/linuxcnc-parameter-bridge.js";
import {
loadTextFile,
saveTextFile,
} from "../../runtime/opfs/file-service.js";
import {
saveMachineToolTableToOpfs,
} from "../../runtime/opfs/linuxcnc-tool-table-bridge.js";
import {
loadMachineSessionFromOpfs,
} from "../../runtime/opfs/linuxcnc-machine-session-bridge.js";
import {
loadMachineTextFiles,
saveMachineTextFiles,
} from "../../runtime/opfs/machine-file-store.js";
import {
INTERP_BASELINE_INI_FIXTURE,
INTERP_BROWSER_FILE_FIXTURES,
INTERP_BROWSER_MDI_FIXTURES,
INTERP_COORDINATE_OFFSETS_FILE_FIXTURE,
INTERP_ERROR_FIXTURES,
INTERP_INI_FIXTURE,
INTERP_POSITION_PARAMS_FILE_FIXTURE,
INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE,
} from "../fixtures/interp-fixture-matrix.mjs";
const status = document.getElementById("status");
async function fetchText(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`${path}: HTTP ${response.status}`);
}
return response.text();
}
function verifyExpectedOutput(fixtureName, output, expectedText) {
const expectedLines = expectedText.split("\n").filter(Boolean);
for (const expectedLine of expectedLines) {
if (expectedLine.startsWith("absent=")) {
const forbidden = expectedLine.slice("absent=".length);
if (output.includes(forbidden)) {
throw new Error(`${fixtureName}: unexpected ${forbidden}`);
}
continue;
}
if (!output.includes(expectedLine)) {
throw new Error(`${fixtureName}: missing ${expectedLine}`);
}
}
}
function parseTsv(text) {
const [headerLine, ...lines] = text.trim().split("\n");
const headers = headerLine.split("\t");
return lines.filter(Boolean).map((line) => {
const values = line.split("\t");
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
});
}
async function verifyBrowserGeneratedArtifactDocumentationCoverage() {
const documentationText = [
await fetchText("../../docs/compatibility-validation.md"),
await fetchText("../../docs/sim-configs-coverage-matrix.md"),
await fetchText("../../docs/full-process-boundary-design.md"),
].join("\n");
const wasmArtifactNames = [
"blocked-dependency-summary.tsv",
"blocked-runtime-promotion-lock.tsv",
"boundary-phase-completion-summary.tsv",
"boundary-proof-gates.tsv",
"boundary-summary.tsv",
"full-process-boundary-summary.tsv",
"ini-boundary-summary.tsv",
"native-proof-alignment-summary.tsv",
"native-runtime-probe-execution-plan.tsv",
"native-runtime-probe-pass-evidence-contract.tsv",
"native-runtime-probe-summary.tsv",
"next-boundary-recommendations.tsv",
"next-boundary-worklist.tsv",
"python-remap-boundary-summary.tsv",
"python-remap-family-summary.tsv",
"python-remap-native-runtime-alignment.tsv",
"python-remap-native-runtime-fixture-plan.tsv",
"python-remap-native-runtime-probe-gate.tsv",
"python-remap-native-runtime-readiness.tsv",
"python-remap-native-runtime-state-plan.tsv",
"python-remap-runtime-contract.tsv",
"python-remap-runtime-gates.tsv",
"runtime-boundary-contract-summary.tsv",
"runtime-boundary-family-host-readiness.tsv",
"runtime-boundary-host-preflight.tsv",
"runtime-boundary-host-readiness-rollup.tsv",
"runtime-boundary-host-requirement-summary.tsv",
"runtime-boundary-host-unblock-plan.tsv",
"runtime-boundary-native-alignment-summary.tsv",
"runtime-boundary-native-evidence-acceptance-gate.tsv",
"runtime-boundary-opt-in-probe-dispatch-plan.tsv",
"runtime-boundary-opt-in-probe-dispatch-rollup.tsv",
"runtime-boundary-opt-in-probe-skip-evidence-contract.tsv",
"runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv",
"runtime-boundary-post-native-pass-gates.tsv",
"runtime-boundary-promotion-blockers.tsv",
"runtime-boundary-promotion-readiness.tsv",
"runtime-probe-gate-alignment.tsv",
"skip-summary.tsv",
"summary.tsv",
"tool-db-process-boundary-summary.tsv",
"tool-db-process-native-protocol-alignment.tsv",
"tool-db-process-native-runtime-probe-gate.tsv",
"tool-db-process-native-runtime-readiness.tsv",
"tool-db-process-protocol-gates.tsv",
"tool-db-process-transaction-plan.tsv",
"user-m-process-boundary-summary.tsv",
"user-m-process-native-runtime-probe-gate.tsv",
"user-m-process-native-runtime-readiness.tsv",
"user-m-process-native-runtime-state-plan.tsv",
"user-m-process-native-state-alignment.tsv",
"user-m-process-native-transition-alignment.tsv",
"user-m-process-state-targets.tsv",
"user-m-process-transition-plan.tsv",
].sort();
const nativeArtifactTokens = [
"build/native/native-runtime-probe-summary.tsv",
"build/native/native-source-proof-summary.tsv",
"build/native/nc-files/summary.tsv",
"build/native/sim-configs/class-summary.tsv",
"build/native/sim-configs/path-matrix.tsv",
"build/native/sim-configs/skipped.tsv",
"build/native/sim-configs/summary.tsv",
"build/native/source-probes.tsv",
];
if (wasmArtifactNames.length !== 54 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift");
}
if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) {
throw new Error("browser_native_artifact_documentation_coverage: native artifact token list drift");
}
const emptyNativeArtifactTokensAllowed = new Set([
"build/native/sim-configs/skipped.tsv",
]);
for (const artifactName of wasmArtifactNames) {
const artifactText = await fetchText(`../../build/wasm/sim-configs-inventory/${artifactName}`);
if (!artifactText.includes("\t") || artifactText.trim().split("\n")[0].length === 0) {
throw new Error(`browser_generated_artifact_fetchability: invalid ${artifactName}`);
}
}
for (const artifactToken of nativeArtifactTokens) {
const artifactText = await fetchText(`../../${artifactToken}`);
if (artifactText.length === 0 && emptyNativeArtifactTokensAllowed.has(artifactToken)) {
continue;
}
if (!artifactText.includes("\t") || artifactText.trim().split("\n")[0].length === 0) {
throw new Error(`browser_native_artifact_fetchability: invalid ${artifactToken}`);
}
}
for (const artifactName of wasmArtifactNames) {
if (!documentationText.includes(artifactName)) {
throw new Error(`browser_generated_artifact_documentation_coverage: missing ${artifactName}`);
}
}
for (const artifactToken of nativeArtifactTokens) {
if (!documentationText.includes(artifactToken)) {
throw new Error(`browser_native_artifact_documentation_coverage: missing ${artifactToken}`);
}
}
return { nativeArtifactTokens, wasmArtifactNames };
}
async function verifyBrowserBoundaryPhaseCompletion() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/boundary-phase-completion-summary.tsv"),
);
const expectedCriteria = [
"vendored_runtime_boundary_reports",
"hard_block_dependency_evidence",
"safe_hal_ui_representatives",
"blocked_families_not_promoted",
"linuxcnc_runtime_owner_proof",
"native_source_proof_alignment",
"runtime_native_alignment_artifacts",
"native_runtime_probe_summary",
"runtime_probe_gate_alignment",
"blocked_runtime_worklist_recommendation_consistency",
"native_runtime_probe_execution_plan",
"native_runtime_probe_pass_evidence_contract",
"blocked_runtime_probe_execution_consistency",
"runtime_boundary_promotion_readiness",
"runtime_boundary_promotion_blockers",
"runtime_boundary_post_native_pass_gates",
"blocked_runtime_promotion_gate_consistency",
"runtime_boundary_host_preflight",
"runtime_boundary_host_requirement_summary",
"runtime_boundary_host_unblock_plan",
"runtime_boundary_family_host_readiness",
"blocked_runtime_host_requirement_consistency",
"runtime_boundary_host_readiness_rollup",
"runtime_boundary_opt_in_probe_dispatch_plan",
"runtime_boundary_opt_in_probe_dispatch_rollup",
"runtime_boundary_opt_in_probe_skip_evidence_contract",
"runtime_boundary_opt_in_probe_skip_evidence_rollup",
"blocked_runtime_rollup_consistency",
"runtime_boundary_native_evidence_acceptance_gate",
"blocked_runtime_opt_in_gate_consistency",
"native_source_proof_runtime_probe_consistency",
"user_m_transition_contract",
"user_m_native_runtime_state_plan",
"user_m_native_runtime_readiness",
"user_m_native_runtime_probe_gate",
"tool_db_transaction_contract",
"tool_db_native_runtime_readiness",
"tool_db_native_runtime_probe_gate",
"python_runtime_contract",
"python_native_runtime_readiness",
"python_native_runtime_probe_gate",
"python_native_runtime_state_plan",
"python_native_runtime_fixture_plan",
"runtime_contract_summary",
"runtime_family_contract_alignment_consistency",
"wasm_inventory_artifact_documentation_coverage",
"native_artifact_documentation_coverage",
];
const actualCriteria = rows.map((row) => row.criterion).join(",");
if (actualCriteria !== expectedCriteria.join(",")) {
throw new Error(`browser_boundary_phase_completion: criterion drift ${actualCriteria}`);
}
for (const row of rows) {
if (row.passed !== "1") {
throw new Error(`${row.criterion}: boundary phase completion failed`);
}
if (!/^[1-9][0-9]*$/.test(row.count)) {
throw new Error(`${row.criterion}: invalid completion count ${row.count}`);
}
if (row.evidence === "-") {
throw new Error(`${row.criterion}: missing completion evidence`);
}
}
return rows;
}
function verifyBrowserBoundaryPhaseCompletionCountParity(completionRows, expectedCounts) {
const completionByCriterion = new Map(completionRows.map((row) => [row.criterion, row]));
for (const [criterion, expectedCount] of expectedCounts) {
const row = completionByCriterion.get(criterion);
if (!row) {
throw new Error(`browser_boundary_phase_completion_count_parity: missing ${criterion}`);
}
if (row.passed !== "1") {
throw new Error(`${criterion}: completion criterion must pass before count parity`);
}
if (row.count !== String(expectedCount)) {
throw new Error(`${criterion}: completion count ${row.count} does not match browser artifact count ${expectedCount}`);
}
if (row.evidence === "-" || row.evidence.length < 12) {
throw new Error(`${criterion}: completion evidence is not reviewable`);
}
}
}
async function verifyBrowserIniBoundarySummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"),
);
const byIni = new Map(rows.map((row) => [row.ini, row]));
const requiredVendoredInis = new Map([
["axis/db_demo/db_nonran.ini", "L4-TOOL-DB"],
["axis/gladevcp/gladevcp_panel.ini", "-"],
["axis/vismach/melfa-sim/melfa.ini", "-"],
["axis/vismach/millturn/millturn.ini", "L4-USER-M-PROCESS"],
["axis/vismach/puma/puma_cube.ini", "-"],
["woodpecker/woodpecker.ini", "-"],
]);
for (const [ini, expectedRecommendation] of requiredVendoredInis) {
const row = byIni.get(ini);
if (!row) {
throw new Error(`browser_ini_boundary_summary: missing ${ini}`);
}
if (row.vendored !== "1" || row.report_available !== "1") {
throw new Error(`${ini}: vendored INI boundary report unavailable`);
}
if (!row.recommended_blocked.split(",").includes(expectedRecommendation)) {
throw new Error(`${ini}: recommended block drift ${row.recommended_blocked}`);
}
if (row.hard_block_policy_aligned !== "1") {
throw new Error(`${ini}: hard-block policy alignment failed`);
}
}
}
async function verifyBrowserBoundaryProofGates() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/boundary-proof-gates.tsv"),
);
const browserRows = rows.filter((row) => row.proof_layer === "browser");
const browserByTarget = new Map(browserRows.map((row) => [row.target, row]));
const requiredBlockedTargets = new Map([
[
"axis/vismach/millturn/example.ngc",
{
blocked: "L4-USER-M-PROCESS",
proof: "no_full_process_claim",
nextAction: "design_m128_m129_linuxcnc_state_boundary",
},
],
[
"axis/db_demo/base.ngc",
{
blocked: "L4-TOOL-DB",
proof: "opfs_persistence_only",
nextAction: "design_tooldata_db_protocol_boundary",
},
],
]);
if (browserRows.length === 0) {
throw new Error("browser_boundary_proof_gates: missing browser proof rows");
}
for (const [target, expected] of requiredBlockedTargets) {
const row = browserByTarget.get(target);
if (!row) {
throw new Error(`browser_boundary_proof_gates: missing ${target}`);
}
if (row.blocked !== expected.blocked) {
throw new Error(`${target}: blocked gate drift ${row.blocked}`);
}
if (row.proof_status !== "pending") {
throw new Error(`${target}: browser proof gate must remain pending`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${target}: browser proof gate must not enable execution or promotion`);
}
if (!row.required_proof.includes(expected.proof)) {
throw new Error(`${target}: browser proof gate missing ${expected.proof}`);
}
if (row.next_action !== expected.nextAction) {
throw new Error(`${target}: next action drift ${row.next_action}`);
}
}
for (const row of browserRows.filter((entry) => entry.boundary_kind === "python_runtime")) {
if (row.blocked !== "L4-PYTHON-REMAP") {
throw new Error(`${row.target}: Python browser proof gate blocked kind drift`);
}
if (row.proof_status !== "pending") {
throw new Error(`${row.target}: Python browser proof gate must remain pending`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.target}: Python browser proof gate must stay disabled`);
}
if (!row.required_proof.includes("no_full_process_claim")) {
throw new Error(`${row.target}: Python browser proof gate must avoid full-process claim`);
}
}
return rows;
}
async function verifyBrowserNextBoundaryWorklist() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/next-boundary-worklist.tsv"),
);
const expectedFirstRows = [
["external_user_m_process", "axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"],
["tool_database_process", "axis/db_demo/base.ngc", "L4-TOOL-DB"],
];
if (rows.length < expectedFirstRows.length) {
throw new Error(`browser_next_boundary_worklist: too few rows ${rows.length}`);
}
const priorities = rows.map((row) => Number(row.priority));
for (let index = 1; index < priorities.length; index += 1) {
if (priorities[index] < priorities[index - 1]) {
throw new Error("browser_next_boundary_worklist: priority order drift");
}
}
for (const [index, expected] of expectedFirstRows.entries()) {
const row = rows[index];
if (
row.boundary_kind !== expected[0] ||
row.target !== expected[1] ||
row.blocked !== expected[2]
) {
throw new Error(`browser_next_boundary_worklist: row ${index + 1} identity drift`);
}
}
for (const row of rows) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.target}: next boundary worklist must remain disabled`);
}
if (
row.linuxcnc_owner === "-" ||
row.runtime_owner_evidence === "-" ||
row.required_native_proof === "-" ||
row.required_node_proof === "-" ||
row.required_browser_proof === "-" ||
row.next_action === "-"
) {
throw new Error(`${row.target}: next boundary worklist lacks traceability`);
}
if (row.boundary_kind === "python_runtime") {
if (
row.blocked !== "L4-PYTHON-REMAP" ||
row.design_status !== "inventory_only" ||
row.next_action !== "design_linuxcnc_python_runtime_boundary" ||
!row.notes.includes("dependency_inventory_only_no_execution")
) {
throw new Error(`${row.target}: Python worklist row must remain inventory-only`);
}
}
}
return rows;
}
function verifyBrowserNextBoundaryWorklistProofGateParity({
worklistRows,
proofGateRows,
promotionLockRows,
}) {
const expectedLayers = ["browser", "native", "node"];
const proofRowsByTarget = new Map();
for (const row of proofGateRows) {
const targetRows = proofRowsByTarget.get(row.target) || [];
targetRows.push(row);
proofRowsByTarget.set(row.target, targetRows);
}
const lockByTarget = new Map(promotionLockRows.map((row) => [row.target, row]));
if (proofGateRows.length !== worklistRows.length * expectedLayers.length) {
throw new Error("browser_next_boundary_worklist_proof_gate_parity: proof gate count drift");
}
for (const worklist of worklistRows) {
const proofRows = proofRowsByTarget.get(worklist.target) || [];
const layers = proofRows.map((row) => row.proof_layer).sort();
if (layers.join(",") !== expectedLayers.join(",")) {
throw new Error(`${worklist.target}: proof gate layer set drift`);
}
const lock = lockByTarget.get(worklist.target);
if (!lock) {
throw new Error(`${worklist.target}: promotion lock row missing`);
}
if (
lock.boundary_kind !== worklist.boundary_kind ||
lock.blocked !== worklist.blocked ||
lock.design_status !== worklist.design_status ||
lock.next_action !== worklist.next_action ||
lock.lock_active !== "1"
) {
throw new Error(`${worklist.target}: worklist/promotion lock drift`);
}
if (lock.execution_enabled !== "0" || lock.promotion_allowed !== "0") {
throw new Error(`${worklist.target}: promotion lock must remain disabled`);
}
for (const proof of proofRows) {
if (
proof.priority !== worklist.priority ||
proof.boundary_kind !== worklist.boundary_kind ||
proof.blocked !== worklist.blocked ||
proof.design_status !== worklist.design_status ||
proof.linuxcnc_owner !== worklist.linuxcnc_owner ||
proof.runtime_owner_evidence !== worklist.runtime_owner_evidence ||
proof.next_action !== worklist.next_action
) {
throw new Error(`${worklist.target}/${proof.proof_layer}: worklist/proof gate metadata drift`);
}
if (
proof.proof_status !== "pending" ||
proof.execution_enabled !== "0" ||
proof.promotion_allowed !== "0"
) {
throw new Error(`${worklist.target}/${proof.proof_layer}: proof gate must remain pending and disabled`);
}
if (proof.proof_layer === "native" && proof.required_proof !== worklist.required_native_proof) {
throw new Error(`${worklist.target}: native proof gate drift`);
}
if (proof.proof_layer === "node" && proof.required_proof !== worklist.required_node_proof) {
throw new Error(`${worklist.target}: Node proof gate drift`);
}
if (
proof.proof_layer === "browser" &&
!proof.required_proof.split(",").includes(worklist.required_browser_proof.split(",")[0])
) {
throw new Error(`${worklist.target}: browser proof gate drift`);
}
}
}
}
async function verifyBrowserBoundarySummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/boundary-summary.tsv"),
);
const byPath = new Map(rows.map((row) => [row.path, row]));
const safeRepresentatives = new Map([
[
"axis/gladevcp/probe.ngc",
{
dependencies: "hal_process,python_runtime,ui_process",
hal_process: "1",
ui_process: "1",
halui_mdi_process: "0",
python_process: "1",
python_remap_process: "0",
},
],
[
"axis/vismach/melfa-sim/example.ngc",
{
dependencies: "hal_process,halui_mdi_process,ui_process",
hal_process: "1",
ui_process: "1",
halui_mdi_process: "1",
python_process: "0",
python_remap_process: "0",
},
],
[
"axis/vismach/puma/puma_cube.ngc",
{
dependencies: "hal_process,halui_mdi_process,ui_process",
hal_process: "1",
ui_process: "1",
halui_mdi_process: "1",
python_process: "0",
python_remap_process: "0",
},
],
[
"woodpecker/on_abort.ngc",
{
dependencies: "hal_process,ui_process",
hal_process: "1",
ui_process: "1",
halui_mdi_process: "0",
python_process: "0",
python_remap_process: "0",
},
],
]);
for (const [path, expected] of safeRepresentatives) {
const row = byPath.get(path);
if (!row) {
throw new Error(`browser_boundary_summary: missing safe representative ${path}`);
}
if (row.blocked !== "-" || row.recommended_blocked !== "-") {
throw new Error(`${path}: safe representative must not be hard-blocked`);
}
if (row.dependencies !== expected.dependencies) {
throw new Error(`${path}: dependency drift ${row.dependencies}`);
}
for (const field of [
"hal_process",
"ui_process",
"halui_mdi_process",
"python_process",
"python_remap_process",
]) {
if (row[field] !== expected[field]) {
throw new Error(`${path}: ${field} drift ${row[field]}`);
}
}
}
const millturn = byPath.get("axis/vismach/millturn/example.ngc");
if (!millturn) {
throw new Error("browser_boundary_summary: missing millturn blocked row");
}
if (
millturn.blocked !== "L4-USER-M-PROCESS" ||
millturn.recommended_blocked !== "L4-USER-M-PROCESS"
) {
throw new Error("millturn boundary summary must remain L4-USER-M-PROCESS");
}
if (
millturn.user_m_execution_codes !== "M128,M129" ||
millturn.user_m_unstaged_execution_codes !== "M128,M129"
) {
throw new Error("millturn boundary summary must keep M128/M129 unstaged");
}
}
async function verifyBrowserUserMTransitionPlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-transition-plan.tsv"),
);
const expected = new Map([
[
"M128",
{
remapCode: "M428",
switchkinsTarget: "0",
activeG5x: "G59.1",
workOffsetPocket: "P7",
guardPin: "kinstype.is-0",
stateMode: "mill",
},
],
[
"M129",
{
remapCode: "M429",
switchkinsTarget: "1",
activeG5x: "G59.2",
workOffsetPocket: "P8",
guardPin: "kinstype.is-1",
stateMode: "turn",
},
],
]);
const byCode = new Map(rows.map((row) => [row.user_m_code, row]));
if (rows.length !== expected.size) {
throw new Error(`browser_user_m_transition_plan: row count drift ${rows.length}`);
}
for (const [code, expectedRow] of expected) {
const row = byCode.get(code);
if (!row) {
throw new Error(`browser_user_m_transition_plan: missing ${code}`);
}
if (
row.path !== "axis/vismach/millturn/example.ngc" ||
row.blocked !== "L4-USER-M-PROCESS" ||
row.remap_code !== expectedRow.remapCode
) {
throw new Error(`${code}: transition plan identity drift`);
}
if (
row.switchkins_output_pin !== "motion.analog-out-03" ||
row.switchkins_target !== expectedRow.switchkinsTarget ||
row.active_g5x !== expectedRow.activeG5x ||
row.work_offset_pocket !== expectedRow.workOffsetPocket ||
row.guard_pin !== expectedRow.guardPin ||
row.state_mode !== expectedRow.stateMode
) {
throw new Error(`${code}: transition plan state drift`);
}
if (row.state_target_count !== "12") {
throw new Error(`${code}: transition plan state target count drift`);
}
for (const pin of ["ini.x.min_limit", "ini.y.min_velocity", "ini.z.max_acceleration"]) {
if (!row.expected_state_pins.includes(pin)) {
throw new Error(`${code}: transition plan missing ${pin}`);
}
}
if (
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${code}: transition plan must remain pending and disabled`);
}
if (!row.notes.includes("no_process_execution")) {
throw new Error(`${code}: transition plan must record non-execution policy`);
}
}
return rows;
}
async function verifyBrowserUserMNativeTransitionAlignment() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-transition-alignment.tsv"),
);
const expected = new Map([
[
"M428",
{
userMCode: "M128",
switchkinsTarget: "0",
activeG5x: "G59.1",
workOffsetPocket: "P7",
},
],
[
"M429",
{
userMCode: "M129",
switchkinsTarget: "1",
activeG5x: "G59.2",
workOffsetPocket: "P8",
},
],
]);
const byRemap = new Map(rows.map((row) => [row.remap_code, row]));
if (rows.length !== expected.size) {
throw new Error(`browser_user_m_native_transition_alignment: row count drift ${rows.length}`);
}
for (const [remapCode, expectedRow] of expected) {
const row = byRemap.get(remapCode);
if (!row) {
throw new Error(`browser_user_m_native_transition_alignment: missing ${remapCode}`);
}
if (
row.path !== "axis/vismach/millturn/example.ngc" ||
row.blocked !== "L4-USER-M-PROCESS" ||
row.user_m_code !== expectedRow.userMCode
) {
throw new Error(`${remapCode}: native transition alignment identity drift`);
}
if (
row.switchkins_output_pin !== "motion.analog-out-03" ||
row.native_switchkins_output_pin !== row.switchkins_output_pin ||
row.switchkins_target !== expectedRow.switchkinsTarget ||
row.native_switchkins_target !== row.switchkins_target ||
row.active_g5x !== expectedRow.activeG5x ||
row.native_active_g5x !== row.active_g5x ||
row.work_offset_pocket !== expectedRow.workOffsetPocket ||
row.native_work_offset_pocket !== row.work_offset_pocket
) {
throw new Error(`${remapCode}: native transition alignment state drift`);
}
if (
row.native_call_key !== `${remapCode}_calls_${expectedRow.userMCode}` ||
row.native_call_value !== "1" ||
row.native_transition_key !== `${remapCode}_source_transition_from_linuxcnc` ||
row.native_transition_value !== "1" ||
row.alignment_ok !== "1"
) {
throw new Error(`${remapCode}: native transition source proof drift`);
}
if (
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${remapCode}: native transition alignment must remain pending and disabled`);
}
if (!row.notes.includes("no_process_execution")) {
throw new Error(`${remapCode}: native transition alignment must record non-execution policy`);
}
}
}
async function verifyBrowserUserMNativeRuntimeStatePlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-state-plan.tsv"),
);
const expected = new Map([
["M428", { userMCode: "M128", target: "0", g5x: "G59.1", pocket: "P7" }],
["M429", { userMCode: "M129", target: "1", g5x: "G59.2", pocket: "P8" }],
]);
const byRemap = new Map(rows.map((row) => [row.remap_code, row]));
if (rows.length !== expected.size) {
throw new Error(`browser_user_m_native_runtime_state_plan: row count drift ${rows.length}`);
}
for (const [remapCode, expectedRow] of expected) {
const row = byRemap.get(remapCode);
if (!row) {
throw new Error(`browser_user_m_native_runtime_state_plan: missing ${remapCode}`);
}
if (
row.path !== "axis/vismach/millturn/example.ngc" ||
row.blocked !== "L4-USER-M-PROCESS" ||
row.user_m_code !== expectedRow.userMCode ||
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process"
) {
throw new Error(`${remapCode}: runtime state plan identity drift`);
}
for (const token of ["INI_FILE_NAME", "motion.switchkins-type", "kinstype.is-0", "kinstype.is-1", "ini.[xyz].*"]) {
if (!row.required_environment.includes(token)) {
throw new Error(`${remapCode}: runtime state plan missing environment ${token}`);
}
}
if (
row.switchkins_output_pin !== "motion.analog-out-03" ||
row.switchkins_target !== expectedRow.target ||
row.active_g5x !== expectedRow.g5x ||
row.work_offset_pocket !== expectedRow.pocket ||
row.state_target_count !== "12" ||
row.source_alignment_ok !== "1"
) {
throw new Error(`${remapCode}: runtime state plan state drift`);
}
if (
!row.source_alignment_artifacts.includes("user-m-process-native-transition-alignment.tsv") ||
!row.source_alignment_artifacts.includes("user-m-process-native-state-alignment.tsv")
) {
throw new Error(`${remapCode}: runtime state plan missing source alignment artifacts`);
}
if (
row.native_runtime_status !== "pending_native_hal_tcl_process_probe" ||
row.proof_kind !== "native_runtime_state_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${remapCode}: runtime state plan must remain pending and disabled`);
}
if (!row.notes.includes("keep_millturn_blocked")) {
throw new Error(`${remapCode}: runtime state plan must keep millturn blocked`);
}
}
return rows;
}
async function verifyBrowserUserMNativeRuntimeReadiness() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-readiness.tsv"),
);
const expectedRequirements = ["tclsh", "halrun", "halcmd", "linuxcnc"];
if (rows.map((row) => row.requirement).join(",") !== expectedRequirements.join(",")) {
throw new Error("browser_user_m_native_runtime_readiness: requirement order drift");
}
for (const row of rows) {
if (
row.path !== "axis/vismach/millturn/example.ngc" ||
row.blocked !== "L4-USER-M-PROCESS" ||
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process" ||
row.requirement_kind !== "host_command"
) {
throw new Error(`${row.requirement}: runtime readiness identity drift`);
}
if (!["0", "1"].includes(row.available)) {
throw new Error(`${row.requirement}: invalid readiness availability ${row.available}`);
}
if (row.available === "1" && row.evidence === "-") {
throw new Error(`${row.requirement}: available runtime command lacks evidence`);
}
if (
![
"blocked_missing_host_runtime",
"host_runtime_available_probe_not_implemented",
].includes(row.native_runtime_status)
) {
throw new Error(`${row.requirement}: invalid runtime readiness status`);
}
if (
row.proof_kind !== "native_runtime_state_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.requirement}: runtime readiness must remain pending and disabled`);
}
if (
!row.notes.includes("keep_millturn_blocked") &&
!row.notes.includes("probe_still_not_implemented")
) {
throw new Error(`${row.requirement}: runtime readiness must keep blocked state`);
}
}
return rows;
}
async function verifyBrowserUserMNativeRuntimeProbeGate() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-probe-gate.tsv"),
);
const expected = new Map([
["M428", "M128"],
["M429", "M129"],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_user_m_native_runtime_probe_gate: row count drift ${rows.length}`);
}
const allRuntimeReady = rows.every((row) => row.runtime_ready === "1");
for (const row of rows) {
if (
row.path !== "axis/vismach/millturn/example.ngc" ||
row.ini !== "axis/vismach/millturn/millturn.ini" ||
row.blocked !== "L4-USER-M-PROCESS" ||
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process"
) {
throw new Error(`${row.remap_code}: runtime probe gate identity drift`);
}
if (expected.get(row.remap_code) !== row.user_m_code) {
throw new Error(`${row.remap_code}: runtime probe gate user-M drift`);
}
for (const requirement of ["tclsh", "halrun", "halcmd", "linuxcnc"]) {
if (!row.runtime_requirements.includes(`${requirement}:`)) {
throw new Error(`${row.remap_code}: runtime probe gate missing ${requirement}`);
}
}
if (
row.source_proof_ready !== "1" ||
row.required_native_proof !== "native_runtime_state_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.remap_code}: runtime probe gate must remain source-ready, pending, and disabled`);
}
if (allRuntimeReady) {
if (
row.missing_requirements !== "-" ||
row.gate_status !== "ready_to_implement_probe" ||
!row.notes.includes("write_native_hal_tcl_state_probe")
) {
throw new Error(`${row.remap_code}: ready runtime probe gate drift`);
}
} else {
if (
row.missing_requirements === "-" ||
row.gate_status !== "blocked_missing_host_runtime" ||
!row.notes.includes("keep_millturn_blocked")
) {
throw new Error(`${row.remap_code}: missing-runtime probe gate drift`);
}
}
}
return rows;
}
async function verifyBrowserToolDbTransactionPlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-transaction-plan.tsv"),
);
const expectedPhases = [
"startup_handshake",
"initial_get_all",
"spindle_load_notify",
"tool_offset_notify",
"spindle_unload_notify",
];
const expectedProtocol = new Map([
["startup_handshake", "startup:expect_reply=v2.1"],
["initial_get_all", "request:g:get_all_until_FINI"],
["spindle_load_notify", "notify:l:SPINDLE_LOAD"],
["tool_offset_notify", "notify:p:TOOL_OFFSET"],
["spindle_unload_notify", "notify:u:SPINDLE_UNLOAD"],
]);
const expectedCallback = new Map([
["startup_handshake", "registration:tooldb_callbacks_tools_loop"],
["initial_get_all", "g:user_get_tool"],
["spindle_load_notify", "l:user_load_spindle_nonran_tc"],
["tool_offset_notify", "p:user_put_tool"],
["spindle_unload_notify", "u:user_unload_spindle_nonran_tc"],
]);
if (rows.length !== expectedPhases.length) {
throw new Error(`browser_tool_db_transaction_plan: row count drift ${rows.length}`);
}
const actualPhases = rows.map((row) => row.transaction_phase).join(",");
if (actualPhases !== expectedPhases.join(",")) {
throw new Error(`browser_tool_db_transaction_plan: phase order drift ${actualPhases}`);
}
for (const row of rows) {
if (
row.path !== "axis/db_demo/base.ngc" ||
row.blocked !== "L4-TOOL-DB" ||
row.db_program !== "./db_nonran.py"
) {
throw new Error(`${row.transaction_phase}: tool DB transaction identity drift`);
}
if (row.protocol_message !== expectedProtocol.get(row.transaction_phase)) {
throw new Error(`${row.transaction_phase}: tool DB protocol message drift`);
}
if (row.db_program_callback !== expectedCallback.get(row.transaction_phase)) {
throw new Error(`${row.transaction_phase}: tool DB callback drift`);
}
if (row.state_targets === "-") {
throw new Error(`${row.transaction_phase}: missing tool DB state targets`);
}
if (
row.required_native_proof === "-" ||
row.required_node_proof === "-" ||
row.required_browser_proof === "-"
) {
throw new Error(`${row.transaction_phase}: missing tool DB required proof`);
}
if (
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.transaction_phase}: tool DB transaction must remain pending and disabled`);
}
if (!row.notes.includes("no_db_process_execution") || !row.notes.includes("no_tool_table_fallback")) {
throw new Error(`${row.transaction_phase}: tool DB transaction must record blocked execution policy`);
}
}
return rows;
}
async function verifyBrowserToolDbNativeRuntimeReadiness() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-native-runtime-readiness.tsv"),
);
const expectedRequirements = [
"python3",
"linuxcnc",
"milltask",
"halcmd",
"axis/db_demo/db_nonran.py",
"linuxcnc.so",
"tooldb.py",
];
if (rows.map((row) => row.requirement).join(",") !== expectedRequirements.join(",")) {
throw new Error("browser_tool_db_native_runtime_readiness: requirement order drift");
}
for (const row of rows) {
if (
row.path !== "axis/db_demo/base.ngc" ||
row.blocked !== "L4-TOOL-DB" ||
row.required_runtime !== "linuxcnc_tooldata_db_process" ||
row.db_program !== "./db_nonran.py"
) {
throw new Error(`${row.requirement}: tool DB readiness identity drift`);
}
if (!["host_command", "source_config_file", "linuxcnc_python_module"].includes(row.requirement_kind)) {
throw new Error(`${row.requirement}: invalid readiness kind ${row.requirement_kind}`);
}
if (!["0", "1"].includes(row.available)) {
throw new Error(`${row.requirement}: invalid readiness availability ${row.available}`);
}
if (row.available === "1" && row.evidence === "-") {
throw new Error(`${row.requirement}: available tool DB requirement lacks evidence`);
}
if (
![
"blocked_missing_host_runtime",
"host_runtime_available_probe_not_implemented",
].includes(row.native_runtime_status)
) {
throw new Error(`${row.requirement}: invalid tool DB runtime readiness status`);
}
if (
row.proof_kind !== "native_db_process_protocol_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.requirement}: tool DB readiness must remain pending and disabled`);
}
if (
!row.notes.includes("keep_tool_db_blocked") &&
!row.notes.includes("probe_still_not_implemented")
) {
throw new Error(`${row.requirement}: tool DB readiness must keep blocked state`);
}
}
return rows;
}
async function verifyBrowserToolDbNativeRuntimeProbeGate() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-native-runtime-probe-gate.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_tool_db_native_runtime_probe_gate: row count drift ${rows.length}`);
}
const row = rows[0];
if (
row.path !== "axis/db_demo/base.ngc" ||
row.ini !== "axis/db_demo/db_nonran.ini" ||
row.blocked !== "L4-TOOL-DB" ||
row.db_program !== "./db_nonran.py" ||
row.required_runtime !== "linuxcnc_tooldata_db_process"
) {
throw new Error("tool DB runtime probe gate identity drift");
}
if (row.python3_sufficient !== "0" || row.tool_table_fallback_sufficient !== "0") {
throw new Error("tool DB runtime probe gate must reject python3-only and tbl fallback proof");
}
for (const field of [
"python3_available",
"db_program_source_ready",
"linuxcnc_python_modules_ready",
"protocol_contract_ready",
"runtime_ready",
"source_proof_ready",
]) {
if (!["0", "1"].includes(row[field])) {
throw new Error(`tool DB runtime probe gate invalid ${field} ${row[field]}`);
}
}
if (
row.db_program_source_ready !== "1" ||
row.linuxcnc_python_modules_ready !== "1" ||
row.protocol_contract_ready !== "1" ||
row.source_proof_ready !== "1"
) {
throw new Error("tool DB runtime probe gate source readiness drift");
}
for (const requirement of [
"python3",
"linuxcnc",
"milltask",
"halcmd",
"axis/db_demo/db_nonran.py",
"linuxcnc.so",
"tooldb.py",
]) {
if (!row.runtime_requirements.includes(`${requirement}:`)) {
throw new Error(`tool DB runtime probe gate missing ${requirement}`);
}
}
for (const phase of [
"startup_handshake",
"initial_get_all",
"spindle_load_notify",
"tool_offset_notify",
"spindle_unload_notify",
]) {
if (!row.protocol_transactions.includes(`${phase}:`)) {
throw new Error(`tool DB runtime probe gate missing transaction ${phase}`);
}
}
if (
row.required_native_proof !== "native_db_process_protocol_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error("tool DB runtime probe gate must remain pending and disabled");
}
if (!row.notes.includes("fallback_not_sufficient")) {
throw new Error("tool DB runtime probe gate must record fallback insufficiency");
}
if (row.runtime_ready === "1") {
if (
row.missing_requirements !== "-" ||
row.gate_status !== "ready_to_implement_protocol_probe"
) {
throw new Error("ready tool DB runtime probe gate drift");
}
} else if (
row.missing_requirements === "-" ||
row.gate_status !== "blocked_missing_host_runtime"
) {
throw new Error("missing-runtime tool DB probe gate drift");
}
return rows;
}
async function verifyBrowserPythonRuntimeContract() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-runtime-contract.tsv"),
);
const requiredFamilies = new Set([
"axis/laser",
"axis/remap/extend-builtins/nc_files",
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
"gmoccapy/macros",
]);
const phases = [
"initialize_python",
"apply_ini_python_path",
"execute_toplevel",
"callable_lookup",
"pycall_dispatch",
"callable_invoke",
"remap_phase_dispatch",
];
if (rows.length < requiredFamilies.size) {
throw new Error(`browser_python_runtime_contract: too few rows ${rows.length}`);
}
const byFamily = new Map(rows.map((row) => [row.family, row]));
for (const family of requiredFamilies) {
if (!byFamily.has(family)) {
throw new Error(`browser_python_runtime_contract: missing ${family}`);
}
}
for (const row of rows) {
if (row.blocked !== "L4-PYTHON-REMAP" || row.design_status !== "inventory_only") {
throw new Error(`${row.family}: Python runtime contract identity drift`);
}
if (row.python_modules === "-" || row.runtime_owner_evidence === "-") {
throw new Error(`${row.family}: Python runtime contract lacks dependency evidence`);
}
for (const phase of phases) {
if (!row.runtime_phases.includes(phase)) {
throw new Error(`${row.family}: Python runtime contract missing phase ${phase}`);
}
}
if (
row.required_native_proof !== "python_runtime_owner_and_fixture" ||
row.required_node_proof !== "linuxcnc_python_runtime_boundary_not_js_semantics" ||
row.required_browser_proof !== "browser_after_node_python_boundary_no_full_process_claim"
) {
throw new Error(`${row.family}: Python runtime contract proof drift`);
}
if (
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.family}: Python runtime contract must remain pending and disabled`);
}
if (!row.notes.includes("python_runtime_contract_only_no_execution")) {
throw new Error(`${row.family}: Python runtime contract must record non-execution policy`);
}
}
return rows;
}
async function verifyBrowserPythonNativeRuntimeReadiness() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-readiness.tsv"),
);
const requiredFamilies = new Set([
"axis/laser",
"axis/remap/extend-builtins/nc_files",
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
"gmoccapy/macros",
]);
if (rows.length <= requiredFamilies.size * 4) {
throw new Error(`browser_python_native_runtime_readiness: too few rows ${rows.length}`);
}
const byFamily = new Map();
for (const row of rows) {
const familyRows = byFamily.get(row.family) || [];
familyRows.push(row);
byFamily.set(row.family, familyRows);
if (row.blocked !== "L4-PYTHON-REMAP") {
throw new Error(`${row.family}: Python readiness blocked kind drift`);
}
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
throw new Error(`${row.family}: Python readiness runtime drift`);
}
if (
![
"host_command",
"linuxcnc_source_file",
"source_config_python_module",
].includes(row.requirement_kind)
) {
throw new Error(`${row.family}: invalid Python readiness kind ${row.requirement_kind}`);
}
if (!["0", "1"].includes(row.available)) {
throw new Error(`${row.family}: invalid Python readiness availability`);
}
if (row.available === "1" && row.evidence === "-") {
throw new Error(`${row.family}: available Python readiness row lacks evidence`);
}
if (
![
"blocked_missing_host_runtime",
"host_runtime_available_probe_not_implemented",
].includes(row.native_runtime_status)
) {
throw new Error(`${row.family}: invalid Python readiness runtime status`);
}
if (
row.proof_kind !== "native_python_runtime_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.family}: Python readiness must remain pending and disabled`);
}
if (
!row.notes.includes("keep_python_remap_blocked") &&
!row.notes.includes("probe_still_not_implemented")
) {
throw new Error(`${row.family}: Python readiness must keep blocked state`);
}
}
for (const family of requiredFamilies) {
const familyRows = byFamily.get(family);
if (!familyRows) {
throw new Error(`browser_python_native_runtime_readiness: missing ${family}`);
}
const requirements = new Set(familyRows.map((row) => row.requirement));
for (const requirement of [
"python3",
"linuxcnc",
"src/emc/rs274ngc/interp_python.cc",
"src/emc/pythonplugin/python_plugin.cc",
]) {
if (!requirements.has(requirement)) {
throw new Error(`${family}: missing Python readiness requirement ${requirement}`);
}
}
if (!familyRows.some((row) => row.requirement_kind === "source_config_python_module")) {
throw new Error(`${family}: Python readiness lacks configured module rows`);
}
}
return rows;
}
async function verifyBrowserPythonNativeRuntimeProbeGate() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-probe-gate.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_python_native_runtime_probe_gate: row count drift ${rows.length}`);
}
const row = rows[0];
if (
row.family !== "axis/remap/stop-lookahead/nc_files" ||
row.blocked !== "L4-PYTHON-REMAP" ||
row.fixture_id !== "stop_lookahead_python_runtime_lifecycle" ||
row.required_runtime !== "linuxcnc_python_remap_runtime"
) {
throw new Error(`${row.family}: Python runtime probe gate identity drift`);
}
if (row.python3_sufficient !== "0") {
throw new Error(`${row.family}: python3 alone must not satisfy Python remap readiness`);
}
for (const field of [
"python3_available",
"linuxcnc_available",
"runtime_owner_sources_ready",
"representative_modules_ready",
"runtime_ready",
"source_proof_ready",
]) {
if (!["0", "1"].includes(row[field])) {
throw new Error(`${row.family}: invalid ${field} ${row[field]}`);
}
}
if (
row.runtime_owner_sources_ready !== "1" ||
row.representative_modules_ready !== "1" ||
row.source_proof_ready !== "1"
) {
throw new Error(`${row.family}: Python runtime probe gate source readiness drift`);
}
for (const requirement of [
"python3",
"linuxcnc",
"src/emc/rs274ngc/interp_python.cc",
"src/emc/pythonplugin/python_plugin.cc",
"axis/remap/stop-lookahead/python/remap.py",
"axis/remap/stop-lookahead/python/toplevel.py",
]) {
if (!row.runtime_requirements.includes(`${requirement}:`)) {
throw new Error(`${row.family}: Python runtime probe gate missing ${requirement}`);
}
}
for (const modulePath of [
"axis/remap/stop-lookahead/python/remap.py",
"axis/remap/stop-lookahead/python/toplevel.py",
]) {
if (!row.representative_module_imports.includes(`import:${modulePath}`)) {
throw new Error(`${row.family}: Python runtime probe gate missing representative import ${modulePath}`);
}
}
if (
row.required_native_proof !== "linuxcnc_python_runtime_lifecycle_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.family}: Python runtime probe gate must remain pending and disabled`);
}
if (!row.notes.includes("python3_alone_not_sufficient")) {
throw new Error(`${row.family}: Python runtime probe gate must record python3-only guard`);
}
if (row.runtime_ready === "1") {
if (
row.missing_requirements !== "-" ||
row.gate_status !== "ready_to_implement_lifecycle_probe"
) {
throw new Error(`${row.family}: ready Python runtime probe gate drift`);
}
} else if (
row.missing_requirements === "-" ||
row.gate_status !== "blocked_missing_host_runtime"
) {
throw new Error(`${row.family}: missing-runtime Python probe gate drift`);
}
return rows;
}
async function verifyBrowserPythonNativeRuntimeStatePlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-state-plan.tsv"),
);
const requiredFamilies = new Set([
"axis/laser",
"axis/remap/extend-builtins/nc_files",
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
"gmoccapy/macros",
]);
const requiredPhaseTargets = [
"phase:initialize_python",
"phase:apply_ini_python_path",
"phase:execute_toplevel",
"phase:callable_lookup",
"phase:pycall_dispatch",
"phase:callable_invoke",
"phase:remap_phase_dispatch",
];
if (rows.length !== 12) {
throw new Error(`browser_python_native_runtime_state_plan: row count drift ${rows.length}`);
}
const byFamily = new Map(rows.map((row) => [row.family, row]));
for (const family of requiredFamilies) {
if (!byFamily.has(family)) {
throw new Error(`browser_python_native_runtime_state_plan: missing ${family}`);
}
}
for (const row of rows) {
if (row.blocked !== "L4-PYTHON-REMAP") {
throw new Error(`${row.family}: Python state plan blocked kind drift`);
}
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
throw new Error(`${row.family}: Python state plan runtime drift`);
}
if (row.python_modules === "-" || row.runtime_phases === "-") {
throw new Error(`${row.family}: Python state plan lacks module or phase evidence`);
}
for (const token of [
"python3",
"linuxcnc",
"interp_python.cc",
"python_plugin.cc",
"configured_python_modules",
]) {
if (!row.required_environment.includes(token)) {
throw new Error(`${row.family}: Python state plan missing environment ${token}`);
}
}
if (
!/^[1-9][0-9]*$/.test(row.readiness_requirement_count) ||
!/^[0-9]+$/.test(row.readiness_available_count)
) {
throw new Error(`${row.family}: invalid Python state plan readiness counts`);
}
if (
!row.source_alignment_artifacts.includes("python-remap-native-runtime-alignment.tsv") ||
!row.source_alignment_artifacts.includes("python-remap-native-runtime-readiness.tsv")
) {
throw new Error(`${row.family}: Python state plan missing source artifacts`);
}
if (
row.source_alignment_ok !== "1" ||
row.native_runtime_status !== "pending_native_python_runtime_probe"
) {
throw new Error(`${row.family}: Python state plan source/runtime status drift`);
}
for (const target of requiredPhaseTargets) {
if (!row.required_state_probe.includes(target)) {
throw new Error(`${row.family}: Python state plan missing target ${target}`);
}
}
if (!row.required_state_probe.includes("module:")) {
throw new Error(`${row.family}: Python state plan lacks module proof target`);
}
if (
row.proof_kind !== "native_python_runtime_state_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.family}: Python state plan must remain pending and disabled`);
}
if (!row.notes.includes("native_runtime_not_executed_keep_python_remap_blocked")) {
throw new Error(`${row.family}: Python state plan must keep blocked state`);
}
}
return rows;
}
async function verifyBrowserPythonNativeRuntimeFixturePlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-fixture-plan.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_python_native_runtime_fixture_plan: row count drift ${rows.length}`);
}
const row = rows[0];
if (row.family !== "axis/remap/stop-lookahead/nc_files") {
throw new Error(`Python fixture family drift ${row.family}`);
}
if (row.blocked !== "L4-PYTHON-REMAP") {
throw new Error(`${row.family}: Python fixture blocked kind drift`);
}
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
throw new Error(`${row.family}: Python fixture runtime drift`);
}
if (
row.selection_reason !== "selected_minimal_python_runtime_lifecycle_fixture" ||
row.fixture_id !== "stop_lookahead_python_runtime_lifecycle" ||
row.fixture_scope !== "no_python_callables_no_ngc_only_subpaths"
) {
throw new Error(`${row.family}: Python fixture selection drift`);
}
for (const phase of [
"initialize_python",
"apply_ini_python_path",
"execute_toplevel",
"callable_lookup",
"pycall_dispatch",
"callable_invoke",
"remap_phase_dispatch",
]) {
if (!row.runtime_phases.includes(phase) || !row.expected_runtime_observations.includes(phase)) {
throw new Error(`${row.family}: Python fixture missing phase ${phase}`);
}
}
for (const modulePath of [
"axis/remap/stop-lookahead/python/remap.py",
"axis/remap/stop-lookahead/python/toplevel.py",
]) {
if (!row.python_modules.includes(modulePath)) {
throw new Error(`${row.family}: Python fixture missing module ${modulePath}`);
}
if (!row.expected_module_inputs.includes(`module:${modulePath}`)) {
throw new Error(`${row.family}: Python fixture missing module input ${modulePath}`);
}
}
if (row.process_assumptions !== "hal_process") {
throw new Error(`${row.family}: Python fixture process assumption drift`);
}
if (
!row.required_environment.includes("python3") ||
!row.required_environment.includes("linuxcnc") ||
!row.required_environment.includes("interp_python.cc") ||
!row.required_environment.includes("python_plugin.cc")
) {
throw new Error(`${row.family}: Python fixture environment drift`);
}
if (
row.source_alignment_ok !== "1" ||
!row.source_alignment_artifacts.includes("python-remap-native-runtime-alignment.tsv") ||
!row.source_alignment_artifacts.includes("python-remap-native-runtime-readiness.tsv")
) {
throw new Error(`${row.family}: Python fixture source alignment drift`);
}
if (
row.proof_kind !== "linuxcnc_python_runtime_lifecycle_probe_required" ||
row.proof_status !== "pending" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.family}: Python fixture must remain pending and disabled`);
}
if (!row.notes.includes("fixture_plan_only_no_python_execution_no_promotion")) {
throw new Error(`${row.family}: Python fixture must remain non-executing`);
}
return rows;
}
async function verifyBrowserNativeProofAlignment() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/native-proof-alignment-summary.tsv"),
);
const byClass = new Map(rows.map((row) => [row.boundary_class, row]));
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
proofKey: "millturn_user_m_native_source_state_proof",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
proofKey: "tool_db_native_source_protocol_proof",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
proofKey: "python_remap_native_source_inventory_proof",
},
],
]);
for (const [boundaryClass, expectedRow] of expected) {
const row = byClass.get(boundaryClass);
if (!row) {
throw new Error(`browser_native_proof_alignment: missing ${boundaryClass}`);
}
if (row.boundary_kind !== expectedRow.boundaryKind) {
throw new Error(`${boundaryClass}: native proof boundary kind drift`);
}
if (row.proof_key !== expectedRow.proofKey) {
throw new Error(`${boundaryClass}: native proof key drift`);
}
if (row.native_summary_available === "0") {
if (
row.alignment_ok !== "0" ||
row.notes !== "native_source_proof_summary_unavailable"
) {
throw new Error(`${boundaryClass}: unavailable native proof alignment drift`);
}
continue;
}
if (
row.proof_value !== "1" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0" ||
row.alignment_ok !== "1"
) {
throw new Error(`${boundaryClass}: native proof alignment failed`);
}
if (row.matching_worklist_count === "0" || row.matching_native_gate_count === "0") {
throw new Error(`${boundaryClass}: native proof lacks generated gate consumer`);
}
}
return rows;
}
async function verifyBrowserRuntimeNativeAlignmentSummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-summary.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
artifact: "user-m-process-native-state-alignment.tsv",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
artifact: "tool-db-process-native-protocol-alignment.tsv",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
artifact: "python-remap-native-runtime-alignment.tsv",
},
],
]);
const byClass = new Map(rows.map((row) => [row.boundary_class, row]));
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_native_alignment_summary: row count drift ${rows.length}`);
}
for (const [boundaryClass, expectedRow] of expected) {
const row = byClass.get(boundaryClass);
if (!row) {
throw new Error(`browser_runtime_native_alignment_summary: missing ${boundaryClass}`);
}
if (row.boundary_kind !== expectedRow.boundaryKind || row.artifact !== expectedRow.artifact) {
throw new Error(`${boundaryClass}: runtime native alignment summary metadata drift`);
}
if (!/^[1-9][0-9]*$/.test(row.row_count)) {
throw new Error(`${boundaryClass}: invalid runtime alignment row count ${row.row_count}`);
}
for (const field of [
"native_stdout_available_count",
"alignment_ok_count",
"pending_count",
"execution_disabled_count",
"promotion_disabled_count",
]) {
if (row[field] !== row.row_count) {
throw new Error(`${boundaryClass}: ${field} must match row_count`);
}
}
if (row.summary_ok !== "1") {
throw new Error(`${boundaryClass}: runtime native alignment summary failed`);
}
if (!row.notes.includes("no_runtime_execution")) {
throw new Error(`${boundaryClass}: runtime native alignment summary must remain non-executing`);
}
}
return rows;
}
async function verifyBrowserNativeRuntimeProbeSummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-summary.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
requiredNativeProof: "native_runtime_state_probe_required",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
requiredNativeProof: "native_db_process_protocol_probe_required",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_native_runtime_probe_summary: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_native_runtime_probe_summary: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.runtime_probe !== expectedRow.runtimeProbe ||
row.required_native_proof !== expectedRow.requiredNativeProof ||
row.opt_in_env !== expectedRow.optInEnv
) {
throw new Error(`${row.boundary_class}: native runtime probe metadata drift`);
}
if (row.source_proof_ready !== "1") {
throw new Error(`${row.boundary_class}: native runtime probe source proof is not ready`);
}
if (!["0", "1"].includes(row.runtime_ready)) {
throw new Error(`${row.boundary_class}: invalid runtime readiness`);
}
if (
![
"skipped_missing_host_runtime",
"ready_disabled_by_default",
"not_implemented_full_process_guard",
"runtime_state_probe_passed",
"runtime_protocol_probe_passed",
"runtime_lifecycle_probe_passed",
].includes(row.probe_status)
) {
throw new Error(`${row.boundary_class}: invalid runtime probe status ${row.probe_status}`);
}
if (row.runtime_ready === "0" && row.probe_status !== "skipped_missing_host_runtime") {
throw new Error(`${row.boundary_class}: non-ready runtime must remain skipped`);
}
if (row.runtime_ready === "0" && !row.probe_note.includes("missing_host_runtime")) {
throw new Error(`${row.boundary_class}: missing-runtime probe note drift`);
}
if (
row.probe_status === "ready_disabled_by_default" &&
!row.probe_note.includes(`set_${row.opt_in_env.split("=")[0]}`)
) {
throw new Error(`${row.boundary_class}: ready-disabled probe note drift`);
}
if (
row.probe_status.startsWith("runtime_") &&
row.probe_status.endsWith("_passed") &&
!row.probe_note.includes("without_promotion")
) {
throw new Error(`${row.boundary_class}: passed probe note drift`);
}
if (row.probe_note === "-") {
throw new Error(`${row.boundary_class}: native runtime probe lacks note`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: native runtime probe must not enable execution or promotion`);
}
if (row.stdout_log === "-") {
throw new Error(`${row.boundary_class}: native runtime probe lacks stdout evidence`);
}
}
return rows;
}
async function verifyBrowserRuntimeProbeGateAlignment() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-probe-gate-alignment.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
gateArtifact: "user-m-process-native-runtime-probe-gate.tsv",
requiredNativeProof: "native_runtime_state_probe_required",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
gateArtifact: "tool-db-process-native-runtime-probe-gate.tsv",
requiredNativeProof: "native_db_process_protocol_probe_required",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
gateArtifact: "python-remap-native-runtime-probe-gate.tsv",
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_probe_gate_alignment: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_probe_gate_alignment: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.gate_artifact !== expectedRow.gateArtifact ||
row.gate_required_native_proof !== expectedRow.requiredNativeProof ||
row.native_required_native_proof !== expectedRow.requiredNativeProof
) {
throw new Error(`${row.boundary_class}: runtime probe gate alignment metadata drift`);
}
if (!/^ENABLE_[A-Z0-9_]+=1$/.test(row.native_opt_in_env)) {
throw new Error(`${row.boundary_class}: runtime probe gate alignment opt-in drift`);
}
if (row.gate_runtime_ready !== row.native_runtime_ready) {
throw new Error(`${row.boundary_class}: gate/native runtime readiness drift`);
}
if (row.gate_source_proof_ready !== "1" || row.native_source_proof_ready !== "1") {
throw new Error(`${row.boundary_class}: runtime probe gate source proof drift`);
}
for (const field of [
"missing_requirements_match",
"required_native_proof_match",
"execution_disabled_match",
"promotion_disabled_match",
"status_compatible",
"alignment_ok",
]) {
if (row[field] !== "1") {
throw new Error(`${row.boundary_class}: runtime probe gate ${field} failed`);
}
}
if (!row.notes.includes("before_promotion")) {
throw new Error(`${row.boundary_class}: runtime probe gate alignment notes drift`);
}
}
return rows;
}
function verifyBrowserNativeSourceProofRuntimeProbeParity(
nativeProofRows,
nativeRuntimeProbeRows,
runtimeProbeGateAlignmentRows,
) {
const expectedClasses = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
];
const proofByClass = new Map(nativeProofRows.map((row) => [row.boundary_class, row]));
const runtimeByClass = new Map(nativeRuntimeProbeRows.map((row) => [row.boundary_class, row]));
const gateByClass = new Map(runtimeProbeGateAlignmentRows.map((row) => [row.boundary_class, row]));
if (
nativeProofRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",") ||
nativeRuntimeProbeRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",") ||
runtimeProbeGateAlignmentRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")
) {
throw new Error("browser_native_source_proof_runtime_probe_parity: class order drift");
}
for (const boundaryClass of expectedClasses) {
const proof = proofByClass.get(boundaryClass);
const runtime = runtimeByClass.get(boundaryClass);
const gate = gateByClass.get(boundaryClass);
if (!proof || !runtime || !gate) {
throw new Error(`${boundaryClass}: missing native proof/runtime/gate parity row`);
}
if (runtime.boundary_kind !== proof.boundary_kind || gate.boundary_kind !== runtime.boundary_kind) {
throw new Error(`${boundaryClass}: native proof/runtime/gate boundary kind drift`);
}
if (proof.target !== "*" && runtime.target !== proof.target) {
throw new Error(`${boundaryClass}: native proof/runtime target drift`);
}
if (gate.target !== runtime.target) {
throw new Error(`${boundaryClass}: runtime gate target drift`);
}
if (
proof.alignment_ok !== "1" ||
runtime.source_proof_ready !== proof.alignment_ok ||
gate.native_source_proof_ready !== proof.alignment_ok ||
gate.gate_source_proof_ready !== proof.alignment_ok
) {
throw new Error(`${boundaryClass}: native source proof readiness drift`);
}
if (
runtime.required_native_proof !== gate.native_required_native_proof ||
gate.required_native_proof_match !== "1"
) {
throw new Error(`${boundaryClass}: native runtime proof requirement drift`);
}
if (
runtime.runtime_ready !== gate.native_runtime_ready ||
gate.gate_runtime_ready !== gate.native_runtime_ready ||
gate.missing_requirements_match !== "1" ||
gate.status_compatible !== "1"
) {
throw new Error(`${boundaryClass}: native runtime readiness/status drift`);
}
if (
proof.execution_enabled !== "0" ||
proof.promotion_allowed !== "0" ||
runtime.execution_enabled !== "0" ||
runtime.promotion_allowed !== "0" ||
gate.execution_disabled_match !== "1" ||
gate.promotion_disabled_match !== "1" ||
gate.alignment_ok !== "1"
) {
throw new Error(`${boundaryClass}: native proof/runtime/gate parity must remain disabled`);
}
}
}
async function verifyBrowserNativeRuntimeProbeExecutionPlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-execution-plan.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
requiredNativeProof: "native_runtime_state_probe_required",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
script: "wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
expectedPassStatus: "runtime_state_probe_passed",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
requiredNativeProof: "native_db_process_protocol_probe_required",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
script: "wasm-port/tests/native/probe_tool_db_runtime.sh",
expectedPassStatus: "runtime_protocol_probe_passed",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
script: "wasm-port/tests/native/probe_python_remap_runtime.sh",
expectedPassStatus: "runtime_lifecycle_probe_passed",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_native_runtime_probe_execution_plan: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_native_runtime_probe_execution_plan: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.runtime_probe !== expectedRow.runtimeProbe ||
row.required_native_proof !== expectedRow.requiredNativeProof ||
row.opt_in_env !== expectedRow.optInEnv ||
row.expected_pass_status !== expectedRow.expectedPassStatus
) {
throw new Error(`${row.boundary_class}: native runtime execution plan metadata drift`);
}
if (row.execution_command !== `${expectedRow.optInEnv} bash ${expectedRow.script}`) {
throw new Error(`${row.boundary_class}: native runtime execution command drift`);
}
if (row.source_proof_ready !== "1" || row.gate_alignment_ok !== "1") {
throw new Error(`${row.boundary_class}: native runtime execution plan is not source/alignment ready`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: native runtime execution plan must not enable execution or promotion`);
}
for (const requirement of [
`native:${expectedRow.expectedPassStatus}`,
"node:inventory_gate_alignment_after_native_pass",
"browser:smoke_gate_alignment_after_node_pass",
"manual:promotion_lock_update_required",
]) {
if (!row.promotion_requires.includes(requirement)) {
throw new Error(`${row.boundary_class}: native runtime execution plan missing ${requirement}`);
}
}
if (row.current_probe_status === row.expected_pass_status) {
if (row.plan_status !== "native_probe_passed_waiting_for_node_browser_promotion_proof") {
throw new Error(`${row.boundary_class}: passed execution plan status drift`);
}
} else if (row.runtime_ready === "1") {
if (row.plan_status !== "ready_to_run_opt_in_probe" || row.missing_requirements !== "-") {
throw new Error(`${row.boundary_class}: ready execution plan status drift`);
}
} else if (
row.plan_status !== "blocked_missing_host_runtime" ||
row.missing_requirements === "-"
) {
throw new Error(`${row.boundary_class}: blocked execution plan status drift`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: native runtime execution plan notes drift`);
}
}
return rows;
}
async function verifyBrowserNativeRuntimeProbePassEvidenceContract() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-pass-evidence-contract.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
expectedPassStatus: "runtime_state_probe_passed",
evidence: [
"millturn_user_m_runtime_probe_status=runtime_state_probe_passed",
"millturn_user_m_M128_runtime_state_ok=1",
"millturn_user_m_M129_runtime_state_ok=1",
],
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
expectedPassStatus: "runtime_protocol_probe_passed",
evidence: [
"tool_db_runtime_probe_status=runtime_protocol_probe_passed",
"tool_db_protocol_version=v2.1",
"tool_db_runtime_protocol_probe_ok=1",
"tool_db_persistence_state_ok=1",
],
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
expectedPassStatus: "runtime_lifecycle_probe_passed",
evidence: [
"python_remap_runtime_probe_status=runtime_lifecycle_probe_passed",
"python_remap_lifecycle_generator_first_yield=2",
"python_remap_runtime_lifecycle_probe_ok=1",
],
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_native_runtime_probe_pass_evidence_contract: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_native_runtime_probe_pass_evidence_contract: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.runtime_probe !== expectedRow.runtimeProbe ||
row.expected_pass_status !== expectedRow.expectedPassStatus ||
row.stdout_log === "-"
) {
throw new Error(`${row.boundary_class}: native pass evidence metadata drift`);
}
for (const evidence of expectedRow.evidence) {
if (!row.expected_evidence_keys.includes(evidence)) {
throw new Error(`${row.boundary_class}: native pass evidence missing ${evidence}`);
}
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: native pass evidence contract must not enable execution or promotion`);
}
if (row.current_probe_status === row.expected_pass_status) {
if (
row.evidence_required_now !== "1" ||
row.observed_evidence_ready !== "1" ||
row.missing_evidence_keys !== "-" ||
row.evidence_status !== "native_pass_evidence_observed"
) {
throw new Error(`${row.boundary_class}: native pass evidence must be observed after pass`);
}
} else if (
row.evidence_required_now !== "0" ||
row.observed_evidence_ready !== "0" ||
row.missing_evidence_keys !== "-" ||
row.evidence_status !== "pending_until_native_pass"
) {
throw new Error(`${row.boundary_class}: native pass evidence must remain pending before pass`);
}
if (
!row.notes.includes("no_automatic_promotion") &&
!row.notes.includes("before_node_browser_promotion")
) {
throw new Error(`${row.boundary_class}: native pass evidence notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryContractSummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-contract-summary.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
contractArtifact: "user-m-process-transition-plan.tsv",
rowCount: "2",
runtimeAlignmentArtifact: "user-m-process-native-state-alignment.tsv",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
contractArtifact: "tool-db-process-transaction-plan.tsv",
rowCount: "5",
runtimeAlignmentArtifact: "tool-db-process-native-protocol-alignment.tsv",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
contractArtifact: "python-remap-runtime-contract.tsv",
rowCount: "12",
runtimeAlignmentArtifact: "python-remap-native-runtime-alignment.tsv",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_contract_summary: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_contract_summary: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.contract_artifact !== expectedRow.contractArtifact ||
row.contract_row_count !== expectedRow.rowCount ||
row.runtime_alignment_artifact !== expectedRow.runtimeAlignmentArtifact
) {
throw new Error(`${row.boundary_class}: runtime contract summary metadata drift`);
}
for (const field of [
"pending_count",
"execution_disabled_count",
"promotion_disabled_count",
]) {
if (row[field] !== row.contract_row_count) {
throw new Error(`${row.boundary_class}: runtime contract ${field} drift`);
}
}
if (row.runtime_alignment_ok !== "1" || row.contract_ok !== "1") {
throw new Error(`${row.boundary_class}: runtime contract summary failed`);
}
if (!row.notes.includes("no_runtime_execution") || !row.notes.includes("no_promotion")) {
throw new Error(`${row.boundary_class}: runtime contract summary must remain non-executing`);
}
}
return rows;
}
function verifyBrowserRuntimeContractNativeAlignmentParity(contractRows, nativeAlignmentRows) {
const nativeByClass = new Map(nativeAlignmentRows.map((row) => [row.boundary_class, row]));
if (contractRows.length !== nativeAlignmentRows.length) {
throw new Error("browser_runtime_contract_native_alignment_parity: row count drift");
}
for (const contract of contractRows) {
const native = nativeByClass.get(contract.boundary_class);
if (!native) {
throw new Error(`${contract.boundary_class}: missing native alignment summary row`);
}
if (
contract.boundary_kind !== native.boundary_kind ||
contract.runtime_alignment_artifact !== native.artifact ||
contract.runtime_alignment_row_count !== native.row_count ||
contract.runtime_alignment_ok !== native.summary_ok
) {
throw new Error(`${contract.boundary_class}: contract/native alignment summary drift`);
}
if (contract.contract_ok !== "1" || native.summary_ok !== "1") {
throw new Error(`${contract.boundary_class}: contract/native alignment summary must remain OK`);
}
if (
contract.execution_disabled_count !== contract.contract_row_count ||
contract.promotion_disabled_count !== contract.contract_row_count ||
native.execution_disabled_count !== native.row_count ||
native.promotion_disabled_count !== native.row_count
) {
throw new Error(`${contract.boundary_class}: contract/native alignment must remain disabled`);
}
}
}
function verifyBrowserRuntimeFamilyContractAlignmentParity({
userMTransitionRows,
userMRuntimeStatePlanRows,
userMRuntimeReadinessRows,
userMRuntimeProbeGateRows,
toolDbTransactionRows,
toolDbRuntimeReadinessRows,
toolDbRuntimeProbeGateRows,
pythonRuntimeContractRows,
pythonRuntimeReadinessRows,
pythonRuntimeProbeGateRows,
pythonRuntimeStatePlanRows,
pythonRuntimeFixturePlanRows,
runtimeContractSummaryRows,
}) {
const unavailableRequirements = (rows) =>
rows.filter((row) => row.available === "0").map((row) => row.requirement).join(",");
const contractByClass = new Map(runtimeContractSummaryRows.map((row) => [row.boundary_class, row]));
const requireDisabled = (row, label) => {
if (row.proof_status !== "pending" || row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${label}: family contract alignment must remain pending and disabled`);
}
};
const userMContract = contractByClass.get("L4-USER-M-PROCESS");
if (
!userMContract ||
userMContract.contract_artifact !== "user-m-process-transition-plan.tsv" ||
userMContract.contract_row_count !== String(userMTransitionRows.length) ||
userMContract.contract_ok !== "1"
) {
throw new Error("browser_runtime_family_contract_alignment: user-M contract summary drift");
}
if (
userMRuntimeStatePlanRows.length !== userMTransitionRows.length ||
userMRuntimeProbeGateRows.length !== userMTransitionRows.length
) {
throw new Error("browser_runtime_family_contract_alignment: user-M row count drift");
}
const userMTransitionByRemap = new Map(userMTransitionRows.map((row) => [row.remap_code, row]));
const userMProbeByRemap = new Map(userMRuntimeProbeGateRows.map((row) => [row.remap_code, row]));
const userMMissingRequirements = unavailableRequirements(userMRuntimeReadinessRows);
for (const plan of userMRuntimeStatePlanRows) {
const transition = userMTransitionByRemap.get(plan.remap_code);
const probe = userMProbeByRemap.get(plan.remap_code);
if (!transition || !probe) {
throw new Error(`${plan.remap_code}: user-M family alignment missing transition/probe row`);
}
for (const field of [
"path",
"ini",
"blocked",
"user_m_code",
"switchkins_target",
"active_g5x",
"work_offset_pocket",
"expected_state_pins",
]) {
if (plan[field] !== transition[field]) {
throw new Error(`${plan.remap_code}: user-M family alignment ${field} drift`);
}
}
if (
probe.required_runtime !== plan.required_runtime ||
probe.required_native_proof !== plan.proof_kind ||
probe.source_proof_ready !== "1" ||
probe.missing_requirements !== userMMissingRequirements
) {
throw new Error(`${plan.remap_code}: user-M family probe/readiness drift`);
}
requireDisabled(transition, `${plan.remap_code}: user-M transition`);
requireDisabled(plan, `${plan.remap_code}: user-M runtime plan`);
requireDisabled(probe, `${plan.remap_code}: user-M probe gate`);
}
const toolDbContract = contractByClass.get("L4-TOOL-DB");
if (
!toolDbContract ||
toolDbContract.contract_artifact !== "tool-db-process-transaction-plan.tsv" ||
toolDbContract.contract_row_count !== String(toolDbTransactionRows.length) ||
toolDbContract.contract_ok !== "1" ||
toolDbRuntimeProbeGateRows.length !== 1
) {
throw new Error("browser_runtime_family_contract_alignment: tool DB contract summary drift");
}
const toolDbProbe = toolDbRuntimeProbeGateRows[0];
if (toolDbProbe.missing_requirements !== unavailableRequirements(toolDbRuntimeReadinessRows)) {
throw new Error("browser_runtime_family_contract_alignment: tool DB missing requirements drift");
}
for (const transaction of toolDbTransactionRows) {
if (
transaction.db_program !== toolDbProbe.db_program ||
!toolDbProbe.protocol_transactions.includes(`${transaction.transaction_phase}:${transaction.protocol_message}`)
) {
throw new Error(`${transaction.transaction_phase}: tool DB family transaction/probe drift`);
}
requireDisabled(transaction, `${transaction.transaction_phase}: tool DB transaction`);
}
if (
toolDbProbe.required_native_proof !== "native_db_process_protocol_probe_required" ||
toolDbProbe.source_proof_ready !== "1"
) {
throw new Error("browser_runtime_family_contract_alignment: tool DB proof drift");
}
requireDisabled(toolDbProbe, "tool DB probe gate");
const pythonContract = contractByClass.get("L4-PYTHON-REMAP");
if (
!pythonContract ||
pythonContract.contract_artifact !== "python-remap-runtime-contract.tsv" ||
pythonContract.contract_row_count !== String(pythonRuntimeContractRows.length) ||
pythonContract.contract_ok !== "1" ||
pythonRuntimeStatePlanRows.length !== pythonRuntimeContractRows.length ||
pythonRuntimeProbeGateRows.length !== 1 ||
pythonRuntimeFixturePlanRows.length !== 1
) {
throw new Error("browser_runtime_family_contract_alignment: Python contract summary drift");
}
const pythonContractByFamily = new Map(pythonRuntimeContractRows.map((row) => [row.family, row]));
const pythonStateByFamily = new Map(pythonRuntimeStatePlanRows.map((row) => [row.family, row]));
for (const contract of pythonRuntimeContractRows) {
const statePlan = pythonStateByFamily.get(contract.family);
if (!statePlan) {
throw new Error(`${contract.family}: Python family state plan missing`);
}
for (const field of [
"blocked",
"runtime_phases",
"python_modules",
"python_callables",
"ngc_only_subpaths",
"process_assumptions",
]) {
if (statePlan[field] !== contract[field]) {
throw new Error(`${contract.family}: Python family ${field} drift`);
}
}
requireDisabled(contract, `${contract.family}: Python contract`);
requireDisabled(statePlan, `${contract.family}: Python state plan`);
}
const pythonProbe = pythonRuntimeProbeGateRows[0];
const pythonFixture = pythonRuntimeFixturePlanRows[0];
const pythonFixtureContract = pythonContractByFamily.get(pythonFixture.family);
const pythonFixtureState = pythonStateByFamily.get(pythonFixture.family);
if (!pythonFixtureContract || !pythonFixtureState || pythonProbe.family !== pythonFixture.family) {
throw new Error("browser_runtime_family_contract_alignment: Python fixture/probe family drift");
}
if (
pythonFixture.runtime_phases !== pythonFixtureContract.runtime_phases ||
pythonFixture.python_modules !== pythonFixtureContract.python_modules ||
pythonFixture.process_assumptions !== pythonFixtureContract.process_assumptions ||
pythonProbe.required_native_proof !== pythonFixture.proof_kind ||
pythonProbe.source_proof_ready !== "1"
) {
throw new Error("browser_runtime_family_contract_alignment: Python fixture/probe proof drift");
}
const pythonFixtureMissingRequirements = pythonRuntimeReadinessRows
.filter((row) => row.family === pythonProbe.family && row.available === "0")
.map((row) => row.requirement)
.join(",");
if (pythonProbe.missing_requirements !== pythonFixtureMissingRequirements) {
throw new Error("browser_runtime_family_contract_alignment: Python missing requirements drift");
}
requireDisabled(pythonFixture, "Python fixture plan");
requireDisabled(pythonProbe, "Python probe gate");
for (const summary of runtimeContractSummaryRows) {
if (
summary.pending_count !== summary.contract_row_count ||
summary.execution_disabled_count !== summary.contract_row_count ||
summary.promotion_disabled_count !== summary.contract_row_count ||
summary.runtime_alignment_ok !== "1" ||
summary.contract_ok !== "1"
) {
throw new Error(`${summary.boundary_class}: runtime family contract summary status drift`);
}
}
}
async function verifyBrowserRuntimeNativeAlignmentDetails(summaryRows) {
for (const summary of summaryRows) {
const rows = parseTsv(
await fetchText(`../../build/wasm/sim-configs-inventory/${summary.artifact}`),
);
if (String(rows.length) !== summary.row_count) {
throw new Error(`${summary.boundary_class}: detailed alignment row count drift`);
}
for (const row of rows) {
if (row.blocked !== summary.boundary_class) {
throw new Error(`${summary.boundary_class}: detailed alignment blocked kind drift`);
}
if (row.native_stdout_available !== "1" || row.alignment_ok !== "1") {
throw new Error(`${summary.boundary_class}: detailed alignment failed`);
}
if (row.proof_status !== "pending") {
throw new Error(`${summary.boundary_class}: detailed alignment proof must remain pending`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${summary.boundary_class}: detailed alignment must not enable execution or promotion`);
}
if (!row.notes.includes("no_")) {
throw new Error(`${summary.boundary_class}: detailed alignment must record non-execution notes`);
}
}
}
}
async function verifyBrowserBlockedRuntimePromotionLock() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv"),
);
const requiredTargets = new Map([
["axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"],
["axis/db_demo/base.ngc", "L4-TOOL-DB"],
]);
if (rows.length < requiredTargets.size) {
throw new Error("browser_blocked_runtime_promotion_lock: too few rows");
}
const byTarget = new Map(rows.map((row) => [row.target, row]));
for (const [target, blocked] of requiredTargets) {
const row = byTarget.get(target);
if (!row) {
throw new Error(`browser_blocked_runtime_promotion_lock: missing ${target}`);
}
if (row.blocked !== blocked) {
throw new Error(`${target}: promotion lock blocked kind drift`);
}
}
for (const row of rows) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.target}: promotion lock must keep execution and promotion disabled`);
}
for (const field of [
"native_gate_pending",
"node_gate_pending",
"browser_gate_pending",
"runtime_alignment_ok",
"lock_active",
]) {
if (row[field] !== "1") {
throw new Error(`${row.target}: promotion lock ${field} drift`);
}
}
if (!/^[1-9][0-9]*$/.test(row.runtime_alignment_row_count)) {
throw new Error(`${row.target}: invalid promotion lock alignment count`);
}
if (!row.notes.includes("blocked_until_linuxcnc_owned_runtime_boundary")) {
throw new Error(`${row.target}: promotion lock note drift`);
}
if (row.boundary_kind === "python_runtime" && row.design_status !== "inventory_only") {
throw new Error(`${row.target}: Python promotion lock must remain inventory-only`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryPromotionReadiness() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-promotion-readiness.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
blocked: "L4-USER-M-PROCESS",
expectedPassStatus: "runtime_state_probe_passed",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
expectedPassStatus: "runtime_protocol_probe_passed",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
blocked: "L4-PYTHON-REMAP",
expectedPassStatus: "runtime_lifecycle_probe_passed",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_promotion_readiness: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_promotion_readiness: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.blocked !== expectedRow.blocked ||
row.expected_pass_status !== expectedRow.expectedPassStatus ||
row.native_evidence_status === "-"
) {
throw new Error(`${row.boundary_class}: promotion readiness metadata drift`);
}
if (
row.node_inventory_gate_complete !== "0" ||
row.browser_smoke_gate_complete !== "0" ||
row.promotion_lock_active !== "1" ||
row.manual_lock_update_required !== "1" ||
row.promotion_ready !== "0" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.boundary_class}: promotion readiness must remain locked`);
}
if (row.current_probe_status === row.expected_pass_status) {
if (row.native_pass_ready !== "1" || row.blocking_reason === "awaiting_native_runtime_probe_pass") {
throw new Error(`${row.boundary_class}: passed promotion readiness state drift`);
}
} else if (
row.native_pass_ready !== "0" ||
row.blocking_reason !== "awaiting_native_runtime_probe_pass"
) {
throw new Error(`${row.boundary_class}: pending promotion readiness state drift`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: promotion readiness notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryPromotionBlockers() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
blocked: "L4-USER-M-PROCESS",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
blocked: "L4-PYTHON-REMAP",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_promotion_blockers: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_promotion_blockers: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.blocked !== expectedRow.blocked
) {
throw new Error(`${row.boundary_class}: promotion blocker metadata drift`);
}
if (
row.promotion_ready !== "0" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0" ||
row.blocker_keys === "-" ||
row.next_unblock_action === "-"
) {
throw new Error(`${row.boundary_class}: promotion blocker summary must remain blocked and actionable`);
}
if (!/^[1-9][0-9]*$/.test(row.blocker_count)) {
throw new Error(`${row.boundary_class}: invalid promotion blocker count`);
}
for (const blocker of [
"node_inventory_gate_not_complete",
"browser_smoke_gate_not_complete",
"promotion_lock_active",
"manual_lock_update_required",
]) {
if (!row.blocker_keys.includes(blocker)) {
throw new Error(`${row.boundary_class}: promotion blocker missing ${blocker}`);
}
}
if (
row.missing_runtime_requirements !== "-" &&
!row.blocker_keys.includes("host_runtime_requirements_missing")
) {
throw new Error(`${row.boundary_class}: host runtime blocker missing`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: promotion blocker notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryPostNativePassGates() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-post-native-pass-gates.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
blocked: "L4-USER-M-PROCESS",
nodeProof: "not_user_m_event_only",
browserProof: "no_full_process_claim",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
nodeProof: "not_tbl_fallback",
browserProof: "opfs_persistence_only",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
blocked: "L4-PYTHON-REMAP",
nodeProof: "not_js_semantics",
browserProof: "no_full_process_claim",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_post_native_pass_gates: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_post_native_pass_gates: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.blocked !== expectedRow.blocked
) {
throw new Error(`${row.boundary_class}: post-native-pass gate metadata drift`);
}
if (
!row.required_node_proof.includes(expectedRow.nodeProof) ||
!row.required_browser_proof.includes(expectedRow.browserProof)
) {
throw new Error(`${row.boundary_class}: post-native-pass proof requirement drift`);
}
if (
row.manual_lock_update_required !== "1" ||
row.promotion_lock_active !== "1" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.boundary_class}: post-native-pass gate must remain locked`);
}
if (row.native_evidence_ready === "1") {
if (
row.native_pass_ready !== "1" ||
row.node_gate_status !== "pending_node_inventory_promotion_gate" ||
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
row.gate_status !== "waiting_for_node_browser_manual_promotion"
) {
throw new Error(`${row.boundary_class}: post-native-pass ready state drift`);
}
} else if (
row.node_gate_status !== "blocked_until_native_pass_evidence" ||
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
row.gate_status !== "blocked_before_native_pass"
) {
throw new Error(`${row.boundary_class}: post-native-pass blocked state drift`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: post-native-pass notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryHostPreflight() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-preflight.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
boundaryKind: "external_user_m_process",
target: "axis/vismach/millturn/example.ngc",
blocked: "L4-USER-M-PROCESS",
requiredRuntime: "linuxcnc_task_hal_tcl_user_m_process",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
script: "probe_millturn_user_m_runtime.sh",
missingToken: "linuxcnc",
},
],
[
"L4-TOOL-DB",
{
boundaryKind: "tool_database_process",
target: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
requiredRuntime: "linuxcnc_tooldata_db_process",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
script: "probe_tool_db_runtime.sh",
missingToken: "milltask",
},
],
[
"L4-PYTHON-REMAP",
{
boundaryKind: "python_runtime",
target: "axis/remap/stop-lookahead/nc_files",
blocked: "L4-PYTHON-REMAP",
requiredRuntime: "linuxcnc_python_remap_runtime",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
script: "probe_python_remap_runtime.sh",
missingToken: "linuxcnc",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_host_preflight: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_host_preflight: unexpected ${row.boundary_class}`);
}
if (
row.boundary_kind !== expectedRow.boundaryKind ||
row.target !== expectedRow.target ||
row.blocked !== expectedRow.blocked ||
row.required_runtime !== expectedRow.requiredRuntime ||
row.opt_in_env !== expectedRow.optInEnv
) {
throw new Error(`${row.boundary_class}: host preflight metadata drift`);
}
if (
row.source_proof_ready !== "1" ||
row.required_native_proof === "-" ||
row.runtime_requirements === "-" ||
!row.runtime_requirements.includes(`${expectedRow.missingToken}:`)
) {
throw new Error(`${row.boundary_class}: host preflight readiness evidence drift`);
}
if (row.execution_command !== `${row.opt_in_env} bash wasm-port/tests/native/${expectedRow.script}`) {
throw new Error(`${row.boundary_class}: host preflight execution command drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: host preflight must not enable execution or promotion`);
}
if (row.runtime_ready === "1") {
if (row.missing_requirements !== "-" || row.preflight_status !== "ready_to_run_opt_in_native_probe") {
throw new Error(`${row.boundary_class}: ready host preflight drift`);
}
} else if (
!row.missing_requirements.includes(expectedRow.missingToken) ||
row.preflight_status !== "blocked_missing_host_runtime"
) {
throw new Error(`${row.boundary_class}: blocked host preflight drift`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: host preflight notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryHostRequirementSummary() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-requirement-summary.tsv"),
);
const byRequirement = new Map(rows.map((row) => [row.requirement, row]));
const required = new Map([
["linuxcnc", { available: "0", requiredByCount: "3", missingFor: "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS" }],
["halcmd", { available: "0", requiredByCount: "2", missingFor: "L4-TOOL-DB,L4-USER-M-PROCESS" }],
["milltask", { available: "0", requiredByCount: "1", missingFor: "L4-TOOL-DB" }],
["python3", { available: "1", requiredByCount: "2", missingFor: "-" }],
["tclsh", { available: "1", requiredByCount: "1", missingFor: "-" }],
]);
if (rows.length < 8) {
throw new Error(`browser_runtime_boundary_host_requirement_summary: row count drift ${rows.length}`);
}
for (const [requirement, expected] of required) {
const row = byRequirement.get(requirement);
if (!row) {
throw new Error(`browser_runtime_boundary_host_requirement_summary: missing ${requirement}`);
}
if (
row.available !== expected.available ||
row.required_by_count !== expected.requiredByCount ||
row.missing_for !== expected.missingFor
) {
throw new Error(`${requirement}: host requirement summary drift`);
}
}
for (const row of rows) {
if (!["host_command", "source_or_module", "runtime_input"].includes(row.requirement_kind)) {
throw new Error(`${row.requirement}: invalid host requirement kind`);
}
if (!["0", "1"].includes(row.available)) {
throw new Error(`${row.requirement}: invalid host requirement availability`);
}
if (!/^[1-9][0-9]*$/.test(row.required_by_count)) {
throw new Error(`${row.requirement}: invalid host requirement count`);
}
if (row.required_by === "-" || row.blocked_families === "-" || row.opt_in_envs === "-") {
throw new Error(`${row.requirement}: host requirement summary lacks traceability`);
}
if (row.available === "0") {
if (row.missing_for === "-" || row.missing_blocks_runtime !== "1") {
throw new Error(`${row.requirement}: unavailable host requirement must block runtime`);
}
} else if (row.missing_for !== "-" || row.missing_blocks_runtime !== "0") {
throw new Error(`${row.requirement}: available host requirement must not block runtime`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.requirement}: host requirement summary must not enable execution or promotion`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.requirement}: host requirement summary notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryHostUnblockPlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-unblock-plan.tsv"),
);
const expected = new Map([
["linuxcnc", { missingFor: "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS", remaining: "halcmd,halrun,milltask" }],
["halcmd", { missingFor: "L4-TOOL-DB,L4-USER-M-PROCESS", remaining: "halrun,linuxcnc,milltask" }],
["halrun", { missingFor: "L4-USER-M-PROCESS", remaining: "halcmd,linuxcnc" }],
["milltask", { missingFor: "L4-TOOL-DB", remaining: "halcmd,linuxcnc" }],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_host_unblock_plan: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.requirement);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_host_unblock_plan: unexpected ${row.requirement}`);
}
if (
row.requirement_kind !== "host_command" ||
row.available !== "0" ||
row.missing_for !== expectedRow.missingFor ||
row.remaining_missing_after_requirement !== expectedRow.remaining
) {
throw new Error(`${row.requirement}: host unblock plan metadata drift`);
}
if (
row.affected_targets === "-" ||
row.opt_in_envs === "-" ||
row.affected_execution_commands === "-"
) {
throw new Error(`${row.requirement}: host unblock plan lacks traceability`);
}
if (
row.unblocks_when_available !== "partial_unblock_other_requirements_remain" ||
row.next_action !== "provide_missing_host_requirement"
) {
throw new Error(`${row.requirement}: host unblock plan status drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.requirement}: host unblock plan must not enable execution or promotion`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.requirement}: host unblock plan notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryFamilyHostReadiness() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-family-host-readiness.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
target: "axis/vismach/millturn/example.ngc",
missingCount: "3",
missing: "halrun,halcmd,linuxcnc",
available: "tclsh",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
},
],
[
"L4-TOOL-DB",
{
target: "axis/db_demo/base.ngc",
missingCount: "3",
missing: "linuxcnc,milltask,halcmd",
available: "python3,axis/db_demo/db_nonran.py,linuxcnc.so,tooldb.py",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
},
],
[
"L4-PYTHON-REMAP",
{
target: "axis/remap/stop-lookahead/nc_files",
missingCount: "1",
missing: "linuxcnc",
available: "python3,src/emc/rs274ngc/interp_python.cc,src/emc/pythonplugin/python_plugin.cc,axis/remap/stop-lookahead/python/remap.py,axis/remap/stop-lookahead/python/toplevel.py",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_family_host_readiness: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_family_host_readiness: unexpected ${row.boundary_class}`);
}
if (
row.target !== expectedRow.target ||
row.missing_requirement_count !== expectedRow.missingCount ||
row.missing_host_requirements !== expectedRow.missing ||
row.available_requirements !== expectedRow.available ||
row.opt_in_env !== expectedRow.optInEnv
) {
throw new Error(`${row.boundary_class}: family host readiness metadata drift`);
}
if (
row.preflight_status !== "blocked_missing_host_runtime" ||
row.family_host_status !== "blocked_missing_host_requirements"
) {
throw new Error(`${row.boundary_class}: family host readiness status drift`);
}
if (!row.execution_command.includes(row.opt_in_env)) {
throw new Error(`${row.boundary_class}: family host readiness command drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: family host readiness must not enable execution or promotion`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: family host readiness notes drift`);
}
}
return rows;
}
function verifyBrowserBlockedRuntimeHostRequirementParity({
hostPreflightRows,
requirementSummaryRows,
unblockPlanRows,
familyHostReadinessRows,
}) {
const splitList = (value) => value === "-" ? [] : value.split(",");
const preflightByClass = new Map(hostPreflightRows.map((row) => [row.boundary_class, row]));
const summaryByRequirement = new Map(requirementSummaryRows.map((row) => [row.requirement, row]));
const unblockByRequirement = new Map(unblockPlanRows.map((row) => [row.requirement, row]));
for (const family of familyHostReadinessRows) {
const preflight = preflightByClass.get(family.boundary_class);
if (!preflight) {
throw new Error(`${family.boundary_class}: missing host preflight parity row`);
}
if (
preflight.target !== family.target ||
preflight.blocked !== family.blocked ||
preflight.required_runtime !== family.required_runtime ||
preflight.opt_in_env !== family.opt_in_env ||
preflight.execution_command !== family.execution_command ||
preflight.current_probe_status !== family.current_probe_status ||
preflight.preflight_status !== family.preflight_status ||
preflight.missing_requirements !== family.missing_host_requirements
) {
throw new Error(`${family.boundary_class}: host preflight/family readiness parity drift`);
}
const missingRequirements = splitList(family.missing_host_requirements);
const availableRequirements = splitList(family.available_requirements);
if (family.missing_requirement_count !== String(missingRequirements.length)) {
throw new Error(`${family.boundary_class}: missing host requirement count drift`);
}
if (family.available_requirement_count !== String(availableRequirements.length)) {
throw new Error(`${family.boundary_class}: available host requirement count drift`);
}
for (const requirement of missingRequirements) {
const summary = summaryByRequirement.get(requirement);
const unblock = unblockByRequirement.get(requirement);
if (!summary || !unblock) {
throw new Error(`${family.boundary_class}: missing host requirement parity source ${requirement}`);
}
if (
summary.available !== "0" ||
summary.missing_blocks_runtime !== "1" ||
!splitList(summary.missing_for).includes(family.boundary_class) ||
!splitList(summary.blocked_families).includes(family.boundary_class) ||
!splitList(summary.opt_in_envs).includes(family.opt_in_env)
) {
throw new Error(`${family.boundary_class}/${requirement}: host requirement summary parity drift`);
}
if (
unblock.available !== "0" ||
!splitList(unblock.missing_for).includes(family.boundary_class) ||
!splitList(unblock.affected_targets).includes(family.target) ||
!splitList(unblock.opt_in_envs).includes(family.opt_in_env) ||
!splitList(unblock.affected_execution_commands).includes(family.execution_command) ||
splitList(unblock.remaining_missing_after_requirement).includes(requirement)
) {
throw new Error(`${family.boundary_class}/${requirement}: host unblock plan parity drift`);
}
}
for (const requirement of availableRequirements) {
const summary = summaryByRequirement.get(requirement);
if (!summary) {
throw new Error(`${family.boundary_class}: missing available requirement summary ${requirement}`);
}
if (
summary.available !== "1" ||
summary.missing_for !== "-" ||
summary.missing_blocks_runtime !== "0" ||
!splitList(summary.required_by).includes(family.boundary_class) ||
!splitList(summary.blocked_families).includes(family.boundary_class) ||
!splitList(summary.opt_in_envs).includes(family.opt_in_env)
) {
throw new Error(`${family.boundary_class}/${requirement}: available host requirement parity drift`);
}
}
for (const row of [preflight, family]) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${family.boundary_class}: host requirement parity must remain disabled`);
}
}
}
for (const summary of requirementSummaryRows) {
if (summary.required_by_count !== String(splitList(summary.required_by).length)) {
throw new Error(`${summary.requirement}: host requirement required_by_count drift`);
}
if (summary.available === "0" && !unblockByRequirement.has(summary.requirement)) {
throw new Error(`${summary.requirement}: unavailable host requirement lacks unblock plan row`);
}
if (summary.available === "1" && unblockByRequirement.has(summary.requirement)) {
throw new Error(`${summary.requirement}: available host requirement must not have unblock plan row`);
}
if (summary.execution_enabled !== "0" || summary.promotion_allowed !== "0") {
throw new Error(`${summary.requirement}: host requirement parity must remain disabled`);
}
}
}
async function verifyBrowserRuntimeBoundaryHostReadinessRollup() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_runtime_boundary_host_readiness_rollup: row count drift ${rows.length}`);
}
const row = rows[0];
if (
row.scope !== "blocked_runtime_host_readiness" ||
row.family_count !== "3" ||
row.host_ready_family_count !== "0" ||
row.host_blocked_family_count !== "3" ||
row.missing_host_requirement_count !== "4" ||
row.ready_opt_in_command_count !== "0" ||
row.ready_opt_in_commands !== "-" ||
row.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes"
) {
throw new Error("host readiness rollup state drift");
}
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
if (!row.missing_host_requirements.includes(requirement)) {
throw new Error(`host readiness rollup missing ${requirement}`);
}
}
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
if (!row.blocked_families.includes(boundaryClass)) {
throw new Error(`host readiness rollup missing ${boundaryClass}`);
}
}
for (const command of [
"ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
"ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime.sh",
"ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh",
]) {
if (!row.blocked_opt_in_commands.includes(command)) {
throw new Error(`host readiness rollup missing blocked command ${command}`);
}
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error("host readiness rollup must not enable execution or promotion");
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error("host readiness rollup notes drift");
}
return rows;
}
async function verifyBrowserRuntimeBoundaryOptInProbeDispatchPlan() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-plan.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
target: "axis/vismach/millturn/example.ngc",
missing: "halrun,halcmd,linuxcnc",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
},
],
[
"L4-TOOL-DB",
{
target: "axis/db_demo/base.ngc",
missing: "linuxcnc,milltask,halcmd",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
},
],
[
"L4-PYTHON-REMAP",
{
target: "axis/remap/stop-lookahead/nc_files",
missing: "linuxcnc",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_plan: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_plan: unexpected ${row.boundary_class}`);
}
if (
row.target !== expectedRow.target ||
row.missing_host_requirements !== expectedRow.missing ||
row.opt_in_env !== expectedRow.optInEnv
) {
throw new Error(`${row.boundary_class}: opt-in probe dispatch metadata drift`);
}
if (!row.execution_command.includes(row.opt_in_env)) {
throw new Error(`${row.boundary_class}: opt-in probe dispatch command drift`);
}
if (
row.family_host_status !== "blocked_missing_host_requirements" ||
row.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
row.host_action !== "skip_missing_host_requirements" ||
row.dispatch_allowed !== "0"
) {
throw new Error(`${row.boundary_class}: opt-in probe dispatch status drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: opt-in probe dispatch must not enable execution or promotion`);
}
if (!row.next_action.includes(row.missing_host_requirements)) {
throw new Error(`${row.boundary_class}: opt-in probe dispatch next action must name missing requirements`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: opt-in probe dispatch notes drift`);
}
}
return rows;
}
async function verifyBrowserRuntimeBoundaryOptInProbeDispatchRollup() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_rollup: row count drift ${rows.length}`);
}
const row = rows[0];
if (
row.scope !== "blocked_runtime_opt_in_probe_dispatch" ||
row.probe_count !== "3" ||
row.dispatch_allowed_count !== "0" ||
row.dispatch_blocked_count !== "3" ||
row.dispatch_actions !== "skip_missing_host_requirements" ||
row.dispatch_allowed_commands !== "-" ||
row.dispatch_status !== "dispatch_blocked_for_all_opt_in_native_probes"
) {
throw new Error("opt-in probe dispatch rollup state drift");
}
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
if (!row.blocked_families.includes(boundaryClass)) {
throw new Error(`opt-in probe dispatch rollup missing ${boundaryClass}`);
}
}
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
if (!row.missing_host_requirements.includes(requirement)) {
throw new Error(`opt-in probe dispatch rollup missing ${requirement}`);
}
}
for (const command of [
"ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
"ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime.sh",
"ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh",
]) {
if (!row.dispatch_blocked_commands.includes(command)) {
throw new Error(`opt-in probe dispatch rollup missing blocked command ${command}`);
}
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error("opt-in probe dispatch rollup must not enable execution or promotion");
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error("opt-in probe dispatch rollup notes drift");
}
return rows;
}
async function verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceContract() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-contract.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
target: "axis/vismach/millturn/example.ngc",
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
missing: "halrun,halcmd,linuxcnc",
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
},
],
[
"L4-TOOL-DB",
{
target: "axis/db_demo/base.ngc",
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
missing: "linuxcnc,milltask,halcmd",
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
},
],
[
"L4-PYTHON-REMAP",
{
target: "axis/remap/stop-lookahead/nc_files",
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
missing: "linuxcnc",
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_contract: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_contract: unexpected ${row.boundary_class}`);
}
if (
row.target !== expectedRow.target ||
row.runtime_probe !== expectedRow.runtimeProbe ||
row.missing_host_requirements !== expectedRow.missing ||
row.opt_in_env !== expectedRow.optInEnv
) {
throw new Error(`${row.boundary_class}: opt-in probe skip evidence metadata drift`);
}
if (!row.execution_command.includes(row.opt_in_env)) {
throw new Error(`${row.boundary_class}: opt-in probe skip evidence command drift`);
}
if (
row.host_action !== "skip_missing_host_requirements" ||
row.skip_reason !== "missing_host_requirements" ||
row.current_probe_status !== "skipped_missing_host_runtime" ||
row.evidence_required_now !== "0" ||
row.observed_evidence_ready !== "0" ||
row.native_evidence_status !== "pending_until_native_pass" ||
row.skip_evidence_status !== "skip_valid_until_host_requirements_available"
) {
throw new Error(`${row.boundary_class}: opt-in probe skip evidence status drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: opt-in probe skip evidence must not enable execution or promotion`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: opt-in probe skip evidence notes drift`);
}
}
return rows;
}
function verifyBrowserBlockedRuntimeProbeExecutionParity({
executionPlanRows,
passEvidenceRows,
hostPreflightRows,
dispatchPlanRows,
skipEvidenceRows,
}) {
const expectedClasses = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
];
const passByClass = new Map(passEvidenceRows.map((row) => [row.boundary_class, row]));
const preflightByClass = new Map(hostPreflightRows.map((row) => [row.boundary_class, row]));
const dispatchByClass = new Map(dispatchPlanRows.map((row) => [row.boundary_class, row]));
const skipByClass = new Map(skipEvidenceRows.map((row) => [row.boundary_class, row]));
for (const rows of [
executionPlanRows,
passEvidenceRows,
hostPreflightRows,
dispatchPlanRows,
skipEvidenceRows,
]) {
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
throw new Error("browser_blocked_runtime_probe_execution_parity: class order drift");
}
}
for (const execution of executionPlanRows) {
const pass = passByClass.get(execution.boundary_class);
const preflight = preflightByClass.get(execution.boundary_class);
const dispatch = dispatchByClass.get(execution.boundary_class);
const skip = skipByClass.get(execution.boundary_class);
if (!pass || !preflight || !dispatch || !skip) {
throw new Error(`${execution.boundary_class}: missing probe execution parity row`);
}
for (const row of [pass, preflight, dispatch, skip]) {
if (row.boundary_kind !== execution.boundary_kind || row.target !== execution.target) {
throw new Error(`${execution.boundary_class}: probe execution kind/target drift`);
}
}
if (
preflight.blocked !== execution.boundary_class ||
dispatch.blocked !== execution.boundary_class ||
skip.blocked !== execution.boundary_class
) {
throw new Error(`${execution.boundary_class}: probe execution blocked kind drift`);
}
if (
pass.runtime_probe !== execution.runtime_probe ||
skip.runtime_probe !== execution.runtime_probe ||
pass.expected_pass_status !== execution.expected_pass_status
) {
throw new Error(`${execution.boundary_class}: probe execution runtime/pass status drift`);
}
if (preflight.required_native_proof !== execution.required_native_proof) {
throw new Error(`${execution.boundary_class}: probe execution required native proof drift`);
}
for (const row of [preflight, dispatch, skip]) {
if (
row.opt_in_env !== execution.opt_in_env ||
row.execution_command !== execution.execution_command
) {
throw new Error(`${execution.boundary_class}: probe execution opt-in command drift`);
}
}
if (
pass.current_probe_status !== execution.current_probe_status ||
preflight.current_probe_status !== execution.current_probe_status ||
skip.current_probe_status !== execution.current_probe_status
) {
throw new Error(`${execution.boundary_class}: probe execution current status drift`);
}
if (
preflight.runtime_ready !== execution.runtime_ready ||
preflight.source_proof_ready !== execution.source_proof_ready ||
preflight.missing_requirements !== execution.missing_requirements ||
dispatch.missing_host_requirements !== execution.missing_requirements ||
skip.missing_host_requirements !== execution.missing_requirements
) {
throw new Error(`${execution.boundary_class}: probe execution readiness/missing requirement drift`);
}
if (
execution.runtime_ready !== "0" ||
execution.source_proof_ready !== "1" ||
execution.current_probe_status !== "skipped_missing_host_runtime" ||
execution.plan_status !== "blocked_missing_host_runtime" ||
preflight.preflight_status !== "blocked_missing_host_runtime" ||
dispatch.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
dispatch.host_action !== "skip_missing_host_requirements" ||
dispatch.dispatch_allowed !== "0" ||
skip.host_action !== "skip_missing_host_requirements" ||
skip.skip_reason !== "missing_host_requirements"
) {
throw new Error(`${execution.boundary_class}: probe execution host-blocked status drift`);
}
if (
pass.evidence_required_now !== "0" ||
skip.evidence_required_now !== "0" ||
pass.observed_evidence_ready !== "0" ||
skip.observed_evidence_ready !== "0" ||
pass.evidence_status !== "pending_until_native_pass" ||
skip.native_evidence_status !== "pending_until_native_pass" ||
skip.skip_evidence_status !== "skip_valid_until_host_requirements_available"
) {
throw new Error(`${execution.boundary_class}: probe execution evidence status drift`);
}
for (const row of [execution, pass, preflight, dispatch, skip]) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${execution.boundary_class}: probe execution parity must remain disabled`);
}
}
}
}
async function verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceRollup() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv"),
);
if (rows.length !== 1) {
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_rollup: row count drift ${rows.length}`);
}
const row = rows[0];
if (
row.scope !== "blocked_runtime_opt_in_probe_skip_evidence" ||
row.probe_count !== "3" ||
row.skip_count !== "3" ||
row.evidence_required_now_count !== "0" ||
row.observed_evidence_ready_count !== "0" ||
row.skip_reasons !== "missing_host_requirements" ||
row.current_probe_statuses !== "skipped_missing_host_runtime" ||
row.native_evidence_statuses !== "pending_until_native_pass" ||
row.skip_evidence_statuses !== "skip_valid_until_host_requirements_available" ||
row.evidence_rollup_status !== "no_native_pass_evidence_accepted_while_host_blocked"
) {
throw new Error("opt-in probe skip evidence rollup state drift");
}
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
if (!row.skipped_families.includes(boundaryClass)) {
throw new Error(`opt-in probe skip evidence rollup missing ${boundaryClass}`);
}
}
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
if (!row.missing_host_requirements.includes(requirement)) {
throw new Error(`opt-in probe skip evidence rollup missing ${requirement}`);
}
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error("opt-in probe skip evidence rollup must not enable execution or promotion");
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error("opt-in probe skip evidence rollup notes drift");
}
return rows;
}
function verifyBrowserBlockedRuntimeRollupParity({
hostReadinessRollupRows,
dispatchRollupRows,
skipEvidenceRollupRows,
familyHostReadinessRows,
dispatchPlanRows,
skipEvidenceRows,
}) {
if (
hostReadinessRollupRows.length !== 1 ||
dispatchRollupRows.length !== 1 ||
skipEvidenceRollupRows.length !== 1
) {
throw new Error("browser_blocked_runtime_rollup_parity: expected one row per rollup");
}
const host = hostReadinessRollupRows[0];
const dispatch = dispatchRollupRows[0];
const skip = skipEvidenceRollupRows[0];
if (
host.family_count !== String(familyHostReadinessRows.length) ||
dispatch.probe_count !== String(dispatchPlanRows.length) ||
skip.probe_count !== String(skipEvidenceRows.length) ||
host.family_count !== dispatch.probe_count ||
dispatch.probe_count !== skip.probe_count
) {
throw new Error("browser_blocked_runtime_rollup_parity: count drift");
}
if (
host.host_ready_family_count !== dispatch.dispatch_allowed_count ||
host.host_blocked_family_count !== dispatch.dispatch_blocked_count ||
dispatch.dispatch_blocked_count !== skip.skip_count
) {
throw new Error("browser_blocked_runtime_rollup_parity: blocked/allowed count drift");
}
if (
host.blocked_families !== dispatch.blocked_families ||
dispatch.blocked_families !== skip.skipped_families
) {
throw new Error("browser_blocked_runtime_rollup_parity: blocked family drift");
}
if (
host.missing_host_requirements !== dispatch.missing_host_requirements ||
dispatch.missing_host_requirements !== skip.missing_host_requirements
) {
throw new Error("browser_blocked_runtime_rollup_parity: missing requirement drift");
}
if (
host.ready_opt_in_commands !== dispatch.dispatch_allowed_commands ||
host.blocked_opt_in_commands !== dispatch.dispatch_blocked_commands
) {
throw new Error("browser_blocked_runtime_rollup_parity: opt-in command drift");
}
if (
host.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
dispatch.dispatch_status !== "dispatch_blocked_for_all_opt_in_native_probes" ||
skip.evidence_rollup_status !== "no_native_pass_evidence_accepted_while_host_blocked" ||
skip.current_probe_statuses !== "skipped_missing_host_runtime" ||
skip.native_evidence_statuses !== "pending_until_native_pass" ||
skip.skip_evidence_statuses !== "skip_valid_until_host_requirements_available" ||
skip.evidence_required_now_count !== "0" ||
skip.observed_evidence_ready_count !== "0"
) {
throw new Error("browser_blocked_runtime_rollup_parity: host-blocked status drift");
}
for (const row of [host, dispatch, skip]) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error("browser_blocked_runtime_rollup_parity: rollup must remain disabled");
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error("browser_blocked_runtime_rollup_parity: rollup notes drift");
}
}
}
async function verifyBrowserRuntimeBoundaryNativeEvidenceAcceptanceGate() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv"),
);
const expected = new Map([
[
"L4-USER-M-PROCESS",
{
target: "axis/vismach/millturn/example.ngc",
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
missing: "halrun,halcmd,linuxcnc",
},
],
[
"L4-TOOL-DB",
{
target: "axis/db_demo/base.ngc",
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
missing: "linuxcnc,milltask,halcmd",
},
],
[
"L4-PYTHON-REMAP",
{
target: "axis/remap/stop-lookahead/nc_files",
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
missing: "linuxcnc",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_runtime_boundary_native_evidence_acceptance_gate: row count drift ${rows.length}`);
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_class);
if (!expectedRow) {
throw new Error(`browser_runtime_boundary_native_evidence_acceptance_gate: unexpected ${row.boundary_class}`);
}
if (row.target !== expectedRow.target || row.runtime_probe !== expectedRow.runtimeProbe) {
throw new Error(`${row.boundary_class}: native evidence acceptance gate metadata drift`);
}
if (
row.current_probe_status !== "skipped_missing_host_runtime" ||
row.native_evidence_status !== "pending_until_native_pass" ||
row.evidence_required_now !== "0" ||
row.observed_evidence_ready !== "0" ||
row.skip_evidence_status !== "skip_valid_until_host_requirements_available" ||
row.promotion_ready !== "0" ||
row.node_gate_status !== "blocked_until_native_pass_evidence" ||
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
row.native_evidence_gate !== "blocked_until_host_requirements_available" ||
row.evidence_acceptance_allowed !== "0"
) {
throw new Error(`${row.boundary_class}: native evidence acceptance gate status drift`);
}
if (!row.next_action.includes(expectedRow.missing)) {
throw new Error(`${row.boundary_class}: native evidence acceptance gate next action must name missing requirements`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_class}: native evidence acceptance gate must not enable execution or promotion`);
}
if (!row.notes.includes("no_automatic_promotion")) {
throw new Error(`${row.boundary_class}: native evidence acceptance gate notes drift`);
}
}
return rows;
}
async function verifyBrowserNextBoundaryRecommendations() {
const rows = parseTsv(
await fetchText("../../build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv"),
);
const expected = new Map([
[
"external_user_m_process",
{
target: "axis/vismach/millturn/example.ngc",
blocked: "L4-USER-M-PROCESS",
recommendation: "implement_native_m128_m129_state_probe",
primaryArtifact: "user-m-process-native-runtime-probe-gate.tsv",
proofToken: "not_user_m_event_only",
},
],
[
"tool_database_process",
{
target: "axis/db_demo/base.ngc",
blocked: "L4-TOOL-DB",
recommendation: "implement_tooldata_db_protocol_probe",
primaryArtifact: "tool-db-process-native-runtime-probe-gate.tsv",
proofToken: "not_tbl_fallback",
},
],
[
"python_runtime",
{
target: "axis/remap/stop-lookahead/nc_files",
blocked: "L4-PYTHON-REMAP",
recommendation: "implement_minimal_python_runtime_lifecycle_probe",
primaryArtifact: "python-remap-native-runtime-fixture-plan.tsv",
proofToken: "not_js_semantics",
},
],
]);
if (rows.length !== expected.size) {
throw new Error(`browser_next_boundary_recommendations: row count drift ${rows.length}`);
}
if (rows.map((row) => row.priority).join(",") !== "1,2,3") {
throw new Error("browser_next_boundary_recommendations: priority order drift");
}
for (const row of rows) {
const expectedRow = expected.get(row.boundary_kind);
if (!expectedRow) {
throw new Error(`${row.boundary_kind}: unexpected recommendation boundary kind`);
}
if (
row.target !== expectedRow.target ||
row.blocked !== expectedRow.blocked ||
row.recommendation !== expectedRow.recommendation ||
row.primary_artifact !== expectedRow.primaryArtifact
) {
throw new Error(`${row.boundary_kind}: recommendation identity drift`);
}
if (!/^[1-9][0-9]*$/.test(row.artifact_row_count)) {
throw new Error(`${row.boundary_kind}: invalid recommendation artifact row count`);
}
if (
row.promotion_lock_active !== "1" ||
row.contract_ok !== "1" ||
row.execution_enabled !== "0" ||
row.promotion_allowed !== "0"
) {
throw new Error(`${row.boundary_kind}: recommendation must keep active lock and disabled execution`);
}
if (
row.required_native_proof === "-" ||
!row.required_node_proof.includes(expectedRow.proofToken) ||
row.required_browser_proof === "-"
) {
throw new Error(`${row.boundary_kind}: recommendation proof drift`);
}
if (
!row.source_artifacts.includes("next-boundary-worklist.tsv") ||
!row.source_artifacts.includes("blocked-runtime-promotion-lock.tsv")
) {
throw new Error(`${row.boundary_kind}: recommendation lacks source artifact trace`);
}
}
return rows;
}
function verifyBrowserNextBoundaryRecommendationCountParity({
recommendationRows,
userMRuntimeProbeGateRows,
toolDbRuntimeProbeGateRows,
pythonRuntimeContractRows,
}) {
const expectedCounts = new Map([
["external_user_m_process", userMRuntimeProbeGateRows.length],
["tool_database_process", toolDbRuntimeProbeGateRows.length],
["python_runtime", pythonRuntimeContractRows.length],
]);
for (const row of recommendationRows) {
const expectedCount = expectedCounts.get(row.boundary_kind);
if (expectedCount === undefined) {
throw new Error(`${row.boundary_kind}: recommendation count parity unexpected kind`);
}
if (row.artifact_row_count !== String(expectedCount)) {
throw new Error(`${row.boundary_kind}: recommendation artifact count ${row.artifact_row_count} drift from generated source count ${expectedCount}`);
}
if (row.boundary_kind === "external_user_m_process" && !row.source_artifacts.includes("user-m-process-native-runtime-probe-gate.tsv")) {
throw new Error("user-M recommendation lacks runtime probe gate source artifact");
}
if (row.boundary_kind === "tool_database_process" && !row.source_artifacts.includes("tool-db-process-native-runtime-probe-gate.tsv")) {
throw new Error("tool DB recommendation lacks runtime probe gate source artifact");
}
if (row.boundary_kind === "python_runtime" && !row.source_artifacts.includes("python-remap-runtime-contract.tsv")) {
throw new Error("Python recommendation lacks runtime contract source artifact");
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_kind}: recommendation count parity must remain disabled`);
}
}
}
function verifyBrowserNextBoundaryRecommendationSourceArtifactCoverage({
recommendationRows,
wasmArtifactNames,
}) {
const artifactNameSet = new Set(wasmArtifactNames);
const expectedSourceArtifacts = new Map([
[
"external_user_m_process",
[
"next-boundary-worklist.tsv",
"blocked-runtime-promotion-lock.tsv",
"user-m-process-transition-plan.tsv",
"user-m-process-native-runtime-state-plan.tsv",
"user-m-process-native-runtime-probe-gate.tsv",
],
],
[
"tool_database_process",
[
"next-boundary-worklist.tsv",
"blocked-runtime-promotion-lock.tsv",
"tool-db-process-transaction-plan.tsv",
"tool-db-process-native-runtime-readiness.tsv",
"tool-db-process-native-runtime-probe-gate.tsv",
],
],
[
"python_runtime",
[
"next-boundary-worklist.tsv",
"blocked-runtime-promotion-lock.tsv",
"python-remap-runtime-contract.tsv",
"python-remap-native-runtime-fixture-plan.tsv",
"python-remap-native-runtime-probe-gate.tsv",
],
],
]);
for (const row of recommendationRows) {
const sourceArtifacts = row.source_artifacts === "-" ? [] : row.source_artifacts.split(",");
const expectedSources = expectedSourceArtifacts.get(row.boundary_kind);
if (!expectedSources) {
throw new Error(`${row.boundary_kind}: unexpected recommendation source artifact kind`);
}
if (sourceArtifacts.length === 0) {
throw new Error(`${row.boundary_kind}: recommendation lacks source artifacts`);
}
if (sourceArtifacts.join(",") !== expectedSources.join(",")) {
throw new Error(`${row.boundary_kind}: recommendation source artifact set drift`);
}
if (new Set(sourceArtifacts).size !== sourceArtifacts.length) {
throw new Error(`${row.boundary_kind}: duplicate recommendation source artifact`);
}
if (!sourceArtifacts.includes(row.primary_artifact)) {
throw new Error(`${row.boundary_kind}: recommendation primary artifact missing from source artifacts`);
}
for (const artifactName of [row.primary_artifact, ...sourceArtifacts]) {
if (!artifactNameSet.has(artifactName)) {
throw new Error(`${row.boundary_kind}: unknown recommendation source artifact ${artifactName}`);
}
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${row.boundary_kind}: recommendation source artifact coverage must remain disabled`);
}
}
}
function verifyBrowserBlockedRuntimePromotionGateParity({
promotionLockRows,
promotionReadinessRows,
promotionBlockerRows,
postNativePassGateRows,
nativeEvidenceAcceptanceGateRows,
passEvidenceRows,
}) {
const expectedClasses = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
];
const lockByTarget = new Map(promotionLockRows.map((row) => [row.target, row]));
const readinessByClass = new Map(promotionReadinessRows.map((row) => [row.boundary_class, row]));
const blockerByClass = new Map(promotionBlockerRows.map((row) => [row.boundary_class, row]));
const postNativeByClass = new Map(postNativePassGateRows.map((row) => [row.boundary_class, row]));
const evidenceGateByClass = new Map(
nativeEvidenceAcceptanceGateRows.map((row) => [row.boundary_class, row]),
);
const passEvidenceByClass = new Map(passEvidenceRows.map((row) => [row.boundary_class, row]));
for (const rows of [
promotionReadinessRows,
promotionBlockerRows,
postNativePassGateRows,
nativeEvidenceAcceptanceGateRows,
passEvidenceRows,
]) {
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
throw new Error("browser_blocked_runtime_promotion_gate_parity: class order drift");
}
}
for (const boundaryClass of expectedClasses) {
const readiness = readinessByClass.get(boundaryClass);
const blocker = blockerByClass.get(boundaryClass);
const postNative = postNativeByClass.get(boundaryClass);
const evidenceGate = evidenceGateByClass.get(boundaryClass);
const passEvidence = passEvidenceByClass.get(boundaryClass);
if (!readiness || !blocker || !postNative || !evidenceGate || !passEvidence) {
throw new Error(`${boundaryClass}: missing promotion gate parity row`);
}
const lock = lockByTarget.get(readiness.target);
if (!lock) {
throw new Error(`${boundaryClass}: missing promotion lock row for ${readiness.target}`);
}
for (const row of [blocker, postNative, evidenceGate, passEvidence]) {
if (row.boundary_kind !== readiness.boundary_kind || row.target !== readiness.target) {
throw new Error(`${boundaryClass}: promotion gate kind/target drift`);
}
}
for (const row of [blocker, postNative, evidenceGate]) {
if (row.blocked !== readiness.blocked) {
throw new Error(`${boundaryClass}: promotion gate blocked kind drift`);
}
}
if (
readiness.current_probe_status !== passEvidence.current_probe_status ||
readiness.expected_pass_status !== passEvidence.expected_pass_status ||
readiness.native_evidence_status !== passEvidence.evidence_status ||
readiness.native_evidence_ready !== passEvidence.observed_evidence_ready ||
readiness.promotion_lock_active !== lock.lock_active ||
readiness.promotion_ready !== blocker.promotion_ready ||
readiness.promotion_ready !== evidenceGate.promotion_ready
) {
throw new Error(`${boundaryClass}: promotion readiness/pass/lock drift`);
}
if (
postNative.current_probe_status !== readiness.current_probe_status ||
postNative.expected_pass_status !== readiness.expected_pass_status ||
postNative.native_pass_ready !== readiness.native_pass_ready ||
postNative.native_evidence_ready !== readiness.native_evidence_ready ||
postNative.manual_lock_update_required !== readiness.manual_lock_update_required ||
postNative.promotion_lock_active !== readiness.promotion_lock_active
) {
throw new Error(`${boundaryClass}: post-native promotion gate drift`);
}
if (
evidenceGate.current_probe_status !== readiness.current_probe_status ||
evidenceGate.native_evidence_status !== readiness.native_evidence_status ||
evidenceGate.evidence_required_now !== passEvidence.evidence_required_now ||
evidenceGate.observed_evidence_ready !== passEvidence.observed_evidence_ready ||
evidenceGate.node_gate_status !== postNative.node_gate_status ||
evidenceGate.browser_gate_status !== postNative.browser_gate_status
) {
throw new Error(`${boundaryClass}: native evidence acceptance parity drift`);
}
if (
readiness.native_pass_ready !== "0" ||
readiness.native_evidence_ready !== "0" ||
readiness.node_inventory_gate_complete !== "0" ||
readiness.browser_smoke_gate_complete !== "0" ||
readiness.promotion_lock_active !== "1" ||
readiness.manual_lock_update_required !== "1" ||
readiness.promotion_ready !== "0" ||
postNative.node_gate_status !== "blocked_until_native_pass_evidence" ||
postNative.browser_gate_status !== "blocked_until_node_gate_complete" ||
postNative.gate_status !== "blocked_before_native_pass" ||
evidenceGate.native_evidence_gate !== "blocked_until_host_requirements_available" ||
evidenceGate.evidence_acceptance_allowed !== "0"
) {
throw new Error(`${boundaryClass}: promotion gate must remain host-blocked`);
}
for (const blockerKey of [
"native_runtime_probe_not_passed",
"native_pass_evidence_not_ready",
"node_inventory_gate_not_complete",
"browser_smoke_gate_not_complete",
"promotion_lock_active",
"manual_lock_update_required",
]) {
if (!blocker.blocker_keys.includes(blockerKey)) {
throw new Error(`${boundaryClass}: promotion blocker missing ${blockerKey}`);
}
}
for (const row of [readiness, blocker, postNative, evidenceGate, passEvidence, lock]) {
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${boundaryClass}: promotion gate parity must remain disabled`);
}
}
}
}
function verifyBrowserBlockedRuntimeOptInGateParity({
recommendationRows,
executionPlanRows,
promotionReadinessRows,
promotionBlockerRows,
postNativePassGateRows,
hostPreflightRows,
familyHostReadinessRows,
dispatchPlanRows,
skipEvidenceRows,
evidenceAcceptanceGateRows,
}) {
const expectedClasses = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
];
const classToKind = new Map([
["L4-USER-M-PROCESS", "external_user_m_process"],
["L4-TOOL-DB", "tool_database_process"],
["L4-PYTHON-REMAP", "python_runtime"],
]);
const recommendationByClass = new Map(recommendationRows.map((row) => [row.blocked, row]));
const byClass = (rows) => new Map(rows.map((row) => [row.boundary_class, row]));
const executionByClass = byClass(executionPlanRows);
const readinessByClass = byClass(promotionReadinessRows);
const blockerByClass = byClass(promotionBlockerRows);
const postNativeByClass = byClass(postNativePassGateRows);
const preflightByClass = byClass(hostPreflightRows);
const familyReadinessByClass = byClass(familyHostReadinessRows);
const dispatchByClass = byClass(dispatchPlanRows);
const skipEvidenceByClass = byClass(skipEvidenceRows);
const evidenceGateByClass = byClass(evidenceAcceptanceGateRows);
for (const rows of [
executionPlanRows,
promotionReadinessRows,
promotionBlockerRows,
postNativePassGateRows,
hostPreflightRows,
familyHostReadinessRows,
dispatchPlanRows,
skipEvidenceRows,
evidenceAcceptanceGateRows,
]) {
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
throw new Error("browser_blocked_runtime_opt_in_gate_parity: class order drift");
}
}
for (const boundaryClass of expectedClasses) {
const expectedKind = classToKind.get(boundaryClass);
const recommendation = recommendationByClass.get(boundaryClass);
const execution = executionByClass.get(boundaryClass);
const readiness = readinessByClass.get(boundaryClass);
const blocker = blockerByClass.get(boundaryClass);
const postNative = postNativeByClass.get(boundaryClass);
const preflight = preflightByClass.get(boundaryClass);
const familyReadiness = familyReadinessByClass.get(boundaryClass);
const dispatch = dispatchByClass.get(boundaryClass);
const skipEvidence = skipEvidenceByClass.get(boundaryClass);
const evidenceGate = evidenceGateByClass.get(boundaryClass);
for (const [label, row] of [
["recommendation", recommendation],
["execution", execution],
["readiness", readiness],
["blocker", blocker],
["postNative", postNative],
["preflight", preflight],
["familyReadiness", familyReadiness],
["dispatch", dispatch],
["skipEvidence", skipEvidence],
["evidenceGate", evidenceGate],
]) {
if (!row) {
throw new Error(`${boundaryClass}: missing ${label} row for opt-in parity`);
}
}
if (recommendation.boundary_kind !== expectedKind) {
throw new Error(`${boundaryClass}: recommendation kind drift`);
}
for (const row of [
execution,
readiness,
blocker,
postNative,
preflight,
familyReadiness,
dispatch,
skipEvidence,
evidenceGate,
]) {
if (
row.boundary_kind !== expectedKind ||
row.target !== recommendation.target ||
(row.blocked || row.boundary_class) !== recommendation.blocked
) {
throw new Error(`${boundaryClass}: opt-in gate identity drift`);
}
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
throw new Error(`${boundaryClass}: opt-in gate parity must remain disabled`);
}
}
if (
execution.required_native_proof !== recommendation.required_native_proof ||
postNative.required_node_proof !== recommendation.required_node_proof ||
postNative.required_browser_proof !== recommendation.required_browser_proof ||
preflight.required_native_proof !== execution.required_native_proof
) {
throw new Error(`${boundaryClass}: opt-in gate proof drift`);
}
if (
preflight.opt_in_env !== execution.opt_in_env ||
dispatch.opt_in_env !== execution.opt_in_env ||
skipEvidence.opt_in_env !== execution.opt_in_env ||
preflight.execution_command !== execution.execution_command ||
dispatch.execution_command !== execution.execution_command ||
skipEvidence.execution_command !== execution.execution_command
) {
throw new Error(`${boundaryClass}: opt-in gate command drift`);
}
if (
preflight.current_probe_status !== execution.current_probe_status ||
readiness.current_probe_status !== execution.current_probe_status ||
postNative.current_probe_status !== execution.current_probe_status ||
evidenceGate.current_probe_status !== execution.current_probe_status ||
familyReadiness.current_probe_status !== execution.current_probe_status
) {
throw new Error(`${boundaryClass}: opt-in gate probe status drift`);
}
if (
preflight.missing_requirements !== execution.missing_requirements ||
dispatch.missing_host_requirements !== execution.missing_requirements ||
skipEvidence.missing_host_requirements !== execution.missing_requirements ||
blocker.missing_runtime_requirements !== execution.missing_requirements
) {
throw new Error(`${boundaryClass}: opt-in gate missing requirement drift`);
}
if (
evidenceGate.evidence_acceptance_allowed !== "0" ||
dispatch.dispatch_allowed !== "0" ||
readiness.promotion_lock_active !== "1" ||
postNative.promotion_lock_active !== "1" ||
evidenceGate.native_evidence_gate.indexOf("blocked") === -1 ||
!blocker.blocker_keys.includes("promotion_lock_active") ||
!blocker.blocker_keys.includes("manual_lock_update_required") ||
!blocker.blocker_keys.includes("host_runtime_requirements_missing")
) {
throw new Error(`${boundaryClass}: opt-in gate must remain blocked`);
}
}
}
function verifyExpectedFileError(fixtureName, output, expectedText) {
if (!output.includes("file_open=0")) {
throw new Error(`${fixtureName}: missing file_open=0`);
}
const expectedLines = expectedText.split("\n").filter(Boolean);
for (const expectedLine of expectedLines) {
if (expectedLine.startsWith("absent=")) {
const forbidden = expectedLine.slice("absent=".length);
if (output.includes(forbidden)) {
throw new Error(`${fixtureName}: unexpected ${forbidden}`);
}
continue;
}
if (expectedLine.startsWith("error_text=")) {
const message = expectedLine.slice("error_text=".length);
if (!output.includes(`file_error_text=${message}`)) {
throw new Error(`${fixtureName}: missing file error ${message}`);
}
continue;
}
if (expectedLine.startsWith("canon_event=") && !output.includes(expectedLine)) {
throw new Error(`${fixtureName}: missing ${expectedLine}`);
}
}
}
function verifyBrowserIniContextStagingPlan() {
const plan = planIniFileContextStaging({
manifestText: [
"tests/browser-staging/example/test.ini",
"tests/browser-staging/example/test.ngc",
"tests/browser-staging/example/test.tbl",
"tests/browser-staging/example/startup.var",
"tests/browser-staging/example/subs/helper.ngc",
"tests/browser-staging/example/subs/rm145.ngc",
"tests/browser-staging/example/more-subs/other.ngc",
"tests/browser-staging/example/M145",
].join("\n"),
sourceRootRel: "tests/browser-staging/example",
iniFile: "test.ini",
iniText: [
"[DISPLAY]",
"OPEN_FILE = test.ngc",
"[RS274NGC]",
"PARAMETER_FILE = startup.var",
"SUBROUTINE_PATH = subs:more-subs",
"USER_M_PATH = .",
"REMAP = M145 modalgroup=10 ngc=rm145",
"[EMCIO]",
"TOOL_TABLE = test.tbl",
].join("\n"),
wasmDir: "/work/browser-staging/example",
});
const actual = plan.files
.map((file) => [file.sourceRel, file.wasmPath, file.executable])
.sort((a, b) => a[0].localeCompare(b[0]));
const expected = [
["tests/browser-staging/example/M145", "/work/browser-staging/example/M145", true],
[
"tests/browser-staging/example/more-subs/other.ngc",
"/work/browser-staging/example/more-subs/other.ngc",
false,
],
[
"tests/browser-staging/example/startup.var",
"/work/browser-staging/example/startup.var",
false,
],
[
"tests/browser-staging/example/subs/helper.ngc",
"/work/browser-staging/example/subs/helper.ngc",
false,
],
[
"tests/browser-staging/example/subs/rm145.ngc",
"/work/browser-staging/example/subs/rm145.ngc",
false,
],
["tests/browser-staging/example/test.ini", "/work/browser-staging/example/test.ini", false],
["tests/browser-staging/example/test.ngc", "/work/browser-staging/example/test.ngc", false],
["tests/browser-staging/example/test.tbl", "/work/browser-staging/example/test.tbl", false],
];
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`browser_ini_context_staging_plan: ${JSON.stringify(actual)}`);
}
}
async function writeFetchedTextFile(interp, sourcePath, wasmPath, options = {}) {
interp.writeTextFile(wasmPath, await fetchText(sourcePath));
if (options.executable) {
interp.module.FS.chmod(wasmPath, 0o755);
}
}
async function writeLocalIniFixture(interp, filename, wasmPath, expectedWasmPath) {
if (wasmPath !== expectedWasmPath) {
throw new Error(`browser_${filename}_local_ini_staging: path drift`);
}
interp.writeTextFile(wasmPath, await fetchText(`../fixtures/ini/${filename}`));
return wasmPath;
}
async function writeLocalGcodeFixture(interp, fixtureName, wasmPath, expectedWasmPath) {
if (wasmPath !== expectedWasmPath) {
throw new Error(`browser_${fixtureName}_local_gcode_staging: path drift`);
}
interp.writeTextFile(wasmPath, await fetchText(`../fixtures/gcode/${fixtureName}.ngc`));
return wasmPath;
}
async function writeLocalErrorGcodeFixture(interp, fixtureName, wasmPath, expectedWasmPath) {
if (wasmPath !== expectedWasmPath) {
throw new Error(`browser_${fixtureName}_local_error_gcode_staging: path drift`);
}
interp.writeTextFile(wasmPath, await fetchText(`../fixtures/gcode_errors/${fixtureName}.ngc`));
return wasmPath;
}
async function writeIniToolTableFixtureStaging(interp, wasmDir, expectedWasmDir) {
if (wasmDir !== expectedWasmDir) {
throw new Error("browser_ini_tool_table_staging_root: path drift");
}
const files = ["ini_tool_table.ini", "ini_tool_table.tbl", "ini_tool_table.ngc"];
if (files.length !== new Set(files).size) {
throw new Error("browser_ini_tool_table_staging_manifest: list drift");
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `${expectedWasmDir}/${file}`) {
throw new Error(`browser_ini_tool_table_${file}_staging: path drift`);
}
}
await writeLocalIniFixture(
interp,
"ini_tool_table.ini",
`${wasmDir}/ini_tool_table.ini`,
`${expectedWasmDir}/ini_tool_table.ini`,
);
await writeLocalIniFixture(
interp,
"ini_tool_table.tbl",
`${wasmDir}/ini_tool_table.tbl`,
`${expectedWasmDir}/ini_tool_table.tbl`,
);
await writeLocalGcodeFixture(
interp,
"ini_tool_table",
`${wasmDir}/ini_tool_table.ngc`,
`${expectedWasmDir}/ini_tool_table.ngc`,
);
return {
iniPath: `${wasmDir}/ini_tool_table.ini`,
programPath: `${wasmDir}/ini_tool_table.ngc`,
};
}
function writeG92PersistenceStaging(interp, wasmDir, expectedWasmDir) {
if (wasmDir !== expectedWasmDir) {
throw new Error("browser_g92_persistence_staging_root: path drift");
}
const files = ["test.ngc", "startup.var", "persist.ini", "disabled.ini"];
if (files.length !== new Set(files).size) {
throw new Error("browser_g92_persistence_staging_manifest: list drift");
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `${expectedWasmDir}/${file}`) {
throw new Error(`browser_g92_persistence_${file}_staging: path drift`);
}
}
const programPath = `${wasmDir}/test.ngc`;
const parameterPath = `${wasmDir}/startup.var`;
const iniPath = `${wasmDir}/persist.ini`;
const disabledIniPath = `${wasmDir}/disabled.ini`;
interp.writeTextFile(
programPath,
[
"(PRINT,Startup G92 params: #5210 #5211 #5212 #5213)",
"M30",
"",
].join("\n"),
);
interp.writeTextFile(
parameterPath,
[
"5210 1",
"5211 1.25",
"5212 2.5",
"5213 3.75",
"5220 1",
"",
].join("\n"),
);
interp.writeTextFile(
iniPath,
[
"[RS274NGC]",
"PARAMETER_FILE = startup.var",
"[TRAJ]",
"COORDINATES = X Y Z",
"",
].join("\n"),
);
interp.writeTextFile(
disabledIniPath,
[
"[RS274NGC]",
"PARAMETER_FILE = startup.var",
"DISABLE_G92_PERSISTENCE = 1",
"[TRAJ]",
"COORDINATES = X Y Z",
"",
].join("\n"),
);
return { disabledIniPath, iniPath, parameterPath, programPath };
}
function generatedParameterPath(wasmPath, expectedWasmPath) {
if (wasmPath !== expectedWasmPath) {
throw new Error(`browser_parameter_fixture_${expectedWasmPath}_staging: path drift`);
}
return wasmPath;
}
function writeGeneratedParameterFixture(interp, wasmPath, expectedWasmPath, text) {
const checkedPath = generatedParameterPath(wasmPath, expectedWasmPath);
interp.writeTextFile(checkedPath, text);
return checkedPath;
}
function assertOpfsParameterStaging(label, parameters, expectedOpfsPath, expectedWasmPath, expectedBackupOpfsPath = undefined) {
if (parameters.opfsPath !== expectedOpfsPath) {
throw new Error(`${label}: unexpected parameter OPFS path ${parameters.opfsPath}`);
}
generatedParameterPath(parameters.wasmPath, expectedWasmPath);
if (expectedBackupOpfsPath !== undefined && parameters.backupOpfsPath !== expectedBackupOpfsPath) {
throw new Error(`${label}: unexpected parameter backup OPFS path ${parameters.backupOpfsPath}`);
}
}
function assertOpfsIniStaging(label, iniFile, expectedOpfsPath, expectedWasmPath) {
if (iniFile.opfsPath !== expectedOpfsPath) {
throw new Error(`${label}: unexpected INI OPFS path ${iniFile.opfsPath}`);
}
if (iniFile.wasmPath !== expectedWasmPath) {
throw new Error(`${label}: unexpected INI WASM path ${iniFile.wasmPath}`);
}
}
function assertMachineTextStorePaths(label, paths, machineId) {
const expectedPaths = {
ini: `linuxcnc/machines/${machineId}/machine.ini`,
toolTable: `linuxcnc/machines/${machineId}/tool.tbl`,
parameters: `linuxcnc/machines/${machineId}/linuxcnc.var`,
};
for (const [name, expectedPath] of Object.entries(expectedPaths)) {
if (paths[name] !== expectedPath) {
throw new Error(`${label}: unexpected ${name} OPFS path ${paths[name]}`);
}
}
}
function generatedToolTablePath(wasmPath, expectedWasmPath) {
if (wasmPath !== expectedWasmPath) {
throw new Error(`browser_tool_table_fixture_${expectedWasmPath}: path drift`);
}
return wasmPath;
}
function writeGeneratedToolTableFixture(interp, wasmPath, expectedWasmPath, text) {
const checkedPath = generatedToolTablePath(wasmPath, expectedWasmPath);
interp.writeTextFile(checkedPath, text);
return checkedPath;
}
function assertOpfsToolTableStaging(label, toolTable, expectedOpfsPath, expectedWasmPath) {
if (toolTable.opfsPath !== expectedOpfsPath) {
throw new Error(`${label}: unexpected tool table OPFS path ${toolTable.opfsPath}`);
}
generatedToolTablePath(toolTable.wasmPath, expectedWasmPath);
}
function opfsMachineFilePath(machineId, filename) {
return `linuxcnc/machines/${machineId}/${filename}`;
}
function assertOpfsSavedParameterStaging(label, parameters, machineId, filename, expectedWasmPath) {
const expectedOpfsPath = opfsMachineFilePath(machineId, filename);
assertOpfsParameterStaging(
label,
parameters,
expectedOpfsPath,
expectedWasmPath,
`${expectedOpfsPath}.bak`,
);
}
function assertOpfsSavedToolTableStaging(label, toolTable, machineId, filename, expectedWasmPath) {
assertOpfsToolTableStaging(
label,
toolTable,
opfsMachineFilePath(machineId, filename),
expectedWasmPath,
);
}
async function verifyOpfsReadback(label, opfsPath, expectedText) {
verifyExpectedOutput(label, await loadTextFile(opfsPath), expectedText);
}
async function writeOpfsMachineFile(machineId, filename, text) {
const opfsPath = opfsMachineFilePath(machineId, filename);
await saveTextFile(opfsPath, text);
return opfsPath;
}
function opfsCustomSessionWasmPath(filename) {
return `/work/${filename}`;
}
function opfsSessionWasmPaths(ini, parameters, toolTable) {
return { ini, parameters, toolTable };
}
function opfsCustomSessionPaths(machineId, parameterFilename, toolTableFilename, iniWasmPath) {
return {
opfs: {
parameters: opfsMachineFilePath(machineId, parameterFilename),
toolTable: opfsMachineFilePath(machineId, toolTableFilename),
},
wasm: {
ini: iniWasmPath,
parameters: opfsCustomSessionWasmPath(parameterFilename),
toolTable: opfsCustomSessionWasmPath(toolTableFilename),
},
};
}
function assertOpfsSessionStaging(label, session, machineId, expectedPaths) {
if (session.machineId !== machineId) {
throw new Error(`${label}: unexpected machine id ${session.machineId}`);
}
const expectedOpfsPaths = {
ini: `linuxcnc/machines/${machineId}/machine.ini`,
parameters: `linuxcnc/machines/${machineId}/linuxcnc.var`,
toolTable: `linuxcnc/machines/${machineId}/tool.tbl`,
...(expectedPaths.opfs ?? {}),
};
assertOpfsIniStaging(
`${label}_ini`,
session.ini,
expectedOpfsPaths.ini,
expectedPaths.wasm.ini,
);
assertOpfsToolTableStaging(
`${label}_tool_table`,
session.toolTable,
expectedOpfsPaths.toolTable,
expectedPaths.wasm.toolTable,
);
assertOpfsParameterStaging(
`${label}_parameters`,
session.parameters,
expectedOpfsPaths.parameters,
expectedPaths.wasm.parameters,
);
}
async function verifyOpfsInvalidIniSessionRejects(label, interp, iniSdk, machineId, iniLines, expectedPattern) {
const machineTextPaths = await saveMachineTextFiles(machineId, {
ini: iniLines.join("\n"),
});
assertMachineTextStorePaths(`${label}_machine_text_files_staging`, machineTextPaths, machineId);
const sessionOptions = {
iniSdk,
iniWasmPath: `/work/${machineId}.ini`,
};
assertOpfsIniStaging(
`${label}_ini_staging`,
{
opfsPath: `linuxcnc/machines/${machineId}/machine.ini`,
wasmPath: sessionOptions.iniWasmPath,
},
`linuxcnc/machines/${machineId}/machine.ini`,
`/work/${machineId}.ini`,
);
await verifyRejects(
label,
() => loadMachineSessionFromOpfs(interp, machineId, sessionOptions),
expectedPattern,
);
}
function assertSimConfigStagingPlan(machineRel, plan, expectedWasmDir) {
if (plan.wasmDir !== expectedWasmDir) {
throw new Error(`${machineRel}: browser sim staging root drift`);
}
if (!plan.iniPath.startsWith(`${expectedWasmDir}/`)) {
throw new Error(`${machineRel}: browser sim INI staging path drift`);
}
if (plan.programPath !== null && !plan.programPath.startsWith(`${expectedWasmDir}/`)) {
throw new Error(`${machineRel}: browser sim program staging path drift`);
}
const paths = [];
for (const file of plan.files) {
if (file.path !== file.wasmPath) {
throw new Error(`${file.sourceRel}: browser sim path/wasmPath drift`);
}
if (!file.wasmPath.startsWith(`${expectedWasmDir}/`)) {
throw new Error(`${file.sourceRel}: browser sim file staging root drift`);
}
if (!file.sourceRel.startsWith("configs/sim/")) {
throw new Error(`${file.sourceRel}: browser sim source root drift`);
}
if (paths.includes(file.wasmPath)) {
throw new Error(`${machineRel}: duplicate browser sim staging path ${file.wasmPath}`);
}
paths.push(file.wasmPath);
}
}
async function loadSimMachineFiles(machineRel, iniFile, programFile = null) {
const iniText = await fetchText(
`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`,
);
const expectedWasmDir = `/work/browser-sim/${machineRel}`;
const plan = planSimConfigStaging({
manifestText: await fetchText("../../tools/source-manifest.txt"),
machineRel,
iniFile,
iniText,
programFile,
wasmDir: expectedWasmDir,
});
assertSimConfigStagingPlan(machineRel, plan, expectedWasmDir);
return {
...plan,
files: await Promise.all(plan.files.map(async (file) => ({
path: file.path,
text: await fetchText(`../../vendor/linuxcnc/${file.sourceRel}`),
executable: file.executable,
}))),
};
}
async function verifySimBoundaryDeclaration(name, options) {
const {
machineRel,
iniFile,
executionFiles = [],
expectedDependencies,
expectedProcesses,
expectedPythonUiReferences = [],
expectedPythonRemapReferences = [],
expectedCoverage,
fullProcessCoverage = false,
} = options;
const boundary = analyzeIniRuntimeBoundaries({
manifestText: await getSourceManifestText(),
sourceRootRel: `configs/sim/${machineRel}`,
sourceSearchRootRel: "configs/sim",
iniFile,
iniText: await fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`),
executionTexts: await Promise.all(executionFiles.map((file) =>
fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${file}`),
)),
});
if (boundary.recommendedBlockedKind !== "-") {
throw new Error(`${name}: unexpected hard block ${boundary.recommendedBlockedKind}`);
}
if (!["file", "remap"].includes(expectedCoverage)) {
throw new Error(`${name}: representative coverage must be file or remap`);
}
if (fullProcessCoverage) {
throw new Error(`${name}: browser sim representatives must not claim full-process coverage`);
}
const actualDependencies = boundary.dependencies.join(",");
const expectedDependencyText = expectedDependencies.join(",");
if (actualDependencies !== expectedDependencyText) {
throw new Error(`${name}: dependency drift ${actualDependencies}`);
}
for (const [field, expected] of Object.entries(expectedProcesses)) {
if (boundary[field].requiresProcess !== expected) {
throw new Error(`${name}: ${field}.requiresProcess drift`);
}
}
const actualPythonUiReferences = boundary.pythonRuntime.uiReferences.join(",");
const expectedPythonUiReferenceText = expectedPythonUiReferences.join(",");
if (actualPythonUiReferences !== expectedPythonUiReferenceText) {
throw new Error(`${name}: Python UI/DB reference drift ${actualPythonUiReferences}`);
}
const actualPythonRemapReferences = boundary.pythonRuntime.remapReferences.join(",");
const expectedPythonRemapReferenceText = expectedPythonRemapReferences.join(",");
if (actualPythonRemapReferences !== expectedPythonRemapReferenceText) {
throw new Error(`${name}: Python remap reference drift ${actualPythonRemapReferences}`);
}
}
async function verifySimHardBlockedBoundary(name, options) {
const {
machineRel,
iniFile,
executionFiles,
expectedBlockedKind,
expectedDependencies,
expectedProcesses,
expectedUserMCodes = [],
expectedUnstagedUserMCodes = expectedUserMCodes,
expectedPythonUiReferences = [],
expectedPythonRemapReferences = [],
expectedVendoredUserMCount = 0,
expectedRequiresExternalUserM = false,
expectedToolDatabaseProgram = "",
} = options;
const boundary = analyzeIniRuntimeBoundaries({
manifestText: await getSourceManifestText(),
sourceRootRel: `configs/sim/${machineRel}`,
sourceSearchRootRel: "configs/sim",
iniFile,
iniText: await fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`),
executionTexts: await Promise.all(executionFiles.map((file) =>
fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${file}`),
)),
});
if (boundary.recommendedBlockedKind !== expectedBlockedKind) {
throw new Error(`${name}: blocked kind drift ${boundary.recommendedBlockedKind}`);
}
const actualDependencies = boundary.dependencies.join(",");
const expectedDependencyText = expectedDependencies.join(",");
if (actualDependencies !== expectedDependencyText) {
throw new Error(`${name}: dependency drift ${actualDependencies}`);
}
const actualUserMCodes = boundary.userMRuntime.executionCodes.join(",");
const expectedUserMCodeText = expectedUserMCodes.join(",");
if (actualUserMCodes !== expectedUserMCodeText) {
throw new Error(`${name}: user-M execution code drift ${actualUserMCodes}`);
}
const actualUnstagedUserMCodes = boundary.userMRuntime.unstagedExecutionCodes.join(",");
const expectedUnstagedUserMCodeText = expectedUnstagedUserMCodes.join(",");
if (actualUnstagedUserMCodes !== expectedUnstagedUserMCodeText) {
throw new Error(`${name}: unstaged user-M execution code drift ${actualUnstagedUserMCodes}`);
}
if (boundary.userMRuntime.vendoredExecutableCount !== expectedVendoredUserMCount) {
throw new Error(`${name}: vendored user-M count drift`);
}
if (boundary.userMRuntime.requiresExternalProcess !== expectedRequiresExternalUserM) {
throw new Error(`${name}: external user-M process requirement drift`);
}
if (boundary.toolDatabaseProgram !== expectedToolDatabaseProgram) {
throw new Error(`${name}: DB_PROGRAM drift ${boundary.toolDatabaseProgram}`);
}
for (const [field, expected] of Object.entries(expectedProcesses)) {
if (boundary[field].requiresProcess !== expected) {
throw new Error(`${name}: ${field}.requiresProcess drift`);
}
}
const actualPythonUiReferences = boundary.pythonRuntime.uiReferences.join(",");
const expectedPythonUiReferenceText = expectedPythonUiReferences.join(",");
if (actualPythonUiReferences !== expectedPythonUiReferenceText) {
throw new Error(`${name}: Python UI/DB reference drift ${actualPythonUiReferences}`);
}
const actualPythonRemapReferences = boundary.pythonRuntime.remapReferences.join(",");
const expectedPythonRemapReferenceText = expectedPythonRemapReferences.join(",");
if (actualPythonRemapReferences !== expectedPythonRemapReferenceText) {
throw new Error(`${name}: Python remap reference drift ${actualPythonRemapReferences}`);
}
}
function wrapSimProgramFiles(machine, wrappedName) {
const sourceFile = machine.files.find((file) => file.path === machine.programPath);
if (!sourceFile) {
throw new Error(`${machine.programPath}: wrapped source file`);
}
return {
...machine,
programPath: `${machine.wasmDir}/${wrappedName}`,
files: [
...machine.files,
{
path: `${machine.wasmDir}/${wrappedName}`,
text: `${sourceFile.text}\nM2\n`,
executable: false,
},
],
};
}
let sourceManifestText = null;
async function getSourceManifestText() {
sourceManifestText ??= await fetchText("../../tools/source-manifest.txt");
return sourceManifestText;
}
async function stageInterpIniContext(interp, name, programFile = "test.ngc") {
const sourceRootRel = `tests/interp/${name}`;
const iniText = await fetchText(
`../../vendor/linuxcnc/${sourceRootRel}/test.ini`,
);
const plan = planIniFileContextStaging({
manifestText: await getSourceManifestText(),
sourceRootRel,
iniFile: "test.ini",
iniText,
programFile,
wasmDir: `/work/browser-interp/${name}`,
});
for (const file of plan.files) {
await writeFetchedTextFile(
interp,
`../../vendor/linuxcnc/${file.sourceRel}`,
file.wasmPath,
{ executable: file.executable },
);
}
return plan;
}
function assertFiveAxisMachineFilesStaging(name, files, wasmDir, expectedWasmDir) {
if (wasmDir !== expectedWasmDir) {
throw new Error(`browser_fiveaxis_${name}_staging_root: path drift`);
}
if (files.length === 0 || new Set(files).size !== files.length) {
throw new Error(`browser_fiveaxis_${name}_staging_manifest: list drift`);
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `${expectedWasmDir}/${file}`) {
throw new Error(`browser_fiveaxis_${name}_${file}_staging_manifest: path drift`);
}
}
}
async function writeFiveAxisMachineFiles(interp, name, sourceDir, wasmDir, expectedWasmDir, files) {
assertFiveAxisMachineFilesStaging(name, files, wasmDir, expectedWasmDir);
for (const file of files) {
await writeFetchedTextFile(interp, `${sourceDir}/${file}`, `${wasmDir}/${file}`);
}
return wasmDir;
}
async function writeFiveAxisTrtMachineFiles(interp) {
const sourceDir =
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting";
const wasmDir = "/work/browser-fiveaxis/table-rotary-tilting";
const files = [
"xyzac-trt.ini",
"xyzbc-trt.ini",
"xyzac-trt.tbl",
"xyzbc-trt.tbl",
"remap_subs/428remap.ngc",
"remap_subs/429remap.ngc",
"remap_subs/430remap.ngc",
"remap_subs/centering.ngc",
"remap_subs/helix_ac.ngc",
"remap_subs/helix_bc.ngc",
"remap_subs/xyzac_switchkins_sub.ngc",
"remap_subs/xyzbc_switchkins_sub.ngc",
"demos/xyzac_switchkins.ngc",
"demos/xyzbc_switchkins.ngc",
"demos/xyzac_switchkins_test_1.ngc",
"demos/xyzac_switchkins_test_2.ngc",
"demos/xyzac_switchkins_test_3.ngc",
"demos/boat-xyzac.ngc",
"demos/boat-xyzbc.ngc",
"demos/impeller-7bl-xyzac.ngc",
];
return writeFiveAxisMachineFiles(
interp,
"table-rotary-tilting",
sourceDir,
wasmDir,
"/work/browser-fiveaxis/table-rotary-tilting",
files,
);
}
async function writeFiveAxisTdrMachineFiles(interp) {
const sourceDir =
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary";
const wasmDir = "/work/browser-fiveaxis/table-dual-rotary";
const files = [
"xyzab-tdr.ini",
"xyzab-tdr.tbl",
"remap_subs/428remap.ngc",
"remap_subs/429remap.ngc",
"demos/xyzab-tdr-demo.ngc",
];
return writeFiveAxisMachineFiles(
interp,
"table-dual-rotary",
sourceDir,
wasmDir,
"/work/browser-fiveaxis/table-dual-rotary",
files,
);
}
async function writeFiveAxisBridgeMillMachineFiles(interp) {
const sourceDir =
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/bridgemill";
const wasmDir = "/work/browser-fiveaxis/bridgemill";
const files = [
"5axis.ini",
"5axis.tbl",
"remap_subs/428remap.ngc",
"remap_subs/429remap.ngc",
"remap_subs/430remap.ngc",
"5axisgui.ngc",
];
return writeFiveAxisMachineFiles(
interp,
"bridgemill",
sourceDir,
wasmDir,
"/work/browser-fiveaxis/bridgemill",
files,
);
}
async function writeRemapRegressionFiles(interp, name, files) {
const sourceDir = `../../vendor/linuxcnc/tests/remap/${name}`;
const wasmDir = `/work/browser-remap/${name}`;
for (const filename of files) {
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
}
return wasmDir;
}
function assertRemapRegressionFilesStaging(name, files, wasmDir) {
if (files.length === 0 || new Set(files).size !== files.length) {
throw new Error(`browser_remap_${name}_fixture_coverage: list drift`);
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `/work/browser-remap/${name}/${file}`) {
throw new Error(`browser_remap_${name}_${file}_staging_manifest: list drift`);
}
}
}
function assertRemapRegressionGeneratedFileStaging(name, filename, wasmPath) {
if (wasmPath !== `/work/browser-remap/${name}/${filename}`) {
throw new Error(`browser_remap_${name}_${filename}_generated_staging: path drift`);
}
}
function assertRemapRegressionUnstagedOverlap(name, files, expectedUnstagedFiles) {
if (expectedUnstagedFiles.some((file) => files.includes(file))) {
throw new Error(`browser_remap_${name}_unstaged_fixture_overlap: list drift`);
}
}
async function writeInterpRegressionFile(interp, name, filename) {
const sourceDir = `../../vendor/linuxcnc/tests/interp/${name}`;
const wasmDir = `/work/browser-interp/${name}`;
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
return `${wasmDir}/${filename}`;
}
function assertInterpRegressionFileStaging(name, filename, wasmPath) {
if (wasmPath !== `/work/browser-interp/${name}/${filename}`) {
throw new Error(`browser_interp_${name}_${filename}_staging_manifest: list drift`);
}
}
function assertInterpRegressionFilesStaging(name, files, wasmDir) {
if (files.length === 0 || new Set(files).size !== files.length) {
throw new Error(`browser_interp_${name}_fixture_coverage: list drift`);
}
for (const file of files) {
assertInterpRegressionFileStaging(name, file, `${wasmDir}/${file}`);
}
}
function stripInterpIniContextStagingPrefix(name, wasmPath) {
const prefix = `/work/browser-interp/${name}/`;
if (!wasmPath.startsWith(prefix)) {
throw new Error(`browser_interp_${name}_staging_prefix: path drift ${wasmPath}`);
}
return wasmPath.slice(prefix.length);
}
function assertInterpIniContextNgcStaging(name, files, plan) {
const stagedNgcFiles = plan.files
.filter((file) => file.sourceRel.endsWith(".ngc"))
.map((file) => stripInterpIniContextStagingPrefix(name, file.wasmPath))
.sort();
if (stagedNgcFiles.join(",") !== files.join(",")) {
throw new Error(`browser_interp_${name}_staging_manifest: list drift`);
}
}
async function writeInterpRegressionFiles(interp, name, files) {
const sourceDir = `../../vendor/linuxcnc/tests/interp/${name}`;
const wasmDir = `/work/browser-interp/${name}`;
for (const filename of files) {
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
}
return wasmDir;
}
async function writeCcompRegressionFiles(interp, name) {
const sourceDir = `../../vendor/linuxcnc/tests/ccomp/${name}`;
const wasmDir = `/work/browser-ccomp/${name}`;
await writeFetchedTextFile(interp, `${sourceDir}/test.ngc`, `${wasmDir}/test.ngc`);
await writeFetchedTextFile(interp, `${sourceDir}/test.tbl`, `${wasmDir}/test.tbl`);
interp.writeTextFile(
`${wasmDir}/test.ini`,
[
"[EMCIO]",
"TOOL_TABLE = test.tbl",
"[TRAJ]",
"COORDINATES = X Y Z A B C U V W",
"",
].join("\n"),
);
return wasmDir;
}
const ccompRegressionFiles = [
"test.ngc",
"test.tbl",
];
function assertCcompRegressionFilesStaging(name, wasmDir) {
if (ccompRegressionFiles.length !== 2 || new Set(ccompRegressionFiles).size !== ccompRegressionFiles.length) {
throw new Error("browser_interp_ccomp_fixture_coverage: list drift");
}
for (const file of ccompRegressionFiles) {
if (`${wasmDir}/${file}` !== `/work/browser-ccomp/${name}/${file}`) {
throw new Error(`browser_interp_ccomp_${name}_${file}_staging_manifest: list drift`);
}
}
}
async function writeG10RegressionFiles(interp, name, files) {
const sourceDir = `../../vendor/linuxcnc/tests/interp/g10/${name}`;
const wasmDir = `/work/browser-interp/g10/${name}`;
for (const filename of files) {
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
}
if (files.includes("test.tbl")) {
interp.writeTextFile(
`${wasmDir}/test.ini`,
[
"[EMCIO]",
"TOOL_TABLE = test.tbl",
"[TRAJ]",
"COORDINATES = X Y Z A B C U V W",
"",
].join("\n"),
);
}
return wasmDir;
}
function assertG10RegressionFilesStaging(name, files, wasmDir) {
if (files.length === 0 || new Set(files).size !== files.length) {
throw new Error(`browser_interp_g10_${name}_fixture_coverage: list drift`);
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `/work/browser-interp/g10/${name}/${file}`) {
throw new Error(`browser_interp_g10_${name}_${file}_staging_manifest: list drift`);
}
}
}
function assertVendorNcFileStaging(filename, wasmPath, expectedRoot) {
if (wasmPath !== `${expectedRoot}/${filename}`) {
throw new Error(`browser_nc_files_${filename}_staging_manifest: path drift`);
}
}
function assertVendorNcFileContextStaging(filename, wasmDir, files) {
const expectedRoot = "/work/browser-nc-files-context";
const expectedFiles = [filename, "tool.tbl", "nc_files_context.ini"];
if (wasmDir !== expectedRoot) {
throw new Error(`browser_nc_files_${filename}_context_root: path drift`);
}
if (
files.length !== expectedFiles.length ||
new Set(files).size !== files.length ||
files.join(",") !== expectedFiles.join(",")
) {
throw new Error(`browser_nc_files_${filename}_context_manifest: list drift`);
}
for (const file of files) {
if (`${wasmDir}/${file}` !== `${expectedRoot}/${file}`) {
throw new Error(`browser_nc_files_${filename}_${file}_context_staging: path drift`);
}
}
}
async function runVendorNcFile(interp, filename) {
const wasmPath = `/work/browser-nc-files/${filename}`;
assertVendorNcFileStaging(filename, wasmPath, "/work/browser-nc-files");
await writeFetchedTextFile(
interp,
`../../vendor/linuxcnc/nc_files/${filename}`,
wasmPath,
);
return interp.runFile(wasmPath);
}
async function runVendorNcFileWithToolTable(interp, filename) {
const wasmDir = "/work/browser-nc-files-context";
const files = [filename, "tool.tbl", "nc_files_context.ini"];
const wasmPath = `${wasmDir}/${filename}`;
const iniPath = `${wasmDir}/nc_files_context.ini`;
assertVendorNcFileContextStaging(filename, wasmDir, files);
await writeFetchedTextFile(
interp,
`../../vendor/linuxcnc/nc_files/${filename}`,
wasmPath,
);
interp.writeTextFile(
`${wasmDir}/tool.tbl`,
"T1 P1 D10.000000 Z+0.000000 ; nc_files representative context tool\n",
);
interp.writeTextFile(
iniPath,
[
"[RS274NGC]",
"SUBROUTINE_PATH = .",
"",
"[TRAJ]",
"COORDINATES = X Y Z A B C U V W",
"",
"[EMCIO]",
"TOOL_TABLE = tool.tbl",
"",
].join("\n"),
);
return interp.runFileWithIni(wasmPath, iniPath);
}
async function verifyRejects(fixtureName, operation, expectedPattern) {
try {
await operation();
} catch (error) {
if (!expectedPattern.test(error.message)) {
throw new Error(`${fixtureName}: unexpected error ${error.message}`);
}
return;
}
throw new Error(`${fixtureName}: expected rejection`);
}
const interpPrints = [];
function runWithCapturedPrints(operation) {
interpPrints.length = 0;
const output = operation();
return { output, prints: interpPrints.join("\n") };
}
try {
verifyBrowserIniContextStagingPlan();
const boundaryPhaseCompletionRows =
await verifyBrowserBoundaryPhaseCompletion();
const generatedArtifactDocumentationCoverage =
await verifyBrowserGeneratedArtifactDocumentationCoverage();
await verifyBrowserIniBoundarySummary();
const nextBoundaryWorklistRows =
await verifyBrowserNextBoundaryWorklist();
const boundaryProofGateRows =
await verifyBrowserBoundaryProofGates();
await verifyBrowserBoundarySummary();
const userMTransitionPlanRows =
await verifyBrowserUserMTransitionPlan();
await verifyBrowserUserMNativeTransitionAlignment();
const userMNativeRuntimeStatePlanRows =
await verifyBrowserUserMNativeRuntimeStatePlan();
const userMNativeRuntimeReadinessRows =
await verifyBrowserUserMNativeRuntimeReadiness();
const userMNativeRuntimeProbeGateRows =
await verifyBrowserUserMNativeRuntimeProbeGate();
const toolDbTransactionPlanRows =
await verifyBrowserToolDbTransactionPlan();
const toolDbNativeRuntimeReadinessRows =
await verifyBrowserToolDbNativeRuntimeReadiness();
const toolDbNativeRuntimeProbeGateRows =
await verifyBrowserToolDbNativeRuntimeProbeGate();
const pythonRuntimeContractRows =
await verifyBrowserPythonRuntimeContract();
const pythonNativeRuntimeReadinessRows =
await verifyBrowserPythonNativeRuntimeReadiness();
const pythonNativeRuntimeProbeGateRows =
await verifyBrowserPythonNativeRuntimeProbeGate();
const pythonNativeRuntimeStatePlanRows =
await verifyBrowserPythonNativeRuntimeStatePlan();
const pythonNativeRuntimeFixturePlanRows =
await verifyBrowserPythonNativeRuntimeFixturePlan();
const nativeProofAlignmentRows = await verifyBrowserNativeProofAlignment();
const runtimeNativeAlignmentSummaryRows =
await verifyBrowserRuntimeNativeAlignmentSummary();
const nativeRuntimeProbeSummaryRows =
await verifyBrowserNativeRuntimeProbeSummary();
const runtimeProbeGateAlignmentRows =
await verifyBrowserRuntimeProbeGateAlignment();
verifyBrowserNativeSourceProofRuntimeProbeParity(
nativeProofAlignmentRows,
nativeRuntimeProbeSummaryRows,
runtimeProbeGateAlignmentRows,
);
const nativeRuntimeProbeExecutionPlanRows =
await verifyBrowserNativeRuntimeProbeExecutionPlan();
const nativeRuntimeProbePassEvidenceRows =
await verifyBrowserNativeRuntimeProbePassEvidenceContract();
const runtimeBoundaryContractSummaryRows =
await verifyBrowserRuntimeBoundaryContractSummary();
verifyBrowserRuntimeContractNativeAlignmentParity(
runtimeBoundaryContractSummaryRows,
runtimeNativeAlignmentSummaryRows,
);
verifyBrowserRuntimeFamilyContractAlignmentParity({
userMTransitionRows: userMTransitionPlanRows,
userMRuntimeStatePlanRows: userMNativeRuntimeStatePlanRows,
userMRuntimeReadinessRows: userMNativeRuntimeReadinessRows,
userMRuntimeProbeGateRows: userMNativeRuntimeProbeGateRows,
toolDbTransactionRows: toolDbTransactionPlanRows,
toolDbRuntimeReadinessRows: toolDbNativeRuntimeReadinessRows,
toolDbRuntimeProbeGateRows: toolDbNativeRuntimeProbeGateRows,
pythonRuntimeContractRows,
pythonRuntimeReadinessRows: pythonNativeRuntimeReadinessRows,
pythonRuntimeProbeGateRows: pythonNativeRuntimeProbeGateRows,
pythonRuntimeStatePlanRows: pythonNativeRuntimeStatePlanRows,
pythonRuntimeFixturePlanRows: pythonNativeRuntimeFixturePlanRows,
runtimeContractSummaryRows: runtimeBoundaryContractSummaryRows,
});
await verifyBrowserRuntimeNativeAlignmentDetails(runtimeNativeAlignmentSummaryRows);
const blockedRuntimePromotionLockRows =
await verifyBrowserBlockedRuntimePromotionLock();
verifyBrowserNextBoundaryWorklistProofGateParity({
worklistRows: nextBoundaryWorklistRows,
proofGateRows: boundaryProofGateRows,
promotionLockRows: blockedRuntimePromotionLockRows,
});
const runtimeBoundaryPromotionReadinessRows =
await verifyBrowserRuntimeBoundaryPromotionReadiness();
const runtimeBoundaryPromotionBlockerRows =
await verifyBrowserRuntimeBoundaryPromotionBlockers();
const runtimeBoundaryPostNativePassGateRows =
await verifyBrowserRuntimeBoundaryPostNativePassGates();
const runtimeBoundaryHostPreflightRows =
await verifyBrowserRuntimeBoundaryHostPreflight();
const runtimeBoundaryHostRequirementSummaryRows =
await verifyBrowserRuntimeBoundaryHostRequirementSummary();
const runtimeBoundaryHostUnblockPlanRows =
await verifyBrowserRuntimeBoundaryHostUnblockPlan();
const runtimeBoundaryFamilyHostReadinessRows =
await verifyBrowserRuntimeBoundaryFamilyHostReadiness();
verifyBrowserBlockedRuntimeHostRequirementParity({
hostPreflightRows: runtimeBoundaryHostPreflightRows,
requirementSummaryRows: runtimeBoundaryHostRequirementSummaryRows,
unblockPlanRows: runtimeBoundaryHostUnblockPlanRows,
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
});
const runtimeBoundaryHostReadinessRollupRows =
await verifyBrowserRuntimeBoundaryHostReadinessRollup();
const runtimeBoundaryOptInProbeDispatchPlanRows =
await verifyBrowserRuntimeBoundaryOptInProbeDispatchPlan();
const runtimeBoundaryOptInProbeDispatchRollupRows =
await verifyBrowserRuntimeBoundaryOptInProbeDispatchRollup();
const runtimeBoundaryOptInProbeSkipEvidenceRows =
await verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceContract();
verifyBrowserBlockedRuntimeProbeExecutionParity({
executionPlanRows: nativeRuntimeProbeExecutionPlanRows,
passEvidenceRows: nativeRuntimeProbePassEvidenceRows,
hostPreflightRows: runtimeBoundaryHostPreflightRows,
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
});
const runtimeBoundaryOptInProbeSkipEvidenceRollupRows =
await verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceRollup();
verifyBrowserBlockedRuntimeRollupParity({
hostReadinessRollupRows: runtimeBoundaryHostReadinessRollupRows,
dispatchRollupRows: runtimeBoundaryOptInProbeDispatchRollupRows,
skipEvidenceRollupRows: runtimeBoundaryOptInProbeSkipEvidenceRollupRows,
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
});
const runtimeBoundaryNativeEvidenceAcceptanceGateRows =
await verifyBrowserRuntimeBoundaryNativeEvidenceAcceptanceGate();
verifyBrowserBlockedRuntimePromotionGateParity({
promotionLockRows: blockedRuntimePromotionLockRows,
promotionReadinessRows: runtimeBoundaryPromotionReadinessRows,
promotionBlockerRows: runtimeBoundaryPromotionBlockerRows,
postNativePassGateRows: runtimeBoundaryPostNativePassGateRows,
nativeEvidenceAcceptanceGateRows: runtimeBoundaryNativeEvidenceAcceptanceGateRows,
passEvidenceRows: nativeRuntimeProbePassEvidenceRows,
});
const nextBoundaryRecommendationRows =
await verifyBrowserNextBoundaryRecommendations();
verifyBrowserNextBoundaryRecommendationCountParity({
recommendationRows: nextBoundaryRecommendationRows,
userMRuntimeProbeGateRows: userMNativeRuntimeProbeGateRows,
toolDbRuntimeProbeGateRows: toolDbNativeRuntimeProbeGateRows,
pythonRuntimeContractRows,
});
verifyBrowserNextBoundaryRecommendationSourceArtifactCoverage({
recommendationRows: nextBoundaryRecommendationRows,
wasmArtifactNames: generatedArtifactDocumentationCoverage.wasmArtifactNames,
});
verifyBrowserBlockedRuntimeOptInGateParity({
recommendationRows: nextBoundaryRecommendationRows,
executionPlanRows: nativeRuntimeProbeExecutionPlanRows,
promotionReadinessRows: runtimeBoundaryPromotionReadinessRows,
promotionBlockerRows: runtimeBoundaryPromotionBlockerRows,
postNativePassGateRows: runtimeBoundaryPostNativePassGateRows,
hostPreflightRows: runtimeBoundaryHostPreflightRows,
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
evidenceAcceptanceGateRows: runtimeBoundaryNativeEvidenceAcceptanceGateRows,
});
verifyBrowserBoundaryPhaseCompletionCountParity(
boundaryPhaseCompletionRows,
new Map([
["native_source_proof_alignment", nativeProofAlignmentRows.length],
["runtime_native_alignment_artifacts", runtimeNativeAlignmentSummaryRows.length],
["native_runtime_probe_summary", nativeRuntimeProbeSummaryRows.length],
["runtime_probe_gate_alignment", runtimeProbeGateAlignmentRows.length],
["blocked_runtime_worklist_recommendation_consistency", nextBoundaryRecommendationRows.length],
["native_runtime_probe_execution_plan", nativeRuntimeProbeExecutionPlanRows.length],
["native_runtime_probe_pass_evidence_contract", nativeRuntimeProbePassEvidenceRows.length],
["blocked_runtime_probe_execution_consistency", nativeRuntimeProbeExecutionPlanRows.length],
["runtime_boundary_promotion_readiness", runtimeBoundaryPromotionReadinessRows.length],
["runtime_boundary_promotion_blockers", runtimeBoundaryPromotionBlockerRows.length],
["runtime_boundary_post_native_pass_gates", runtimeBoundaryPostNativePassGateRows.length],
["blocked_runtime_promotion_gate_consistency", runtimeBoundaryPromotionReadinessRows.length],
["runtime_boundary_host_preflight", runtimeBoundaryHostPreflightRows.length],
["runtime_boundary_host_requirement_summary", runtimeBoundaryHostRequirementSummaryRows.length],
["runtime_boundary_host_unblock_plan", runtimeBoundaryHostUnblockPlanRows.length],
["runtime_boundary_family_host_readiness", runtimeBoundaryFamilyHostReadinessRows.length],
["blocked_runtime_host_requirement_consistency", runtimeBoundaryFamilyHostReadinessRows.length],
["runtime_boundary_host_readiness_rollup", runtimeBoundaryHostReadinessRollupRows.length],
["runtime_boundary_opt_in_probe_dispatch_plan", runtimeBoundaryOptInProbeDispatchPlanRows.length],
["runtime_boundary_opt_in_probe_dispatch_rollup", runtimeBoundaryOptInProbeDispatchRollupRows.length],
["runtime_boundary_opt_in_probe_skip_evidence_contract", runtimeBoundaryOptInProbeSkipEvidenceRows.length],
["runtime_boundary_opt_in_probe_skip_evidence_rollup", runtimeBoundaryOptInProbeSkipEvidenceRollupRows.length],
["blocked_runtime_rollup_consistency", runtimeBoundaryFamilyHostReadinessRows.length],
["runtime_boundary_native_evidence_acceptance_gate", runtimeBoundaryNativeEvidenceAcceptanceGateRows.length],
["blocked_runtime_opt_in_gate_consistency", nextBoundaryRecommendationRows.length],
["native_source_proof_runtime_probe_consistency", nativeProofAlignmentRows.length],
["user_m_transition_contract", userMTransitionPlanRows.length],
["user_m_native_runtime_state_plan", userMNativeRuntimeStatePlanRows.length],
["user_m_native_runtime_readiness", userMNativeRuntimeReadinessRows.length],
["user_m_native_runtime_probe_gate", userMNativeRuntimeProbeGateRows.length],
["tool_db_transaction_contract", toolDbTransactionPlanRows.length],
["tool_db_native_runtime_readiness", toolDbNativeRuntimeReadinessRows.length],
["tool_db_native_runtime_probe_gate", toolDbNativeRuntimeProbeGateRows.length],
["python_runtime_contract", pythonRuntimeContractRows.length],
["python_native_runtime_readiness", pythonNativeRuntimeReadinessRows.length],
["python_native_runtime_probe_gate", pythonNativeRuntimeProbeGateRows.length],
["python_native_runtime_state_plan", pythonNativeRuntimeStatePlanRows.length],
["python_native_runtime_fixture_plan", pythonNativeRuntimeFixturePlanRows.length],
["runtime_contract_summary", runtimeBoundaryContractSummaryRows.length],
["runtime_family_contract_alignment_consistency", runtimeBoundaryContractSummaryRows.length],
[
"wasm_inventory_artifact_documentation_coverage",
generatedArtifactDocumentationCoverage.wasmArtifactNames.length,
],
[
"native_artifact_documentation_coverage",
generatedArtifactDocumentationCoverage.nativeArtifactTokens.length,
],
]),
);
const interp = await createLinuxCncInterpSdk({
locateFile(path) {
if (path === "linuxcnc_interp.wasm") {
return "../../build/wasm/core/linuxcnc_interp.wasm";
}
return path;
},
print(message) {
interpPrints.push(message);
},
printErr(message) {
console.error(message);
},
});
const ini = await createLinuxCncIniSdk({
locateFile(path) {
if (path === "linuxcnc_ini.wasm") {
return "../../runtime/ui/ini-panel/linuxcnc_ini.wasm";
}
return path;
},
print() {},
printErr(message) {
console.error(message);
},
});
const namedParamIniPath = "/work/namedparams.ini";
interp.writeTextFile(namedParamIniPath, await fetchText("../fixtures/ini/namedparams.ini"));
verifyExpectedOutput(
"probe_init_and_synch",
interp.probeInitAndSynch(),
[
"init=0",
"setup.length_units=2",
"setup.origin_index=1",
"setup.distance_mode=0",
"setup.feed_mode=0",
"setup.motion_mode=800",
"canon_event=INIT_CANON",
"canon_event=USE_LENGTH_UNITS units=2",
"canon_event=SET_G5X_OFFSET index=1",
"canon_event=SET_G92_OFFSET x=0 y=0 z=0",
"canon_event=SET_XY_ROTATION rotation=0",
"canon_event=SET_FEED_REFERENCE reference=2",
"inch_init=0",
"inch_setup.length_units=1",
"inch_external_length_units=0.0393701",
"inch_canon_event=USE_LENGTH_UNITS units=1",
"synch=0",
"synch.current_pocket=2",
"synch.selected_pocket=2",
"synch.tool_0=2",
"synch.tool_2=2",
].join("\n"),
);
verifyExpectedOutput(
"probe_indexer",
interp.probeIndexer(),
[
"execute=0",
"post_a=90",
"canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0",
"canon_event=UNLOCK_ROTARY line=1 joint=0",
"canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0",
"canon_event=LOCK_ROTARY line=1 joint=0",
"canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0",
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=0",
"canon_event=UPDATE_TAG line=1",
].join("\n"),
);
verifyExpectedOutput(
"probe_named_parameters",
interp.probeNamedParameters(namedParamIniPath),
[
"init_named_parameters=0",
"global_named_count=57",
"_metric_machine: rc=0 found=1 value=1",
"_motion_mode: rc=0 found=1 value=10",
"_metric: rc=0 found=1 value=1",
"_feed: rc=0 found=1 value=123.45",
"_rpm: rc=0 found=1 value=678.9",
"_x: rc=0 found=1 value=1.25",
"_current_tool: rc=0 found=1 value=12",
"_ini[traj]max_linear_velocity: rc=0 found=1 value=35",
"_hal[standalone.pin-bit]: rc=0 found=1 value=1",
"_hal[standalone.signal-float]: rc=0 found=1 value=98.25",
"_hal[standalone.param-s32]: rc=0 found=1 value=-17",
"_hal[standalone.pin-u32]: rc=0 found=1 value=1.23457e+08",
"_hal[standalone.signal-s64]: rc=0 found=1 value=-9e+09",
"_hal[standalone.param-u64]: rc=0 found=1 value=9e+09",
"_hal[standalone.disconnected-float]: rc=0 found=1 value=12.5",
"_hal[standalone.missing]: rc=0 found=0 value=0",
].join("\n"),
);
for (const fixtureName of INTERP_BROWSER_MDI_FIXTURES) {
const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`);
const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`);
verifyExpectedOutput(fixtureName, interp.runProgram(programText), expectedText.trimEnd());
}
for (const errorFixtureName of INTERP_ERROR_FIXTURES) {
verifyExpectedOutput(
errorFixtureName,
interp.runProgram(await fetchText(`../fixtures/gcode_errors/${errorFixtureName}.ngc`)),
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
);
}
for (const errorFixtureName of INTERP_ERROR_FIXTURES) {
const programPath = await writeLocalErrorGcodeFixture(
interp,
errorFixtureName,
`/work/${errorFixtureName}-error.ngc`,
`/work/${errorFixtureName}-error.ngc`,
);
verifyExpectedFileError(
`${errorFixtureName}_file`,
interp.runFile(programPath),
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
);
}
const namedParamProgramText = await fetchText(
`../fixtures/gcode/${INTERP_INI_FIXTURE}.ngc`,
);
const namedParamExpectedText = (
await fetchText(`../fixtures/canon/${INTERP_INI_FIXTURE}.events`)
).trimEnd();
verifyExpectedOutput(
INTERP_INI_FIXTURE,
interp.runProgramWithIni(namedParamProgramText, namedParamIniPath),
namedParamExpectedText,
);
const baselineIniProgramText = await fetchText(
`../fixtures/gcode/${INTERP_BASELINE_INI_FIXTURE}.ngc`,
);
const baselineIniExpectedText = (
await fetchText(`../fixtures/canon/${INTERP_BASELINE_INI_FIXTURE}.events`)
).trimEnd();
verifyExpectedOutput(
INTERP_BASELINE_INI_FIXTURE,
interp.runProgramWithIni(baselineIniProgramText, namedParamIniPath),
baselineIniExpectedText,
);
const spindleOrientOffsetProgramText = await fetchText(
`../fixtures/gcode/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.ngc`,
);
const spindleOrientOffsetExpectedText = (
await fetchText(`../fixtures/canon/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.events`)
).trimEnd();
const spindleOrientOffsetIniPath = await writeLocalIniFixture(
interp,
"spindle_orient_offset.ini",
"/work/browser-spindle-orient-offset.ini",
"/work/browser-spindle-orient-offset.ini",
);
const disableFanucStyleSubIniPath = await writeLocalIniFixture(
interp,
"disable_fanuc_style_sub.ini",
"/work/browser-disable-fanuc-style-sub.ini",
"/work/browser-disable-fanuc-style-sub.ini",
);
verifyExpectedOutput(
INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE,
interp.runProgramWithIni(spindleOrientOffsetProgramText, spindleOrientOffsetIniPath),
spindleOrientOffsetExpectedText,
);
const coordinateOffsetsPath = await writeLocalGcodeFixture(
interp,
INTERP_COORDINATE_OFFSETS_FILE_FIXTURE,
`/work/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.ngc`,
`/work/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.ngc`,
);
verifyExpectedOutput(
`${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}_file`,
interp.runFile(coordinateOffsetsPath),
(
await fetchText(
`../fixtures/canon_file/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.events`,
)
).trimEnd(),
);
const positionParamsPath = await writeLocalGcodeFixture(
interp,
INTERP_POSITION_PARAMS_FILE_FIXTURE,
`/work/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.ngc`,
`/work/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.ngc`,
);
verifyExpectedOutput(
`${INTERP_POSITION_PARAMS_FILE_FIXTURE}_file`,
interp.runFile(positionParamsPath),
(
await fetchText(
`../fixtures/canon_file/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.events`,
)
).trimEnd(),
);
for (const fileFixtureName of INTERP_BROWSER_FILE_FIXTURES) {
const programPath = await writeLocalGcodeFixture(
interp,
fileFixtureName,
`/work/${fileFixtureName}.ngc`,
`/work/${fileFixtureName}.ngc`,
);
verifyExpectedOutput(
`${fileFixtureName}_file`,
interp.runFile(programPath),
(await fetchText(`../fixtures/canon/${fileFixtureName}.events`)).trimEnd(),
);
}
const minimalLinearRunStepsPath = await writeLocalGcodeFixture(
interp,
"minimal_linear",
"/work/minimal_linear.ngc",
"/work/minimal_linear.ngc",
);
verifyExpectedOutput(
"browser_minimal_linear_run_steps",
interp.runFile(minimalLinearRunStepsPath),
[
"run_step phase=read step=1 rc=0 line=1",
"run_step phase=execute step=1 rc=0 line=1 x=1 y=2 z=0",
"statement_uri=G0%20X1.0%20Y2.0%20%28Comment%29",
"run_step phase=execute step=2 rc=0 line=2 x=3 y=4 z=0",
"statement_uri=G1%20X3.0%20Y4.0%20F120.0",
].join("\n"),
);
const namedParamPath = await writeLocalGcodeFixture(
interp,
INTERP_INI_FIXTURE,
`/work/${INTERP_INI_FIXTURE}.ngc`,
`/work/${INTERP_INI_FIXTURE}.ngc`,
);
verifyExpectedOutput(
`${INTERP_INI_FIXTURE}_file`,
interp.runFileWithIni(namedParamPath, namedParamIniPath),
namedParamExpectedText,
);
const iniToolTableDir = "/work/browser-ini-tool-table";
const iniToolTable = await writeIniToolTableFixtureStaging(
interp,
iniToolTableDir,
"/work/browser-ini-tool-table",
);
verifyExpectedOutput(
"browser_ini_tool_table_file",
interp.runFileWithIni(iniToolTable.programPath, iniToolTable.iniPath),
(await fetchText("../fixtures/canon/ini_tool_table.events")).trimEnd(),
);
const baselineIniPath = await writeLocalGcodeFixture(
interp,
INTERP_BASELINE_INI_FIXTURE,
`/work/${INTERP_BASELINE_INI_FIXTURE}.ngc`,
`/work/${INTERP_BASELINE_INI_FIXTURE}.ngc`,
);
verifyExpectedOutput(
`${INTERP_BASELINE_INI_FIXTURE}_file`,
interp.runFileWithIni(baselineIniPath, namedParamIniPath),
baselineIniExpectedText,
);
const spindleOrientOffsetPath = await writeLocalGcodeFixture(
interp,
INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE,
`/work/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.ngc`,
`/work/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.ngc`,
);
verifyExpectedOutput(
`${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}_file`,
interp.runFileWithIni(spindleOrientOffsetPath, spindleOrientOffsetIniPath),
spindleOrientOffsetExpectedText,
);
const disableFanucStyleSubPath = await writeLocalGcodeFixture(
interp,
"disable_fanuc_style_sub_m98",
"/work/browser-disable_fanuc_style_sub_m98.ngc",
"/work/browser-disable_fanuc_style_sub_m98.ngc",
);
verifyExpectedOutput(
"browser_disable_fanuc_style_sub_m98_file",
interp.runFileWithIni(disableFanucStyleSubPath, disableFanucStyleSubIniPath),
[
"file_open=0",
"file_saw_error=1",
"file_error_text=DISABLE_FANUC_STYLE_SUB set in INI file, but found m98",
].join("\n"),
);
verifyExpectedOutput(
"browser_nc_files_3d_chips_context",
await runVendorNcFileWithToolTable(interp, "3D_Chips.ngc"),
[
"file_open=0",
"file_read_count=4706",
"file_execute_count=4706",
"file_saw_error=0",
"run_step phase=execute step=18 rc=2 line=18",
"statement_uri=N50T%23%3Ctoolno%3EM6",
"run_step phase=execute step=19 rc=0 line=19",
"statement_uri=N60M8",
"canon_event=CHANGE_TOOL",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=STRAIGHT_TRAVERSE line=21 x=0 y=0 z=10",
"canon_event=STRAIGHT_FEED line=23 x=53 y=-56.128 z=-25.372",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
"absent=Requested tool 1 not found",
].join("\n"),
);
verifyExpectedOutput(
"browser_nc_files_arcspiral",
await runVendorNcFile(interp, "arcspiral.ngc"),
[
"file_open=0",
"file_read_count=1008",
"file_execute_count=1008",
"file_saw_error=0",
"canon_event=STRAIGHT_TRAVERSE line=3 x=0 y=0 z=1",
"canon_event=STRAIGHT_FEED line=6 x=1.72464 y=-1.01273 z=-0.1",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
verifyExpectedOutput(
"browser_nc_files_hole_circle",
await runVendorNcFile(interp, "hole-circle.ngc"),
[
"file_open=0",
"file_read_count=12",
"file_execute_count=12",
"file_saw_error=0",
"canon_event=STRAIGHT_TRAVERSE line=7 x=1.5 y=0 z=0",
"canon_event=STRAIGHT_FEED line=10 x=0.75 y=1.29904 z=-0.4",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
verifyExpectedOutput(
"browser_nc_files_factorial",
await runVendorNcFile(interp, "factorial.ngc"),
[
"file_open=0",
"file_read_count=11",
"file_execute_count=11",
"file_saw_error=0",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
verifyExpectedOutput(
"browser_nc_files_m6demo",
await runVendorNcFile(interp, "m6demo.ngc"),
[
"file_open=0",
"file_read_count=88",
"file_execute_count=88",
"file_saw_error=0",
"canon_event=SET_G5X_OFFSET index=1",
"canon_event=STOP_SPINDLE_TURNING spindle=0",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
const foam = await loadSimMachineFiles("axis/foam", "axis_foam.ini", "foam.ngc");
verifyExpectedOutput(
"browser_sim_axis_foam_uv",
interp.runSimConfigProgram({
files: foam.files,
programPath: foam.programPath,
iniPath: foam.iniPath,
}),
[
"file_open=0",
"file_saw_error=0",
"canon_event=PROGRAM_END",
"absent=Bad character 'u' used",
"absent=Bad character 'v' used",
].join("\n"),
);
const bridgeMillSim = await loadSimMachineFiles(
"axis/vismach/5axis/bridgemill",
"5axis.ini",
"5axisgui.ngc",
);
verifyExpectedOutput(
"browser_sim_axis_bridgemill_w",
interp.runSimConfigProgram({
files: bridgeMillSim.files,
programPath: bridgeMillSim.programPath,
iniPath: bridgeMillSim.iniPath,
executionMode: "fiveAxisRemap",
}),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_parsed_remap_count=3",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=2197",
"fiveaxis_file_execute_count=2197",
"fiveaxis_file_finish_count=137",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
"absent=Bad character 'w' used",
].join("\n"),
);
const geometry = await loadSimMachineFiles(
"axis/geometry",
"xyzc.ini",
"xyzc.ngc",
);
verifyExpectedOutput(
"browser_sim_axis_geometry_xyzc_user_m110",
interp.runSimConfigProgram({
files: geometry.files,
programPath: geometry.programPath,
iniPath: geometry.iniPath,
}),
[
"file_open=0",
"file_saw_error=0",
"canon_event=USER_M_COMMAND code=M110",
"canon_event=PROGRAM_END",
"absent=Unknown m code used: M110",
"absent=Bad character 'c' used",
].join("\n"),
);
const gladevcpProbe = await loadSimMachineFiles(
"axis/gladevcp",
"gladevcp_panel.ini",
"probe.ngc",
);
await verifySimBoundaryDeclaration("browser_sim_axis_gladevcp_probe_boundary", {
machineRel: "axis/gladevcp",
iniFile: "gladevcp_panel.ini",
executionFiles: ["probe.ngc"],
expectedDependencies: ["hal_process", "python_runtime", "ui_process"],
expectedProcesses: {
halRuntime: true,
uiRuntime: true,
haluiRuntime: false,
pythonRuntime: true,
},
expectedPythonUiReferences: ["-u hitcounter.py manual-example.ui"],
expectedPythonRemapReferences: [],
expectedCoverage: "file",
});
verifyExpectedOutput(
"browser_sim_axis_gladevcp_probe",
interp.runSimConfigProgram({
files: gladevcpProbe.files,
programPath: gladevcpProbe.programPath,
iniPath: gladevcpProbe.iniPath,
}),
[
"file_open=0",
"file_saw_error=0",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
const externalOffsetsMacro = wrapSimProgramFiles(
await loadSimMachineFiles(
"axis/external_offsets",
"dynamic_offsets.ini",
"circles.ngc",
),
"__wrapped_circles.ngc",
);
verifyExpectedOutput(
"browser_sim_axis_external_offsets_circles_macro",
interp.runSimConfigProgram({
files: externalOffsetsMacro.files,
programPath: externalOffsetsMacro.programPath,
iniPath: externalOffsetsMacro.iniPath,
}),
[
"file_open=0",
"file_execute_45=1",
"statement_uri=M2",
"file_read_count=45",
"file_execute_count=45",
"file_saw_error=0",
"canon_event=COMMENT: info: Multiple circles at radius R, use 0 for current val",
"canon_event=PROGRAM_END",
"post_execute.origin_index=1",
"post_execute.mist=0",
"post_execute.flood=0",
"absent=file_error_text=",
].join("\n"),
);
for (const externalOffsetCase of [
{
name: "dyn_demo",
program: "dyn_demo.ngc",
ini: "dynamic_offsets.ini",
readCount: 833,
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=21 x=0 y=0 z=0",
firstFeed: "canon_event=STRAIGHT_FEED line=26 x=9 y=0 z=1.5",
},
{
name: "eoffsets",
program: "eoffsets.ngc",
ini: "eoffsets.ini",
readCount: 35,
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=24 x=0 y=0 z=0",
firstFeed: "canon_event=STRAIGHT_FEED line=27 x=2.25 y=2.64 z=0",
},
{
name: "jwp_z",
program: "jwp_z.ngc",
ini: "jwp_z.ini",
readCount: 35,
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=17 x=0 y=0 z=0.5",
firstFeed: "canon_event=STRAIGHT_FEED line=24 x=2.5 y=3.3 z=0",
},
{
name: "opa_demo",
program: "opa_demo.ngc",
ini: "opa.ini",
readCount: 58,
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=13 x=1 y=0 z=0",
firstFeed: "canon_event=STRAIGHT_FEED line=35 x=1 y=0 z=0",
},
]) {
const externalOffsets = await loadSimMachineFiles(
"axis/external_offsets",
externalOffsetCase.ini,
externalOffsetCase.program,
);
verifyExpectedOutput(
`browser_sim_axis_external_offsets_${externalOffsetCase.name}_user_m111`,
interp.runSimConfigProgram({
files: externalOffsets.files,
programPath: externalOffsets.programPath,
iniPath: externalOffsets.iniPath,
}),
[
"file_open=0",
`file_read_count=${externalOffsetCase.readCount}`,
`file_execute_count=${externalOffsetCase.readCount}`,
"file_saw_error=0",
"canon_event=USER_M_COMMAND code=M111",
externalOffsetCase.firstTraverse,
externalOffsetCase.firstFeed,
"canon_event=PROGRAM_END",
"absent=Unknown m code used: M111",
"absent=Bad character",
"absent=Cannot use axis values",
].join("\n"),
);
}
const melfaSim = await loadSimMachineFiles(
"axis/vismach/melfa-sim",
"melfa.ini",
"example.ngc",
);
await verifySimBoundaryDeclaration("browser_sim_axis_vismach_melfa_boundary", {
machineRel: "axis/vismach/melfa-sim",
iniFile: "melfa.ini",
executionFiles: ["example.ngc"],
expectedDependencies: ["hal_process", "halui_mdi_process", "ui_process"],
expectedProcesses: {
halRuntime: true,
uiRuntime: true,
haluiRuntime: true,
pythonRuntime: false,
},
expectedCoverage: "remap",
});
verifyExpectedOutput(
"browser_sim_axis_vismach_melfa",
interp.runSimConfigProgram({
files: melfaSim.files,
programPath: melfaSim.programPath,
iniPath: melfaSim.iniPath,
executionMode: "fiveAxisRemap",
}),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_file_open=0",
"fiveaxis_file_reached_exit=1",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
const pumaCube = await loadSimMachineFiles(
"axis/vismach/puma",
"puma_cube.ini",
"puma_cube.ngc",
);
await verifySimBoundaryDeclaration("browser_sim_axis_vismach_puma_cube_boundary", {
machineRel: "axis/vismach/puma",
iniFile: "puma_cube.ini",
executionFiles: ["puma_cube.ngc"],
expectedDependencies: ["hal_process", "halui_mdi_process", "ui_process"],
expectedProcesses: {
halRuntime: true,
uiRuntime: true,
haluiRuntime: true,
pythonRuntime: false,
},
expectedCoverage: "file",
});
verifyExpectedOutput(
"browser_sim_axis_vismach_puma_cube",
interp.runSimConfigProgram({
files: pumaCube.files,
programPath: pumaCube.programPath,
iniPath: pumaCube.iniPath,
}),
[
"file_open=0",
"file_saw_error=0",
"canon_event=PROGRAM_END",
"absent=file_error_text=",
].join("\n"),
);
const woodpeckerOnAbort = await loadSimMachineFiles(
"woodpecker",
"woodpecker.ini",
"on_abort.ngc",
);
await verifySimBoundaryDeclaration("browser_sim_woodpecker_on_abort_boundary", {
machineRel: "woodpecker",
iniFile: "woodpecker.ini",
executionFiles: ["on_abort.ngc"],
expectedDependencies: ["hal_process", "ui_process"],
expectedProcesses: {
halRuntime: true,
uiRuntime: true,
haluiRuntime: false,
pythonRuntime: false,
},
expectedCoverage: "file",
});
verifyExpectedOutput(
"browser_sim_woodpecker_on_abort",
interp.runSimConfigProgram({
files: woodpeckerOnAbort.files,
programPath: woodpeckerOnAbort.programPath,
iniPath: woodpeckerOnAbort.iniPath,
}),
[
"file_open=0",
"run_step phase=execute step=17 rc=1 line=17",
"statement_uri=m2",
"file_read_count=17",
"file_execute_count=17",
"file_saw_error=0",
"canon_event=ON_RESET",
"canon_event=PROGRAM_END",
"post_execute.origin_index=1",
"post_execute.mist=0",
"post_execute.flood=0",
"absent=file_error_text=",
].join("\n"),
);
await verifySimHardBlockedBoundary("browser_sim_axis_vismach_millturn_boundary", {
machineRel: "axis/vismach/millturn",
iniFile: "millturn.ini",
executionFiles: [
"example.ngc",
"remap_subs/428remap.ngc",
"remap_subs/429remap.ngc",
],
expectedBlockedKind: "L4-USER-M-PROCESS",
expectedDependencies: [
"external_user_m_process",
"hal_process",
"halui_mdi_process",
"ui_process",
],
expectedProcesses: {
halRuntime: true,
uiRuntime: true,
haluiRuntime: true,
pythonRuntime: false,
},
expectedUserMCodes: ["M128", "M129"],
expectedUnstagedUserMCodes: ["M128", "M129"],
expectedPythonUiReferences: [],
expectedPythonRemapReferences: [],
expectedVendoredUserMCount: 0,
expectedRequiresExternalUserM: true,
expectedToolDatabaseProgram: "",
});
await verifySimHardBlockedBoundary("browser_sim_axis_db_demo_tool_db_boundary", {
machineRel: "axis/db_demo",
iniFile: "db_nonran.ini",
executionFiles: ["base.ngc"],
expectedBlockedKind: "L4-TOOL-DB",
expectedDependencies: ["python_runtime", "tool_database_process"],
expectedProcesses: {
halRuntime: false,
uiRuntime: false,
haluiRuntime: false,
pythonRuntime: true,
},
expectedUserMCodes: [],
expectedUnstagedUserMCodes: [],
expectedPythonUiReferences: ["./db_nonran.py"],
expectedPythonRemapReferences: [],
expectedVendoredUserMCount: 0,
expectedRequiresExternalUserM: false,
expectedToolDatabaseProgram: "./db_nonran.py",
});
const fiveAxisTrtDir = await writeFiveAxisTrtMachineFiles(interp);
verifyExpectedOutput(
"browser_fiveaxis_xyzac_switchkins",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/xyzac_switchkins.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=620",
"fiveaxis_file_execute_count=620",
"fiveaxis_file_finish_count=21",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_xyzbc_switchkins",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/xyzbc_switchkins.ngc`,
`${fiveAxisTrtDir}/xyzbc-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=620",
"fiveaxis_file_execute_count=620",
"fiveaxis_file_finish_count=21",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_xyzac_switchkins_test_1",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_1.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_parsed_remap_count=3",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=248",
"fiveaxis_file_execute_count=248",
"fiveaxis_file_finish_count=9",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_xyzac_switchkins_test_2",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_2.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_parsed_remap_count=3",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=120",
"fiveaxis_file_execute_count=120",
"fiveaxis_file_finish_count=3",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_xyzac_switchkins_test_3",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_3.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_parsed_remap_count=3",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=86",
"fiveaxis_file_execute_count=86",
"fiveaxis_file_finish_count=3",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_boat_xyzac",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/boat-xyzac.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=1927",
"fiveaxis_file_execute_count=1927",
"fiveaxis_file_finish_count=3",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_boat_xyzbc",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/boat-xyzbc.ngc`,
`${fiveAxisTrtDir}/xyzbc-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=1913",
"fiveaxis_file_execute_count=1913",
"fiveaxis_file_finish_count=3",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
verifyExpectedOutput(
"browser_fiveaxis_impeller_7bl_xyzac",
interp.runFiveAxisRemapFile(
`${fiveAxisTrtDir}/demos/impeller-7bl-xyzac.ngc`,
`${fiveAxisTrtDir}/xyzac-trt.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=4558",
"fiveaxis_file_execute_count=4558",
"fiveaxis_file_finish_count=2",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
const fiveAxisTdrDir = await writeFiveAxisTdrMachineFiles(interp);
verifyExpectedOutput(
"browser_fiveaxis_xyzab_tdr_demo",
interp.runFiveAxisRemapFile(
`${fiveAxisTdrDir}/demos/xyzab-tdr-demo.ngc`,
`${fiveAxisTdrDir}/xyzab-tdr.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parsed_remap_count=2",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=97",
"fiveaxis_file_execute_count=97",
"fiveaxis_file_finish_count=3",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=1",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
const fiveAxisBridgeMillDir = await writeFiveAxisBridgeMillMachineFiles(interp);
verifyExpectedOutput(
"browser_fiveaxis_bridgemill_5axisgui",
interp.runFiveAxisRemapFile(
`${fiveAxisBridgeMillDir}/5axisgui.ngc`,
`${fiveAxisBridgeMillDir}/5axis.ini`,
),
[
"fiveaxis_ini_open=1",
"fiveaxis_tool_table_load=0",
"fiveaxis_parse_remap_1=0",
"fiveaxis_parse_remap_2=0",
"fiveaxis_parse_remap_3=0",
"fiveaxis_parsed_remap_count=3",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_open=0",
"fiveaxis_file_read_count=2197",
"fiveaxis_file_execute_count=2197",
"fiveaxis_file_finish_count=137",
"fiveaxis_file_final_rc=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
"fiveaxis_linuxcnc_remap_file_execute=1",
].join("\n"),
);
const duplicateOwordFiles = [
"test.ini",
"test.ngc",
"rm207.ngc",
"rm208.ngc",
];
const duplicateOwordDir = await writeRemapRegressionFiles(interp, "duplicate-o-word", duplicateOwordFiles);
assertRemapRegressionFilesStaging("duplicate-o-word", duplicateOwordFiles, duplicateOwordDir);
verifyExpectedOutput(
"browser_duplicate_oword_remap",
interp.runRemapFile(
`${duplicateOwordDir}/test.ngc`,
`${duplicateOwordDir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parse_remap_2=0",
"remap_parsed_remap_count=2",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_read_count=31",
"remap_file_execute_count=31",
"remap_file_finish_count=0",
"remap_file_final_rc=3",
"remap_file_reached_endfile=1",
"remap_canon_event=MESSAGE: executing m207: run remapped m208 before o<problem>",
"remap_canon_event=MESSAGE: rm207 running remapped m208",
"remap_canon_event=MESSAGE: rm208 starting and done",
"remap_canon_event=FINISH",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const failArgs0Files = [
"test.ini",
"test.ngc",
"rm400.ngc",
];
const failArgs0Dir = await writeRemapRegressionFiles(interp, "fail/args.0", failArgs0Files);
assertRemapRegressionFilesStaging("fail/args.0", failArgs0Files, failArgs0Dir);
verifyExpectedOutput(
"browser_fail_args_0_remap",
interp.runRemapFile(
`${failArgs0Dir}/test.ngc`,
`${failArgs0Dir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parsed_remap_count=1",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_saw_error=0",
"remap_canon_event=MESSAGE: M400 call_level= 1.000000 remap_level=1.000000",
"remap_canon_event=MESSAGE: P word set: 47.110000",
"remap_canon_event=MESSAGE: Q word set: 8.150000",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const failArgs1Files = [
"test.ini",
"test.ngc",
"rm400.ngc",
];
const failArgs1Dir = await writeRemapRegressionFiles(interp, "fail/args.1", failArgs1Files);
assertRemapRegressionFilesStaging("fail/args.1", failArgs1Files, failArgs1Dir);
verifyExpectedOutput(
"browser_fail_args_1_remap",
interp.runRemapFileContinueOnError(
`${failArgs1Dir}/test.ngc`,
`${failArgs1Dir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_file_execute_1=5",
"remap_file_error_text=user-defined M400: missing: Q",
"remap_file_saw_error=1",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const failArgs2Files = [
"test.ini",
"test.ngc",
"rm400.ngc",
];
const failArgs2Dir = await writeRemapRegressionFiles(interp, "fail/args.2", failArgs2Files);
assertRemapRegressionFilesStaging("fail/args.2", failArgs2Files, failArgs2Dir);
verifyExpectedOutput(
"browser_fail_args_2_remap",
interp.runRemapFileContinueOnError(
`${failArgs2Dir}/test.ngc`,
`${failArgs2Dir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_file_execute_1=5",
"remap_file_error_text=user-defined M400: missing: P,Q",
"remap_file_saw_error=1",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const failBodyNgcFiles = [
"test.ini",
"test.ngc",
"rm400.ngc",
];
const failBodyNgcDir = await writeRemapRegressionFiles(interp, "fail/body-ngc", failBodyNgcFiles);
assertRemapRegressionFilesStaging("fail/body-ngc", failBodyNgcFiles, failBodyNgcDir);
verifyExpectedOutput(
"browser_fail_body_ngc_remap",
interp.runRemapFileContinueOnError(
`${failBodyNgcDir}/test.ngc`,
`${failBodyNgcDir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_file_read_5=5",
"remap_file_error_text=Attempt to divide by zero",
"remap_file_saw_error=1",
"remap_canon_event=MESSAGE: before M400: call_level= 0.000000 remap_level=0.000000",
"remap_canon_event=MESSAGE: in rm400: call_level= 1.000000 remap_level=1.000000",
"remap_canon_event=MESSAGE: after failed M400: call_level= 0.000000 remap_level=0.000000",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const m30InteractionFiles = [
"test.ini",
"test.ngc",
"rm400.ngc",
];
const m30InteractionDir = await writeRemapRegressionFiles(interp, "m30-interaction", m30InteractionFiles);
assertRemapRegressionFilesStaging("m30-interaction", m30InteractionFiles, m30InteractionDir);
verifyExpectedOutput(
"browser_m30_interaction_remap",
interp.runRemapFile(
`${m30InteractionDir}/test.ngc`,
`${m30InteractionDir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parsed_remap_count=1",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_read_count=4",
"remap_file_execute_count=4",
"remap_file_finish_count=0",
"remap_file_final_rc=1",
"remap_file_reached_exit=1",
"remap_canon_event=MESSAGE: m400 call_level=1.000000 remap_level=1.000000 line=2.000000",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const nestedRemapsOwordFiles = [
"test.ini",
"test.ngc",
"rm400.ngc",
"rm401.ngc",
"rm402.ngc",
"rm403.ngc",
];
const nestedRemapsOwordUnstagedFiles = [
"testsub.ngc",
];
const nestedRemapsOwordDir = await writeRemapRegressionFiles(
interp,
"nested-remaps-oword",
nestedRemapsOwordFiles,
);
assertRemapRegressionUnstagedOverlap(
"nested-remaps-oword",
nestedRemapsOwordFiles,
nestedRemapsOwordUnstagedFiles,
);
assertRemapRegressionFilesStaging(
"nested-remaps-oword",
nestedRemapsOwordFiles,
nestedRemapsOwordDir,
);
verifyExpectedOutput(
"browser_nested_remaps_oword_remap",
interp.runRemapFile(
`${nestedRemapsOwordDir}/test.ngc`,
`${nestedRemapsOwordDir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parse_remap_2=0",
"remap_parse_remap_3=0",
"remap_parse_remap_4=0",
"remap_parsed_remap_count=4",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_read_count=21",
"remap_file_execute_count=21",
"remap_file_finish_count=0",
"remap_file_final_rc=3",
"remap_file_reached_endfile=1",
"remap_canon_event=MESSAGE: main call_level=0.000000 remap_level=0.000000 line=2.000000",
"remap_canon_event=MESSAGE: m400 handler pre call_level=1.000000 remap_level=1.000000 line=2.000000",
"remap_canon_event=MESSAGE: m401 handler pre call_level=2.000000 remap_level=2.000000 line=2.000000",
"remap_canon_event=MESSAGE: m402 handler pre call_level=3.000000 remap_level=3.000000 line=2.000000",
"remap_canon_event=MESSAGE: m403 handler pre call_level=4.000000 remap_level=4.000000 line=2.000000",
"remap_canon_event=MESSAGE: m400 handler post call_level=1.000000 remap_level=1.000000 line=4.000000",
"remap_canon_event=FINISH",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const posargs0Files = [
"test.ini",
"test.ngc",
"rg881.ngc",
];
const posargs0Dir = await writeRemapRegressionFiles(interp, "posargs.0", posargs0Files);
assertRemapRegressionFilesStaging("posargs.0", posargs0Files, posargs0Dir);
verifyExpectedOutput(
"browser_posargs_0_remap",
interp.runRemapFile(
`${posargs0Dir}/test.ngc`,
`${posargs0Dir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parsed_remap_count=1",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_read_count=19",
"remap_file_execute_count=19",
"remap_file_finish_count=0",
"remap_file_final_rc=1",
"remap_file_reached_exit=1",
"remap_canon_event=MESSAGE: in rg881: n_args=3.000000 [1.000000] [2.000000] [3.000000] [0.000000] [0.000000]",
"remap_canon_event=MESSAGE: in rg881: n_args=4.000000 [1.000000] [2.000000] [3.000000] [4.000000] [0.000000]",
"remap_canon_event=MESSAGE: in rg881: n_args=4.000000 [1.000000] [2.000000] [3.000000] [5.000000] [0.000000]",
"remap_canon_event=MESSAGE: in rg881: n_args=5.000000 [1.000000] [2.000000] [3.000000] [4.000000] [5.000000]",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const sequencingFiles = [
"test.ini",
"test.ngc",
"rg881.ngc",
"rm405.ngc",
"rm406.ngc",
"rm407.ngc",
"rm408.ngc",
"rm409.ngc",
"rm410.ngc",
];
const sequencingDir = await writeRemapRegressionFiles(interp, "sequencing", sequencingFiles);
assertRemapRegressionFilesStaging("sequencing", sequencingFiles, sequencingDir);
verifyExpectedOutput(
"browser_sequencing_remap",
interp.runRemapFile(
`${sequencingDir}/test.ngc`,
`${sequencingDir}/test.ini`,
),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parse_remap_2=0",
"remap_parse_remap_3=0",
"remap_parse_remap_4=0",
"remap_parse_remap_5=0",
"remap_parse_remap_6=0",
"remap_parse_remap_7=0",
"remap_parsed_remap_count=7",
"remap_remaps_ready=1",
"remap_file_open=0",
"remap_file_read_count=10084",
"remap_file_execute_count=10084",
"remap_file_final_rc=1",
"remap_file_reached_exit=1",
"remap_canon_event=MESSAGE: seq=1.000000",
"remap_canon_event=MESSAGE: seq=5.000000",
"remap_canon_event=MESSAGE: seq=6.000000",
"remap_canon_event=MESSAGE: seq=7.000000",
"remap_canon_event=MESSAGE: seq=8.000000 - reset to 1",
"remap_canon_event=MESSAGE: seq=8.000000",
"remap_canon_event=MESSAGE: seq=9.000000",
"remap_canon_event=MESSAGE: seq=10.000000 - reset to 1",
"remap_canon_event=PROGRAM_END",
"remap_linuxcnc_file_execute=1",
].join("\n"),
);
const remapIoFiles = [
"test-ngc.ini",
"io_input_m66.ngc",
"io_output_m62.ngc",
"io_output_m63.ngc",
"io_output_m64.ngc",
"io_output_m65.ngc",
"io_output_m67.ngc",
"io_output_m68.ngc",
];
const remapIoDir = await writeRemapRegressionFiles(interp, "remap-io", remapIoFiles);
assertRemapRegressionFilesStaging("remap-io", remapIoFiles, remapIoDir);
const remapIoGeneratedToolTablePath = `${remapIoDir}/simpockets.tbl`;
assertRemapRegressionGeneratedFileStaging("remap-io", "simpockets.tbl", remapIoGeneratedToolTablePath);
interp.writeTextFile(remapIoGeneratedToolTablePath, "T2 P2 Z0 ;remap-io tool\n");
verifyExpectedOutput(
"browser_remap_io_ngc_mdi",
interp.runRemapIoMdiSequence(`${remapIoDir}/test-ngc.ini`),
[
"remap_ini_open=1",
"remap_subroutine_path_count=1",
"remap_parse_remap_1=0",
"remap_parse_remap_7=0",
"remap_parsed_remap_count=7",
"remap_remaps_ready=1",
"remap_mdi_M62 P1_execute_0=0",
"remap_mdi_M66 P1_execute_0=2",
"remap_mdi_M66 P1_execute_1=0",
"remap_mdi_M66 E1 L0_execute_0=2",
"remap_mdi_M66 E1 L0_execute_1=0",
"remap_mdi_parameter_5399=42.13",
"remap_mdi_parameter_5399=-13.42",
"remap_mdi_canon_event=SET_MOTION_OUTPUT_BIT index=0",
"remap_mdi_canon_event=CLEAR_MOTION_OUTPUT_BIT index=0",
"remap_mdi_canon_event=SET_AUX_OUTPUT_BIT index=0",
"remap_mdi_canon_event=CLEAR_AUX_OUTPUT_BIT index=0",
"remap_mdi_canon_event=WAIT index=0 input_type=1 wait_type=0 timeout=0",
"remap_mdi_canon_event=WAIT index=0 input_type=0 wait_type=0 timeout=0",
"remap_mdi_canon_event=SET_MOTION_OUTPUT_VALUE index=0 value=42.13",
"remap_mdi_canon_event=SET_AUX_OUTPUT_VALUE index=0 value=-13.42",
"remap_io_ngc_linuxcnc_mdi_sequence=1",
].join("\n"),
);
const doWhileBreakNgcFiles = [
"bug.ngc",
"test.ngc",
];
const doWhileBreakDir = await writeInterpRegressionFiles(
interp,
"do-while-break",
doWhileBreakNgcFiles,
);
assertInterpRegressionFilesStaging("do-while-break", doWhileBreakNgcFiles, doWhileBreakDir);
const doWhileBreakPath = `${doWhileBreakDir}/test.ngc`;
verifyExpectedOutput(
"browser_interp_do_while_break",
interp.runFile(doWhileBreakPath),
[
"file_open=0",
"file_read_count=30",
"file_execute_count=30",
"canon_event=MESSAGE: must-execute-outer",
"canon_event=MESSAGE: must-execute-nested",
"canon_event=MESSAGE: must-execute-inner",
"canon_event=MESSAGE: post-inner-while",
"canon_event=MESSAGE: post-nested-while",
"canon_event=MESSAGE: post-outer-while",
"canon_event=PROGRAM_END",
"absent=canon_event=MESSAGE: do-not-execute",
].join("\n"),
);
const doWhileBreakBugPath = `${doWhileBreakDir}/bug.ngc`;
verifyExpectedOutput(
"browser_interp_do_while_break_bug",
interp.runFile(doWhileBreakBugPath),
[
"file_open=0",
"file_read_count=7",
"file_execute_count=7",
"file_saw_error=0",
"canon_event=PROGRAM_END",
"absent=@end of do-while-loop",
"absent=file_saw_error=1",
].join("\n"),
);
const owordBug315Path = await writeInterpRegressionFile(
interp,
"oword-bug315",
"test.ngc",
);
const owordBug315NgcFiles = [
"test.ngc",
];
if (owordBug315NgcFiles.length !== 1 || new Set(owordBug315NgcFiles).size !== owordBug315NgcFiles.length) {
throw new Error("browser_interp_oword_bug315_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("oword-bug315", "test.ngc", owordBug315Path);
verifyExpectedOutput(
"browser_interp_oword_bug315",
interp.runFile(owordBug315Path),
[
"file_open=0",
"file_read_count=23",
"file_execute_count=23",
"canon_event=MESSAGE: running sub",
"canon_event=MESSAGE: breaking out of sub",
"canon_event=MESSAGE: done",
"canon_event=PROGRAM_END",
].join("\n"),
);
const owordBug315P2Path = await writeInterpRegressionFile(
interp,
"oword-bug315-p2",
"test.ngc",
);
const owordBug315P2NgcFiles = [
"test.ngc",
];
if (owordBug315P2NgcFiles.length !== 1 || new Set(owordBug315P2NgcFiles).size !== owordBug315P2NgcFiles.length) {
throw new Error("browser_interp_oword_bug315_p2_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("oword-bug315-p2", "test.ngc", owordBug315P2Path);
verifyExpectedOutput(
"browser_interp_oword_bug315_p2",
interp.runFile(owordBug315P2Path),
[
"file_open=0",
"file_read_count=26",
"file_execute_count=26",
"canon_event=STRAIGHT_TRAVERSE line=7 x=2 y=2 z=2",
"canon_event=MESSAGE: executing this one",
"canon_event=PROGRAM_END",
"absent=canon_event=MESSAGE: not executing this one",
].join("\n"),
);
const existsPath = await writeInterpRegressionFile(interp, "exists", "test.ngc");
const existsNgcFiles = [
"test.ngc",
];
if (existsNgcFiles.length !== 1 || new Set(existsNgcFiles).size !== existsNgcFiles.length) {
throw new Error("browser_interp_exists_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("exists", "test.ngc", existsPath);
verifyExpectedOutput(
"browser_interp_exists",
interp.runFile(existsPath),
[
"file_open=0",
"file_read_count=7",
"file_execute_count=7",
"canon_event=STRAIGHT_TRAVERSE line=2 x=1 y=0 z=1",
"canon_event=COMMENT: comment",
"canon_event=STRAIGHT_TRAVERSE line=6 x=1 y=0 z=0",
"canon_event=PROGRAM_END",
].join("\n"),
);
const returnValuePath = await writeInterpRegressionFile(
interp,
"return-value",
"test.ngc",
);
const returnValueNgcFiles = [
"test.ngc",
];
if (returnValueNgcFiles.length !== 1 || new Set(returnValueNgcFiles).size !== returnValueNgcFiles.length) {
throw new Error("browser_interp_return_value_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("return-value", "test.ngc", returnValuePath);
verifyExpectedOutput(
"browser_interp_return_value",
interp.runFile(returnValuePath),
[
"file_open=0",
"file_read_count=75",
"file_execute_count=75",
"canon_event=MESSAGE: line 6.000000: _value: - expected 0.000000, got 0.000000",
"canon_event=MESSAGE: line 28.000000: call with arg1=2.000000 expect 246.000000, got 246.000000",
"canon_event=MESSAGE: line 37.000000: call with arg1=-1.000000 expect 0.000000, got 0.000000",
"canon_event=MESSAGE: line 47.000000: call with arg1=0.000000 expect 4712.000000, got 4712.000000",
"canon_event=MESSAGE: line 53.000000: _value=4712.000000 - expected 4712.000000",
"canon_event=PROGRAM_END",
"absent=fail:",
].join("\n"),
);
const subsFollowMainPath = await writeInterpRegressionFile(
interp,
"subs-follow-main",
"test.ngc",
);
const subsFollowMainNgcFiles = [
"test.ngc",
];
if (subsFollowMainNgcFiles.length !== 1 || new Set(subsFollowMainNgcFiles).size !== subsFollowMainNgcFiles.length) {
throw new Error("browser_interp_subs_follow_main_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("subs-follow-main", "test.ngc", subsFollowMainPath);
verifyExpectedOutput(
"browser_interp_subs_follow_main",
interp.runFile(subsFollowMainPath),
[
"file_open=0",
"file_read_count=9",
"file_execute_count=9",
"canon_event=COMMENT: Subs may follow main program ",
"canon_event=PALLET_SHUTTLE",
"canon_event=PROGRAM_END",
].join("\n"),
);
const fractionalLinenumbersPath = await writeInterpRegressionFile(
interp,
"fractional-linenumbers",
"test.ngc",
);
const fractionalLinenumbersNgcFiles = [
"test.ngc",
];
if (fractionalLinenumbersNgcFiles.length !== 1 || new Set(fractionalLinenumbersNgcFiles).size !== fractionalLinenumbersNgcFiles.length) {
throw new Error("browser_interp_fractional_linenumbers_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("fractional-linenumbers", "test.ngc", fractionalLinenumbersPath);
verifyExpectedOutput(
"browser_interp_fractional_linenumbers",
interp.runFile(fractionalLinenumbersPath),
[
"file_open=0",
"file_read_count=4",
"file_execute_count=4",
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=300",
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=600",
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=1200",
"canon_event=PROGRAM_END",
].join("\n"),
);
const crazyPathsPath = await writeInterpRegressionFile(
interp,
"crazy-paths",
"test.ngc",
);
const crazyPathsNgcFiles = [
"test.ngc",
];
if (crazyPathsNgcFiles.length !== 1 || new Set(crazyPathsNgcFiles).size !== crazyPathsNgcFiles.length) {
throw new Error("browser_interp_crazy_paths_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("crazy-paths", "test.ngc", crazyPathsPath);
verifyExpectedOutput(
"browser_interp_crazy_paths",
interp.runFile(crazyPathsPath),
[
"file_open=0",
"file_read_count=1526",
"file_execute_count=1526",
"file_saw_error=0",
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=ARC_FEED line=8 first_end=-99.2047 second_end=-102.892 first_axis=-98.9043 second_axis=-102.097 rotation=-1 axis_end_point=0",
"canon_event=ARC_FEED line=103 first_end=-40.3437 second_end=-103.278 first_axis=-45.0196 second_axis=-102.442 rotation=-1 axis_end_point=0",
"canon_event=STRAIGHT_FEED line=260 x=3.58919 y=30.3694 z=0",
"canon_event=ARC_FEED line=445 first_end=101.539 second_end=44.5843 first_axis=100.896 second_axis=46.212 rotation=1 axis_end_point=0",
"canon_event=STRAIGHT_FEED line=469 x=98.8154 y=102.176 z=0",
"canon_event=PROGRAM_END",
].join("\n"),
);
const camNisleyFiles = [
"cam.ngc",
"test.tbl",
];
const camNisleyDir = await writeInterpRegressionFiles(interp, "cam-nisley", camNisleyFiles);
assertInterpRegressionFilesStaging("cam-nisley", camNisleyFiles, camNisleyDir);
interp.writeTextFile(
`${camNisleyDir}/test.ini`,
[
"[EMCIO]",
"TOOL_TABLE = test.tbl",
"[TRAJ]",
"COORDINATES = X Y Z A B C U V W",
"",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_cam_nisley",
interp.runFileWithIni(`${camNisleyDir}/cam.ngc`, `${camNisleyDir}/test.ini`),
[
"file_open=0",
"file_read_count=1584",
"file_execute_count=1584",
"file_saw_error=0",
"canon_event=COMMENT: Ed Nisley - Nov 2006 - Mar 2007",
"canon_event=SELECT_TOOL tool=1",
"canon_event=CHANGE_TOOL",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
"canon_event=ARC_FEED line=223 first_end=1.91351e-18 second_end=-15.1438",
"canon_event=STRAIGHT_TRAVERSE line=241 x=75 y=0 z=75",
"canon_event=MESSAGE: Done!",
"canon_event=PROGRAM_END",
"absent=file_error_text=Requested tool 1 not found in the tool table",
].join("\n"),
);
const camNisleyBareInterp = await createLinuxCncInterpSdk({
locateFile(path) {
if (path === "linuxcnc_interp.wasm") {
return "../../build/wasm/core/linuxcnc_interp.wasm";
}
return path;
},
print() {},
printErr(message) {
console.error(message);
},
});
const camNisleyBarePath = "/work/browser-interp/cam-nisley-bare/cam.ngc";
assertInterpRegressionFileStaging("cam-nisley-bare", "cam.ngc", camNisleyBarePath);
await writeFetchedTextFile(
camNisleyBareInterp,
"../../vendor/linuxcnc/tests/interp/cam-nisley/cam.ngc",
camNisleyBarePath,
);
verifyExpectedOutput(
"browser_interp_cam_nisley_bare",
camNisleyBareInterp.runFile(camNisleyBarePath),
[
"file_open=0",
"file_read_count=57",
"file_execute_count=57",
"file_saw_error=1",
"file_error_text=Requested tool 1 not found in the tool table",
].join("\n"),
);
const namedparamBug424Path = await writeInterpRegressionFile(
interp,
"namedparam-bug424",
"test.ngc",
);
const namedparamBug424NgcFiles = [
"test.ngc",
];
if (namedparamBug424NgcFiles.length !== 1 || new Set(namedparamBug424NgcFiles).size !== namedparamBug424NgcFiles.length) {
throw new Error("browser_interp_namedparam_bug424_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("namedparam-bug424", "test.ngc", namedparamBug424Path);
verifyExpectedOutput(
"browser_interp_namedparam_bug424",
interp.runFile(namedparamBug424Path),
[
"file_open=0",
"file_read_count=6",
"file_execute_count=6",
"setup.current_x=2",
"setup.current_y=3",
"setup.current_z=4",
"canon_event=USE_LENGTH_UNITS units=1",
"canon_event=STRAIGHT_TRAVERSE line=5 x=2 y=3 z=4",
"canon_event=PROGRAM_END",
].join("\n"),
);
const flowsnakePath = await writeInterpRegressionFile(
interp,
"flowsnake",
"flowsnake.ngc",
);
const flowsnakeNgcFiles = [
"flowsnake.ngc",
];
if (flowsnakeNgcFiles.length !== 1 || new Set(flowsnakeNgcFiles).size !== flowsnakeNgcFiles.length) {
throw new Error("browser_interp_flowsnake_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("flowsnake", "flowsnake.ngc", flowsnakePath);
verifyExpectedOutput(
"browser_interp_flowsnake",
interp.runFile(flowsnakePath),
[
"file_open=0",
"file_read_count=6414",
"file_execute_count=6414",
"file_saw_error=0",
"canon_event=COMMENT: Program to mill a flowsnake",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=STRAIGHT_TRAVERSE line=33 x=0.25 y=1 z=1",
"canon_event=STRAIGHT_FEED line=13 x=3.75 y=1 z=0",
"canon_event=STRAIGHT_FEED line=13 x=2 y=3.95 z=0",
"canon_event=STRAIGHT_FEED line=13 x=0.25 y=1 z=0",
"canon_event=STOP_SPINDLE_TURNING spindle=0",
"setup.current_x=0.25",
"setup.current_y=1",
"setup.current_z=1",
].join("\n"),
);
const insideCornersPath = await writeInterpRegressionFile(
interp,
"inside-corners",
"test.ngc",
);
const insideCornersNgcFiles = [
"test.ngc",
];
if (insideCornersNgcFiles.length !== 1 || new Set(insideCornersNgcFiles).size !== insideCornersNgcFiles.length) {
throw new Error("browser_interp_inside_corners_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("inside-corners", "test.ngc", insideCornersPath);
verifyExpectedOutput(
"browser_interp_inside_corners",
interp.runFile(insideCornersPath),
[
"file_open=0",
"file_read_count=397",
"file_execute_count=397",
"file_saw_error=0",
"canon_event=PROGRAM_STOP",
"canon_event=DWELL seconds=3",
"canon_event=SELECT_PLANE plane=1",
"canon_event=SELECT_PLANE plane=3",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
"canon_event=ARC_FEED line=16 first_end=1.2281 second_end=1.3456 first_axis=0.628 second_axis=1.0458 rotation=1 axis_end_point=-0.1",
"canon_event=STRAIGHT_FEED line=14 x=0.701154 y=0.162499 z=-0.1",
"canon_event=ARC_FEED line=74 first_end=0.298564 second_end=1.82077 first_axis=0.3459 second_axis=1.7626 rotation=1 axis_end_point=0",
"canon_event=STRAIGHT_FEED line=107 x=0 y=0 z=1",
"canon_event=PROGRAM_END",
].join("\n"),
);
const inverseTimeWithCompPath = await writeInterpRegressionFile(
interp,
"inverse-time-with-comp",
"inverse.ngc",
);
const inverseTimeWithCompNgcFiles = [
"inverse.ngc",
];
if (inverseTimeWithCompNgcFiles.length !== 1 || new Set(inverseTimeWithCompNgcFiles).size !== inverseTimeWithCompNgcFiles.length) {
throw new Error("browser_interp_inverse_time_with_comp_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging(
"inverse-time-with-comp",
"inverse.ngc",
inverseTimeWithCompPath,
);
verifyExpectedOutput(
"browser_interp_inverse_time_with_comp",
interp.runFile(inverseTimeWithCompPath),
[
"file_open=0",
"file_read_count=68",
"file_execute_count=68",
"file_saw_error=0",
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=COMMENT: interpreter: feed mode set to inverse time",
"canon_event=COMMENT: interpreter: feed mode set to units per minute",
"canon_event=SET_FEED_RATE rate=30",
"canon_event=SET_FEED_RATE rate=20",
"canon_event=SET_FEED_RATE rate=40",
"canon_event=SET_FEED_RATE rate=33",
"canon_event=SET_FEED_RATE rate=10",
"canon_event=SET_FEED_RATE rate=25",
"canon_event=STRAIGHT_FEED line=5 x=1 y=0 z=0",
"canon_event=ARC_FEED line=6 first_end=1.2 second_end=0",
"canon_event=STRAIGHT_FEED line=13 x=0.2 y=3.82918 z=0",
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=5.2",
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=4.2",
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=4.8",
"canon_event=COMMENT: interpreter: cutter radius compensation off",
"canon_event=PROGRAM_END",
].join("\n"),
);
const ccompLatheCompDir = await writeCcompRegressionFiles(interp, "lathe-comp");
assertCcompRegressionFilesStaging("lathe-comp", ccompLatheCompDir);
verifyExpectedOutput(
"browser_interp_ccomp_lathe_comp",
interp.runFileWithIni(
`${ccompLatheCompDir}/test.ngc`,
`${ccompLatheCompDir}/test.ini`,
),
[
"file_open=0",
"file_read_count=49",
"file_execute_count=49",
"file_saw_error=0",
"canon_event=SELECT_PLANE plane=3",
"canon_event=SELECT_TOOL tool=2",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=SELECT_TOOL tool=7",
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
"canon_event=PROGRAM_END",
].join("\n"),
);
const ccompMillG90G91G92Dir = await writeCcompRegressionFiles(
interp,
"mill-g90g91g92",
);
assertCcompRegressionFilesStaging("mill-g90g91g92", ccompMillG90G91G92Dir);
verifyExpectedOutput(
"browser_interp_ccomp_mill_g90g91g92",
interp.runFileWithIni(
`${ccompMillG90G91G92Dir}/test.ngc`,
`${ccompMillG90G91G92Dir}/test.ini`,
),
[
"file_open=0",
"file_read_count=28",
"file_execute_count=28",
"file_saw_error=0",
"canon_event=SELECT_TOOL tool=3",
"canon_event=SET_G92_OFFSET x=0 y=0 z=0.5",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=COMMENT: interpreter: cutter radius compensation off",
"canon_event=PROGRAM_END",
].join("\n"),
);
const ccompMillLineArcEntryDir = await writeCcompRegressionFiles(
interp,
"mill-line-arc-entry",
);
assertCcompRegressionFilesStaging("mill-line-arc-entry", ccompMillLineArcEntryDir);
verifyExpectedOutput(
"browser_interp_ccomp_mill_line_arc_entry",
interp.runFileWithIni(
`${ccompMillLineArcEntryDir}/test.ngc`,
`${ccompMillLineArcEntryDir}/test.ini`,
),
[
"file_open=0",
"file_read_count=33",
"file_execute_count=33",
"file_saw_error=0",
"canon_event=SELECT_TOOL tool=4",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=ARC_FEED line=23 first_end=2 second_end=3.01969",
"canon_event=COMMENT: interpreter: cutter radius compensation off",
"canon_event=PROGRAM_END",
].join("\n"),
);
const ccompMillZchangesDir = await writeCcompRegressionFiles(interp, "mill-zchanges");
assertCcompRegressionFilesStaging("mill-zchanges", ccompMillZchangesDir);
verifyExpectedOutput(
"browser_interp_ccomp_mill_zchanges",
interp.runFileWithIni(
`${ccompMillZchangesDir}/test.ngc`,
`${ccompMillZchangesDir}/test.ini`,
),
[
"file_open=0",
"file_read_count=81",
"file_execute_count=81",
"file_saw_error=0",
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
"canon_event=ARC_FEED line=15 first_end=0.5 second_end=0.752461",
"canon_event=STRAIGHT_FEED line=54 x=0.49826 y=-0.24826 z=0",
"canon_event=COMMENT: interpreter: cutter radius compensation off",
"canon_event=PROGRAM_END",
].join("\n"),
);
const badInterpFixtures = [
{
name: "a_in_canned_cycle",
file: "a-in-canned-cycle.ngc",
readCount: 3,
executeCount: 2,
errorText: "Cannot put an a in canned cycle",
},
{
name: "a_in_canned_cycle2",
file: "a-in-canned-cycle2.ngc",
readCount: 2,
executeCount: 1,
errorText: "Cannot put an a in canned cycle",
},
{
name: "bad_arc_big_imperial_center_format",
file: "bad-arc.big.imperial.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X300.1428,Y0.0000) end=(X600.0000,Y0.0000) r1=300.1428 r2=299.8572 abs_err=0.2857 rel_err=0.0952%",
},
{
name: "bad_arc_big_metric_center_format",
file: "bad-arc.big.metric.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X3001.4284,Y0.0000) end=(X6000.0000,Y0.0000) r1=3001.4284 r2=2998.5716 abs_err=2.857 rel_err=0.0952%",
},
{
name: "bad_arc_medium_imperial_center_format",
file: "bad-arc.medium.imperial.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X99.8500,Y0.0000) end=(X200.0000,Y0.0000) r1=99.8500 r2=100.1500 abs_err=0.3 rel_err=0.2996%",
},
{
name: "bad_arc_medium_metric_center_format",
file: "bad-arc.medium.metric.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X998.5000,Y0.0000) end=(X2000.0000,Y0.0000) r1=998.5000 r2=1001.5000 abs_err=3 rel_err=0.2996%",
},
{
name: "bad_arc_small_imperial_center_format",
file: "bad-arc.small.imperial.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X2.4986,Y0.0000) end=(X5.0000,Y0.0000) r1=2.4986 r2=2.5014 abs_err=0.002857 rel_err=0.1142%",
},
{
name: "bad_arc_small_metric_center_format",
file: "bad-arc.small.metric.center-format.ngc",
readCount: 6,
executeCount: 6,
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X24.9857,Y0.0000) end=(X50.0000,Y0.0000) r1=24.9857 r2=25.0143 abs_err=0.02857 rel_err=0.1142%",
},
{
name: "ccomp_arcexit",
file: "ccomp-arcexit.ngc",
readCount: 7,
executeCount: 7,
errorText: "The move just after exiting cutter compensation mode must be straight, not an arc",
},
{
name: "ccomp_gouging",
file: "ccomp-gouging.ngc",
readCount: 7,
executeCount: 7,
errorText: "Straight feed in concave corner cannot be reached by the tool without gouging",
},
{
name: "exists_1",
file: "exists-1.ngc",
readCount: 2,
executeCount: 1,
errorText: "Expected # reading parameter",
},
{
name: "exists_2",
file: "exists-2.ngc",
readCount: 2,
executeCount: 1,
errorText: "Expected ] reading bracketed parameter",
},
{
name: "exists_3",
file: "exists-3.ngc",
readCount: 3,
executeCount: 2,
errorText: "Parameter number out of range",
},
{
name: "exists_4",
file: "exists-4.ngc",
readCount: 2,
executeCount: 1,
errorText: "Unknown word starting with f",
},
{
name: "exists_5",
file: "exists-5.ngc",
readCount: 2,
executeCount: 1,
errorText: "Named parameter not terminated",
},
{
name: "exists_6",
file: "exists-6.ngc",
readCount: 2,
executeCount: 1,
errorText: "bad number format (conversion failed) parsing ''",
},
{
name: "exists_7",
file: "exists-7.ngc",
readCount: 2,
executeCount: 1,
errorText: "Expected ] reading bracketed parameter",
},
{
name: "nested",
file: "nested.ngc",
readCount: 6,
executeCount: 5,
errorText: "Nested subroutine definition",
},
{
name: "no_feed_rate",
file: "no-feed-rate.ngc",
readCount: 2,
executeCount: 2,
errorText: "Cannot do g1 with zero feed rate",
},
{
name: "no_ijr",
file: "no-ijr.ngc",
readCount: 2,
executeCount: 2,
errorText: "R i j k words all missing for arc",
},
{
name: "probe_no_axes",
file: "probe-no-axes.ngc",
readCount: 2,
executeCount: 1,
errorText: "All axes missing with motion code",
},
];
if (badInterpFixtures.length !== 21 || new Set(badInterpFixtures.map((fixture) => fixture.file)).size !== badInterpFixtures.length) {
throw new Error("browser_interp_bad_fixture_coverage: list drift");
}
for (const badFixture of badInterpFixtures) {
const badPath = await writeInterpRegressionFile(interp, "bad", badFixture.file);
assertInterpRegressionFileStaging("bad", badFixture.file, badPath);
verifyExpectedOutput(
`browser_interp_bad_${badFixture.name}`,
interp.runFile(badPath),
[
"file_open=0",
`file_read_count=${badFixture.readCount}`,
`file_execute_count=${badFixture.executeCount}`,
"file_saw_error=1",
`file_error_text=${badFixture.errorText}`,
"canon_event=ON_RESET",
].join("\n"),
);
}
const g33_1Path = await writeInterpRegressionFile(interp, "g33.1", "g33.1.ngc");
const g33_1NgcFiles = [
"g33.1.ngc",
];
if (g33_1NgcFiles.length !== 1 || new Set(g33_1NgcFiles).size !== g33_1NgcFiles.length) {
throw new Error("browser_interp_g33_1_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("g33.1", "g33.1.ngc", g33_1Path);
verifyExpectedOutput(
"browser_interp_g33_1",
interp.runFile(g33_1Path),
[
"file_open=0",
"file_read_count=5",
"file_execute_count=5",
"file_saw_error=0",
"run_step phase=execute step=4 rc=0 line=4",
"statement_uri=g33.1%20z-1.2%20k0.1",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.1 velocity_mode=0",
"canon_event=RIGID_TAP line=4 x=0 y=0 z=-1.2 scale=1",
"canon_event=STOP_SPEED_FEED_SYNCH",
"canon_event=PROGRAM_END",
].join("\n"),
);
const goodArcFixtures = [
{
name: "big_imperial_center_format",
file: "good-arc.big.imperial.center-format.ngc",
x: 600,
firstEnd: 600,
firstAxis: 300.14,
},
{
name: "big_metric_center_format",
file: "good-arc.big.metric.center-format.ngc",
x: 6000,
firstEnd: 6000,
firstAxis: 3001.4,
},
{
name: "medium_imperial_center_format",
file: "good-arc.medium.imperial.center-format.ngc",
x: 200,
firstEnd: 200,
firstAxis: 99.95,
},
{
name: "medium_metric_center_format",
file: "good-arc.medium.metric.center-format.ngc",
x: 2000,
firstEnd: 2000,
firstAxis: 999.5,
},
{
name: "small_imperial_center_format",
file: "good-arc.small.imperial.center-format.ngc",
x: 5,
firstEnd: 5,
firstAxis: 2.4986,
},
{
name: "small_metric_center_format",
file: "good-arc.small.metric.center-format.ngc",
x: 50,
firstEnd: 50,
firstAxis: 24.986,
},
];
if (goodArcFixtures.length !== 6 || new Set(goodArcFixtures.map((fixture) => fixture.file)).size !== goodArcFixtures.length) {
throw new Error("browser_interp_good_fixture_coverage: list drift");
}
for (const goodArcFixture of goodArcFixtures) {
const goodArcPath = await writeInterpRegressionFile(interp, "good", goodArcFixture.file);
assertInterpRegressionFileStaging("good", goodArcFixture.file, goodArcPath);
verifyExpectedOutput(
`browser_interp_good_arc_${goodArcFixture.name}`,
interp.runFile(goodArcPath),
[
"file_open=0",
"file_read_count=5",
"file_execute_count=5",
"file_saw_error=0",
`run_step phase=execute step=4 rc=0 line=4 x=${goodArcFixture.x} y=0`,
`canon_event=ARC_FEED line=4 first_end=${goodArcFixture.firstEnd} second_end=0 first_axis=${goodArcFixture.firstAxis} second_axis=0 rotation=-1 axis_end_point=0`,
"canon_event=PROGRAM_END",
"absent=Radius to end of arc differs from radius to start",
].join("\n"),
);
}
const g6164Path = await writeInterpRegressionFile(interp, "g6164", "test.ngc");
const g6164NgcFiles = [
"test.ngc",
];
if (g6164NgcFiles.length !== 1 || new Set(g6164NgcFiles).size !== g6164NgcFiles.length) {
throw new Error("browser_interp_g6164_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging("g6164", "test.ngc", g6164Path);
verifyExpectedOutput(
"browser_interp_g6164",
interp.runFile(g6164Path),
[
"file_open=0",
"file_read_count=6",
"file_execute_count=6",
"canon_event=SET_MOTION_CONTROL_MODE mode=3 tolerance=0",
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=0",
"canon_event=SET_MOTION_CONTROL_MODE mode=3 tolerance=1",
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=1",
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=2",
"canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0",
"canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g72Fixtures = [
{
name: "facing",
dir: "g72-facing",
file: "g72-iterations-present.ngc",
finalZ: "-39.999",
},
{
name: "missing_iteration",
dir: "g72-missing-iteration",
file: "g72-iterations-missing.ngc",
finalZ: "-40",
},
];
if (g72Fixtures.length !== 2 || new Set(g72Fixtures.map((fixture) => `${fixture.dir}/${fixture.file}`)).size !== g72Fixtures.length) {
throw new Error("browser_interp_g72_fixture_coverage: list drift");
}
for (const g72Fixture of g72Fixtures) {
const g72Path = await writeInterpRegressionFile(interp, g72Fixture.dir, g72Fixture.file);
assertInterpRegressionFileStaging(g72Fixture.dir, g72Fixture.file, g72Path);
verifyExpectedOutput(
`browser_interp_g72_${g72Fixture.name}`,
interp.runFile(g72Path),
[
"file_open=0",
"file_read_count=10",
"file_execute_count=10",
"file_saw_error=0",
"run_step phase=execute step=8 rc=0 line=8 x=100 y=0 z=0",
"canon_event=STRAIGHT_TRAVERSE line=8 x=50 y=0 z=0",
"canon_event=STRAIGHT_FEED line=-1 x=100 y=0 z=-39",
`canon_event=STRAIGHT_FEED line=-1 x=100 y=0 z=${g72Fixture.finalZ}`,
"canon_event=PROGRAM_END",
"absent=program seem to be stuck",
].join("\n"),
);
}
const g71Fixtures = [
{
name: "endless_loop",
dir: "g71-endless-loop",
file: "g71-endless-loop.ngc",
readCount: 19,
executeCount: 19,
keyEvents: [
"canon_event=STRAIGHT_TRAVERSE line=17 x=15 y=0 z=0",
"canon_event=ARC_FEED line=-1 first_end=-14.04 second_end=10",
"canon_event=STRAIGHT_FEED line=-1 x=15 y=0 z=-21.96",
],
},
{
name: "endless_loop2",
dir: "g71-endless-loop2",
file: "g71-endless-loop2.ngc",
readCount: 12,
executeCount: 12,
keyEvents: [
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=STRAIGHT_TRAVERSE line=9 x=168 y=0 z=0",
"canon_event=STRAIGHT_FEED line=-1 x=170 y=0 z=-415",
"canon_event=STRAIGHT_TRAVERSE line=11 x=300 y=0 z=10",
],
},
{
name: "endless_loop_2",
dir: "g71-endless-loop_2",
file: "g71-endless-loop_2.ngc",
readCount: 40,
executeCount: 40,
keyEvents: [
"canon_event=COMMENT: interpreter: Lathe diameter mode changed to diameter",
"canon_event=STRAIGHT_TRAVERSE line=31 x=10 y=0 z=2.8",
"canon_event=ARC_FEED line=-1 first_end=-27 second_end=10.113",
"canon_event=COMMENT: interpreter: cutter radius compensation off",
"canon_event=STRAIGHT_TRAVERSE line=38 x=12.5 y=0 z=-25",
],
},
{
name: "with_g70",
dir: "g71-with-g70",
file: "g71-with-g70.ngc",
readCount: 32,
executeCount: 32,
keyEvents: [
"canon_event=ARC_FEED line=-1 first_end=1.62661 second_end=1.78761",
"canon_event=ARC_FEED line=-1 first_end=-36.1 second_end=13",
"canon_event=STRAIGHT_FEED line=-1 x=15 y=0 z=-38.1",
],
},
];
if (g71Fixtures.length !== 4 || new Set(g71Fixtures.map((fixture) => `${fixture.dir}/${fixture.file}`)).size !== g71Fixtures.length) {
throw new Error("browser_interp_g71_fixture_coverage: list drift");
}
for (const g71Fixture of g71Fixtures) {
const g71Path = await writeInterpRegressionFile(interp, g71Fixture.dir, g71Fixture.file);
assertInterpRegressionFileStaging(g71Fixture.dir, g71Fixture.file, g71Path);
verifyExpectedOutput(
`browser_interp_g71_${g71Fixture.name}`,
interp.runFile(g71Path),
[
"file_open=0",
`file_read_count=${g71Fixture.readCount}`,
`file_execute_count=${g71Fixture.executeCount}`,
"file_saw_error=0",
...g71Fixture.keyEvents,
"canon_event=PROGRAM_END",
"absent=program seem to be stuck",
"absent=killing",
].join("\n"),
);
}
const g76Files = [
"g76only.ngc",
"test.tbl",
];
const g76Dir = await writeInterpRegressionFiles(interp, "g76", g76Files);
assertInterpRegressionFilesStaging("g76", g76Files, g76Dir);
interp.writeTextFile(
`${g76Dir}/test.ini`,
[
"[EMCIO]",
"TOOL_TABLE = test.tbl",
"[TRAJ]",
"COORDINATES = X Y Z A B C U V W",
"",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_g76",
interp.runFileWithIni(`${g76Dir}/g76only.ngc`, `${g76Dir}/test.ini`),
[
"file_open=0",
"file_read_count=30",
"file_execute_count=30",
"file_saw_error=0",
"run_step phase=execute step=6 rc=2 line=6",
"statement_uri=t4%20m6",
"run_step phase=execute step=25 rc=0 line=25 x=0.2 y=0 z=-0.5",
"canon_event=SELECT_TOOL tool=4",
"canon_event=CHANGE_TOOL",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.05 velocity_mode=0",
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.0672681 velocity_mode=0",
"canon_event=STOP_SPEED_FEED_SYNCH",
"canon_event=STRAIGHT_TRAVERSE line=25 x=0.237 y=0 z=0.195474",
"canon_event=STRAIGHT_TRAVERSE line=26 x=0.5 y=0 z=-0.5",
"canon_event=PROGRAM_END",
"absent=Requested tool 4 not found",
].join("\n"),
);
const rotationAbsPtsPath = await writeInterpRegressionFile(
interp,
"rotation/abs-pts",
"test.ngc",
);
assertInterpRegressionFileStaging("rotation/abs-pts", "test.ngc", rotationAbsPtsPath);
const rotationRegressionFiles = [
"abs-pts/test.ngc",
"g28/g28.ngc",
"g53/g53.ngc",
];
if (rotationRegressionFiles.length !== 3 || new Set(rotationRegressionFiles).size !== rotationRegressionFiles.length) {
throw new Error("browser_interp_rotation_fixture_coverage: list drift");
}
const rotationAbsPtsOutput = interp.runFile(rotationAbsPtsPath);
verifyExpectedOutput(
"browser_interp_rotation_abs_pts",
rotationAbsPtsOutput,
[
"file_open=0",
"file_read_count=192",
"file_execute_count=192",
"file_saw_error=0",
"canon_event=MESSAGE: 0.000000 0.000000 0.000000",
"canon_event=MESSAGE: 1.000000 2.000000 3.000000",
"canon_event=PROGRAM_END",
].join("\n"),
);
if (
(rotationAbsPtsOutput.match(/canon_event=MESSAGE: 0\.000000 0\.000000 0\.000000/g) || [])
.length !== 14
) {
throw new Error("browser_interp_rotation_abs_pts: zero absolute-position message count");
}
if (
(rotationAbsPtsOutput.match(/canon_event=MESSAGE: 1\.000000 2\.000000 3\.000000/g) || [])
.length !== 14
) {
throw new Error("browser_interp_rotation_abs_pts: target absolute-position message count");
}
const rotationG28Path = await writeInterpRegressionFile(interp, "rotation/g28", "g28.ngc");
assertInterpRegressionFileStaging("rotation/g28", "g28.ngc", rotationG28Path);
const rotationG28Output = interp.runFile(rotationG28Path);
verifyExpectedOutput(
"browser_interp_rotation_g28",
rotationG28Output,
[
"file_open=0",
"file_read_count=13",
"file_execute_count=13",
"file_saw_error=0",
"canon_event=SET_XY_ROTATION rotation=45",
"canon_event=COMMENT: G55 G28",
"canon_event=STRAIGHT_TRAVERSE line=9 x=10 y=10 z=0",
"canon_event=COMMENT: G56 G28",
"canon_event=STRAIGHT_TRAVERSE line=14 x=0 y=14.1421 z=0 a=1",
"canon_event=FINISH",
].join("\n"),
);
if (!/canon_event=STRAIGHT_TRAVERSE line=14 x=(0|[0-9.e-]+) y=14\.1421 z=0 a=0/.test(rotationG28Output)) {
throw new Error("browser_interp_rotation_g28: G56 G28 final machine point");
}
const rotationG53Path = await writeInterpRegressionFile(interp, "rotation/g53", "g53.ngc");
assertInterpRegressionFileStaging("rotation/g53", "g53.ngc", rotationG53Path);
const rotationG53Output = interp.runFile(rotationG53Path);
verifyExpectedOutput(
"browser_interp_rotation_g53",
rotationG53Output,
[
"file_open=0",
"file_read_count=17",
"file_execute_count=17",
"file_saw_error=0",
"canon_event=SET_XY_ROTATION rotation=45",
"canon_event=COMMENT: g53 + g55 to 1,1",
"canon_event=COMMENT: g53 + g55 + g92 to 1,1",
"canon_event=STRAIGHT_TRAVERSE line=13 x=0 y=0 z=0",
"canon_event=COMMENT: g53 + g56 + g92 to 1,1",
"canon_event=STRAIGHT_TRAVERSE line=18 x=-0.414214 y=1 z=0 a=1",
"canon_event=FINISH",
].join("\n"),
);
if (!/canon_event=STRAIGHT_TRAVERSE line=8 x=1\.41421 y=([0-9.e-]+) z=0/.test(rotationG53Output)) {
throw new Error("browser_interp_rotation_g53: rotated G53 endpoint");
}
const iniparamPlan = await stageInterpIniContext(interp, "iniparam");
const iniparamNgcFiles = [
"test.ngc",
];
if (iniparamNgcFiles.length !== 1 || new Set(iniparamNgcFiles).size !== iniparamNgcFiles.length) {
throw new Error("browser_interp_iniparam_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging("iniparam", iniparamNgcFiles, iniparamPlan);
verifyExpectedOutput(
"browser_interp_iniparam",
interp.runFileWithIniContinueOnError(
iniparamPlan.programPath,
iniparamPlan.iniPath,
),
[
"file_open=0",
"file_read_count=9",
"file_execute_count=8",
"file_saw_error=1",
"file_error_text=Named parameter #<_ini[nosuchsection]nosuchname> not defined",
"setup.current_x=10",
"setup.current_y=20",
"setup.current_z=30",
"canon_event=STRAIGHT_TRAVERSE line=1 x=10 y=20 z=30",
"canon_event=MESSAGE: position now: 10.000000 20.000000 30.000000",
"canon_event=MESSAGE: not in INI: ######",
"canon_event=PROGRAM_END",
].join("\n"),
);
const iniparamFailassignPlan = await stageInterpIniContext(
interp,
"iniparam-failassign",
);
const iniparamFailassignNgcFiles = [
"test.ngc",
];
if (iniparamFailassignNgcFiles.length !== 1 || new Set(iniparamFailassignNgcFiles).size !== iniparamFailassignNgcFiles.length) {
throw new Error("browser_interp_iniparam_failassign_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"iniparam-failassign",
iniparamFailassignNgcFiles,
iniparamFailassignPlan,
);
verifyExpectedOutput(
"browser_interp_iniparam_failassign",
interp.runFileWithIni(
iniparamFailassignPlan.programPath,
iniparamFailassignPlan.iniPath,
),
[
"file_open=0",
"file_execute_1=5",
"file_read_count=1",
"file_execute_count=1",
"file_saw_error=1",
"file_error_text=Cannot assign to read-only parameter #<_ini[vars]toolchange_x>",
"absent=canon_event=MESSAGE: notreached",
].join("\n"),
);
const subCallFromSubPlan = await stageInterpIniContext(interp, "sub-call-from-sub");
const subCallFromSubNgcFiles = [
"subs/caller.ngc",
"subs/helper.ngc",
"test.ngc",
];
if (subCallFromSubNgcFiles.length !== 3 || new Set(subCallFromSubNgcFiles).size !== subCallFromSubNgcFiles.length) {
throw new Error("browser_interp_sub_call_from_sub_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"sub-call-from-sub",
subCallFromSubNgcFiles,
subCallFromSubPlan,
);
verifyExpectedOutput(
"browser_interp_sub_call_from_sub",
interp.runFileWithIni(
subCallFromSubPlan.programPath,
subCallFromSubPlan.iniPath,
),
[
"file_open=0",
"file_read_count=10",
"file_execute_count=10",
"canon_event=COMMENT: Test: calling a sub from within another sub is valid",
"canon_event=PROGRAM_END",
].join("\n"),
);
const sequenceNumberPlan = await stageInterpIniContext(interp, "sequence-number");
const sequenceNumberNgcFiles = [
"rm400.ngc",
"test.ngc",
];
if (sequenceNumberNgcFiles.length !== 2 || new Set(sequenceNumberNgcFiles).size !== sequenceNumberNgcFiles.length) {
throw new Error("browser_interp_sequence_number_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"sequence-number",
sequenceNumberNgcFiles,
sequenceNumberPlan,
);
verifyExpectedOutput(
"browser_interp_sequence_number",
interp.runFileWithIni(
sequenceNumberPlan.programPath,
sequenceNumberPlan.iniPath,
),
[
"file_open=0",
"file_read_count=13",
"file_execute_count=13",
"canon_event=MESSAGE: main: line=7.000000 - expect 7",
"canon_event=MESSAGE: in rm400.ngc line=2.000000 - expect 2",
"canon_event=MESSAGE: main: line=9.000000 - expect 9",
"canon_event=PROGRAM_END",
].join("\n"),
);
const nestedSubErrorPlan = await stageInterpIniContext(interp, "nested-sub-error");
const nestedSubErrorNgcFiles = [
"subs/nested.ngc",
"test.ngc",
];
if (nestedSubErrorNgcFiles.length !== 2 || new Set(nestedSubErrorNgcFiles).size !== nestedSubErrorNgcFiles.length) {
throw new Error("browser_interp_nested_sub_error_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"nested-sub-error",
nestedSubErrorNgcFiles,
nestedSubErrorPlan,
);
verifyExpectedOutput(
"browser_interp_nested_sub_error",
interp.runFileWithIni(
nestedSubErrorPlan.programPath,
nestedSubErrorPlan.iniPath,
),
[
"file_open=0",
"file_read_4=5",
"file_error_text=Nested subroutine definition: 'O100 sub' found inside called subroutine 'Onested'",
"file_read_count=4",
"file_execute_count=3",
"canon_event=COMMENT: Test: nested sub definition inside named sub should error",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const nestedSubInFileErrorPlan = await stageInterpIniContext(
interp,
"nested-sub-in-file-error",
);
const nestedSubInFileErrorNgcFiles = [
"subs/sequential.ngc",
"test.ngc",
];
if (nestedSubInFileErrorNgcFiles.length !== 2 || new Set(nestedSubInFileErrorNgcFiles).size !== nestedSubInFileErrorNgcFiles.length) {
throw new Error("browser_interp_nested_sub_in_file_error_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"nested-sub-in-file-error",
nestedSubInFileErrorNgcFiles,
nestedSubInFileErrorPlan,
);
verifyExpectedOutput(
"browser_interp_nested_sub_in_file_error",
interp.runFileWithIni(
nestedSubInFileErrorPlan.programPath,
nestedSubInFileErrorPlan.iniPath,
),
[
"file_open=0",
"file_execute_5=5",
"file_error_text=Subroutine 'O200' not found -- not in offset table and no file '",
"file_read_count=5",
"file_execute_count=5",
"canon_event=COMMENT: Test: numbered sub after named endsub in same file should error",
"canon_event=MESSAGE: sequential main: 7.000000 8.000000 9.000000",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const owordUnwindPlan = await stageInterpIniContext(interp, "oword-unwind");
const owordUnwindNgcFiles = [
"fail.ngc",
"test.ngc",
];
if (owordUnwindNgcFiles.length !== 2 || new Set(owordUnwindNgcFiles).size !== owordUnwindNgcFiles.length) {
throw new Error("browser_interp_oword_unwind_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"oword-unwind",
owordUnwindNgcFiles,
owordUnwindPlan,
);
verifyExpectedOutput(
"browser_interp_oword_unwind",
interp.runFileWithIniContinueOnError(
owordUnwindPlan.programPath,
owordUnwindPlan.iniPath,
),
[
"file_open=0",
"file_read_5=5",
"file_read_10=5",
"file_read_count=11",
"file_execute_count=9",
"file_saw_error=1",
"canon_event=MESSAGE: pre divide-by-zero",
"canon_event=PROGRAM_END",
"absent=canon_event=MESSAGE: post divide-by-zero",
].join("\n"),
);
const abortHotCommentPlan = await stageInterpIniContext(interp, "abort-hot-comment");
const abortHotCommentNgcFiles = [
"test.ngc",
];
if (abortHotCommentNgcFiles.length !== 1 || new Set(abortHotCommentNgcFiles).size !== abortHotCommentNgcFiles.length) {
throw new Error("browser_interp_abort_hot_comment_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"abort-hot-comment",
abortHotCommentNgcFiles,
abortHotCommentPlan,
);
verifyExpectedOutput(
"browser_interp_abort_hot_comment",
interp.runFileWithIniContinueOnError(
abortHotCommentPlan.programPath,
abortHotCommentPlan.iniPath,
),
[
"file_open=0",
"file_read_count=125",
"file_execute_count=125",
"file_saw_error=1",
"file_error_text= MixedCase param42=20.000000 named=4711.000000 INI=3.140000",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const m19Plan = await stageInterpIniContext(interp, "m19");
const m19NgcFiles = [
"test.ngc",
];
if (m19NgcFiles.length !== 1 || new Set(m19NgcFiles).size !== m19NgcFiles.length) {
throw new Error("browser_interp_m19_fixture_coverage: list drift");
}
assertInterpIniContextNgcStaging(
"m19",
m19NgcFiles,
m19Plan,
);
verifyExpectedOutput(
"browser_interp_m19",
interp.runFileWithIni(m19Plan.programPath, m19Plan.iniPath),
[
"file_open=0",
"file_read_count=8",
"file_execute_count=8",
"file_saw_error=0",
"canon_event=ORIENT_SPINDLE spindle=0 orientation=87 mode=0",
"canon_event=WAIT_SPINDLE_ORIENT_COMPLETE spindle=0 timeout=2",
"canon_event=ORIENT_SPINDLE spindle=0 orientation=42 mode=1",
"canon_event=ORIENT_SPINDLE spindle=0 orientation=132 mode=0",
"canon_event=ORIENT_SPINDLE spindle=0 orientation=132 mode=1",
"canon_event=WAIT_SPINDLE_ORIENT_COMPLETE spindle=0 timeout=1",
"canon_event=PROGRAM_END",
].join("\n"),
);
const magicCommentsParamFormatPath = await writeInterpRegressionFile(
interp,
"magic_comments/param_format_printing",
"test.ngc",
);
const magicCommentsRegressionFiles = [
"param_format_printing/test.ngc",
];
if (magicCommentsRegressionFiles.length !== 1 || new Set(magicCommentsRegressionFiles).size !== magicCommentsRegressionFiles.length) {
throw new Error("browser_interp_magic_comments_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging(
"magic_comments/param_format_printing",
"test.ngc",
magicCommentsParamFormatPath,
);
verifyExpectedOutput(
"browser_interp_magic_comments_param_format",
interp.runFile(magicCommentsParamFormatPath),
[
"file_open=0",
"file_read_count=13",
"file_execute_count=13",
"file_saw_error=0",
"canon_event=MESSAGE: native value = 1.123457",
"canon_event=MESSAGE: Test round to integer: 1",
"canon_event=MESSAGE: Test round to 4 decimals: 1.1235",
"canon_event=MESSAGE: Test format separate from param: The value is: 1.1235",
"canon_event=MESSAGE: Test round to 7 decimals: 1.1234568",
"canon_event=MESSAGE: native value2 = 2.345679",
"canon_event=MESSAGE: Test2 round all params in line to 4 decimals: The values are: 1.1235, 2.3457",
"canon_event=MESSAGE: Test2 only round last value to integer: The values are: 1.123457, 2",
"canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99RegressionFiles = [
"01-basics/test.ngc",
"02-variables/test.ngc",
"03-error-M98-no-P-word/test.ngc",
"04-M98-but-no-sub/test.ngc",
"05-M98-loops/test.ngc",
"06-error-mixed-sub-styles/O...-called-with-O..._call.ngc",
"06-error-mixed-sub-styles/O...-ended-with-O..._endsub.ngc",
"06-error-mixed-sub-styles/O...-sub-called-with-M98.ngc",
"06-error-mixed-sub-styles/O...-sub-ended-with-M99.ngc",
"07-nested-subs/test.ngc",
"08-sub-follows-main/test.ngc",
"09-disable-fanuc-subs/test-fanuc.ngc",
"09-disable-fanuc-subs/test-rs274ngc.ngc",
"10-M98-P001/test.ngc",
"11-main-program-oword/test-illegal-end-main-with-eof.ngc",
"11-main-program-oword/test-illegal-no-m30-before-osub.ngc",
"11-main-program-oword/test-illegal-sub-after-percent.ngc",
"11-main-program-oword/test-legal-end-main-with-m02.ngc",
"11-main-program-oword/test-legal-end-main-with-m2.ngc",
"11-main-program-oword/test-legal-end-main-with-m30.ngc",
"11-main-program-oword/test-legal-end-main-with-percent.ngc",
"13-named-program/test-named.ngc",
"13-named-program/test-numbered.ngc",
"14-o-expression-call/test.ngc",
];
if (m98m99RegressionFiles.length !== 24 || new Set(m98m99RegressionFiles).size !== m98m99RegressionFiles.length) {
throw new Error("browser_interp_m98m99_fixture_coverage: list drift");
}
const m98m99BasicsPath = await writeInterpRegressionFile(
interp,
"m98m99/01-basics",
"test.ngc",
);
assertInterpRegressionFileStaging("m98m99/01-basics", "test.ngc", m98m99BasicsPath);
verifyExpectedOutput(
"browser_interp_m98m99_01_basics",
interp.runFile(m98m99BasicsPath),
[
"file_open=0",
"file_read_count=22",
"file_execute_count=22",
"file_saw_error=0",
"canon_event=COMMENT: A simple O sub example ",
"canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
"canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
"canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99VariablesPath = await writeInterpRegressionFile(
interp,
"m98m99/02-variables",
"test.ngc",
);
assertInterpRegressionFileStaging("m98m99/02-variables", "test.ngc", m98m99VariablesPath);
const m98m99VariablesRun = runWithCapturedPrints(() =>
interp.runFile(m98m99VariablesPath),
);
verifyExpectedOutput(
"browser_interp_m98m99_02_variables",
m98m99VariablesRun.output,
[
"file_open=0",
"file_read_count=54",
"file_execute_count=54",
"file_saw_error=0",
"canon_event=COMMENT: Fanuc params have global scope; rs274ngc params #1..#30 have local scope ",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_02_variables_prints",
m98m99VariablesRun.prints,
[
"Q MAIN/FANUC: POST-M98: 1=13.010000; 30=13.300000; 31=13.310000",
"Q MAIN/RS274NGC: POST-O-CALL: 1=42.010000; 30=42.300000; 31=13.310000",
].join("\n"),
);
const m98m99M98NoPWordPath = await writeInterpRegressionFile(
interp,
"m98m99/03-error-M98-no-P-word",
"test.ngc",
);
assertInterpRegressionFileStaging(
"m98m99/03-error-M98-no-P-word",
"test.ngc",
m98m99M98NoPWordPath,
);
verifyExpectedOutput(
"browser_interp_m98m99_03_error_m98_no_p_word",
interp.runFile(m98m99M98NoPWordPath),
[
"file_open=0",
"file_read_count=3",
"file_execute_count=2",
"file_saw_error=1",
"file_error_text=Found 'm98' code with no P-word",
"run_step phase=read step=3 rc=5 line=4",
"statement_uri=M98",
"canon_event=COMMENT: Fanuc M98 requires P-word ",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99M98ButNoSubPath = await writeInterpRegressionFile(
interp,
"m98m99/04-M98-but-no-sub",
"test.ngc",
);
assertInterpRegressionFileStaging(
"m98m99/04-M98-but-no-sub",
"test.ngc",
m98m99M98ButNoSubPath,
);
verifyExpectedOutput(
"browser_interp_m98m99_04_m98_but_no_sub",
interp.runFile(m98m99M98ButNoSubPath),
[
"file_open=0",
"file_read_count=5",
"file_execute_count=4",
"file_saw_error=1",
"file_error_text=Failed to find sub 'O1' before EOF",
"run_step phase=read step=5 rc=5 line=4",
"statement_uri=%25",
"canon_event=COMMENT: M98 call non-existant sub ",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99M98LoopsPath = await writeInterpRegressionFile(
interp,
"m98m99/05-M98-loops",
"test.ngc",
);
assertInterpRegressionFileStaging("m98m99/05-M98-loops", "test.ngc", m98m99M98LoopsPath);
const m98m99M98LoopsRun = runWithCapturedPrints(() =>
interp.runFile(m98m99M98LoopsPath),
);
verifyExpectedOutput(
"browser_interp_m98m99_05_m98_loops",
m98m99M98LoopsRun.output,
[
"file_open=0",
"file_read_count=102",
"file_execute_count=102",
"file_saw_error=0",
"canon_event=COMMENT: A simple O sub example ",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_05_m98_loops_prints",
m98m99M98LoopsRun.prints,
[
"X SUB O1 PARAM1 = 10.000000",
"X MAIN 10LOOP PARAM1 = 10.000000",
"X MAIN 0LOOP PARAM1 = 0.000000",
"X MAIN 2LOOP PARAM1 = 2.000000",
].join("\n"),
);
const m98m99MixedSubStyleFiles = [
"O...-called-with-O..._call.ngc",
"O...-ended-with-O..._endsub.ngc",
"O...-sub-called-with-M98.ngc",
"O...-sub-ended-with-M99.ngc",
];
const m98m99MixedSubStyleDir = await writeInterpRegressionFiles(
interp,
"m98m99/06-error-mixed-sub-styles",
m98m99MixedSubStyleFiles,
);
assertInterpRegressionFilesStaging(
"m98m99/06-error-mixed-sub-styles",
m98m99MixedSubStyleFiles,
m98m99MixedSubStyleDir,
);
for (const mixedSubStyleFixture of [
{
name: "called_with_o_call",
file: "O...-called-with-O..._call.ngc",
readCount: 5,
executeCount: 5,
errorText: "Fanuc 'O....' subroutine must be called with 'M98'",
errorStep: "run_step phase=execute step=5 rc=5 line=4",
statementUri: "statement_uri=O1",
comment: "canon_event=COMMENT: Fanuc 'O...' sub must be called with M98 ",
},
{
name: "ended_with_o_endsub",
file: "O...-ended-with-O..._endsub.ngc",
readCount: 7,
executeCount: 7,
errorText: "Fanuc 'O....' subroutine definition must end with 'M99'",
errorStep: "run_step phase=execute step=7 rc=5 line=4",
statementUri: "statement_uri=O1%20endsub",
comment: "canon_event=COMMENT: Fanuc 'O...' sub must end with M99 ",
print: "X FANUC",
},
{
name: "sub_called_with_m98",
file: "O...-sub-called-with-M98.ngc",
readCount: 5,
executeCount: 5,
errorText: "'O.... sub' subroutine must be called with 'O.... call'",
errorStep: "run_step phase=execute step=5 rc=5 line=4",
statementUri: "statement_uri=O1%20sub",
comment: "canon_event=COMMENT: RS274NGC 'O...' sub must not be called with M98 ",
},
{
name: "sub_ended_with_m99",
file: "O...-sub-ended-with-M99.ngc",
readCount: 7,
executeCount: 7,
errorText: "'O.... endsub' or 'O.... return' must follow 'O.... sub' subroutine definition",
errorStep: "run_step phase=execute step=7 rc=5 line=4",
statementUri: "statement_uri=M99",
comment: "canon_event=COMMENT: RS274NGC 'O... sub' sub must not end with M99 ",
print: "X FANUC",
},
]) {
const mixedSubStylePath = `${m98m99MixedSubStyleDir}/${mixedSubStyleFixture.file}`;
const mixedSubStyleRun = runWithCapturedPrints(() =>
interp.runFile(mixedSubStylePath),
);
verifyExpectedOutput(
`browser_interp_m98m99_06_${mixedSubStyleFixture.name}`,
mixedSubStyleRun.output,
[
"file_open=0",
`file_read_count=${mixedSubStyleFixture.readCount}`,
`file_execute_count=${mixedSubStyleFixture.executeCount}`,
"file_saw_error=1",
`file_error_text=${mixedSubStyleFixture.errorText}`,
mixedSubStyleFixture.errorStep,
mixedSubStyleFixture.statementUri,
mixedSubStyleFixture.comment,
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
if (mixedSubStyleFixture.print) {
verifyExpectedOutput(
`browser_interp_m98m99_06_${mixedSubStyleFixture.name}_prints`,
mixedSubStyleRun.prints,
mixedSubStyleFixture.print,
);
}
}
const m98m99NestedSubsPath = await writeInterpRegressionFile(
interp,
"m98m99/07-nested-subs",
"test.ngc",
);
assertInterpRegressionFileStaging("m98m99/07-nested-subs", "test.ngc", m98m99NestedSubsPath);
const m98m99NestedSubsRun = runWithCapturedPrints(() =>
interp.runFile(m98m99NestedSubsPath),
);
verifyExpectedOutput(
"browser_interp_m98m99_07_nested_subs",
m98m99NestedSubsRun.output,
[
"file_open=0",
"file_read_count=163",
"file_execute_count=163",
"file_saw_error=0",
"canon_event=COMMENT: A nested Fanuc subroutine example ",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_07_nested_subs_prints",
m98m99NestedSubsRun.prints,
[
"X >>>> LOOP [O2.O1]: 4.500000",
"X MAIN END: 1=5.000000",
].join("\n"),
);
const m98m99SubFollowsMainPath = await writeInterpRegressionFile(
interp,
"m98m99/08-sub-follows-main",
"test.ngc",
);
assertInterpRegressionFileStaging(
"m98m99/08-sub-follows-main",
"test.ngc",
m98m99SubFollowsMainPath,
);
const m98m99SubFollowsMainRun = runWithCapturedPrints(() =>
interp.runFile(m98m99SubFollowsMainPath),
);
verifyExpectedOutput(
"browser_interp_m98m99_08_sub_follows_main",
m98m99SubFollowsMainRun.output,
[
"file_open=0",
"file_read_count=9",
"file_execute_count=9",
"file_saw_error=0",
"canon_event=COMMENT: Fanuc-style subs may follow main program ",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_08_sub_follows_main_prints",
m98m99SubFollowsMainRun.prints,
"X IN O1",
);
const m98m99DisableFanucSubsFiles = [
"test-fanuc.ini",
"test-no-fanuc.ini",
"test-fanuc.ngc",
"test-rs274ngc.ngc",
];
const m98m99DisableFanucSubsDir = await writeInterpRegressionFiles(
interp,
"m98m99/09-disable-fanuc-subs",
m98m99DisableFanucSubsFiles,
);
assertInterpRegressionFilesStaging(
"m98m99/09-disable-fanuc-subs",
m98m99DisableFanucSubsFiles,
m98m99DisableFanucSubsDir,
);
for (const disableFanucCase of [
{
name: "fanuc_ini",
program: "test-fanuc.ngc",
ini: "test-fanuc.ini",
readCount: 22,
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
keyFeed: "canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
},
{
name: "rs274ngc_ini",
program: "test-rs274ngc.ngc",
ini: "test-fanuc.ini",
readCount: 15,
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
},
{
name: "rs274ngc_no_fanuc",
program: "test-rs274ngc.ngc",
ini: "test-no-fanuc.ini",
readCount: 15,
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
},
{
name: "fanuc_default",
program: "test-fanuc.ngc",
readCount: 22,
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
keyFeed: "canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
},
{
name: "rs274ngc_default",
program: "test-rs274ngc.ngc",
readCount: 15,
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
},
]) {
const output = disableFanucCase.ini
? interp.runFileWithIni(
`${m98m99DisableFanucSubsDir}/${disableFanucCase.program}`,
`${m98m99DisableFanucSubsDir}/${disableFanucCase.ini}`,
)
: interp.runFile(`${m98m99DisableFanucSubsDir}/${disableFanucCase.program}`);
verifyExpectedOutput(
`browser_interp_m98m99_09_${disableFanucCase.name}`,
output,
[
"file_open=0",
`file_read_count=${disableFanucCase.readCount}`,
`file_execute_count=${disableFanucCase.readCount}`,
"file_saw_error=0",
"canon_event=COMMENT: A simple O sub example ",
disableFanucCase.keyTraverse,
disableFanucCase.keyFeed,
"canon_event=PROGRAM_END",
"absent=file_error_text=DISABLE_FANUC_STYLE_SUB",
].join("\n"),
);
}
verifyExpectedOutput(
"browser_interp_m98m99_09_fanuc_no_fanuc",
interp.runFileWithIni(
`${m98m99DisableFanucSubsDir}/test-fanuc.ngc`,
`${m98m99DisableFanucSubsDir}/test-no-fanuc.ini`,
),
[
"file_open=0",
"file_read_count=5",
"file_execute_count=4",
"file_saw_error=1",
"file_error_text=DISABLE_FANUC_STYLE_SUB set in INI file, but found m98",
"run_step phase=read step=5 rc=5 line=6",
"statement_uri=M98%20P1%20L3",
"canon_event=STRAIGHT_TRAVERSE line=5 x=0 y=0 z=0.25",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99M98P001Path = await writeInterpRegressionFile(
interp,
"m98m99/10-M98-P001",
"test.ngc",
);
assertInterpRegressionFileStaging("m98m99/10-M98-P001", "test.ngc", m98m99M98P001Path);
const m98m99M98P001Run = runWithCapturedPrints(() =>
interp.runFile(m98m99M98P001Path),
);
verifyExpectedOutput(
"browser_interp_m98m99_10_m98_p001",
m98m99M98P001Run.output,
[
"file_open=0",
"file_read_count=8",
"file_execute_count=8",
"file_saw_error=0",
"canon_event=COMMENT: main program",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_10_m98_p001_prints",
m98m99M98P001Run.prints,
[
"x got here",
"absent=ERROR: should not get here",
].join("\n"),
);
const m98m99MainProgramOwordFiles = [
"test-illegal-end-main-with-eof.ngc",
"test-illegal-no-m30-before-osub.ngc",
"test-illegal-sub-after-percent.ngc",
"test-legal-end-main-with-m02.ngc",
"test-legal-end-main-with-m2.ngc",
"test-legal-end-main-with-m30.ngc",
"test-legal-end-main-with-percent.ngc",
];
const m98m99MainProgramOwordDir = await writeInterpRegressionFiles(
interp,
"m98m99/11-main-program-oword",
m98m99MainProgramOwordFiles,
);
assertInterpRegressionFilesStaging(
"m98m99/11-main-program-oword",
m98m99MainProgramOwordFiles,
m98m99MainProgramOwordDir,
);
for (const mainProgramFixture of [
{
name: "legal_m2",
file: "test-legal-end-main-with-m2.ngc",
readCount: 17,
firstLine: 4,
subLine: 13,
endEvent: "canon_event=PROGRAM_END",
},
{
name: "legal_m02",
file: "test-legal-end-main-with-m02.ngc",
readCount: 17,
firstLine: 4,
subLine: 13,
endEvent: "canon_event=PROGRAM_END",
},
{
name: "legal_m30",
file: "test-legal-end-main-with-m30.ngc",
readCount: 17,
firstLine: 4,
subLine: 13,
endEvent: "canon_event=PROGRAM_END",
},
{
name: "legal_percent",
file: "test-legal-end-main-with-percent.ngc",
readCount: 20,
firstLine: 11,
subLine: 6,
endEvent: "canon_event=FINISH",
},
]) {
verifyExpectedOutput(
`browser_interp_m98m99_11_${mainProgramFixture.name}`,
interp.runFile(`${m98m99MainProgramOwordDir}/${mainProgramFixture.file}`),
[
"file_open=0",
`file_read_count=${mainProgramFixture.readCount}`,
`file_execute_count=${mainProgramFixture.readCount}`,
"file_saw_error=0",
"canon_event=COMMENT: legal example",
`canon_event=STRAIGHT_TRAVERSE line=${mainProgramFixture.firstLine} x=1 y=2 z=0`,
`canon_event=STRAIGHT_TRAVERSE line=${mainProgramFixture.subLine} x=1 y=2 z=3`,
mainProgramFixture.endEvent,
"absent=should never get here",
].join("\n"),
);
}
for (const mainProgramErrorFixture of [
{
name: "illegal_eof",
file: "test-illegal-end-main-with-eof.ngc",
readCount: 21,
executeCount: 20,
errorText: "File ended with no percent sign (%) or program end (M2)",
marker: "canon_event=COMMENT: end of main signaled by EOF",
},
{
name: "illegal_no_m30",
file: "test-illegal-no-m30-before-osub.ngc",
readCount: 18,
executeCount: 18,
errorText: "File:",
marker: "statement_uri=O2%20sub%20%28subprogram%20begin%29",
extraMarker: "sub: o|2| found in illegal location",
},
{
name: "illegal_sub_after_percent",
file: "test-illegal-sub-after-percent.ngc",
readCount: 8,
executeCount: 7,
errorText: "Failed to find sub 'O2' before EOF",
marker: "canon_event=FINISH",
},
]) {
verifyExpectedOutput(
`browser_interp_m98m99_11_${mainProgramErrorFixture.name}`,
interp.runFile(`${m98m99MainProgramOwordDir}/${mainProgramErrorFixture.file}`),
[
"file_open=0",
`file_read_count=${mainProgramErrorFixture.readCount}`,
`file_execute_count=${mainProgramErrorFixture.executeCount}`,
"file_saw_error=1",
`file_error_text=${mainProgramErrorFixture.errorText}`,
mainProgramErrorFixture.marker,
mainProgramErrorFixture.extraMarker ?? "",
"absent=canon_event=PROGRAM_END",
].join("\n"),
);
}
const m98m99NamedProgramFiles = [
"test-named.ngc",
"test-numbered.ngc",
];
const m98m99NamedProgramDir = await writeInterpRegressionFiles(
interp,
"m98m99/13-named-program",
m98m99NamedProgramFiles,
);
assertInterpRegressionFilesStaging(
"m98m99/13-named-program",
m98m99NamedProgramFiles,
m98m99NamedProgramDir,
);
verifyExpectedOutput(
"browser_interp_m98m99_13_named_program_named",
interp.runFile(`${m98m99NamedProgramDir}/test-named.ngc`),
[
"file_open=0",
"file_read_count=4",
"file_execute_count=4",
"file_saw_error=0",
"canon_event=COMMENT: test-named.ngc: Test named programs",
"canon_event=COMMENT: ...program body",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_13_named_program_numbered",
interp.runFile(`${m98m99NamedProgramDir}/test-numbered.ngc`),
[
"file_open=0",
"file_read_count=4",
"file_execute_count=4",
"file_saw_error=0",
"canon_event=COMMENT: test-numbered.ngc: Test numbered programs",
"canon_event=COMMENT: ...program body",
"canon_event=PROGRAM_END",
].join("\n"),
);
const m98m99OExpressionCallPath = await writeInterpRegressionFile(
interp,
"m98m99/14-o-expression-call",
"test.ngc",
);
assertInterpRegressionFileStaging(
"m98m99/14-o-expression-call",
"test.ngc",
m98m99OExpressionCallPath,
);
const m98m99OExpressionCallRun = runWithCapturedPrints(() =>
interp.runFile(m98m99OExpressionCallPath),
);
verifyExpectedOutput(
"browser_interp_m98m99_14_o_expression_call",
m98m99OExpressionCallRun.output,
[
"file_open=0",
"file_read_count=29",
"file_execute_count=29",
"file_saw_error=0",
"canon_event=PROGRAM_END",
].join("\n"),
);
verifyExpectedOutput(
"browser_interp_m98m99_14_o_expression_call_prints",
m98m99OExpressionCallRun.prints,
[
"In sub 100",
"In sub 200",
].join("\n"),
);
const g10RegressionFiles = [
"g10-l1-l10/test.ngc",
"g10-l11/test.ngc",
"g10-l2-while-active/test.ngc",
"g10-l20-while-active/test.ngc",
"g10-with-g92/test.ngc",
];
if (g10RegressionFiles.length !== 5 || new Set(g10RegressionFiles).size !== g10RegressionFiles.length) {
throw new Error("browser_interp_g10_fixture_coverage: list drift");
}
const g10L1L10Dir = await writeG10RegressionFiles(interp, "g10-l1-l10", [
"test.ngc",
"test.tbl",
]);
assertG10RegressionFilesStaging("g10-l1-l10", ["test.ngc", "test.tbl"], g10L1L10Dir);
verifyExpectedOutput(
"browser_interp_g10_l1_l10",
interp.runFileWithIni(`${g10L1L10Dir}/test.ngc`, `${g10L1L10Dir}/test.ini`),
[
"file_open=0",
"file_read_count=115",
"file_execute_count=115",
"file_saw_error=0",
"canon_event=MESSAGE: G10 L1: set tool offsets direct",
"canon_event=MESSAGE: G10 L10: set tool offsets relative to position + 45 deg. rotation",
"canon_event=MESSAGE: Model B contract: M6 alone and G10 alone do not change tool offset params",
"canon_event=MESSAGE: G10 alone does not apply offset, should still be 0 0 0: 0.000000 0.000000 0.000000",
"canon_event=USE_TOOL_LENGTH_OFFSET x=8 y=6 z=7",
"canon_event=SET_XY_ROTATION rotation=45",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g10L11Dir = await writeG10RegressionFiles(interp, "g10-l11", [
"test.ngc",
"test.tbl",
]);
assertG10RegressionFilesStaging("g10-l11", ["test.ngc", "test.tbl"], g10L11Dir);
verifyExpectedOutput(
"browser_interp_g10_l11",
interp.runFileWithIni(`${g10L11Dir}/test.ngc`, `${g10L11Dir}/test.ini`),
[
"file_open=0",
"file_read_count=41",
"file_execute_count=41",
"file_saw_error=0",
"canon_event=SET_G5X_OFFSET index=1 x=1 y=2 z=-3",
"canon_event=SET_G92_OFFSET x=-41.1962 y=-46.1962 z=-72",
"canon_event=USE_TOOL_LENGTH_OFFSET x=0 y=0 z=-4",
"canon_event=MESSAGE: -101.600000",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g10L2WhileActiveDir = await writeG10RegressionFiles(
interp,
"g10-l2-while-active",
["test.ngc"],
);
assertG10RegressionFilesStaging("g10-l2-while-active", ["test.ngc"], g10L2WhileActiveDir);
verifyExpectedOutput(
"browser_interp_g10_l2_while_active",
interp.runFile(`${g10L2WhileActiveDir}/test.ngc`),
[
"file_open=0",
"file_read_count=24",
"file_execute_count=24",
"file_saw_error=0",
"canon_event=SET_G5X_OFFSET index=1 x=1 y=0 z=0",
"canon_event=SET_XY_ROTATION rotation=-45",
"canon_event=MESSAGE: Should be 0 1.414214: 0.000000 1.414214",
"canon_event=SET_XY_ROTATION rotation=90",
"canon_event=MESSAGE: Should be 1 1: 1.000000 1.000000",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g10L20WhileActiveDir = await writeG10RegressionFiles(
interp,
"g10-l20-while-active",
["test.ngc"],
);
assertG10RegressionFilesStaging("g10-l20-while-active", ["test.ngc"], g10L20WhileActiveDir);
verifyExpectedOutput(
"browser_interp_g10_l20_while_active",
interp.runFile(`${g10L20WhileActiveDir}/test.ngc`),
[
"file_open=0",
"file_read_count=20",
"file_execute_count=20",
"file_saw_error=0",
"canon_event=SET_XY_ROTATION rotation=45",
"canon_event=MESSAGE: Should be -1 0: -1.000000 0.000000",
"canon_event=MESSAGE: Should be 0 -1: 0.000000 -1.000000",
"canon_event=MESSAGE: Should be 1 1: 1.000000 1.000000",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g10WithG92Dir = await writeG10RegressionFiles(interp, "g10-with-g92", [
"test.ngc",
"test.tbl",
]);
assertG10RegressionFilesStaging("g10-with-g92", ["test.ngc", "test.tbl"], g10WithG92Dir);
verifyExpectedOutput(
"browser_interp_g10_with_g92",
interp.runFileWithIni(`${g10WithG92Dir}/test.ngc`, `${g10WithG92Dir}/test.ini`),
[
"file_open=0",
"file_read_count=61",
"file_execute_count=61",
"file_saw_error=0",
"canon_event=SET_G5X_OFFSET index=9 x=25 y=26 z=27",
"canon_event=SET_G92_OFFSET x=-0.1 y=-0.2 z=-10.3",
"canon_event=MESSAGE: X0.100000 Y0.200000 Z0.300000",
"canon_event=MESSAGE: X-23.900000 Y-23.800000 Z-23.700000",
"canon_event=SET_G92_OFFSET x=0 y=0 z=0",
"canon_event=MESSAGE: X-24.000000 Y-24.000000 Z-34.000000",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g52G92InteractionPath = await writeInterpRegressionFile(
interp,
"g52/g52-g92-interaction",
"g52-g92-interaction.ngc",
);
const g52RegressionFiles = [
"g52-g92-interaction/g52-g92-interaction.ngc",
];
if (g52RegressionFiles.length !== 1 || new Set(g52RegressionFiles).size !== g52RegressionFiles.length) {
throw new Error("browser_interp_g52_fixture_coverage: list drift");
}
assertInterpRegressionFileStaging(
"g52/g52-g92-interaction",
"g52-g92-interaction.ngc",
g52G92InteractionPath,
);
verifyExpectedOutput(
"browser_interp_g52_g92_interaction",
interp.runFile(g52G92InteractionPath),
[
"file_open=0",
"file_read_count=39",
"file_execute_count=39",
"file_saw_error=0",
"canon_event=COMMENT: G52 and G92 param #5210 setting behavior",
"G92 params: 1.000000 -25.400000 -50.800000",
"G92 params: 0.000000 -25.400000 -50.800000",
"G92 params: 1.000000 50.800000 76.200000",
"G92 params: 1.000000 0.000000 0.000000",
"canon_event=SET_G92_OFFSET x=-1 y=-2 z=0",
"canon_event=SET_G92_OFFSET x=2 y=3 z=0",
"canon_event=PALLET_SHUTTLE",
"canon_event=PROGRAM_END",
].join("\n"),
);
const g92Persistence = writeG92PersistenceStaging(
interp,
"/work/browser-interp/g52/g92-persistence",
"/work/browser-interp/g52/g92-persistence",
);
verifyExpectedOutput(
"browser_interp_g92_persistence_enabled",
runWithCapturedPrints(() =>
interp.runFileWithIni(g92Persistence.programPath, g92Persistence.iniPath),
).prints,
"Startup G92 params: 1.000000 1.250000 2.500000 3.750000",
);
verifyExpectedOutput(
"browser_interp_g92_persistence_disabled",
runWithCapturedPrints(() =>
interp.runFileWithIni(g92Persistence.programPath, g92Persistence.disabledIniPath),
).prints,
"Startup G92 params: 0.000000 0.000000 0.000000 0.000000",
);
verifyExpectedOutput(
"restore_parameters_missing_file",
interp.restoreParameters(
generatedParameterPath("/work/browser-missing.var", "/work/browser-missing.var"),
),
"restore_parameters=0",
);
const outOfOrderParameterFilePath = writeGeneratedParameterFixture(
interp,
"/work/browser-out-of-order.var",
"/work/browser-out-of-order.var",
"5220 1\n5161 2\n",
);
verifyExpectedOutput(
"restore_parameters_out_of_order",
interp.restoreParameters(outOfOrderParameterFilePath),
[
"restore_parameters=5",
"restore_error_text=Parameter file out of order",
].join("\n"),
);
const missingRequiredParameterFilePath = writeGeneratedParameterFixture(
interp,
"/work/browser-missing-required.var",
"/work/browser-missing-required.var",
"5161 3.5\n5220 1\n",
);
verifyExpectedOutput(
"restore_parameters_missing_required",
interp.restoreParameters(missingRequiredParameterFilePath),
[
"restore_parameters=0",
"parameter_5161=3.5",
"parameter_5162=0",
].join("\n"),
);
const browserParameterFilePath = writeGeneratedParameterFixture(
interp,
"/work/browser-direct.var",
"/work/browser-direct.var",
[
"5161 10.5",
"5162 20.25",
"5220 1",
"5221 2.25",
"5399 44",
"<_named_param> 123",
"",
].join("\n"),
);
verifyExpectedOutput(
"restore_parameters_direct",
interp.restoreParameters(browserParameterFilePath),
[
"restore_parameters=0",
"parameter_5161=10.5",
"parameter_5162=20.25",
"parameter_5221=2.25",
"parameter_5399=44",
].join("\n"),
);
verifyExpectedOutput(
"save_parameters_direct",
interp.saveParameters(browserParameterFilePath, {
5161: 12.34,
5162: 56.78,
5220: 1.0,
5221: 9.87,
5399: 66.6,
}),
[
"save_parameters=0",
"parameter_5161=12.34",
"parameter_5162=56.78",
"parameter_5221=9.87",
"parameter_5399=66.6",
].join("\n"),
);
verifyExpectedOutput(
"saved_parameters_direct_text",
interp.readTextFile(browserParameterFilePath),
[
"5161\t12.340000",
"5162\t56.780000",
"5221\t9.870000",
"5399\t66.600000",
"absent=<_named_param>",
].join("\n"),
);
verifyExpectedOutput(
"saved_parameters_direct_backup",
interp.readTextFile(`${browserParameterFilePath}.bak`),
[
"5161 10.5",
"5162 20.25",
"5221 2.25",
"5399 44",
"<_named_param> 123",
].join("\n"),
);
const browserToolTablePath = writeGeneratedToolTableFixture(
interp,
"/work/browser-direct-tool.tbl",
"/work/browser-direct-tool.tbl",
[
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser direct finish tool",
"T5 P9 X1 Y2 Z3 ;browser direct rough tool",
"",
].join("\n"),
);
verifyExpectedOutput(
"load_tool_table_direct",
interp.loadToolTable(browserToolTablePath),
[
"tooldata_load=0",
"tooldata_last_index=2",
"tool_1.toolno=2",
"tool_1.pocketno=7",
"tool_1.z=3.125",
"tool_1.diameter=1.5",
"tool_1.frontangle=12",
"tool_1.backangle=34",
"tool_1.orientation=4",
"tool_1.comment=browser direct finish tool",
"tool_2.toolno=5",
"tool_2.pocketno=9",
"tool_2.comment=browser direct rough tool",
"tool_index_for_tool_2=1",
].join("\n"),
);
verifyExpectedOutput(
"save_tool_table_direct",
interp.saveToolTable(browserToolTablePath),
[
"tooldata_save=0",
"tool_1.toolno=2",
"tool_2.toolno=5",
].join("\n"),
);
verifyExpectedOutput(
"saved_tool_table_direct_text",
interp.readTextFile(browserToolTablePath),
[
"T2",
"P7",
"Z+3.125000",
"D+1.500000",
"I+12.000000",
"J+34.000000",
"Q4",
";browser direct finish tool",
"T5",
"P9",
";browser direct rough tool",
].join("\n"),
);
const browserRandomToolTablePath = writeGeneratedToolTableFixture(
interp,
"/work/browser-direct-random-tool.tbl",
"/work/browser-direct-random-tool.tbl",
[
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser direct random finish tool",
"T5 P9 X1 Y2 Z3 ;browser direct random rough tool",
"",
].join("\n"),
);
verifyExpectedOutput(
"load_tool_table_direct_random",
interp.loadToolTable(browserRandomToolTablePath, { randomToolChanger: true }),
[
"tooldata_random_toolchanger=1",
"tooldata_load=0",
"tooldata_last_index=9",
"tool_pocket_7.toolno=2",
"tool_pocket_7.pocketno=7",
"tool_pocket_7.z=3.125",
"tool_pocket_7.diameter=1.5",
"tool_pocket_7.frontangle=12",
"tool_pocket_7.backangle=34",
"tool_pocket_7.orientation=4",
"tool_pocket_7.comment=browser direct random finish tool",
"tool_pocket_9.toolno=5",
"tool_pocket_9.pocketno=9",
"tool_pocket_9.comment=browser direct random rough tool",
"tool_index_for_tool_2=7",
].join("\n"),
);
verifyExpectedOutput(
"save_tool_table_direct_random",
interp.saveToolTable(browserRandomToolTablePath),
[
"tooldata_save=0",
"tool_pocket_7.toolno=2",
"tool_pocket_9.toolno=5",
].join("\n"),
);
verifyExpectedOutput(
"saved_tool_table_direct_random_text",
interp.readTextFile(browserRandomToolTablePath),
[
"T2",
"P7",
";browser direct random finish tool",
"T5",
"P9",
";browser direct random rough tool",
].join("\n"),
);
const browserInterpMachineId = "browser-interp";
const browserInterpIniWasmPath = "/work/browser-machine.ini";
const browserInterpParameterWasmPath = "/work/browser-linuxcnc.var";
const browserInterpToolTableWasmPath = "/work/browser-tool.tbl";
const browserInterpTextPaths = await saveMachineTextFiles(browserInterpMachineId, {
ini: `[EMC]\nMACHINE = ${browserInterpMachineId}\n`,
toolTable: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser finish tool\n",
parameters: [
"5161 10.5",
"5162 20.25",
"5220 1",
"5221 2.25",
"5399 44",
"<_named_param> 123",
"",
].join("\n"),
});
assertMachineTextStorePaths(
"opfs_save_machine_text_files_staging",
browserInterpTextPaths,
browserInterpMachineId,
);
const loadedSession = await loadMachineSessionFromOpfs(
interp,
browserInterpMachineId,
{
iniWasmPath: browserInterpIniWasmPath,
parameterWasmPath: browserInterpParameterWasmPath,
toolTableWasmPath: browserInterpToolTableWasmPath,
},
);
assertOpfsSessionStaging(
"opfs_load_session_staging",
loadedSession,
browserInterpMachineId,
{
wasm: opfsSessionWasmPaths(
browserInterpIniWasmPath,
browserInterpParameterWasmPath,
browserInterpToolTableWasmPath,
),
},
);
verifyExpectedOutput(
"opfs_restore_parameters",
loadedSession.parameters.result,
[
"restore_parameters=0",
"parameter_5161=10.5",
"parameter_5162=20.25",
"parameter_5221=2.25",
"parameter_5399=44",
].join("\n"),
);
verifyExpectedOutput(
"opfs_load_tool_table",
loadedSession.toolTable.result,
[
"tooldata_load=0",
"tool_1.toolno=2",
"tool_1.pocketno=7",
"tool_1.z=3.125",
"tool_1.diameter=1.5",
"tool_1.frontangle=12",
"tool_1.backangle=34",
"tool_1.orientation=4",
"tool_1.comment=browser finish tool",
"tool_index_for_tool_2=1",
].join("\n"),
);
const savedParameters = await saveMachineParametersToOpfs(
interp,
browserInterpMachineId,
{
5161: 12.34,
5162: 56.78,
5220: 1.0,
5221: 9.87,
5399: 66.6,
},
{ wasmPath: browserInterpParameterWasmPath },
);
assertOpfsSavedParameterStaging(
"opfs_save_parameters_staging",
savedParameters,
browserInterpMachineId,
"linuxcnc.var",
browserInterpParameterWasmPath,
);
verifyExpectedOutput(
"opfs_save_parameters",
savedParameters.result,
[
"save_parameters=0",
"parameter_5161=12.34",
"parameter_5162=56.78",
"parameter_5221=9.87",
"parameter_5399=66.6",
].join("\n"),
);
const machineFiles = await loadMachineTextFiles(browserInterpMachineId);
verifyExpectedOutput(
"opfs_saved_parameter_text",
machineFiles.parameters,
[
"5161\t12.340000",
"5162\t56.780000",
"5221\t9.870000",
"5399\t66.600000",
"absent=<_named_param>",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_parameter_backup_text",
savedParameters.backupOpfsPath,
[
"5161 10.5",
"5162 20.25",
"5221 2.25",
"5399 44",
"<_named_param> 123",
].join("\n"),
);
const savedToolTable = await saveMachineToolTableToOpfs(
interp,
browserInterpMachineId,
{ wasmPath: browserInterpToolTableWasmPath },
);
assertOpfsSavedToolTableStaging(
"opfs_save_tool_table_staging",
savedToolTable,
browserInterpMachineId,
"tool.tbl",
browserInterpToolTableWasmPath,
);
verifyExpectedOutput(
"opfs_save_tool_table",
savedToolTable.result,
[
"tooldata_save=0",
"tool_1.toolno=2",
"tool_1.pocketno=7",
].join("\n"),
);
const savedMachineFiles = await loadMachineTextFiles(browserInterpMachineId);
verifyExpectedOutput(
"opfs_saved_tool_table_text",
savedMachineFiles.toolTable,
[
"T2",
"P7",
"Z+3.125000",
"D+1.500000",
"I+12.000000",
"J+34.000000",
"Q4",
";browser finish tool",
].join("\n"),
);
const browserRandomMachineId = "browser-random-toolchanger";
const browserRandomIniWasmPath = "/work/browser-random-machine.ini";
const browserRandomParameterWasmPath = "/work/browser-random-linuxcnc.var";
const browserRandomToolTableWasmPath = "/work/browser-random-tool.tbl";
const browserRandomTextPaths = await saveMachineTextFiles(browserRandomMachineId, {
ini: [
"[EMC]",
`MACHINE = ${browserRandomMachineId}`,
"",
"[EMCIO]",
"RANDOM_TOOLCHANGER = yes",
"",
].join("\n"),
toolTable: [
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser random finish tool",
"T5 P9 X1 Y2 Z3 ;browser random rough tool",
"",
].join("\n"),
parameters: [
"5161 10.5",
"5162 20.25",
"5220 1",
"5221 2.25",
"5399 44",
"",
].join("\n"),
});
assertMachineTextStorePaths(
"opfs_save_random_machine_text_files_staging",
browserRandomTextPaths,
browserRandomMachineId,
);
const loadedRandomSession = await loadMachineSessionFromOpfs(
interp,
browserRandomMachineId,
{
iniSdk: ini,
iniWasmPath: browserRandomIniWasmPath,
parameterWasmPath: browserRandomParameterWasmPath,
toolTableWasmPath: browserRandomToolTableWasmPath,
},
);
assertOpfsSessionStaging(
"opfs_load_random_session_staging",
loadedRandomSession,
browserRandomMachineId,
{
wasm: opfsSessionWasmPaths(
browserRandomIniWasmPath,
browserRandomParameterWasmPath,
browserRandomToolTableWasmPath,
),
},
);
verifyExpectedOutput(
"opfs_load_random_tool_table",
loadedRandomSession.toolTable.result,
[
"tooldata_random_toolchanger=1",
"tooldata_load=0",
"tool_pocket_7.toolno=2",
"tool_pocket_7.pocketno=7",
"tool_pocket_7.z=3.125",
"tool_pocket_7.diameter=1.5",
"tool_pocket_7.frontangle=12",
"tool_pocket_7.backangle=34",
"tool_pocket_7.orientation=4",
"tool_pocket_7.comment=browser random finish tool",
"tool_pocket_9.toolno=5",
"tool_pocket_9.pocketno=9",
"tool_index_for_tool_2=7",
].join("\n"),
);
const savedRandomToolTable = await saveMachineToolTableToOpfs(
interp,
browserRandomMachineId,
{ wasmPath: browserRandomToolTableWasmPath },
);
assertOpfsSavedToolTableStaging(
"opfs_save_random_tool_table_staging",
savedRandomToolTable,
browserRandomMachineId,
"tool.tbl",
browserRandomToolTableWasmPath,
);
verifyExpectedOutput(
"opfs_save_random_tool_table",
savedRandomToolTable.result,
[
"tooldata_save=0",
"tool_pocket_7.toolno=2",
"tool_pocket_9.toolno=5",
].join("\n"),
);
const savedRandomMachineFiles = await loadMachineTextFiles(
browserRandomMachineId,
);
verifyExpectedOutput(
"opfs_saved_random_tool_table_text",
savedRandomMachineFiles.toolTable,
[
"T2",
"P7",
";browser random finish tool",
"T5",
"P9",
";browser random rough tool",
].join("\n"),
);
const iniFileMachineId = "browser-ini-file-session";
const iniFileParameterFilename = "browser-custom.var";
const iniFileToolTableFilename = "browser-custom-tool.tbl";
const iniFileIniWasmPath = "/work/browser-ini-file-session.ini";
const browserIniFileTextPaths = await saveMachineTextFiles(iniFileMachineId, {
ini: [
"[EMC]",
`MACHINE = ${iniFileMachineId}`,
"",
"[RS274NGC]",
`PARAMETER_FILE = ${iniFileParameterFilename}`,
"",
"[EMCIO]",
`TOOL_TABLE = ${iniFileToolTableFilename}`,
"",
].join("\n"),
});
assertMachineTextStorePaths(
"opfs_save_ini_named_machine_text_files_staging",
browserIniFileTextPaths,
iniFileMachineId,
);
await writeOpfsMachineFile(
iniFileMachineId,
iniFileParameterFilename,
[
"5161 31.25",
"5162 62.5",
"5220 1",
"5221 4.5",
"5399 99",
"",
].join("\n"),
);
await writeOpfsMachineFile(
iniFileMachineId,
iniFileToolTableFilename,
"T8 P8 Z4.5 D0.5 ;browser custom tool\n",
);
const loadedIniFileSession = await loadMachineSessionFromOpfs(
interp,
iniFileMachineId,
{
iniSdk: ini,
iniWasmPath: iniFileIniWasmPath,
parameterWasmPath: opfsCustomSessionWasmPath(iniFileParameterFilename),
toolTableWasmPath: opfsCustomSessionWasmPath(iniFileToolTableFilename),
},
);
assertOpfsSessionStaging(
"opfs_load_ini_named_session_staging",
loadedIniFileSession,
iniFileMachineId,
opfsCustomSessionPaths(
iniFileMachineId,
iniFileParameterFilename,
iniFileToolTableFilename,
iniFileIniWasmPath,
),
);
verifyExpectedOutput(
"opfs_load_ini_named_parameter_file",
loadedIniFileSession.parameters.result,
[
"restore_parameters=0",
"parameter_5161=31.25",
"parameter_5162=62.5",
"parameter_5221=4.5",
"parameter_5399=99",
].join("\n"),
);
const savedIniNamedParameters = await saveMachineParametersToOpfs(
interp,
iniFileMachineId,
{
5161: 33.75,
5162: 67.5,
5220: 1.0,
5221: 5.25,
5399: 199,
},
{
opfsPath: loadedIniFileSession.parameters.opfsPath,
wasmPath: loadedIniFileSession.parameters.wasmPath,
},
);
assertOpfsSavedParameterStaging(
"opfs_save_ini_named_parameters_staging",
savedIniNamedParameters,
iniFileMachineId,
iniFileParameterFilename,
opfsCustomSessionWasmPath(iniFileParameterFilename),
);
verifyExpectedOutput(
"opfs_save_ini_named_parameter_file",
savedIniNamedParameters.result,
[
"save_parameters=0",
"parameter_5161=33.75",
"parameter_5162=67.5",
"parameter_5221=5.25",
"parameter_5399=199",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_ini_named_parameter_text",
loadedIniFileSession.parameters.opfsPath,
[
"5161\t33.750000",
"5162\t67.500000",
"5221\t5.250000",
"5399\t199.000000",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_ini_named_parameter_backup_text",
savedIniNamedParameters.backupOpfsPath,
[
"5161 31.25",
"5162 62.5",
"5221 4.5",
"5399 99",
].join("\n"),
);
verifyExpectedOutput(
"opfs_load_ini_named_tool_table",
loadedIniFileSession.toolTable.result,
[
"tooldata_load=0",
"tool_1.toolno=8",
"tool_1.pocketno=8",
"tool_1.z=4.5",
"tool_1.diameter=0.5",
"tool_1.comment=browser custom tool",
].join("\n"),
);
const savedIniNamedToolTable = await saveMachineToolTableToOpfs(
interp,
iniFileMachineId,
{
opfsPath: loadedIniFileSession.toolTable.opfsPath,
wasmPath: loadedIniFileSession.toolTable.wasmPath,
},
);
assertOpfsSavedToolTableStaging(
"opfs_save_ini_named_tool_table_staging",
savedIniNamedToolTable,
iniFileMachineId,
iniFileToolTableFilename,
opfsCustomSessionWasmPath(iniFileToolTableFilename),
);
verifyExpectedOutput(
"opfs_save_ini_named_tool_table",
savedIniNamedToolTable.result,
[
"tooldata_save=0",
"tool_1.toolno=8",
"tool_1.pocketno=8",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_ini_named_tool_table_text",
loadedIniFileSession.toolTable.opfsPath,
[
"T8",
"P8",
"Z+4.500000",
"D+0.500000",
";browser custom tool",
].join("\n"),
);
const iniOverrideMachineId = "browser-ini-override-session";
const explicitParameterFilename = "browser-explicit.var";
const explicitToolTableFilename = "browser-explicit-tool.tbl";
const iniOverrideIniWasmPath = "/work/browser-ini-override-session.ini";
const browserIniOverrideTextPaths = await saveMachineTextFiles(iniOverrideMachineId, {
ini: [
"[EMC]",
`MACHINE = ${iniOverrideMachineId}`,
"",
"[RS274NGC]",
"PARAMETER_FILE = browser-ignored.var",
"",
"[EMCIO]",
"TOOL_TABLE = browser-ignored-tool.tbl",
"",
].join("\n"),
});
assertMachineTextStorePaths(
"opfs_save_ini_override_machine_text_files_staging",
browserIniOverrideTextPaths,
iniOverrideMachineId,
);
await writeOpfsMachineFile(
iniOverrideMachineId,
explicitParameterFilename,
[
"5161 41.25",
"5162 82.5",
"5220 1",
"5221 6.5",
"5399 101",
"",
].join("\n"),
);
await writeOpfsMachineFile(
iniOverrideMachineId,
explicitToolTableFilename,
"T9 P9 Z5.5 D0.625 ;browser explicit tool\n",
);
const loadedIniOverrideSession = await loadMachineSessionFromOpfs(
interp,
iniOverrideMachineId,
{
iniSdk: ini,
iniWasmPath: iniOverrideIniWasmPath,
parameterFilename: explicitParameterFilename,
parameterWasmPath: opfsCustomSessionWasmPath(explicitParameterFilename),
toolTableFilename: explicitToolTableFilename,
toolTableWasmPath: opfsCustomSessionWasmPath(explicitToolTableFilename),
},
);
assertOpfsSessionStaging(
"opfs_load_explicit_session_staging",
loadedIniOverrideSession,
iniOverrideMachineId,
opfsCustomSessionPaths(
iniOverrideMachineId,
explicitParameterFilename,
explicitToolTableFilename,
iniOverrideIniWasmPath,
),
);
verifyExpectedOutput(
"opfs_load_explicit_parameter_file",
loadedIniOverrideSession.parameters.result,
[
"restore_parameters=0",
"parameter_5161=41.25",
"parameter_5162=82.5",
"parameter_5221=6.5",
"parameter_5399=101",
].join("\n"),
);
verifyExpectedOutput(
"opfs_load_explicit_tool_table",
loadedIniOverrideSession.toolTable.result,
[
"tooldata_load=0",
"tool_1.toolno=9",
"tool_1.pocketno=9",
"tool_1.z=5.5",
"tool_1.diameter=0.625",
"tool_1.comment=browser explicit tool",
].join("\n"),
);
const savedExplicitParameters = await saveMachineParametersToOpfs(
interp,
iniOverrideMachineId,
{
5161: 51.25,
5162: 102.5,
5220: 1.0,
5221: 7.5,
5399: 202,
},
{
opfsPath: loadedIniOverrideSession.parameters.opfsPath,
wasmPath: loadedIniOverrideSession.parameters.wasmPath,
},
);
assertOpfsSavedParameterStaging(
"opfs_save_explicit_parameters_staging",
savedExplicitParameters,
iniOverrideMachineId,
explicitParameterFilename,
opfsCustomSessionWasmPath(explicitParameterFilename),
);
verifyExpectedOutput(
"opfs_save_explicit_parameter_file",
savedExplicitParameters.result,
[
"save_parameters=0",
"parameter_5161=51.25",
"parameter_5162=102.5",
"parameter_5221=7.5",
"parameter_5399=202",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_explicit_parameter_text",
loadedIniOverrideSession.parameters.opfsPath,
[
"5161\t51.250000",
"5162\t102.500000",
"5221\t7.500000",
"5399\t202.000000",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_explicit_parameter_backup_text",
savedExplicitParameters.backupOpfsPath,
[
"5161 41.25",
"5162 82.5",
"5221 6.5",
"5399 101",
].join("\n"),
);
const savedExplicitToolTable = await saveMachineToolTableToOpfs(
interp,
iniOverrideMachineId,
{
opfsPath: loadedIniOverrideSession.toolTable.opfsPath,
wasmPath: loadedIniOverrideSession.toolTable.wasmPath,
},
);
assertOpfsSavedToolTableStaging(
"opfs_save_explicit_tool_table_staging",
savedExplicitToolTable,
iniOverrideMachineId,
explicitToolTableFilename,
opfsCustomSessionWasmPath(explicitToolTableFilename),
);
verifyExpectedOutput(
"opfs_save_explicit_tool_table",
savedExplicitToolTable.result,
[
"tooldata_save=0",
"tool_1.toolno=9",
"tool_1.pocketno=9",
].join("\n"),
);
await verifyOpfsReadback(
"opfs_saved_explicit_tool_table_text",
loadedIniOverrideSession.toolTable.opfsPath,
[
"T9",
"P9",
"Z+5.500000",
"D+0.625000",
";browser explicit tool",
].join("\n"),
);
await verifyOpfsInvalidIniSessionRejects(
"opfs_reject_invalid_ini_parameter_file",
interp,
ini,
"browser-invalid-param-file-session",
[
"[EMC]",
"MACHINE = browser-invalid-param-file-session",
"",
"[RS274NGC]",
"PARAMETER_FILE = ../escape.var",
"",
],
/Invalid parameter filename/,
);
await verifyOpfsInvalidIniSessionRejects(
"opfs_reject_invalid_ini_tool_table",
interp,
ini,
"browser-invalid-tool-file-session",
[
"[EMC]",
"MACHINE = browser-invalid-tool-file-session",
"",
"[EMCIO]",
"TOOL_TABLE = nested/tool.tbl",
"",
],
/Invalid tool table filename/,
);
status.textContent = "browser_interp_smoke=ok";
} catch (error) {
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
}
</script>
</body>
</html>