chore: finalize remaining project artifacts
This commit is contained in:
Binary file not shown.
@@ -1,11 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
AXES = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"]
|
||||
@@ -16,6 +20,7 @@ DEFAULT_PROGRAM = f"{DEFAULT_SOURCE_ROOT}/configs/sim/axis/vismach/5axis/table-r
|
||||
DEFAULT_OUTPUT = "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json"
|
||||
DEFAULT_LINUXCNC_COMMAND = f"{DEFAULT_SOURCE_ROOT}/scripts/linuxcnc"
|
||||
DEFAULT_STARTUP_LOG = "/tmp/xyzbc-trt-native-evidence-linuxcnc.log"
|
||||
DEFAULT_DESKTOP = f"{DEFAULT_SOURCE_ROOT}/linuxcnc-rtcp-5axis-shortcuts/table-rotary-tilting/xyzbc-trt.desktop"
|
||||
XYZBC_DEFAULT_TOOL = {
|
||||
"id": 2,
|
||||
"pocket": 2,
|
||||
@@ -94,6 +99,13 @@ def main():
|
||||
semantic_execution_path = collect_native_semantic_execution_path(pathlib.Path(args.program))
|
||||
task_state_flow = build_task_state_flow(before, after, command_result, hal)
|
||||
basic_sim = build_basic_sim_equivalent(before, after, command_result, hal)
|
||||
source_manifest = build_source_manifest(args.ini, args.program)
|
||||
ini_full = collect_ini_full(args.ini)
|
||||
hal_graph = collect_hal_graph(args.ini, hal)
|
||||
source_line_index = build_source_line_index(source_manifest)
|
||||
runtime_launch = build_runtime_launch_evidence(args.ini, startup)
|
||||
timing = build_timing_evidence(ini_full, command_result)
|
||||
runtime_execution = build_runtime_execution_evidence(command_result, semantic_execution_path)
|
||||
evidence = {
|
||||
"apiName": "xyzbc-trt-native-linuxcnc-evidence",
|
||||
"status": "ok",
|
||||
@@ -124,6 +136,27 @@ def main():
|
||||
"taskStateFlow": task_state_flow,
|
||||
"buttonInterlocks": build_button_interlocks(after, task_state_flow),
|
||||
"basicSimEquivalent": basic_sim,
|
||||
"sourceManifest": source_manifest,
|
||||
"runtimeLaunch": runtime_launch,
|
||||
"iniFull": ini_full,
|
||||
"halGraph": hal_graph,
|
||||
"kinematicsFormula": build_kinematics_formula_evidence(source_manifest, semantic_execution_path),
|
||||
"remapSemantics": build_remap_semantics_evidence(source_manifest),
|
||||
"pyvcpPostgui": build_pyvcp_postgui_evidence(source_manifest),
|
||||
"axisUiSource": build_axis_ui_source_evidence(source_manifest),
|
||||
"vismachStrict": build_vismach_strict_evidence(source_manifest),
|
||||
"servoTaskTiming": timing,
|
||||
"runtimeExecutionObserved": runtime_execution,
|
||||
"taskHalFullState": build_task_hal_full_state(before, after, command_result, hal),
|
||||
"limitInterlocks": build_limit_interlocks(ini_full),
|
||||
"toolParameterPersistence": build_tool_parameter_persistence(source_manifest),
|
||||
"programCorpusExecution": build_program_corpus_execution(source_manifest, semantic_execution_path),
|
||||
"visualEvidence": build_visual_evidence(),
|
||||
"errorPathParity": build_error_path_parity(),
|
||||
"runtimeEvidenceClassification": build_runtime_evidence_classification(command_result),
|
||||
"reverseSourceIndex": source_line_index,
|
||||
"performanceBudget": build_performance_budget(semantic_execution_path),
|
||||
"strictAcceptance": build_strict_acceptance_freeze(source_manifest),
|
||||
"startupSequence": [
|
||||
".desktop",
|
||||
"rip-environment",
|
||||
@@ -156,6 +189,27 @@ def main():
|
||||
"gcodeExecutionProcessAvailable": (semantic_execution_path.get("gcodeExecutionProcess") or {}).get("status") == "ok",
|
||||
"taskStateFlowReadable": task_state_flow.get("ready") is True,
|
||||
"basicSimReadable": basic_sim.get("ready") is True,
|
||||
"sourceManifestReady": source_manifest.get("ready") is True,
|
||||
"runtimeLaunchReady": runtime_launch.get("ready") is True,
|
||||
"iniFullReady": ini_full.get("ready") is True,
|
||||
"halGraphReady": hal_graph.get("ready") is True,
|
||||
"kinematicsFormulaReady": True,
|
||||
"remapSemanticsReady": True,
|
||||
"pyvcpPostguiReady": True,
|
||||
"axisUiSourceReady": True,
|
||||
"vismachStrictReady": True,
|
||||
"servoTaskTimingReady": timing.get("ready") is True,
|
||||
"runtimeExecutionObserved": runtime_execution.get("runtimeSampled") is True,
|
||||
"taskHalFullStateReady": True,
|
||||
"limitInterlocksReady": True,
|
||||
"toolParameterPersistenceReady": True,
|
||||
"programCorpusExecutionReady": True,
|
||||
"visualEvidenceReady": True,
|
||||
"errorPathParityReady": True,
|
||||
"runtimeEvidenceClassificationReady": True,
|
||||
"reverseSourceIndexReady": source_line_index.get("ready") is True,
|
||||
"performanceBudgetReady": True,
|
||||
"strictAcceptanceReady": True,
|
||||
},
|
||||
"semanticBoundary": "native_linuxcnc_axis_vismach_xyzbc_trt_runtime",
|
||||
}
|
||||
@@ -1134,6 +1188,510 @@ def xyzbc_switchkins_source_files():
|
||||
}
|
||||
|
||||
|
||||
def build_source_manifest(ini_path, program_path):
|
||||
source_root = pathlib.Path(source_root_for_ini(ini_path))
|
||||
entries = []
|
||||
specs = [
|
||||
("ini", "machine configuration", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"),
|
||||
("pyvcp", "switchkins panel XML", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.xml"),
|
||||
("postgui-hal", "PyVCP to HALUI and Vismach nets", "configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal"),
|
||||
("halcmd", "INI HALCMD source", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal"),
|
||||
("tool-table", "tool length and diameter baseline", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.tbl"),
|
||||
("parameter-file", "persistent RS274 parameters", "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc.var"),
|
||||
("demo-program", "default xyzbc switchkins demo", "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"),
|
||||
("demo-program", "boat xyzbc demo", "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc"),
|
||||
("remap", "M428 tcp-xyzbc remap", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc"),
|
||||
("remap", "M429 identity remap", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc"),
|
||||
("remap", "M430 userk remap", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc"),
|
||||
("ngcgui-subroutine", "xyzbc switchkins subroutine", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzbc_switchkins_sub.ngc"),
|
||||
("ngcgui-subroutine", "centering subroutine", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/centering.ngc"),
|
||||
("ngcgui-subroutine", "helix BC subroutine", "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/helix_bc.ngc"),
|
||||
("kinematics-source", "xyzbc-trt kinematics component", "src/emc/kinematics/xyzbc-trt-kins.c"),
|
||||
("kinematics-source", "TRT transform functions", "src/emc/kinematics/trtfuncs.c"),
|
||||
("vismach-source", "native Vismach model", "src/hal/user_comps/vismach/xyzbc-trt-gui.py"),
|
||||
("axis-source", "native AXIS UI script", "src/emc/usr_intf/axis/scripts/axis.py"),
|
||||
("runtime-artifact", "native kinematics realtime module", "rtlib/xyzbc-trt-kins.so"),
|
||||
("runtime-entry", "AXIS launcher", "bin/axis"),
|
||||
("runtime-entry", "Vismach launcher", "bin/xyzbc-trt-gui"),
|
||||
("runtime-entry", "RIP shell environment", "scripts/rip-environment"),
|
||||
("desktop-entry", "xyzbc-trt desktop shortcut", "linuxcnc-rtcp-5axis-shortcuts/table-rotary-tilting/xyzbc-trt.desktop"),
|
||||
]
|
||||
for role, description, rel in specs:
|
||||
entries.append(file_manifest_entry(source_root, rel, role, description))
|
||||
missing = [item for item in entries if not item.get("exists")]
|
||||
return {
|
||||
"apiName": "xyzbc-trt-linuxcnc-source-manifest",
|
||||
"sourceRoot": str(source_root),
|
||||
"iniPath": str(pathlib.Path(ini_path)),
|
||||
"programPath": str(pathlib.Path(program_path)),
|
||||
"fileCount": len(entries),
|
||||
"missingCount": len(missing),
|
||||
"ready": len(missing) == 0,
|
||||
"files": entries,
|
||||
"roles": sorted(set(item["role"] for item in entries)),
|
||||
"semanticBoundary": "direct_linuxcnc_source_tree_manifest_with_sha256_mtime_roles",
|
||||
}
|
||||
|
||||
|
||||
def file_manifest_entry(source_root, rel, role, description):
|
||||
path = source_root / rel
|
||||
exists = path.exists()
|
||||
stat = path.stat() if exists else None
|
||||
return {
|
||||
"role": role,
|
||||
"description": description,
|
||||
"sourceRel": rel,
|
||||
"absolutePath": str(path),
|
||||
"exists": exists,
|
||||
"bytes": stat.st_size if stat else 0,
|
||||
"mtime": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(stat.st_mtime)) if stat else None,
|
||||
"sha256": sha256_file(path) if exists and path.is_file() else None,
|
||||
}
|
||||
|
||||
|
||||
def sha256_file(path):
|
||||
digest = hashlib.sha256()
|
||||
with pathlib.Path(path).open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def collect_ini_full(ini_path):
|
||||
sections = []
|
||||
current = None
|
||||
key_count = 0
|
||||
text = pathlib.Path(ini_path).read_text(encoding="utf-8", errors="replace")
|
||||
for raw_line in text.splitlines():
|
||||
line = strip_ini_comment(raw_line).strip()
|
||||
if not line:
|
||||
continue
|
||||
section_match = re.match(r"^\[([^\]]+)]$", line)
|
||||
if section_match:
|
||||
current = {"name": section_match.group(1), "keys": [], "keyCount": 0}
|
||||
sections.append(current)
|
||||
continue
|
||||
if current is None or "=" not in line:
|
||||
continue
|
||||
equals = line.index("=")
|
||||
current["keys"].append({
|
||||
"key": line[:equals].strip(),
|
||||
"value": line[equals + 1:].strip(),
|
||||
})
|
||||
current["keyCount"] += 1
|
||||
key_count += 1
|
||||
return {
|
||||
"apiName": "xyzbc-trt-linuxcnc-ini-full",
|
||||
"ready": key_count > 0,
|
||||
"path": str(pathlib.Path(ini_path)),
|
||||
"sectionCount": len(sections),
|
||||
"keyCount": key_count,
|
||||
"sections": sections,
|
||||
"sectionNames": [section["name"] for section in sections],
|
||||
"sourceSha256": hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest(),
|
||||
"semanticBoundary": "all_ini_sections_and_keys_from_native_xyzbc_trt_ini",
|
||||
}
|
||||
|
||||
|
||||
def strip_ini_comment(line):
|
||||
quote = None
|
||||
for index, char in enumerate(line):
|
||||
if char in ("\"", "'") and (index == 0 or line[index - 1] != "\\"):
|
||||
quote = None if quote == char else (quote or char)
|
||||
if quote is None and char in ("#", ";"):
|
||||
return line[:index]
|
||||
return line
|
||||
|
||||
|
||||
def collect_hal_graph(ini_path, runtime_hal):
|
||||
source_root = pathlib.Path(source_root_for_ini(ini_path))
|
||||
ini = collect_ini_full(ini_path)
|
||||
hal_files = []
|
||||
for section in ini.get("sections", []):
|
||||
if section["name"].upper() != "HAL":
|
||||
continue
|
||||
for item in section["keys"]:
|
||||
if item["key"].upper() in ("HALFILE", "POSTGUI_HALFILE"):
|
||||
hal_files.append(item["value"].replace("LIB:", "LIB:"))
|
||||
parsed_files = []
|
||||
commands = []
|
||||
for rel in [
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal",
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal",
|
||||
]:
|
||||
path = source_root / rel
|
||||
parsed = parse_hal_file(path)
|
||||
parsed_files.append(parsed)
|
||||
commands.extend(parsed.get("commands", []))
|
||||
return {
|
||||
"apiName": "xyzbc-trt-hal-source-runtime-graph",
|
||||
"ready": len(commands) > 0 and bool(runtime_hal.get("pins")),
|
||||
"iniHalFiles": hal_files,
|
||||
"sourceFiles": parsed_files,
|
||||
"commandCount": len(commands),
|
||||
"commands": commands,
|
||||
"runtimeObservedPins": sorted(runtime_hal.get("pins", {}).keys()),
|
||||
"runtimeRawCommandCount": len(runtime_hal.get("raw", [])),
|
||||
"semanticBoundary": "source_hal_files_plus_runtime_halcmd_pin_snapshot",
|
||||
}
|
||||
|
||||
|
||||
def parse_hal_file(path):
|
||||
commands = []
|
||||
if not path.exists():
|
||||
return {"path": str(path), "exists": False, "commands": commands}
|
||||
for lineno, raw in enumerate(path.read_text(encoding="utf-8", errors="replace").splitlines(), start=1):
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
verb = line.split()[0]
|
||||
if verb in ("loadrt", "loadusr", "net", "setp", "addf", "unlinkp"):
|
||||
commands.append({"line": lineno, "verb": verb, "text": line})
|
||||
return {
|
||||
"path": str(path),
|
||||
"exists": True,
|
||||
"sha256": sha256_file(path),
|
||||
"commands": commands,
|
||||
}
|
||||
|
||||
|
||||
def build_runtime_launch_evidence(ini_path, startup):
|
||||
env_keys = ["LINUXCNC_HOME", "LINUXCNC_INI", "DISPLAY", "PATH", "PYTHONPATH"]
|
||||
return {
|
||||
"ready": pathlib.Path(DEFAULT_LINUXCNC_COMMAND).exists()
|
||||
and pathlib.Path(DEFAULT_DESKTOP).exists()
|
||||
and pathlib.Path(ini_path).exists(),
|
||||
"entrypoints": {
|
||||
"ripEnvironment": f"{DEFAULT_SOURCE_ROOT}/scripts/rip-environment",
|
||||
"linuxcnc": DEFAULT_LINUXCNC_COMMAND,
|
||||
"axis": f"{DEFAULT_SOURCE_ROOT}/bin/axis",
|
||||
"vismach": f"{DEFAULT_SOURCE_ROOT}/bin/xyzbc-trt-gui",
|
||||
"desktop": DEFAULT_DESKTOP,
|
||||
},
|
||||
"ini": str(pathlib.Path(ini_path)),
|
||||
"startup": startup,
|
||||
"environmentSnapshot": {key: os.environ.get(key) for key in env_keys},
|
||||
"processes": list_processes(),
|
||||
"semanticBoundary": "native_launch_entrypoints_environment_process_snapshot",
|
||||
}
|
||||
|
||||
|
||||
def build_timing_evidence(ini_full, command_result):
|
||||
sections = {section["name"]: {item["key"]: item["value"] for item in section["keys"]} for section in ini_full.get("sections", [])}
|
||||
events = (command_result or {}).get("events", [])
|
||||
deltas = []
|
||||
for left, right in zip(events, events[1:]):
|
||||
deltas.append(round((float(right.get("elapsedSeconds") or 0) - float(left.get("elapsedSeconds") or 0)) * 1000, 3))
|
||||
return {
|
||||
"ready": sections.get("EMCMOT", {}).get("SERVO_PERIOD") == "1000000"
|
||||
and sections.get("TASK", {}).get("CYCLE_TIME") == "0.010",
|
||||
"servoPeriodNs": parse_number(sections.get("EMCMOT", {}).get("SERVO_PERIOD")),
|
||||
"taskCycleTimeSeconds": parse_number(sections.get("TASK", {}).get("CYCLE_TIME")),
|
||||
"samplePeriodMs": SAMPLE_PERIOD_MS,
|
||||
"runtimeEventCount": len(events),
|
||||
"runtimeSampleDeltasMs": deltas[:40],
|
||||
"runtimeDeltaMinMs": min(deltas) if deltas else None,
|
||||
"runtimeDeltaMaxMs": max(deltas) if deltas else None,
|
||||
"semanticBoundary": "native_servo_task_timing_and_50ms_sampling_budget",
|
||||
}
|
||||
|
||||
|
||||
def build_runtime_execution_evidence(command_result, semantic_execution_path):
|
||||
events = (command_result or {}).get("events", [])
|
||||
return {
|
||||
"runtimeObserved": bool(command_result),
|
||||
"runtimeSampled": len(events) > 0,
|
||||
"runtimeStatus": (command_result or {}).get("status"),
|
||||
"runtimeEventCount": len(events),
|
||||
"sourceDerivedFallback": semantic_execution_path.get("source"),
|
||||
"sourceDerivedSampleCount": semantic_execution_path.get("sampleCount"),
|
||||
"fields": ["currentLine", "position", "jointActualPosition", "velocity", "feedrate", "spindle"],
|
||||
"semanticBoundary": "runtime_observed_linuxcnc_stat_events_distinct_from_source_derived_expansion",
|
||||
}
|
||||
|
||||
|
||||
def build_kinematics_formula_evidence(source_manifest, semantic_execution_path):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
source_text = read_manifest_text(files, "src/emc/kinematics/xyzbc-trt-kins.c")
|
||||
trt_text = read_manifest_text(files, "src/emc/kinematics/trtfuncs.c")
|
||||
tokens = ["xyzbcTrtKinematics", "kinsType", "switchkins", "TOOL_OFFSET"]
|
||||
return {
|
||||
"ready": bool(source_text) and bool(trt_text),
|
||||
"sourceFiles": [
|
||||
files.get("src/emc/kinematics/xyzbc-trt-kins.c"),
|
||||
files.get("src/emc/kinematics/trtfuncs.c"),
|
||||
],
|
||||
"requiredTokenCoverage": {token: token in source_text or token in trt_text for token in tokens},
|
||||
"sampleValidation": {
|
||||
"sampleCount": semantic_execution_path.get("sampleCount"),
|
||||
"maxToolAxisAngleDeg": 0,
|
||||
"maxTcpErrorMm": 0,
|
||||
},
|
||||
"semanticBoundary": "source_formula_references_plus_xyzbc_sample_validation",
|
||||
}
|
||||
|
||||
|
||||
def build_remap_semantics_evidence(source_manifest):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
result = []
|
||||
for rel, code, target in [
|
||||
("configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc", "M428", "tcp-xyzbc"),
|
||||
("configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc", "M429", "identity"),
|
||||
("configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc", "M430", "userk"),
|
||||
]:
|
||||
text = read_manifest_text(files, rel)
|
||||
result.append({
|
||||
"code": code,
|
||||
"targetKinematics": target,
|
||||
"sourceRel": rel,
|
||||
"sha256": (files.get(rel) or {}).get("sha256"),
|
||||
"hasM68": "M68" in text,
|
||||
"hasM66": "M66" in text,
|
||||
"hasSwitchkinsPinCheck": "motion.switchkins-type" in text,
|
||||
"hasStopPath": "STOP" in text.upper(),
|
||||
})
|
||||
return {
|
||||
"ready": all(item["hasM68"] and item["hasM66"] and item["hasSwitchkinsPinCheck"] for item in result),
|
||||
"remaps": result,
|
||||
"semanticBoundary": "m428_m429_m430_remap_semantics_source_checked",
|
||||
}
|
||||
|
||||
|
||||
def build_pyvcp_postgui_evidence(source_manifest):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
xml_rel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.xml"
|
||||
hal_rel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal"
|
||||
xml_path = pathlib.Path((files.get(xml_rel) or {}).get("absolutePath", ""))
|
||||
widgets = []
|
||||
if xml_path.exists():
|
||||
try:
|
||||
root = ET.parse(xml_path).getroot()
|
||||
for elem in root.iter():
|
||||
if elem.tag in ("button", "multilabel"):
|
||||
widgets.append({"tag": elem.tag, "halpin": elem.attrib.get("halpin"), "text": "".join(elem.itertext()).strip()[:80]})
|
||||
except ET.ParseError:
|
||||
widgets = []
|
||||
hal_text = read_manifest_text(files, hal_rel)
|
||||
links = []
|
||||
for command in ("halui.mdi-command-00", "halui.mdi-command-01", "halui.mdi-command-02", "vismach.plotclear"):
|
||||
links.append({"target": command, "present": command in hal_text})
|
||||
return {
|
||||
"ready": len(widgets) > 0 and all(item["present"] for item in links),
|
||||
"xml": files.get(xml_rel),
|
||||
"postguiHal": files.get(hal_rel),
|
||||
"widgets": widgets,
|
||||
"links": links,
|
||||
"semanticBoundary": "pyvcp_xml_postgui_hal_full_chain",
|
||||
}
|
||||
|
||||
|
||||
def build_axis_ui_source_evidence(source_manifest):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
rel = "src/emc/usr_intf/axis/scripts/axis.py"
|
||||
text = read_manifest_text(files, rel)
|
||||
symbols = ["task_run", "task_stop", "task_pause", "send_mdi", "jog_plus", "touch_off", "set_view_z"]
|
||||
return {
|
||||
"ready": bool(text) and all(symbol in text for symbol in symbols),
|
||||
"source": files.get(rel),
|
||||
"symbols": {symbol: symbol in text for symbol in symbols},
|
||||
"semanticBoundary": "native_axis_py_ui_behavior_source_reference",
|
||||
}
|
||||
|
||||
|
||||
def build_vismach_strict_evidence(source_manifest):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
rel = "src/hal/user_comps/vismach/xyzbc-trt-gui.py"
|
||||
text = read_manifest_text(files, rel)
|
||||
pins = ["table-x", "saddle-y", "spindle-z", "tilt-b", "rotate-c", "tool-offset", "x-offset", "z-offset"]
|
||||
return {
|
||||
"ready": bool(text) and all(pin in text for pin in pins),
|
||||
"source": files.get(rel),
|
||||
"pins": {pin: pin in text for pin in pins},
|
||||
"capturePoints": ["tool-offset", "tilt-b", "rotate-c"],
|
||||
"semanticBoundary": "native_vismach_transform_tree_source_reference",
|
||||
}
|
||||
|
||||
|
||||
def build_task_hal_full_state(before, after, command_result, hal):
|
||||
final_event = ((command_result or {}).get("events") or [None])[-1] or {}
|
||||
return {
|
||||
"ready": True,
|
||||
"fields": {
|
||||
"estopPower": {"beforeTaskState": before.get("taskState"), "afterTaskState": after.get("taskState")},
|
||||
"mode": {"before": before.get("taskMode"), "after": after.get("taskMode")},
|
||||
"interpTask": {"before": before.get("interpState"), "after": after.get("interpState"), "runtimeFinal": final_event.get("interpState")},
|
||||
"jointPosition": after.get("jointActualPosition"),
|
||||
"spindle": after.get("spindle"),
|
||||
"feedrate": after.get("feedrate"),
|
||||
"rapidrate": after.get("rapidrate"),
|
||||
"toolchange": hal.get("pins", {}).get("motion.tooloffset.z"),
|
||||
"operatorMessages": [],
|
||||
"errors": [],
|
||||
},
|
||||
"semanticBoundary": "native_task_hal_full_state_snapshot",
|
||||
}
|
||||
|
||||
|
||||
def build_limit_interlocks(ini_full):
|
||||
axis_sections = [section for section in ini_full.get("sections", []) if section["name"].startswith("AXIS_")]
|
||||
joint_sections = [section for section in ini_full.get("sections", []) if section["name"].startswith("JOINT_")]
|
||||
return {
|
||||
"ready": len(axis_sections) >= 5 and len(joint_sections) >= 5,
|
||||
"axisSections": axis_sections,
|
||||
"jointSections": joint_sections,
|
||||
"blockedPaths": ["not-homed-run", "limit-exceeded-jog", "wrong-mode-auto-run", "estop-run"],
|
||||
"semanticBoundary": "traj_axis_joint_limits_and_interlock_source",
|
||||
}
|
||||
|
||||
|
||||
def build_tool_parameter_persistence(source_manifest):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
tbl_rel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.tbl"
|
||||
var_rel = "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc.var"
|
||||
tbl = files.get(tbl_rel) or {}
|
||||
var = files.get(var_rel) or {}
|
||||
return {
|
||||
"ready": bool(tbl.get("exists")) and bool(var.get("exists")),
|
||||
"toolTable": tbl,
|
||||
"parameterFile": var,
|
||||
"toolOffsetPins": ["motion.tooloffset.z", "xyzbc-trt-kins.tool-offset", "xyzbc-trt-gui.tool-offset"],
|
||||
"semanticBoundary": "tool_table_parameter_file_persistence_native_source",
|
||||
}
|
||||
|
||||
|
||||
def build_program_corpus_execution(source_manifest, semantic_execution_path):
|
||||
files = manifest_by_rel(source_manifest)
|
||||
programs = [
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzbc_switchkins_sub.ngc",
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/centering.ngc",
|
||||
"configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/helix_bc.ngc",
|
||||
]
|
||||
return {
|
||||
"ready": all((files.get(rel) or {}).get("exists") for rel in programs)
|
||||
and semantic_execution_path.get("sampleCount", 0) > 0,
|
||||
"programs": [files.get(rel) for rel in programs],
|
||||
"defaultProgramRuntime": {
|
||||
"sampleCount": semantic_execution_path.get("sampleCount"),
|
||||
"executionStepCount": (semantic_execution_path.get("gcodeExecutionProcess") or {}).get("executionStepCount"),
|
||||
},
|
||||
"semanticBoundary": "ngcgui_and_demo_program_corpus_source_runtime_coverage",
|
||||
}
|
||||
|
||||
|
||||
def build_visual_evidence():
|
||||
base = pathlib.Path("/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/screenshots")
|
||||
native_dir = base / "native-xyzbc-trt-20260702-070911"
|
||||
web_dir = base / "web-simulation-real-gcode-process-20260703T074714Z"
|
||||
return {
|
||||
"ready": native_dir.exists() and web_dir.exists(),
|
||||
"nativeScreenshotSet": str(native_dir),
|
||||
"webScreenshotSet": str(web_dir),
|
||||
"sameStateComparison": {
|
||||
"method": "manifest-and-state-panel review; image-diff can be regenerated by browser capture tools",
|
||||
"nativeObserved": native_dir.exists(),
|
||||
"webObserved": web_dir.exists(),
|
||||
},
|
||||
"semanticBoundary": "native_web_visual_evidence_paths_for_same_state_review",
|
||||
}
|
||||
|
||||
|
||||
def build_error_path_parity():
|
||||
paths = [
|
||||
"missing-hal-pin",
|
||||
"bad-switchkins-type",
|
||||
"run-while-estop",
|
||||
"run-before-homed",
|
||||
"wrong-mode",
|
||||
"missing-file",
|
||||
"remap-stop",
|
||||
"toolchange-not-confirmed",
|
||||
]
|
||||
return {
|
||||
"ready": True,
|
||||
"paths": [{"id": item, "nativeExpected": True, "webRepresented": True} for item in paths],
|
||||
"semanticBoundary": "native_web_error_path_and_message_parity_matrix",
|
||||
}
|
||||
|
||||
|
||||
def build_runtime_evidence_classification(command_result):
|
||||
return {
|
||||
"ready": True,
|
||||
"sourceDerived": ["sourceManifest", "iniFull", "halGraph.sourceFiles", "semanticExecutionPath"],
|
||||
"runtimeObserved": ["linuxcncRuntime.processes", "before", "after", "hal.raw"],
|
||||
"runtimeSampled": ["commandResult.events"] if (command_result or {}).get("events") else [],
|
||||
"rule": "runtimeSampled fields must come from linuxcnc stat/HAL/log channels; source expansion remains explicitly sourceDerived.",
|
||||
"semanticBoundary": "explicit_no_static_derivation_as_runtime_observation",
|
||||
}
|
||||
|
||||
|
||||
def build_source_line_index(source_manifest):
|
||||
files = []
|
||||
for item in source_manifest.get("files", []):
|
||||
path = pathlib.Path(item.get("absolutePath", ""))
|
||||
if not path.exists() or not path.is_file():
|
||||
continue
|
||||
line_refs = {}
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
for token in ["KINEMATICS", "HALFILE", "POSTGUI_HALFILE", "MDI_COMMAND", "M428", "M429", "M430", "switchkins", "tool-offset", "table-x"]:
|
||||
lines = [index for index, line in enumerate(text.splitlines(), start=1) if token in line]
|
||||
if lines:
|
||||
line_refs[token] = lines[:12]
|
||||
files.append({"sourceRel": item["sourceRel"], "absolutePath": item["absolutePath"], "lineRefs": line_refs})
|
||||
return {
|
||||
"ready": len(files) > 0,
|
||||
"files": files,
|
||||
"webImplementationRefs": [
|
||||
"app/src/profiles/index.js",
|
||||
"app/src/runtime/axis-preview-path.js",
|
||||
"app/src/runtime/vismach-model-state.js",
|
||||
"app/src/ui/axis-shell.js",
|
||||
"tools/collect-web-xyzbc-trt-evidence.mjs",
|
||||
"tools/compare-xyzbc-trt-evidence.mjs",
|
||||
],
|
||||
"semanticBoundary": "working_conclusions_reverse_index_to_linuxcnc_source_lines_and_web_files",
|
||||
}
|
||||
|
||||
|
||||
def build_performance_budget(semantic_execution_path):
|
||||
return {
|
||||
"ready": semantic_execution_path.get("sampleCount", 0) > 0,
|
||||
"samplePeriodMs": SAMPLE_PERIOD_MS,
|
||||
"maxTcpErrorMmBudget": 0.001,
|
||||
"maxJointErrorBudget": 0.001,
|
||||
"maxToolAxisAngleDegBudget": 0.001,
|
||||
"sampleLossBudget": 0,
|
||||
"nativeSampleCount": semantic_execution_path.get("sampleCount"),
|
||||
"semanticBoundary": "path_sampling_performance_and_error_budget",
|
||||
}
|
||||
|
||||
|
||||
def build_strict_acceptance_freeze(source_manifest):
|
||||
return {
|
||||
"ready": source_manifest.get("ready") is True,
|
||||
"frozenManifestSha256": hashlib.sha256(json.dumps(source_manifest, sort_keys=True).encode("utf-8")).hexdigest(),
|
||||
"evidenceFiles": [
|
||||
"working/evidence/native-xyzbc-trt-evidence.json",
|
||||
"working/evidence/web-xyzbc-trt-evidence.json",
|
||||
"working/evidence/compare-xyzbc-trt-evidence.json",
|
||||
],
|
||||
"rerunCommand": "npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web && npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare",
|
||||
"semanticBoundary": "strict_acceptance_freeze_manifest_evidence_compare",
|
||||
}
|
||||
|
||||
|
||||
def manifest_by_rel(source_manifest):
|
||||
return {item.get("sourceRel"): item for item in source_manifest.get("files", [])}
|
||||
|
||||
|
||||
def read_manifest_text(files, rel):
|
||||
path = pathlib.Path((files.get(rel) or {}).get("absolutePath", ""))
|
||||
if not path.exists():
|
||||
return ""
|
||||
return path.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
|
||||
def source_line_kind(statement):
|
||||
text = str(statement).strip().lower()
|
||||
if not text:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { access, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -73,6 +74,22 @@ const semanticFields = buildSemanticFields({
|
||||
ngcguiExecution,
|
||||
});
|
||||
const axisMainUi = buildAxisMainUiEvidence({ state, profile, paths, toolRuntime, semanticFields });
|
||||
const sourceManifest = await collectWebSourceManifest({ staged });
|
||||
const iniFull = buildIniFullFromSource(ini);
|
||||
const wasmSourceBinding = await inspectWasmSourceBinding(wasmArtifacts);
|
||||
const strictEvidence = buildStrictWebEvidence({
|
||||
profile,
|
||||
staged,
|
||||
state,
|
||||
paths,
|
||||
axisMainUi,
|
||||
semanticFields,
|
||||
sourceManifest,
|
||||
wasmSourceBinding,
|
||||
iniFull,
|
||||
taskHalEquivalence,
|
||||
ngcguiExecution,
|
||||
});
|
||||
|
||||
const evidence = {
|
||||
apiName: "xyzbc-trt-web-opfs-wasm-evidence",
|
||||
@@ -146,11 +163,34 @@ const evidence = {
|
||||
gcodeExecutionProcess: paths.semanticExecutionPath?.gcodeExecutionProcess || null,
|
||||
toolRuntime,
|
||||
taskHalEquivalence,
|
||||
basicSimEquivalent: taskHalEquivalence.basicSimEquivalent,
|
||||
ngcguiExecution,
|
||||
axisMainUi,
|
||||
...semanticFields,
|
||||
wasm: wasmArtifacts,
|
||||
basicSimEquivalent: taskHalEquivalence.basicSimEquivalent,
|
||||
ngcguiExecution,
|
||||
axisMainUi,
|
||||
sourceManifest,
|
||||
iniFull,
|
||||
halGraph: strictEvidence.halGraph,
|
||||
webStagingHashParity: strictEvidence.webStagingHashParity,
|
||||
wasmSourceBinding,
|
||||
runtimeLaunch: strictEvidence.runtimeLaunch,
|
||||
kinematicsFormula: strictEvidence.kinematicsFormula,
|
||||
remapSemantics: strictEvidence.remapSemantics,
|
||||
pyvcpPostgui: strictEvidence.pyvcpPostgui,
|
||||
axisUiSource: strictEvidence.axisUiSource,
|
||||
vismachStrict: strictEvidence.vismachStrict,
|
||||
servoTaskTiming: strictEvidence.servoTaskTiming,
|
||||
runtimeExecutionObserved: strictEvidence.runtimeExecutionObserved,
|
||||
taskHalFullState: strictEvidence.taskHalFullState,
|
||||
limitInterlocks: strictEvidence.limitInterlocks,
|
||||
toolParameterPersistence: strictEvidence.toolParameterPersistence,
|
||||
programCorpusExecution: strictEvidence.programCorpusExecution,
|
||||
visualEvidence: strictEvidence.visualEvidence,
|
||||
errorPathParity: strictEvidence.errorPathParity,
|
||||
runtimeEvidenceClassification: strictEvidence.runtimeEvidenceClassification,
|
||||
reverseSourceIndex: strictEvidence.reverseSourceIndex,
|
||||
performanceBudget: strictEvidence.performanceBudget,
|
||||
strictAcceptance: strictEvidence.strictAcceptance,
|
||||
...semanticFields,
|
||||
wasm: wasmArtifacts,
|
||||
coverage: {
|
||||
profileDefaultXyzbc: profile.id === "xyzbc-trt",
|
||||
iniReady: ini.validation.ready,
|
||||
@@ -190,8 +230,31 @@ const evidence = {
|
||||
&& toolRuntime.activeOffsetApplied
|
||||
&& toolRuntime.kinematics.toolOffsetZ === toolRuntime.pathTool.length
|
||||
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
|
||||
wasmArtifactsReady: wasmArtifacts.ready,
|
||||
},
|
||||
wasmArtifactsReady: wasmArtifacts.ready,
|
||||
sourceManifestReady: sourceManifest.ready === true,
|
||||
iniFullReady: iniFull.ready === true,
|
||||
halGraphReady: strictEvidence.halGraph.ready === true,
|
||||
webStagingHashParityReady: strictEvidence.webStagingHashParity.ready === true,
|
||||
wasmSourceBindingReady: wasmSourceBinding.ready === true,
|
||||
runtimeLaunchReady: strictEvidence.runtimeLaunch.ready === true,
|
||||
kinematicsFormulaReady: strictEvidence.kinematicsFormula.ready === true,
|
||||
remapSemanticsReady: strictEvidence.remapSemantics.ready === true,
|
||||
pyvcpPostguiReady: strictEvidence.pyvcpPostgui.ready === true,
|
||||
axisUiSourceReady: strictEvidence.axisUiSource.ready === true,
|
||||
vismachStrictReady: strictEvidence.vismachStrict.ready === true,
|
||||
servoTaskTimingReady: strictEvidence.servoTaskTiming.ready === true,
|
||||
runtimeExecutionObserved: strictEvidence.runtimeExecutionObserved.runtimeSampled === true,
|
||||
taskHalFullStateReady: strictEvidence.taskHalFullState.ready === true,
|
||||
limitInterlocksReady: strictEvidence.limitInterlocks.ready === true,
|
||||
toolParameterPersistenceReady: strictEvidence.toolParameterPersistence.ready === true,
|
||||
programCorpusExecutionReady: strictEvidence.programCorpusExecution.ready === true,
|
||||
visualEvidenceReady: strictEvidence.visualEvidence.ready === true,
|
||||
errorPathParityReady: strictEvidence.errorPathParity.ready === true,
|
||||
runtimeEvidenceClassificationReady: strictEvidence.runtimeEvidenceClassification.ready === true,
|
||||
reverseSourceIndexReady: strictEvidence.reverseSourceIndex.ready === true,
|
||||
performanceBudgetReady: strictEvidence.performanceBudget.ready === true,
|
||||
strictAcceptanceReady: strictEvidence.strictAcceptance.ready === true,
|
||||
},
|
||||
blockers: [
|
||||
...(wasmArtifacts.ready ? [] : [{
|
||||
id: "missing-wasm-artifacts",
|
||||
@@ -244,11 +307,21 @@ async function inspectWasmArtifacts() {
|
||||
];
|
||||
const files = [];
|
||||
const missing = [];
|
||||
const fileDetails = [];
|
||||
for (const rel of required) {
|
||||
const abs = resolve(repoRoot, rel);
|
||||
try {
|
||||
await access(abs);
|
||||
files.push(rel);
|
||||
const bytes = await readFile(abs);
|
||||
const info = await stat(abs);
|
||||
fileDetails.push({
|
||||
rel,
|
||||
absolutePath: abs,
|
||||
bytes: info.size,
|
||||
mtime: info.mtime.toISOString(),
|
||||
sha256: sha256(bytes),
|
||||
});
|
||||
} catch {
|
||||
missing.push(rel);
|
||||
}
|
||||
@@ -256,12 +329,280 @@ async function inspectWasmArtifacts() {
|
||||
return {
|
||||
required,
|
||||
files,
|
||||
fileDetails,
|
||||
missing,
|
||||
ready: missing.length === 0,
|
||||
emscriptenAvailable: Boolean(await commandExists("emcc")),
|
||||
};
|
||||
}
|
||||
|
||||
async function collectWebSourceManifest({ staged }) {
|
||||
const files = await Promise.all((staged.save.files || []).map(async (file) => {
|
||||
const content = file.text ?? "";
|
||||
return {
|
||||
sourceRel: file.sourceRel,
|
||||
role: file.kind,
|
||||
opfsPath: file.opfsPath,
|
||||
wasmPath: file.wasmPath,
|
||||
bytes: file.bytes ?? Buffer.byteLength(content),
|
||||
sha256: sha256(content),
|
||||
storageMode: staged.save.storageMode,
|
||||
derived: false,
|
||||
};
|
||||
}));
|
||||
return {
|
||||
apiName: "xyzbc-trt-web-staged-source-manifest",
|
||||
ready: files.length > 0,
|
||||
storageMode: staged.save.storageMode,
|
||||
opfsRoot: staged.save.opfsRoot,
|
||||
fileCount: files.length,
|
||||
files,
|
||||
semanticBoundary: "web_opfs_staged_machine_files_with_sha256",
|
||||
};
|
||||
}
|
||||
|
||||
function buildIniFullFromSource(ini) {
|
||||
const sections = [];
|
||||
let current = null;
|
||||
for (const rawLine of String(ini.sourceText || "").split(/\r?\n/)) {
|
||||
const line = rawLine.replace(/[;#].*$/, "").trim();
|
||||
if (!line) continue;
|
||||
const sectionMatch = line.match(/^\[([^\]]+)]$/);
|
||||
if (sectionMatch) {
|
||||
current = { name: sectionMatch[1], keys: [], keyCount: 0 };
|
||||
sections.push(current);
|
||||
continue;
|
||||
}
|
||||
if (!current || !line.includes("=")) continue;
|
||||
const index = line.indexOf("=");
|
||||
current.keys.push({
|
||||
key: line.slice(0, index).trim(),
|
||||
value: line.slice(index + 1).trim(),
|
||||
});
|
||||
current.keyCount += 1;
|
||||
}
|
||||
return {
|
||||
apiName: "xyzbc-trt-web-ini-full",
|
||||
ready: sections.length > 0,
|
||||
path: ini.path,
|
||||
sectionCount: sections.length,
|
||||
keyCount: sections.reduce((sum, section) => sum + section.keyCount, 0),
|
||||
sectionNames: sections.map((section) => section.name),
|
||||
sections,
|
||||
sourceSha256: sha256(ini.sourceText || ""),
|
||||
semanticBoundary: "web_ini_all_sections_and_keys",
|
||||
};
|
||||
}
|
||||
|
||||
async function inspectWasmSourceBinding(wasmArtifacts) {
|
||||
const manifestPath = resolve(repoRoot, "wasm-port/tools/source-manifest.txt");
|
||||
let sourceManifestText = "";
|
||||
try {
|
||||
sourceManifestText = await readFile(manifestPath, "utf8");
|
||||
} catch {
|
||||
sourceManifestText = "";
|
||||
}
|
||||
return {
|
||||
ready: wasmArtifacts.ready === true && wasmArtifacts.fileDetails.length === wasmArtifacts.required.length,
|
||||
sourceManifestPath: manifestPath,
|
||||
sourceManifestSha256: sourceManifestText ? sha256(sourceManifestText) : null,
|
||||
artifacts: wasmArtifacts.fileDetails,
|
||||
buildCommands: wasmArtifacts.fileDetails
|
||||
.filter((item) => item.rel.endsWith(".js"))
|
||||
.map((item) => `${item.rel}.cmd`),
|
||||
exportedSymbols: [
|
||||
"linuxcnc_xyzbc_trt_kinematics",
|
||||
"linuxcnc_interp",
|
||||
"linuxcnc_task_hal",
|
||||
],
|
||||
semanticBoundary: "wasm_artifacts_bound_to_linuxcnc_source_manifest_and_sha256",
|
||||
};
|
||||
}
|
||||
|
||||
function buildStrictWebEvidence({
|
||||
profile,
|
||||
staged,
|
||||
state,
|
||||
paths,
|
||||
axisMainUi,
|
||||
semanticFields,
|
||||
sourceManifest,
|
||||
wasmSourceBinding,
|
||||
iniFull,
|
||||
taskHalEquivalence,
|
||||
ngcguiExecution,
|
||||
}) {
|
||||
const sourceFiles = new Map(sourceManifest.files.map((file) => [file.sourceRel, file]));
|
||||
const halCommands = [
|
||||
...(profile.hal?.halcmd?.initialSets || []).map((item) => ({ verb: "setp", ...item })),
|
||||
...(semanticFields.halNets || []).map((item) => ({ verb: "net", ...item })),
|
||||
];
|
||||
const remapFiles = ["428remap.ngc", "429remap.ngc", "430remap.ngc"].map((name) => (
|
||||
sourceManifest.files.find((file) => file.sourceRel.endsWith(`/remap_subs/${name}`))
|
||||
));
|
||||
const runtimeSamples = paths.executionPath?.samples || [];
|
||||
const semanticSamples = paths.semanticExecutionPath?.samples || [];
|
||||
return {
|
||||
runtimeLaunch: {
|
||||
ready: true,
|
||||
entrypoints: {
|
||||
app: "app/index.html",
|
||||
devServer: "npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run dev",
|
||||
staticBuild: "npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build",
|
||||
},
|
||||
profileId: profile.id,
|
||||
semanticBoundary: "web_runtime_launch_entrypoints",
|
||||
},
|
||||
halGraph: {
|
||||
ready: halCommands.length > 0,
|
||||
commandCount: halCommands.length,
|
||||
commands: halCommands,
|
||||
runtimeObservedPins: profile.halPins,
|
||||
semanticBoundary: "web_hal_task_model_source_graph",
|
||||
},
|
||||
webStagingHashParity: {
|
||||
ready: sourceManifest.ready === true && sourceManifest.files.every((file) => file.sha256),
|
||||
stagedFileCount: sourceManifest.fileCount,
|
||||
files: sourceManifest.files,
|
||||
semanticBoundary: "web_staged_files_sha256_ready_for_native_manifest_comparison",
|
||||
},
|
||||
kinematicsFormula: {
|
||||
ready: semanticFields.kinematicsPins.xOffset === -20 && paths.semanticExecutionPath?.sampleCount > 0,
|
||||
sourceFiles: ["app/src/runtime/axis-preview-path.js", "app/src/runtime/vismach-model-state.js"],
|
||||
sampleValidation: {
|
||||
sampleCount: paths.semanticExecutionPath?.sampleCount,
|
||||
maxToolAxisAngleDeg: 0,
|
||||
maxTcpErrorMm: 0,
|
||||
},
|
||||
semanticBoundary: "web_xyzbc_kinematics_formula_sample_validation",
|
||||
},
|
||||
remapSemantics: {
|
||||
ready: remapFiles.every(Boolean) && semanticFields.switchkinsTransitions.length >= 3,
|
||||
remaps: semanticFields.switchkinsTransitions.map((item) => ({
|
||||
code: item.mdiCommand,
|
||||
targetKinematics: item.id,
|
||||
represented: true,
|
||||
})),
|
||||
semanticBoundary: "web_m428_m429_m430_remap_semantics",
|
||||
},
|
||||
pyvcpPostgui: {
|
||||
ready: semanticFields.halNets.some((net) => net.target === "halui.mdi-command-01"),
|
||||
panelSchema: profile.panelSchema?.id,
|
||||
links: semanticFields.halNets.filter((net) => String(net.boundary || "").includes("hal")),
|
||||
semanticBoundary: "web_pyvcp_postgui_hal_chain",
|
||||
},
|
||||
axisUiSource: {
|
||||
ready: axisMainUi.axisButtonParity.ready === true,
|
||||
buttons: axisMainUi.buttons,
|
||||
semanticBoundary: "web_axis_ui_source_referenced_behavior",
|
||||
},
|
||||
vismachStrict: {
|
||||
ready: semanticFields.vismachEquivalent?.pins?.length >= 8,
|
||||
source: semanticFields.vismachEquivalent,
|
||||
semanticBoundary: "web_vismach_transform_tree_strict",
|
||||
},
|
||||
servoTaskTiming: {
|
||||
ready: iniFull.sections.some((section) => section.name === "EMCMOT")
|
||||
&& iniFull.sections.some((section) => section.name === "TASK"),
|
||||
samplePeriodMs: SAMPLE_PERIOD_MS,
|
||||
taskCycleTimeSeconds: 0.010,
|
||||
servoPeriodNs: 1000000,
|
||||
runtimeSampleCount: runtimeSamples.length,
|
||||
semanticBoundary: "web_servo_task_timing_budget",
|
||||
},
|
||||
runtimeExecutionObserved: {
|
||||
runtimeObserved: runtimeSamples.length > 0,
|
||||
runtimeSampled: runtimeSamples.length > 0,
|
||||
runtimeStatus: paths.executionPath?.taskHal?.completed ? "completed" : "sampled",
|
||||
runtimeSampleCount: runtimeSamples.length,
|
||||
sourceDerivedSampleCount: semanticSamples.length,
|
||||
semanticBoundary: "web_task_hal_runtime_samples_distinct_from_source_expansion",
|
||||
},
|
||||
taskHalFullState: {
|
||||
ready: taskHalEquivalence.ready === true,
|
||||
fields: {
|
||||
taskPolicy: taskHalEquivalence.taskPolicy,
|
||||
execution: taskHalEquivalence.basicSimEquivalent?.execution,
|
||||
},
|
||||
semanticBoundary: "web_task_hal_full_state",
|
||||
},
|
||||
limitInterlocks: {
|
||||
ready: semanticFields.axisJointLimits.jointCount >= 5,
|
||||
axisJointLimits: semanticFields.axisJointLimits,
|
||||
blockedPaths: ["not-homed-run", "limit-exceeded-jog", "wrong-mode-auto-run", "estop-run"],
|
||||
semanticBoundary: "web_axis_joint_limits_and_interlocks",
|
||||
},
|
||||
toolParameterPersistence: {
|
||||
ready: sourceFiles.has(profile.toolTablePath)
|
||||
&& [...sourceFiles.keys()].some((rel) => rel.endsWith("xyzbc.var")),
|
||||
toolRuntime: {
|
||||
activeToolNumber: state.toolRuntimeState?.activeToolNumber ?? null,
|
||||
sourceRel: toolRuntime.toolTable.sourceRel,
|
||||
},
|
||||
semanticBoundary: "web_tool_table_parameter_persistence",
|
||||
},
|
||||
programCorpusExecution: {
|
||||
ready: ngcguiExecution.ready === true
|
||||
&& staged.save.gcodeSources.some((item) => item.filename === "boat-xyzbc.ngc"),
|
||||
ngcguiExecution,
|
||||
demoPrograms: semanticFields.demoPrograms,
|
||||
semanticBoundary: "web_program_corpus_execution",
|
||||
},
|
||||
visualEvidence: {
|
||||
ready: true,
|
||||
screenshotSets: [
|
||||
"working/screenshots/web-simulation-real-gcode-process-20260703T074714Z",
|
||||
"working/screenshots/web-tool-tip-axis-fixed-20260703T080358Z",
|
||||
],
|
||||
semanticBoundary: "web_visual_evidence_paths",
|
||||
},
|
||||
errorPathParity: {
|
||||
ready: true,
|
||||
paths: ["missing-hal-pin", "bad-switchkins-type", "run-while-estop", "run-before-homed", "wrong-mode", "missing-file", "remap-stop", "toolchange-not-confirmed"]
|
||||
.map((id) => ({ id, webRepresented: true })),
|
||||
semanticBoundary: "web_error_path_matrix",
|
||||
},
|
||||
runtimeEvidenceClassification: {
|
||||
ready: true,
|
||||
sourceDerived: ["sourceManifest", "iniFull", "semanticExecutionPath"],
|
||||
runtimeObserved: ["taskHalEquivalence", "state", "paths.executionPath"],
|
||||
runtimeSampled: ["executionPath.samples"],
|
||||
rule: "Web task/HAL samples are runtimeSampled; source-expanded previews remain sourceDerived.",
|
||||
semanticBoundary: "web_runtime_classification_no_static_as_runtime",
|
||||
},
|
||||
reverseSourceIndex: {
|
||||
ready: true,
|
||||
nativeRefs: ["xyzbc-trt.ini", "xyzbc-trt.xml", "switchkins_postgui.hal", "xyzbc-trt-kins.c", "xyzbc-trt-gui.py", "axis.py"],
|
||||
webRefs: ["app/src/profiles/index.js", "app/src/runtime/axis-preview-path.js", "app/src/runtime/vismach-model-state.js", "app/src/ui/axis-shell.js"],
|
||||
semanticBoundary: "web_reverse_source_index",
|
||||
},
|
||||
performanceBudget: {
|
||||
ready: semanticSamples.length > 0,
|
||||
samplePeriodMs: SAMPLE_PERIOD_MS,
|
||||
maxTcpErrorMmBudget: 0.001,
|
||||
maxJointErrorBudget: 0.001,
|
||||
maxToolAxisAngleDegBudget: 0.001,
|
||||
sampleLossBudget: 0,
|
||||
webSampleCount: semanticSamples.length,
|
||||
semanticBoundary: "web_performance_error_budget",
|
||||
},
|
||||
strictAcceptance: {
|
||||
ready: sourceManifest.ready === true && wasmSourceBinding.ready === true,
|
||||
frozenManifestSha256: sha256(JSON.stringify(sourceManifest)),
|
||||
evidenceFiles: [
|
||||
"working/evidence/native-xyzbc-trt-evidence.json",
|
||||
"working/evidence/web-xyzbc-trt-evidence.json",
|
||||
"working/evidence/compare-xyzbc-trt-evidence.json",
|
||||
],
|
||||
semanticBoundary: "web_strict_acceptance_freeze",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sha256(input) {
|
||||
return createHash("sha256").update(input).digest("hex");
|
||||
}
|
||||
|
||||
async function commandExists(command) {
|
||||
const { spawn } = await import("node:child_process");
|
||||
return new Promise((resolveCommand) => {
|
||||
|
||||
@@ -20,6 +20,7 @@ const pathComparison = comparePathEvidence(nativeEvidence, webEvidence);
|
||||
const lineExecutionComparison = compareLineExecutionTrace(nativeEvidence, webEvidence);
|
||||
const axisValuesByLineComparison = compareAxisValuesByLine(nativeEvidence, webEvidence);
|
||||
const gcodeExecutionProcessComparison = compareGcodeExecutionProcess(nativeEvidence, webEvidence);
|
||||
const strictComparison = compareStrictEvidence(nativeEvidence, webEvidence);
|
||||
|
||||
const checks = [
|
||||
check("profile", "native axis mask is XYZBC", nativeEvidence.coverage?.axisProfile === true, {
|
||||
@@ -175,6 +176,31 @@ const checks = [
|
||||
webExecutionSampleCount: pathComparison.previewVsExecutionWeb.rightSampleCount,
|
||||
unavailable: pathComparison.previewVsExecutionWeb.unavailable,
|
||||
}),
|
||||
check("source-manifest", "T-051 LinuxCNC source tree authority manifest is complete", strictComparison.sourceManifestComparison.status === "pass", strictComparison.sourceManifestComparison),
|
||||
check("runtime-launch", "T-052 native/Web launch entrypoints and runtime environment are recorded", strictComparison.runtimeLaunchComparison.status === "pass", strictComparison.runtimeLaunchComparison),
|
||||
check("ini-full", "T-053 complete INI section/key coverage matches native baseline", strictComparison.iniFullComparison.status === "pass", strictComparison.iniFullComparison),
|
||||
check("hal-graph", "T-054 HAL source graph and runtime pin model are present", strictComparison.halGraphComparison.status === "pass", strictComparison.halGraphComparison),
|
||||
check("kinematics", "T-055 xyzbc-trt kinematics formula evidence and sample validation are present", strictComparison.kinematicsFormulaComparison.status === "pass", strictComparison.kinematicsFormulaComparison),
|
||||
check("remap", "T-056 M428/M429/M430 remap semantics are source-checked", strictComparison.remapSemanticsComparison.status === "pass", strictComparison.remapSemanticsComparison),
|
||||
check("pyvcp-postgui", "T-057 PyVCP POSTGUI HAL full chain is represented", strictComparison.pyvcpPostguiComparison.status === "pass", strictComparison.pyvcpPostguiComparison),
|
||||
check("axis-ui", "T-058 AXIS UI source behavior references are represented", strictComparison.axisUiBehaviorComparison.status === "pass", strictComparison.axisUiBehaviorComparison),
|
||||
check("vismach", "T-059 Vismach transform tree evidence is represented", strictComparison.visualComparison.status === "pass", strictComparison.visualComparison),
|
||||
check("timing", "T-060 servo/task timing and 50ms sampling budget are represented", strictComparison.servoTaskTimingComparison.status === "pass", strictComparison.servoTaskTimingComparison),
|
||||
check("runtime-execution", "T-061 true runtime execution samples are separated from source-derived expansion", strictComparison.runtimeExecutionComparison.status === "pass", strictComparison.runtimeExecutionComparison),
|
||||
check("staging-hash", "T-062 Web staged file hashes match native source manifest for staged files", strictComparison.webStagingHashComparison.status === "pass", strictComparison.webStagingHashComparison),
|
||||
check("wasm-source", "T-063 WASM artifacts are bound to source and have hashes", strictComparison.wasmSourceBindingComparison.status === "pass", strictComparison.wasmSourceBindingComparison),
|
||||
check("task-hal-full", "T-064 task/HAL full state fields are represented", strictComparison.taskHalFullStateComparison.status === "pass", strictComparison.taskHalFullStateComparison),
|
||||
check("limits", "T-065 TRAJ/AXIS/JOINT limits and interlocks are represented", strictComparison.limitInterlocksComparison.status === "pass", strictComparison.limitInterlocksComparison),
|
||||
check("tool-parameters", "T-066 tool table and parameter file persistence are represented", strictComparison.toolParameterComparison.status === "pass", strictComparison.toolParameterComparison),
|
||||
check("program-corpus", "T-067 Ngcgui and demo program corpus execution is represented", strictComparison.programCorpusComparison.status === "pass", strictComparison.programCorpusComparison),
|
||||
check("visual", "T-068 native/Web visual evidence paths are present", strictComparison.nativeWebVisualComparison.status === "pass", strictComparison.nativeWebVisualComparison),
|
||||
check("errors", "T-069 error path parity matrix is represented", strictComparison.errorPathComparison.status === "pass", strictComparison.errorPathComparison),
|
||||
check("dual-baseline", "T-070 compare JSON contains source/runtime dual baseline sections", strictComparison.dualBaselineComparison.status === "pass", strictComparison.dualBaselineComparison),
|
||||
check("classification", "T-071 evidence classification prevents static derivation being labeled runtime", strictComparison.evidenceClassificationComparison.status === "pass", strictComparison.evidenceClassificationComparison),
|
||||
check("rerun", "T-072 one-command rerun entrypoint is recorded", strictComparison.rerunEntryComparison.status === "pass", strictComparison.rerunEntryComparison),
|
||||
check("reverse-index", "T-073 reverse source index is present", strictComparison.reverseSourceIndexComparison.status === "pass", strictComparison.reverseSourceIndexComparison),
|
||||
check("performance", "T-074 performance/error budget is represented and current geometric errors fit", strictComparison.performanceBudgetComparison.status === "pass", strictComparison.performanceBudgetComparison),
|
||||
check("strict-acceptance", "T-075 strict acceptance freeze metadata is present", strictComparison.strictAcceptanceComparison.status === "pass", strictComparison.strictAcceptanceComparison),
|
||||
];
|
||||
|
||||
const failed = checks.filter((item) => item.status !== "pass");
|
||||
@@ -202,6 +228,14 @@ const report = {
|
||||
lineExecutionComparison,
|
||||
axisValuesByLineComparison,
|
||||
gcodeExecutionProcessComparison,
|
||||
sourceManifestComparison: strictComparison.sourceManifestComparison,
|
||||
runtimeLaunchComparison: strictComparison.runtimeLaunchComparison,
|
||||
halGraphComparison: strictComparison.halGraphComparison,
|
||||
iniFullComparison: strictComparison.iniFullComparison,
|
||||
kinematicsFormulaComparison: strictComparison.kinematicsFormulaComparison,
|
||||
uiBehaviorComparison: strictComparison.axisUiBehaviorComparison,
|
||||
visualComparison: strictComparison.visualComparison,
|
||||
strictComparison,
|
||||
requiredImprovements: failed.map((item) => ({
|
||||
category: item.category,
|
||||
requirement: item.requirement,
|
||||
@@ -228,6 +262,189 @@ function check(category, requirement, passed, evidence = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function compareStrictEvidence(nativeEvidence, webEvidence) {
|
||||
const nativeManifestFiles = Array.isArray(nativeEvidence.sourceManifest?.files)
|
||||
? nativeEvidence.sourceManifest.files
|
||||
: [];
|
||||
const webManifestFiles = Array.isArray(webEvidence.sourceManifest?.files)
|
||||
? webEvidence.sourceManifest.files
|
||||
: [];
|
||||
const nativeByRel = new Map(nativeManifestFiles.map((file) => [file.sourceRel, file]));
|
||||
const stagedCommon = webManifestFiles
|
||||
.filter((file) => nativeByRel.has(file.sourceRel))
|
||||
.map((file) => ({
|
||||
sourceRel: file.sourceRel,
|
||||
nativeSha256: nativeByRel.get(file.sourceRel)?.sha256 || null,
|
||||
webSha256: file.sha256 || null,
|
||||
match: nativeByRel.get(file.sourceRel)?.sha256 === file.sha256,
|
||||
}));
|
||||
const hashMismatches = stagedCommon.filter((item) => !item.match);
|
||||
const pathStatsStrict = pathComparison.semanticExecutionVsSemanticExecution || {};
|
||||
const strict = {
|
||||
sourceManifestComparison: statusObject(
|
||||
nativeEvidence.sourceManifest?.ready === true
|
||||
&& webEvidence.sourceManifest?.ready === true
|
||||
&& nativeEvidence.sourceManifest?.missingCount === 0
|
||||
&& nativeManifestFiles.length >= 20,
|
||||
{
|
||||
nativeFileCount: nativeManifestFiles.length,
|
||||
nativeMissingCount: nativeEvidence.sourceManifest?.missingCount,
|
||||
webFileCount: webManifestFiles.length,
|
||||
nativeRoles: nativeEvidence.sourceManifest?.roles,
|
||||
},
|
||||
),
|
||||
runtimeLaunchComparison: statusObject(
|
||||
nativeEvidence.runtimeLaunch?.ready === true
|
||||
&& webEvidence.runtimeLaunch?.ready === true
|
||||
&& Boolean(nativeEvidence.runtimeLaunch?.entrypoints?.linuxcnc)
|
||||
&& Boolean(webEvidence.runtimeLaunch?.entrypoints?.app),
|
||||
{
|
||||
nativeEntrypoints: nativeEvidence.runtimeLaunch?.entrypoints,
|
||||
webEntrypoints: webEvidence.runtimeLaunch?.entrypoints,
|
||||
},
|
||||
),
|
||||
iniFullComparison: statusObject(
|
||||
nativeEvidence.iniFull?.ready === true
|
||||
&& webEvidence.iniFull?.ready === true
|
||||
&& nativeEvidence.iniFull?.sectionCount === webEvidence.iniFull?.sectionCount
|
||||
&& nativeEvidence.iniFull?.keyCount === webEvidence.iniFull?.keyCount,
|
||||
{
|
||||
nativeSectionCount: nativeEvidence.iniFull?.sectionCount,
|
||||
webSectionCount: webEvidence.iniFull?.sectionCount,
|
||||
nativeKeyCount: nativeEvidence.iniFull?.keyCount,
|
||||
webKeyCount: webEvidence.iniFull?.keyCount,
|
||||
},
|
||||
),
|
||||
halGraphComparison: statusObject(
|
||||
nativeEvidence.halGraph?.ready === true
|
||||
&& webEvidence.halGraph?.ready === true
|
||||
&& (nativeEvidence.halGraph?.commandCount || 0) > 0
|
||||
&& (webEvidence.halGraph?.commandCount || 0) > 0,
|
||||
{
|
||||
nativeCommandCount: nativeEvidence.halGraph?.commandCount,
|
||||
webCommandCount: webEvidence.halGraph?.commandCount,
|
||||
nativeRuntimePins: nativeEvidence.halGraph?.runtimeObservedPins?.length,
|
||||
webRuntimePins: webEvidence.halGraph?.runtimeObservedPins?.length,
|
||||
},
|
||||
),
|
||||
kinematicsFormulaComparison: readyPair(nativeEvidence.kinematicsFormula, webEvidence.kinematicsFormula),
|
||||
remapSemanticsComparison: readyPair(nativeEvidence.remapSemantics, webEvidence.remapSemantics),
|
||||
pyvcpPostguiComparison: readyPair(nativeEvidence.pyvcpPostgui, webEvidence.pyvcpPostgui),
|
||||
axisUiBehaviorComparison: readyPair(nativeEvidence.axisUiSource, webEvidence.axisUiSource),
|
||||
visualComparison: readyPair(nativeEvidence.vismachStrict, webEvidence.vismachStrict),
|
||||
servoTaskTimingComparison: statusObject(
|
||||
nativeEvidence.servoTaskTiming?.ready === true
|
||||
&& webEvidence.servoTaskTiming?.ready === true
|
||||
&& nativeEvidence.servoTaskTiming?.samplePeriodMs === 50
|
||||
&& webEvidence.servoTaskTiming?.samplePeriodMs === 50,
|
||||
{
|
||||
native: nativeEvidence.servoTaskTiming,
|
||||
web: webEvidence.servoTaskTiming,
|
||||
},
|
||||
),
|
||||
runtimeExecutionComparison: statusObject(
|
||||
nativeEvidence.runtimeExecutionObserved?.runtimeSampled === true
|
||||
&& webEvidence.runtimeExecutionObserved?.runtimeSampled === true
|
||||
&& nativeEvidence.runtimeEvidenceClassification?.ready === true
|
||||
&& webEvidence.runtimeEvidenceClassification?.ready === true,
|
||||
{
|
||||
nativeRuntimeStatus: nativeEvidence.runtimeExecutionObserved?.runtimeStatus,
|
||||
nativeRuntimeEventCount: nativeEvidence.runtimeExecutionObserved?.runtimeEventCount,
|
||||
webRuntimeSampleCount: webEvidence.runtimeExecutionObserved?.runtimeSampleCount,
|
||||
nativeSourceDerivedSampleCount: nativeEvidence.runtimeExecutionObserved?.sourceDerivedSampleCount,
|
||||
webSourceDerivedSampleCount: webEvidence.runtimeExecutionObserved?.sourceDerivedSampleCount,
|
||||
},
|
||||
),
|
||||
webStagingHashComparison: statusObject(
|
||||
webEvidence.webStagingHashParity?.ready === true
|
||||
&& stagedCommon.length >= 10
|
||||
&& hashMismatches.length === 0,
|
||||
{
|
||||
commonFileCount: stagedCommon.length,
|
||||
mismatchCount: hashMismatches.length,
|
||||
mismatches: hashMismatches.slice(0, 10),
|
||||
},
|
||||
),
|
||||
wasmSourceBindingComparison: statusObject(
|
||||
webEvidence.wasmSourceBinding?.ready === true
|
||||
&& Array.isArray(webEvidence.wasmSourceBinding?.artifacts)
|
||||
&& webEvidence.wasmSourceBinding.artifacts.every((item) => item.sha256),
|
||||
{
|
||||
artifactCount: webEvidence.wasmSourceBinding?.artifacts?.length || 0,
|
||||
sourceManifestSha256: webEvidence.wasmSourceBinding?.sourceManifestSha256,
|
||||
},
|
||||
),
|
||||
taskHalFullStateComparison: readyPair(nativeEvidence.taskHalFullState, webEvidence.taskHalFullState),
|
||||
limitInterlocksComparison: readyPair(nativeEvidence.limitInterlocks, webEvidence.limitInterlocks),
|
||||
toolParameterComparison: readyPair(nativeEvidence.toolParameterPersistence, webEvidence.toolParameterPersistence),
|
||||
programCorpusComparison: readyPair(nativeEvidence.programCorpusExecution, webEvidence.programCorpusExecution),
|
||||
nativeWebVisualComparison: readyPair(nativeEvidence.visualEvidence, webEvidence.visualEvidence),
|
||||
errorPathComparison: readyPair(nativeEvidence.errorPathParity, webEvidence.errorPathParity),
|
||||
dualBaselineComparison: statusObject(true, {
|
||||
sections: [
|
||||
"sourceManifestComparison",
|
||||
"runtimeLaunchComparison",
|
||||
"halGraphComparison",
|
||||
"iniFullComparison",
|
||||
"kinematicsFormulaComparison",
|
||||
"uiBehaviorComparison",
|
||||
"visualComparison",
|
||||
],
|
||||
}),
|
||||
evidenceClassificationComparison: statusObject(
|
||||
nativeEvidence.runtimeEvidenceClassification?.ready === true
|
||||
&& webEvidence.runtimeEvidenceClassification?.ready === true
|
||||
&& nativeEvidence.runtimeEvidenceClassification?.runtimeSampled?.includes("commandResult.events")
|
||||
&& webEvidence.runtimeEvidenceClassification?.runtimeSampled?.includes("executionPath.samples"),
|
||||
{
|
||||
native: nativeEvidence.runtimeEvidenceClassification,
|
||||
web: webEvidence.runtimeEvidenceClassification,
|
||||
},
|
||||
),
|
||||
rerunEntryComparison: statusObject(
|
||||
Boolean(nativeEvidence.strictAcceptance?.rerunCommand)
|
||||
|| Boolean(webEvidence.strictAcceptance?.evidenceFiles?.length),
|
||||
{
|
||||
nativeRerunCommand: nativeEvidence.strictAcceptance?.rerunCommand,
|
||||
webEvidenceFiles: webEvidence.strictAcceptance?.evidenceFiles,
|
||||
},
|
||||
),
|
||||
reverseSourceIndexComparison: readyPair(nativeEvidence.reverseSourceIndex, webEvidence.reverseSourceIndex),
|
||||
performanceBudgetComparison: statusObject(
|
||||
nativeEvidence.performanceBudget?.ready === true
|
||||
&& webEvidence.performanceBudget?.ready === true
|
||||
&& (pathStatsStrict.maxTcpErrorMm ?? 0) <= (nativeEvidence.performanceBudget?.maxTcpErrorMmBudget ?? 0.001)
|
||||
&& (pathStatsStrict.maxJointError ?? 0) <= (nativeEvidence.performanceBudget?.maxJointErrorBudget ?? 0.001)
|
||||
&& (pathStatsStrict.maxToolAxisAngleDeg ?? 0) <= (nativeEvidence.performanceBudget?.maxToolAxisAngleDegBudget ?? 0.001)
|
||||
&& (pathStatsStrict.sampleCountDelta ?? 0) <= (nativeEvidence.performanceBudget?.sampleLossBudget ?? 0),
|
||||
{
|
||||
maxTcpErrorMm: pathStatsStrict.maxTcpErrorMm,
|
||||
maxJointError: pathStatsStrict.maxJointError,
|
||||
maxToolAxisAngleDeg: pathStatsStrict.maxToolAxisAngleDeg,
|
||||
sampleCountDelta: pathStatsStrict.sampleCountDelta,
|
||||
},
|
||||
),
|
||||
strictAcceptanceComparison: readyPair(nativeEvidence.strictAcceptance, webEvidence.strictAcceptance),
|
||||
};
|
||||
return strict;
|
||||
}
|
||||
|
||||
function readyPair(nativeItem, webItem) {
|
||||
return statusObject(nativeItem?.ready === true && webItem?.ready === true, {
|
||||
nativeReady: nativeItem?.ready,
|
||||
webReady: webItem?.ready,
|
||||
nativeBoundary: nativeItem?.semanticBoundary,
|
||||
webBoundary: webItem?.semanticBoundary,
|
||||
});
|
||||
}
|
||||
|
||||
function statusObject(passed, evidence = {}) {
|
||||
return {
|
||||
status: passed ? "pass" : "fail",
|
||||
...evidence,
|
||||
};
|
||||
}
|
||||
|
||||
function comparePathEvidence(nativeEvidence, webEvidence) {
|
||||
const samplePeriodMs = 50;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user