继续完成 web-rtcp-5axis-sim-plan

结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
2026-06-21 16:44:29 +08:00
parent a6eda3fbff
commit 626bcfe8e3
101 changed files with 101586 additions and 770 deletions

View File

@@ -0,0 +1,230 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import {
createLinuxCncKinematicsSdk,
linuxCncKinematicsWasmFile,
supportedLinuxCncKinematicsModules,
} from "../../../runtime/sdk/src/index.js";
const EXPECTED_MODULES = [
"trivkins",
"5axiskins",
"xyzac-trt",
"xyzbc-trt",
"corexy",
"rotate",
"rose",
"max",
"lineardelta",
"rotarydelta",
"scorbot",
"tripod",
"scara",
"puma",
"genser",
"genhex",
"pentakins",
];
const EXPECTED_META = {
trivkins: { type: 1, switchable: 0 },
"5axiskins": { type: 4, switchable: 1 },
"xyzac-trt": { type: 4, switchable: 1 },
"xyzbc-trt": { type: 4, switchable: 1 },
corexy: { type: 4, switchable: 0 },
rotate: { type: 4, switchable: 0 },
rose: { type: 4, switchable: 0 },
max: { type: 4, switchable: 0 },
lineardelta: { type: 4, switchable: 0 },
rotarydelta: { type: 4, switchable: 0 },
scorbot: { type: 4, switchable: 0 },
tripod: { type: 4, switchable: 0 },
scara: { type: 4, switchable: 1 },
puma: { type: 4, switchable: 1 },
genser: { type: 4, switchable: 1 },
genhex: { type: 4, switchable: 1 },
pentakins: { type: 4, switchable: 0 },
};
function near(actual, expected, tolerance = 1e-7) {
return Math.abs(actual - expected) < tolerance;
}
function assertNear(actual, expected, label, tolerance = 1e-7) {
assert.equal(near(actual, expected, tolerance), true, `${label}: ${actual} != ${expected}`);
}
function assertArrayNear(actual, expected, label, tolerance = 1e-7) {
assert.equal(actual.length >= expected.length, true, `${label}: missing values`);
expected.forEach((value, index) => assertNear(actual[index], value, `${label}[${index}]`, tolerance));
}
function assertPoseNear(actual, expected, label, tolerance = 1e-7) {
for (const key of ["x", "y", "z", "a", "b", "c", "u", "v", "w"]) {
assertNear(actual[key] ?? 0, expected[key] ?? 0, `${label}.${key}`, tolerance);
}
}
function assertProbeBasics(moduleId, probe) {
assert.equal(probe.includes("kinematics_init=0"), true, `${moduleId}: init probe`);
assert.equal(
probe.includes(`kinematics_type=${EXPECTED_META[moduleId].type}`),
true,
`${moduleId}: type probe`,
);
assert.equal(
probe.includes(`kinematics_switchable=${EXPECTED_META[moduleId].switchable}`),
true,
`${moduleId}: switchable probe`,
);
}
function assertSwitchIdentity(kins, moduleId, joints, expectedPose) {
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch identity`);
const identity = kins.forward(joints);
assert.equal(identity.rc, 0, `${moduleId}: identity forward`);
assertPoseNear(identity.pose, expectedPose, `${moduleId}: identity pose`);
}
function assertJointRoundtrip(kins, moduleId, joints, jointCount = joints.length, tolerance = 1e-7) {
if (kins.switchable()) {
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
}
const forward = kins.forward(joints);
assert.equal(forward.rc, 0, `${moduleId}: forward`);
const inverse = kins.inverse(forward.pose, jointCount, { seedJoints: joints });
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
assertArrayNear(inverse.joints, joints.slice(0, jointCount), `${moduleId}: joints`, tolerance);
}
function assertPoseRoundtrip(kins, moduleId, pose, jointCount, options = {}) {
if (kins.switchable()) {
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
}
const inverse = kins.inverse(pose, jointCount, options.inverseOptions ?? {});
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
if (options.warmupForward) {
kins.forward(inverse.joints, { seedPose: pose });
}
const forward = kins.forward(inverse.joints, { seedPose: pose });
assert.equal(forward.rc, 0, `${moduleId}: forward`);
assertPoseNear(forward.pose, pose, `${moduleId}: pose`, options.tolerance ?? 1e-7);
return { inverse, forward };
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
assert.deepEqual(supportedLinuxCncKinematicsModules(), EXPECTED_MODULES);
for (const moduleId of supportedLinuxCncKinematicsModules()) {
const wasmFile = linuxCncKinematicsWasmFile(moduleId);
const wasmPath = resolve(rootDir, "build/wasm/kinematics", wasmFile);
const kins = await createLinuxCncKinematicsSdk({
moduleId,
moduleOptions: {
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(`[${moduleId}] ${message}`);
},
},
});
assert.equal(kins.apiName, "linuxcnc-kinematics-wasm-sdk");
assert.equal(kins.wasmFile, wasmFile);
for (const functionName of ["lckins_forward", "lckins_inverse", "lckins_run_probe"]) {
assert.equal(kins.hasWasmFunction(functionName), true, `${moduleId}: ${functionName}`);
}
assert.equal(kins.type(), EXPECTED_META[moduleId].type, `${moduleId}: type`);
assert.equal(kins.switchable(), EXPECTED_META[moduleId].switchable, `${moduleId}: switchable`);
assertProbeBasics(moduleId, kins.runProbe());
switch (moduleId) {
case "trivkins":
assertJointRoundtrip(kins, moduleId, [1, 2, 3, 4, 5, 6, 7, 8, 9], 9);
break;
case "5axiskins":
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 45, 30, 5], 6);
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 45, 30, 5], { x: 10, y: 20, z: 30, b: 45, c: 30, w: 5 });
break;
case "xyzac-trt":
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 25, 40], 5);
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 25, 40], { x: 10, y: 20, z: 30, a: 25, c: 40 });
break;
case "xyzbc-trt":
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 35, 40], 5);
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 35, 40], { x: 10, y: 20, z: 30, b: 35, c: 40 });
break;
case "corexy":
assertJointRoundtrip(kins, moduleId, [12, 4, 3, 4, 5, 6, 7, 8, 9], 9);
break;
case "rotate":
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 5, 30, 7, 8, 9], 9);
break;
case "rose":
assertJointRoundtrip(kins, moduleId, [12, 5, 30], 3);
break;
case "max":
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 0, 25, 0, 0, 3.5], 9);
break;
case "lineardelta":
assertPoseRoundtrip(kins, moduleId, { x: 10, y: 20, z: -30, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
break;
case "rotarydelta":
assertPoseRoundtrip(kins, moduleId, { x: 0, y: 0, z: -12, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
break;
case "scorbot": {
const forward = kins.forward([25, 20, 10, 4, 5]);
assert.equal(forward.rc, 0, `${moduleId}: forward`);
const inverse = kins.inverse(forward.pose, 5);
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
const roundtrip = kins.forward(inverse.joints);
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
break;
}
case "tripod":
assertPoseRoundtrip(kins, moduleId, { x: 0.25, y: 0.25, z: 0.5 }, 3);
break;
case "scara":
assertJointRoundtrip(kins, moduleId, [20, 110, 30, 15, 4, 5], 6);
assertSwitchIdentity(kins, moduleId, [20, 110, 30, 15, 4, 5], { x: 20, y: 110, z: 30, a: 15, b: 4, c: 5 });
break;
case "puma": {
if (kins.switchable()) {
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
}
const forward = kins.forward([20, -30, 40, 15, 35, -25]);
assert.equal(forward.rc, 0, `${moduleId}: forward`);
const inverse = kins.inverse(forward.pose, 6);
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
const roundtrip = kins.forward(inverse.joints);
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
assertSwitchIdentity(kins, moduleId, [20, -30, 40, 15, 35, -25], { x: 20, y: -30, z: 40, a: 15, b: 35, c: -25 });
break;
}
case "genser":
assertJointRoundtrip(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], 9);
assertSwitchIdentity(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], { x: 10, y: -15, z: 20, a: 5, b: -10, c: 12, u: 7, v: 8, w: 9 });
break;
case "genhex":
assertPoseRoundtrip(kins, moduleId, { x: 1.25, y: -2.5, z: 3.75, a: 1, b: -2, c: 3 }, 6, { warmupForward: true });
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch alternate`);
break;
case "pentakins": {
const { inverse } = assertPoseRoundtrip(kins, moduleId, { x: 12.5, y: -8.25, z: 25, a: 2, b: -1.5 }, 5, { tolerance: 1e-6 });
assert.equal(inverse.joints.every((value) => value > 0), true, `${moduleId}: positive struts`);
break;
}
default:
throw new Error(`missing kinematics verification for ${moduleId}`);
}
}
console.log("kinematics_wasm_node_smoke=ok");

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
if [[ "${SKIP_KINEMATICS_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_kinematics_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_kinematics_wasm.mjs"

View File

@@ -108,6 +108,7 @@ const nativeRuntimeProbePassEvidenceContractPath = resolve(buildDir, "native-run
const promotionCandidatesPath = resolve(buildDir, "promotion-candidates.tsv");
const evidenceExpansionCandidatesPath = resolve(buildDir, "evidence-expansion-candidates.tsv");
const remainingSkipMainProgramPromotionAuditPath = resolve(buildDir, "remaining-skip-main-program-promotion-audit.tsv");
const remainingSkipSimulationImplementationCoveragePath = resolve(buildDir, "remaining-skip-simulation-implementation-coverage.tsv");
const blockedRuntimePromotionLockPath = resolve(buildDir, "blocked-runtime-promotion-lock.tsv");
const runtimeBoundaryPromotionReadinessPath = resolve(buildDir, "runtime-boundary-promotion-readiness.tsv");
const runtimeBoundaryPromotionBlockersPath = resolve(buildDir, "runtime-boundary-promotion-blockers.tsv");
@@ -168,6 +169,7 @@ const generatedSimConfigInventoryArtifactPaths = [
nativeRuntimeProbeExecutionPlanPath,
nativeRuntimeProbePassEvidenceContractPath,
promotionCandidatesPath,
remainingSkipSimulationImplementationCoveragePath,
remainingSkipMainProgramPromotionAuditPath,
blockedRuntimePromotionLockPath,
runtimeBoundaryPromotionReadinessPath,
@@ -6925,7 +6927,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) {
const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row]));
assert.equal(
rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count,
"60",
"61",
"WASM artifact documentation completion count must match generated artifact baseline",
);
assert.equal(
@@ -9678,7 +9680,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
);
assert.equal(
artifactNames.length,
60,
61,
"generated sim-config inventory artifact list count drift",
);
assert.deepEqual(
@@ -9712,6 +9714,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
"python-remap-runtime-gates.tsv",
"python-remap-wasm-node-row-proof.tsv",
"remaining-skip-main-program-promotion-audit.tsv",
"remaining-skip-simulation-implementation-coverage.tsv",
"runtime-boundary-contract-summary.tsv",
"runtime-boundary-family-host-readiness.tsv",
"runtime-boundary-host-preflight.tsv",
@@ -9778,7 +9781,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
"generated sim-config inventory artifacts must be listed in browser smoke",
);
assert.ok(
browserSmokeText.includes("wasmArtifactNames.length !== 60"),
browserSmokeText.includes("wasmArtifactNames.length !== 61"),
"browser smoke must keep generated WASM artifact fixed-count guard",
);
assert.ok(
@@ -13673,6 +13676,223 @@ function verifyRemainingSkipMainProgramPromotionAuditRows({
return parsedRows;
}
function simulationImplementationMode(row) {
if (row.reason === "ASSET-ONLY") {
return "source_asset_dependency";
}
if (row.reason === "NON_MAIN_CLASS") {
return "macro_load_or_non_main_class_coverage";
}
if (row.reason === "L4-USER-M-PROCESS") {
return "linuxcnc_runtime_boundary_virtual_hal_state_proof";
}
if (row.reason === "UPSTREAM-DEMO") {
return "upstream_demo_preserved_invalid_motion_source";
}
return "unclassified_skip";
}
function simulationImplementationStatus(row) {
if (row.reason === "ASSET-ONLY") {
return "implemented_as_linuxcnc_source_asset_dependency_not_standalone_main";
}
if (row.reason === "NON_MAIN_CLASS") {
return "implemented_as_linuxcnc_macro_load_or_non_main_class_not_standalone_main";
}
if (row.reason === "L4-USER-M-PROCESS") {
return "implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked";
}
if (row.reason === "UPSTREAM-DEMO") {
return "implemented_as_preserved_upstream_demo_edge_invalid_motion_not_forced_pass";
}
return "skip_kind_requires_review";
}
function simulationImplementationNextStep(row) {
if (row.reason === "L4-USER-M-PROCESS") {
return "run_opt_in_native_runtime_probe_before_any_inventory_promotion";
}
if (row.reason === "UPSTREAM-DEMO") {
return "wait_for_upstream_source_fix_then_regenerate_inventory";
}
return "use_as_dependency_or_class_coverage_no_inventory_promotion";
}
function remainingSkipSimulationImplementationCoverageRows({
summaryRows,
boundaryRows,
remainingSkipAuditRows,
}) {
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
return summaryRows
.map((row) => {
const [
path,
inventoryStatus,
reason,
className,
nativeStatus,
nativeExpectedFailure,
] = row.split("\t");
return {
path,
inventoryStatus,
reason,
className,
nativeStatus,
nativeExpectedFailure,
};
})
.filter((row) => row.inventoryStatus === "SKIP")
.sort((left, right) => left.path.localeCompare(right.path))
.map((row) => {
const boundary = boundaryByPath.get(row.path);
const audit = auditByPath.get(row.path);
const sourceRel = `configs/sim/${row.path}`;
const sourceAvailable = sourceConfigPathExists(row.path);
const implementationMode = simulationImplementationMode(row);
const standaloneMainReady = row.className === "main" &&
row.reason === "-" &&
row.nativeStatus === "PASS";
const runtimePromotionBlocked = ["L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.reason);
return [
row.path,
boundary?.ini ?? audit?.ini ?? "-",
row.reason,
row.className,
row.nativeStatus,
row.nativeExpectedFailure,
sourceRel,
flagValue(sourceAvailable),
implementationMode,
simulationImplementationStatus(row),
flagValue(row.className === "main"),
flagValue(standaloneMainReady),
boundary?.dependencies ?? audit?.dependency_class ?? "-",
boundary?.recommended_blocked ?? audit?.blocked_kind ?? row.reason,
audit?.promotion_ready ?? "0",
audit?.promotion_allowed ?? "0",
flagValue(runtimePromotionBlocked),
"1",
simulationImplementationNextStep(row),
].map(tsvValue).join("\t");
});
}
function verifyRemainingSkipSimulationImplementationCoverageRows({
rows,
summaryRows,
remainingSkipAuditRows,
}) {
const expectedHeaders = [
"path",
"ini",
"skip_kind",
"class",
"native_status",
"native_expected_failure",
"linuxcnc_source_path",
"linuxcnc_source_available",
"simulation_implementation_mode",
"simulation_implementation_status",
"main_program_class",
"standalone_main_program_ready",
"dependency_class",
"blocked_kind",
"promotion_ready",
"promotion_allowed",
"runtime_promotion_blocked",
"simulation_coverage_ready",
"recommended_next_step",
];
const parsedRows = parseTsv(
`${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`,
expectedHeaders,
);
const skippedSummaryRows = summaryRows
.map((row) => {
const [path, inventoryStatus, reason, className] = row.split("\t");
return { path, inventoryStatus, reason, className };
})
.filter((row) => row.inventoryStatus === "SKIP");
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
assert.equal(parsedRows.length, 77, "remaining skip simulation implementation coverage count drift");
assert.deepEqual(
parsedRows.map((row) => row.path).sort(),
skippedSummaryRows.map((row) => row.path).sort(),
"remaining skip simulation implementation coverage must cover every skipped row",
);
assert.deepEqual(
Object.fromEntries(
[...parsedRows.reduce((counts, row) => {
counts.set(row.skip_kind, (counts.get(row.skip_kind) ?? 0) + 1);
return counts;
}, new Map()).entries()].sort(),
),
{
"ASSET-ONLY": 65,
"L4-USER-M-PROCESS": 1,
"NON_MAIN_CLASS": 10,
"UPSTREAM-DEMO": 1,
},
"remaining skip simulation implementation coverage kind counts drift",
);
for (const row of parsedRows) {
assert.equal(row.linuxcnc_source_available, "1", `${row.path}: LinuxCNC source path missing`);
assert.equal(row.simulation_coverage_ready, "1", `${row.path}: skipped row lacks simulation coverage`);
assert.equal(row.standalone_main_program_ready, "0", `${row.path}: skipped row must not claim standalone main readiness`);
assert.equal(row.promotion_allowed, "0", `${row.path}: skipped row must not allow promotion`);
assert.notEqual(row.simulation_implementation_mode, "unclassified_skip", `${row.path}: unclassified skip implementation mode`);
assert.notEqual(row.recommended_next_step, "-", `${row.path}: skipped row lacks next implementation step`);
if (row.skip_kind === "ASSET-ONLY") {
assert.equal(row.class, "remap_subroutine", `${row.path}: asset-only row should remain remap subroutine coverage`);
assert.equal(
row.simulation_implementation_mode,
"source_asset_dependency",
`${row.path}: asset-only implementation mode drift`,
);
} else if (row.skip_kind === "NON_MAIN_CLASS") {
assert.equal(row.class, "macro_load", `${row.path}: non-main row should remain macro/load coverage`);
assert.equal(
row.simulation_implementation_mode,
"macro_load_or_non_main_class_coverage",
`${row.path}: non-main implementation mode drift`,
);
} else if (row.skip_kind === "L4-USER-M-PROCESS") {
const audit = auditByPath.get(row.path);
assert.ok(audit, `${row.path}: user-M skipped main row must have promotion audit`);
assert.equal(row.path, "axis/vismach/millturn/example.ngc", `${row.path}: user-M path drift`);
assert.equal(row.main_program_class, "1", `${row.path}: user-M row must remain main class`);
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: user-M runtime promotion block drift`);
assert.equal(
row.simulation_implementation_status,
"implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked",
`${row.path}: user-M implementation status drift`,
);
} else if (row.skip_kind === "UPSTREAM-DEMO") {
const audit = auditByPath.get(row.path);
assert.ok(audit, `${row.path}: upstream demo skipped main row must have promotion audit`);
assert.equal(
row.path,
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc",
`${row.path}: upstream demo path drift`,
);
assert.equal(row.native_expected_failure, "upstream-demo-missing-motion-gcode", `${row.path}: upstream demo failure drift`);
assert.equal(row.main_program_class, "1", `${row.path}: upstream demo row must remain main class`);
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: upstream demo runtime promotion block drift`);
} else {
assert.fail(`${row.path}: unexpected skip kind ${row.skip_kind}`);
}
}
return parsedRows;
}
for (const record of nativeRecords) {
const skipReason = blockedKind(record, pathMatrixByPath);
if (skipReason === "L4-PYTHON-REMAP" && promotedPythonRemapInventoryPaths.has(record.path)) {
@@ -14191,6 +14411,40 @@ verifyRemainingSkipMainProgramPromotionAuditRows({
summaryRows,
promotionCandidateRows: promotionCandidateRecords,
});
const remainingSkipMainProgramPromotionAuditRecords = parseTsv(
`${[
"path",
"ini",
"current_status",
"skip_kind",
"class",
"native_status",
"native_expected_failure",
"blocked_kind",
"dependency_class",
"simulation_proof_status",
"native_pass_ready",
"node_inventory_gate_complete",
"browser_smoke_gate_complete",
"promotion_lock_active",
"promotion_ready",
"promotion_allowed",
"promotion_decision",
"block_reason",
"recommended_next_command",
].join("\t")}\n${remainingSkipMainProgramPromotionAuditRowsGenerated.join("\n")}\n`,
);
const remainingSkipSimulationImplementationCoverageRowsGenerated =
remainingSkipSimulationImplementationCoverageRows({
summaryRows,
boundaryRows: boundarySummaryRecords,
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
});
verifyRemainingSkipSimulationImplementationCoverageRows({
rows: remainingSkipSimulationImplementationCoverageRowsGenerated,
summaryRows,
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
});
const evidenceExpansionCandidateRowsGenerated = evidenceExpansionCandidateRows({
summaryRows,
boundaryRows: boundarySummaryRecords,
@@ -14280,6 +14534,34 @@ writeFileSync(
].join("\n")}\n`,
);
writeFileSync(
remainingSkipSimulationImplementationCoveragePath,
`${[
[
"path",
"ini",
"skip_kind",
"class",
"native_status",
"native_expected_failure",
"linuxcnc_source_path",
"linuxcnc_source_available",
"simulation_implementation_mode",
"simulation_implementation_status",
"main_program_class",
"standalone_main_program_ready",
"dependency_class",
"blocked_kind",
"promotion_ready",
"promotion_allowed",
"runtime_promotion_blocked",
"simulation_coverage_ready",
"recommended_next_step",
].join("\t"),
...remainingSkipSimulationImplementationCoverageRowsGenerated,
].join("\n")}\n`,
);
writeFileSync(
boundarySummaryPath,
`${[