diff --git a/README.md b/README.md index a4a095b..94aae8f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # LinuxCNC Web/WASM +当前配置驱动改造的重启接续入口:[`nextTask-hardcoding.md`](nextTask-hardcoding.md)。 + 五轴机床 INI 参数化设计(XYZAC / XYZBC)的当前工作入口: [`项目文档、任务矩阵与验收证据`](docs/trt-parametric/README.md)。本专项按该索引接续。 diff --git a/coverage/configuration/browser.report.json b/coverage/configuration/browser.report.json new file mode 100644 index 0000000..0bdcd94 --- /dev/null +++ b/coverage/configuration/browser.report.json @@ -0,0 +1,18 @@ +{ + "report_id": "configurable-ui-browser-v1", + "status": "verified", + "default_machine": "xyzbc-trt", + "entrypoint": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/configured-machine.ini", + "program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/configured-preview.ngc", + "increments": [ + "0.25", + "2.5" + ], + "feed_max": 1.3, + "colors": { + "purple": 143449, + "green": 85 + }, + "configured_icons": 24, + "browser_errors": [] +} diff --git a/coverage/configuration/configured-ui.png b/coverage/configuration/configured-ui.png new file mode 100644 index 0000000..8c3e01d Binary files /dev/null and b/coverage/configuration/configured-ui.png differ diff --git a/coverage/configuration/input-audit.report.json b/coverage/configuration/input-audit.report.json new file mode 100644 index 0000000..836e38a --- /dev/null +++ b/coverage/configuration/input-audit.report.json @@ -0,0 +1,76 @@ +{ + "report_id": "configuration-input-audit-v1", + "status": "verified", + "scope": "configurable inputs; not a zero-literals claim", + "checks": { + "machine_catalog_parses": true, + "resource_catalog_parses": true, + "settings_parse": true, + "worker_reparses_ini": true, + "axis_limits_from_ini": true, + "joint_limits_from_ini": true, + "no_kinematics_example_limits": true, + "no_loader_fixed_timing": true, + "no_loader_axis3_override": true, + "native_axis_dynamics_input": true, + "no_native_xyzac_speed_override": true, + "native_acceleration_upper_bound": true, + "model_uses_visual_configuration": true, + "machine_path_color_configured": true + }, + "sources": [ + { + "path": "web/workspace/machine-catalog.json", + "sha256": "b71a4cf6f5517710c8cc7ed496b3d5d8d7850c07ab4dc5096548407f93dd9130" + }, + { + "path": "web/workspace/simulation-resources.json", + "sha256": "872a0e52dc4f8725d623ad47f3cbb446b6cb257793ce78ca1f8f0bcf1ee0966b" + }, + { + "path": "web/workspace/web-settings.json", + "sha256": "c9f7da5ef8ec0d17ee2e9e9f55e71acc051eb4afe75b85a961a18599f28c8a82" + }, + { + "path": "web/workspace/trt-configuration.mjs", + "sha256": "5ede37c1066545da1825b98ccedd82c22dad7f8f311c8ea0112595a916e6b432" + }, + { + "path": "web/worker/sim-session.mjs", + "sha256": "07c40bb0e8603291cdea245be03c8afc7d9b6582452433ed4f5eac413add988f" + }, + { + "path": "web/worker/wasm-linuxcnc-tp.mjs", + "sha256": "50616c4d75e3693f06e0095c9df76671f7af9d417e351a68796bdba92dcbe5f7" + }, + { + "path": "wasm-core/src/sim_linuxcnc_tp.c", + "sha256": "2db45da43cd7951a172d118c3dd346a2d5182eae29f0bbe2abd9b9d5a645da26" + }, + { + "path": "web/worker/wasm-xyzac-kinematics.mjs", + "sha256": "7453c58a220b31a1c74ff2e2f9f3d6b7d16cc84973b9ea5be17d06fa179af37f" + }, + { + "path": "web/workspace/sim-case-loader.mjs", + "sha256": "d6399f2fcb053fc625933d72118b82e7f6eff73df7dc02a606fe45f238b06207" + }, + { + "path": "web/react/src/xyzac-machine-model.ts", + "sha256": "1d22af1384e4e26adad8bed122fabd17ecfb046d0320c920ca1c7a385c40f5b6" + }, + { + "path": "web/react/src/XyzacMachineView.tsx", + "sha256": "d0f42c020a8da8708323fc778d206460fa1ea02d114d5df2a3c2eaf85f138f3a" + } + ], + "wasm_sha256": "213b55e64b45c14df5c40e59827fa8bb4903135cd788664bb08263f08a49738f", + "retained": [ + "mathematical constants and tolerances", + "ABI layouts, enums, and capacities", + "source model mesh topology", + "CSS/icon assets", + "test fixtures and historical evidence", + "upstream LinuxCNC and dependency locks" + ] +} diff --git a/coverage/configuration/parametric-regression.report.json b/coverage/configuration/parametric-regression.report.json new file mode 100644 index 0000000..b490116 --- /dev/null +++ b/coverage/configuration/parametric-regression.report.json @@ -0,0 +1,1679 @@ +{ + "report_id": "trt-parametric-worker-v1", + "status": "verified", + "rows": [ + { + "machine": "xyzac-trt", + "source": "G21 G90 G94\nM428\nG1 X12 Y8 Z6 A20 C30 F600\nM2\n", + "baseline": { + "trace": [ + { + "tick": 11, + "pose": { + "x": 0.24299999999999994, + "y": 0.16199999999999995, + "z": 0.12149999999999997, + "a": 0.4049999999999999, + "b": 0, + "c": 0.6074999999999998, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.24126870738107997, + 0.2348893000862482, + -0.01846045585371492, + 0.4049999999999999, + 0.6074999999999998 + ] + }, + { + "tick": 21, + "pose": { + "x": 0.9678160869523862, + "y": 0.6452107246349241, + "z": 0.4839080434761931, + "a": 1.6130268115873103, + "b": 0, + "c": 2.4195402173809657, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.93971480019559, + 0.961014383566603, + -0.05600343387738782, + 1.6130268115873103, + 2.4195402173809657 + ] + }, + { + "tick": 31, + "pose": { + "x": 1.7360373665497584, + "y": 1.1573582443665056, + "z": 0.8680186832748792, + "a": 2.893395610916264, + "b": 0, + "c": 4.340093416374396, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.6434743220797516, + 1.7702368446872363, + -0.06501105623829595, + 2.893395610916264, + 4.340093416374396 + ] + }, + { + "tick": 41, + "pose": { + "x": 2.5042586461471177, + "y": 1.6695057640980786, + "z": 1.2521293230735588, + "a": 4.173764410245196, + "b": 0, + "c": 6.260646615367795, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.3072612238106966, + 2.6172419942226632, + -0.03964030378401873, + 4.173764410245196, + 6.260646615367795 + ] + }, + { + "tick": 51, + "pose": { + "x": 3.272479925744477, + "y": 2.1816532838296516, + "z": 1.6362399628722386, + "a": 5.45413320957413, + "b": 0, + "c": 8.181199814361195, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.9287174964693956, + 3.498766910981569, + 0.022644811888046235, + 5.45413320957413, + 8.181199814361195 + ] + }, + { + "tick": 61, + "pose": { + "x": 4.040701205341837, + "y": 2.6938008035612246, + "z": 2.0203506026709186, + "a": 6.734502008903062, + "b": 0, + "c": 10.101753013354594, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.5055773344146646, + 4.411338137725343, + 0.12414600455426239, + 6.734502008903062, + 10.101753013354594 + ] + }, + { + "tick": 71, + "pose": { + "x": 4.808922484939196, + "y": 3.2059483232927977, + "z": 2.404461242469598, + "a": 8.014870808231995, + "b": 0, + "c": 12.022306212347992, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.0356714420625925, + 5.351288462561346, + 0.2669161059031424, + 8.014870808231995, + 12.022306212347992 + ] + }, + { + "tick": 81, + "pose": { + "x": 5.577143764536555, + "y": 3.7180958430243707, + "z": 2.7885718822682777, + "a": 9.295239607560926, + "b": 0, + "c": 13.94285941134139, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.516931180101328, + 6.314774667081458, + 0.4527456054216614, + 9.295239607560926, + 13.94285941134139 + ] + }, + { + "tick": 91, + "pose": { + "x": 6.345365044133916, + "y": 4.230243362755944, + "z": 3.172682522066958, + "a": 10.57560840688986, + "b": 0, + "c": 15.863412610334791, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.947392544562939, + 7.297796164821417, + 0.6831503990098113, + 10.57560840688986, + 15.863412610334791 + ] + }, + { + "tick": 101, + "pose": { + "x": 7.113586323731275, + "y": 4.742390882487517, + "z": 3.5567931618656377, + "a": 11.855977206218792, + "b": 0, + "c": 17.783965809328187, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 5.325199972422579, + 8.296214449818592, + 0.9593608027219727, + 11.855977206218792, + 17.783965809328187 + ] + }, + { + "tick": 111, + "pose": { + "x": 7.881807603328635, + "y": 5.25453840221909, + "z": 3.9409038016643176, + "a": 13.136346005547727, + "b": 0, + "c": 19.70451900832159, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 5.648609967652907, + 9.305773271567634, + 1.2823118828214888, + 13.136346005547727, + 19.70451900832159 + ] + }, + { + "tick": 121, + "pose": { + "x": 8.650028882925994, + "y": 5.766685921950663, + "z": 4.325014441462997, + "a": 14.416714804876658, + "b": 0, + "c": 21.62507220731499, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 5.915994541928214, + 10.322119449528216, + 1.652635147912715, + 14.416714804876658, + 21.62507220731499 + ] + }, + { + "tick": 131, + "pose": { + "x": 9.418250162523353, + "y": 6.278833441682236, + "z": 4.709125081261677, + "a": 15.697083604205591, + "b": 0, + "c": 23.54562540630839, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.125844464447852, + 11.340824237539714, + 2.0706516433030995, + 15.697083604205591, + 23.54562540630839 + ] + }, + { + "tick": 141, + "pose": { + "x": 10.186471442120713, + "y": 6.790980961413809, + "z": 5.093235721060356, + "a": 16.977452403534524, + "b": 0, + "c": 25.466178605301785, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.27677231563149, + 12.357405146054923, + 2.5363664819692477, + 16.977452403534524, + 25.466178605301785 + ] + }, + { + "tick": 151, + "pose": { + "x": 10.954692721718073, + "y": 7.303128481145382, + "z": 5.477346360859037, + "a": 18.257821202863454, + "b": 0, + "c": 27.386731804295184, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.367515339729723, + 13.367348128029015, + 3.049464840576775, + 18.257821202863454, + 27.386731804295184 + ] + }, + { + "tick": 161, + "pose": { + "x": 11.69947693080819, + "y": 7.79965128720546, + "z": 5.849738465404095, + "a": 19.49912821801365, + "b": 0, + "c": 29.248692327020475, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.396957397127961, + 14.335867874536444, + 3.5915467763450657, + 19.49912821801365, + 29.248692327020475 + ] + }, + { + "tick": 171, + "pose": { + "x": 11.999999772197402, + "y": 7.999999848131601, + "z": 5.999999886098701, + "a": 19.999999620329007, + "b": 0, + "c": 29.99999943049351, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.3923048525679045, + 14.722765040787896, + 3.822532394371681, + 19.999999620329007, + 29.99999943049351 + ] + }, + { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 20, + "b": 0, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.392304845413264, + 14.722765333094944, + 3.822532572105194, + 20, + 30 + ] + } + ], + "offsets": { + "tool_offset": 0, + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10 + }, + "final": { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 20, + "b": 0, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.392304845413264, + 14.722765333094944, + 3.822532572105194, + 20, + 30 + ] + }, + "preview_hash": "fnv1a64:0740b3272128b1bb", + "trace_hash": "fnv1a64:4d88635666d790c0", + "model_samples": 4 + }, + "modified": { + "trace": [ + { + "tick": 11, + "pose": { + "x": 0.24299999999999994, + "y": 0.16199999999999995, + "z": 0.12149999999999997, + "a": 0.4049999999999999, + "b": 0, + "c": 0.6074999999999998, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.16755586117431243, + 0.13170582267194675, + 0.0656595683305703, + 0.4049999999999999, + 0.6074999999999998 + ] + }, + { + "tick": 21, + "pose": { + "x": 0.9678160869523862, + "y": 0.6452107246349241, + "z": 0.4839080434761931, + "a": 1.6130268115873103, + "b": 0, + "c": 2.4195402173809657, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.6522236312541274, + 0.5420752903639041, + 0.2705164541020988, + 1.6130268115873103, + 2.4195402173809657 + ] + }, + { + "tick": 31, + "pose": { + "x": 1.7360373665497584, + "y": 1.1573582443665056, + "z": 0.8680186832748792, + "a": 2.893395610916264, + "b": 0, + "c": 4.340093416374396, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.1395470174778106, + 1.004193514160768, + 0.5040548484118901, + 2.893395610916264, + 4.340093416374396 + ] + }, + { + "tick": 41, + "pose": { + "x": 2.5042586461471177, + "y": 1.6695057640980786, + "z": 1.2521293230735588, + "a": 4.173764410245196, + "b": 0, + "c": 6.260646615367795, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.5975752904201315, + 1.492137380538665, + 0.7566149649412672, + 4.173764410245196, + 6.260646615367795 + ] + }, + { + "tick": 51, + "pose": { + "x": 3.272479925744477, + "y": 2.1816532838296516, + "z": 1.6362399628722386, + "a": 5.45413320957413, + "b": 0, + "c": 8.181199814361195, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.024181607615926, + 2.0035336819911826, + 1.0302001299965937, + 5.45413320957413, + 8.181199814361195 + ] + }, + { + "tick": 61, + "pose": { + "x": 4.040701205341837, + "y": 2.6938008035612246, + "z": 2.0203506026709186, + "a": 6.734502008903062, + "b": 0, + "c": 10.101753013354594, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.4173190741618305, + 2.5358255301257557, + 1.3266289219701726, + 6.734502008903062, + 10.101753013354594 + ] + }, + { + "tick": 71, + "pose": { + "x": 4.808922484939196, + "y": 3.2059483232927977, + "z": 2.404461242469598, + "a": 8.014870808231995, + "b": 0, + "c": 12.022306212347992, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.7750248035530722, + 3.086286337930197, + 1.6475219419534017, + 8.014870808231995, + 12.022306212347992 + ] + }, + { + "tick": 81, + "pose": { + "x": 5.577143764536555, + "y": 3.7180958430243707, + "z": 2.7885718822682777, + "a": 9.295239607560926, + "b": 0, + "c": 13.94285941134139, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.0954238320010283, + 3.6520346970672257, + 1.9942895736047923, + 9.295239607560926, + 13.94285941134139 + ] + }, + { + "tick": 91, + "pose": { + "x": 6.345365044133916, + "y": 4.230243362755944, + "z": 3.172682522066958, + "a": 10.57560840688986, + "b": 0, + "c": 15.863412610334791, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.37673287991381, + 4.230050082705562, + 2.36812078991586, + 10.57560840688986, + 15.863412610334791 + ] + }, + { + "tick": 101, + "pose": { + "x": 7.113586323731275, + "y": 4.742390882487517, + "z": 3.5567931618656377, + "a": 11.855977206218792, + "b": 0, + "c": 17.783965809328187, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.6172639544545424, + 4.817189314792596, + 2.7699730600405736, + 11.855977206218792, + 17.783965809328187 + ] + }, + { + "tick": 111, + "pose": { + "x": 7.881807603328635, + "y": 5.25453840221909, + "z": 3.9409038016643176, + "a": 13.136346005547727, + "b": 0, + "c": 19.70451900832159, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.8154277873334177, + 5.410203701361359, + 3.200563404656565, + 13.136346005547727, + 19.70451900832159 + ] + }, + { + "tick": 121, + "pose": { + "x": 8.650028882925994, + "y": 5.766685921950663, + "z": 4.325014441462997, + "a": 14.416714804876658, + "b": 0, + "c": 21.62507220731499, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.9697371022394945, + 6.005756786456001, + 3.6603606434267615, + 14.416714804876658, + 21.62507220731499 + ] + }, + { + "tick": 131, + "pose": { + "x": 9.418250162523353, + "y": 6.278833441682236, + "z": 4.709125081261677, + "a": 15.697083604205591, + "b": 0, + "c": 23.54562540630839, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.07880970657646, + 6.600442622568249, + 4.149578873048714, + 15.697083604205591, + 23.54562540630839 + ] + }, + { + "tick": 141, + "pose": { + "x": 10.186471442120713, + "y": 6.790980961413809, + "z": 5.093235721060356, + "a": 16.977452403534524, + "b": 0, + "c": 25.466178605301785, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.1413714024325605, + 7.190804485112055, + 4.668172209134653, + 16.977452403534524, + 25.466178605301785 + ] + }, + { + "tick": 151, + "pose": { + "x": 10.954692721718073, + "y": 7.303128481145382, + "z": 5.477346360859037, + "a": 18.257821202863454, + "b": 0, + "c": 27.386731804295184, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.156258711988499, + 7.7733539444356285, + 5.215830819780104, + 18.257821202863454, + 27.386731804295184 + ] + }, + { + "tick": 161, + "pose": { + "x": 11.69947693080819, + "y": 7.79965128720546, + "z": 5.849738465404095, + "a": 19.49912821801365, + "b": 0, + "c": 29.248692327020475, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.12418367088165, + 8.327364076287035, + 5.773987247691979, + 19.49912821801365, + 29.248692327020475 + ] + }, + { + "tick": 171, + "pose": { + "x": 11.999999772197402, + "y": 7.999999848131601, + "z": 5.999999886098701, + "a": 19.999999620329007, + "b": 0, + "c": 29.99999943049351, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.098076234035635, + 8.547175103891929, + 6.006622015702936, + 19.999999620329007, + 29.99999943049351 + ] + }, + { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 20, + "b": 0, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.098076211353317, + 8.54717526960785, + 6.0066221936361055, + 20, + 30 + ] + } + ], + "offsets": { + "tool_offset": 0, + "x_rot_point": 9, + "y_rot_point": -7, + "z_rot_point": 13, + "x_offset": 0, + "y_offset": 15, + "z_offset": -4 + }, + "final": { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 20, + "b": 0, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.098076211353317, + 8.54717526960785, + 6.0066221936361055, + 20, + 30 + ] + }, + "preview_hash": "fnv1a64:0740b3272128b1bb", + "trace_hash": "fnv1a64:d9e843abc9867e80", + "model_samples": 4 + }, + "repeat_hash": "fnv1a64:d9e843abc9867e80", + "first_divergent_tick": 11 + }, + { + "machine": "xyzbc-trt", + "source": "G21 G90 G94\nM428\nG1 X12 Y8 Z6 B20 C30 F600\nM2\n", + "baseline": { + "trace": [ + { + "tick": 11, + "pose": { + "x": 0.24299999999999994, + "y": 0.16199999999999995, + "z": 0.12149999999999997, + "a": 0, + "b": 0.4049999999999999, + "c": 0.6074999999999998, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.3476497281321649, + 0.16456734438722265, + -0.021953676275151324, + 0.4049999999999999, + 0.6074999999999998 + ] + }, + { + "tick": 21, + "pose": { + "x": 0.9678160869523862, + "y": 0.6452107246349241, + "z": 0.4839080434761931, + "a": 0, + "b": 1.6130268115873103, + "c": 2.4195402173809657, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.3672723837031882, + 0.6854932177936666, + -0.11165774530313222, + 1.6130268115873103, + 2.4195402173809657 + ] + }, + { + "tick": 31, + "pose": { + "x": 1.7360373665497584, + "y": 1.1573582443665056, + "z": 0.8680186832748792, + "a": 0, + "b": 2.893395610916264, + "c": 4.340093416374396, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.416865813571949, + 1.2854166626135826, + -0.2447255274733866, + 2.893395610916264, + 4.340093416374396 + ] + }, + { + "tick": 41, + "pose": { + "x": 2.5042586461471177, + "y": 1.6695057640980786, + "z": 1.2521293230735588, + "a": 0, + "b": 4.173764410245196, + "c": 6.260646615367795, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.430955023990105, + 1.932642364292698, + -0.41452923265797814, + 4.173764410245196, + 6.260646615367795 + ] + }, + { + "tick": 51, + "pose": { + "x": 3.272479925744477, + "y": 2.1816532838296516, + "z": 1.6362399628722386, + "a": 0, + "b": 5.45413320957413, + "c": 8.181199814361195, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.4061661594334005, + 2.6251380422978365, + -0.6184277795055717, + 5.45413320957413, + 8.181199814361195 + ] + }, + { + "tick": 61, + "pose": { + "x": 4.040701205341837, + "y": 2.6938008035612246, + "z": 2.0203506026709186, + "a": 0, + "b": 6.734502008903062, + "c": 10.101753013354594, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 5.339349568752038, + 3.36076725652882, + -0.8535564930766153, + 6.734502008903062, + 10.101753013354594 + ] + }, + { + "tick": 71, + "pose": { + "x": 4.808922484939196, + "y": 3.2059483232927977, + "z": 2.404461242469598, + "a": 0, + "b": 8.014870808231995, + "c": 12.022306212347992, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 6.227594962679156, + 4.137293333750716, + -1.1168434682505861, + 8.014870808231995, + 12.022306212347992 + ] + }, + { + "tick": 81, + "pose": { + "x": 5.577143764536555, + "y": 3.7180958430243707, + "z": 2.7885718822682777, + "a": 0, + "b": 9.295239607560926, + "c": 13.94285941134139, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 7.068245433572351, + 4.952383464802834, + -1.4050269125555133, + 9.295239607560926, + 13.94285941134139 + ] + }, + { + "tick": 91, + "pose": { + "x": 6.345365044133916, + "y": 4.230243362755944, + "z": 3.172682522066958, + "a": 0, + "b": 10.57560840688986, + "c": 15.863412610334791, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 7.85891027564163, + 5.803612966201346, + -1.7146733922683808, + 10.57560840688986, + 15.863412610334791 + ] + }, + { + "tick": 101, + "pose": { + "x": 7.113586323731275, + "y": 4.742390882487517, + "z": 3.5567931618656377, + "a": 0, + "b": 11.855977206218792, + "c": 17.783965809328187, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 8.59747654889259, + 6.688469699504358, + -2.042196901828067, + 11.855977206218792, + 17.783965809328187 + ] + }, + { + "tick": 111, + "pose": { + "x": 7.881807603328635, + "y": 5.25453840221909, + "z": 3.9409038016643176, + "a": 0, + "b": 13.136346005547727, + "c": 19.70451900832159, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 9.282119335219008, + 7.604358641569745, + -2.38387867311514, + 13.136346005547727, + 19.70451900832159 + ] + }, + { + "tick": 121, + "pose": { + "x": 8.650028882925994, + "y": 5.766685921950663, + "z": 4.325014441462997, + "a": 0, + "b": 14.416714804876658, + "c": 21.62507220731499, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 9.91131064049511, + 8.548606598607371, + -2.735887637998001, + 14.416714804876658, + 21.62507220731499 + ] + }, + { + "tick": 131, + "pose": { + "x": 9.418250162523353, + "y": 6.278833441682236, + "z": 4.709125081261677, + "a": 0, + "b": 15.697083604205591, + "c": 23.54562540630839, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 10.483826902123656, + 9.518467056708868, + -3.0943014547360406, + 15.697083604205591, + 23.54562540630839 + ] + }, + { + "tick": 141, + "pose": { + "x": 10.186471442120713, + "y": 6.790980961413809, + "z": 5.093235721060356, + "a": 0, + "b": 16.977452403534524, + "c": 25.466178605301785, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 10.998755067268661, + 10.511125161330149, + -3.455128006377935, + 16.977452403534524, + 25.466178605301785 + ] + }, + { + "tick": 151, + "pose": { + "x": 10.954692721718073, + "y": 7.303128481145382, + "z": 5.477346360859037, + "a": 0, + "b": 18.257821202863454, + "c": 27.386731804295184, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.455497212920392, + 11.523702818004569, + -3.8143272772073935, + 18.257821202863454, + 27.386731804295184 + ] + }, + { + "tick": 161, + "pose": { + "x": 11.69947693080819, + "y": 7.79965128720546, + "z": 5.849738465404095, + "a": 0, + "b": 19.49912821801365, + "c": 29.248692327020475, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.842488019118306, + 12.521631551672836, + -4.157172386602891, + 19.49912821801365, + 29.248692327020475 + ] + }, + { + "tick": 171, + "pose": { + "x": 11.999999772197402, + "y": 7.999999848131601, + "z": 5.999999886098701, + "a": 0, + "b": 19.999999620329007, + "c": 29.99999943049351, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.983077015423682, + 12.928202921314321, + -4.293154746962006, + 19.999999620329007, + 29.99999943049351 + ] + }, + { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 20, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.983077118606069, + 12.928203230275509, + -4.293154849418908, + 20, + 30 + ] + } + ], + "offsets": { + "tool_offset": 0, + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15 + }, + "final": { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 20, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.983077118606069, + 12.928203230275509, + -4.293154849418908, + 20, + 30 + ] + }, + "preview_hash": "fnv1a64:2595e1a5d1cf33eb", + "trace_hash": "fnv1a64:64ed08ff910588cc", + "model_samples": 4 + }, + "modified": { + "trace": [ + { + "tick": 11, + "pose": { + "x": 0.24299999999999994, + "y": 0.16199999999999995, + "z": 0.12149999999999997, + "a": 0, + "b": 0.4049999999999999, + "c": 0.6074999999999998, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.10539335548359574, + 0.06874978581953695, + 0.2901820234818224, + 0.4049999999999999, + 0.6074999999999998 + ] + }, + { + "tick": 21, + "pose": { + "x": 0.9678160869523862, + "y": 0.6452107246349241, + "z": 0.4839080434761931, + "a": 0, + "b": 1.6130268115873103, + "c": 2.4195402173809657, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.42175669269525784, + 0.29930511312561325, + 1.1444970804870227, + 1.6130268115873103, + 2.4195402173809657 + ] + }, + { + "tick": 31, + "pose": { + "x": 1.7360373665497584, + "y": 1.1573582443665056, + "z": 0.8680186832748792, + "a": 0, + "b": 2.893395610916264, + "c": 4.340093416374396, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 0.7582052460566509, + 0.5842551102038751, + 2.032331285340442, + 2.893395610916264, + 4.340093416374396 + ] + }, + { + "tick": 41, + "pose": { + "x": 2.5042586461471177, + "y": 1.6695057640980786, + "z": 1.2521293230735588, + "a": 0, + "b": 4.173764410245196, + "c": 6.260646615367795, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.093086758049374, + 0.9094307221749949, + 2.9031599479305346, + 4.173764410245196, + 6.260646615367795 + ] + }, + { + "tick": 51, + "pose": { + "x": 3.272479925744477, + "y": 2.1816532838296516, + "z": 1.6362399628722386, + "a": 0, + "b": 5.45413320957413, + "c": 8.181199814361195, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.4237576350612429, + 1.273161486513839, + 3.758355546301283, + 5.45413320957413, + 8.181199814361195 + ] + }, + { + "tick": 61, + "pose": { + "x": 4.040701205341837, + "y": 2.6938008035612246, + "z": 2.0203506026709186, + "a": 0, + "b": 6.734502008903062, + "c": 10.101753013354594, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 1.7477400166770884, + 1.6736803251231542, + 4.599483292750644, + 6.734502008903062, + 10.101753013354594 + ] + }, + { + "tick": 71, + "pose": { + "x": 4.808922484939196, + "y": 3.2059483232927977, + "z": 2.404461242469598, + "a": 0, + "b": 8.014870808231995, + "c": 12.022306212347992, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.062735464363172, + 2.109127055793646, + 5.428287765599858, + 8.014870808231995, + 12.022306212347992 + ] + }, + { + "tick": 81, + "pose": { + "x": 5.577143764536555, + "y": 3.7180958430243707, + "z": 2.7885718822682777, + "a": 0, + "b": 9.295239607560926, + "c": 13.94285941134139, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.3666376749349958, + 2.577552066431478, + 6.246678635197696, + 9.295239607560926, + 13.94285941134139 + ] + }, + { + "tick": 91, + "pose": { + "x": 6.345365044133916, + "y": 4.230243362755944, + "z": 3.172682522066958, + "a": 0, + "b": 10.57560840688986, + "c": 15.863412610334791, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.657544160842008, + 3.0769201461452305, + 7.0567155512784865, + 10.57560840688986, + 15.863412610334791 + ] + }, + { + "tick": 101, + "pose": { + "x": 7.113586323731275, + "y": 4.742390882487517, + "z": 3.5567931618656377, + "a": 0, + "b": 11.855977206218792, + "c": 17.783965809328187, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 2.9337668437710924, + 3.6051144670449116, + 7.860592262404443, + 11.855977206218792, + 17.783965809328187 + ] + }, + { + "tick": 111, + "pose": { + "x": 7.881807603328635, + "y": 5.25453840221909, + "z": 3.9409038016643176, + "a": 0, + "b": 13.136346005547727, + "c": 19.70451900832159, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.1938415127596294, + 4.159940710374837, + 8.660620041543572, + 13.136346005547727, + 19.70451900832159 + ] + }, + { + "tick": 121, + "pose": { + "x": 8.650028882925994, + "y": 5.766685921950663, + "z": 4.325014441462997, + "a": 0, + "b": 14.416714804876658, + "c": 21.62507220731499, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.436536102899666, + 4.739131330381026, + 9.459210494851341, + 14.416714804876658, + 21.62507220731499 + ] + }, + { + "tick": 131, + "pose": { + "x": 9.418250162523353, + "y": 6.278833441682236, + "z": 4.709125081261677, + "a": 0, + "b": 15.697083604205591, + "c": 23.54562540630839, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.6608577557883777, + 5.340349949102061, + 10.258857833425182, + 15.697083604205591, + 23.54562540630839 + ] + }, + { + "tick": 141, + "pose": { + "x": 10.186471442120713, + "y": 6.790980961413809, + "z": 5.093235721060356, + "a": 0, + "b": 16.977452403534524, + "c": 25.466178605301785, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 3.866058628115681, + 5.9611958750705565, + 11.062120690174867, + 16.977452403534524, + 25.466178605301785 + ] + }, + { + "tick": 151, + "pose": { + "x": 10.954692721718073, + "y": 7.303128481145382, + "z": 5.477346360859037, + "a": 0, + "b": 18.257821202863454, + "c": 27.386731804295184, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.051640420157767, + 6.599208738720886, + 11.871603565989233, + 18.257821202863454, + 27.386731804295184 + ] + }, + { + "tick": 161, + "pose": { + "x": 11.69947693080819, + "y": 7.79965128720546, + "z": 5.849738465404095, + "a": 0, + "b": 19.49912821801365, + "c": 29.248692327020475, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.212596186050696, + 7.2317697089136, + 12.664815519082325, + 19.49912821801365, + 29.248692327020475 + ] + }, + { + "tick": 171, + "pose": { + "x": 11.999999772197402, + "y": 7.999999848131601, + "z": 5.999999886098701, + "a": 0, + "b": 19.999999620329007, + "c": 29.99999943049351, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.272248602320978, + 7.490380860067354, + 12.987780718775609, + 19.999999620329007, + 29.99999943049351 + ] + }, + { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 20, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.272248646388174, + 7.49038105676658, + 12.98778096429175, + 20, + 30 + ] + } + ], + "offsets": { + "tool_offset": 0, + "x_rot_point": 9, + "y_rot_point": -7, + "z_rot_point": 13, + "x_offset": 15, + "y_offset": 0, + "z_offset": -4 + }, + "final": { + "tick": 181, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 20, + "c": 30, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 4.272248646388174, + 7.49038105676658, + 12.98778096429175, + 20, + 30 + ] + }, + "preview_hash": "fnv1a64:2595e1a5d1cf33eb", + "trace_hash": "fnv1a64:5b9defc022c0dd0f", + "model_samples": 4 + }, + "repeat_hash": "fnv1a64:5b9defc022c0dd0f", + "first_divergent_tick": 11 + } + ], + "state_hash": "fnv1a64:977b2134a633e5c1" +} diff --git a/coverage/configuration/rtcp-jog-browser.report.json b/coverage/configuration/rtcp-jog-browser.report.json new file mode 100644 index 0000000..20d2b68 --- /dev/null +++ b/coverage/configuration/rtcp-jog-browser.report.json @@ -0,0 +1,657 @@ +{ + "report_id": "trt-rtcp-jog-browser-v1", + "status": "verified", + "machines": [ + { + "coordinates": "XYZAC", + "rows": [ + { + "axis": "a", + "before": { + "joints": [ + 5.066649999999981, + 4.066650000000003, + 0, + 0, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 64, + "rendered_tick": 64, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 5.066649999999981, + 4.841821345591999, + -1.1400746004481288, + 4.199999999999992, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 88, + "rendered_tick": 88, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 1.3877787807814457e-17 + }, + "stopped": { + "joints": [ + 5.066649999999981, + 5.19921919111046, + -1.6107075438884007, + 6.000000000000007, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 99, + "rendered_tick": 99, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 2.2247786310271853e-16 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 5.066649999999981, + 5.19921919111046, + -1.6107075438884007, + 6.000000000000007, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 99, + "rendered_tick": 99, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 4.266433017896709, + 6.027611642587745, + -1.5236399887760221, + 6.000000000000007, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 143, + "rendered_tick": 136, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 2.2473876566261383e-16 + }, + "stopped": { + "joints": [ + 4.110427463793882, + 6.158484995689042, + -1.5098846450816552, + 6.000000000000007, + 11.999999999999938 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 154, + "rendered_tick": 154, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 6.938893903907228e-18 + } + }, + { + "axis": "a", + "before": { + "joints": [ + 4.110427463793882, + 6.158484995689042, + -1.5098846450816552, + 6.000000000000007, + 11.999999999999938 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 154, + "rendered_tick": 154, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 4.110427463793882, + 9.152485181578204, + -4.3665028598393185, + 19.19999999999992, + 11.999999999999938 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 208, + "rendered_tick": 202, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 1.3877787807814457e-17 + }, + "stopped": { + "joints": [ + 4.110427463793882, + 9.532273556710663, + -4.645534652791378, + 20.69999999999989, + 11.999999999999938 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 218, + "rendered_tick": 218, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 4.445768331151997e-16 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 4.110427463793882, + 9.532273556710663, + -4.645534652791378, + 20.69999999999989, + 11.999999999999938 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 218, + "rendered_tick": 218, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 3.8414816180013283, + 9.727059259543312, + -4.571931269146798, + 20.69999999999989, + 14.999999999999936 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 238, + "rendered_tick": 238, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 1.3877787807814457e-17 + }, + "stopped": { + "joints": [ + 3.6750116751266004, + 9.837515099025248, + -4.53019348546481, + 20.69999999999989, + 16.799999999999933 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 249, + "rendered_tick": 249, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 4.443601777255714e-16 + } + } + ] + }, + { + "coordinates": "XYZBC", + "rows": [ + { + "axis": "b", + "before": { + "joints": [ + 5.0666499999999814, + 3.666650000000003, + 0, + 0, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 311, + "rendered_tick": 311, + "tool_tip": [ + 0, + 1.5, + 0 + ] + }, + "moving": { + "joints": [ + 6.166264319539167, + 3.666650000000003, + -2.0129466859294602, + 4.499999999999995, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 336, + "rendered_tick": 330, + "tool_tip": [ + 0, + 1.4879917702595602, + 0 + ], + "tcp_error": 2.7755575615628914e-17 + }, + "stopped": { + "joints": [ + 6.56128605042624, + 3.666650000000003, + -2.8412572372826563, + 6.30000000000001, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 347, + "rendered_tick": 347, + "tool_tip": [ + 0, + 1.4715874276271734, + 0 + ], + "tcp_error": 5.551115123125783e-17 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 6.56128605042624, + 3.666650000000003, + -2.8412572372826563, + 6.30000000000001, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 347, + "rendered_tick": 347, + "tool_tip": [ + 0, + 1.4715874276271734, + 0 + ] + }, + "moving": { + "joints": [ + 6.259816863284065, + 4.05287171910206, + -2.807974729167359, + 6.30000000000001, + 4.499999999999995 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 372, + "rendered_tick": 372, + "tool_tip": [ + 0, + 1.4719202527083264, + 0 + ], + "tcp_error": 6.938893903907228e-18 + }, + "stopped": { + "joints": [ + 6.130945648374414, + 4.200492284609181, + -2.793747214585462, + 6.30000000000001, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 383, + "rendered_tick": 383, + "tool_tip": [ + 0, + 1.4720625278541453, + 0 + ], + "tcp_error": 2.2887833992611187e-16 + } + }, + { + "axis": "b", + "before": { + "joints": [ + 6.130945648374414, + 4.200492284609181, + -2.793747214585462, + 6.30000000000001, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 383, + "rendered_tick": 383, + "tool_tip": [ + 0, + 1.4720625278541453, + 0 + ] + }, + "moving": { + "joints": [ + 7.840448318636497, + 4.200492284609181, + -7.46817651846061, + 16.199999999999992, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 426, + "rendered_tick": 420, + "tool_tip": [ + 0, + 1.4340259659607903, + 0 + ], + "tcp_error": 0 + }, + "stopped": { + "joints": [ + 8.063291030797899, + 4.200492284609181, + -8.346382633448467, + 17.999999999999954, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 437, + "rendered_tick": 437, + "tool_tip": [ + 0, + 1.4165361736655155, + 0 + ], + "tcp_error": 2.2887833992611187e-16 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 8.063291030797899, + 4.200492284609181, + -8.346382633448467, + 17.999999999999954, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 437, + "rendered_tick": 437, + "tool_tip": [ + 0, + 1.4165361736655155, + 0 + ] + }, + "moving": { + "joints": [ + 7.5982993210492396, + 4.6836006491845, + -8.195297668366113, + 17.999999999999954, + 12.60000000000002 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 468, + "rendered_tick": 461, + "tool_tip": [ + 0, + 1.41752527642095, + 0 + ], + "tcp_error": 2.2247786310271853e-16 + }, + "stopped": { + "joints": [ + 7.456439115506413, + 4.811480064556107, + -8.1492044934736, + 17.999999999999954, + 14.40000000000003 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 479, + "rendered_tick": 474, + "tool_tip": [ + 0, + 1.418507955065264, + 0 + ], + "tcp_error": 1.5515838457795457e-17 + } + } + ] + } + ], + "browser_errors": [] +} diff --git a/coverage/configuration/rtcp-jog-regression.report.json b/coverage/configuration/rtcp-jog-regression.report.json new file mode 100644 index 0000000..0855982 --- /dev/null +++ b/coverage/configuration/rtcp-jog-regression.report.json @@ -0,0 +1,3589 @@ +{ + "report_id": "trt-rtcp-jog-v1", + "status": "verified", + "rows": [ + { + "machine": "xyzac-trt", + "samples": [ + { + "phase": "M428", + "tick": 164, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8, + 6, + 0, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-0", + "tick": 165, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.0075000000000000015, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.002225337626763, + 5.999345647177577, + 0.0075000000000000015, + 0 + ], + "error": 4.440892098500626e-16 + }, + { + "phase": "a-continuous-1", + "tick": 166, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.03, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.00890186416763, + 5.9973843365648385, + 0.03, + 0 + ], + "error": 0 + }, + { + "phase": "a-continuous-2", + "tick": 167, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.06749999999999999, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.020031118316258, + 5.994121312397244, + 0.06749999999999999, + 0 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "a-continuous-3", + "tick": 168, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.12000000000000002, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.035615656933835, + 5.989565317302457, + 0.12000000000000002, + 0 + ], + "error": 0 + }, + { + "phase": "a-continuous-4", + "tick": 169, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.18750000000000008, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.055659043602471, + 5.983728595638528, + 0.18750000000000008, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-5", + "tick": 170, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.2700000000000002, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.08016583259137, + 5.976626898138001, + 0.2700000000000002, + 0 + ], + "error": 4.440892098500626e-16 + }, + { + "phase": "a-continuous-6", + "tick": 171, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.3675000000000004, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.109141548227306, + 5.968279487828493, + 0.3675000000000004, + 0 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "a-continuous-7", + "tick": 172, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.4800000000000005, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.142592659658698, + 5.958709147191975, + 0.4800000000000005, + 0 + ], + "error": 4.440892098500626e-16 + }, + { + "phase": "a-continuous-8", + "tick": 173, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6075000000000007, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.180526551000199, + 5.9479421865164355, + 0.6075000000000007, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-9", + "tick": 174, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.7500000000000009, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.222951486842817, + 5.936008453385156, + 0.7500000000000009, + 0 + ], + "error": 5.551115123125783e-17 + }, + { + "phase": "a-continuous-10", + "tick": 175, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.9075000000000011, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.269876573112445, + 5.922941343240353, + 0.9075000000000011, + 0 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "a-continuous-11", + "tick": 176, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.0800000000000012, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.321311713257895, + 5.908777810949207, + 1.0800000000000012, + 0 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "a-continuous-12", + "tick": 177, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.2675000000000012, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.377267559747976, + 5.893558383291939, + 1.2675000000000012, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-13", + "tick": 178, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.470000000000001, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.43775546085554, + 5.8773271722827545, + 1.470000000000001, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-14", + "tick": 179, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.6875000000000009, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.502787402705268, + 5.860131889225823, + 1.6875000000000009, + 0 + ], + "error": 4.449557262054371e-16 + }, + { + "phase": "a-released", + "tick": 179, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.6875000000000009, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.502787402705268, + 5.860131889225823, + 1.6875000000000009, + 0 + ], + "error": 4.449557262054371e-16 + }, + { + "phase": "a-idle", + "tick": 180, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.6875000000000009, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.502787402705268, + 5.860131889225823, + 1.6875000000000009, + 0 + ], + "error": 4.449557262054371e-16 + }, + { + "phase": "a-increment", + "tick": 197, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8.20434040541591, + 5.941229556240771, + 0.6875000000000009, + 0 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-continuous-0", + "tick": 198, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.0075, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.998036478895019, + 8.20473294772533, + 5.9412342666364175, + 0.6875000000000009, + 0.0075 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "c-continuous-1", + "tick": 199, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.03, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.992145607491388, + 8.20590903257168, + 5.9412483793188215, + 0.6875000000000009, + 0.03 + ], + "error": 5.721958498152797e-17 + }, + { + "phase": "c-continuous-2", + "tick": 200, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.06749999999999999, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.982326463541861, + 8.207864033306459, + 5.9412718387695165, + 0.6875000000000009, + 0.06749999999999999 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-3", + "tick": 201, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.12, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.968577516697847, + 8.210590237509955, + 5.941304552441675, + 0.6875000000000009, + 0.12 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "c-continuous-4", + "tick": 202, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.18750000000000008, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.95089663860719, + 8.214076844995875, + 5.941346390736136, + 0.6875000000000009, + 0.18750000000000008 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "c-continuous-5", + "tick": 203, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.2700000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.929281117056187, + 8.218309965043737, + 5.941397186968224, + 0.6875000000000009, + 0.2700000000000002 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-continuous-6", + "tick": 204, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.3675000000000003, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.903727674160862, + 8.223272612884942, + 5.9414567373255665, + 0.6875000000000009, + 0.3675000000000003 + ], + "error": 4.462523459318059e-16 + }, + { + "phase": "c-continuous-7", + "tick": 205, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.4800000000000005, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.874232488613883, + 8.228944705475879, + 5.941524800817369, + 0.6875000000000009, + 0.4800000000000005 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-continuous-8", + "tick": 206, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.6075000000000007, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.840791221994715, + 8.235303056598847, + 5.941601099215642, + 0.6875000000000009, + 0.6075000000000007 + ], + "error": 4.449557262054371e-16 + }, + { + "phase": "c-continuous-9", + "tick": 207, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.7500000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.803399049151855, + 8.242321371339045, + 5.9416853169889094, + 0.6875000000000009, + 0.7500000000000009 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-10", + "tick": 208, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.9075000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.762050692666904, + 8.249970239993363, + 5.941777101229139, + 0.6875000000000009, + 0.9075000000000012 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-11", + "tick": 209, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.0800000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.716740461411304, + 8.258217131474186, + 5.941876061572557, + 0.6875000000000009, + 1.0800000000000012 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-12", + "tick": 210, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.2675000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.667462293207176, + 8.267026386279003, + 5.941981770115319, + 0.6875000000000009, + 1.2675000000000012 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-13", + "tick": 211, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.470000000000001, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.614209801604428, + 8.27635920910408, + 5.9420937613248554, + 0.6875000000000009, + 1.470000000000001 + ], + "error": 4.462523459318059e-16 + }, + { + "phase": "c-continuous-14", + "tick": 212, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.556976326786616, + 8.286173661188121, + 5.942211531947997, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 4.451720917549152e-16 + }, + { + "phase": "c-released", + "tick": 212, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.556976326786616, + 8.286173661188121, + 5.942211531947997, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 4.451720917549152e-16 + }, + { + "phase": "c-idle", + "tick": 213, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.556976326786616, + 8.286173661188121, + 5.942211531947997, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 4.451720917549152e-16 + }, + { + "phase": "c-increment", + "tick": 230, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6875000000000009, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.239254611819097, + 5.941648516750181, + 0.6875000000000009, + 0.6875000000000009 + ], + "error": 4.477603233993732e-16 + }, + { + "phase": "a-continuous-0", + "tick": 231, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.6950000000000008, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.24148758558735, + 5.941025482783033, + 0.6950000000000008, + 0.6875000000000009 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "a-continuous-1", + "tick": 232, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.7175000000000009, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.248186995954008, + 5.939158134729794, + 0.7175000000000009, + 0.6875000000000009 + ], + "error": 4.451720917549152e-16 + }, + { + "phase": "a-continuous-2", + "tick": 233, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.7550000000000009, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.259354307808827, + 5.936051734774676, + 0.7550000000000009, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "a-continuous-3", + "tick": 234, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.8075000000000009, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.274991954980429, + 5.931715055352591, + 0.8075000000000009, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "a-continuous-4", + "tick": 235, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.875000000000001, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.295103328750798, + 5.926160382326266, + 0.875000000000001, + 0.6875000000000009 + ], + "error": 4.462523459318059e-16 + }, + { + "phase": "a-continuous-5", + "tick": 236, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0.9575000000000011, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.31969276176741, + 5.919403519404693, + 0.9575000000000011, + 0.6875000000000009 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "a-continuous-6", + "tick": 237, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.0550000000000013, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.348765507344947, + 5.911463793773528, + 1.0550000000000013, + 0.6875000000000009 + ], + "error": 4.44522679167363e-16 + }, + { + "phase": "a-continuous-7", + "tick": 238, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.1675000000000013, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.382327714146385, + 5.9023640628993235, + 1.1675000000000013, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "a-continuous-8", + "tick": 239, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.2950000000000017, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.420386396231086, + 5.892130722461324, + 1.2950000000000017, + 0.6875000000000009 + ], + "error": 4.44522679167363e-16 + }, + { + "phase": "a-continuous-9", + "tick": 240, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.4375000000000018, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.46294939845562, + 5.8807937153557575, + 1.4375000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "a-continuous-10", + "tick": 241, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.595000000000002, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.51002535721109, + 5.868386541709221, + 1.595000000000002, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "a-continuous-11", + "tick": 242, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.767500000000002, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.56162365647916, + 5.8549462698289645, + 1.767500000000002, + 0.6875000000000009 + ], + "error": 4.44522679167363e-16 + }, + { + "phase": "a-continuous-12", + "tick": 243, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.955000000000002, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.617754379187257, + 5.840513548009525, + 1.955000000000002, + 0.6875000000000009 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "a-continuous-13", + "tick": 244, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 2.157500000000002, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.678428253842373, + 5.825132617106203, + 2.157500000000002, + 0.6875000000000009 + ], + "error": 4.443059973708341e-16 + }, + { + "phase": "a-continuous-14", + "tick": 245, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 2.3750000000000018, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.743656596421383, + 5.808851323777407, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "a-released", + "tick": 245, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 2.3750000000000018, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.743656596421383, + 5.808851323777407, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "a-idle", + "tick": 246, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 2.3750000000000018, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.743656596421383, + 5.808851323777407, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "a-increment", + "tick": 263, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.819801273527016, + 8.444277944316603, + 5.885753054003896, + 1.3750000000000018, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-0", + "tick": 264, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.6950000000000008, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.817833183377914, + 8.444646820382943, + 5.8857619080938015, + 1.3750000000000018, + 0.6950000000000008 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-1", + "tick": 265, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.7175000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.811928623368642, + 8.44575190324844, + 5.885788433271053, + 1.3750000000000018, + 0.7175000000000009 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-2", + "tick": 266, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.7550000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.802086726836809, + 8.447588556534093, + 5.885832518249174, + 1.3750000000000018, + 0.7550000000000009 + ], + "error": 4.44522679167363e-16 + }, + { + "phase": "c-continuous-3", + "tick": 267, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.8075000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.788306056090821, + 8.450149051683805, + 5.885893977520524, + 1.3750000000000018, + 0.8075000000000009 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-4", + "tick": 268, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.875000000000001, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.770584612530897, + 8.453422566090719, + 5.885972551311308, + 1.3750000000000018, + 0.875000000000001 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-5", + "tick": 269, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 0.9575000000000011, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.748919850823262, + 8.45739518050003, + 5.886067905519255, + 1.3750000000000018, + 0.9575000000000011 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-6", + "tick": 270, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.0550000000000013, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.723308697132284, + 8.462049875714303, + 5.886179631634512, + 1.3750000000000018, + 1.0550000000000013 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-7", + "tick": 271, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.1675000000000013, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.693747571416512, + 8.467366528634642, + 5.886307246644655, + 1.3750000000000018, + 1.1675000000000013 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-8", + "tick": 272, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.2950000000000017, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.660232413795724, + 8.473321907678628, + 5.8864501929246735, + 1.3750000000000018, + 1.2950000000000017 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-9", + "tick": 273, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.4375000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.622758714997243, + 8.479889667623343, + 5.8866078381132, + 1.3750000000000018, + 1.4375000000000018 + ], + "error": 3.925231146709438e-17 + }, + { + "phase": "c-continuous-10", + "tick": 274, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.595000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.581321550890626, + 8.487040343929323, + 5.886779474976279, + 1.3750000000000018, + 1.595000000000002 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "c-continuous-11", + "tick": 275, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.767500000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.535915621120779, + 8.494741346608787, + 5.886964321260166, + 1.3750000000000018, + 1.767500000000002 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-12", + "tick": 276, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.955000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.486535291850082, + 8.502956953708953, + 5.887161519534931, + 1.3750000000000018, + 1.955000000000002 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-13", + "tick": 277, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 2.157500000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.43317464262076, + 8.511648304489007, + 5.887370137030672, + 1.3750000000000018, + 2.157500000000002 + ], + "error": 4.44522679167363e-16 + }, + { + "phase": "c-continuous-14", + "tick": 278, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.37582751734897, + 8.520773392376572, + 5.8875891654684445, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-released", + "tick": 278, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.37582751734897, + 8.520773392376572, + 5.8875891654684445, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-idle", + "tick": 279, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.37582751734897, + 8.520773392376572, + 5.8875891654684445, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-increment", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.639196558834685, + 8.477020542332117, + 5.886538970827971, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "invalid-jog-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.639196558834685, + 8.477020542332117, + 5.886538970827971, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "repeated-stop", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.639196558834685, + 8.477020542332117, + 5.886538970827971, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "abort-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.639196558834685, + 8.477020542332117, + 5.886538970827971, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "power-off-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 1.3750000000000018, + "b": 0, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.639196558834685, + 8.477020542332117, + 5.886538970827971, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.2247786310271853e-16 + } + ], + "identity_before": [ + 12, + 8.000000000000002, + 6, + 1.3750000000000018, + 1.3750000000000018 + ], + "identity_after": [ + 12, + 8.000000000000002, + 6, + 3.3750000000000018, + 1.3750000000000018 + ], + "reused_segment_id": "verified" + }, + { + "machine": "xyzbc-trt", + "samples": [ + { + "phase": "M428", + "tick": 164, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12, + 8, + 6, + 0, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "b-continuous-0", + "tick": 165, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.0075, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.001047000498811, + 8, + 5.99698923850999, + 0.0075, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-continuous-1", + "tick": 166, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.03, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.004185637223166, + 8, + 5.987956132088819, + 0.03, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "b-continuous-2", + "tick": 167, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.06749999999999999, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.009408814781619, + 8, + 5.972898217978826, + 0.06749999999999999, + 0 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-3", + "tick": 168, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.12, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.016704703943478, + 8, + 5.951811401904989, + 0.12, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-continuous-4", + "tick": 169, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.18750000000000008, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.026056736291231, + 8, + 5.924689973563461, + 0.18750000000000008, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "b-continuous-5", + "tick": 170, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.2700000000000002, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.037443596773691, + 8, + 5.89152662831918, + 0.2700000000000002, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-continuous-6", + "tick": 171, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.3675000000000003, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.050839214199659, + 8, + 5.852312495126218, + 0.3675000000000003, + 0 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "b-continuous-7", + "tick": 172, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.4800000000000005, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.066212749723217, + 8, + 5.807037170688121, + 0.4800000000000005, + 0 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-8", + "tick": 173, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6075000000000007, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.083528583383508, + 8, + 5.755688759879201, + 0.6075000000000007, + 0 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "b-continuous-9", + "tick": 174, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.7500000000000009, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.102746298772917, + 8, + 5.6982539224511335, + 0.7500000000000009, + 0 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "b-continuous-10", + "tick": 175, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.9075000000000012, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.123820665919645, + 8, + 5.63471792605251, + 0.9075000000000012, + 0 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-11", + "tick": 176, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.0800000000000012, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.14670162248187, + 8, + 5.565064705592083, + 1.0800000000000012, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-continuous-12", + "tick": 177, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.2675000000000012, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.171334253362684, + 8, + 5.489276928979275, + 1.2675000000000012, + 0 + ], + "error": 2.2633986948989007e-16 + }, + { + "phase": "b-continuous-13", + "tick": 178, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.470000000000001, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.19765876886656, + 8, + 5.407336069278104, + 1.470000000000001, + 0 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "b-continuous-14", + "tick": 179, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.6875000000000009, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.225610481530193, + 8, + 5.319222483313015, + 1.6875000000000009, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-released", + "tick": 179, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.6875000000000009, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.225610481530193, + 8, + 5.319222483313015, + 1.6875000000000009, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-idle", + "tick": 180, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.6875000000000009, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.225610481530193, + 8, + 5.319222483313015, + 1.6875000000000009, + 0 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-increment", + "tick": 197, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.094335062974402, + 8, + 5.723450524155055, + 0.6875000000000009, + 0 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "c-continuous-0", + "tick": 198, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.0075, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.092371683220946, + 8.000392570570105, + 5.723474084151584, + 0.6875000000000009, + 0.0075 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-continuous-1", + "tick": 199, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.03, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.08648123589408, + 8.001568740087485, + 5.723544767837882, + 0.6875000000000009, + 0.03 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-2", + "tick": 200, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.06749999999999999, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.076662798812944, + 8.003523881570553, + 5.723662586279854, + 0.6875000000000009, + 0.06749999999999999 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-3", + "tick": 201, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.12, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.062914841739111, + 8.006250282044348, + 5.723827557839927, + 0.6875000000000009, + 0.12 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "c-continuous-4", + "tick": 202, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.18750000000000008, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.045235236473651, + 8.009737140545028, + 5.724039708055878, + 0.6875000000000009, + 0.18750000000000008 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "c-continuous-5", + "tick": 203, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.2700000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.023621270998103, + 8.01397056535206, + 5.724299069471157, + 0.6875000000000009, + 0.2700000000000002 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-6", + "tick": 204, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.3675000000000003, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.998069667664467, + 8.018933570474049, + 5.724605681416602, + 0.6875000000000009, + 0.3675000000000003 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-7", + "tick": 205, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.4800000000000005, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.968576605440497, + 8.024606071421521, + 5.724959589743509, + 0.6875000000000009, + 0.4800000000000005 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-8", + "tick": 206, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.6075000000000007, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.935137746217983, + 8.03096488030751, + 5.72536084650794, + 0.6875000000000009, + 0.6075000000000007 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-9", + "tick": 207, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.7500000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.897748265192767, + 8.037983700324139, + 5.725809509606167, + 0.6875000000000009, + 0.7500000000000009 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-continuous-10", + "tick": 208, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.9075000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.856402885326382, + 8.045633119650974, + 5.726305642361144, + 0.6875000000000009, + 0.9075000000000012 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-11", + "tick": 209, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.0800000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.811095915899951, + 8.053880604858358, + 5.726849313059873, + 0.6875000000000009, + 1.0800000000000012 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-12", + "tick": 210, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.2675000000000012, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.761821295172016, + 8.062690493876515, + 5.727440594441521, + 0.6875000000000009, + 1.2675000000000012 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-13", + "tick": 211, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.470000000000001, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.70857263715223, + 8.072023988608676, + 5.728079563136151, + 0.6875000000000009, + 1.470000000000001 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "c-continuous-14", + "tick": 212, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.651343282503586, + 8.081839147274207, + 5.728766299053904, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-released", + "tick": 212, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.651343282503586, + 8.081839147274207, + 5.728766299053904, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-idle", + "tick": 213, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 1.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.651343282503586, + 8.081839147274207, + 5.728766299053904, + 0.6875000000000009, + 1.6875000000000009 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-increment", + "tick": 230, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6875000000000009, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.914149308791483, + 8.034916720015959, + 5.725612701765208, + 0.6875000000000009, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-0", + "tick": 231, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.6950000000000008, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.915160392812567, + 8.034916720015959, + 5.7226131804551414, + 0.6950000000000008, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-1", + "tick": 232, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.7175000000000009, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.91819128893605, + 8.034916720015959, + 5.71361382278119, + 0.7175000000000009, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "b-continuous-2", + "tick": 233, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.7550000000000009, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.923234928304385, + 8.034916720015959, + 5.698612250596281, + 0.7550000000000009, + 0.6875000000000009 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "b-continuous-3", + "tick": 234, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.8075000000000009, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.930279526033083, + 8.034916720015959, + 5.677604510604414, + 0.8075000000000009, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-4", + "tick": 235, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.875000000000001, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.939308576047644, + 8.034916720015959, + 5.650585089790985, + 0.875000000000001, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-5", + "tick": 236, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 0.9575000000000011, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.950300843894858, + 8.034916720015959, + 5.617546937038483, + 0.9575000000000011, + 0.6875000000000009 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "b-continuous-6", + "tick": 237, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.0550000000000013, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.963230357568055, + 8.034916720015959, + 5.578481490940659, + 1.0550000000000013, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "b-continuous-7", + "tick": 238, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.1675000000000013, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.978066396397452, + 8.034916720015959, + 5.533378713831883, + 1.1675000000000013, + 0.6875000000000009 + ], + "error": 2.2633986948989007e-16 + }, + { + "phase": "b-continuous-8", + "tick": 239, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.2950000000000017, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.994773478067838, + 8.034916720015959, + 5.482227132051872, + 1.2950000000000017, + 0.6875000000000009 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "b-continuous-9", + "tick": 240, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.4375000000000018, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.013311343837586, + 8.034916720015959, + 5.425013882469283, + 1.4375000000000018, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "b-continuous-10", + "tick": 241, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.595000000000002, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.033634942044415, + 8.034916720015959, + 5.3617247652908, + 1.595000000000002, + 0.6875000000000009 + ], + "error": 2.2929868617541516e-16 + }, + { + "phase": "b-continuous-11", + "tick": 242, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.767500000000002, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.055694409994807, + 8.034916720015959, + 5.292344303185328, + 1.767500000000002, + 0.6875000000000009 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "b-continuous-12", + "tick": 243, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.955000000000002, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.079435054345751, + 8.034916720015959, + 5.216855806755586, + 1.955000000000002, + 0.6875000000000009 + ], + "error": 2.2633986948989007e-16 + }, + { + "phase": "b-continuous-13", + "tick": 244, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 2.157500000000002, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.104797330099164, + 8.034916720015959, + 5.135241446391842, + 2.157500000000002, + 0.6875000000000009 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "b-continuous-14", + "tick": 245, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 2.3750000000000018, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.131716818341094, + 8.034916720015959, + 5.0474823305447885, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "b-released", + "tick": 245, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 2.3750000000000018, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.131716818341094, + 8.034916720015959, + 5.0474823305447885, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "b-idle", + "tick": 246, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 2.3750000000000018, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.131716818341094, + 8.034916720015959, + 5.0474823305447885, + 2.3750000000000018, + 0.6875000000000009 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "b-increment", + "tick": 263, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.6875000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.005198219836434, + 8.034916720015959, + 5.450113088356436, + 1.3750000000000018, + 0.6875000000000009 + ], + "error": 1.962615573354719e-17 + }, + { + "phase": "c-continuous-0", + "tick": 264, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.6950000000000008, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 12.003230696388727, + 8.03528570232885, + 5.4501603145960456, + 1.3750000000000018, + 0.6950000000000008 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-continuous-1", + "tick": 265, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.7175000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.99732783656701, + 8.0363911034889, + 5.450302000263195, + 1.3750000000000018, + 0.7175000000000009 + ], + "error": 4.449557262054371e-16 + }, + { + "phase": "c-continuous-2", + "tick": 266, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.7550000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.987488773958447, + 8.038228285781706, + 5.450538166154292, + 1.3750000000000018, + 0.7550000000000009 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-continuous-3", + "tick": 267, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.8075000000000009, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.973712071285423, + 8.04078951842513, + 5.450868846768148, + 1.3750000000000018, + 0.8075000000000009 + ], + "error": 3.1031676915590914e-17 + }, + { + "phase": "c-continuous-4", + "tick": 268, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.875000000000001, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.955995730523636, + 8.0440639756951, + 5.451294090063109, + 1.3750000000000018, + 0.875000000000001 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "c-continuous-5", + "tick": 269, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 0.9575000000000011, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.934337207072232, + 8.048037734327684, + 5.451813957116938, + 1.3750000000000018, + 0.9575000000000011 + ], + "error": 5.721958498152797e-17 + }, + { + "phase": "c-continuous-6", + "tick": 270, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.0550000000000013, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.908733427980678, + 8.052693770223444, + 5.452428521689316, + 1.3750000000000018, + 1.0550000000000013 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "c-continuous-7", + "tick": 271, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.1675000000000013, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.879180814238367, + 8.058011954487473, + 5.453137869686821, + 1.3750000000000018, + 1.1675000000000013 + ], + "error": 2.237726045655905e-16 + }, + { + "phase": "c-continuous-8", + "tick": 272, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.2950000000000017, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.845675307134108, + 8.063969048846008, + 5.453942098530234, + 1.3750000000000018, + 1.2950000000000017 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "c-continuous-9", + "tick": 273, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.4375000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.808212398693637, + 8.070538700487983, + 5.454841316423944, + 1.3750000000000018, + 1.4375000000000018 + ], + "error": 2.2291027918243403e-16 + }, + { + "phase": "c-continuous-10", + "tick": 274, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.595000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.766787166204399, + 8.077691436387322, + 5.455835641527256, + 1.3750000000000018, + 1.595000000000002 + ], + "error": 4.3885418357208765e-17 + }, + { + "phase": "c-continuous-11", + "tick": 275, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.767500000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.721394310837503, + 8.085394657169418, + 5.45692520102735, + 1.3750000000000018, + 1.767500000000002 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-12", + "tick": 276, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.955000000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.67202820037756, + 8.093612630592586, + 5.458110130113648, + 1.3750000000000018, + 1.955000000000002 + ], + "error": 2.2247786310271853e-16 + }, + { + "phase": "c-continuous-13", + "tick": 277, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 2.157500000000002, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.618682916071574, + 8.102306484723032, + 5.459390570853299, + 1.3750000000000018, + 2.157500000000002 + ], + "error": 1.3877787807814457e-17 + }, + { + "phase": "c-continuous-14", + "tick": 278, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.561352303608299, + 8.111434200889319, + 5.460766670967529, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "c-released", + "tick": 278, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.561352303608299, + 8.111434200889319, + 5.460766670967529, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "c-idle", + "tick": 279, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 2.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.561352303608299, + 8.111434200889319, + 5.460766670967529, + 1.3750000000000018, + 2.3750000000000018 + ], + "error": 2.242025235105681e-16 + }, + { + "phase": "c-increment", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.824645509338854, + 8.067668748808995, + 5.4544468743542645, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "invalid-jog-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.824645509338854, + 8.067668748808995, + 5.4544468743542645, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "repeated-stop", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.824645509338854, + 8.067668748808995, + 5.4544468743542645, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "abort-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.824645509338854, + 8.067668748808995, + 5.4544468743542645, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.7755575615628914e-17 + }, + { + "phase": "power-off-retains-rtcp", + "tick": 296, + "pose": { + "x": 12, + "y": 8, + "z": 6, + "a": 0, + "b": 1.3750000000000018, + "c": 1.3750000000000018, + "u": 0, + "v": 0, + "w": 0 + }, + "joints": [ + 11.824645509338854, + 8.067668748808995, + 5.4544468743542645, + 1.3750000000000018, + 1.3750000000000018 + ], + "error": 2.7755575615628914e-17 + } + ], + "identity_before": [ + 12.000000000000004, + 8.000000000000002, + 6.000000000000002, + 1.3750000000000018, + 1.3750000000000018 + ], + "identity_after": [ + 12.000000000000004, + 8.000000000000002, + 6.000000000000002, + 3.3750000000000018, + 1.3750000000000018 + ], + "reused_segment_id": "verified" + } + ], + "state_hash": "fnv1a64:d955794c61cce0f7" +} diff --git a/coverage/configuration/rtcp-jog-xyzac.png b/coverage/configuration/rtcp-jog-xyzac.png new file mode 100644 index 0000000..6a33de3 Binary files /dev/null and b/coverage/configuration/rtcp-jog-xyzac.png differ diff --git a/coverage/configuration/rtcp-jog-xyzbc.png b/coverage/configuration/rtcp-jog-xyzbc.png new file mode 100644 index 0000000..597232f Binary files /dev/null and b/coverage/configuration/rtcp-jog-xyzbc.png differ diff --git a/coverage/configuration/subpath.report.json b/coverage/configuration/subpath.report.json new file mode 100644 index 0000000..277b3fe --- /dev/null +++ b/coverage/configuration/subpath.report.json @@ -0,0 +1,129 @@ +{ + "report_id": "subpath-deployment-v1", + "status": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzac.ngc", + "url": "http://127.0.0.1:5175/dist/", + "machine": "xyzac-trt", + "icons": [ + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/axis-24x24.png", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_estop.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_power.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_open.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_verify.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_reload.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_run.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_step.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_pause.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_stop.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_blockdelete.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_optpause.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_zoomin.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_zoomout.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_z.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_z2.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_x.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_y.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_y2.png", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_p.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_rotate.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_clear.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/spindle_ccw.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/spindle_cw.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_zoomin.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_zoomout.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_axis_z.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_verify.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_reload.gif", + "loaded": true + }, + { + "src": "http://127.0.0.1:5175/dist/linuxcnc/share/axis/images/tool_open.gif", + "loaded": true + } + ], + "errors": [] +} diff --git a/coverage/configuration/trt-runtime.report.json b/coverage/configuration/trt-runtime.report.json new file mode 100644 index 0000000..218dc7b --- /dev/null +++ b/coverage/configuration/trt-runtime.report.json @@ -0,0 +1,1781 @@ +{ + "report_id": "trt-configuration-v1", + "status": "verified", + "rows": [ + { + "machine": "xyzac-trt", + "max_velocity": 4, + "max_acceleration": 4.0000000000000036, + "period": 0.002, + "joint_limit": [ + -200, + 17 + ], + "trace": [ + { + "tick": 2, + "x": 0.0008, + "velocity": 0.08000000000000002 + }, + { + "tick": 3, + "x": 0.0032000000000000015, + "velocity": 0.1600000000000001 + }, + { + "tick": 4, + "x": 0.007200000000000003, + "velocity": 0.24000000000000016 + }, + { + "tick": 5, + "x": 0.01280000000000001, + "velocity": 0.32000000000000023 + }, + { + "tick": 6, + "x": 0.02000000000000001, + "velocity": 0.4000000000000003 + }, + { + "tick": 7, + "x": 0.028800000000000013, + "velocity": 0.48000000000000037 + }, + { + "tick": 8, + "x": 0.03920000000000002, + "velocity": 0.5600000000000004 + }, + { + "tick": 9, + "x": 0.05120000000000004, + "velocity": 0.6400000000000005 + }, + { + "tick": 10, + "x": 0.06480000000000005, + "velocity": 0.7200000000000005 + }, + { + "tick": 11, + "x": 0.08000000000000007, + "velocity": 0.8000000000000006 + }, + { + "tick": 12, + "x": 0.09680000000000008, + "velocity": 0.8800000000000007 + }, + { + "tick": 13, + "x": 0.11520000000000011, + "velocity": 0.9600000000000007 + }, + { + "tick": 14, + "x": 0.13520000000000013, + "velocity": 1.0400000000000007 + }, + { + "tick": 15, + "x": 0.15680000000000016, + "velocity": 1.1200000000000008 + }, + { + "tick": 16, + "x": 0.18000000000000016, + "velocity": 1.2000000000000008 + }, + { + "tick": 17, + "x": 0.20480000000000018, + "velocity": 1.280000000000001 + }, + { + "tick": 18, + "x": 0.2312000000000002, + "velocity": 1.360000000000001 + }, + { + "tick": 19, + "x": 0.25920000000000026, + "velocity": 1.440000000000001 + }, + { + "tick": 20, + "x": 0.2888000000000003, + "velocity": 1.5200000000000011 + }, + { + "tick": 21, + "x": 0.32000000000000034, + "velocity": 1.6000000000000012 + }, + { + "tick": 22, + "x": 0.3528000000000004, + "velocity": 1.6800000000000013 + }, + { + "tick": 23, + "x": 0.38720000000000043, + "velocity": 1.7600000000000013 + }, + { + "tick": 24, + "x": 0.42320000000000035, + "velocity": 1.8400000000000014 + }, + { + "tick": 25, + "x": 0.46080000000000043, + "velocity": 1.9200000000000015 + }, + { + "tick": 26, + "x": 0.5000000000000004, + "velocity": 2.0000000000000013 + }, + { + "tick": 27, + "x": 0.5408000000000005, + "velocity": 2.0800000000000014 + }, + { + "tick": 28, + "x": 0.5832000000000004, + "velocity": 2.1600000000000015 + }, + { + "tick": 29, + "x": 0.6272000000000004, + "velocity": 2.2400000000000015 + }, + { + "tick": 30, + "x": 0.6728000000000004, + "velocity": 2.3200000000000016 + }, + { + "tick": 31, + "x": 0.7200000000000004, + "velocity": 2.4000000000000017 + }, + { + "tick": 32, + "x": 0.7688000000000005, + "velocity": 2.4800000000000018 + }, + { + "tick": 33, + "x": 0.8192000000000006, + "velocity": 2.560000000000002 + }, + { + "tick": 34, + "x": 0.8712000000000006, + "velocity": 2.640000000000002 + }, + { + "tick": 35, + "x": 0.9248000000000006, + "velocity": 2.720000000000002 + }, + { + "tick": 36, + "x": 0.9800000000000008, + "velocity": 2.800000000000002 + }, + { + "tick": 37, + "x": 1.0368000000000004, + "velocity": 2.880000000000002 + }, + { + "tick": 38, + "x": 1.0952000000000006, + "velocity": 2.960000000000002 + }, + { + "tick": 39, + "x": 1.1552000000000007, + "velocity": 3.0400000000000023 + }, + { + "tick": 40, + "x": 1.2168000000000005, + "velocity": 3.1200000000000023 + }, + { + "tick": 41, + "x": 1.2800000000000007, + "velocity": 3.2000000000000024 + }, + { + "tick": 42, + "x": 1.344800000000001, + "velocity": 3.2800000000000025 + }, + { + "tick": 43, + "x": 1.411200000000001, + "velocity": 3.3600000000000025 + }, + { + "tick": 44, + "x": 1.479200000000001, + "velocity": 3.4400000000000026 + }, + { + "tick": 45, + "x": 1.548800000000001, + "velocity": 3.5200000000000027 + }, + { + "tick": 46, + "x": 1.6200000000000014, + "velocity": 3.6000000000000028 + }, + { + "tick": 47, + "x": 1.6928000000000012, + "velocity": 3.680000000000003 + }, + { + "tick": 48, + "x": 1.7672000000000012, + "velocity": 3.760000000000003 + }, + { + "tick": 49, + "x": 1.8432000000000013, + "velocity": 3.840000000000003 + }, + { + "tick": 50, + "x": 1.9208000000000012, + "velocity": 3.920000000000003 + }, + { + "tick": 51, + "x": 2.0000000000000013, + "velocity": 4 + }, + { + "tick": 52, + "x": 2.0800000000000014, + "velocity": 4 + }, + { + "tick": 53, + "x": 2.1600000000000015, + "velocity": 4 + }, + { + "tick": 54, + "x": 2.2400000000000015, + "velocity": 4 + }, + { + "tick": 55, + "x": 2.3200000000000016, + "velocity": 4 + }, + { + "tick": 56, + "x": 2.4000000000000017, + "velocity": 4 + }, + { + "tick": 57, + "x": 2.4800000000000018, + "velocity": 4 + }, + { + "tick": 58, + "x": 2.560000000000002, + "velocity": 4 + }, + { + "tick": 59, + "x": 2.640000000000002, + "velocity": 4 + }, + { + "tick": 60, + "x": 2.720000000000002, + "velocity": 4 + }, + { + "tick": 61, + "x": 2.800000000000002, + "velocity": 4 + }, + { + "tick": 62, + "x": 2.880000000000002, + "velocity": 4 + }, + { + "tick": 63, + "x": 2.960000000000002, + "velocity": 4 + }, + { + "tick": 64, + "x": 3.0400000000000023, + "velocity": 4 + }, + { + "tick": 65, + "x": 3.1200000000000023, + "velocity": 4 + }, + { + "tick": 66, + "x": 3.200000000000002, + "velocity": 4 + }, + { + "tick": 67, + "x": 3.2800000000000025, + "velocity": 4 + }, + { + "tick": 68, + "x": 3.360000000000002, + "velocity": 4 + }, + { + "tick": 69, + "x": 3.4400000000000026, + "velocity": 4 + }, + { + "tick": 70, + "x": 3.5200000000000022, + "velocity": 4 + }, + { + "tick": 71, + "x": 3.6000000000000028, + "velocity": 4 + }, + { + "tick": 72, + "x": 3.6800000000000024, + "velocity": 4 + }, + { + "tick": 73, + "x": 3.760000000000003, + "velocity": 4 + }, + { + "tick": 74, + "x": 3.8400000000000025, + "velocity": 4 + }, + { + "tick": 75, + "x": 3.920000000000003, + "velocity": 4 + }, + { + "tick": 76, + "x": 4.000000000000003, + "velocity": 4 + }, + { + "tick": 77, + "x": 4.080000000000003, + "velocity": 4 + }, + { + "tick": 78, + "x": 4.160000000000003, + "velocity": 4 + }, + { + "tick": 79, + "x": 4.240000000000003, + "velocity": 4 + }, + { + "tick": 80, + "x": 4.320000000000003, + "velocity": 4 + }, + { + "tick": 81, + "x": 4.400000000000003, + "velocity": 4 + }, + { + "tick": 82, + "x": 4.480000000000003, + "velocity": 4 + }, + { + "tick": 83, + "x": 4.560000000000003, + "velocity": 4 + }, + { + "tick": 84, + "x": 4.640000000000003, + "velocity": 4 + }, + { + "tick": 85, + "x": 4.720000000000003, + "velocity": 4 + }, + { + "tick": 86, + "x": 4.800000000000003, + "velocity": 4 + }, + { + "tick": 87, + "x": 4.8800000000000034, + "velocity": 4 + }, + { + "tick": 88, + "x": 4.9600000000000035, + "velocity": 4 + }, + { + "tick": 89, + "x": 5.040000000000004, + "velocity": 4 + }, + { + "tick": 90, + "x": 5.120000000000004, + "velocity": 4 + }, + { + "tick": 91, + "x": 5.200000000000004, + "velocity": 4 + }, + { + "tick": 92, + "x": 5.280000000000004, + "velocity": 4 + }, + { + "tick": 93, + "x": 5.360000000000004, + "velocity": 4 + }, + { + "tick": 94, + "x": 5.440000000000004, + "velocity": 4 + }, + { + "tick": 95, + "x": 5.520000000000004, + "velocity": 4 + }, + { + "tick": 96, + "x": 5.600000000000004, + "velocity": 4 + }, + { + "tick": 97, + "x": 5.680000000000004, + "velocity": 4 + }, + { + "tick": 98, + "x": 5.760000000000004, + "velocity": 4 + }, + { + "tick": 99, + "x": 5.840000000000004, + "velocity": 4 + }, + { + "tick": 100, + "x": 5.920000000000004, + "velocity": 4 + }, + { + "tick": 101, + "x": 6.000000000000004, + "velocity": 4 + }, + { + "tick": 102, + "x": 6.0800000000000045, + "velocity": 4 + }, + { + "tick": 103, + "x": 6.160000000000005, + "velocity": 4 + }, + { + "tick": 104, + "x": 6.240000000000005, + "velocity": 4 + }, + { + "tick": 105, + "x": 6.320000000000005, + "velocity": 4 + }, + { + "tick": 106, + "x": 6.400000000000004, + "velocity": 4 + }, + { + "tick": 107, + "x": 6.480000000000006, + "velocity": 4 + }, + { + "tick": 108, + "x": 6.560000000000005, + "velocity": 4 + }, + { + "tick": 109, + "x": 6.640000000000005, + "velocity": 4 + }, + { + "tick": 110, + "x": 6.720000000000004, + "velocity": 4 + }, + { + "tick": 111, + "x": 6.800000000000006, + "velocity": 4 + }, + { + "tick": 112, + "x": 6.880000000000005, + "velocity": 4 + }, + { + "tick": 113, + "x": 6.960000000000005, + "velocity": 4 + }, + { + "tick": 114, + "x": 7.0400000000000045, + "velocity": 4 + }, + { + "tick": 115, + "x": 7.120000000000006, + "velocity": 4 + }, + { + "tick": 116, + "x": 7.2000000000000055, + "velocity": 4 + }, + { + "tick": 117, + "x": 7.280000000000006, + "velocity": 4 + }, + { + "tick": 118, + "x": 7.360000000000005, + "velocity": 4 + }, + { + "tick": 119, + "x": 7.440000000000007, + "velocity": 4 + }, + { + "tick": 120, + "x": 7.520000000000006, + "velocity": 4 + }, + { + "tick": 121, + "x": 7.600000000000006, + "velocity": 4 + }, + { + "tick": 122, + "x": 7.680000000000005, + "velocity": 4 + }, + { + "tick": 123, + "x": 7.760000000000007, + "velocity": 4 + }, + { + "tick": 124, + "x": 7.840000000000006, + "velocity": 4 + }, + { + "tick": 125, + "x": 7.920000000000006, + "velocity": 4 + }, + { + "tick": 126, + "x": 8.000000000000005, + "velocity": 3.9999999999999933 + }, + { + "tick": 127, + "x": 8.079200000000004, + "velocity": 3.919999999999996 + }, + { + "tick": 128, + "x": 8.156800000000004, + "velocity": 3.839999999999997 + }, + { + "tick": 129, + "x": 8.232800000000005, + "velocity": 3.759999999999997 + }, + { + "tick": 130, + "x": 8.307200000000003, + "velocity": 3.679999999999997 + }, + { + "tick": 131, + "x": 8.380000000000004, + "velocity": 3.5999999999999974 + }, + { + "tick": 132, + "x": 8.451200000000004, + "velocity": 3.5199999999999974 + }, + { + "tick": 133, + "x": 8.520800000000003, + "velocity": 3.4399999999999973 + }, + { + "tick": 134, + "x": 8.588800000000004, + "velocity": 3.359999999999997 + }, + { + "tick": 135, + "x": 8.655200000000004, + "velocity": 3.279999999999997 + }, + { + "tick": 136, + "x": 8.720000000000004, + "velocity": 3.199999999999997 + }, + { + "tick": 137, + "x": 8.783200000000004, + "velocity": 3.119999999999997 + }, + { + "tick": 138, + "x": 8.844800000000005, + "velocity": 3.039999999999997 + }, + { + "tick": 139, + "x": 8.904800000000003, + "velocity": 2.959999999999997 + }, + { + "tick": 140, + "x": 8.963200000000004, + "velocity": 2.879999999999997 + }, + { + "tick": 141, + "x": 9.020000000000003, + "velocity": 2.7999999999999967 + }, + { + "tick": 142, + "x": 9.075200000000002, + "velocity": 2.719999999999997 + }, + { + "tick": 143, + "x": 9.128800000000004, + "velocity": 2.639999999999997 + }, + { + "tick": 144, + "x": 9.180800000000001, + "velocity": 2.559999999999997 + }, + { + "tick": 145, + "x": 9.231200000000003, + "velocity": 2.4799999999999978 + }, + { + "tick": 146, + "x": 9.280000000000003, + "velocity": 2.3999999999999977 + }, + { + "tick": 147, + "x": 9.327200000000001, + "velocity": 2.3199999999999976 + }, + { + "tick": 148, + "x": 9.372800000000003, + "velocity": 2.2399999999999975 + }, + { + "tick": 149, + "x": 9.416800000000002, + "velocity": 2.1599999999999975 + }, + { + "tick": 150, + "x": 9.459200000000003, + "velocity": 2.0799999999999974 + }, + { + "tick": 151, + "x": 9.500000000000004, + "velocity": 1.9999999999999973 + }, + { + "tick": 152, + "x": 9.539200000000003, + "velocity": 1.9199999999999973 + }, + { + "tick": 153, + "x": 9.576800000000004, + "velocity": 1.8399999999999972 + }, + { + "tick": 154, + "x": 9.612800000000004, + "velocity": 1.7599999999999971 + }, + { + "tick": 155, + "x": 9.647200000000003, + "velocity": 1.679999999999997 + }, + { + "tick": 156, + "x": 9.680000000000003, + "velocity": 1.599999999999997 + }, + { + "tick": 157, + "x": 9.711200000000003, + "velocity": 1.519999999999997 + }, + { + "tick": 158, + "x": 9.740800000000002, + "velocity": 1.4399999999999968 + }, + { + "tick": 159, + "x": 9.768800000000002, + "velocity": 1.3599999999999968 + }, + { + "tick": 160, + "x": 9.795200000000003, + "velocity": 1.2799999999999967 + }, + { + "tick": 161, + "x": 9.820000000000002, + "velocity": 1.1999999999999966 + }, + { + "tick": 162, + "x": 9.843200000000003, + "velocity": 1.1199999999999966 + }, + { + "tick": 163, + "x": 9.864800000000002, + "velocity": 1.0399999999999965 + }, + { + "tick": 164, + "x": 9.884800000000002, + "velocity": 0.9599999999999964 + }, + { + "tick": 165, + "x": 9.903200000000004, + "velocity": 0.8799999999999963 + }, + { + "tick": 166, + "x": 9.920000000000002, + "velocity": 0.7999999999999963 + }, + { + "tick": 167, + "x": 9.935200000000004, + "velocity": 0.7199999999999962 + }, + { + "tick": 168, + "x": 9.948800000000004, + "velocity": 0.6399999999999961 + }, + { + "tick": 169, + "x": 9.960800000000003, + "velocity": 0.5599999999999961 + }, + { + "tick": 170, + "x": 9.971200000000003, + "velocity": 0.479999999999996 + }, + { + "tick": 171, + "x": 9.980000000000002, + "velocity": 0.3999999999999959 + }, + { + "tick": 172, + "x": 9.987200000000001, + "velocity": 0.31999999999999584 + }, + { + "tick": 173, + "x": 9.992800000000003, + "velocity": 0.23999999999999577 + }, + { + "tick": 174, + "x": 9.996800000000002, + "velocity": 0.1599999999999957 + }, + { + "tick": 175, + "x": 9.999200000000002, + "velocity": 0.07999999999999563 + }, + { + "tick": 176, + "x": 10, + "velocity": 0 + } + ] + }, + { + "machine": "xyzbc-trt", + "max_velocity": 4, + "max_acceleration": 4.0000000000000036, + "period": 0.002, + "joint_limit": [ + -200, + 17 + ], + "trace": [ + { + "tick": 2, + "x": 0.0008, + "velocity": 0.08000000000000002 + }, + { + "tick": 3, + "x": 0.0032000000000000015, + "velocity": 0.1600000000000001 + }, + { + "tick": 4, + "x": 0.007200000000000003, + "velocity": 0.24000000000000016 + }, + { + "tick": 5, + "x": 0.01280000000000001, + "velocity": 0.32000000000000023 + }, + { + "tick": 6, + "x": 0.02000000000000001, + "velocity": 0.4000000000000003 + }, + { + "tick": 7, + "x": 0.028800000000000013, + "velocity": 0.48000000000000037 + }, + { + "tick": 8, + "x": 0.03920000000000002, + "velocity": 0.5600000000000004 + }, + { + "tick": 9, + "x": 0.05120000000000004, + "velocity": 0.6400000000000005 + }, + { + "tick": 10, + "x": 0.06480000000000005, + "velocity": 0.7200000000000005 + }, + { + "tick": 11, + "x": 0.08000000000000007, + "velocity": 0.8000000000000006 + }, + { + "tick": 12, + "x": 0.09680000000000008, + "velocity": 0.8800000000000007 + }, + { + "tick": 13, + "x": 0.11520000000000011, + "velocity": 0.9600000000000007 + }, + { + "tick": 14, + "x": 0.13520000000000013, + "velocity": 1.0400000000000007 + }, + { + "tick": 15, + "x": 0.15680000000000016, + "velocity": 1.1200000000000008 + }, + { + "tick": 16, + "x": 0.18000000000000016, + "velocity": 1.2000000000000008 + }, + { + "tick": 17, + "x": 0.20480000000000018, + "velocity": 1.280000000000001 + }, + { + "tick": 18, + "x": 0.2312000000000002, + "velocity": 1.360000000000001 + }, + { + "tick": 19, + "x": 0.25920000000000026, + "velocity": 1.440000000000001 + }, + { + "tick": 20, + "x": 0.2888000000000003, + "velocity": 1.5200000000000011 + }, + { + "tick": 21, + "x": 0.32000000000000034, + "velocity": 1.6000000000000012 + }, + { + "tick": 22, + "x": 0.3528000000000004, + "velocity": 1.6800000000000013 + }, + { + "tick": 23, + "x": 0.38720000000000043, + "velocity": 1.7600000000000013 + }, + { + "tick": 24, + "x": 0.42320000000000035, + "velocity": 1.8400000000000014 + }, + { + "tick": 25, + "x": 0.46080000000000043, + "velocity": 1.9200000000000015 + }, + { + "tick": 26, + "x": 0.5000000000000004, + "velocity": 2.0000000000000013 + }, + { + "tick": 27, + "x": 0.5408000000000005, + "velocity": 2.0800000000000014 + }, + { + "tick": 28, + "x": 0.5832000000000004, + "velocity": 2.1600000000000015 + }, + { + "tick": 29, + "x": 0.6272000000000004, + "velocity": 2.2400000000000015 + }, + { + "tick": 30, + "x": 0.6728000000000004, + "velocity": 2.3200000000000016 + }, + { + "tick": 31, + "x": 0.7200000000000004, + "velocity": 2.4000000000000017 + }, + { + "tick": 32, + "x": 0.7688000000000005, + "velocity": 2.4800000000000018 + }, + { + "tick": 33, + "x": 0.8192000000000006, + "velocity": 2.560000000000002 + }, + { + "tick": 34, + "x": 0.8712000000000006, + "velocity": 2.640000000000002 + }, + { + "tick": 35, + "x": 0.9248000000000006, + "velocity": 2.720000000000002 + }, + { + "tick": 36, + "x": 0.9800000000000008, + "velocity": 2.800000000000002 + }, + { + "tick": 37, + "x": 1.0368000000000004, + "velocity": 2.880000000000002 + }, + { + "tick": 38, + "x": 1.0952000000000006, + "velocity": 2.960000000000002 + }, + { + "tick": 39, + "x": 1.1552000000000007, + "velocity": 3.0400000000000023 + }, + { + "tick": 40, + "x": 1.2168000000000005, + "velocity": 3.1200000000000023 + }, + { + "tick": 41, + "x": 1.2800000000000007, + "velocity": 3.2000000000000024 + }, + { + "tick": 42, + "x": 1.344800000000001, + "velocity": 3.2800000000000025 + }, + { + "tick": 43, + "x": 1.411200000000001, + "velocity": 3.3600000000000025 + }, + { + "tick": 44, + "x": 1.479200000000001, + "velocity": 3.4400000000000026 + }, + { + "tick": 45, + "x": 1.548800000000001, + "velocity": 3.5200000000000027 + }, + { + "tick": 46, + "x": 1.6200000000000014, + "velocity": 3.6000000000000028 + }, + { + "tick": 47, + "x": 1.6928000000000012, + "velocity": 3.680000000000003 + }, + { + "tick": 48, + "x": 1.7672000000000012, + "velocity": 3.760000000000003 + }, + { + "tick": 49, + "x": 1.8432000000000013, + "velocity": 3.840000000000003 + }, + { + "tick": 50, + "x": 1.9208000000000012, + "velocity": 3.920000000000003 + }, + { + "tick": 51, + "x": 2.0000000000000013, + "velocity": 4 + }, + { + "tick": 52, + "x": 2.0800000000000014, + "velocity": 4 + }, + { + "tick": 53, + "x": 2.1600000000000015, + "velocity": 4 + }, + { + "tick": 54, + "x": 2.2400000000000015, + "velocity": 4 + }, + { + "tick": 55, + "x": 2.3200000000000016, + "velocity": 4 + }, + { + "tick": 56, + "x": 2.4000000000000017, + "velocity": 4 + }, + { + "tick": 57, + "x": 2.4800000000000018, + "velocity": 4 + }, + { + "tick": 58, + "x": 2.560000000000002, + "velocity": 4 + }, + { + "tick": 59, + "x": 2.640000000000002, + "velocity": 4 + }, + { + "tick": 60, + "x": 2.720000000000002, + "velocity": 4 + }, + { + "tick": 61, + "x": 2.800000000000002, + "velocity": 4 + }, + { + "tick": 62, + "x": 2.880000000000002, + "velocity": 4 + }, + { + "tick": 63, + "x": 2.960000000000002, + "velocity": 4 + }, + { + "tick": 64, + "x": 3.0400000000000023, + "velocity": 4 + }, + { + "tick": 65, + "x": 3.1200000000000023, + "velocity": 4 + }, + { + "tick": 66, + "x": 3.200000000000002, + "velocity": 4 + }, + { + "tick": 67, + "x": 3.2800000000000025, + "velocity": 4 + }, + { + "tick": 68, + "x": 3.360000000000002, + "velocity": 4 + }, + { + "tick": 69, + "x": 3.4400000000000026, + "velocity": 4 + }, + { + "tick": 70, + "x": 3.5200000000000022, + "velocity": 4 + }, + { + "tick": 71, + "x": 3.6000000000000028, + "velocity": 4 + }, + { + "tick": 72, + "x": 3.6800000000000024, + "velocity": 4 + }, + { + "tick": 73, + "x": 3.760000000000003, + "velocity": 4 + }, + { + "tick": 74, + "x": 3.8400000000000025, + "velocity": 4 + }, + { + "tick": 75, + "x": 3.920000000000003, + "velocity": 4 + }, + { + "tick": 76, + "x": 4.000000000000003, + "velocity": 4 + }, + { + "tick": 77, + "x": 4.080000000000003, + "velocity": 4 + }, + { + "tick": 78, + "x": 4.160000000000003, + "velocity": 4 + }, + { + "tick": 79, + "x": 4.240000000000003, + "velocity": 4 + }, + { + "tick": 80, + "x": 4.320000000000003, + "velocity": 4 + }, + { + "tick": 81, + "x": 4.400000000000003, + "velocity": 4 + }, + { + "tick": 82, + "x": 4.480000000000003, + "velocity": 4 + }, + { + "tick": 83, + "x": 4.560000000000003, + "velocity": 4 + }, + { + "tick": 84, + "x": 4.640000000000003, + "velocity": 4 + }, + { + "tick": 85, + "x": 4.720000000000003, + "velocity": 4 + }, + { + "tick": 86, + "x": 4.800000000000003, + "velocity": 4 + }, + { + "tick": 87, + "x": 4.8800000000000034, + "velocity": 4 + }, + { + "tick": 88, + "x": 4.9600000000000035, + "velocity": 4 + }, + { + "tick": 89, + "x": 5.040000000000004, + "velocity": 4 + }, + { + "tick": 90, + "x": 5.120000000000004, + "velocity": 4 + }, + { + "tick": 91, + "x": 5.200000000000004, + "velocity": 4 + }, + { + "tick": 92, + "x": 5.280000000000004, + "velocity": 4 + }, + { + "tick": 93, + "x": 5.360000000000004, + "velocity": 4 + }, + { + "tick": 94, + "x": 5.440000000000004, + "velocity": 4 + }, + { + "tick": 95, + "x": 5.520000000000004, + "velocity": 4 + }, + { + "tick": 96, + "x": 5.600000000000004, + "velocity": 4 + }, + { + "tick": 97, + "x": 5.680000000000004, + "velocity": 4 + }, + { + "tick": 98, + "x": 5.760000000000004, + "velocity": 4 + }, + { + "tick": 99, + "x": 5.840000000000004, + "velocity": 4 + }, + { + "tick": 100, + "x": 5.920000000000004, + "velocity": 4 + }, + { + "tick": 101, + "x": 6.000000000000004, + "velocity": 4 + }, + { + "tick": 102, + "x": 6.0800000000000045, + "velocity": 4 + }, + { + "tick": 103, + "x": 6.160000000000005, + "velocity": 4 + }, + { + "tick": 104, + "x": 6.240000000000005, + "velocity": 4 + }, + { + "tick": 105, + "x": 6.320000000000005, + "velocity": 4 + }, + { + "tick": 106, + "x": 6.400000000000004, + "velocity": 4 + }, + { + "tick": 107, + "x": 6.480000000000006, + "velocity": 4 + }, + { + "tick": 108, + "x": 6.560000000000005, + "velocity": 4 + }, + { + "tick": 109, + "x": 6.640000000000005, + "velocity": 4 + }, + { + "tick": 110, + "x": 6.720000000000004, + "velocity": 4 + }, + { + "tick": 111, + "x": 6.800000000000006, + "velocity": 4 + }, + { + "tick": 112, + "x": 6.880000000000005, + "velocity": 4 + }, + { + "tick": 113, + "x": 6.960000000000005, + "velocity": 4 + }, + { + "tick": 114, + "x": 7.0400000000000045, + "velocity": 4 + }, + { + "tick": 115, + "x": 7.120000000000006, + "velocity": 4 + }, + { + "tick": 116, + "x": 7.2000000000000055, + "velocity": 4 + }, + { + "tick": 117, + "x": 7.280000000000006, + "velocity": 4 + }, + { + "tick": 118, + "x": 7.360000000000005, + "velocity": 4 + }, + { + "tick": 119, + "x": 7.440000000000007, + "velocity": 4 + }, + { + "tick": 120, + "x": 7.520000000000006, + "velocity": 4 + }, + { + "tick": 121, + "x": 7.600000000000006, + "velocity": 4 + }, + { + "tick": 122, + "x": 7.680000000000005, + "velocity": 4 + }, + { + "tick": 123, + "x": 7.760000000000007, + "velocity": 4 + }, + { + "tick": 124, + "x": 7.840000000000006, + "velocity": 4 + }, + { + "tick": 125, + "x": 7.920000000000006, + "velocity": 4 + }, + { + "tick": 126, + "x": 8.000000000000005, + "velocity": 3.9999999999999933 + }, + { + "tick": 127, + "x": 8.079200000000004, + "velocity": 3.919999999999996 + }, + { + "tick": 128, + "x": 8.156800000000004, + "velocity": 3.839999999999997 + }, + { + "tick": 129, + "x": 8.232800000000005, + "velocity": 3.759999999999997 + }, + { + "tick": 130, + "x": 8.307200000000003, + "velocity": 3.679999999999997 + }, + { + "tick": 131, + "x": 8.380000000000004, + "velocity": 3.5999999999999974 + }, + { + "tick": 132, + "x": 8.451200000000004, + "velocity": 3.5199999999999974 + }, + { + "tick": 133, + "x": 8.520800000000003, + "velocity": 3.4399999999999973 + }, + { + "tick": 134, + "x": 8.588800000000004, + "velocity": 3.359999999999997 + }, + { + "tick": 135, + "x": 8.655200000000004, + "velocity": 3.279999999999997 + }, + { + "tick": 136, + "x": 8.720000000000004, + "velocity": 3.199999999999997 + }, + { + "tick": 137, + "x": 8.783200000000004, + "velocity": 3.119999999999997 + }, + { + "tick": 138, + "x": 8.844800000000005, + "velocity": 3.039999999999997 + }, + { + "tick": 139, + "x": 8.904800000000003, + "velocity": 2.959999999999997 + }, + { + "tick": 140, + "x": 8.963200000000004, + "velocity": 2.879999999999997 + }, + { + "tick": 141, + "x": 9.020000000000003, + "velocity": 2.7999999999999967 + }, + { + "tick": 142, + "x": 9.075200000000002, + "velocity": 2.719999999999997 + }, + { + "tick": 143, + "x": 9.128800000000004, + "velocity": 2.639999999999997 + }, + { + "tick": 144, + "x": 9.180800000000001, + "velocity": 2.559999999999997 + }, + { + "tick": 145, + "x": 9.231200000000003, + "velocity": 2.4799999999999978 + }, + { + "tick": 146, + "x": 9.280000000000003, + "velocity": 2.3999999999999977 + }, + { + "tick": 147, + "x": 9.327200000000001, + "velocity": 2.3199999999999976 + }, + { + "tick": 148, + "x": 9.372800000000003, + "velocity": 2.2399999999999975 + }, + { + "tick": 149, + "x": 9.416800000000002, + "velocity": 2.1599999999999975 + }, + { + "tick": 150, + "x": 9.459200000000003, + "velocity": 2.0799999999999974 + }, + { + "tick": 151, + "x": 9.500000000000004, + "velocity": 1.9999999999999973 + }, + { + "tick": 152, + "x": 9.539200000000003, + "velocity": 1.9199999999999973 + }, + { + "tick": 153, + "x": 9.576800000000004, + "velocity": 1.8399999999999972 + }, + { + "tick": 154, + "x": 9.612800000000004, + "velocity": 1.7599999999999971 + }, + { + "tick": 155, + "x": 9.647200000000003, + "velocity": 1.679999999999997 + }, + { + "tick": 156, + "x": 9.680000000000003, + "velocity": 1.599999999999997 + }, + { + "tick": 157, + "x": 9.711200000000003, + "velocity": 1.519999999999997 + }, + { + "tick": 158, + "x": 9.740800000000002, + "velocity": 1.4399999999999968 + }, + { + "tick": 159, + "x": 9.768800000000002, + "velocity": 1.3599999999999968 + }, + { + "tick": 160, + "x": 9.795200000000003, + "velocity": 1.2799999999999967 + }, + { + "tick": 161, + "x": 9.820000000000002, + "velocity": 1.1999999999999966 + }, + { + "tick": 162, + "x": 9.843200000000003, + "velocity": 1.1199999999999966 + }, + { + "tick": 163, + "x": 9.864800000000002, + "velocity": 1.0399999999999965 + }, + { + "tick": 164, + "x": 9.884800000000002, + "velocity": 0.9599999999999964 + }, + { + "tick": 165, + "x": 9.903200000000004, + "velocity": 0.8799999999999963 + }, + { + "tick": 166, + "x": 9.920000000000002, + "velocity": 0.7999999999999963 + }, + { + "tick": 167, + "x": 9.935200000000004, + "velocity": 0.7199999999999962 + }, + { + "tick": 168, + "x": 9.948800000000004, + "velocity": 0.6399999999999961 + }, + { + "tick": 169, + "x": 9.960800000000003, + "velocity": 0.5599999999999961 + }, + { + "tick": 170, + "x": 9.971200000000003, + "velocity": 0.479999999999996 + }, + { + "tick": 171, + "x": 9.980000000000002, + "velocity": 0.3999999999999959 + }, + { + "tick": 172, + "x": 9.987200000000001, + "velocity": 0.31999999999999584 + }, + { + "tick": 173, + "x": 9.992800000000003, + "velocity": 0.23999999999999577 + }, + { + "tick": 174, + "x": 9.996800000000002, + "velocity": 0.1599999999999957 + }, + { + "tick": 175, + "x": 9.999200000000002, + "velocity": 0.07999999999999563 + }, + { + "tick": 176, + "x": 10, + "velocity": 0 + } + ] + } + ], + "state_hash": "fnv1a64:ece01c08718cef9c" +} diff --git a/coverage/recovery/acceptance.report.json b/coverage/recovery/acceptance.report.json new file mode 100644 index 0000000..994b292 --- /dev/null +++ b/coverage/recovery/acceptance.report.json @@ -0,0 +1,65 @@ +{ + "report_id": "cfg-r01-trt-r01-acceptance-v1", + "status": "verified", + "tasks": [ + "CFG-R01", + "TRT-R01" + ], + "native_build_id": "fff6a3970ae13c6a575d90373332f25c90034318d4758616dee3bedd227cfc89", + "wasm_sha256": "aa0f5d551e42859eef3b37aee9079f09ae04e668819da4e62aee2b74522e8bc6", + "checkpoint_sizes": { + "sim_core": 24, + "sim_protocol": 12, + "sim_event": 786448, + "sim_program": 49496, + "sim_linuxcnc_tp": 47304, + "sim_hal": 49544 + }, + "continuation_samples": 120, + "first_divergent_tick": null, + "boundaries": [ + "matching native build and machine configuration required", + "old snapshots lacking native state are rejected", + "TRT checkpoint event tails require explicit subsequent commands", + "backward public recovery requires a fresh transport session", + "OPFS retains its existing quota; no physical crash or cross-browser parity claim" + ], + "evidence": [ + { + "path": "coverage/recovery/axis3-selected-case.report.json", + "sha256": "24a017e622073950726652399da8959fb695097b1f26e9a88c3357269f09b96e" + }, + { + "path": "coverage/recovery/axis-gates.report.json", + "sha256": "e92a7ee1b9545cffbabe8a4933eca73330703746e1169627cef1fd838d19fdd4" + }, + { + "path": "coverage/recovery/axis3-checkpoint.report.json", + "sha256": "638b13148f0e82b3b78a9ff9b8a91ead5c144ff0c3ebec4cf0ce9df913c2989b" + }, + { + "path": "coverage/recovery/checkpoint-state.report.json", + "sha256": "12b7792cd312e327154d4b66a13d869906a4c53a9143dd0d04493f687452da3b" + }, + { + "path": "coverage/recovery/exact-checkpoint.report.json", + "sha256": "bda234634bcfbaef24dfa7291cf8650c67d33805a3ceefe96e251fdb2a64059e" + }, + { + "path": "coverage/recovery/power-off.report.json", + "sha256": "d0f18fbe4749d2a89fac242e99596c125ad8aa056067d18b59589519d83c6b46" + }, + { + "path": "coverage/recovery/storage-bridge.report.json", + "sha256": "e9489615ac04e8f6186f340c7eecf78d5aca32db715b8a0b73ef7bef693d0dc6" + }, + { + "path": "coverage/recovery/browser-reload.report.json", + "sha256": "108b1da696ac1cc4666301c8b5b1d478d3b6853372640428192211a03771e993" + }, + { + "path": "coverage/recovery/browser-reload-xyzbc-trt.report.json", + "sha256": "d136b7985b0c7f0d0215cb04f9b4a35aa239e50534ebd65f80e57af278cf1f9f" + } + ] +} diff --git a/coverage/recovery/axis-gates.report.json b/coverage/recovery/axis-gates.report.json new file mode 100644 index 0000000..e56602d --- /dev/null +++ b/coverage/recovery/axis-gates.report.json @@ -0,0 +1 @@ +{"schema":"axis-contract-sample.v1","status":"verified","commands":25,"matrix":25,"blocked_reasons":["axis_mdi_queue_full","axis_estop_active"]} diff --git a/coverage/recovery/axis3-checkpoint.report.json b/coverage/recovery/axis3-checkpoint.report.json new file mode 100644 index 0000000..abd9584 --- /dev/null +++ b/coverage/recovery/axis3-checkpoint.report.json @@ -0,0 +1 @@ +{"schema":"wasm-motion-checkpoint-t3.v1","status":"verified","case":"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini","checkpoint_hash":"fnv1a64:0fec51679b74f35c","continuous_hash":"fnv1a64:6ad7c87921fa2384","recovered_hash":"fnv1a64:6ad7c87921fa2384","endpoint_equal":true,"trace_import":"verified","checkpoint_queue_depth":1,"blocked_damaged_checkpoint":"blocked"} diff --git a/coverage/recovery/axis3-selected-case.report.json b/coverage/recovery/axis3-selected-case.report.json new file mode 100644 index 0000000..4048018 --- /dev/null +++ b/coverage/recovery/axis3-selected-case.report.json @@ -0,0 +1 @@ +{"schema":"axis3-selected-case-parity.v1","status":"verified","case":"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini","machine":{"type":"cartesian_vismach","axes":["X","Y","Z"],"joints":3,"kinematics":"trivkins","model":"3axis-tutorial"},"exact_files":["linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini","linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.hal","linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial","linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial_mm.var","linuxcnc/configs/nc_files/3dtest.ngc","linuxcnc/configs/sim/axis/vismach/3axis-tutorial/tool.tbl","linuxcnc/linuxcnc.gif"],"loader":{"fetched":7,"missing":0,"state_hash":"fnv1a64:94e21a5c7a684e63"},"startup":{"state":"TASK_READY","hal_blocked":0},"operation_sequence":["HOME","JOG_START","SIM_STEP","JOG_STOP","SET_OVERRIDE","PROGRAM_LOAD","PROGRAM_RUN","CHECKPOINT","SPINDLE_COMMAND","COOLANT_COMMAND","TOOL_PREPARE","TOOL_CHANGE","ESTOP","IO_RECOVER","RECOVER"],"observed":{"jog_x":0.19999999999999998,"program_status":"completed","checkpoint_hash":"fnv1a64:9d53b245b2779038","recovery":"recovered","final_hash":"fnv1a64:47a5f0e6ae76a4af"}} diff --git a/coverage/recovery/browser-reload-failure.png b/coverage/recovery/browser-reload-failure.png new file mode 100644 index 0000000..ada6856 Binary files /dev/null and b/coverage/recovery/browser-reload-failure.png differ diff --git a/coverage/recovery/browser-reload-xyzbc-trt.png b/coverage/recovery/browser-reload-xyzbc-trt.png new file mode 100644 index 0000000..aa948af Binary files /dev/null and b/coverage/recovery/browser-reload-xyzbc-trt.png differ diff --git a/coverage/recovery/browser-reload-xyzbc-trt.report.json b/coverage/recovery/browser-reload-xyzbc-trt.report.json new file mode 100644 index 0000000..7bcb90e --- /dev/null +++ b/coverage/recovery/browser-reload-xyzbc-trt.report.json @@ -0,0 +1,39 @@ +{ + "report_id": "checkpoint-browser-reload-v1", + "machine": "xyzbc-trt", + "status": "verified", + "before": [ + 2.4666500000000022, + 0, + 0, + 0, + 0 + ], + "saved": { + "schema": "linuxcnc-ui-temp-json.v1", + "status": "verified", + "storage": "opfs", + "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", + "key": "xyzbc-trt-power-off", + "generation": 1, + "slot": "state-b.json", + "state_hash": "fnv1a64:9dc45f4891e8b147", + "bytes": 102567 + }, + "after": [ + 2.4666500000000022, + 0, + 0, + 0, + 0 + ], + "preview_segments": 1, + "continued": [ + 2.5666500000000023, + 0, + 0, + 0, + 0 + ], + "browser_errors": [] +} diff --git a/coverage/recovery/browser-reload.png b/coverage/recovery/browser-reload.png new file mode 100644 index 0000000..d100e89 Binary files /dev/null and b/coverage/recovery/browser-reload.png differ diff --git a/coverage/recovery/browser-reload.report.json b/coverage/recovery/browser-reload.report.json new file mode 100644 index 0000000..392582a --- /dev/null +++ b/coverage/recovery/browser-reload.report.json @@ -0,0 +1,39 @@ +{ + "report_id": "checkpoint-browser-reload-v1", + "machine": "xyzac-trt", + "status": "verified", + "before": [ + 2.266650000000002, + 0, + 0, + 0, + 0 + ], + "saved": { + "schema": "linuxcnc-ui-temp-json.v1", + "status": "verified", + "storage": "opfs", + "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", + "key": "xyzac-trt-power-off", + "generation": 1, + "slot": "state-b.json", + "state_hash": "fnv1a64:bc9feb6862938390", + "bytes": 103146 + }, + "after": [ + 2.266650000000002, + 0, + 0, + 0, + 0 + ], + "preview_segments": 1, + "continued": [ + 2.366650000000002, + 0, + 0, + 0, + 0 + ], + "browser_errors": [] +} diff --git a/coverage/recovery/build.log b/coverage/recovery/build.log new file mode 100644 index 0000000..72dff07 --- /dev/null +++ b/coverage/recovery/build.log @@ -0,0 +1,168 @@ +configure: cmake -S /home/mes006/linuxcncWebWASM/wasm-core -B /home/mes006/linuxcncWebWASM/build-wasm -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=/usr/share/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/node;--experimental-wasm-threads +-- Configuring done (0.5s) +-- Generating done (0.0s) +-- Build files have been written to: /home/mes006/linuxcncWebWASM/build-wasm +[0/2] Re-checking globbed directories... +[1/55] Building C object CMakeFiles/sim_core.dir/src/sim_api.c.o +[2/55] Building C object CMakeFiles/sim_core.dir/src/sim_scheduler.c.o +[3/55] Building C object CMakeFiles/sim_core.dir/src/sim_protocol.c.o +[4/55] Building C object CMakeFiles/sim_core.dir/src/sim_event.c.o +[5/55] Building C object CMakeFiles/sim_core.dir/src/sim_task.c.o +[6/55] Building C object CMakeFiles/sim_core.dir/src/sim_posemath.c.o +[7/55] Building C object CMakeFiles/sim_core.dir/src/sim_device_io.c.o +[8/55] Building C object CMakeFiles/sim_core.dir/src/sim_motion_queue.c.o +[9/55] Building C object CMakeFiles/sim_core.dir/src/sim_switchkins.c.o +[10/55] Building C object CMakeFiles/sim_core.dir/src/sim_linuxcnc_tp.c.o +[11/55] Building C object CMakeFiles/sim_core.dir/src/sim_userkins.c.o +[12/55] Building C object CMakeFiles/sim_core.dir/src/sim_interpreter.c.o +[13/55] Building C object CMakeFiles/sim_core.dir/src/sim_hal.c.o +[14/55] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_registry.c.o +[15/55] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_motion.c.o +[16/55] Building C object CMakeFiles/sim_core.dir/src/sim_program.c.o +[17/55] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_feedback.c.o +[18/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_api.c.o +[19/55] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_home_limit.c.o +[20/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_protocol.c.o +[21/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_event.c.o +[22/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_task.c.o +[23/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_scheduler.c.o +[24/55] Building C object CMakeFiles/sim_core.dir/src/sim_io.c.o +[25/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_posemath.c.o +[26/55] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics.c.o +[27/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_device_io.c.o +[28/55] Linking CXX static library libsim_core.a +[29/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_motion_queue.c.o +[30/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_hal.c.o +[31/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_switchkins.c.o +[32/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_linuxcnc_tp.c.o +[33/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_program.c.o +[34/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_interpreter.c.o +[35/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_kinematics.c.o +[36/55] Linking CXX executable sim_kinematics_registry_wasm_test.js +[37/55] Linking CXX executable sim_hal_wasm_test.js +[38/55] Linking CXX executable sim_kinematics_wasm_test.js +[39/55] Linking CXX executable sim_switchkins_wasm_test.js +[40/55] Linking CXX executable sim_kinematics_motion_wasm_test.js +[41/55] Linking CXX executable sim_userkins_wasm_test.js +[42/55] Linking CXX executable sim_kinematics_feedback_wasm_test.js +[43/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_userkins.c.o +[44/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_kinematics_registry.c.o +[45/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_kinematics_motion.c.o +[46/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_kinematics_feedback.c.o +[47/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_kinematics_home_limit.c.o +[48/55] Linking CXX executable sim_kinematics_home_limit_wasm_test.js +[49/55] Building C object CMakeFiles/sim_core_module.dir/src/sim_io.c.o +[50/55] Linking CXX executable sim_io_wasm_test.js +[51/55] Linking CXX executable sim_device_io_wasm_test.js +[52/55] Linking CXX executable sim_motion_queue_wasm_test.js +[53/55] Linking CXX executable sim_source_unit_adapter_wasm_test.js +[54/55] Linking CXX executable sim_xyzac_trace_wasm_test.js +[55/55] Linking CXX executable linuxcnc-sim-core.js +{"schema":"kinematics-coupled-samples.v1","corexy":{"pose":[4.000000000,2.000000000,3.000000000],"inverse":[6.000000000,2.000000000,3.000000000],"state_hash":15100884384408177781},"rose":{"pose":[-1.969615506,-0.347296355,7.000000000],"inverse":[2.000000000,7.000000000,190.000000000],"branch":1,"state_hash":2438572543122669123},"maxkins":{"pose":[-4.000000000,3.000000000,5.000000000],"inverse":[3.000000000,4.000000000,5.000000000,0.000000000,30.000000000,90.000000000],"state_hash":9619955171041365392},"rotatekins":{"pose":[4.000000000,-3.000000000,5.000000000],"inverse":[3.000000000,4.000000000,5.000000000,0.000000000,0.000000000,90.000000000],"state_hash":4875770340631299468},"fiveaxiskins":{"pose":[118.967490359,111.434580971,76.850293016],"inverse":[10.000000000,20.000000000,30.000000000,35.000000000,40.000000000,-2.000000000,20.000000000],"state_hash":7748142909372720337},"xyzac_trt":{"pose":[142.826706376,9.689621386,163.102540378],"inverse":[100.000000000,50.000000000,200.000000000,30.000000000,45.000000000],"state_hash":13756563895642799233},"xyzbc_trt":{"pose":[44.740352549,25.970325570,227.671759760],"inverse":[100.000000000,50.000000000,200.000000000,30.000000000,45.000000000],"state_hash":16201786080257949760},"scara":{"pose":[590.460310444,130.945742721,465.000000000],"inverse":[-3.644300703,40.000000000,25.000000000,-31.355699297,5.000000000,-3.000000000],"state_hash":16144366080292311245},"puma":{"pose":[450.112320486,207.114833215,-612.180622409],"inverse":[10.000000000,20.000000000,-30.000000000,40.000000000,25.000000000,-15.000000000],"state_hash":3358632253473456540},"scorbot":{"pose":[366.786994761,171.035584569,191.173940759],"inverse":[25.000000000,35.000000000,-20.000000000,10.000000000,-5.000000000],"state_hash":7814353589808450675},"genser":{"pose":[619.051467462,30.411237800,-349.855537721],"inverse":[0.000000000,10.000000000,-20.000000000,29.999999999,15.000000000,-9.999999999],"state_hash":7456282805679296496,"iterations":0,"residual":0.000000000000},"lineardelta":{"pose":[0.000000000,0.000000000,64.636584194],"inverse":[300.000000000,300.000000000,300.000000000],"state_hash":5975346607623231017},"rotarydelta":{"pose":[0.000000000,0.000000000,-3.793360169],"inverse":[10.000000000,10.000000000,10.000000000],"state_hash":2370894419412951220},"tripod":{"pose":[2.000000000,3.000000000,4.000000000],"inverse":[5.385164807,9.433981132,6.447715952],"state_hash":15608486964708188096},"genhex":{"pose":[10.000000000,-5.000000000,60.000000000,4.999999999,-3.000000001,10.000000000],"inverse":[68.274402001,63.007928550,63.783820692,65.475127133,60.456723205,68.231500501],"state_hash":7575268797291078131,"iterations":14,"residual":0.000000000857},"pentapod":{"pose":[0.000000000,500.000000000,300.000000000,0.010000000,-0.010000000],"inverse":[625.895566556,649.261562588,754.767615986,338.490149644,341.118326622],"state_hash":13978136356142463125,"iterations":12,"residual":0.000000001000},"delta_edges":{"lineardelta":{"alternate_status":0,"alternate_branch":1,"alternate_pose":[-0.000000000,-0.000000000,535.363415806],"no_solution":-702,"limit_error":-702,"geometry_error":-703,"branch":0,"iterations":0},"rotarydelta":{"alternate_status":0,"alternate_branch":1,"alternate_pose":[-0.000000000,-0.000000000,0.320396615],"singular":-704,"geometry_error":-703,"branch":0,"iterations":0},"tripod":{"alternate_status":0,"alternate_branch":1,"alternate_pose":[2.000000000,3.000000000,-4.000000000],"no_solution":-702,"singular":-704,"limit_error":-702,"geometry_error":-703,"branch":0,"iterations":0},"genhex":{"alternate_status":0,"alternate_branch":1,"alternate_pose":[10.000000000,-5.000000000,-60.000000000],"nonpositive":-702,"iteration_limit":-702,"max_error":-702,"singular":-704,"limit_error":-702,"branch":0,"iterations":14,"residual":0.000000000857},"pentapod":{"alternate_status":-702,"alternate_branch":1,"alternate_pose":[0.000000000,0.000000000,0.000000000],"alternate_iterations":1,"nonpositive":-702,"nonpositive_iterations":0,"iteration_limit":-702,"iteration_limit_iterations":1,"max_error":-702,"max_error_iterations":1,"singular":-704,"singular_iterations":0,"limit_error":-702,"branch":0,"iterations":12,"residual":0.000000001000}}} +{"schema":"hal-corexy-sample.v1","command":{"j0":6.000,"j1":2.000,"alpha":4.000,"beta":2.000},"feedback":{"alpha":4.000,"beta":1.000,"j0":5.000,"j1":3.000},"tick":1,"function_calls":1,"component_batch":{"gate":1,"constant":3.500,"rollback_tick":1}} +{"schema":"linuxcnc-source-unit-adapter.v1","cases":[{"id":"source-unit:interp/movement-linear","name":"Movement commands / Linear Feed","source":"linuxcnc/unit_tests/interp/test_interp_block.cc","result_hash":16718210192490359486,"status":"PASSED"},{"id":"source-unit:interp/movement-rapid","name":"Movement commands / Rapid","source":"linuxcnc/unit_tests/interp/test_interp_block.cc","result_hash":10600861468726077694,"status":"PASSED"},{"id":"source-unit:interp/movement-arc","name":"Movement commands / Clockwise circular","source":"linuxcnc/unit_tests/interp/test_interp_block.cc","result_hash":3344452133792667065,"status":"PASSED"},{"id":"source-unit:interp/gcode-to-string","name":"convert G-codes to string","source":"linuxcnc/unit_tests/interp/test_string_conversion.cc","result_hash":18096020171334179193,"status":"PASSED"},{"id":"source-unit:interp/g10-coordinate-offset","name":"G10 L2 / G54 coordinate offset","source":"linuxcnc/unit_tests/interp/test_interp_basics.cc","result_hash":10503294382485028241,"status":"PASSED"},{"id":"source-unit:interp/parameter-expression","name":"parameter expression assignment","source":"linuxcnc/unit_tests/interp/test_interp_basics.cc","result_hash":12191524797282674768,"status":"PASSED"},{"id":"source-unit:interp/modal-units","name":"G20 length/feed conversion","source":"linuxcnc/unit_tests/interp/test_interp_basics.cc","result_hash":17337372050960250323,"status":"PASSED"},{"id":"source-unit:tp/pmCartCartParallel_numerical","name":"pmCartCartParallel_numerical","source":"linuxcnc/unit_tests/tp/test_blendmath.c","result_hash":8528287329722895704,"status":"PASSED"},{"id":"source-unit:tp/pmCartCartAntiParallel_numerical","name":"pmCartCartAntiParallel_numerical","source":"linuxcnc/unit_tests/tp/test_blendmath.c","result_hash":6154898365857503476,"status":"PASSED"}],"counts":{"FAILED":0,"PASSED":9},"aggregate_hash":12570001048447364769,"pending":[{"source_id":"source-test:linuxcnc/tests/abort/g64/test.sh","adapter":"WORKER_TASK_MOTION_PENDING","reason":"G64 blend/tolerance and abort endpoint require Worker Task plus motion queue"},{"source_id":"source-test:linuxcnc/tests/abort/feed-rate/test.sh","adapter":"WORKER_TASK_MOTION_PENDING","reason":"abort/restart timing is owned by Worker Task and fixed-tick motion"}]} +-- Configuring done (0.0s) +-- Generating done (0.0s) +-- Build files have been written to: /home/mes006/linuxcncWebWASM/build-native +[0/2] Re-checking globbed directories... +[1/43] Building C object CMakeFiles/sim_core.dir/src/sim_api.c.o +[2/43] Building C object CMakeFiles/sim_core.dir/src/sim_scheduler.c.o +[3/43] Building C object CMakeFiles/sim_core.dir/src/sim_protocol.c.o +[4/43] Building C object CMakeFiles/sim_core.dir/src/sim_event.c.o +[5/43] Building C object CMakeFiles/sim_core.dir/src/sim_posemath.c.o +[6/43] Building C object CMakeFiles/sim_core.dir/src/sim_device_io.c.o +[7/43] Building C object CMakeFiles/sim_core.dir/src/sim_switchkins.c.o +[8/43] Building C object CMakeFiles/sim_core.dir/src/sim_task.c.o +[9/43] Building C object CMakeFiles/sim_core.dir/src/sim_program.c.o +[10/43] Building C object CMakeFiles/sim_core.dir/src/sim_userkins.c.o +[11/43] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_registry.c.o +[12/43] Building C object CMakeFiles/sim_core.dir/src/sim_hal.c.o +[13/43] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_feedback.c.o +[14/43] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_motion.c.o +[15/43] Building C object CMakeFiles/sim_core.dir/src/sim_linuxcnc_tp.c.o +[16/43] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics_home_limit.c.o +[17/43] Building C object CMakeFiles/sim_core.dir/src/sim_motion_queue.c.o +[18/43] Building C object CMakeFiles/sim_core.dir/src/sim_io.c.o +[19/43] Building C object CMakeFiles/sim_core.dir/src/sim_interpreter.c.o +[20/43] Building C object CMakeFiles/sim_core.dir/src/sim_kinematics.c.o +[21/43] Linking CXX static library libsim_core.a +[22/43] Linking CXX executable sim_task_test +[23/43] Linking CXX executable sim_protocol_test +[24/43] Linking CXX executable sim_native_homing_test +[25/43] Linking CXX executable sim_event_test +[26/43] Linking CXX executable sim_api_test +[27/43] Linking CXX executable sim_contract_test +[28/43] Linking CXX executable sim_scheduler_test +[29/43] Linking CXX executable sim_hal_test +[30/43] Linking CXX executable sim_posemath_test +[31/43] Linking CXX executable sim_interpreter_test +[32/43] Linking CXX executable sim_switchkins_test +[33/43] Linking CXX executable sim_kinematics_registry_test +[34/43] Linking CXX executable sim_source_unit_adapter_test +[35/43] Linking CXX executable sim_userkins_test +[36/43] Linking CXX executable sim_kinematics_motion_test +[37/43] Linking CXX executable sim_motion_queue_test +[38/43] Linking CXX executable sim_kinematics_test +[39/43] Linking CXX executable sim_kinematics_feedback_test +[40/43] Linking CXX executable sim_kinematics_home_limit_test +[41/43] Linking CXX executable sim_xyzac_trace_test +[42/43] Linking CXX executable sim_io_test +[43/43] Linking CXX executable sim_device_io_test +Internal ctest changing into directory: /home/mes006/linuxcncWebWASM/build-native +Test project /home/mes006/linuxcncWebWASM/build-native + Start 1: sim_native_homing_test + 1/23 Test #1: sim_native_homing_test ........... Passed 0.00 sec + Start 2: sim_api_test + 2/23 Test #2: sim_api_test ..................... Passed 0.00 sec + Start 3: sim_protocol_test + 3/23 Test #3: sim_protocol_test ................ Passed 0.00 sec + Start 4: sim_event_test + 4/23 Test #4: sim_event_test ................... Passed 0.00 sec + Start 5: sim_contract_test + 5/23 Test #5: sim_contract_test ................ Passed 0.00 sec + Start 6: sim_task_test + 6/23 Test #6: sim_task_test .................... Passed 0.00 sec + Start 7: sim_scheduler_test + 7/23 Test #7: sim_scheduler_test ............... Passed 0.00 sec + Start 8: sim_hal_test + 8/23 Test #8: sim_hal_test ..................... Passed 0.00 sec + Start 9: sim_posemath_test + 9/23 Test #9: sim_posemath_test ................ Passed 0.00 sec + Start 10: sim_interpreter_test +10/23 Test #10: sim_interpreter_test ............. Passed 0.00 sec + Start 11: sim_source_unit_adapter_test +11/23 Test #11: sim_source_unit_adapter_test ..... Passed 0.00 sec + Start 12: sim_motion_queue_test +12/23 Test #12: sim_motion_queue_test ............ Passed 0.00 sec + Start 13: sim_kinematics_test +13/23 Test #13: sim_kinematics_test .............. Passed 0.00 sec + Start 14: sim_switchkins_test +14/23 Test #14: sim_switchkins_test .............. Passed 0.00 sec + Start 15: sim_userkins_test +15/23 Test #15: sim_userkins_test ................ Passed 0.00 sec + Start 16: sim_kinematics_registry_test +16/23 Test #16: sim_kinematics_registry_test ..... Passed 0.00 sec + Start 17: sim_kinematics_motion_test +17/23 Test #17: sim_kinematics_motion_test ....... Passed 0.00 sec + Start 18: sim_kinematics_feedback_test +18/23 Test #18: sim_kinematics_feedback_test ..... Passed 0.00 sec + Start 19: sim_kinematics_home_limit_test +19/23 Test #19: sim_kinematics_home_limit_test ... Passed 0.00 sec + Start 20: sim_io_test +20/23 Test #20: sim_io_test ...................... Passed 0.00 sec + Start 21: sim_device_io_test +21/23 Test #21: sim_device_io_test ............... Passed 0.00 sec + Start 22: sim_xyzac_trace_test +22/23 Test #22: sim_xyzac_trace_test ............. Passed 0.00 sec + Start 23: sim_linuxcnc_tp_test +23/23 Test #23: sim_linuxcnc_tp_test ............. Passed 0.00 sec + +100% tests passed, 0 tests failed out of 23 + +Total Test time (real) = 0.03 sec +wasm_build=/home/mes006/linuxcncWebWASM/build-wasm/linuxcnc-sim-core.js +native_test=/home/mes006/linuxcncWebWASM/build-native/sim_api_test +native_protocol_test=/home/mes006/linuxcncWebWASM/build-native/sim_protocol_test +wasm_kinematics_test=/home/mes006/linuxcncWebWASM/build-wasm/sim_kinematics_wasm_test.js +wasm_hal_test=/home/mes006/linuxcncWebWASM/build-wasm/sim_hal_wasm_test.js +wasm_source_unit_test=/home/mes006/linuxcncWebWASM/build-wasm/sim_source_unit_adapter_wasm_test.js diff --git a/coverage/recovery/checkpoint-state.report.json b/coverage/recovery/checkpoint-state.report.json new file mode 100644 index 0000000..acf3946 --- /dev/null +++ b/coverage/recovery/checkpoint-state.report.json @@ -0,0 +1 @@ +{"schema":"xyzac-recovery-rtcp-11.v1","status":"verified","case":"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini","checkpoint":{"required_fields":["context_id","simulation_tick","event_cursor","state_hash","task","program","kinematics","motion","wasm_motion","ledger"],"active_type":1,"state_hash":"fnv1a64:00e5c37554124a5d"},"continuous_endpoint_hash":"fnv1a64:6c0c36b47a65d571","restored_endpoint_hash":"fnv1a64:6c0c36b47a65d571","checkpoint_tick":3,"unreplayed_tail_endpoint_hash":"fnv1a64:40beb05449a569c8","endpoint_at_checkpoint_equal":true,"no_duplicate_m428":true,"replayed_operation":true,"corrupt_checkpoint":"blocked","schema_mismatch":"blocked","journal":{"status":"recovered","corrupt_status":"blocked","schema_status":"blocked"},"opfs":{"unavailable_status":"blocked","probe_status":"blocked","physical_crash":"REAL-EXCLUDED"},"first_divergent_tick":null} diff --git a/coverage/recovery/exact-checkpoint.report.json b/coverage/recovery/exact-checkpoint.report.json new file mode 100644 index 0000000..a77fd3c --- /dev/null +++ b/coverage/recovery/exact-checkpoint.report.json @@ -0,0 +1,131 @@ +{ + "report_id": "trt-checkpoint-exact-v1", + "status": "verified", + "safeguards": { + "corrupt_native_rejected": true, + "foreign_build_rejected": true, + "frozen_subroutines": true, + "heap_relocation_checked": true, + "receipts_nonrecursive": true, + "backward_transport_restore_rejected": true + }, + "rows": [ + { + "machine": "xyzac-trt", + "phase": "subroutine", + "checkpoint_tick": 6, + "continuation_tick": 18, + "state_hash": "fnv1a64:113cda82a07e692a", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "paused-resumed", + "checkpoint_tick": 18, + "continuation_tick": 30, + "state_hash": "fnv1a64:e380c733d2e27194", + "samples": 12, + "new_transport_recovery": true, + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "line", + "checkpoint_tick": 35, + "continuation_tick": 47, + "state_hash": "fnv1a64:8e128e04aca13b45", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "arc", + "checkpoint_tick": 72, + "continuation_tick": 84, + "state_hash": "fnv1a64:16fef8e7095d1cc7", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "completed", + "checkpoint_tick": 684, + "continuation_tick": 696, + "state_hash": "fnv1a64:571fff3045139a9b", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "trace-and-transport", + "state_hash": "fnv1a64:50c207f5409d5062", + "first_divergent_tick": null + }, + { + "machine": "xyzac-trt", + "phase": "post-recovery-jog", + "samples": 6, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "subroutine", + "checkpoint_tick": 6, + "continuation_tick": 18, + "state_hash": "fnv1a64:94587fdc5ce1e3b1", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "paused-resumed", + "checkpoint_tick": 18, + "continuation_tick": 30, + "state_hash": "fnv1a64:5f0adc4aa9f2a037", + "samples": 12, + "new_transport_recovery": true, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "line", + "checkpoint_tick": 35, + "continuation_tick": 47, + "state_hash": "fnv1a64:74b83ca0c127a6c1", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "arc", + "checkpoint_tick": 72, + "continuation_tick": 84, + "state_hash": "fnv1a64:89a768be32ed9eda", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "completed", + "checkpoint_tick": 704, + "continuation_tick": 716, + "state_hash": "fnv1a64:d0cf609cae7c894b", + "samples": 12, + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "trace-and-transport", + "state_hash": "fnv1a64:4e6307e65d801db6", + "first_divergent_tick": null + }, + { + "machine": "xyzbc-trt", + "phase": "post-recovery-jog", + "samples": 6, + "first_divergent_tick": null + } + ], + "state_hash": "fnv1a64:d7f8b093cb3e4510" +} diff --git a/coverage/recovery/power-off.report.json b/coverage/recovery/power-off.report.json new file mode 100644 index 0000000..796f206 --- /dev/null +++ b/coverage/recovery/power-off.report.json @@ -0,0 +1 @@ +{"schema":"xyzac-runtime-followups.v1","status":"verified","motion_delta":{"compact_bytes":7224,"full_bytes":23215,"reduction_ratio":0.689},"errors":{"code":"axis_gate","reason":"xyzac_machine_off","message_key":"xyzac_machine_off"},"power_off_persistence":{"schema":"xyzac-power-off-checkpoint.v1","bytes":101157,"restored":"verified"}} diff --git a/coverage/recovery/rtcp-jog-browser.report.json b/coverage/recovery/rtcp-jog-browser.report.json new file mode 100644 index 0000000..6f8c2ee --- /dev/null +++ b/coverage/recovery/rtcp-jog-browser.report.json @@ -0,0 +1,657 @@ +{ + "report_id": "trt-rtcp-jog-browser-v1", + "status": "verified", + "machines": [ + { + "coordinates": "XYZAC", + "rows": [ + { + "axis": "a", + "before": { + "joints": [ + 4.266649999999998, + 3.8666500000000035, + 0, + 0, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 59, + "rendered_tick": 59, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 4.266649999999998, + 5.061423130217634, + -1.709991602392515, + 6.300000000000009, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 90, + "rendered_tick": 84, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 1.3877787807814457e-17 + }, + "stopped": { + "joints": [ + 4.266649999999998, + 5.436614141154411, + -2.173445467445781, + 8.100000000000021, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 101, + "rendered_tick": 101, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 4.441434166503682e-16 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 4.266649999999998, + 5.436614141154411, + -2.173445467445781, + 8.100000000000021, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 101, + "rendered_tick": 101, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 3.816579336612649, + 5.877023221778387, + -2.1107659733420316, + 8.100000000000021, + 6.30000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 132, + "rendered_tick": 126, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 4.441976168356516e-16 + }, + "stopped": { + "joints": [ + 3.6792686907248924, + 5.993602656197197, + -2.0941742628307054, + 8.100000000000021, + 8.100000000000021 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 143, + "rendered_tick": 143, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 1.962615573354719e-17 + } + }, + { + "axis": "a", + "before": { + "joints": [ + 3.6792686907248924, + 5.993602656197197, + -2.0941742628307054, + 8.100000000000021, + 8.100000000000021 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 143, + "rendered_tick": 143, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 3.6792686907248924, + 6.985677681046019, + -3.15582122980466, + 12.600000000000016, + 8.100000000000021 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 168, + "rendered_tick": 162, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 2.5018537765542006e-17 + }, + "stopped": { + "joints": [ + 3.6792686907248924, + 7.405333794615657, + -3.558119369114298, + 14.40000000000003, + 8.100000000000021 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 179, + "rendered_tick": 179, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 3.7367087251761353e-17 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 3.6792686907248924, + 7.405333794615657, + -3.558119369114298, + 14.40000000000003, + 8.100000000000021 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 179, + "rendered_tick": 179, + "tool_tip": [ + 0, + 2, + 0 + ] + }, + "moving": { + "joints": [ + 3.044109145431041, + 7.8492876810852135, + -3.444131385053268, + 14.40000000000003, + 15.900000000000045 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 215, + "rendered_tick": 209, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 2.2247786310271853e-16 + }, + "stopped": { + "joints": [ + 2.8890836739630625, + 7.9395654828013065, + -3.4209519852626453, + 14.40000000000003, + 17.70000000000001 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": 0, + "y_offset": 20, + "z_offset": 10, + "tool_offset": 0 + }, + "tick": 226, + "rendered_tick": 226, + "tool_tip": [ + 0, + 2, + 0 + ], + "tcp_error": 3.622208959480119e-17 + } + } + ] + }, + { + "coordinates": "XYZBC", + "rows": [ + { + "axis": "b", + "before": { + "joints": [ + 4.466649999999994, + 3.466650000000003, + 0, + 0, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 284, + "rendered_tick": 284, + "tool_tip": [ + 0, + 1.5, + 0 + ] + }, + "moving": { + "joints": [ + 7.000111209846697, + 3.466650000000003, + -5.273183620607902, + 11.699999999999944, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 333, + "rendered_tick": 333, + "tool_tip": [ + 0, + 1.4472681637939209, + 0 + ], + "tcp_error": 2.237726045655905e-16 + }, + "stopped": { + "joints": [ + 7.292314970736374, + 3.466650000000003, + -6.126077205619893, + 13.499999999999906, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 344, + "rendered_tick": 344, + "tool_tip": [ + 0, + 1.4387392279438012, + 0 + ], + "tcp_error": 2.7755575615628914e-17 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 7.292314970736374, + 3.466650000000003, + -6.126077205619893, + 13.499999999999906, + 0 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 344, + "rendered_tick": 344, + "tool_tip": [ + 0, + 1.4387392279438012, + 0 + ] + }, + "moving": { + "joints": [ + 6.753295838822215, + 4.081962692594443, + -5.996670161309499, + 13.499999999999906, + 8.400000000000015 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 382, + "rendered_tick": 376, + "tool_tip": [ + 0, + 1.4397384871103407, + 0 + ], + "tcp_error": 2.2247786310271853e-16 + }, + "stopped": { + "joints": [ + 6.626743801709193, + 4.202837259715039, + -5.966287705280346, + 13.499999999999906, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 393, + "rendered_tick": 393, + "tool_tip": [ + 0, + 1.4403371229471966, + 0 + ], + "tcp_error": 6.938893903907228e-18 + } + }, + { + "axis": "b", + "before": { + "joints": [ + 6.626743801709193, + 4.202837259715039, + -5.966287705280346, + 13.499999999999906, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 393, + "rendered_tick": 393, + "tool_tip": [ + 0, + 1.4403371229471966, + 0 + ] + }, + "moving": { + "joints": [ + 7.783411671211734, + 4.202837259715039, + -10.678717873870822, + 23.39999999999989, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 436, + "rendered_tick": 429, + "tool_tip": [ + 0, + 1.4033646717032329, + 0 + ], + "tcp_error": 2.2473876566261383e-16 + }, + "stopped": { + "joints": [ + 7.783411671211734, + 4.202837259715039, + -10.678717873870822, + 23.39999999999989, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 441, + "rendered_tick": 441, + "tool_tip": [ + 0, + 1.3932128212612918, + 0 + ], + "tcp_error": 2.2473876566261383e-16 + } + }, + { + "axis": "c", + "before": { + "joints": [ + 7.783411671211734, + 4.202837259715039, + -10.678717873870822, + 23.39999999999989, + 10.199999999999976 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 441, + "rendered_tick": 441, + "tool_tip": [ + 0, + 1.3932128212612918, + 0 + ] + }, + "moving": { + "joints": [ + 7.317075360390216, + 4.609695511941566, + -10.476916131895246, + 23.39999999999989, + 16.79999999999999 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 473, + "rendered_tick": 473, + "tool_tip": [ + 0, + 1.3952308386810475, + 0 + ], + "tcp_error": 2.238801616996866e-16 + }, + "stopped": { + "joints": [ + 7.205302306697099, + 4.693820235514115, + -10.428547612400239, + 23.39999999999989, + 18.299999999999997 + ], + "offsets": { + "x_rot_point": 0, + "y_rot_point": 0, + "z_rot_point": 0, + "x_offset": -20, + "y_offset": 0, + "z_offset": -15, + "tool_offset": 0 + }, + "tick": 483, + "rendered_tick": 483, + "tool_tip": [ + 0, + 1.3957145238759976, + 0 + ], + "tcp_error": 2.234765555274009e-16 + } + } + ] + } + ], + "browser_errors": [] +} diff --git a/coverage/recovery/rtcp-jog-xyzac.png b/coverage/recovery/rtcp-jog-xyzac.png new file mode 100644 index 0000000..3856e8e Binary files /dev/null and b/coverage/recovery/rtcp-jog-xyzac.png differ diff --git a/coverage/recovery/rtcp-jog-xyzbc.png b/coverage/recovery/rtcp-jog-xyzbc.png new file mode 100644 index 0000000..b999836 Binary files /dev/null and b/coverage/recovery/rtcp-jog-xyzbc.png differ diff --git a/coverage/recovery/storage-bridge.report.json b/coverage/recovery/storage-bridge.report.json new file mode 100644 index 0000000..b65236c --- /dev/null +++ b/coverage/recovery/storage-bridge.report.json @@ -0,0 +1 @@ +{"schema":"opfs-worker-bridge.v1","status":"verified","ui_commands_do_not_consume_wasm_sequence":true,"poweroff_checkpoint_roundtrip":true} diff --git a/docs/trt-parametric/03-推进台账.md b/docs/trt-parametric/03-推进台账.md index ac3b344..f8ed64a 100644 --- a/docs/trt-parametric/03-推进台账.md +++ b/docs/trt-parametric/03-推进台账.md @@ -1,5 +1,40 @@ # 推进台账 +## R08 / 2026-09-18 / 仓库保存 + +- 用户授权:将 R06 配置驱动改造、R07 三轴门控与五轴精确恢复完整保存到指定 Gitea 仓库的 `main` 分支。 +- 保存范围:自有源码、配置、测试脚本、项目文档与接续入口、`coverage/configuration/` 和 `coverage/recovery/` 验收证据。 +- 检查:远程 main 与提交前 HEAD 一致;提交前执行差异格式检查。功能验证沿用 E07/E08/E09/E10,本轮仅更新保存记录。 +- 原有大型 tick trace、linuxcnc 子模块本地改动、临时脚本和用户图片保留在本地,不纳入专项提交。 +- 推送完成以远程 main 与本地 HEAD 的提交 ID 一致为准;实际提交 ID 以 Git 记录为准。专项矩阵无待办任务,后续按新需求推进。 + +## R07 / 2026-09-18 / 三轴门控与五轴精确恢复 + +- 完成 CFG-R01:为直接工具准备/换刀补齐开机、空闲、操作者模式及运动队列检查;三轴综合测试先验证运行中拒绝,再等待实际队列完成。没有删除原断言或放宽急停/故障约束。 +- 完成 TRT-R01:TP 保存完整原生段状态、混合/速度/进度及队列;程序保存解释器参数、调用栈和子程序源;HAL 保存 tick、引脚/参数和组件内部状态;同时保存调度交接、核心及协议/事件状态。恢复不再根据剩余几何重新建队列或累加 HAL tick。 +- 指针与兼容性:TP 队列、程序缓冲区和解释器 owner 在恢复时重新绑定;CMake 生成源码/编译环境指纹;不同构建、不同配置、损坏块及旧的不完整记录明确拒绝。配置 hash 增加完整解析 INI 的 hash,包含回零参数变更。 +- 幂等性:恢复操作回执,重复 M428 不再执行;回执不递归携带旧 checkpoint 和 UI 快照,避免多次保存时内存增长。 +- 浏览器修复:OPFS 按机型隔离,仅冷启动自动恢复匹配记录;不兼容缓存保留并提示重新加载。关机记录省去可重建的预览,恢复后重新生成并校验。App 保留恢复出来的程序,不再覆盖为船体示例。 +- 额外实测修复:identity Jog 的 motion_delta 曾使用上一采样的关节值,导致增量 Jog 结束后模型落后约一个采样。现直接投影当前 WASM 规划器关节值。 +- 改动文件组:`wasm-core/src` 及对应头文件/CMake/单测;`web/worker/native-checkpoint.mjs`、TP/程序/IO 适配器、SimSession、OPFS 桥;`web/app/axis-contract.mjs`、React App;新增精确恢复和浏览器重启测试、验收汇总脚本。 +- 验证:E08/E09/E10。native 23/23;两机型各五个阶段、合计 120 个连续任务 tick 的状态 hash 完全一致;另有 12 个恢复后 Jog 样本。覆盖暂停、子程序、不同堆地址、trace 导入、新传输会话、损坏/不同构建拒绝及真实 OPFS 重启。 +- 本轮证据统一在 `coverage/recovery/`,旧 `coverage/trt-parametric/` 报告保持原版本;前轮配置证据保留。 +- 当前专项矩阵已无待办任务。代码和证据尚未提交/推送;后续按用户保存指令或新需求推进,不重复已通过整轮测试。 + +## R06 / 2026-09-18 / 配置驱动改造 + +- 用户确认范围:机床参数、轴配置、路径、可调界面设置改为配置驱动;保留数学/协议常量及 LinuxCNC 上游代码。 +- 新配置:`machine-catalog.json`、`simulation-resources.json`、`web-settings.json`;新增校验/解析模块 `web-settings.mjs`、`trt-configuration.mjs`。具体键和入口见 07。 +- 运行链路:`sim-case-loader`、`sim-session`、`wasm-linuxcnc-tp`、`wasm-xyzac-kinematics`、`wasm-native-homing`、IO/device-IO、`xyzac-session`;取消固定几何、限位、速度和周期覆盖。TRT 的 IO 时间基准跟随实际 task tick。旧兼容仿真的可调数值归入配置。 +- 核心:自有 `sim_linuxcnc_tp.c/.h` 增加逐轴动力学、倍率、缩放下限、轨迹混合配置入口和边界测试;修正加速度上限取值方向。重建 WASM/native。 +- 界面:App、模型和两个预览、资源路径及类型声明采用配置;支持不同默认机型、INI/程序文件名、图标目录、相机/材质/颜色、Jog 增量、刷新设置。物理计算仍走 WASM。 +- 修复:规划器初始化不再使用上一台机床的快速速度;默认 XYZBC 的会话来源信息同步;通用加载器不再把三轴 Z 上限写成 100;旧队列进给倍率投影使用 WASM 已确认的 controls 值。 +- 恢复:checkpoint 保存运行配置 hash,参数变化或旧记录缺少该 hash 时拒绝恢复,不把旧速度/限位状态带入新配置。 +- 验证:E07,native 23/23、专项 WASM/Worker、改配置的浏览器实际渲染、生产构建、子路径部署和 RTCP 回归通过。旧三轴综合脚本的操作门控问题登记 CFG-R01,不放宽 gate。 +- 证据:统一归档 `coverage/configuration/`,本轮重跑产生的旧测试报告已归档到该目录;`coverage/trt-parametric/` 保留原已提交版本,不破坏 E02/E06 的历史哈希。 +- 接续文件:用户要求后创建 `nextTask-hardcoding.md`;随后用户明确继续,现已更新为收口状态。 +- 未提交/推送;下一开发任务为 CFG-R01,随后 TRT-R01。原有大型 trace、linuxcnc 子模块及临时文件未纳入本轮。 + ## R05 / 2026-09-18 / 仓库保存 - 用户授权:保存到 `https://gitea.meswork.com/wangdequan/linuxcncWebWASM.git` 的 `main` 分支。 diff --git a/docs/trt-parametric/04-任务矩阵.md b/docs/trt-parametric/04-任务矩阵.md index 0d4c870..93649eb 100644 --- a/docs/trt-parametric/04-任务矩阵.md +++ b/docs/trt-parametric/04-任务矩阵.md @@ -10,6 +10,10 @@ | TRT-P04 | 参数化结构投影 | P02 | 已验收 | 旋转中心及偏置驱动两模型;转台路径层级一致 | E02 | | TRT-P05 | 参数编辑及文件流程 | P03,P04 | 已验收 | 分组布局、应用/重新读取/导入/保存、保留程序、错误反馈 | E03 | | TRT-P06 | 回归及证据收口 | P03,P04,P05 | 已验收 | 专项、相关回归、构建、桌面/移动交互截图和像素检查通过;既有恢复偏差独立登记 | E02,E03,E04 | -| TRT-R01 | 既有 checkpoint 恢复状态精度 | P06 | 待办 | 定位 HAL tick 与 TP queue/progress 恢复差异;将恢复测试的相等性变为有效断言,再验证固定 tick 连续与恢复轨迹 | E04:遗留项 | +| TRT-R01 | 既有 checkpoint 恢复状态精度 | P06 | 已验收 | HAL/TP/解释器原生状态恢复;相等性强断言;固定 tick 续跑、暂停、子程序、trace、新会话及浏览器重启通过 | E09,E10 | | TRT-UI01 | 机床预览路径改为黄色 | P04 | 已验收 | 工作台普通刀具预览路径使用黄色材质 | E05 | | TRT-JOG01 | M428 旋转轴 Jog 刀尖跟随 | P03,P04 | 已验收 | A/B/C 连续和增量 Jog 使用 RTCP 补偿;松开、重复 Jog 不丢失模式;WASM 与机床模型刀尖位置一致;M429 对照 | E06 | +| CFG-01 | 机床运行参数由 INI 驱动 | P06 | 已验收 | 实际限位、逐轴速度/加速度、周期随文件改变;无固定机型值覆盖 | E07 | +| CFG-02 | 机型资源及界面配置入口 | CFG-01 | 已验收 | 资源路径与可调界面设置独立配置;子路径部署、非默认设置可用 | E07 | +| CFG-03 | 配置审计与回归 | CFG-01,CFG-02 | 已验收 | 本专项 native/WASM/Worker/UI 验证通过,保留项与范围在 07 记录;旧三轴综合测试单列 | E07 | +| CFG-R01 | 旧三轴综合验收流程与 tool gate | 无 | 已验收 | 运行中拒绝工具命令,空闲且队列完成后允许;急停/关机/故障拒绝;完整三轴流程通过 | E08 | diff --git a/docs/trt-parametric/05-验收证据.md b/docs/trt-parametric/05-验收证据.md index 6a4afcf..cb3e09b 100644 --- a/docs/trt-parametric/05-验收证据.md +++ b/docs/trt-parametric/05-验收证据.md @@ -75,3 +75,55 @@ PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwrigh - 回归通过:`node web/react/src/test_trt_parametric.mjs`、`node web/worker/test_xyzac_trt_case.mjs`、`node web/worker/test_xyzac_program_replace.mjs`、`node web/worker/test_xyzac_runtime_followups.mjs`、`node web/worker/test_boat_trt_programs.mjs`、`npm --prefix web/react run build`。构建保留既有大 chunk 提示。 - `runtime_followups` 初次失败因未等待 HOME 完成便启动程序;现等待 WASM homed,并断言 PROGRAM_LOAD/RUN 成功后再验收。 - job_id / PDF:不适用;使用原有 WASM,无核心 ABI 变更。本轮不声称修复 TRT-R01。 + +## E07 配置驱动改造 / 2026-09-18 + +本轮新增兼容的 WASM 配置 setter,核心已重建。WASM SHA-256:`213b55e64b45c14df5c40e59827fa8bb4903135cd788664bb08263f08a49738f`。 + +| 命令 / 场景 | 实测结果 | 证据 | +| --- | --- | --- | +| `bash scripts/build-wasm.sh` | WASM 测试通过,native 23/23 通过;随后扩展的 TP 配置边界单测也通过 | `/tmp/trt-config-build.log`,native `sim_linuxcnc_tp_test` | +| `node web/worker/test_trt_configuration.mjs` | 两机型 X 实际速度均为 4,实际加速度约 4 且不超过配置 8;周期 .002;axis X 上限 15、joint X 上限 17;非法值、倍率越界拒绝;改速度后拒绝旧 checkpoint | [运行配置报告](../../coverage/configuration/trt-runtime.report.json) | +| `PLAYWRIGHT_MODULE=... node scripts/test_configurable_ui_browser.mjs` | 配置为默认 XYZBC、自定义 INI/程序名、Jog 0.25/2.5、进给上限130%、自定义图标路径;紫背景143449像素、绿色路径85像素,零 pageerror | [浏览器报告](../../coverage/configuration/browser.report.json)、[截图](../../coverage/configuration/configured-ui.png) | +| `npm --prefix web/react run build` | 类型检查及生产构建通过;保留大 chunk 提示 | 构建输出 | +| 生产页面 `http://127.0.0.1:5175/dist/` | 子路径部署加载机床/程序成功,30个图标加载成功,零 pageerror | [子路径报告](../../coverage/configuration/subpath.report.json) | +| `python3 scripts/audit_configuration_inputs.py` | 14 项迁移输入检查通过,记录源码/配置/WASM 哈希及保留类型 | [审计报告](../../coverage/configuration/input-audit.report.json) | +| 真实 WASM 参数化与 RTCP Jog 回归 | 参数链路、两机型各77检查点、零角/单轴/复合姿态、重复停止通过 | [参数回归](../../coverage/configuration/parametric-regression.report.json)、[Jog 回归](../../coverage/configuration/rtcp-jog-regression.report.json) | +| 浏览器实际 A-C-A-C、B-C-B-C 按住/松开 | 两机型各4轮通过,TCP模型误差小于1e-7场景单位,零 pageerror | [报告](../../coverage/configuration/rtcp-jog-browser.report.json)、[XYZAC](../../coverage/configuration/rtcp-jog-xyzac.png)、[XYZBC](../../coverage/configuration/rtcp-jog-xyzbc.png) | + +相关回归命令通过:`node web/worker/test_boat_trt_programs.mjs`(两机型 completed)、`node web/worker/test_xyzac_runtime_followups.mjs`、`node web/worker/test_xyzac_trt_case.mjs`、`node web/worker/test_authoritative_kinematics_s6.mjs`、`node web/worker/test_motion_state.mjs`、`node web/workspace/test_sim_case_loader.mjs`、`node web/react/src/test_xyzac_threejs.mjs`、`node web/react/src/test_xyzac_tool_preview.mjs`。 + +旧 `node web/worker/test_axis3_selected_case.mjs` 未通过:脚本在程序/运动尚未空闲时发主轴命令,被 `axis_gate` 拒绝。诊断期间临时增加等待后发现 TOOL_PREPARE 的 `tool` action 缺少旧兼容 gate 分支;临时测试调整已撤回,正式登记 CFG-R01。本轮不声称全仓历史测试全通过,也不以此删除/放宽控制门控。 + +审计范围是已识别可配置输入,静态检查不是对任意数值字面量的完整证明。旧报告保留在原目录,本轮报告单独归档。截图已经查看;job_id/PDF 不适用。 + +## E08 三轴控制门控收口 / 2026-09-18 + +- `node web/app/test_axis_contract.mjs`:工具准备/换刀在运行、暂停、急停、关机、故障、非空运动队列和 AUTO 操作者模式下拒绝;空闲 MANUAL/MDI 允许交由 IO 状态机检查。来源为 `emctaskmain.cc::emcTaskCheckPreconditions` 的 PREPARE 等待 IO、LOAD 等待 motion+IO。 +- `node web/worker/test_axis3_selected_case.mjs`:完整流程通过,含运行中拒绝、等待真实队列结束、主轴/冷却、工具握手、急停和恢复。 +- `node web/worker/test_wasm_motion_checkpoint.mjs`:三轴原有恢复及 trace 导入回归通过,连续/恢复 hash 相同。 +- 报告:[门控](../../coverage/recovery/axis-gates.report.json)、[三轴流程](../../coverage/recovery/axis3-selected-case.report.json)、[三轴恢复](../../coverage/recovery/axis3-checkpoint.report.json)。E07 的 CFG-R01 遗留已在本轮关闭。 + +## E09 五轴原生恢复精度 / 2026-09-18 + +- `bash scripts/build-wasm.sh`:WASM 测试与 native 23/23 通过,日志 `/tmp/trt-recovery-build.log`。native TP 单测保存后继续 100 个伺服步,再恢复逐步对比;HAL 单测验证 tick、函数调用计数和完整状态字节一致。 +- `node web/worker/test_xyzac_recovery.mjs`:恢复点前后完整 hash 相同;旧的仅打印差异已改成硬断言;实际重发 M428 验证 switch_count 不增加。报告中 baseline/restored 比较相同 checkpoint tick,未重放尾部另列。 +- `node web/worker/test_trt_checkpoint_exact.mjs`:XYZAC/XYZBC 在子程序、暂停恢复、直线、圆弧和程序完成阶段分别比较 12 个连续任务 tick,共 120 个全状态比较,没有 divergent tick;另有 12 个恢复后 Jog 的当前关节采样验证。 +- 同时验证空程序、不同堆布局、外部子程序文件改变时使用快照中冻结的源、trace 导入、新传输会话 RECOVER/RESUME、损坏 native 块和构建不匹配在修改状态前拒绝。 +- `node web/worker/test_xyzac_runtime_followups.mjs`:关机记录重启恢复通过。 +- 报告:[恢复点](../../coverage/recovery/checkpoint-state.report.json)、[连续轨迹](../../coverage/recovery/exact-checkpoint.report.json)、[关机恢复](../../coverage/recovery/power-off.report.json);最终源码构建指纹、WASM SHA 和报告 SHA 见[验收汇总](../../coverage/recovery/acceptance.report.json)。 + +## E10 浏览器真实 OPFS 重启 / 2026-09-18 + +```bash +PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.mjs node scripts/test_checkpoint_reload_browser.mjs +RECOVERY_MACHINE=xyzbc-trt PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.mjs node scripts/test_checkpoint_reload_browser.mjs +``` + +- 页面 `http://127.0.0.1:5174/`;上传真实测试程序、开机/回零/Jog、关机写入 OPFS、页面重载、恢复程序和预览、再次开机增量 Jog。 +- 两机型关机前/重启后关节数组严格相同,保留 `recovery-browser.ngc`,预览重建非空;继续 Jog 精确增加 0.1 mm;无 pageerror。记录约 103 KB,低于已有 1 MiB OPFS 上限。 +- [XYZAC 报告](../../coverage/recovery/browser-reload.report.json)、[截图](../../coverage/recovery/browser-reload.png);[XYZBC 报告](../../coverage/recovery/browser-reload-xyzbc-trt.report.json)、[截图](../../coverage/recovery/browser-reload-xyzbc-trt.png)。 +- `node web/worker/test_opfs_ui_state.mjs`、`node web/worker/test_opfs_worker_bridge.mjs`:双槽位校验、机型隔离、配置重载不注入旧缓存、不兼容记录提示通过,见[桥接报告](../../coverage/recovery/storage-bridge.report.json)。 +- `npm --prefix web/react run build`:类型检查及构建通过,仍有既有大 chunk 提示。 +- 中间浏览器测试发现 identity Jog 最后关节采样滞后,已修复当前 WASM 样本投影;`browser-reload-failure.png` 保留中间失败截图,最终结论以 verified 报告为准。 +- 边界:只允许匹配构建/配置的原生记录;旧不完整记录不近似恢复。TRT checkpoint 的后续事件须作为显式命令执行,不静默丢弃尾部;公开恢复向更早逻辑 tick 回退需要新传输会话,避免改写已有事件历史。OPFS 真实断电崩溃与所有浏览器矩阵不在本次结论内。job_id/PDF 不适用。 diff --git a/docs/trt-parametric/06-决策记录.md b/docs/trt-parametric/06-决策记录.md index 9b20913..61fac71 100644 --- a/docs/trt-parametric/06-决策记录.md +++ b/docs/trt-parametric/06-决策记录.md @@ -37,3 +37,35 @@ XYZAC 不支持 x-offset,XYZBC 不支持 y-offset。六项都可见,但不 JOG_STOP/abort 停止运动不能隐式执行 M429。保留停止姿态对应的运动学模式,只清理待执行段和历史 ID 映射;重新初始化使用原有 reset 路径。Jog 的 native tag 是段 ID,与运动学模式不同,必须关联本段入队时的当前模式,不能沿用同编号的历史程序行。 RTCP 的判据为:旋转 Jog 时刀尖在工件坐标中的 XYZ 不变,机器 XYZ 按 WASM 逆解联动;Three.js 用相同关节投影。若刀尖恰好在 C 旋转轴上,单独转 C 可没有 XYZ 位移,这属于正确几何行为;验收特意先移动 X/Y 离开旋转中心。 + +## D10 / 2026-09-18 / 硬编码清理的确认范围 + +用户确认:项目自有代码中的机床参数、轴配置、路径和可调界面设置使用配置;保留数学常量、协议定义和上游 LinuxCNC。代码资产的固定网格拓扑、CSS/图标、测试输入和生成来源索引不通过机械替换数字来“清零”。配置改动必须能改变真实行为,不能只迁移到另一个常量表。 + +INI 是机床物理参数来源;JSON 是 Web 行为、资源注册和外观配置来源。图形外观参数不参与逆解。必需 INI 参数缺失即报错;可选原生规划策略/仿真 IO 参数使用有名称的配置默认值。 + +## D11 / 2026-09-18 / 完整运行配置和恢复 + +区分 AXIS 和 JOINT 限位,逐轴配置动力学;全局加速度上限以及轴上限均取约束较小者。新 C setter 是兼容增量导出;公开旧 ABI 函数签名保持,WASM 与前端应同时重新构建。 + +TASK/伺服周期必须为整数倍,IO/device-IO 按实际调度的 task 周期推进,避免模拟时钟分叉。恢复除几何外,还比较运行配置 hash;旧无 hash 的存档明确拒绝,不猜测它对应哪套限位和速度。 + +## D12 / 2026-09-18 / 验收与遗留项 + +配置专项的通过与全仓历史验收区分。三轴综合脚本的状态流程/工具门控列 CFG-R01;TP 恢复精度仍是 TRT-R01。保留真实失败信息。本轮新回归证据归档到 coverage/configuration,保留旧目录的原报告,以维持历史哈希可核验。 + +## D13 / 2026-09-18 / 原生恢复不重建剩余轨迹 + +只保存段端点/最终位置不能恢复 TP 的积分、混合、优化、队列进度。采用原生 TP/TC 快照,并保存解释器 side table、调用栈、HAL 内部状态及 Worker 的交接信息。TP 队列和程序缓冲区指针去除后重新绑定,用不同堆布局验证;源码/编译环境指纹限制跨构建读取。旧的缺失原生状态记录明确拒绝。 + +保存主程序及子程序输入闭包,恢复先在临时程序实例中验证后替换;不会因工作区子程序后来修改而改变已保存任务。机床配置 hash 包含解析 INI 的完整 hash,回零参数等变化同样使旧记录失效。 + +## D14 / 2026-09-18 / 恢复的传输与幂等边界 + +精确恢复和后续命令执行分开:TRT 不再静默忽略 checkpoint 后事件。新会话可重设传输序号并续跑;既有会话不能以公开 RECOVER 改写更晚的历史 tick,需新建传输会话。恢复操作回执用于阻止重复副作用,去掉回执中递归嵌套的 checkpoint/UI 大对象;重复操作投影当前机床状态。 + +## D15 / 2026-09-18 / 浏览器自动恢复 + +按机型分开保存,只在冷启动自动使用匹配配置和构建的记录。显式配置重载、机型切换以本次输入为准。不兼容缓存不删除,提示按当前配置初始化。 + +关机记录省去可由 WASM 重建的预览,并校验重建 hash;前端保持恢复出的程序文本和路径,不再次用默认示例覆盖。存储容量边界仍保留,过大记录须报告 quota,不声明已保存。运动增量投影直接使用当前规划器关节样本,避免 identity 模式下的最后一个采样滞后。 diff --git a/docs/trt-parametric/07-配置驱动改造.md b/docs/trt-parametric/07-配置驱动改造.md new file mode 100644 index 0000000..3ff0152 --- /dev/null +++ b/docs/trt-parametric/07-配置驱动改造.md @@ -0,0 +1,33 @@ +# 配置驱动改造 + +2026-09-18 用户确认范围:项目自有代码中的机床参数、轴配置、路径和可调界面设置改为配置驱动;保留算法数学常量、协议定义和 LinuxCNC 上游源码。 + +## 配置入口 + +| 内容 | 输入来源 | 生效方式 | +| --- | --- | --- | +| TRT 几何、关节/轴限位、逐轴速度与加速度、周期、进给倍率上限 | 当前机床 INI:HAL、KINS、TRAJ、JOINT_n、AXIS_n、EMCMOT、TASK、DISPLAY | 关机空闲后重新读取 INI,Worker 再解析,再创建 WASM 实例 | +| 圆弧混合策略 | INI `[TRAJ] ARC_BLEND_*`;缺项使用 web-settings.json 的 runtime.planner.blend | INI 重载 | +| 机型入口、默认机型、程序与模型资源路径 | `web/workspace/machine-catalog.json` | 开发服务刷新;发布构建重新打包 | +| 案例适配器、HAL 资源映射、预加载组件 | `web/workspace/simulation-resources.json` | 同上 | +| Web 刷新节奏、逻辑采样批量、Jog 增量、初始图层、缩放范围 | `web/workspace/web-settings.json` 的 ui | 同上;这不是改变伺服周期的入口 | +| 预览颜色、相机、材质、显示单位比例、模型基准高度 | web-settings.json 的 viewer | 同上;只影响图形表现,不参与数控逆解 | +| 仿真 IO 容量与响应参数 | INI 可选 `[WEB_RUNTIME]`;缺项使用 web-settings.json 的 runtime.io | INI 重载;配置中的键名用大写,例如 SPINDLE_COUNT | +| 旧三轴/Delta/Tripod 兼容仿真默认参数 | web-settings.json 的 runtime.compatibility | 旧兼容入口;不用于 TRT WASM 计算 | + +## 约束与验收 + +- 同一值只保留一个输入来源。Worker 不信任调用者另外传来的 limits、周期和速度副本,重新从文件解析。 +- 无效、缺失、非有限值和不支持的配置拒绝加载,不能回退到 XYZAC 示例值。 +- AXIS 范围用于编程坐标 Jog,JOINT 范围用于运动学输出/回零检查,二者不能混用。不同轴有独立速度及加速度。 +- 当前固定 tick 执行器要求 TASK/CYCLE_TIME 为 SERVO_PERIOD 的整数倍,不再静默四舍五入。 +- IO/device-IO 的 cycle_time 跟随 task 周期;`SPINDLE_TOLERANCE` 是主轴到速容差,允许为 0,不是换刀延时。 +- 机型可通过 catalog 的 runtime_fixture 指定资源适配配置,自定义入口文件名不必修改加载器;相关资源仍须完整提供并通过 preflight。 +- JSON 修改在开发服务中刷新生效;静态发布包需重新构建。界面配置没有额外的磁盘写入 API。 +- LinuxCNC 上游源码未改;自有 C 适配层新增逐轴动力学和倍率上限 setter,原有 ABI 参数和协议编号不变,需要重建 WASM。 +- 数学常数、单位换算、位掩码、ABI 字段布局、状态枚举、误差阈值、模型原始网格拓扑、图标/CSS 资源定义及固定测试输入不是可调机床参数,不机械替换成无意义变量。 +- 历史报告、生成源索引、版本锁和第三方依赖锁不当作运行配置编辑。 + +验收必须包含修改 INI 后的实际运动速度、加速度约束、限位、周期,以及前端修改设置后的渲染变化;不能仅检查读取到的 JSON。 + +CFG-01/02/03 专项验收见 E07;接续的 CFG-R01、TRT-R01 也已按 E08/E09/E10 收口。恢复兼容边界见 D13/D14/D15;本专项通过不等于全仓全功能验收完成。 diff --git a/docs/trt-parametric/README.md b/docs/trt-parametric/README.md index 90b708d..2819fe3 100644 --- a/docs/trt-parametric/README.md +++ b/docs/trt-parametric/README.md @@ -4,6 +4,8 @@ ## 读取顺序 +当前配置改造及接续任务 CFG-R01、TRT-R01 均已专项验收。重启后先读 [`nextTask-hardcoding.md`](../../nextTask-hardcoding.md);配置入口见 [07](07-配置驱动改造.md),最新验收见 E08/E09/E10。 + 1. 本索引 → [04-任务矩阵](04-任务矩阵.md):选择首个依赖满足且未验收的任务。 2. [03-推进台账](03-推进台账.md):只读最新一轮和下一步。 3. 按任务读取 [01-项目功能内容](01-项目功能内容.md)、[02-项目程序开发详细步骤](02-项目程序开发详细步骤.md) 和直接相关代码。 @@ -11,7 +13,7 @@ 每轮必须记录实际改动、命令、结果和下一任务。未运行的检查不能记为通过;历史通过任务仅在相关代码改变时回归。此目录是本专项接续入口,历史仿真/发布轨道仍保留原有指针,不据此扩大发布范围。 -当前状态:`TRT-P01` 至 `TRT-P06`、`TRT-UI01`、`TRT-JOG01` 已验收。最新修复为 M428 下停止 Jog 后保留 RTCP,避免随后 A/B/C Jog 丢失 XYZ 补偿。下一项为矩阵中的 `TRT-R01`,属于既有恢复精度问题,不影响本专项参数应用流程。实现不依赖聊天记录。用户文字提及 Image #1,但本轮未收到可读取的图片资源,布局按现有结构预览实现。 +当前状态:`TRT-P01` 至 `TRT-P06`、`TRT-UI01`、`TRT-JOG01` 已验收。最新修复为 M428 下停止 Jog 后保留 RTCP,避免随后 A/B/C Jog 丢失 XYZ 补偿。配置改造 CFG-01/02/03 也已专项验收;CFG-R01 三轴门控和 TRT-R01 恢复精度也已收口,本专项暂无待办项。实现不依赖聊天记录。用户文字提及 Image #1,但本轮未收到可读取的图片资源,布局按现有结构预览实现。 本地体验地址:`http://127.0.0.1:5174/`。启动命令:`npm --prefix web/react run dev -- --port 5174 --strictPort`;该端口被占用时选择其他端口。 diff --git a/nextTask-hardcoding.md b/nextTask-hardcoding.md new file mode 100644 index 0000000..2fcd4a5 --- /dev/null +++ b/nextTask-hardcoding.md @@ -0,0 +1,48 @@ +# 配置驱动与恢复改造接续入口 + +更新时间:2026-09-18。用户已确认清理自有代码的机床参数、轴配置、路径、可调界面设置硬编码,保留数学/协议常量和上游 LinuxCNC;之后要求接续 CFG-R01、TRT-R01。以上专项均已收口。 + +## 当前状态 + +- 任务矩阵:`docs/trt-parametric/04-任务矩阵.md`;CFG-01/02/03、CFG-R01、TRT-R01 均为已验收。 +- 最新台账 R08(仓库保存)、证据 E08/E09/E10、决策 D13/D14/D15;R06 配置改造的历史证据仍保留。 +- 用户已于 2026-09-18 授权将 **R06 配置改造与 R07 精确恢复的完整依赖** 保存到指定仓库 main;本轮为该保存提交,实际提交 ID 以 Git 记录为准。 +- 验收汇总:`coverage/recovery/acceptance.report.json`;包含真实 WASM SHA、native 构建指纹和证据文件 SHA。 +- 本专项暂无未完成任务。后续按新需求推进;不要重跑已通过整轮测试,也不要自动认领其他历史发布轨道的阻塞任务。 + +## 实现与验证落点 + +1. 配置:`web/workspace/machine-catalog.json`、`simulation-resources.json`、`web-settings.json` 和 `trt-configuration.mjs`;完整说明见 `07-配置驱动改造.md`。INI 输入包含运行参数及回零信息的完整 hash;Worker 从文件重新解析。 +2. 三轴:`web/app/axis-contract.mjs` 和 `test_axis3_selected_case.mjs`。工具准备/换刀在开机、空闲、操作者模式、队列为空时交由 IO 状态机处理;运行、急停、故障等拒绝,未删除旧断言。 +3. 恢复:自有 C 适配层新增 TP/TC、解释器/程序、HAL、核心及协议/事件快照 API;`web/worker/native-checkpoint.mjs` 做带构建指纹、长度/hash 校验的稀疏块编解码;数学计算仍来自 WASM。 +4. 程序恢复:保存参数、调用栈及冻结子程序源,验证候选程序后替换;TP 原生队列/进度/混合状态直接恢复。`sim-session.mjs` 保存待处理运动/切换等交接信息;HAL 不再重复 step。 +5. 幂等/内存:checkpoint 回执剔除递归旧快照,保留操作身份;恢复后重复 M428 不重新执行,返回当前机床投影。 +6. 浏览器:OPFS 按机型隔离,只有冷启动自动采用兼容缓存;配置重载不覆盖当前输入。省去可重建预览,重建后验证 hash;App 保留恢复出来的程序而非重新加载船体示例。 +7. 新发现并修复:identity Jog 的增量响应使用当前 WASM 规划器关节,避免最后采样落后。 + +## 已完成验证 + +- `bash scripts/build-wasm.sh`:WASM 测试通过,native 23/23;native TP 保存后继续 100 个伺服步逐步对比,HAL tick/函数计数和字节恢复验证。日志 `coverage/recovery/build.log`。 +- `node web/worker/test_trt_checkpoint_exact.mjs`:两机型五阶段共120个连续任务tick全状态hash一致,另12个 Jog 样本;覆盖不同堆地址、暂停/子程序/圆弧/结束、空程序、trace、新传输会话、冻结子程序、损坏块和不同构建拒绝。 +- `node web/worker/test_xyzac_recovery.mjs`:同一恢复点完整hash相同,实际重发 M428 不重复切换。 +- `node web/worker/test_axis3_selected_case.mjs`、`node web/app/test_axis_contract.mjs`、`node web/worker/test_wasm_motion_checkpoint.mjs`:通过。 +- `node web/worker/test_xyzac_runtime_followups.mjs`、`node web/worker/test_opfs_ui_state.mjs`、`node web/worker/test_opfs_worker_bridge.mjs`、`node web/worker/test_trt_configuration.mjs`:通过。 +- `scripts/test_checkpoint_reload_browser.mjs`:真实 OPFS 关机保存/页面重载恢复,两机型位置严格相同,原程序/预览保留,之后 Jog 正常。XYZBC 使用 `RECOVERY_MACHINE=xyzbc-trt`。 +- `scripts/test_trt_rtcp_jog_browser.mjs`:两机型 A/B/C Jog 回归通过;产物已归档 `coverage/recovery/`。 +- `npm --prefix web/react run build`:通过,已有大 chunk 提示保留。 +- Playwright 环境:`PLAYWRIGHT_MODULE=/home/mes006/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.mjs`;Chrome `/usr/bin/google-chrome`。 + +## 已确定的恢复边界 + +- 原生记录只允许匹配构建和配置。旧缺失原生状态的记录不能近似恢复;自动缓存不兼容时提示并按当前配置启动,原缓存不删除。 +- TRT checkpoint 后的事件不能静默忽略:须恢复后显式执行后续命令。公开 RECOVER 向更早逻辑tick回退需要新传输会话,避免改写历史;原生状态可在新会话重设传输序号后继续执行。 +- OPFS 原有1MiB限制仍在,超限须报告quota;不宣称物理断电崩溃和全浏览器矩阵完成。 +- 本次是专项验收,不等于全 LinuxCNC parity,不更改历史 release gate/context lock。 + +## 工作区与保存 + +- 开发地址 `http://127.0.0.1:5174/`;不存在时运行 `npm --prefix web/react run dev -- --port 5174 --strictPort`。 +- 最新代码必须与重建的 WASM 配套;构建产物在 Git ignore 中,保存源码后其他环境需运行 build-wasm.sh。 +- `coverage/configuration/`、`coverage/recovery/` 是本轮证据;`coverage/trt-parametric/` 保持先前已提交历史版本,不破坏历史哈希。 +- 两份大型 tick trace、linuxcnc 子模块原有本地改动、`scripts/.tmp_test_nc_files_xyzac.mjs`、用户 Copy.png 不属于本轮,保留原样,不混入提交。 +- 指定远程为 `https://gitea.meswork.com/wangdequan/linuxcncWebWASM.git`。本轮保存已获授权,推送后核验远端 main 与本地 HEAD 一致;账号口令不写入仓库。 diff --git a/scripts/audit_configuration_inputs.py b/scripts/audit_configuration_inputs.py new file mode 100644 index 0000000..874cd41 --- /dev/null +++ b/scripts/audit_configuration_inputs.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Audit the configuration input migration, without classifying every literal as an error.""" +import hashlib +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def main(): + sources = { + "machine_catalog": "web/workspace/machine-catalog.json", + "resource_catalog": "web/workspace/simulation-resources.json", + "web_settings": "web/workspace/web-settings.json", + "ini_reader": "web/workspace/trt-configuration.mjs", + "worker": "web/worker/sim-session.mjs", + "tp_adapter": "web/worker/wasm-linuxcnc-tp.mjs", + "tp_core": "wasm-core/src/sim_linuxcnc_tp.c", + "kinematics_adapter": "web/worker/wasm-xyzac-kinematics.mjs", + "case_loader": "web/workspace/sim-case-loader.mjs", + "model": "web/react/src/xyzac-machine-model.ts", + "machine_view": "web/react/src/XyzacMachineView.tsx", + } + content = {key: (ROOT / path).read_text() for key, path in sources.items()} + checks = { + "machine_catalog_parses": bool(json.loads(content["machine_catalog"])["machines"]), + "resource_catalog_parses": bool(json.loads(content["resource_catalog"])["runtime_fixtures"]), + "settings_parse": bool(json.loads(content["web_settings"])["viewer"]), + "worker_reparses_ini": "...readTrtConfiguration(configuration)" in content["worker"], + "axis_limits_from_ini": 'limits(`AXIS_${axis.toUpperCase()}`)' in content["ini_reader"], + "joint_limits_from_ini": 'limits(`JOINT_${joint}`)' in content["ini_reader"], + "no_kinematics_example_limits": "DEFAULT_LIMITS" not in content["kinematics_adapter"], + "no_loader_fixed_timing": "servo_cycle_time: 0.001" not in content["case_loader"], + "no_loader_axis3_override": "[-240, 100]" not in content["case_loader"], + "native_axis_dynamics_input": "sim_linuxcnc_tp_set_axis_dynamics" in content["tp_core"] and "sim_linuxcnc_tp_set_axis_dynamics" in content["tp_adapter"], + "no_native_xyzac_speed_override": "fmin(20.0, velocity_limit)" not in content["tp_core"] and "fmin(30.0, velocity_limit)" not in content["tp_core"], + "native_acceleration_upper_bound": "*acceleration = fmin" in content["tp_core"] and "*acceleration = fmax" not in content["tp_core"], + "model_uses_visual_configuration": "WEB_SETTINGS.viewer.models" in content["model"] and "WEB_SETTINGS.viewer.materials" in content["model"], + "machine_path_color_configured": "VIEWER.colors.path" in content["machine_view"], + } + report = { + "report_id": "configuration-input-audit-v1", + "status": "verified" if all(checks.values()) else "failed", + "scope": "configurable inputs; not a zero-literals claim", + "checks": checks, + "sources": [{"path": path, "sha256": hashlib.sha256((ROOT / path).read_bytes()).hexdigest()} for path in sources.values()], + "wasm_sha256": hashlib.sha256((ROOT / "build-wasm/linuxcnc-sim-core.wasm").read_bytes()).hexdigest(), + "retained": ["mathematical constants and tolerances", "ABI layouts, enums, and capacities", "source model mesh topology", "CSS/icon assets", "test fixtures and historical evidence", "upstream LinuxCNC and dependency locks"], + } + output = ROOT / "coverage/configuration/input-audit.report.json" + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(report, indent=2) + "\n") + print(json.dumps({"report_id": report["report_id"], "status": report["status"], "checks": len(checks)})) + if report["status"] != "verified": + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_recovery_acceptance.mjs b/scripts/generate_recovery_acceptance.mjs new file mode 100644 index 0000000..4b71226 --- /dev/null +++ b/scripts/generate_recovery_acceptance.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { readFile, writeFile } from "node:fs/promises"; +import createModule from "../build-wasm/linuxcnc-sim-core.js"; + +const root = new URL("../", import.meta.url); +const names = ["axis3-selected-case.report.json", "axis-gates.report.json", "axis3-checkpoint.report.json", "checkpoint-state.report.json", "exact-checkpoint.report.json", "power-off.report.json", "storage-bridge.report.json", "browser-reload.report.json", "browser-reload-xyzbc-trt.report.json"]; +const evidence = []; +for (const name of names) { + const path = `coverage/recovery/${name}`; + const bytes = await readFile(new URL(path, root)); + const report = JSON.parse(bytes); + assert.equal(report.status, "verified", path); + evidence.push({ path, sha256: createHash("sha256").update(bytes).digest("hex") }); +} +const wasmBinary = await readFile(new URL("build-wasm/linuxcnc-sim-core.wasm", root)); +const core = await createModule({ wasmBinary }); +const pointer = core._sim_checkpoint_build_id(); +const buildId = new TextDecoder().decode(core.HEAPU8.subarray(pointer, pointer + 64)); +assert.match(buildId, /^[a-f0-9]{64}$/); +const exact = JSON.parse(await readFile(new URL("coverage/recovery/exact-checkpoint.report.json", root))); +const report = { + report_id: "cfg-r01-trt-r01-acceptance-v1", status: "verified", + tasks: ["CFG-R01", "TRT-R01"], + native_build_id: buildId, + wasm_sha256: createHash("sha256").update(wasmBinary).digest("hex"), + checkpoint_sizes: Object.fromEntries(["sim_core", "sim_protocol", "sim_event", "sim_program", "sim_linuxcnc_tp", "sim_hal"].map((name) => [name, core[`_${name}_checkpoint_size`]() ])), + continuation_samples: exact.rows.filter((row) => row.phase !== "post-recovery-jog").reduce((sum, row) => sum + (row.samples ?? 0), 0), + first_divergent_tick: null, + boundaries: ["matching native build and machine configuration required", "old snapshots lacking native state are rejected", "TRT checkpoint event tails require explicit subsequent commands", "backward public recovery requires a fresh transport session", "OPFS retains its existing quota; no physical crash or cross-browser parity claim"], + evidence, +}; +await writeFile(new URL("coverage/recovery/acceptance.report.json", root), JSON.stringify(report, null, 2) + "\n"); +console.log(JSON.stringify({ report_id: report.report_id, status: report.status, continuation_samples: report.continuation_samples, evidence: evidence.length, native_build_id: buildId })); diff --git a/scripts/test_checkpoint_reload_browser.mjs b/scripts/test_checkpoint_reload_browser.mjs new file mode 100644 index 0000000..6bf66af --- /dev/null +++ b/scripts/test_checkpoint_reload_browser.mjs @@ -0,0 +1,75 @@ +import assert from "node:assert/strict"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href); +const output = new URL("../coverage/recovery/", import.meta.url); +await mkdir(output, { recursive: true }); +const browser = await chromium.launch({ executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }); +const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); +const selectedMachine = process.env.RECOVERY_MACHINE ?? "xyzac-trt"; +const suffix = selectedMachine === "xyzac-trt" ? "" : `-${selectedMachine}`; +if (selectedMachine !== "xyzac-trt") { + const catalog = JSON.parse(await readFile(new URL("../web/workspace/machine-catalog.json", import.meta.url), "utf8")); + catalog.default_machine = selectedMachine; + await page.route(/machine-catalog\.json\?import/, (route) => route.fulfill({ contentType: "text/javascript", body: `export default ${JSON.stringify(catalog)};` })); +} +const errors = []; page.on("pageerror", (error) => errors.push(error.message)); +await page.addInitScript(() => { + window.recoveryMessages = []; + const NativeWorker = window.Worker; + window.Worker = class extends NativeWorker { + constructor(...args) { + super(...args); + this.addEventListener("message", ({ data }) => { + if (!data.payload?.command) return; + window.recoveryMessages.push({ command: data.payload.command, kind: data.kind, status: data.payload.status, persistence: data.payload.recovery_persistence, recovery: data.payload.power_off_recovery?.status }); + }); + } + }; +}); +const control = (id) => page.locator(`[data-axis-control="${id}"]`); +const ready = () => page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramLoading === "false"); +const joints = () => page.locator(".xyzac-machine-view").evaluate((node) => JSON.parse(node.dataset.xyzacJoints)); +const report = { report_id: "checkpoint-browser-reload-v1", machine: selectedMachine, status: "running" }; +try { + await page.goto(process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5174/"); await ready(); + await control("program-file-input").setInputFiles({ name: "recovery-browser.ngc", mimeType: "text/plain", buffer: Buffer.from("G21 G90\nG1 X8 Y3 Z2 F600\nM2\n") }); + await page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramPath?.endsWith("recovery-browser.ngc") && document.querySelector("main")?.dataset.axisProgramLoading === "false"); + await control("toolbar-power").click(); await control("manual-home-all").click(); + await page.waitForFunction(() => !document.querySelector('[data-axis-control="manual-axis-x"]').disabled); + await control("manual-axis-x").check(); + const button = await control("manual-jog-positive").boundingBox(); + await page.mouse.move(button.x + button.width / 2, button.y + button.height / 2); await page.mouse.down(); + await page.waitForFunction(() => JSON.parse(document.querySelector(".xyzac-machine-view").dataset.xyzacJoints)[0] > 2); + await page.mouse.up(); + await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "JOG_STOP")); + report.before = await joints(); + await control("toolbar-power").click(); + await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "POWER" && row.persistence?.status === "verified" && row.persistence?.bytes > 0)); + report.saved = await page.evaluate(() => window.recoveryMessages.filter((row) => row.command === "POWER" && row.persistence?.bytes > 0).at(-1).persistence); + await page.reload(); await ready(); + await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "LOAD_WORKSPACE" && row.recovery === "verified")); + report.after = await joints(); + report.preview_segments = Number(await page.locator(".xyzac-tool-preview").getAttribute("data-xyzac-toolpath-count")); + assert.ok(report.preview_segments > 0, "restore must publish the regenerated preview"); + assert.deepEqual(report.after, report.before); + assert.match(await page.locator("main").getAttribute("data-axis-program-path"), /recovery-browser\.ngc$/); + assert.equal(await page.evaluate(() => window.recoveryMessages.filter((row) => row.command === "PROGRAM_LOAD").length), 0, "startup must not overwrite the recovered program with the packaged demo"); + await control("toolbar-power").click(); + await control("manual-jog-increment").selectOption("0.1000"); + await control("manual-axis-x").check(); await control("manual-jog-positive").click(); + await page.waitForFunction((start) => JSON.parse(document.querySelector(".xyzac-machine-view").dataset.xyzacJoints)[0] >= start + .0999, report.after[0]); + report.continued = await joints(); + assert.deepEqual(errors, []); report.status = "verified"; + await page.screenshot({ path: new URL(`browser-reload${suffix}.png`, output).pathname, fullPage: true }); +} catch (error) { + report.status = "failed"; report.error = String(error.stack ?? error); + report.messages = await page.evaluate(() => window.recoveryMessages); + await page.screenshot({ path: new URL(`browser-reload-failure${suffix}.png`, output).pathname, fullPage: true }); + throw error; +} finally { + report.browser_errors = errors; + await writeFile(new URL(`browser-reload${suffix}.report.json`, output), JSON.stringify(report, null, 2) + "\n"); + await browser.close(); +} +console.log(JSON.stringify(report)); diff --git a/scripts/test_configurable_ui_browser.mjs b/scripts/test_configurable_ui_browser.mjs new file mode 100644 index 0000000..77f807b --- /dev/null +++ b/scripts/test_configurable_ui_browser.mjs @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import { readFile, writeFile, mkdir } from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href); +const root = new URL("../", import.meta.url); +const settings = JSON.parse(await readFile(new URL("web/workspace/web-settings.json", root), "utf8")); +const catalog = JSON.parse(await readFile(new URL("web/workspace/machine-catalog.json", root), "utf8")); +settings.ui.jog_increments = ["0.25", "2.5"]; +settings.viewer.colors.background = "#550055"; +settings.viewer.colors.path = "#00ff00"; +settings.assets.axis_images = "configured-icons"; +catalog.default_machine = "xyzbc-trt"; +const sourceIni = catalog.machines[1].entrypoint; +catalog.machines[1].entrypoint = sourceIni.replace(/[^/]+$/, "configured-machine.ini"); +catalog.machines[1].program = catalog.machines[1].program.replace(/[^/]+$/, "configured-preview.ngc"); +const browser = await chromium.launch({ executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }); +const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); +const errors = []; page.on("pageerror", (error) => errors.push(error.message)); +const configRequests = [], iconRequests = []; +await page.route(/web-settings\.json\?import|machine-catalog\.json\?import/, async (route) => { + configRequests.push(route.request().url()); + const value = route.request().url().includes("web-settings.json") ? settings : catalog; + await route.fulfill({ contentType: "text/javascript", body: `export default ${JSON.stringify(value)};` }); +}); +await page.route("**/configured-icons/*", async (route) => { + iconRequests.push(route.request().url()); + const name = new URL(route.request().url()).pathname.split("/").at(-1); + const response = await route.fetch({ url: new URL(`/linuxcnc/share/axis/images/${name}`, route.request().url()).href }); + await route.fulfill({ response }); +}); +await page.route("**/configured-machine.ini", async (route) => { + const response = await route.fetch({ url: new URL(`/${sourceIni}`, route.request().url()).href }); + const content = (await response.text()).replace(/MAX_FEED_OVERRIDE\s*=\s*[^\r\n]+/, "MAX_FEED_OVERRIDE = 1.3"); + await route.fulfill({ response, body: content }); +}); +await page.route("**/configured-preview.ngc", (route) => route.fulfill({ contentType: "text/plain", body: "G21 G90\nG0 X-30 Y-30 Z20\nG1 X30 Y-30 F600\nY30\nX-30\nY-30\nM2\n" })); +try { + await page.goto(process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5174/"); + await page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramLoading === "false"); + assert.equal(await page.locator(".xyzac-machine-view").getAttribute("data-xyzac-model-id"), "xyzbc-trt"); + assert.equal(await page.locator("main").getAttribute("data-axis-program-path"), catalog.machines[1].program); + assert.deepEqual(await page.locator('[data-axis-control="manual-jog-increment"] option').allTextContents(), ["Continuous", "0.25", "2.5"]); + assert.equal(await page.locator('[data-axis-control="override-feed"]').getAttribute("max"), "130"); + assert.ok(configRequests.length >= 2); assert.ok(iconRequests.length > 10); + const colors = await page.locator('.xyzac-machine-view canvas[data-engine]').evaluate(async (canvas) => { + await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))); + const gl = canvas.getContext("webgl2"); const data = new Uint8Array(canvas.width * canvas.height * 4); + gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, data); + let purple = 0, green = 0; + for (let i = 0; i < data.length; i += 4) { + if (data[i] > 30 && data[i + 2] > 30 && data[i + 1] < 10) purple++; + if (data[i + 1] > 150 && data[i + 1] > data[i] + 30 && data[i + 1] > data[i + 2] + 30) green++; + } + return { purple, green }; + }); + assert.ok(colors.purple > 1000, JSON.stringify(colors)); + await page.screenshot({ path: new URL("coverage/configuration/configured-ui.png", root).pathname, fullPage: true }); + assert.ok(colors.green > 0, JSON.stringify(colors)); + assert.deepEqual(errors, []); + const output = new URL("coverage/configuration/", root); await mkdir(output, { recursive: true }); + await page.screenshot({ path: new URL("configured-ui.png", output).pathname, fullPage: true }); + const report = { report_id: "configurable-ui-browser-v1", status: "verified", default_machine: catalog.default_machine, entrypoint: catalog.machines[1].entrypoint, program: catalog.machines[1].program, increments: settings.ui.jog_increments, feed_max: 1.3, colors, configured_icons: iconRequests.length, browser_errors: errors }; + await writeFile(new URL("browser.report.json", output), JSON.stringify(report, null, 2) + "\n"); + console.log(JSON.stringify(report)); +} finally { await browser.close(); } diff --git a/wasm-core/CMakeLists.txt b/wasm-core/CMakeLists.txt index 990d7e7..369de26 100644 --- a/wasm-core/CMakeLists.txt +++ b/wasm-core/CMakeLists.txt @@ -300,11 +300,11 @@ if(EMSCRIPTEN) set_target_properties(sim_core_module PROPERTIES OUTPUT_NAME "linuxcnc-sim-core" SUFFIX ".js" - LINK_FLAGS "-sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,worker -sEXPORTED_FUNCTIONS=['_malloc','_free','_sim_create','_sim_load_workspace','_sim_command','_sim_step','_sim_read_snapshot','_sim_read_event','_sim_export_state','_sim_import_state','_sim_destroy','_sim_protocol_reset','_sim_protocol_accept','_sim_protocol_pause','_sim_protocol_resume','_sim_protocol_state_value','_sim_protocol_last_seq','_sim_protocol_tick','_sim_protocol_set_tick','_sim_event_reset','_sim_event_append','_sim_event_count','_sim_event_cursor','_sim_event_read','_sim_event_export','_sim_event_import','_sim_task_size','_sim_task_init','_sim_task_reset','_sim_task_attach','_sim_task_set_command','_sim_task_set_tick','_sim_task_set_mode','_sim_task_set_program','_sim_task_transition','_sim_task_fault','_sim_task_estop','_sim_task_recover','_sim_task_restore','_sim_task_read_state','_sim_task_canonical','_sim_task_state_hash','_sim_scheduler_size','_sim_scheduler_init','_sim_scheduler_reset','_sim_scheduler_step','_sim_scheduler_restore','_sim_scheduler_read','_sim_scheduler_tick','_sim_scheduler_state_hash','_sim_scheduler_canonical','_sim_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_set_param','_sim_hal_get_param','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_sim_hal_register_component','_sim_hal_set_function_failure','_sim_hal_step','_sim_hal_tick','_sim_hal_function_calls','_sim_hal_pin_count','_sim_hal_thread_count','_sim_hal_function_count','_sim_hal_component_count','_sim_hal_param_count','_sim_device_init','_sim_device_reset','_sim_device_encoder_set_speed','_sim_device_encoder_set_config','_sim_device_encoder_set_index_enable','_sim_device_coolant_command','_sim_device_coolant_set_fault','_sim_device_door_set','_sim_device_interlock_set','_sim_device_interlock_reset','_sim_device_estop_set','_sim_device_step','_sim_device_ack_issue','_sim_device_ack_ack','_sim_device_ack_abort','_sim_device_ack_step','_sim_device_state_hash','_sim_device_canonical','_sim_posemath_normalize','_sim_posemath_dot','_sim_posemath_line_point','_sim_posemath_is_finite','_sim_posemath_pose_is_finite','_sim_posemath_pose_line_point','_sim_posemath_segment_validate','_sim_posemath_segment_point','_sim_posemath_hash_state','_sim_tp_init','_sim_tp_reset','_sim_tp_enqueue','_sim_tp_step','_sim_tp_read_state','_sim_tp_state_hash','_sim_interpreter_size','_sim_interpreter_init','_sim_interpreter_reset','_sim_interpreter_execute','_sim_interpreter_read_state','_sim_interpreter_read_motion','_sim_interpreter_motion_size','_sim_interpreter_canonical','_sim_interpreter_state_hash','_sim_interpreter_block_hash','_sim_interpreter_modal_hash','_sim_interpreter_error_column','_sim_interpreter_error_code','_sim_interpreter_parameter_set','_sim_interpreter_parameter_get','_sim_motion_init','_sim_motion_size','_sim_motion_command_size','_sim_motion_state_size','_sim_motion_reset','_sim_motion_enqueue','_sim_motion_step','_sim_motion_read_state','_sim_motion_queue_depth','_sim_motion_state_hash','_sim_motion_canonical','_sim_kinematics_init','_sim_kinematics_forward','_sim_kinematics_forward_ex','_sim_kinematics_inverse','_sim_kinematics_inverse_ex','_sim_kinematics_home','_sim_kinematics_validate_pose','_sim_kinematics_state_hash','_sim_kinematics_switch_init','_sim_kinematics_switch_register','_sim_kinematics_switch_begin','_sim_kinematics_switch_abort','_sim_kinematics_switch_commit','_sim_kinematics_switch_select','_sim_kinematics_switch_forward','_sim_kinematics_switch_inverse','_sim_kinematics_switch_active','_sim_kinematics_switch_state_hash','_sim_kinematics_user_init','_sim_kinematics_user_bind_switch','_sim_kinematics_user_forward','_sim_kinematics_user_inverse','_sim_kinematics_user_reject_dynamic','_sim_kinematics_user_state_hash','_sim_kinematics_registry_count','_sim_kinematics_registry_get','_sim_kinematics_registry_find','_sim_kinematics_registry_require','_sim_kinematics_registry_validate_config','_sim_kinematics_registry_state_hash','_sim_kinematics_motion_init','_sim_kinematics_motion_reset','_sim_kinematics_motion_enqueue','_sim_kinematics_motion_pause','_sim_kinematics_motion_step','_sim_kinematics_motion_peek','_sim_kinematics_motion_pop','_sim_kinematics_motion_depth','_sim_kinematics_motion_state_hash','_sim_kinematics_feedback_init','_sim_kinematics_feedback_reset','_sim_kinematics_feedback_update','_sim_kinematics_feedback_read','_sim_kinematics_feedback_state_hash','_sim_kinematics_home_limit_init','_sim_kinematics_home_limit_reset','_sim_kinematics_home_limit_home','_sim_kinematics_home_limit_check','_sim_kinematics_home_limit_read','_sim_kinematics_home_limit_state_hash','_sim_io_init','_sim_io_reset','_sim_io_register_pins','_sim_io_spindle_command','_sim_io_spindle_set_inhibit','_sim_io_spindle_set_fault','_sim_io_spindle_set_index_enable','_sim_io_spindle_set_feedback','_sim_io_spindle_orient','_sim_io_spindle_set_oriented','_sim_io_tool_prepare','_sim_io_tool_set_prepared','_sim_io_tool_change','_sim_io_tool_set_changed','_sim_io_tool_abort','_sim_io_tool_set_fault','_sim_io_tool_clear_fault','_sim_io_probe_arm','_sim_io_probe_update','_sim_io_probe_disarm','_sim_io_set_estop','_sim_io_recover','_sim_io_step','_sim_io_read_state','_sim_io_state_hash','_sim_io_canonical'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']" + LINK_FLAGS "-sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,worker -sEXPORTED_FUNCTIONS=['_malloc','_free','_sim_checkpoint_build_id','_sim_core_checkpoint_size','_sim_core_checkpoint_export','_sim_core_checkpoint_restore','_sim_protocol_checkpoint_size','_sim_protocol_checkpoint_export','_sim_protocol_checkpoint_restore','_sim_event_checkpoint_size','_sim_event_checkpoint_export','_sim_event_checkpoint_restore','_sim_create','_sim_load_workspace','_sim_command','_sim_step','_sim_read_snapshot','_sim_read_event','_sim_export_state','_sim_import_state','_sim_destroy','_sim_protocol_reset','_sim_protocol_accept','_sim_protocol_pause','_sim_protocol_resume','_sim_protocol_state_value','_sim_protocol_last_seq','_sim_protocol_tick','_sim_protocol_set_tick','_sim_event_reset','_sim_event_append','_sim_event_count','_sim_event_cursor','_sim_event_read','_sim_event_export','_sim_event_import','_sim_task_size','_sim_task_init','_sim_task_reset','_sim_task_attach','_sim_task_set_command','_sim_task_set_tick','_sim_task_set_mode','_sim_task_set_program','_sim_task_transition','_sim_task_fault','_sim_task_estop','_sim_task_recover','_sim_task_restore','_sim_task_read_state','_sim_task_canonical','_sim_task_state_hash','_sim_scheduler_size','_sim_scheduler_init','_sim_scheduler_reset','_sim_scheduler_step','_sim_scheduler_restore','_sim_scheduler_read','_sim_scheduler_tick','_sim_scheduler_state_hash','_sim_scheduler_canonical','_sim_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_set_param','_sim_hal_get_param','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_sim_hal_register_component','_sim_hal_set_function_failure','_sim_hal_step','_sim_hal_checkpoint_size','_sim_hal_checkpoint_export','_sim_hal_checkpoint_restore','_sim_hal_tick','_sim_hal_function_calls','_sim_hal_pin_count','_sim_hal_thread_count','_sim_hal_function_count','_sim_hal_component_count','_sim_hal_param_count','_sim_device_init','_sim_device_reset','_sim_device_encoder_set_speed','_sim_device_encoder_set_config','_sim_device_encoder_set_index_enable','_sim_device_coolant_command','_sim_device_coolant_set_fault','_sim_device_door_set','_sim_device_interlock_set','_sim_device_interlock_reset','_sim_device_estop_set','_sim_device_step','_sim_device_ack_issue','_sim_device_ack_ack','_sim_device_ack_abort','_sim_device_ack_step','_sim_device_state_hash','_sim_device_canonical','_sim_posemath_normalize','_sim_posemath_dot','_sim_posemath_line_point','_sim_posemath_is_finite','_sim_posemath_pose_is_finite','_sim_posemath_pose_line_point','_sim_posemath_segment_validate','_sim_posemath_segment_point','_sim_posemath_hash_state','_sim_tp_init','_sim_tp_reset','_sim_tp_enqueue','_sim_tp_step','_sim_tp_read_state','_sim_tp_state_hash','_sim_interpreter_size','_sim_interpreter_init','_sim_interpreter_reset','_sim_interpreter_execute','_sim_interpreter_read_state','_sim_interpreter_read_motion','_sim_interpreter_motion_size','_sim_interpreter_canonical','_sim_interpreter_state_hash','_sim_interpreter_block_hash','_sim_interpreter_modal_hash','_sim_interpreter_error_column','_sim_interpreter_error_code','_sim_interpreter_parameter_set','_sim_interpreter_parameter_get','_sim_motion_init','_sim_motion_size','_sim_motion_command_size','_sim_motion_state_size','_sim_motion_reset','_sim_motion_enqueue','_sim_motion_step','_sim_motion_read_state','_sim_motion_queue_depth','_sim_motion_state_hash','_sim_motion_canonical','_sim_kinematics_init','_sim_kinematics_forward','_sim_kinematics_forward_ex','_sim_kinematics_inverse','_sim_kinematics_inverse_ex','_sim_kinematics_home','_sim_kinematics_validate_pose','_sim_kinematics_state_hash','_sim_kinematics_switch_init','_sim_kinematics_switch_register','_sim_kinematics_switch_begin','_sim_kinematics_switch_abort','_sim_kinematics_switch_commit','_sim_kinematics_switch_select','_sim_kinematics_switch_forward','_sim_kinematics_switch_inverse','_sim_kinematics_switch_active','_sim_kinematics_switch_state_hash','_sim_kinematics_user_init','_sim_kinematics_user_bind_switch','_sim_kinematics_user_forward','_sim_kinematics_user_inverse','_sim_kinematics_user_reject_dynamic','_sim_kinematics_user_state_hash','_sim_kinematics_registry_count','_sim_kinematics_registry_get','_sim_kinematics_registry_find','_sim_kinematics_registry_require','_sim_kinematics_registry_validate_config','_sim_kinematics_registry_state_hash','_sim_kinematics_motion_init','_sim_kinematics_motion_reset','_sim_kinematics_motion_enqueue','_sim_kinematics_motion_pause','_sim_kinematics_motion_step','_sim_kinematics_motion_peek','_sim_kinematics_motion_pop','_sim_kinematics_motion_depth','_sim_kinematics_motion_state_hash','_sim_kinematics_feedback_init','_sim_kinematics_feedback_reset','_sim_kinematics_feedback_update','_sim_kinematics_feedback_read','_sim_kinematics_feedback_state_hash','_sim_kinematics_home_limit_init','_sim_kinematics_home_limit_reset','_sim_kinematics_home_limit_home','_sim_kinematics_home_limit_check','_sim_kinematics_home_limit_read','_sim_kinematics_home_limit_state_hash','_sim_io_init','_sim_io_reset','_sim_io_register_pins','_sim_io_spindle_command','_sim_io_spindle_set_inhibit','_sim_io_spindle_set_fault','_sim_io_spindle_set_index_enable','_sim_io_spindle_set_feedback','_sim_io_spindle_orient','_sim_io_spindle_set_oriented','_sim_io_tool_prepare','_sim_io_tool_set_prepared','_sim_io_tool_change','_sim_io_tool_set_changed','_sim_io_tool_abort','_sim_io_tool_set_fault','_sim_io_tool_clear_fault','_sim_io_probe_arm','_sim_io_probe_update','_sim_io_probe_disarm','_sim_io_set_estop','_sim_io_recover','_sim_io_step','_sim_io_read_state','_sim_io_state_hash','_sim_io_canonical'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']" ) get_target_property(_sim_module_link_flags sim_core_module LINK_FLAGS) string(REPLACE "_sim_task_recover','_sim_task_restore" "_sim_task_recover','_sim_task_command_gate','_sim_task_restore" _sim_module_link_flags "${_sim_module_link_flags}") - string(REPLACE "_sim_interpreter_size" "_sim_program_size','_sim_program_requires_subroutines','_sim_program_init','_sim_program_reset','_sim_program_destroy','_sim_program_load','_sim_program_add_subroutine','_sim_program_set_rapid_velocity','_sim_program_set_position','_sim_program_start','_sim_program_pause','_sim_program_resume','_sim_program_abort','_sim_program_step','_sim_program_execute_block','_sim_program_seek','_sim_program_read_motion','_sim_program_clear_motion','_sim_program_canonical','_sim_program_state_hash','_sim_interpreter_size" _sim_module_link_flags "${_sim_module_link_flags}") + string(REPLACE "_sim_interpreter_size" "_sim_program_size','_sim_program_requires_subroutines','_sim_program_checkpoint_size','_sim_program_checkpoint_export','_sim_program_checkpoint_restore','_sim_program_init','_sim_program_reset','_sim_program_destroy','_sim_program_load','_sim_program_add_subroutine','_sim_program_set_rapid_velocity','_sim_program_set_position','_sim_program_start','_sim_program_pause','_sim_program_resume','_sim_program_abort','_sim_program_step','_sim_program_execute_block','_sim_program_seek','_sim_program_read_motion','_sim_program_clear_motion','_sim_program_canonical','_sim_program_state_hash','_sim_interpreter_size" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_kinematics_switch_select','_sim_kinematics_switch_forward" "_sim_kinematics_switch_select','_sim_kinematics_switch_select_physical','_sim_kinematics_switch_forward" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_kinematics_switch_inverse','_sim_kinematics_switch_active" "_sim_kinematics_switch_inverse','_sim_kinematics_switch_inverse_mode','_sim_kinematics_switch_active" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_motion_init" "_sim_motion_set_profile','_sim_motion_set_lookahead','_sim_motion_set_dynamics','_sim_motion_set_feed_override','_sim_motion_set_paused','_sim_motion_peek','_sim_motion_init" _sim_module_link_flags "${_sim_module_link_flags}") @@ -317,7 +317,33 @@ if(EMSCRIPTEN) string(REPLACE "_sim_device_init" "_sim_device_io_size','_sim_device_init" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_motion_step','_sim_motion_read_state" "_sim_motion_step','_sim_motion_set_profile','_sim_motion_set_lookahead','_sim_motion_set_feed_override','_sim_motion_peek','_sim_motion_read_state" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_motion_state_hash','_sim_motion_canonical" "_sim_motion_state_hash','_sim_motion_plan_hash','_sim_motion_canonical" _sim_module_link_flags "${_sim_module_link_flags}") - string(REPLACE "_sim_motion_init" "_sim_motion_init','_sim_linuxcnc_tp_size','_sim_linuxcnc_tp_queue_capacity','_sim_linuxcnc_tp_init','_sim_linuxcnc_tp_reset','_sim_linuxcnc_tp_set_profile','_sim_linuxcnc_tp_set_feed_override','_sim_linuxcnc_tp_set_velocity_scale','_sim_linuxcnc_tp_set_jog_scale','_sim_linuxcnc_tp_set_axis_limit','_sim_linuxcnc_tp_jog','_sim_linuxcnc_tp_velocity_scale','_sim_linuxcnc_tp_velocity_limit','_sim_linuxcnc_tp_jog_scale','_sim_linuxcnc_tp_jog_velocity','_sim_linuxcnc_tp_feed_override','_sim_linuxcnc_tp_set_dynamics','_sim_linuxcnc_tp_axis_limit','_sim_linuxcnc_tp_step_line','_sim_linuxcnc_tp_is_stepping','_sim_linuxcnc_tp_set_paused','_sim_linuxcnc_tp_abort','_sim_linuxcnc_tp_set_position','_sim_linuxcnc_tp_set_goal_position','_sim_linuxcnc_tp_add_line','_sim_linuxcnc_tp_add_circle','_sim_linuxcnc_tp_step','_sim_linuxcnc_tp_idle','_sim_linuxcnc_tp_read_state','_sim_linuxcnc_tp_queue_depth','_sim_linuxcnc_tp_queue_segment','_sim_linuxcnc_tp_restore_progress','_sim_linuxcnc_tp_state_hash','_sim_linuxcnc_tp_canonical" _sim_module_link_flags "${_sim_module_link_flags}") + string(REPLACE "_sim_motion_init" "_sim_motion_init','_sim_linuxcnc_tp_checkpoint_size','_sim_linuxcnc_tp_checkpoint_export','_sim_linuxcnc_tp_checkpoint_restore','_sim_linuxcnc_tp_size','_sim_linuxcnc_tp_queue_capacity','_sim_linuxcnc_tp_init','_sim_linuxcnc_tp_reset','_sim_linuxcnc_tp_set_profile','_sim_linuxcnc_tp_set_feed_override','_sim_linuxcnc_tp_set_velocity_scale','_sim_linuxcnc_tp_set_jog_scale','_sim_linuxcnc_tp_set_axis_limit','_sim_linuxcnc_tp_set_axis_dynamics','_sim_linuxcnc_tp_set_feed_limit','_sim_linuxcnc_tp_set_scale_minimum','_sim_linuxcnc_tp_set_blend','_sim_linuxcnc_tp_jog','_sim_linuxcnc_tp_velocity_scale','_sim_linuxcnc_tp_velocity_limit','_sim_linuxcnc_tp_jog_scale','_sim_linuxcnc_tp_jog_velocity','_sim_linuxcnc_tp_feed_override','_sim_linuxcnc_tp_set_dynamics','_sim_linuxcnc_tp_axis_limit','_sim_linuxcnc_tp_step_line','_sim_linuxcnc_tp_is_stepping','_sim_linuxcnc_tp_set_paused','_sim_linuxcnc_tp_abort','_sim_linuxcnc_tp_set_position','_sim_linuxcnc_tp_set_goal_position','_sim_linuxcnc_tp_add_line','_sim_linuxcnc_tp_add_circle','_sim_linuxcnc_tp_step','_sim_linuxcnc_tp_idle','_sim_linuxcnc_tp_read_state','_sim_linuxcnc_tp_queue_depth','_sim_linuxcnc_tp_queue_segment','_sim_linuxcnc_tp_restore_progress','_sim_linuxcnc_tp_state_hash','_sim_linuxcnc_tp_canonical" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_linuxcnc_tp_velocity_limit" "_sim_linuxcnc_tp_velocity_limit','_sim_linuxcnc_tp_velocity_mm_min" _sim_module_link_flags "${_sim_module_link_flags}") set_target_properties(sim_core_module PROPERTIES LINK_FLAGS "${_sim_module_link_flags}") endif() + +# Opaque native checkpoints are only portable between matching source/layout builds. +file(GLOB_RECURSE checkpoint_inputs CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/emc/tp/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/emc/tp/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/emc/motion/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/emc/motion/homing.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/emc/nml_intf/emcpos.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/*.h") +list(APPEND checkpoint_inputs "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt") +list(SORT checkpoint_inputs) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${checkpoint_inputs}) +string(TOUPPER "${CMAKE_BUILD_TYPE}" checkpoint_build_mode) +set(checkpoint_identity "${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR};${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR};${CMAKE_C_COMPILER_ID};${CMAKE_C_COMPILER_VERSION};${CMAKE_CXX_COMPILER_ID};${CMAKE_CXX_COMPILER_VERSION};${CMAKE_SIZEOF_VOID_P};${CMAKE_C_BYTE_ORDER};${CMAKE_BUILD_TYPE};${CMAKE_C_FLAGS};${CMAKE_C_FLAGS_${checkpoint_build_mode}};${CMAKE_CXX_FLAGS};${CMAKE_CXX_FLAGS_${checkpoint_build_mode}};${CMAKE_EXE_LINKER_FLAGS};${CMAKE_EXE_LINKER_FLAGS_${checkpoint_build_mode}};${_sim_module_link_flags}") +foreach(checkpoint_input IN LISTS checkpoint_inputs) + file(SHA256 "${checkpoint_input}" checkpoint_digest) + string(APPEND checkpoint_identity ";${checkpoint_digest}") +endforeach() +string(SHA256 checkpoint_build_id "${checkpoint_identity}") +target_compile_definitions(sim_core PRIVATE SIM_CHECKPOINT_BUILD_ID="${checkpoint_build_id}") +if(TARGET sim_core_module) + target_compile_definitions(sim_core_module PRIVATE SIM_CHECKPOINT_BUILD_ID="${checkpoint_build_id}") +endif() diff --git a/wasm-core/include/sim_api.h b/wasm-core/include/sim_api.h index 11e7e6b..aa994d9 100644 --- a/wasm-core/include/sim_api.h +++ b/wasm-core/include/sim_api.h @@ -25,6 +25,10 @@ uint32_t sim_read_snapshot(uint8_t *out, uint32_t capacity); uint32_t sim_read_event(uint8_t *out, uint32_t capacity); int32_t sim_export_state(uint8_t *out, uint32_t capacity); int32_t sim_import_state(const uint8_t *data, uint32_t len); +uint32_t sim_core_checkpoint_size(void); +const char *sim_checkpoint_build_id(void); +int32_t sim_core_checkpoint_export(uint8_t *output, uint32_t capacity); +int32_t sim_core_checkpoint_restore(const uint8_t *input, uint32_t length); void sim_destroy(void); #ifdef __cplusplus diff --git a/wasm-core/include/sim_event.h b/wasm-core/include/sim_event.h index bd6e28a..e9496dd 100644 --- a/wasm-core/include/sim_event.h +++ b/wasm-core/include/sim_event.h @@ -27,6 +27,9 @@ typedef struct sim_event_record { } sim_event_record; void sim_event_reset(void); +uint32_t sim_event_checkpoint_size(void); +int32_t sim_event_checkpoint_export(uint8_t *output, uint32_t capacity); +int32_t sim_event_checkpoint_restore(const uint8_t *input, uint32_t length); int32_t sim_event_append(uint32_t seq, uint32_t tick, const char *kind, const uint8_t *payload, uint32_t payload_len); uint32_t sim_event_count(void); diff --git a/wasm-core/include/sim_hal.h b/wasm-core/include/sim_hal.h index 9091270..5129638 100644 --- a/wasm-core/include/sim_hal.h +++ b/wasm-core/include/sim_hal.h @@ -14,6 +14,10 @@ extern "C" { #define SIM_HAL_COMPONENT_CAPACITY 64U #define SIM_HAL_PARAM_CAPACITY 256U +uint32_t sim_hal_checkpoint_size(void); +int32_t sim_hal_checkpoint_export(uint8_t *output, uint32_t capacity); +int32_t sim_hal_checkpoint_restore(const uint8_t *input, uint32_t length); + enum sim_hal_result { SIM_HAL_OK = 0, SIM_HAL_ERR_ARGUMENT = -300, diff --git a/wasm-core/include/sim_interpreter.h b/wasm-core/include/sim_interpreter.h index 5c26b6c..27f54e1 100644 --- a/wasm-core/include/sim_interpreter.h +++ b/wasm-core/include/sim_interpreter.h @@ -74,6 +74,9 @@ int sim_interpreter_set_coordinates(sim_interpreter *interpreter, const sim_coor int sim_interpreter_sync_position(sim_interpreter *interpreter, const sim_posemath_pose *position); int sim_interpreter_init(sim_interpreter *interpreter); +uint32_t sim_interpreter_checkpoint_size(void); +int32_t sim_interpreter_checkpoint_export(const sim_interpreter *interpreter, uint8_t *output, uint32_t capacity); +int32_t sim_interpreter_checkpoint_restore(sim_interpreter *interpreter, const uint8_t *input, uint32_t length); int sim_interpreter_reset(sim_interpreter *interpreter); int sim_interpreter_execute(sim_interpreter *interpreter, const char *block, uint32_t length); uint32_t sim_interpreter_size(void); diff --git a/wasm-core/include/sim_linuxcnc_tp.h b/wasm-core/include/sim_linuxcnc_tp.h index faebe4c..66fbf1f 100644 --- a/wasm-core/include/sim_linuxcnc_tp.h +++ b/wasm-core/include/sim_linuxcnc_tp.h @@ -18,6 +18,9 @@ typedef struct sim_linuxcnc_tp_segment { } sim_linuxcnc_tp_segment; uint32_t sim_linuxcnc_tp_size(void); +uint32_t sim_linuxcnc_tp_checkpoint_size(void); +int32_t sim_linuxcnc_tp_checkpoint_export(const sim_linuxcnc_tp *planner, uint8_t *output, uint32_t capacity); +int32_t sim_linuxcnc_tp_checkpoint_restore(sim_linuxcnc_tp *planner, const uint8_t *input, uint32_t length); uint32_t sim_linuxcnc_tp_queue_capacity(const sim_linuxcnc_tp *planner); int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time, double velocity_limit, double acceleration_limit); @@ -27,6 +30,10 @@ int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale); int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale); int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, double minimum, double maximum); +int32_t sim_linuxcnc_tp_set_axis_dynamics(sim_linuxcnc_tp *planner, int axis, double velocity, double acceleration); +int32_t sim_linuxcnc_tp_set_feed_limit(sim_linuxcnc_tp *planner, double maximum); +int32_t sim_linuxcnc_tp_set_scale_minimum(sim_linuxcnc_tp *planner, double minimum); +int32_t sim_linuxcnc_tp_set_blend(sim_linuxcnc_tp *planner, int enable, int fallback, int depth, int gap, double ramp, double kink); int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag); double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner); double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner); diff --git a/wasm-core/include/sim_program.h b/wasm-core/include/sim_program.h index 4e06cf9..f55e16f 100644 --- a/wasm-core/include/sim_program.h +++ b/wasm-core/include/sim_program.h @@ -83,6 +83,9 @@ typedef struct sim_program { } sim_program; uint32_t sim_program_size(void); +uint32_t sim_program_checkpoint_size(void); +int32_t sim_program_checkpoint_export(const sim_program *program, uint8_t *output, uint32_t capacity); +int32_t sim_program_checkpoint_restore(sim_program *program, const uint8_t *input, uint32_t length); int32_t sim_program_requires_subroutines(const char *source, uint32_t length); int32_t sim_program_init(sim_program *program); int32_t sim_program_reset(sim_program *program); diff --git a/wasm-core/include/sim_protocol.h b/wasm-core/include/sim_protocol.h index a8c3266..c558679 100644 --- a/wasm-core/include/sim_protocol.h +++ b/wasm-core/include/sim_protocol.h @@ -37,6 +37,9 @@ int32_t sim_protocol_resume(void); int32_t sim_protocol_state_value(void); uint32_t sim_protocol_last_seq(void); uint32_t sim_protocol_tick(void); +uint32_t sim_protocol_checkpoint_size(void); +int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity); +int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length); int32_t sim_protocol_set_tick(uint32_t simulation_tick); #ifdef __cplusplus diff --git a/wasm-core/src/sim_api.c b/wasm-core/src/sim_api.c index 6220f51..b66d843 100644 --- a/wasm-core/src/sim_api.c +++ b/wasm-core/src/sim_api.c @@ -11,6 +11,23 @@ typedef struct { static sim_state state; +#ifndef SIM_CHECKPOINT_BUILD_ID +#define SIM_CHECKPOINT_BUILD_ID "unversioned" +#endif +const char *sim_checkpoint_build_id(void) { return SIM_CHECKPOINT_BUILD_ID; } + +uint32_t sim_core_checkpoint_size(void) { return sizeof(state); } +int32_t sim_core_checkpoint_export(uint8_t *output, uint32_t capacity) { + if (output == NULL || capacity != sizeof(state)) return SIM_ERR_INVALID_ARGUMENT; + memcpy(output, &state, sizeof(state)); return SIM_OK; +} +int32_t sim_core_checkpoint_restore(const uint8_t *input, uint32_t length) { + if (input == NULL || length != sizeof(state)) return SIM_ERR_INVALID_ARGUMENT; + const sim_state *saved = (const sim_state *)input; + if (saved->created > 1 || saved->loaded > 1 || saved->pending_event > 1) return SIM_ERR_INVALID_ARGUMENT; + memcpy(&state, saved, sizeof(state)); return SIM_OK; +} + static int valid_buffer(const uint8_t *data, uint32_t len) { return len == 0 || data != NULL; } diff --git a/wasm-core/src/sim_event.c b/wasm-core/src/sim_event.c index 932a2cf..791e0fb 100644 --- a/wasm-core/src/sim_event.c +++ b/wasm-core/src/sim_event.c @@ -12,6 +12,14 @@ typedef struct sim_event_state { static sim_event_state state; +uint32_t sim_event_checkpoint_size(void) { return sizeof(state); } +int32_t sim_event_checkpoint_export(uint8_t *output, uint32_t capacity) { + return sim_event_export(output, capacity) == sizeof(state) ? SIM_EVENT_OK : SIM_EVENT_ERR_BUFFER; +} +int32_t sim_event_checkpoint_restore(const uint8_t *input, uint32_t length) { + return sim_event_import(input, length); +} + static uint64_t hash_payload(const uint8_t *payload, uint32_t payload_len) { uint64_t hash = 1469598103934665603ULL; uint32_t index; diff --git a/wasm-core/src/sim_hal.c b/wasm-core/src/sim_hal.c index 9deb07c..99fd05e 100644 --- a/wasm-core/src/sim_hal.c +++ b/wasm-core/src/sim_hal.c @@ -87,6 +87,24 @@ typedef struct { static hal_state state; +uint32_t sim_hal_checkpoint_size(void) { return (uint32_t)sizeof(hal_state); } + +int32_t sim_hal_checkpoint_export(uint8_t *output, uint32_t capacity) { + if (output == NULL || capacity != sizeof(state)) return SIM_HAL_ERR_ARGUMENT; + memcpy(output, &state, sizeof(state)); + return SIM_HAL_OK; +} + +int32_t sim_hal_checkpoint_restore(const uint8_t *input, uint32_t length) { + if (input == NULL || length != sizeof(state)) return SIM_HAL_ERR_ARGUMENT; + const hal_state *saved = (const hal_state *)input; + for (uint32_t i = 0; i < SIM_HAL_PIN_CAPACITY; ++i) if (saved->pins[i].used && (memchr(saved->pins[i].name, '\0', SIM_HAL_NAME_CAPACITY) == NULL || saved->pins[i].value.type < SIM_HAL_BIT || saved->pins[i].value.type > SIM_HAL_FLOAT)) return SIM_HAL_ERR_ARGUMENT; + for (uint32_t i = 0; i < SIM_HAL_FUNCTION_CAPACITY; ++i) if (saved->functions[i].used && (saved->functions[i].thread >= SIM_HAL_THREAD_CAPACITY || (saved->functions[i].component_index != UINT32_MAX && saved->functions[i].component_index >= SIM_HAL_COMPONENT_CAPACITY))) return SIM_HAL_ERR_ARGUMENT; + for (uint32_t i = 0; i < sizeof(saved->links) / sizeof(saved->links[0]); ++i) if (saved->links[i].used && (saved->links[i].source >= SIM_HAL_PIN_CAPACITY || saved->links[i].target >= SIM_HAL_PIN_CAPACITY)) return SIM_HAL_ERR_ARGUMENT; + memcpy(&state, saved, sizeof(state)); + return SIM_HAL_OK; +} + static int valid_name(const char *name) { return name != NULL && name[0] != '\0' && strlen(name) < SIM_HAL_NAME_CAPACITY; } diff --git a/wasm-core/src/sim_interpreter.c b/wasm-core/src/sim_interpreter.c index 5177319..5313ae0 100644 --- a/wasm-core/src/sim_interpreter.c +++ b/wasm-core/src/sim_interpreter.c @@ -85,6 +85,28 @@ static int code_is(double value, int integer_code) { return isfinite(value) && fabs(value - (double)integer_code) <= 1e-12; } +typedef struct { uint32_t magic, size; sim_interpreter interpreter; interpreter_meta meta; } interpreter_checkpoint; +uint32_t sim_interpreter_checkpoint_size(void) { return sizeof(interpreter_checkpoint); } +int32_t sim_interpreter_checkpoint_export(const sim_interpreter *interpreter, uint8_t *output, uint32_t capacity) { + const interpreter_meta *meta = meta_for(interpreter, 0); + if (meta == NULL || output == NULL || capacity != sizeof(interpreter_checkpoint)) return SIM_INTERP_ERR_ARGUMENT; + interpreter_checkpoint *saved = (interpreter_checkpoint *)output; + memset(saved, 0, sizeof(*saved)); + saved->magic = UINT32_C(0x49504331); saved->size = sizeof(*saved); + saved->interpreter = *interpreter; saved->meta = *meta; saved->meta.owner = NULL; + return SIM_INTERP_OK; +} +int32_t sim_interpreter_checkpoint_restore(sim_interpreter *interpreter, const uint8_t *input, uint32_t length) { + if (interpreter == NULL || input == NULL || length != sizeof(interpreter_checkpoint)) return SIM_INTERP_ERR_ARGUMENT; + const interpreter_checkpoint *saved = (const interpreter_checkpoint *)input; + if (saved->magic != UINT32_C(0x49504331) || saved->size != sizeof(*saved) || saved->meta.owner != NULL || !finite_state(&saved->interpreter.state) || saved->interpreter.canonical_length >= SIM_INTERPRETER_CANONICAL_CAPACITY || saved->meta.coordinate_system < 0 || saved->meta.coordinate_system > 9) return SIM_INTERP_ERR_ARGUMENT; + for (unsigned i = 0; i < NAMED_PARAMETER_CAPACITY; ++i) if (saved->meta.named[i].used && memchr(saved->meta.named[i].name, '\0', NAMED_PARAMETER_LENGTH) == NULL) return SIM_INTERP_ERR_ARGUMENT; + interpreter_meta *meta = meta_for(interpreter, 1); + if (meta == NULL) return SIM_INTERP_ERR_ARGUMENT; + *interpreter = saved->interpreter; *meta = saved->meta; meta->owner = interpreter; + return SIM_INTERP_OK; +} + static int code_is_value(double value, double expected) { return isfinite(value) && fabs(value - expected) <= 1e-12; } diff --git a/wasm-core/src/sim_linuxcnc_tp.c b/wasm-core/src/sim_linuxcnc_tp.c index eb5df83..ff8d4d3 100644 --- a/wasm-core/src/sim_linuxcnc_tp.c +++ b/wasm-core/src/sim_linuxcnc_tp.c @@ -22,6 +22,7 @@ struct sim_linuxcnc_tp { double axis_max[9]; double velocity_scale; double jog_scale; + double scale_minimum; uint32_t tick; uint32_t last_active_id; int just_completed; @@ -31,6 +32,45 @@ struct sim_linuxcnc_tp { static sim_linuxcnc_tp *active_planner; +/* Same-build checkpoint: TP/TC contain the integration and blend state which + cannot be reconstructed by re-enqueuing the remaining geometric path. + The queue pointer is removed on export and rebound on restore. All other + members of these source structs are values, not process addresses. */ +typedef struct { + uint32_t magic, planner_size, tc_size, capacity; + sim_linuxcnc_tp planner; + TC_STRUCT queue[TP_DEFAULT_QUEUE_SIZE]; +} tp_checkpoint; + +uint32_t sim_linuxcnc_tp_checkpoint_size(void) { return (uint32_t)sizeof(tp_checkpoint); } + +int32_t sim_linuxcnc_tp_checkpoint_export(const sim_linuxcnc_tp *planner, uint8_t *output, uint32_t capacity) { + if (planner == NULL || !planner->initialized || output == NULL || capacity != sizeof(tp_checkpoint) || planner->tp.queue.size != TP_DEFAULT_QUEUE_SIZE) return -1228; + tp_checkpoint *saved = (tp_checkpoint *)output; + memset(saved, 0, sizeof(*saved)); + saved->magic = UINT32_C(0x54504331); + saved->planner_size = sizeof(sim_linuxcnc_tp); saved->tc_size = sizeof(TC_STRUCT); saved->capacity = TP_DEFAULT_QUEUE_SIZE; + saved->planner = *planner; + saved->planner.tp.queue.queue = NULL; + memcpy(saved->queue, planner->tp.queue.queue, sizeof(saved->queue)); + return 0; +} + +int32_t sim_linuxcnc_tp_checkpoint_restore(sim_linuxcnc_tp *planner, const uint8_t *input, uint32_t length) { + if (planner == NULL || !planner->initialized || input == NULL || length != sizeof(tp_checkpoint)) return -1228; + const tp_checkpoint *saved = (const tp_checkpoint *)input; + const TC_QUEUE_STRUCT *queue = &saved->planner.tp.queue; + if (saved->magic != UINT32_C(0x54504331) || saved->planner_size != sizeof(sim_linuxcnc_tp) || saved->tc_size != sizeof(TC_STRUCT) || saved->capacity != TP_DEFAULT_QUEUE_SIZE || saved->planner.initialized != 1 || queue->queue != NULL || queue->size != TP_DEFAULT_QUEUE_SIZE || queue->start < 0 || queue->start >= queue->size || queue->end < 0 || queue->end >= queue->size || queue->_len < 0 || queue->_rlen < 0 || queue->_len > queue->size || queue->_rlen > queue->size - queue->_len) return -1228; + if (!isfinite(saved->planner.tp.cycleTime) || saved->planner.tp.cycleTime != planner->tp.cycleTime || memcmp(&saved->planner.config, &planner->config, sizeof(planner->config)) != 0 || memcmp(saved->planner.axis_min, planner->axis_min, sizeof(planner->axis_min)) != 0 || memcmp(saved->planner.axis_max, planner->axis_max, sizeof(planner->axis_max)) != 0 || memcmp(saved->planner.axis_velocity, planner->axis_velocity, sizeof(planner->axis_velocity)) != 0 || memcmp(saved->planner.axis_acceleration, planner->axis_acceleration, sizeof(planner->axis_acceleration)) != 0) return -1229; + TC_STRUCT *storage = planner->tp.queue.queue; + *planner = saved->planner; + planner->tp.queue.queue = storage; + memcpy(storage, saved->queue, sizeof(saved->queue)); + active_planner = planner; + tpMotData(&planner->status, &planner->config); + return 0; +} + static void noop_dio(int index, char value) { (void)index; (void)value; } static void noop_aio(int index, double value) { (void)index; (void)value; } static void noop_rotary_unlock(int index, int value) { (void)index; (void)value; } @@ -60,7 +100,7 @@ static void xyzac_line_limits(const sim_linuxcnc_tp *planner, const EmcPose *sta const double distance = has_linear ? sqrt(linear_length) : has_angular ? sqrt(angular_length) : 0.0; if (!(distance > 0.0)) return; if (max_velocity_time > 0.0) *velocity = fmin(*velocity, distance / max_velocity_time); - if (max_acceleration_time > 0.0) *acceleration = fmax(*acceleration, distance / max_acceleration_time); + if (max_acceleration_time > 0.0) *acceleration = fmin(*acceleration, distance / max_acceleration_time); } static void xyzac_circle_limits(const sim_linuxcnc_tp *planner, const EmcPose *start, const EmcPose *end, const PmCartesian *center, const PmCartesian *normal, int turn, double *velocity, double *initial_velocity, double *acceleration) { @@ -95,7 +135,7 @@ static void xyzac_circle_limits(const sim_linuxcnc_tp *planner, const EmcPose *s const double max_acceleration = total_xyz_length / fmax(time_motion_acceleration, time_spiral_acceleration); *velocity = fmin(*velocity, max_velocity); *initial_velocity = max_velocity; - *acceleration = fmax(*acceleration, max_acceleration); + *acceleration = fmin(*acceleration, max_acceleration); } static EmcPose to_emc_pose(const sim_posemath_pose *value) { @@ -143,18 +183,17 @@ int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time, double planner->config.arcBlendEnable = 1; planner->config.arcBlendFallbackEnable = 0; planner->config.arcBlendOptDepth = 50; planner->config.arcBlendGapCycles = 4; planner->config.arcBlendRampFreq = 100.0; planner->config.arcBlendTangentKinkRatio = 0.1; - // Match xyzac-trt.ini joint limits: the trajectory cap is 35 mm/s, but - // XYZ joints are limited to 20 and rotary A/C joints to 30 deg/s. The - // upstream TP uses these per-axis callbacks when calculating tangent - // velocity and acceleration at a corner. + // The caller configures each axis from INI before motion is queued. + // Standalone planners use their supplied trajectory limits. for (int axis = 0; axis < 9; ++axis) { - planner->axis_velocity[axis] = axis < 3 ? fmin(20.0, velocity_limit) : (axis == 3 || axis == 5) ? fmin(30.0, velocity_limit) : velocity_limit; + planner->axis_velocity[axis] = velocity_limit; planner->axis_acceleration[axis] = acceleration_limit; planner->axis_min[axis] = -INFINITY; planner->axis_max[axis] = INFINITY; } planner->velocity_scale = 1.0; planner->jog_scale = 1.0; + planner->scale_minimum = 0.0; planner->status.feed_scale = 1.0; planner->status.rapid_scale = 1.0; planner->status.net_feed_scale = 1.0; planner->status.enables_new = FS_ENABLED | FH_ENABLED; planner->status.enables_queued = planner->status.enables_new; planner->status.motionFlag = EMCMOT_MOTION_ENABLE_BIT | EMCMOT_MOTION_COORD_BIT | EMCMOT_MOTION_INPOS_BIT; @@ -182,12 +221,12 @@ int32_t sim_linuxcnc_tp_set_profile(sim_linuxcnc_tp *planner, int profile, doubl } int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale) { - if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.0 || scale > 2.0) return -1207; + if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.0 || scale > planner->config.maxFeedScale) return -1207; planner->status.net_feed_scale = scale; planner->status.feed_scale = scale; return 0; } int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale) { - if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1211; + if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1211; active_planner = planner; if (tpSetVlimit(&planner->tp, planner->config.limitVel * scale) != TP_ERR_OK) return -1212; planner->velocity_scale = scale; @@ -195,7 +234,7 @@ int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scal } int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale) { - if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1213; + if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1213; planner->jog_scale = scale; return 0; } @@ -207,6 +246,36 @@ int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, doubl return 0; } +int32_t sim_linuxcnc_tp_set_axis_dynamics(sim_linuxcnc_tp *planner, int axis, double velocity, double acceleration) { + if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || !isfinite(velocity) || velocity <= 0.0 || !isfinite(acceleration) || acceleration <= 0.0) return -1214; + planner->axis_velocity[axis] = velocity; + planner->axis_acceleration[axis] = acceleration; + return 0; +} + +int32_t sim_linuxcnc_tp_set_feed_limit(sim_linuxcnc_tp *planner, double maximum) { + if (planner == NULL || !planner->initialized || !isfinite(maximum) || maximum < 1.0) return -1207; + planner->config.maxFeedScale = maximum; + return 0; +} + +int32_t sim_linuxcnc_tp_set_scale_minimum(sim_linuxcnc_tp *planner, double minimum) { + if (planner == NULL || !planner->initialized || !isfinite(minimum) || minimum <= 0.0 || minimum > 1.0) return -1211; + planner->scale_minimum = minimum; + return 0; +} + +int32_t sim_linuxcnc_tp_set_blend(sim_linuxcnc_tp *planner, int enable, int fallback, int depth, int gap, double ramp, double kink) { + if (planner == NULL || !planner->initialized || (enable != 0 && enable != 1) || (fallback != 0 && fallback != 1) || depth < 1 || gap < 0 || !isfinite(ramp) || ramp <= 0.0 || !isfinite(kink) || kink <= 0.0) return -1205; + planner->config.arcBlendEnable = enable; + planner->config.arcBlendFallbackEnable = fallback; + planner->config.arcBlendOptDepth = depth; + planner->config.arcBlendGapCycles = gap; + planner->config.arcBlendRampFreq = ramp; + planner->config.arcBlendTangentKinkRatio = kink; + return 0; +} + int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag) { EmcPose end; double *value; diff --git a/wasm-core/src/sim_program.c b/wasm-core/src/sim_program.c index 37642ac..b4eb403 100644 --- a/wasm-core/src/sim_program.c +++ b/wasm-core/src/sim_program.c @@ -318,6 +318,30 @@ static void reset_runtime(sim_program *program) { uint32_t sim_program_size(void) { return (uint32_t)sizeof(sim_program); } +typedef struct { uint32_t magic, size; sim_program program; } program_checkpoint; +uint32_t sim_program_checkpoint_size(void) { return sizeof(program_checkpoint) + sim_interpreter_checkpoint_size(); } +int32_t sim_program_checkpoint_export(const sim_program *program, uint8_t *output, uint32_t capacity) { + if (program == NULL || output == NULL || capacity != sim_program_checkpoint_size()) return SIM_PROGRAM_ERR_ARGUMENT; + program_checkpoint *saved = (program_checkpoint *)output; + memset(saved, 0, sizeof(*saved)); saved->magic = UINT32_C(0x50524331); saved->size = sizeof(*saved); + saved->program = *program; + saved->program.source = NULL; saved->program.line_offsets = NULL; + saved->program.source_capacity = 0; saved->program.line_capacity = 0; + return sim_interpreter_checkpoint_export(&program->interpreter, output + sizeof(*saved), sim_interpreter_checkpoint_size()) == SIM_INTERP_OK ? SIM_PROGRAM_OK : SIM_PROGRAM_ERR_INTERPRETER; +} +int32_t sim_program_checkpoint_restore(sim_program *program, const uint8_t *input, uint32_t length) { + if (program == NULL || input == NULL || length != sim_program_checkpoint_size()) return SIM_PROGRAM_ERR_ARGUMENT; + const program_checkpoint *saved = (const program_checkpoint *)input; + if (saved->magic != UINT32_C(0x50524331) || saved->size != sizeof(*saved) || saved->program.source != NULL || saved->program.line_offsets != NULL || saved->program.source_length != program->source_length || saved->program.line_count != program->line_count || saved->program.current_line > program->line_count || saved->program.call_depth > SIM_PROGRAM_CALL_DEPTH || saved->program.subroutine_count > SIM_PROGRAM_SUBROUTINE_CAPACITY || saved->program.status > SIM_PROGRAM_ERROR) return SIM_PROGRAM_ERR_ARGUMENT; + if (sim_interpreter_checkpoint_restore(&program->interpreter, input + sizeof(*saved), sim_interpreter_checkpoint_size()) != SIM_INTERP_OK) return SIM_PROGRAM_ERR_INTERPRETER; + char *source = program->source; uint32_t *lines = program->line_offsets; + uint32_t source_capacity = program->source_capacity, line_capacity = program->line_capacity; + *program = saved->program; + program->source = source; program->line_offsets = lines; + program->source_capacity = source_capacity; program->line_capacity = line_capacity; + return SIM_PROGRAM_OK; +} + int32_t sim_program_init(sim_program *program) { if (program == NULL) return SIM_PROGRAM_ERR_ARGUMENT; memset(program, 0, sizeof(*program)); diff --git a/wasm-core/src/sim_protocol.c b/wasm-core/src/sim_protocol.c index 1efd11c..f44add5 100644 --- a/wasm-core/src/sim_protocol.c +++ b/wasm-core/src/sim_protocol.c @@ -12,6 +12,18 @@ typedef struct { static protocol_state state; +uint32_t sim_protocol_checkpoint_size(void) { return sizeof(state); } +int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity) { + if (output == NULL || capacity != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT; + memcpy(output, &state, sizeof(state)); return SIM_PROTOCOL_OK; +} +int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length) { + if (input == NULL || length != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT; + const protocol_state *saved = (const protocol_state *)input; + if (saved->state > SIM_PROTOCOL_DESTROYED) return SIM_PROTOCOL_ERR_ARGUMENT; + memcpy(&state, saved, sizeof(state)); return SIM_PROTOCOL_OK; +} + static int has_text(const char *value) { return value != NULL && value[0] != '\0' && strlen(value) <= 128; } diff --git a/wasm-core/tests/sim_hal_test.c b/wasm-core/tests/sim_hal_test.c index 4e7572d..9ad8bb0 100644 --- a/wasm-core/tests/sim_hal_test.c +++ b/wasm-core/tests/sim_hal_test.c @@ -2,6 +2,8 @@ #include #include +#include +#include static float get_float(const char *name) { sim_hal_value value = {0}; @@ -32,6 +34,19 @@ int sim_hal_test_run(void) { assert(sim_hal_register_function("missing", "bad") == SIM_HAL_ERR_NOT_FOUND); assert(sim_hal_step(5) == SIM_HAL_OK); assert(sim_hal_tick() == 5 && sim_hal_function_calls("update") == 2); + const uint32_t saved_size = sim_hal_checkpoint_size(); + uint8_t *saved = malloc(saved_size), *restored = malloc(saved_size); + assert(saved && restored); + assert(sim_hal_checkpoint_export(saved, saved_size) == SIM_HAL_OK); + assert(sim_hal_step(3) == SIM_HAL_OK); + assert(sim_hal_tick() == 8 && sim_hal_function_calls("update") == 4); + assert(sim_hal_checkpoint_restore(saved, saved_size - 1) == SIM_HAL_ERR_ARGUMENT); + assert(sim_hal_tick() == 8); + assert(sim_hal_checkpoint_restore(saved, saved_size) == SIM_HAL_OK); + assert(sim_hal_tick() == 5 && sim_hal_function_calls("update") == 2); + assert(sim_hal_checkpoint_export(restored, saved_size) == SIM_HAL_OK); + assert(memcmp(saved, restored, saved_size) == 0); + free(saved); free(restored); assert(sim_hal_set_function_failure("update", 1) == SIM_HAL_OK); assert(sim_hal_step(1) == SIM_HAL_ERR_FUNCTION); assert(sim_hal_tick() == 5); diff --git a/wasm-core/tests/sim_linuxcnc_tp_test.c b/wasm-core/tests/sim_linuxcnc_tp_test.c index 2d642d8..62ba33b 100644 --- a/wasm-core/tests/sim_linuxcnc_tp_test.c +++ b/wasm-core/tests/sim_linuxcnc_tp_test.c @@ -18,6 +18,26 @@ int main(void) { sim_posemath_pose first = pose(0, 0, 10); sim_posemath_pose second = pose(20, 20, 10); assert(planner != NULL); + assert(sim_linuxcnc_tp_init(planner, .001, 35, 300) == 0); + assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, 7, 40) == 0); + assert(fabs(sim_linuxcnc_tp_jog_velocity(planner, 0) - 7) < 1e-9); + assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 4, 11, 20) == 0); + assert(fabs(sim_linuxcnc_tp_jog_velocity(planner, 4) - 11) < 1e-9); + assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 9, 7, 40) != 0); + assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, NAN, 40) != 0); + assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, 7, 0) != 0); + assert(sim_linuxcnc_tp_set_feed_limit(planner, 1.3) == 0); + assert(sim_linuxcnc_tp_set_feed_override(planner, 1.3) == 0); + assert(sim_linuxcnc_tp_set_feed_override(planner, 1.31) != 0); + assert(sim_linuxcnc_tp_set_scale_minimum(planner, .02) == 0); + assert(sim_linuxcnc_tp_set_jog_scale(planner, .02) == 0); + assert(sim_linuxcnc_tp_set_velocity_scale(planner, .02) == 0); + assert(sim_linuxcnc_tp_set_jog_scale(planner, .01) != 0); + assert(sim_linuxcnc_tp_set_scale_minimum(planner, NAN) != 0); + assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 25, 3, 50, .2) == 0); + assert(sim_linuxcnc_tp_set_blend(planner, 2, 0, 25, 3, 50, .2) != 0); + assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 0, 3, 50, .2) != 0); + assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 25, 3, NAN, .2) != 0); assert(sim_linuxcnc_tp_init(planner, 0.001, 35.0, 300.0) == 0); assert(sim_linuxcnc_tp_queue_capacity(planner) > 0); assert(sim_linuxcnc_tp_velocity_mm_min(planner) == 0.0); @@ -25,6 +45,28 @@ int main(void) { assert(sim_linuxcnc_tp_set_profile(planner, 0, 0.0) == 0); assert(sim_linuxcnc_tp_add_line(planner, &first, 20.0, 300.0, 16, 1) == 0); assert(sim_linuxcnc_tp_add_line(planner, &second, 35.0, 300.0, 18, 1) == 0); + for (int index = 0; index < 20; ++index) assert(sim_linuxcnc_tp_step(planner) == 0); + uint32_t checkpoint_size = sim_linuxcnc_tp_checkpoint_size(); + uint8_t *checkpoint = malloc(checkpoint_size); + uint64_t continuation[100]; + assert(checkpoint != NULL); + assert(sim_linuxcnc_tp_checkpoint_export(planner, checkpoint, checkpoint_size) == 0); + for (int index = 0; index < 100; ++index) { + assert(sim_linuxcnc_tp_step(planner) == 0); + continuation[index] = sim_linuxcnc_tp_state_hash(planner); + } + const uint64_t before_invalid = sim_linuxcnc_tp_state_hash(planner); + assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size - 1) != 0); + checkpoint[0] ^= 1; + assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size) != 0); + assert(sim_linuxcnc_tp_state_hash(planner) == before_invalid); + checkpoint[0] ^= 1; + assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size) == 0); + for (int index = 0; index < 100; ++index) { + assert(sim_linuxcnc_tp_step(planner) == 0); + assert(sim_linuxcnc_tp_state_hash(planner) == continuation[index]); + } + free(checkpoint); for (int index = 0; index < 5000; ++index) { int result = sim_linuxcnc_tp_step(planner); if (result != 0) fprintf(stderr, "tp_step[%d]=%d\\n", index, result); diff --git a/web/app/axis-contract.mjs b/web/app/axis-contract.mjs index 1bf6675..bb1390b 100644 --- a/web/app/axis-contract.mjs +++ b/web/app/axis-contract.mjs @@ -61,7 +61,7 @@ export function normalizeAxisState(input = {}) { const hasEditor = input.has_editor !== false; return Object.freeze({ taskState, interpState, mode, motionMode, programStatus, queue, queueLimit, - taskFile, hasEditor, onAnyLimit: Boolean(limits), toolLoaded: toolNumber > 0, + taskFile, hasEditor, onAnyLimit: Boolean(limits), toolLoaded: toolNumber > 0, motionQueueDepth: number(motion.queue_depth), allHomed: homed.length > 0 && homed.every(Boolean), homed, fault, }); } @@ -101,6 +101,13 @@ export function axisGate(action, input = {}) { case "restart": enabled = machineOn && idle && state.taskFile; reason = enabled ? "axis_restart_available" : !machineOn ? "axis_machine_off" : !state.taskFile ? "axis_taskfile_missing" : "axis_restart_state_gate"; break; case "mdi": enabled = machineOn && state.queue < state.queueLimit && (idle || state.mode === "MDI"); reason = enabled ? "axis_mdi_available" : estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : state.queue >= state.queueLimit ? "axis_mdi_queue_full" : "axis_interp_running"; break; case "jogStart": case "jogStop": case "home": case "spindle": case "coolant": enabled = manualOk && state.mode === "MANUAL"; reason = enabled ? "axis_manual_available" : estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : "axis_manual_state_gate"; break; + // Direct operator tool commands must wait for the interpreter and motion. + // emcTaskCheckPreconditions waits for IO on PREPARE and motion+IO on LOAD; + // the IO handler below this gate owns prepared/changed acknowledgements. + case "tool": case "toolPrepare": case "toolChange": + enabled = machineOn && idle && state.motionQueueDepth === 0 && ["MANUAL", "MDI"].includes(state.mode); + reason = estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : !idle ? "axis_interp_running" : state.motionQueueDepth > 0 ? "axis_motion_active" : !["MANUAL", "MDI"].includes(state.mode) ? "axis_tool_operator_mode_required" : "axis_tool_available"; + break; case "touchOff": enabled = manualOk && state.mode === "MANUAL" && trajectoryTouchOffAllowed; reason = !manualOk ? "axis_manual_state_gate" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : "axis_touch_off_available"; break; case "toolTouchOff": enabled = manualOk && state.mode === "MANUAL" && trajectoryTouchOffAllowed && state.toolLoaded; reason = !state.toolLoaded ? "axis_tool_missing" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : !manualOk ? "axis_manual_state_gate" : "axis_tool_touch_off_available"; break; case "overrideLimits": enabled = state.onAnyLimit; reason = enabled ? "axis_limit_override_available" : "axis_no_active_limit"; break; diff --git a/web/app/source-assets.mjs b/web/app/source-assets.mjs index c18c504..64b08b8 100644 --- a/web/app/source-assets.mjs +++ b/web/app/source-assets.mjs @@ -1,7 +1,8 @@ -export const AXIS_ASSET_ROOT = "/linuxcnc/share/axis/images"; +import { WEB_SETTINGS, axisImagePath } from "../workspace/web-settings.mjs"; +export const AXIS_ASSET_ROOT = `./${WEB_SETTINGS.assets.axis_images}`; export function axisAsset(name) { - return `${AXIS_ASSET_ROOT}/${name}`; + return axisImagePath(name); } export function appendAxisIcon(document, button, asset, label) { diff --git a/web/app/test_axis_contract.mjs b/web/app/test_axis_contract.mjs index e2397a4..bf685ef 100644 --- a/web/app/test_axis_contract.mjs +++ b/web/app/test_axis_contract.mjs @@ -31,4 +31,14 @@ assert.equal(axisGate("overrideLimits", limit).enabled, true); assert.equal(axisGateMatrix(ready).run.reason, "axis_run_available"); assert.equal(AXIS_COMMANDS.run, "PROGRAM_RUN"); assert.equal(AXIS_BOUNDARIES.physicalHardware, "REAL-EXCLUDED"); +for (const action of ["tool", "toolPrepare", "toolChange"]) { + assert.equal(axisGate(action, ready).enabled, true); + assert.equal(axisGate(action, running).enabled, false); + assert.equal(axisGate(action, paused).enabled, false); + assert.equal(axisGate(action, estop).enabled, false); + assert.equal(axisGate(action, { ...ready, task: { ...ready.task, state: "OFF" } }).enabled, false); + assert.equal(axisGate(action, { ...ready, motion: { ...ready.motion, queue_depth: 1 } }).reason, "axis_motion_active"); + assert.equal(axisGate(action, { ...ready, devices: { fault: true } }).reason, "axis_fault_active"); + assert.equal(axisGate(action, { ...ready, task: { ...ready.task, mode: "AUTO" } }).enabled, false); +} console.log(JSON.stringify({ schema: "axis-contract-sample.v1", status: "verified", commands: Object.keys(AXIS_COMMANDS).length, matrix: Object.keys(axisGateMatrix(ready)).length, blocked_reasons: [axisGate("mdi", queueFull).reason, axisGate("jogStart", estop).reason] })); diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index 11725d4..276d7b9 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -3,6 +3,7 @@ import { INITIAL_STATE, reduceFrontendState } from "./store"; import { ReactWorkerClient } from "./worker-client"; import { XyzacToolPreviewFromSnapshot } from "./XyzacToolPreview"; import { XyzacMachineViewFromSnapshot } from "./XyzacMachineView"; +import { WEB_SETTINGS } from "../../workspace/web-settings.mjs"; import { TrtGeometryPanel } from "./TrtGeometryPanel"; import { readTrtGeometry } from "../../workspace/trt-geometry.mjs"; import { loadSimulationCase, toWorkerConfiguration } from "../../workspace/sim-case-loader.mjs"; @@ -94,7 +95,7 @@ function ManualControl({ action, axes = DEFAULT_MACHINE.axes, canJog, canHome, c

Jog:

jog(-1)} onRelease={jogRelease} label={`Jog ${selectedAxis.toUpperCase()} negative`}>- jog(1)} onRelease={jogRelease} label={`Jog ${selectedAxis.toUpperCase()} positive`}>+ -
+
Touch OffTool Touch Off

Spindle:

onSpindle(-1)} /> onSpindle(0)} ariaLabel="Stop spindle">Stop Spindle onSpindle(1)} />
action("SPINDLE_COMMAND", { spindle: 0, delta: -100 })} onRelease={() => action("SPINDLE_CONSTANT", { spindle: 0 })} label="Decrease spindle speed">- @@ -233,8 +234,8 @@ export function App() { const [selectedHomeJoint, setSelectedHomeJoint] = useState(0); const [leftTab, setLeftTab] = useState<"manual" | "mdi" | "diagnostics">("manual"); const [rightTab, setRightTab] = useState<"preview" | "dro">("preview"); - const [view, setView] = useState<"z" | "z2" | "x" | "y" | "y2" | "p">("p"); - const [zoom, setZoom] = useState(1); + const [view, setView] = useState<"z" | "z2" | "x" | "y" | "y2" | "p">(WEB_SETTINGS.ui.initial_view as "p"); + const [zoom, setZoom] = useState(WEB_SETTINGS.ui.zoom); const [rotateMode, setRotateMode] = useState(false); const [coordType, setCoordType] = useState<"machine" | "relative">("machine"); const [displayType, setDisplayType] = useState<"actual" | "commanded">("actual"); @@ -242,7 +243,7 @@ export function App() { const [touchOffSystem, setTouchOffSystem] = useState("P1 G54"); const [aboutOpen, setAboutOpen] = useState(false); const aboutDialog = useRef(null); - const [layers, setLayers] = useState({ grid: true, tool: true, path: true, live: true, limits: false, velocity: false, offsets: false, extents: false }); + const [layers, setLayers] = useState({ ...WEB_SETTINGS.ui.layers }); const runTimer = useRef | null>(null); const homeTimer = useRef | null>(null); const jogTimer = useRef | null>(null); @@ -297,7 +298,7 @@ export function App() { if (["manual", "mdi", "diagnostics"].includes(String(cached.leftTab))) setLeftTab(cached.leftTab as "manual" | "mdi" | "diagnostics"); if (["preview", "dro"].includes(String(cached.rightTab))) setRightTab(cached.rightTab as "preview" | "dro"); if (["z", "z2", "x", "y", "y2", "p"].includes(String(cached.view))) setView(cached.view as "z" | "z2" | "x" | "y" | "y2" | "p"); - if (typeof cached.zoom === "number" && Number.isFinite(cached.zoom)) setZoom(Math.max(.5, Math.min(2, cached.zoom))); + if (typeof cached.zoom === "number" && Number.isFinite(cached.zoom)) setZoom(Math.max(WEB_SETTINGS.ui.zoom_min, Math.min(WEB_SETTINGS.ui.zoom_max, cached.zoom))); if (typeof cached.rotateMode === "boolean") setRotateMode(cached.rotateMode); if (["machine", "relative"].includes(String(cached.coordType))) setCoordType(cached.coordType as "machine" | "relative"); if (["actual", "commanded"].includes(String(cached.displayType))) setDisplayType(cached.displayType as "actual" | "commanded"); @@ -319,13 +320,17 @@ export function App() { const normalizedSource = normalizeProgramSource(source); setProgramTextState(normalizedSource); setProgramPath(DEFAULT_MACHINE.program); const configuration = toWorkerConfiguration(bundle); - await operationController.execute("LOAD_WORKSPACE", { root_path: "/workspace", configuration }, { state: {}, gate: false }); + const workspace = await operationController.execute("LOAD_WORKSPACE", { root_path: "/workspace", configuration }, { state: {}, gate: false }); setGeometryBundle(bundle); - await operationController.execute("PROGRAM_LOAD", { source: normalizedSource, path: DEFAULT_MACHINE.program }, { state: {}, gate: false }); + const payload = (workspace.response as { payload?: { power_off_recovery?: { program?: { source: string; path: string | null } }; recovery_persistence?: { status?: string } } }).payload; + const restoredProgram = payload?.power_off_recovery?.program; + if (restoredProgram?.source) { + setProgramTextState(restoredProgram.source); setProgramPath(restoredProgram.path ?? DEFAULT_MACHINE.program); + } else await operationController.execute("PROGRAM_LOAD", { source: normalizedSource, path: DEFAULT_MACHINE.program }, { state: {}, gate: false }); uiCacheHydrated.current = true; setOperationTrace(operationController.exportTrace()); - setCommandStatus("PROGRAM_LOAD acknowledged"); - setCaseStatus(`${DEFAULT_CASE} + ${DEFAULT_MACHINE.program} loaded`); + setCommandStatus(restoredProgram ? "已恢复关机前的程序和机床位置" : payload?.recovery_persistence?.status === "ignored" ? "恢复记录与当前版本或配置不匹配,已重新加载" : "PROGRAM_LOAD acknowledged"); + setCaseStatus(`${DEFAULT_CASE} + ${restoredProgram?.path ?? DEFAULT_MACHINE.program} loaded`); }).catch((error: Error) => { setOperationTrace(operationController.exportTrace()); setCommandStatus(`Error: ${error.message}`); setCaseStatus(`blocked: ${error.message}`); }).finally(() => setProgramLoading(false)); return () => { unsubscribe(); if (runTimer.current) clearInterval(runTimer.current); if (homeTimer.current) clearInterval(homeTimer.current); if (jogTimer.current) clearInterval(jogTimer.current); if (previewProgressTimer.current) clearInterval(previewProgressTimer.current); if (feedOverrideTimer.current) clearTimeout(feedOverrideTimer.current); if (uiCacheSaveTimer.current) clearTimeout(uiCacheSaveTimer.current); client.close(); }; }, [client, operationController]); @@ -373,7 +378,7 @@ export function App() { }) .catch(() => stopHomeTimer()) .finally(() => { homeStepPending.current = false; }); - }, 50); + }, WEB_SETTINGS.ui.poll_ms); }; const stopJogTimer = () => { continuousJog.current = null; if (jogTimer.current) { clearInterval(jogTimer.current); jogTimer.current = null; } }; const startJogTimer = (axis: string, direction: number) => { @@ -383,12 +388,12 @@ export function App() { const jog = continuousJog.current; if (!jog || jogStepPending.current) return; jogStepPending.current = true; - void operationController.execute("SIM_STEP", { cycles: 1, response: "motion_delta" }, { state: axisState, gate: false }) + void operationController.execute("SIM_STEP", { cycles: WEB_SETTINGS.ui.jog_cycles, response: "motion_delta" }, { state: axisState, gate: false }) .catch(() => stopJogTimer()) .finally(() => { jogStepPending.current = false; }); - }, 50); + }, WEB_SETTINGS.ui.poll_ms); }; - const startRunTimer = () => { if (runTimer.current) return; runTimer.current = setInterval(() => { if (runStepPending.current) return; runStepPending.current = true; operationController.execute("SIM_STEP", { cycles: 5, response: "motion_delta" }, { state: axisState, gate: false }).catch(() => undefined).finally(() => { runStepPending.current = false; }); }, 50); }; + const startRunTimer = () => { if (runTimer.current) return; runTimer.current = setInterval(() => { if (runStepPending.current) return; runStepPending.current = true; operationController.execute("SIM_STEP", { cycles: WEB_SETTINGS.ui.run_cycles, response: "motion_delta" }, { state: axisState, gate: false }).catch(() => undefined).finally(() => { runStepPending.current = false; }); }, WEB_SETTINGS.ui.poll_ms); }; const dispatch = async (kind: string, payload: Record = {}, gate = true, action: string | null = null): Promise => { setCommandStatus(`${kind} pending`); // Serialize browser samples around control edges in the Worker transport. @@ -456,7 +461,7 @@ export function App() { if (!previous || previous.percent >= 88) return previous; return { percent: Math.min(88, previous.percent + 4), phase: "wasm", detail: `${label}: LinuxCNC WASM 解析刀具路径` }; }); - }, 100); + }, WEB_SETTINGS.ui.preview_progress_ms); }; const markPreviewProgress = (percent: number, phase: "reading" | "wasm", detail: string) => { setPreviewProgress({ percent, phase, detail }); @@ -728,16 +733,17 @@ export function App() { void dispatch(toolOnly ? "TOOL_TOUCH_OFF" : "TOUCH_OFF", { axis: selectedAxis, value, system: normalizedSystem, units: metric ? "mm" : "inch" }).catch(() => undefined); }; const wasmMotion = (state.snapshot?.wasm_motion as { state?: Record } | undefined)?.state ?? {}; + const maxFeedPercent = Number((state.snapshot?.motion as Record | undefined)?.max_feed_override ?? WEB_SETTINGS.runtime.planner.max_feed_override) * 100; const sourceFeedOverride = Number(state.snapshot?.overrides?.feed ?? wasmMotion.feed_override); - const feedOverridePercent = Number.isFinite(sourceFeedOverride) ? Math.max(0, Math.min(200, Math.round(sourceFeedOverride * 100))) : null; + const feedOverridePercent = Number.isFinite(sourceFeedOverride) ? Math.max(0, Math.min(maxFeedPercent, Math.round(sourceFeedOverride * 100))) : null; const sourceJogScale = Number((state.snapshot?.motion as Record | undefined)?.jog_scale ?? wasmMotion.jog_scale); const sourceVelocityScale = Number((state.snapshot?.motion as Record | undefined)?.velocity_scale ?? wasmMotion.velocity_scale); - const jogScalePercent = Number.isFinite(sourceJogScale) ? Math.max(6, Math.min(100, Math.round(sourceJogScale * 100))) : null; - const maxVelocityPercent = Number.isFinite(sourceVelocityScale) ? Math.max(6, Math.min(100, Math.round(sourceVelocityScale * 100))) : null; + const jogScalePercent = Number.isFinite(sourceJogScale) ? Math.max(WEB_SETTINGS.ui.motion_scale_min * 100, Math.min(100, Math.round(sourceJogScale * 100))) : null; + const maxVelocityPercent = Number.isFinite(sourceVelocityScale) ? Math.max(WEB_SETTINGS.ui.motion_scale_min * 100, Math.min(100, Math.round(sourceVelocityScale * 100))) : null; const spindleOverrideSource = Number(state.snapshot?.overrides?.spindle?.[0] ?? (state.snapshot?.motion as Record | undefined)?.spindle_override ?? spindleState?.override); const spindleOverridePercent = Number.isFinite(spindleOverrideSource) ? Math.max(0, Math.min(200, Math.round(spindleOverrideSource * 100))) : null; const requestFeedOverride = (event: React.ChangeEvent) => { - const percent = Math.max(0, Math.min(200, Number(event.target.value))); + const percent = Math.max(0, Math.min(maxFeedPercent, Number(event.target.value))); if (!Number.isFinite(percent)) return; feedOverridePending.current = percent; if (feedOverrideTimer.current) clearTimeout(feedOverrideTimer.current); @@ -746,7 +752,7 @@ export function App() { const target = feedOverridePending.current; if (target === null) return; void dispatch("SET_OVERRIDE", { feed: target / 100 }).then(() => { if (feedOverridePending.current === target) feedOverridePending.current = null; }).catch(() => { if (feedOverridePending.current === target) feedOverridePending.current = null; }); - }, 80); + }, WEB_SETTINGS.ui.override_debounce_ms); }; const activeGcodes = programLoading ? "" : state.program?.active_gcodes ? String(state.program.active_gcodes) : "-"; const toolNumber = String((state.devices as Record | null)?.tool?.current ?? state.snapshot?.tool?.current ?? 0); @@ -790,13 +796,13 @@ export function App() { return
AXIS AXISLinuxCNC Web/WASM
Connection: {state.status.toUpperCase()}
-
execute("PROGRAM_STEP")} /> execute(programPaused ? "PROGRAM_RESUME" : "PROGRAM_PAUSE")} pressed={relief.pauseResume} /> stopProgram()} pressed={relief.abort} /> setZoom((value) => Math.min(2, value + .2))} /> setZoom((value) => Math.max(.5, value - .2))} /> { setView("z"); setRightTab("preview"); }} /> { setView("z2"); setRightTab("preview"); }} /> { setView("x"); setRightTab("preview"); }} /> { setView("y"); setRightTab("preview"); }} /> { setView("y2"); setRightTab("preview"); }} /> { setView("p"); setRightTab("preview"); }} /> setRotateMode((value) => !value)} /> xyzacCommand("VISUAL_CLEAR", {}, "vismachClear")} />
+
execute("PROGRAM_STEP")} /> execute(programPaused ? "PROGRAM_RESUME" : "PROGRAM_PAUSE")} pressed={relief.pauseResume} /> stopProgram()} pressed={relief.abort} /> setZoom((value) => Math.min(WEB_SETTINGS.ui.zoom_max, value + WEB_SETTINGS.ui.zoom_step))} /> setZoom((value) => Math.max(WEB_SETTINGS.ui.zoom_min, value - WEB_SETTINGS.ui.zoom_step))} /> { setView("z"); setRightTab("preview"); }} /> { setView("z2"); setRightTab("preview"); }} /> { setView("x"); setRightTab("preview"); }} /> { setView("y"); setRightTab("preview"); }} /> { setView("y2"); setRightTab("preview"); }} /> { setView("p"); setRightTab("preview"); }} /> setRotateMode((value) => !value)} /> xyzacCommand("VISUAL_CLEAR", {}, "vismachClear")} />
Load {selectedMachine.coordinates} boat program{programLoading ? "Loading program..." : caseStatus.startsWith("blocked:") ? caseStatus : `Program: ${loadedProgramName}`}{previewProgress &&
{previewProgress.detail}{previewProgress.percent}%{previewProgress.percent}%
}
{ void loadCase(path); }} /> -
-
{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => )}
+
+
{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => )}
{aboutOpen &&
{ if (event.target === event.currentTarget) setAboutOpen(false); }}>

LinuxCNC AXIS Web/WASM

{selectedMachine.label}

setAboutOpen(false)}>OK
} diff --git a/web/react/src/XyzacMachineView.tsx b/web/react/src/XyzacMachineView.tsx index c3a5f65..a60b800 100644 --- a/web/react/src/XyzacMachineView.tsx +++ b/web/react/src/XyzacMachineView.tsx @@ -1,4 +1,7 @@ import { useEffect, useRef } from "react"; +import { WEB_SETTINGS } from "../../workspace/web-settings.mjs"; +const VIEWER = WEB_SETTINGS.viewer; +const CAMERA = VIEWER.camera; import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js"; @@ -11,7 +14,7 @@ type Segment = { id?: number; active?: boolean; motion_type?: number; samples?: type Props = { visual: XyzacVisualState; toolpath?: Segment[]; view?: "z" | "z2" | "x" | "y" | "y2" | "p"; zoom?: number; rotateMode?: boolean; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean }; type Controller = Record void>; type Host = HTMLDivElement & { xyzacMachineController?: Controller }; -const toView = (point: Point) => new THREE.Vector3(Number(point.x ?? 0) * .01, Number(point.z ?? 0) * .01, Number(point.y ?? 0) * .01); +const toView = (point: Point) => new THREE.Vector3(Number(point.x ?? 0) * VIEWER.mm_scale, Number(point.z ?? 0) * VIEWER.mm_scale, Number(point.y ?? 0) * VIEWER.mm_scale); export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotateMode = false, showPath = false, showLive = false, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) { const host = useRef(null); @@ -23,7 +26,7 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat const element = host.current; if (!element) return; const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, preserveDrawingBuffer: true }); - renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, 2)); + renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, VIEWER.pixel_ratio_limit)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1; renderer.shadowMap.enabled = true; @@ -31,7 +34,7 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat renderer.domElement.dataset.engine = "three.js r185 / xyzac-trt-vismach"; element.appendChild(renderer.domElement); - const scene = new THREE.Scene(); scene.background = new THREE.Color(0x252b2c); + const scene = new THREE.Scene(); scene.background = new THREE.Color(VIEWER.colors.background); const studio = new RoomEnvironment(); const pmrem = new THREE.PMREMGenerator(renderer); const environment = pmrem.fromScene(studio, .04); @@ -60,11 +63,11 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat const nameplate = new THREE.Mesh(new THREE.PlaneGeometry(.84, .21), new THREE.MeshBasicMaterial({ map: nameplateTexture })); nameplate.position.set(-.57, -1.48, -1.046); nameplate.rotation.y = Math.PI; assembly.machine.add(nameplate); - const camera = new THREE.PerspectiveCamera(36, 1, .02, 150); + const camera = new THREE.PerspectiveCamera(CAMERA.fov, 1, CAMERA.near, CAMERA.far); const controls = new OrbitControls(camera, renderer.domElement); - controls.enableDamping = true; controls.dampingFactor = .12; + controls.enableDamping = true; controls.dampingFactor = CAMERA.damping; controls.enableRotate = true; - controls.screenSpacePanning = true; controls.minDistance = .25; controls.maxDistance = 65; + controls.screenSpacePanning = true; controls.minDistance = CAMERA.min_distance; controls.maxDistance = CAMERA.max_distance; // Keep the standard Three.js mouse contract: left-drag orbits, middle // drag zooms, right-drag pans, and the wheel zooms. The machine canvas // must remain directly navigable without first toggling a mode button. @@ -96,7 +99,7 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat const point = new THREE.Vector3(x, y, z).sub(modelCenter); distance = Math.max(distance, point.dot(direction) + Math.max(Math.abs(point.dot(right)) / (tanV * camera.aspect), Math.abs(point.dot(up)) / tanV)); } - controls.target.copy(modelCenter); camera.position.copy(modelCenter).addScaledVector(direction, distance * 1.12 / Math.max(.5, scale)); + controls.target.copy(modelCenter); camera.position.copy(modelCenter).addScaledVector(direction, distance * CAMERA.fit_margin / Math.max(.5, scale)); camera.lookAt(controls.target); controls.update(); }; const resize = () => { @@ -106,7 +109,7 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat }; const observer = new ResizeObserver(resize); observer.observe(element); resize(); const pan = (x: number, y: number) => { - const amount = camera.position.distanceTo(controls.target) * .035; + const amount = camera.position.distanceTo(controls.target) * CAMERA.pan_fraction; camera.updateMatrix(); const offset = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 0).multiplyScalar(x * amount) .addScaledVector(new THREE.Vector3().setFromMatrixColumn(camera.matrix, 1), y * amount); @@ -118,12 +121,12 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat camera.position.copy(controls.target).add(offset); controls.update(); }; element.xyzacMachineController = { - zoomIn: () => zoomCamera(.8), zoomOut: () => zoomCamera(1.25), + zoomIn: () => zoomCamera(CAMERA.zoom_in), zoomOut: () => zoomCamera(CAMERA.zoom_out), panLeft: () => pan(-1, 0), panRight: () => pan(1, 0), panUp: () => pan(0, 1), panDown: () => pan(0, -1), - rotateLeft: () => { controls.rotateLeft(Math.PI / 12); controls.update(); }, - rotateRight: () => { controls.rotateLeft(-Math.PI / 12); controls.update(); }, - rotateUp: () => { controls.rotateUp(Math.PI / 12); controls.update(); }, - rotateDown: () => { controls.rotateUp(-Math.PI / 12); controls.update(); }, + rotateLeft: () => { controls.rotateLeft(THREE.MathUtils.degToRad(CAMERA.rotate_degrees)); controls.update(); }, + rotateRight: () => { controls.rotateLeft(-THREE.MathUtils.degToRad(CAMERA.rotate_degrees)); controls.update(); }, + rotateUp: () => { controls.rotateUp(THREE.MathUtils.degToRad(CAMERA.rotate_degrees)); controls.update(); }, + rotateDown: () => { controls.rotateUp(-THREE.MathUtils.degToRad(CAMERA.rotate_degrees)); controls.update(); }, reset: () => { lastPreset = ""; }, tool: () => { const target = assembly.toolTip.getWorldPosition(new THREE.Vector3()).add(new THREE.Vector3(0, .2, 0)); @@ -136,11 +139,11 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat // workpiece path together with the physical table. const pathGroup = new THREE.Group(); pathGroup.name = "xyzac-toolpath"; assembly.joints.rotate.add(pathGroup); const liveGroup = new THREE.Group(); liveGroup.name = "xyzac-live-toolpath"; assembly.joints.rotate.add(liveGroup); - const pathMaterials = [0xffff00, 0xff9a55, 0x5c9d9a].map((color) => new THREE.LineBasicMaterial({ color })); + const pathMaterials = [VIEWER.colors.path, VIEWER.colors.live, VIEWER.colors.rapid].map((color) => new THREE.LineBasicMaterial({ color })); const clearLines = (group: THREE.Group) => { group.children.forEach((child) => (child as THREE.Line).geometry.dispose()); group.clear(); }; - const limits = new THREE.Box3Helper(new THREE.Box3(), 0xdd7566); scene.add(limits); + const limits = new THREE.Box3Helper(new THREE.Box3(), VIEWER.colors.limits); scene.add(limits); const offsets = new THREE.AxesHelper(.8); scene.add(offsets); - const extents = new THREE.Box3Helper(initialBounds, 0xe6c56b); scene.add(extents); + const extents = new THREE.Box3Helper(initialBounds, VIEWER.colors.extents); scene.add(extents); const render = () => { const state = visualRef.current, config = settings.current; assembly.update(state); @@ -165,7 +168,7 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat } offsets.visible = config.showOffsets; const coordinates = state.machineModel?.coordinate_system ?? "XYZAC"; - offsets.position.set(coordinates === "XYZBC" ? Number(state.offsets.x_offset ?? 0) * .01 : 0, state.offsets.z_offset * .01, state.offsets.y_offset * .01); + offsets.position.set(coordinates === "XYZBC" ? Number(state.offsets.x_offset ?? 0) * VIEWER.mm_scale : 0, state.offsets.z_offset * VIEWER.mm_scale, state.offsets.y_offset * VIEWER.mm_scale); extents.visible = config.showExtents; const preset = `${config.view}:${config.zoom}`; if (preset !== lastPreset) { presetCamera(); lastPreset = preset; } diff --git a/web/react/src/XyzacToolPreview.tsx b/web/react/src/XyzacToolPreview.tsx index c2c9bf5..8631cbf 100644 --- a/web/react/src/XyzacToolPreview.tsx +++ b/web/react/src/XyzacToolPreview.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef } from "react"; +import { WEB_SETTINGS } from "../../workspace/web-settings.mjs"; import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { projectXyzacVisual, type XyzacVisualSource, type XyzacVisualState } from "./xyzac-visual-adapter"; @@ -8,7 +9,7 @@ type Segment = { id?: number; source_line?: number | null; active?: boolean; kin type Props = { visual: XyzacVisualState; view?: "z" | "z2" | "x" | "y" | "y2" | "p"; zoom?: number; rotateMode?: boolean; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean }; const DEG = Math.PI / 180; -const MM = 0.01; +const MM = WEB_SETTINGS.viewer.mm_scale; const sourcePoint = (point: Point) => new THREE.Vector3(Number(point.x ?? 0) * MM, Number(point.z ?? 0) * MM, Number(point.y ?? 0) * MM); export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = false, showPath = true, showLive = true, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) { @@ -24,11 +25,11 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa const element = host.current; if (!element) return; const scene = new THREE.Scene(); - scene.background = new THREE.Color(0x050a0e); + scene.background = new THREE.Color(WEB_SETTINGS.viewer.toolpath_colors.background); scene.add(new THREE.HemisphereLight(0xdcecff, 0x121820, 1.4)); let camera: THREE.PerspectiveCamera | THREE.OrthographicCamera = new THREE.PerspectiveCamera(40, 1, 0.001, 100); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, preserveDrawingBuffer: true }); - renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, 2)); + renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, WEB_SETTINGS.viewer.pixel_ratio_limit)); renderer.domElement.dataset.engine = "three.js r185 / xyzac-tool-preview"; element.appendChild(renderer.domElement); let controls: OrbitControls = new OrbitControls(camera, renderer.domElement); @@ -85,12 +86,12 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa } group.clear(); }; - const completed = new THREE.LineBasicMaterial({ color: 0xd8dfe3, depthTest: false }); - const rapid = new THREE.LineDashedMaterial({ color: 0xa85d5d, dashSize: .025, gapSize: .018, depthTest: false }); + const completed = new THREE.LineBasicMaterial({ color: WEB_SETTINGS.viewer.toolpath_colors.completed, depthTest: false }); + const rapid = new THREE.LineDashedMaterial({ color: WEB_SETTINGS.viewer.toolpath_colors.rapid, dashSize: .025, gapSize: .018, depthTest: false }); // LinuxCNC uses yellow for jog backplot. Coordinated program execution is // red (live plot), while an unambiguous current program block is cyan. - const live = new THREE.LineBasicMaterial({ color: 0x00ffff, depthTest: false }); - const liveHistory = new THREE.LineBasicMaterial({ color: 0xff6b4a, depthTest: false }); + const live = new THREE.LineBasicMaterial({ color: WEB_SETTINGS.viewer.toolpath_colors.active, depthTest: false }); + const liveHistory = new THREE.LineBasicMaterial({ color: WEB_SETTINGS.viewer.toolpath_colors.live, depthTest: false }); let frame = 0; let liveTick = -1; let liveTrace: THREE.Vector3[] = []; diff --git a/web/react/src/legacy.d.ts b/web/react/src/legacy.d.ts index bf137c9..b81ea63 100644 --- a/web/react/src/legacy.d.ts +++ b/web/react/src/legacy.d.ts @@ -1,4 +1,6 @@ declare module "*.mjs" { + export const WEB_SETTINGS: typeof import("../../workspace/web-settings.json"); + export function axisImagePath(name: string, base?: string): string; export const TRT_GEOMETRY_KEYS: readonly string[]; export function readTrtGeometry(configuration: { entrypoint: string; files: Record }): { coordinates: string; offsets: Record; sources: Record; unsupported: string; state_hash: string }; export function writeTrtGeometry }>(configuration: T, values: Record): T; diff --git a/web/react/src/sourceAssets.ts b/web/react/src/sourceAssets.ts index 095d10e..f8a6e21 100644 --- a/web/react/src/sourceAssets.ts +++ b/web/react/src/sourceAssets.ts @@ -1,6 +1,6 @@ -// AXIS ships these exact 21/22/24px bitmap assets in the source tree. -export const LINUXCNC_AXIS_ASSETS = "/linuxcnc/share/axis/images"; +import { axisImagePath, WEB_SETTINGS } from "../../workspace/web-settings.mjs"; +export const LINUXCNC_AXIS_ASSETS = `${import.meta.env.BASE_URL}${WEB_SETTINGS.assets.axis_images}`; export function axisIcon(name: string): string { - return `${LINUXCNC_AXIS_ASSETS}/${name}`; + return axisImagePath(name, import.meta.env.BASE_URL); } diff --git a/web/react/src/test_trt_parametric.mjs b/web/react/src/test_trt_parametric.mjs index de5ffd2..eade2d3 100644 --- a/web/react/src/test_trt_parametric.mjs +++ b/web/react/src/test_trt_parametric.mjs @@ -82,7 +82,7 @@ async function execute(bundle, source) { const beforeRecovery = session.xyzacKinematics.snapshot(); assert.equal(session.restoreCheckpoint(savedCheckpoint).status, "blocked"); assert.equal(session.restoreCompactPowerOff(savedCompact, seq + 1).status, "blocked"); - assert.throws(() => session.importTrace(savedTrace), /trt_recovery_geometry_mismatch/); + assert.throws(() => session.importTrace(savedTrace), /trt_recovery_(geometry|configuration)_mismatch/); assert.deepEqual(session.xyzacKinematics.snapshot(), beforeRecovery); assert.equal(session.wasmHomeLimit.read().state.homed, false); return { trace, offsets: expected, final: trace.at(-1), preview_hash: fnv1a64(preview.payload.preview), trace_hash: fnv1a64(trace), model_samples: samples.length }; diff --git a/web/react/src/test_xyzac_threejs.mjs b/web/react/src/test_xyzac_threejs.mjs index 9d5681d..3bc6dcb 100644 --- a/web/react/src/test_xyzac_threejs.mjs +++ b/web/react/src/test_xyzac_threejs.mjs @@ -42,7 +42,7 @@ assert.equal(visual.source, "linuxcnc-wasm-worker"); assert.equal(visual.machineModel?.id, "xyzac-trt"); assert.equal(visual.machineModel?.source_hash, "fnv1a64:model"); assert.deepEqual(XYZAC_MACHINE_GROUPS, ["xyzac-machine", "table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c", "tool-offset", "xyzac-toolpath", "xyzac-live-toolpath"]); -for (const token of ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c", "tool-offset", "tableX.position.x = -tableXJoint * MM", "saddle.position.z = -saddleYJoint * MM", "zslide.position.y = (200 - spindleZJoint) * MM", "tilt.rotation.x = -tiltAJoint * DEG", "rotate.rotation.y = -rotateCJoint * DEG", "tool.position.y = -state.offsets.tool_offset * MM", "assembly.joints.rotate.add(pathGroup)", "assembly.joints.rotate.add(liveGroup)", "dataset.xyzacToolpathFrame", "dataset.xyzacToolpathAttached", "data-xyzac-tick", "data-xyzac-state-hash", "data-xyzac-active-segment", "data-xyzac-joints", "data-xyzac-offsets"]) assert.ok(source.includes(token), token); +for (const token of ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c", "tool-offset", "tableX.position.x = -tableXJoint * MM", "saddle.position.z = -saddleYJoint * MM", "zslide.position.y = (datum - spindleZJoint) * MM", "tilt.rotation.x = -tiltAJoint * DEG", "rotate.rotation.y = -rotateCJoint * DEG", "tool.position.y = -state.offsets.tool_offset * MM", "assembly.joints.rotate.add(pathGroup)", "assembly.joints.rotate.add(liveGroup)", "dataset.xyzacToolpathFrame", "dataset.xyzacToolpathAttached", "data-xyzac-tick", "data-xyzac-state-hash", "data-xyzac-active-segment", "data-xyzac-joints", "data-xyzac-offsets"]) assert.ok(source.includes(token), token); for (const forbidden of [/accept\s*\(/, /postMessage\s*\(/, /_sim_/, /_malloc/, /navigator\.storage/, /HAL_SET/, /KINEMATICS_SELECT/]) assert.equal(forbidden.test(source), false, String(forbidden)); assert.ok(adapter.includes("source: \"linuxcnc-wasm-worker\"")); assert.equal(source.includes("axis-preview-hud xyzac-machine-hud"), false, "machine preview HUD must stay empty"); diff --git a/web/react/src/xyzac-machine-model.ts b/web/react/src/xyzac-machine-model.ts index c44562c..297462f 100644 --- a/web/react/src/xyzac-machine-model.ts +++ b/web/react/src/xyzac-machine-model.ts @@ -2,27 +2,18 @@ import * as THREE from "three"; import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js"; import type { XyzacVisualState } from "./xyzac-visual-adapter"; -const MM = .01; +import { WEB_SETTINGS } from "../../workspace/web-settings.mjs"; +const MM = WEB_SETTINGS.viewer.mm_scale; const DEG = Math.PI / 180; // Source coordinates remain millimetres, Z up. The existing viewer uses Y up. function buildTrtMachine(coordinates = "XYZAC") { const isXyzbc = coordinates === "XYZBC"; + const datum = WEB_SETTINGS.viewer.models[isXyzbc ? "XYZBC" : "XYZAC"].datum_z_mm; // Separate source identities: XYZAC uses tilt-a, XYZBC uses tilt-b. const rotaryAxis = isXyzbc ? "b" : "a"; const geometries = new Map(); - const materials = { - enamel: new THREE.MeshStandardMaterial({ color: 0xd9e1e1, roughness: .32, metalness: .25 }), - teal: new THREE.MeshStandardMaterial({ color: 0x167f79, roughness: .3, metalness: .3 }), - dark: new THREE.MeshStandardMaterial({ color: 0x303638, roughness: .42, metalness: .35 }), - steel: new THREE.MeshStandardMaterial({ color: 0xb2bdc4, roughness: .27, metalness: .85 }), - polished: new THREE.MeshStandardMaterial({ color: 0xe2e8ec, roughness: .19, metalness: .9 }), - recess: new THREE.MeshStandardMaterial({ color: 0x111718, roughness: .66, metalness: .2 }), - rubber: new THREE.MeshStandardMaterial({ color: 0x222729, roughness: .88 }), - carbide: new THREE.MeshStandardMaterial({ color: 0xb9aa79, roughness: .29, metalness: .8 }), - warning: new THREE.MeshStandardMaterial({ color: 0xe7bd48, roughness: .48, metalness: .12 }), - lamp: new THREE.MeshStandardMaterial({ color: 0xe2f8ff, emissive: 0xd4f2ff, emissiveIntensity: 1.5 }), - }; + const materials = Object.fromEntries(Object.entries(WEB_SETTINGS.viewer.materials).map(([name, properties]) => [name, new THREE.MeshStandardMaterial(properties)])) as Record; type Finish = keyof typeof materials; const geometry = (key: string, create: () => THREE.BufferGeometry) => { if (!geometries.has(key)) geometries.set(key, create()); @@ -84,7 +75,7 @@ function buildTrtMachine(coordinates = "XYZAC") { box(machine, "z-servo", 0, -105, -169, 49, 21, 50, "dark", 3); box(machine, "z-servo-cover", 0, -117, -169, 40, 3, 38, "teal", 2); - const head = group(machine, "head", 0, 0, isXyzbc ? 150 : 200); + const head = group(machine, "head", 0, 0, datum); // XYZAC has the long moving knee bridge; XYZBC has the shorter moving // spindle head from xyzbc-trt-gui.py. const headDepth = isXyzbc ? 130 : 270; @@ -175,8 +166,8 @@ function buildTrtMachine(coordinates = "XYZAC") { } for (let i = 0; i < 4; i++) { const slot = box(rotate, "radial-t-slot", 0, 0, .14, 3, 42, .3, "recess", .1); - slot.position.x = Math.sin(i * Math.PI / 2) * .28; - slot.position.z = Math.cos(i * Math.PI / 2) * .28; + slot.position.x = Math.sin(i * Math.PI / 2) * (28 * MM); + slot.position.z = Math.cos(i * Math.PI / 2) * (28 * MM); slot.rotation.y = i * Math.PI / 2; } for (let i = 0; i < 72; i++) { @@ -197,18 +188,18 @@ function buildTrtMachine(coordinates = "XYZAC") { tableX.position.x = -tableXJoint * MM; saddle.position.z = -saddleYJoint * MM; if (coordinates === "XYZBC") { - head.position.y = (150 + spindleZJoint) * MM; + head.position.y = (datum + spindleZJoint) * MM; // XYZBC is a table tilt: the source model rotates the B assembly // around machine Y. The Web scene maps source Y to Three.js Z. head.rotation.set(0, 0, 0); - zslide.position.y = 150 * MM; + zslide.position.y = datum * MM; tilt.rotation.z = -tiltAJoint * DEG; tilt.position.set(xOffset * MM, zOffset * MM, 0).add(pivot); brackets.position.copy(tilt.position); rotaryBase.position.set(-xOffset * MM, -zOffset * MM, 0); } else { - zslide.position.y = (200 - spindleZJoint) * MM; - head.position.y = 2; + zslide.position.y = (datum - spindleZJoint) * MM; + head.position.y = datum * MM; head.rotation.set(0, 0, 0); // XYZAC source transform: tilt.rotation.x = -tiltAJoint * DEG. tilt.rotation.x = -tiltAJoint * DEG; @@ -251,8 +242,8 @@ function createFlutedCutter() { const angle = col / radial * Math.PI * 2; const phase = (angle * 4 - t * Math.PI * 2 + Math.PI * 4) % (Math.PI * 2); const relief = Math.pow(Math.max(0, Math.sin(phase / 2)), 2); - const radius = (3 - 1.35 * relief) * .01; - positions.push(radius * Math.cos(angle), t * .08, radius * Math.sin(angle)); + const radius = (3 - 1.35 * relief) * MM; + positions.push(radius * Math.cos(angle), t * 8 * MM, radius * Math.sin(angle)); if (row < axial && col < radial) { const a = row * (radial + 1) + col, b = a + radial + 1; indices.push(a, b, a + 1, a + 1, b, b + 1); @@ -260,7 +251,7 @@ function createFlutedCutter() { } } const bottom = positions.length / 3; positions.push(0, 0, 0); - const top = positions.length / 3; positions.push(0, .08, 0); + const top = positions.length / 3; positions.push(0, 8 * MM, 0); for (let col = 0; col < radial; col++) { indices.push(bottom, col, col + 1); const a = axial * (radial + 1) + col; diff --git a/web/react/vite.config.ts b/web/react/vite.config.ts index d42826b..a1c6d29 100644 --- a/web/react/vite.config.ts +++ b/web/react/vite.config.ts @@ -8,7 +8,7 @@ import tailwindcss from "@tailwindcss/vite"; const repoRoot = fileURLToPath(new URL("../../", import.meta.url)); const sourceRoot = resolve(repoRoot, "linuxcnc"); const contentTypes: Record = { - ".ini": "text/plain; charset=utf-8", ".hal": "text/plain; charset=utf-8", ".inc": "text/plain; charset=utf-8", ".tbl": "text/plain; charset=utf-8", ".var": "text/plain; charset=utf-8", ".tcl": "text/plain; charset=utf-8", ".ngc": "text/plain; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".mjs": "text/javascript; charset=utf-8", ".gif": "image/gif", ".png": "image/png", ".svg": "image/svg+xml", + ".json": "application/json; charset=utf-8", ".ini": "text/plain; charset=utf-8", ".hal": "text/plain; charset=utf-8", ".inc": "text/plain; charset=utf-8", ".tbl": "text/plain; charset=utf-8", ".var": "text/plain; charset=utf-8", ".tcl": "text/plain; charset=utf-8", ".ngc": "text/plain; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".mjs": "text/javascript; charset=utf-8", ".gif": "image/gif", ".png": "image/png", ".svg": "image/svg+xml", }; function linuxcncAssets(): Plugin { diff --git a/web/worker/authoritative-runtime.mjs b/web/worker/authoritative-runtime.mjs index 7acbc75..7814418 100644 --- a/web/worker/authoritative-runtime.mjs +++ b/web/worker/authoritative-runtime.mjs @@ -1,17 +1,19 @@ import { fnv1a64 } from "./replay.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; +const CONFIG = WEB_SETTINGS.runtime.compatibility; export const AUTHORITATIVE_RUNTIME_SCHEMA = "sim_authoritative_runtime.v1"; const AXES = Object.freeze(["x", "y", "z"]); -const STEP_FRACTION = 0.25; +const STEP_FRACTION = CONFIG.step_fraction; const KINEMATICS = new Set(["trivkins", "corexy", "tripod", "lineardelta", "rotarydelta"]); const KINEMATICS_ALIASES = new Map([["corexykins", "corexy"], ["tripodkins", "tripod"], ["lineardeltakins", "lineardelta"], ["rotarydeltakins", "rotarydelta"]]); -const TRIPOD_BASE = Object.freeze([[0, 0], [10, 0], [5, 7.071]]); -const LINEAR_DELTA_RADIUS = 130.25; -const LINEAR_DELTA_ROD = 269; -const ROTARY_DELTA_PLATFORM = 10; -const ROTARY_DELTA_THIGH = 10; -const ROTARY_DELTA_SHIN = 14; -const ROTARY_DELTA_FOOT = 6; +const TRIPOD_BASE = CONFIG.tripod_base; +const LINEAR_DELTA_RADIUS = CONFIG.linear_delta.radius; +const LINEAR_DELTA_ROD = CONFIG.linear_delta.rod; +const ROTARY_DELTA_PLATFORM = CONFIG.rotary_delta.platform; +const ROTARY_DELTA_THIGH = CONFIG.rotary_delta.thigh; +const ROTARY_DELTA_SHIN = CONFIG.rotary_delta.shin; +const ROTARY_DELTA_FOOT = CONFIG.rotary_delta.foot; const DEG_TO_RAD = Math.PI / 180; const RAD_TO_DEG = 180 / Math.PI; diff --git a/web/worker/motion-state.mjs b/web/worker/motion-state.mjs index fac6b85..aa5de7c 100644 --- a/web/worker/motion-state.mjs +++ b/web/worker/motion-state.mjs @@ -1,10 +1,12 @@ import { fnv1a64 } from "./replay.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; +const CONFIG = WEB_SETTINGS.runtime.compatibility; export const MOTION_SCHEMA = "sim_motion.v1"; const AXES = Object.freeze(["x", "y", "z"]); -const MAX_POSITION = 1e9; -const MAX_SPEED = 1e6; -const CYCLE_DISTANCE = 0.001; +const MAX_POSITION = CONFIG.max_position; +const MAX_SPEED = CONFIG.max_speed; +const CYCLE_DISTANCE = CONFIG.cycle_time; function clone(value) { return structuredClone(value); } function finite(value) { return typeof value === "number" && Number.isFinite(value); } diff --git a/web/worker/native-checkpoint.mjs b/web/worker/native-checkpoint.mjs new file mode 100644 index 0000000..316616b --- /dev/null +++ b/web/worker/native-checkpoint.mjs @@ -0,0 +1,55 @@ +import { fnv1a64 } from "./replay.mjs"; + +function buildId(core) { + if (typeof core._sim_checkpoint_build_id !== "function") throw new Error("native_checkpoint_build_id_missing"); + const pointer = core._sim_checkpoint_build_id(); + const id = new TextDecoder().decode(core.HEAPU8.subarray(pointer, pointer + 64)); + if (!/^[a-f0-9]{64}$/.test(id)) throw new Error("native_checkpoint_unversioned_build"); + return id; +} + +// Copy opaque native state with zero-filled blocks omitted. No arithmetic or +// state reconstruction occurs here; each C adapter owns its layout/validation. +export function exportNativeCheckpoint(core, prefix, owner = null) { + const length = core[`_${prefix}_checkpoint_size`](); + const pointer = core._malloc(length); + if (!pointer) throw new Error(`${prefix}_checkpoint_allocation`); + try { + const args = owner === null ? [pointer, length] : [owner, pointer, length]; + const code = core[`_${prefix}_checkpoint_export`](...args); + if (code !== 0) throw new Error(`${prefix}_checkpoint_export_${code}`); + const bytes = core.HEAPU8.subarray(pointer, pointer + length), blocks = []; + for (let offset = 0; offset < length; offset += 256) { + const block = bytes.subarray(offset, Math.min(offset + 256, length)); + if (block.some((value) => value !== 0)) blocks.push([offset, Array.from(block, (value) => value.toString(16).padStart(2, "0")).join("")]); + } + const value = { schema: "native-checkpoint.v1", component: prefix, build_id: buildId(core), byte_length: length, blocks }; + return { ...value, state_hash: fnv1a64(value) }; + } finally { core._free(pointer); } +} + +export function validateNativeCheckpoint(core, prefix, value) { + if (value?.build_id !== buildId(core)) throw new Error(`${prefix}_checkpoint_build_mismatch`); + const length = core[`_${prefix}_checkpoint_size`](); + if (!value || value.schema !== "native-checkpoint.v1" || value.component !== prefix || value.byte_length !== length || !Array.isArray(value.blocks) || fnv1a64(Object.fromEntries(Object.entries(value).filter(([key]) => key !== "state_hash"))) !== value.state_hash) throw new Error(`${prefix}_checkpoint_invalid`); + const bytes = new Uint8Array(length); let end = 0; + for (const [offset, hex] of value.blocks) { + if (!Number.isSafeInteger(offset) || offset < end || typeof hex !== "string" || !/^(?:[0-9a-f]{2})+$/.test(hex) || offset + hex.length / 2 > length) throw new Error(`${prefix}_checkpoint_blocks`); + for (let index = 0; index < hex.length; index += 2) bytes[offset + index / 2] = Number.parseInt(hex.slice(index, index + 2), 16); + end = offset + hex.length / 2; + } + return bytes; +} + +export function restoreNativeCheckpoint(core, prefix, value, owner = null) { + const bytes = validateNativeCheckpoint(core, prefix, value); + const length = bytes.length; + const pointer = core._malloc(length); + if (!pointer) throw new Error(`${prefix}_checkpoint_allocation`); + try { + core.HEAPU8.set(bytes, pointer); + const args = owner === null ? [pointer, length] : [owner, pointer, length]; + const code = core[`_${prefix}_checkpoint_restore`](...args); + if (code !== 0) throw new Error(`${prefix}_checkpoint_restore_${code}`); + } finally { core._free(pointer); } +} diff --git a/web/worker/opfs-ui-state.mjs b/web/worker/opfs-ui-state.mjs index 939ec7b..99a04c8 100644 --- a/web/worker/opfs-ui-state.mjs +++ b/web/worker/opfs-ui-state.mjs @@ -1,4 +1,5 @@ import { fnv1a64 } from "./replay.mjs"; +import { DEFAULT_MACHINE } from "../workspace/machine-definitions.mjs"; export const OPFS_UI_STATE_SCHEMA = "linuxcnc-ui-temp-json.v1"; export const OPFS_UI_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000; @@ -77,7 +78,7 @@ async function writeFile(project, name, value) { return { status: "verified", bytes: bytes.byteLength }; } -export async function loadUiState({ contextId, key = "xyzac-trt", storage } = {}) { +export async function loadUiState({ contextId, key = DEFAULT_MACHINE.id, storage } = {}) { const resolved = await handles(key, false, storage).catch((error) => ({ status: "blocked", code: "opfs_open_failed", reason: error?.name ?? "unknown" })); if (resolved.status === "blocked" && resolved.reason === "NotFoundError") return emptyResult(contextId, key); if (resolved.status !== "verified") return { schema: OPFS_UI_STATE_SCHEMA, ...resolved, context_id: contextId, key }; @@ -92,7 +93,7 @@ export async function loadUiState({ contextId, key = "xyzac-trt", storage } = {} return { schema: OPFS_UI_STATE_SCHEMA, status: "verified", storage: "opfs", context_id: contextId, key, generation: selected.generation, state: selected.state, state_hash: selected.state_hash }; } -export async function saveUiState({ contextId, key = "xyzac-trt", state, storage } = {}) { +export async function saveUiState({ contextId, key = DEFAULT_MACHINE.id, state, storage } = {}) { if (!validObject(state) || !finiteTree(state)) return { schema: OPFS_UI_STATE_SCHEMA, status: "blocked", code: "opfs_ui_state_invalid", context_id: contextId, key }; const resolved = await handles(key, true, storage).catch((error) => ({ status: "blocked", code: "opfs_open_failed", reason: error?.name ?? "unknown" })); if (resolved.status !== "verified") return { schema: OPFS_UI_STATE_SCHEMA, ...resolved, context_id: contextId, key }; @@ -108,23 +109,23 @@ export async function saveUiState({ contextId, key = "xyzac-trt", state, storage return { schema: OPFS_UI_STATE_SCHEMA, status: "verified", storage: "opfs", context_id: contextId, key, generation, slot, state_hash: next.state_hash, bytes: written.bytes }; } -export async function clearUiState({ contextId, key = "xyzac-trt", storage } = {}) { +export async function clearUiState({ contextId, key = DEFAULT_MACHINE.id, storage } = {}) { const resolved = await handles(key, false, storage).catch((error) => ({ status: "blocked", code: "opfs_open_failed", reason: error?.name ?? "unknown" })); if (resolved.status !== "verified") return { schema: OPFS_UI_STATE_SCHEMA, ...resolved, context_id: contextId, key }; for (const name of [...SLOT_NAMES, MANIFEST_NAME]) { try { await resolved.project.removeEntry(name); } catch {} } return { schema: OPFS_UI_STATE_SCHEMA, status: "verified", storage: "opfs", context_id: contextId, key, cleared: true }; } -export async function savePowerOffCheckpoint({ contextId, checkpoint, key = "xyzac-trt", storage } = {}) { +export async function savePowerOffCheckpoint({ contextId, checkpoint, key = DEFAULT_MACHINE.id, storage } = {}) { return saveUiState({ contextId, key: `${key}-power-off`, state: { kind: "power_off_checkpoint", checkpoint }, storage }); } -export async function loadPowerOffCheckpoint({ contextId, key = "xyzac-trt", storage } = {}) { +export async function loadPowerOffCheckpoint({ contextId, key = DEFAULT_MACHINE.id, storage } = {}) { const result = await loadUiState({ contextId, key: `${key}-power-off`, storage }); if (result.status !== "verified" || result.state?.kind !== "power_off_checkpoint" || !result.state.checkpoint) return { ...result, checkpoint: null }; return { ...result, checkpoint: result.state.checkpoint }; } -export async function clearPowerOffCheckpoint({ contextId, key = "xyzac-trt", storage } = {}) { +export async function clearPowerOffCheckpoint({ contextId, key = DEFAULT_MACHINE.id, storage } = {}) { return clearUiState({ contextId, key: `${key}-power-off`, storage }); } diff --git a/web/worker/opfs-worker-bridge.mjs b/web/worker/opfs-worker-bridge.mjs index 35d0ec0..9c2b249 100644 --- a/web/worker/opfs-worker-bridge.mjs +++ b/web/worker/opfs-worker-bridge.mjs @@ -6,6 +6,7 @@ import { savePowerOffCheckpoint, saveUiState, } from "./opfs-ui-state.mjs"; +import { DEFAULT_MACHINE, machineDefinition } from "../workspace/machine-definitions.mjs"; const UI_COMMANDS = new Set(["UI_STATE_SAVE", "UI_STATE_LOAD", "UI_STATE_CLEAR"]); @@ -34,7 +35,7 @@ export async function handleWorkerMessage(session, input) { // UI cache is a Worker storage service, not a LinuxCNC command. It never // consumes the WASM protocol sequence or changes task/motion authority. const payload = object(input.payload); - const options = { contextId: input.context_id, key: payload.key ?? "xyzac-trt" }; + const options = { contextId: input.context_id, key: payload.key ?? session.machineDefinition?.id ?? DEFAULT_MACHINE.id }; const result = input.kind === "UI_STATE_SAVE" ? await saveUiState({ ...options, state: payload.state }) : input.kind === "UI_STATE_LOAD" @@ -44,18 +45,32 @@ export async function handleWorkerMessage(session, input) { } let request = input; - if (input?.kind === "LOAD_WORKSPACE" && !object(input.payload).recovery_checkpoint) { - const persisted = await loadPowerOffCheckpoint({ contextId: input.context_id, key: "xyzac-trt" }); - if (persisted.status === "verified" && persisted.checkpoint) request = { ...input, payload: { ...object(input.payload), recovery_checkpoint: persisted.checkpoint } }; + let recoveryNotice = null; + const configuration = object(input.payload).configuration; + const requestedMachine = machineDefinition(configuration?.entrypoint); + const key = requestedMachine?.id ?? session.machineDefinition?.id ?? DEFAULT_MACHINE.id; + // Automatic restoration belongs to a new Worker session. Explicit INI + // changes and machine switches must not be overwritten by saved state. + if (input?.kind === "LOAD_WORKSPACE" && !session.machineDefinition && (!configuration?.entrypoint || requestedMachine) && !object(input.payload).recovery_checkpoint) { + const persisted = await loadPowerOffCheckpoint({ contextId: input.context_id, key }); + if (persisted.status === "verified" && persisted.checkpoint) { + try { + if (configuration && session.core && typeof session.validateRecoveryGeometry === "function") session.validateRecoveryGeometry(persisted.checkpoint.state, configuration); + request = { ...input, payload: { ...object(input.payload), recovery_checkpoint: persisted.checkpoint } }; + } catch (error) { + recoveryNotice = { status: "ignored", code: String(error?.message ?? error), reason: "saved_checkpoint_incompatible_with_current_configuration" }; + } + } } const response = session.accept(request); const payload = object(response?.payload); + if (recoveryNotice) payload.recovery_persistence = recoveryNotice; if (input?.kind === "POWER" && response?.kind === "COMMAND_RESULT" && payload.status === "verified") { if (payload.active === false && payload.recovery_checkpoint) { - const persisted = await savePowerOffCheckpoint({ contextId: input.context_id, checkpoint: payload.recovery_checkpoint, key: "xyzac-trt" }); + const persisted = await savePowerOffCheckpoint({ contextId: input.context_id, checkpoint: payload.recovery_checkpoint, key }); payload.recovery_persistence = persisted; } else if (payload.active === true) { - payload.recovery_persistence = await clearPowerOffCheckpoint({ contextId: input.context_id, key: "xyzac-trt" }); + payload.recovery_persistence = await clearPowerOffCheckpoint({ contextId: input.context_id, key }); } } return response; diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index 6d33092..7ecbb5d 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -1,3 +1,4 @@ +import { exportNativeCheckpoint, restoreNativeCheckpoint, validateNativeCheckpoint } from "./native-checkpoint.mjs"; import { ReplayTimeline, fnv1a64 } from "./replay.mjs"; import { capabilities, validateLimit } from "./capabilities.mjs"; import { mapCoreError } from "./error-map.mjs"; @@ -10,7 +11,7 @@ import { PythonBridge } from "./python-bridge.mjs"; import { ProgramRunner } from "./program-runner.mjs"; import { SimTask } from "./sim-task.mjs"; import { SimScheduler, SCHEDULER_PHASES } from "./sim-scheduler.mjs"; -import { OperationLedger } from "./operation-ledger.mjs"; +import { OperationLedger, LEDGER_SCHEMA } from "./operation-ledger.mjs"; import { MotionState } from "./motion-state.mjs"; import { PlcRuntime } from "./plc-runtime.mjs"; import { HalRuntime } from "./hal-runtime.mjs"; @@ -31,8 +32,12 @@ import { WasmXyzacIo } from "./wasm-xyzac-io.mjs"; import { WasmXyzacDeviceIo } from "./wasm-xyzac-device-io.mjs"; import { WasmProgramRuntime } from "./wasm-program-runtime.mjs"; import { axisGate, xyzacGate, xyzacGateMatrix } from "../app/axis-contract.mjs"; -import { machineDefinition } from "../workspace/machine-definitions.mjs"; -import { readTrtGeometry, TRT_GEOMETRY_KEYS } from "../workspace/trt-geometry.mjs"; +import { machineDefinition, DEFAULT_MACHINE } from "../workspace/machine-definitions.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; +import { TRT_GEOMETRY_KEYS } from "../workspace/trt-geometry.mjs"; +import { readTrtConfiguration } from "../workspace/trt-configuration.mjs"; + +const TRT_EXECUTION_KEYS = Object.freeze(["planner", "xyzacProgramKinematicsMode", "pendingProgramControl", "pendingTaskSync", "pendingXyzacMotion", "xyzacStepTargetId", "pendingServoSync", "xyzacExecutionWaitNextMotion", "xyzacExecutionWaitSamples", "xyzacLastEnqueuedKinematicsMode", "xyzacDeferredPhysicalSwitchMode", "xyzacDeferredBlockedActiveId", "xyzacDeferredControlMotionType", "xyzacQueuedLookaheadTargetId", "xyzacAllowQueuedLookahead", "xyzacLastServoActiveId", "xyzacLastQueuedMotion", "xyzacDeferredPhysicalPose", "xyzacDeferredPhysicalPoseApplied", "xyzacAppendDeferredOnActive", "xyzacDeferredCompletionActiveId"]); const PROTOCOL_SCHEMA = 1; const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"; @@ -138,10 +143,11 @@ export class SimSession { this.xyzacDeviceIo = null; this.xyzacProgram = null; this.machineDefinition = null; + this.machineConfigurationHash = null; this.homingHistory = []; this.xyzacProgramKinematicsMode = XYZAC_MODES.identity; - this.xyzacRapidVelocity = 35; - this.xyzacMotionCycleTime = 0.01; + this.xyzacRapidVelocity = null; + this.xyzacMotionCycleTime = WEB_SETTINGS.runtime.io.cycle_time; this.xyzacTaskCycleTicks = 1; this.xyzacAxisVelocityLimits = {}; this.xyzacAxisAccelerationLimits = {}; @@ -300,7 +306,7 @@ export class SimSession { velocity: wasmMotion.status === "verified" ? wasmMotion.state.velocity : 0, velocity_mm_min: Number(wasmMotion.state?.velocity_mm_min ?? 0), feed_command_mm_min: Number(sourceProgram?.feed ?? 0), - feed_override: Number(wasmMotion.state?.feed_override ?? 1), + feed_override: Number(wasmMotion.state?.feed_override ?? this.wasmMotion.controls?.feed_override ?? 1), spindle_override: Number(ioState?.spindles?.[0]?.override ?? 1), spindle_speed_rpm: Number(spindle?.speed_rpm ?? 0), spindle_command_rpm: Number(spindle?.command_rpm ?? 0), @@ -310,6 +316,7 @@ export class SimSession { jog_scale: Number(wasmMotion.state?.jog_scale ?? 1), jog_velocity_linear: Number(wasmMotion.state?.jog_velocity_linear ?? 0), jog_velocity_angular: Number(wasmMotion.state?.jog_velocity_angular ?? 0), + max_feed_override: this.wasmMotion.maxFeedOverride ?? null, max_velocity: Number(wasmMotion.state?.velocity_limit ?? 0), homed: Boolean(home.state?.homed), homed_axes: Array.from({ length: 5 }, (_, index) => Boolean(Number(home.state?.homed_mask ?? 0) & (1 << index))), @@ -330,7 +337,7 @@ export class SimSession { if (this.xyzacProgram) return this.wasmMotionProjection(); if (this.motionAuthority !== "wasm") return this.motion.export(); const wasm = this.wasmMotion.read(); - return wasm.status === "verified" ? { ...this.motion.export(), axes: { ...wasm.state.position } } : this.motion.export(); + return wasm.status === "verified" ? { ...this.motion.export(), axes: { ...wasm.state.position }, queue_depth: wasm.state.queue_depth } : this.motion.export(); } wasmDeviceProjection({ includeBytes = false } = {}) { @@ -359,7 +366,7 @@ export class SimSession { if (requirement.status !== "verified") return requirement; if (!requirement.required) return { status: "verified", count: 0 }; const basePath = String(programPath ?? "").split("/").slice(0, -1).join("/"); - const coordinates = this.machineDefinition?.coordinates ?? "XYZAC"; + const coordinates = this.machineDefinition?.coordinates ?? DEFAULT_MACHINE.coordinates; const stem = coordinates.toLowerCase(); const rows = [[`remap_subs/${stem}_switchkins_sub.ngc`, `${stem}_switchkins_sub`], [`remap_subs/helix_${stem.slice(3)}.ngc`, `helix_${stem.slice(3)}`]]; for (const [suffix, name] of rows) { @@ -407,7 +414,7 @@ export class SimSession { ? { schema: "wasm_authority_boundary.v1", source: "linuxcnc-wasm-worker", state_hash: fnv1a64({ motion: wasmMotion, kinematics: this.xyzacKinematics?.snapshot() ?? null, task }) } : this.authoritative.export(); const axis = axisState({ task, program: program ?? {}, motion, devices, io: this.xyzacIo ? this.xyzacIo.export() : {}, device_io: deviceIo }); - const xyzac = this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true, active_type: this.xyzacKinematics.snapshot().active_type } : null; + const xyzac = this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? DEFAULT_MACHINE.coordinates, joint_count: this.xyzacKinematics.coordinates.length, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true, active_type: this.xyzacKinematics.snapshot().active_type } : null; const gateInput = { task, program: program ?? {}, motion, devices, io: this.xyzacIo ? this.xyzacIo.export() : {}, device_io: deviceIo, xyzac, taskfile: Boolean(this.xyzacProgram ? this.xyzacProgram.snapshot().status !== 0 : this.program.loaded), kinematicsIdentity: this.motionAuthority === "wasm" }; const gateActions = ["load", "run", "runLine", "step", "pause", "resume", "abort", "restart", "reload", "edit", "mdi", "jogStart", "jogStop", "home", "estop", "recover", "override", "overrideLimits", "reset", "spindle", "coolant", "touchOff", "toolTouchOff"]; const ui = axisSourceUi({ task_state: axis.source_task_state, interp_state: axis.source_interp_state, @@ -473,7 +480,7 @@ export class SimSession { // renderer can audit that its geometry matches the loaded LinuxCNC case. const modelResource = this.configurationState?.exact_resources?.find?.((row) => row.role === "vismach_source"); const machineDefinition = this.machineDefinition; - const modelSourcePath = this.machineDefinition?.source_path ?? "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py"; + const modelSourcePath = this.machineDefinition?.source_path ?? DEFAULT_MACHINE.source_path; const modelSourceText = this.workspaceFiles?.[modelSourcePath]; const machineModel = this.xyzacProgram ? { schema: "linuxcnc-vismach-model.v1", @@ -481,7 +488,7 @@ export class SimSession { source: "linuxcnc-wasm-worker", source_path: modelResource?.path ?? modelSourcePath, source_hash: modelResource?.data_hash ?? this.visualModelSourceHash ?? (typeof modelSourceText === "string" ? fnv1a64(modelSourceText) : null), - coordinate_system: machineDefinition?.coordinates ?? "XYZAC", + coordinate_system: machineDefinition?.coordinates ?? DEFAULT_MACHINE.coordinates, geometry_variant: machineDefinition?.coordinates === "XYZBC" ? "moving-head-y-tilt" : "moving-knee-x-tilt", tilt_axis: machineDefinition?.coordinates === "XYZBC" ? "Y" : "X", rotary_axis: "Z", @@ -507,7 +514,7 @@ export class SimSession { coordinate_offset: program?.coordinate_context?.offsets?.[(program?.coordinate_context?.system ?? 1) - 1]?.slice(0, 3) ?? null, ...(machineModel ? { machine_model: machineModel } : {}), }; - const coordinateAxes = machineDefinition?.axes ?? ["x", "y", "z", "a", "c"]; + const coordinateAxes = machineDefinition?.axes ?? DEFAULT_MACHINE.axes; const axisCoordinates = coordinateAxes.map((axis) => position[axis] ?? 0); const coordinateContext = program?.coordinate_context; const activeOffsets = coordinateContext?.offsets?.[coordinateContext.system - 1] ?? Array(9).fill(0); @@ -540,7 +547,7 @@ export class SimSession { authoritative, motion_authority: this.motionAuthority, task_authority: this.task.authority(), - ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot() } : {}), + ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot(), machine_configuration_hash: this.machineConfigurationHash } : {}), ...(includeStableRuntime && this.xyzacIo ? { io: this.xyzacIo.export() } : {}), ...(xyzac ? { xyzac: { ...xyzac, coordinates: machineDefinition?.coordinates ?? xyzac.coordinates } } : {}), }; @@ -571,7 +578,7 @@ export class SimSession { commandGate(input) { const task = this.task.export(); const program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : {}; - const motion = this.xyzacProgram ? this.wasmMotionProjection() : this.motion.export(); + const motion = this.motionProjection(); const devices = this.wasmDeviceProjection(); // The selected XYZAC case is governed by the native WASM task/IO/program // handlers. JS only receives the result; it must not pre-reject a command @@ -582,7 +589,7 @@ export class SimSession { const type = Number(input.payload?.type); const action = input.kind === "VISUAL_CLEAR" ? "vismachClear" : type === 0 ? "switchkinsIdentity" : type === 1 ? "switchkinsXyzac" : type === 2 ? "switchkinsUserk" : null; if (!action) return { action: null, enabled: false, reason: "xyzac_kinematics_type_invalid" }; - return xyzacGate(action, { task, program, motion, devices, xyzac: this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true } : null }); + return xyzacGate(action, { task, program, motion, devices, xyzac: this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? DEFAULT_MACHINE.coordinates, joint_count: this.xyzacKinematics.coordinates.length, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true } : null }); } const action = PANEL_GATE_ACTIONS[input.kind]; if (!action) return { action: null, enabled: true, reason: "axis_internal_command" }; @@ -688,7 +695,7 @@ export class SimSession { if (!this.xyzacProgram) return { status: "error", code: "xyzac_program_unavailable" }; const payload = input.payload ?? {}; const axis = String(payload.axis ?? "").trim().toUpperCase(); - const coordinateAxes = this.machineDefinition?.axes ?? ["x", "y", "z", "a", "c"]; + const coordinateAxes = this.machineDefinition?.axes ?? DEFAULT_MACHINE.axes; if (!coordinateAxes.includes(axis.toLowerCase())) return { status: "error", code: "touch_off_axis_invalid" }; const value = Number(payload.value); if (!Number.isFinite(value)) return { status: "error", code: "touch_off_value_invalid" }; @@ -732,6 +739,14 @@ export class SimSession { } } + checkpointLedger() { + // Durable receipts must not recursively contain older checkpoints or + // complete UI snapshots. Preserve intent/result identity for deduplication. + const omitted = new Set(["panel_snapshot", "configuration", "program", "motion", "wasm_motion", "wasm_home_limit", "device_io", "devices", "hal_runtime", "snapshot", "state", "checkpoint", "recovery_checkpoint", "preview", "operations", "ledger"]); + const entries = [...this.ledger.entries.values()].map((entry) => ({ ...entry, response: entry.response ? { ...entry.response, payload: Object.fromEntries(Object.entries(entry.response.payload ?? {}).filter(([key]) => !omitted.has(key))) } : null })); + return { schema: LEDGER_SCHEMA, entries: structuredClone(entries) }; + } + stateForCheckpoint() { const state = { context_id: this.contextId, @@ -742,7 +757,7 @@ export class SimSession { devices: this.wasmDeviceProjection({ includeBytes: true }), motion: this.xyzacProgram ? this.wasmMotionProjection() : this.motion.export(), plc: this.plc.export(), - hal_runtime: this.xyzacIo ? this.xyzacIo.halExport() : this.hal.export(), + hal_runtime: this.xyzacIo ? this.xyzacIo.halExport({ includeState: true }) : this.hal.export(), device_io: this.xyzacDeviceIo ? this.xyzacDeviceIo.export() : this.xyzacIo ? this.xyzacIo.export() : this.deviceIo.export(), configuration_batch: this.configurationBatch.export(), io_ack: this.ioAck.export(), @@ -754,15 +769,19 @@ export class SimSession { wasm_motion: this.wasmMotion.exportState(), task_authority: this.task.authority(), wasm_home_limit: this.wasmHomeLimit.exportState(), - ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot() } : {}), + ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot(), machine_configuration_hash: this.machineConfigurationHash } : {}), preview: this.previewState, live_path_epoch: this.livePathEpoch, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: this.xyzacProgram ? { schema: "wasm_authority_boundary.v1", source: "linuxcnc-wasm-worker" } : this.authoritative.export(), - ledger: this.ledger.export(), + ledger: this.xyzacProgram ? this.checkpointLedger() : this.ledger.export(), }; - if (this.xyzacProgram) state.program = { ...this.xyzacProgram.snapshot(), source_text: this.xyzacProgram.source }; + if (this.xyzacProgram) { + state.program = { ...this.xyzacProgram.checkpoint(), source_text: this.xyzacProgram.source }; + state.trt_execution = Object.fromEntries(TRT_EXECUTION_KEYS.map((key) => [key, structuredClone(this[key])])); + state.native_runtime = Object.fromEntries(["sim_core", "sim_protocol", "sim_event"].map((prefix) => [prefix, exportNativeCheckpoint(this.core, prefix)])); + } else if (this.program.loaded) state.program = this.program.exportState(); return state; } @@ -792,7 +811,7 @@ export class SimSession { task_authority: this.task.authority(), wasm_home_limit: this.wasmHomeLimit.exportState(), live_path_epoch: this.livePathEpoch, - ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot() } : {}), + ...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot(), machine_configuration_hash: this.machineConfigurationHash } : {}), }; if (this.xyzacProgram) value.program = { ...this.xyzacProgram.snapshot(), source_text: this.xyzacProgram.source }; else if (this.program.loaded) value.program = this.program.exportState(); @@ -824,6 +843,8 @@ export class SimSession { // recovery command, but are redundant for a power-off restart. Omitting // them keeps the browser durable record bounded during long JOG runs. delete state.ledger; + state.power_off_preview_hash = state.preview?.preview_hash ?? null; + delete state.preview; const compact = { schema: "xyzac-power-off-checkpoint.v1", context_id: this.contextId, @@ -834,10 +855,51 @@ export class SimSession { return { ...compact, checkpoint_hash: fnv1a64(compact) }; } - validateRecoveryGeometry(state) { - if (!this.xyzacKinematics) return; - const current = this.xyzacKinematics.snapshot(); + restoreTrtExecution(state, transport = true) { + if (!state.trt_execution || !state.native_runtime || !TRT_EXECUTION_KEYS.every((key) => Object.hasOwn(state.trt_execution, key))) throw new Error("trt_checkpoint_execution_required"); + for (const key of TRT_EXECUTION_KEYS) this[key] = structuredClone(state.trt_execution[key]); + restoreNativeCheckpoint(this.core, "sim_core", state.native_runtime.sim_core); + if (transport) for (const prefix of ["sim_protocol", "sim_event"]) restoreNativeCheckpoint(this.core, prefix, state.native_runtime[prefix]); + this.xyzacIo.restoreHalState(state.hal_runtime); + } + + validateRecoveryGeometry(state, configuration = null) { + if (!this.xyzacKinematics && !configuration) return; + const supplied = configuration ? readTrtConfiguration(configuration) : null; + const expectedHash = supplied ? fnv1a64({ kinematics: supplied.kinematics, home_limits: supplied.home_limits, motion_scale_min: WEB_SETTINGS.ui.motion_scale_min }) : this.machineConfigurationHash; + if (state?.machine_configuration_hash !== expectedHash) throw new Error("trt_recovery_configuration_mismatch"); + const current = supplied?.kinematics ?? this.xyzacKinematics.snapshot(); if (!state?.kinematics || state.kinematics.coordinates !== current.coordinates || TRT_GEOMETRY_KEYS.some((key) => Number(state.kinematics.offsets?.[key] ?? 0) !== current.offsets[key])) throw new Error("trt_recovery_geometry_mismatch"); + if (!state.trt_execution || !TRT_EXECUTION_KEYS.every((key) => Object.hasOwn(state.trt_execution, key))) throw new Error("trt_checkpoint_execution_required"); + for (const [prefix, value] of [["sim_program", state.program?.native_state], ["sim_linuxcnc_tp", state.wasm_motion?.native_state], ["sim_hal", state.hal_runtime?.native_state], ...["sim_core", "sim_protocol", "sim_event"].map((key) => [key, state.native_runtime?.[key]])]) validateNativeCheckpoint(this.core, prefix, value); + } + + restoreTrtProgram(state) { + if (typeof state.program?.source_text !== "string") throw new Error("trt_checkpoint_program_missing"); + const previous = this.xyzacProgram; + const candidate = new WasmProgramRuntime(this.core); + try { + if (state.program.source_text) { + const loaded = candidate.load(state.program.source_text, state.program.path ?? null); + if (loaded.status !== "verified") throw new Error(loaded.code); + if (Array.isArray(state.program.subroutine_sources)) { + for (const sub of state.program.subroutine_sources) { + const added = candidate.addSubroutine(sub.name, sub.source, sub.path); + if (added.status !== "verified") throw new Error(added.code); + } + } else { + this.xyzacProgram = candidate; + const subroutines = this.loadXyzacSubroutines(state.program.path ?? null); + if (subroutines.status !== "verified") throw new Error(subroutines.code); + } + } + const result = candidate.restoreState(state.program); + if (result.status === "error") throw new Error(result.code ?? "trt_checkpoint_program_restore_failed"); + this.xyzacProgram = candidate; + previous.destroy(); + } catch (error) { + this.xyzacProgram = previous; candidate.destroy(); throw error; + } } restoreCompactPowerOff(record, transportSeq) { @@ -850,17 +912,13 @@ export class SimSession { const liveOperations = [...this.operations.values()]; try { this.validateRecoveryGeometry(state); - if (!this.xyzacProgram || !state.program?.source_text) throw new Error("power_off_program_missing"); - const loaded = this.xyzacProgram.load(state.program.source_text, state.program.path ?? null); - if (loaded.status !== "verified") throw new Error(loaded.code); - const subroutines = this.loadXyzacSubroutines(state.program.path ?? null); - if (subroutines.status !== "verified") throw new Error(subroutines.code); - if (this.xyzacProgram.restoreState(state.program).status === "error") throw new Error("power_off_program_restore_failed"); + this.restoreTrtProgram(state); if (state.wasm_motion) this.wasmMotion.restoreState(state.wasm_motion); if (state.wasm_home_limit) this.wasmHomeLimit.restoreState(state.wasm_home_limit); if (state.device_io && this.xyzacDeviceIo) this.xyzacDeviceIo.restoreState(state.device_io); if (state.devices && this.xyzacIo) this.xyzacIo.restoreState(state.devices); if (state.kinematics && this.xyzacKinematics) this.xyzacKinematics.restoreState(state.kinematics); + this.restoreTrtExecution(state, false); if (state.task) { const task = { ...state.task, session_id: this.sessionId, seq: transportSeq, simulation_tick: record.simulation_tick }; delete task.state_hash; @@ -875,7 +933,12 @@ export class SimSession { this.callbacks = new PythonCallbacks(); if (state.callbacks) this.callbacks.restore(state.callbacks); this.configurationState = state.configuration ?? this.configurationState; this.halState = state.hal ?? this.halState; - this.previewState = state.preview ?? this.previewState; + if (state.preview) this.previewState = state.preview; + else if (state.program.source_text) { + const preview = this.xyzacProgram.preview(); + if (preview.status !== "verified" || state.power_off_preview_hash && state.power_off_preview_hash !== preview.preview_hash) throw new Error("power_off_preview_restore_mismatch"); + this.previewState = preview; + } else this.previewState = null; this.livePathEpoch = Number.isSafeInteger(state.live_path_epoch) ? state.live_path_epoch : this.livePathEpoch; this.python = new PythonBridge(); if (state.python) this.python.restoreState(state.python); const liveEvents = liveTimeline.events.events; @@ -897,6 +960,7 @@ export class SimSession { restorePersistedPowerOff(checkpoint, transportSeq) { if (!checkpoint || !Number.isSafeInteger(transportSeq) || transportSeq < 1) return { status: "blocked", code: "runtime_checkpoint_input_invalid", reason: "checkpoint_or_sequence_invalid" }; + if (Number.isSafeInteger(checkpoint.simulation_tick) && checkpoint.simulation_tick < this.timeline.events.tick) return { status: "blocked", code: "runtime_checkpoint_requires_new_session", reason: "recovery_must_not_rewrite_transport_history" }; if (checkpoint.schema === "xyzac-power-off-checkpoint.v1") return this.restoreCompactPowerOff(checkpoint, transportSeq); const liveTimeline = this.timeline.export(); const liveLedger = this.ledger; @@ -914,7 +978,7 @@ export class SimSession { const unsigned = Object.fromEntries(Object.entries(checkpointForRestore).filter(([key]) => key !== "checkpoint_hash")); checkpointForRestore.checkpoint_hash = fnv1a64(unsigned); } - const restored = this.restoreCheckpoint(checkpointForRestore); + const restored = this.restoreCheckpoint(checkpointForRestore, null, undefined, false); if (restored.status !== "recovered") return restored; const restoredTick = this.tick; const restoredTask = this.task.export(); @@ -953,6 +1017,12 @@ export class SimSession { importTrace(trace) { const validated = validateTrace(trace, { contextId: this.contextId }); this.validateRecoveryGeometry(trace.state); + if (this.xyzacProgram && trace.state?.program?.native_state) { + const checkpoint = createCheckpoint({ contextId: this.contextId, timeline: validated.timeline, state: trace.state, operations: trace.state.ledger?.entries ?? [] }); + const result = this.restoreCheckpoint(checkpoint); + if (result.status !== "recovered") throw new Error(result.blocked?.[0]?.reason ?? "trt_trace_restore_failed"); + return { status: "verified", trace_hash: trace.trace_hash, state_hash: fnv1a64(trace.state), seq: trace.seq, tick: trace.tick }; + } this.timeline = validated.timeline; this.created = Boolean(trace.state?.created); if (trace.state?.ledger?.entries) this.ledger = new OperationLedger(trace.state.ledger.entries); @@ -986,27 +1056,22 @@ export class SimSession { return { status: "verified", trace_hash: trace.trace_hash, state_hash: fnv1a64(trace.state), seq: trace.seq, tick: trace.tick }; } - restoreCheckpoint(checkpoint, events = null, reconcile = undefined) { + restoreCheckpoint(checkpoint, events = null, reconcile = undefined, transport = true) { try { validateCheckpoint(checkpoint, { contextId: this.contextId }); this.validateRecoveryGeometry(checkpoint.state); } catch (error) { return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, checkpoint_hash: checkpoint?.checkpoint_hash ?? null, blocked: [{ reason: String(error?.message ?? error) }] }; } + const tailEvents = Array.isArray(events) ? events : events?.events?.events ?? events?.events ?? []; + if (this.xyzacProgram && Array.isArray(tailEvents) && tailEvents.some((event) => event.seq > checkpoint.event_cursor)) return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, blocked: [{ reason: "trt_checkpoint_tail_requires_explicit_commands" }] }; const replayMotionAuthority = checkpoint?.state?.motion_authority ?? this.motionAuthority; if (replayMotionAuthority === "wasm" && !checkpoint?.state?.wasm_motion) { return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, checkpoint_hash: checkpoint?.checkpoint_hash ?? null, blocked: [{ reason: "wasm_motion_checkpoint_missing" }] }; } - if (this.xyzacProgram && checkpoint?.state?.program?.source_text) { + if (this.xyzacProgram && checkpoint?.state?.program) { try { - const loaded = this.xyzacProgram.load(checkpoint.state.program.source_text, checkpoint.state.program.path ?? null); - if (loaded.status !== "verified") throw new Error(loaded.code); - const subroutines = this.loadXyzacSubroutines(checkpoint.state.program.path ?? null); - if (subroutines.status !== "verified") throw new Error(subroutines.code); - const targetLine = Number.isSafeInteger(checkpoint.state.program.current_line) ? checkpoint.state.program.current_line : 0; - const seek = this.xyzacProgram.seek(targetLine); - if (seek.status !== "verified") throw new Error(seek.code); - if (checkpoint.state.program) this.xyzacProgram.restoreState(checkpoint.state.program); + this.restoreTrtProgram(checkpoint.state); if (checkpoint.state.wasm_motion) this.wasmMotion.restoreState(checkpoint.state.wasm_motion); if (checkpoint.state.wasm_home_limit) this.wasmHomeLimit.restoreState(checkpoint.state.wasm_home_limit); let restoredWasmDeviceIo = false; @@ -1028,11 +1093,11 @@ export class SimSession { this.livePathEpoch = Number.isSafeInteger(checkpoint.state.live_path_epoch) ? checkpoint.state.live_path_epoch : this.livePathEpoch; this.python = new PythonBridge(); if (checkpoint.state.python) this.python.restoreState(checkpoint.state.python); this.timeline = new ReplayTimeline(checkpoint.timeline); - if (checkpoint.state.hal_runtime?.schema === "wasm_xyzac_hal.v1" && this.xyzacIo) this.xyzacIo.halCommand({ action: "step", ticks: checkpoint.state.hal_runtime.tick ?? 0 }); + this.restoreTrtExecution(checkpoint.state, transport); if (!restoredWasmDeviceIo && checkpoint.state.device_io?.schema === "wasm_xyzac_device_io.v1" && this.xyzacDeviceIo) this.xyzacDeviceIo.command("IO_STEP", { ticks: checkpoint.state.device_io.state?.tick ?? 0 }); const currentOperations = [...this.operations.values()]; this.operations = new Map([...checkpoint.operations ?? [], ...currentOperations].map((operation) => [operation.operation_id, operation])); - const checkpointEntries = checkpoint.operations ?? []; + const checkpointEntries = checkpoint.state.ledger?.entries ?? checkpoint.operations ?? []; const currentEntries = this.ledger.export().entries.filter((entry) => !checkpointEntries.some((operation) => operation.operation_id === entry.operation_id)); this.ledger = new this.ledger.constructor(); for (const operation of [...checkpointEntries, ...currentEntries]) { @@ -1317,6 +1382,7 @@ export class SimSession { let ledgerPreparation; try { ledgerPreparation = this.ledger.prepare(input); + if (ledgerPreparation.replay && !ledgerPreparation.entry?.response) throw new Error("ledger_replay_result_missing"); if (ledgerPreparation.replay && ledgerPreparation.entry?.response) { const replayTick = Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick ? input.simulation_tick : this.tick; this.timeline.appendEvent(input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: ledgerPreparation.entry.seq }); @@ -1324,7 +1390,9 @@ export class SimSession { if (replayEventResult !== 0) return this.error(input, "event_log", "core_event_append_failed"); this.tick = replayTick; try { this.task.setCommand(input.seq, replayTick); } catch (error) { return this.error(input, "task_state", String(error?.message ?? error)); } - return { ...ledgerPreparation.entry.response, replayed: true, seq: input.seq, ledger: this.ledger.reconcile(input.operation_id) }; + const cached = ledgerPreparation.entry.response; + const payload = this.xyzacProgram ? { ...cached.payload, task: this.task.export(), program: this.xyzacProgram.snapshot(), motion: this.wasmMotionProjection(), kinematics: this.xyzacKinematics.snapshot() } : cached.payload; + return { ...this.envelope(cached.kind, input, payload), replayed: true, ledger: this.ledger.reconcile(input.operation_id) }; } if (ledgerPreparation.entry) this.ledger.execute(input.operation_id); } catch (error) { @@ -1610,8 +1678,7 @@ export class SimSession { let configuration = input.payload?.configuration ?? null; if (machineDefinition(configuration?.entrypoint) || machineDefinition(configuration?.kinematics?.type)) { try { - const geometry = readTrtGeometry(configuration); - configuration = { ...configuration, kinematics: { ...configuration.kinematics, offsets: geometry.offsets } }; + configuration = { ...configuration, ...readTrtConfiguration(configuration) }; } catch (error) { return finish(this.error(input, "trt_geometry", error.message), "failed"); } @@ -1619,7 +1686,7 @@ export class SimSession { if (configuration) { this.machineDefinition = machineDefinition(configuration.entrypoint) ?? machineDefinition(configuration.kinematics?.type) ?? this.machineDefinition; this.workspaceFiles = { ...(configuration.files ?? configuration.workspace ?? configuration.workspace_files ?? {}) }; - const visualSourcePath = (this.machineDefinition ?? machineDefinition(configuration.entrypoint))?.source_path ?? "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py"; + const visualSourcePath = (this.machineDefinition ?? machineDefinition(configuration.entrypoint))?.source_path ?? DEFAULT_MACHINE.source_path; this.visualModelSourceHash = typeof this.workspaceFiles[visualSourcePath] === "string" ? fnv1a64(this.workspaceFiles[visualSourcePath]) : null; const startupWorkspace = configuration.workspace ?? configuration.files ?? configuration.workspace_files; if (startupWorkspace) { @@ -1672,22 +1739,23 @@ export class SimSession { this.xyzacKinematics?.destroy(); this.xyzacKinematics = new WasmXyzacKinematics(this.core, { ...configuration.kinematics.offsets, coordinates: configuredMachine?.coordinates ?? configuration.kinematics.coordinates ?? "XYZAC", limits: configuration.home_limits }); this.xyzacIo?.destroy(); - this.xyzacIo = new WasmXyzacIo(this.core, { spindleCount: 2, toolCapacity: 1001 }); + this.xyzacIo = new WasmXyzacIo(this.core, { spindleCount: configuration.kinematics.io.spindle_count, toolCapacity: configuration.kinematics.io.tool_capacity, cycleTime: configuration.kinematics.io.cycle_time, spindleAcceleration: configuration.kinematics.io.spindle_acceleration, spindleTolerance: configuration.kinematics.io.spindle_tolerance }); const initialPower = this.xyzacIo.command("POWER", { active: false }); if (initialPower.status !== "verified") throw new Error(initialPower.code ?? "wasm_power_off_init"); this.xyzacDeviceIo?.destroy(); - this.xyzacDeviceIo = new WasmXyzacDeviceIo(this.core, { encoderCount: 2 }); + this.xyzacDeviceIo = new WasmXyzacDeviceIo(this.core, { encoderCount: configuration.kinematics.io.encoder_count, cycleTime: configuration.kinematics.io.cycle_time }); this.xyzacProgram?.destroy(); this.wasmMotion.destroy(); - this.xyzacMotionCycleTime = Number(configuration.kinematics?.servo_cycle_time ?? configuration.kinematics?.cycle_time ?? 0.01); + this.xyzacMotionCycleTime = configuration.kinematics.servo_cycle_time; if (!Number.isFinite(this.xyzacMotionCycleTime) || this.xyzacMotionCycleTime <= 0) throw new Error("wasm_xyzac_cycle_time_invalid"); - const taskCycleTime = Number(configuration.kinematics?.task_cycle_time ?? this.xyzacMotionCycleTime); - this.xyzacTaskCycleTicks = Math.max(1, Math.round(taskCycleTime / this.xyzacMotionCycleTime)); + const taskCycleTime = configuration.kinematics.task_cycle_time; + this.xyzacTaskCycleTicks = Math.round(taskCycleTime / this.xyzacMotionCycleTime); + this.xyzacRapidVelocity = configuration.kinematics.max_linear_velocity; // The selected XYZAC TRT case always uses the upstream LinuxCNC TP; // a caller must not silently downgrade it to the compatibility // motion queue by omitting the planner option. this.wasmMotion = isTrt - ? new WasmLinuxcncTp(this.core, { cycleTime: this.xyzacMotionCycleTime, velocityLimit: this.xyzacRapidVelocity, accelerationLimit: Number(configuration.kinematics?.max_acceleration ?? 300), limits: configuration.home_limits ?? {} }) + ? new WasmLinuxcncTp(this.core, { cycleTime: this.xyzacMotionCycleTime, velocityLimit: this.xyzacRapidVelocity, accelerationLimit: configuration.kinematics.max_acceleration, limits: configuration.kinematics.axis_limits, axisVelocities: configuration.kinematics.axis_velocity_limits, axisAccelerations: configuration.kinematics.axis_acceleration_limits, maxFeedOverride: configuration.kinematics.max_feed_override, blend: configuration.kinematics.blend }) : new WasmMotionQueue(this.core, { cycleTime: this.xyzacMotionCycleTime, velocityLimit: 1e9 }); this.xyzacProgram = new WasmProgramRuntime(this.core); this.previewState = null; @@ -1696,14 +1764,8 @@ export class SimSession { this.homingHistory = []; this.xyzacProgramKinematicsMode = XYZAC_MODES.identity; if (typeof this.wasmMotion.setKinematics === "function") this.wasmMotion.setKinematics(this.xyzacKinematics); - this.xyzacRapidVelocity = Number(configuration.kinematics?.max_linear_velocity ?? 35); - const linearLimit = Number(configuration.kinematics?.axis_linear_velocity_limit ?? 20); - const angularLimit = Number(configuration.kinematics?.axis_angular_velocity_limit ?? 30); - const rotaryAxis = (configuredMachine?.coordinates ?? "XYZAC")[3].toLowerCase(); - this.xyzacAxisVelocityLimits = { x: linearLimit, y: linearLimit, z: linearLimit, [rotaryAxis]: angularLimit, c: angularLimit }; - const accelerationLimit = Number(configuration.kinematics?.max_acceleration ?? 300); - this.xyzacAxisAccelerationLimits = { x: accelerationLimit, y: accelerationLimit, z: accelerationLimit, [rotaryAxis]: accelerationLimit, c: accelerationLimit }; - this.wasmMotion.setDynamics(Number(configuration.kinematics?.max_acceleration ?? 300), Number(configuration.kinematics?.max_jerk ?? 1000000000)); + this.xyzacAxisVelocityLimits = configuration.kinematics.axis_velocity_limits; + this.xyzacAxisAccelerationLimits = configuration.kinematics.axis_acceleration_limits; this.xyzacProgram.setRapidVelocity(this.xyzacRapidVelocity); } if (configuration?.home_limits && this.motionAuthority === "wasm") { @@ -1734,6 +1796,7 @@ export class SimSession { }; } const configuredKinematics = input.payload?.kinematics ?? configuration?.kinematics?.type ?? null; + if (isTrt) this.machineConfigurationHash = fnv1a64({ kinematics: configuration.kinematics, home_limits: configuration.home_limits, motion_scale_min: WEB_SETTINGS.ui.motion_scale_min }); if (configuredKinematics && !(this.motionAuthority === "wasm" && isTrt)) { const kinematics = this.authoritative.setKinematics(configuredKinematics); if (kinematics.status === "blocked") return finish(this.envelope("ERROR", input, { status: "blocked", code: kinematics.code, kinematics: this.authoritative.export() }), "failed"); @@ -1757,7 +1820,7 @@ export class SimSession { motion_authority: this.motionAuthority, state_hash: this.task.hash(), task: this.task.export(), - ...(powerOffRecovery ? { power_off_recovery: powerOffRecovery } : {}), + ...(powerOffRecovery ? { power_off_recovery: { ...powerOffRecovery, program: { source: this.xyzacProgram.source, path: this.xyzacProgram.path } } } : {}), ...(this.configurationState?.schema === "configuration-startup-runtime.v1" ? { configuration: this.configurationState } : {}), })) : finish(this.coreError(input, loadResult, "sim_load_workspace"), "failed"); } @@ -2081,7 +2144,7 @@ export class SimSession { motion_trace.push({ tick: nextTick, pose: { ...traceState.position }, - joints: traceKinematics?.joints?.length === 5 ? [...traceKinematics.joints] : [], + joints: Array.isArray(traceState.joint_commanded) ? [...traceState.joint_commanded] : traceKinematics?.joints?.length === 5 ? [...traceKinematics.joints] : [], active_segment_id: traceState.active_id, active_progress: traceState.progress, }); @@ -2618,7 +2681,7 @@ export class SimSession { } if (input.kind === "SET_OVERRIDE") { const feed = Number(input.payload?.feed ?? input.payload?.feed_override); - if (!Number.isFinite(feed) || feed < 0 || feed > 2) return finish(this.error(input, "motion_override", "motion_override_range"), "failed"); + if (!Number.isFinite(feed) || feed < 0 || feed > (this.wasmMotion.maxFeedOverride ?? WEB_SETTINGS.runtime.planner.max_feed_override)) return finish(this.error(input, "motion_override", "motion_override_range"), "failed"); try { this.wasmMotion.setFeedOverride(feed); } catch (error) { @@ -2629,7 +2692,7 @@ export class SimSession { } if (input.kind === "SET_JOG_SCALE" || input.kind === "SET_MAX_VELOCITY") { const scale = Number(input.payload?.scale ?? Number(input.payload?.percent) / 100); - if (!Number.isFinite(scale) || scale < 0.06 || scale > 1) return finish(this.error(input, "motion_scale", input.kind === "SET_JOG_SCALE" ? "jog_scale_range" : "max_velocity_scale_range"), "failed"); + if (!Number.isFinite(scale) || scale < WEB_SETTINGS.ui.motion_scale_min || scale > 1) return finish(this.error(input, "motion_scale", input.kind === "SET_JOG_SCALE" ? "jog_scale_range" : "max_velocity_scale_range"), "failed"); try { if (input.kind === "SET_JOG_SCALE") this.wasmMotion.setJogScale(scale); else this.wasmMotion.setVelocityScale(scale); @@ -2641,7 +2704,7 @@ export class SimSession { } if (input.kind === "FOLLOWING_ERROR") return finish(this.error(input, "motion_following_error", "wasm_following_error_unavailable"), "failed"); const axis = String(input.payload?.axis ?? "").toLowerCase(); - const availableAxes = this.machineDefinition?.axes ?? ["x", "y", "z", "a", "c"]; + const availableAxes = this.machineDefinition?.axes ?? DEFAULT_MACHINE.axes; if (!availableAxes.includes(axis)) return finish(this.error(input, "motion_axis", "motion_axis_invalid"), "failed"); const direction = input.payload?.direction === -1 ? -1 : input.payload?.direction === 1 ? 1 : null; if (direction === null) return finish(this.error(input, "motion_jog", "motion_jog_range"), "failed"); @@ -2790,7 +2853,13 @@ export class SimSession { } } case "RECOVER": { - const result = this.restoreCheckpoint(input.payload?.checkpoint, input.payload?.events, input.payload?.reconcile); + const recoveryEvents = input.payload?.events; + const tailEvents = Array.isArray(recoveryEvents) ? recoveryEvents : recoveryEvents?.events?.events ?? recoveryEvents?.events ?? []; + if (this.xyzacProgram && Array.isArray(tailEvents) && tailEvents.some((event) => event.seq > (input.payload?.checkpoint?.event_cursor ?? 0))) return finish(this.envelope("RECOVERY_STATUS", input, { status: "blocked", code: "trt_checkpoint_tail_requires_explicit_commands" }), "failed"); + let result = this.xyzacProgram + ? this.restorePersistedPowerOff(input.payload?.checkpoint, input.seq) + : this.restoreCheckpoint(input.payload?.checkpoint, input.payload?.events, input.payload?.reconcile); + if (this.xyzacProgram && result.status === "verified") result = { ...result, schema: "simulation-recovery.v1", status: "recovered", storage: "checkpoint", state: this.stateForCheckpoint() }; return finish(this.envelope("RECOVERY_STATUS", input, result), result.status === "blocked" ? "failed" : "verified"); } case "CANCEL": { diff --git a/web/worker/test_axis3_selected_case.mjs b/web/worker/test_axis3_selected_case.mjs index 3afd619..6f81ecb 100644 --- a/web/worker/test_axis3_selected_case.mjs +++ b/web/worker/test_axis3_selected_case.mjs @@ -39,9 +39,10 @@ const wasm = await createModule({ wasmBinary: await readFile(new URL("../../buil const session = new SimSession(wasm, { sessionId: "axis3-selected-case" }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId }; let sequence = 0; -const send = (kind, operationId, payload = {}) => { +const send = (kind, operationId, payload = {}, expected = "ok") => { const response = session.accept({ ...base, kind, seq: ++sequence, operation_id: operationId, payload }); - assert.notEqual(response.kind, "ERROR", `${kind}:${response.payload?.code ?? "error"}`); + if (expected === "error") assert.equal(response.kind, "ERROR"); + else assert.notEqual(response.kind, "ERROR", `${kind}:${response.payload?.code ?? "error"}`); return response; }; @@ -67,6 +68,12 @@ const checkpoint = send("CHECKPOINT", "checkpoint").payload.checkpoint; send("SIM_STEP", "program-step", { cycles: 8 }); const programSnapshot = send("SNAPSHOT_REQUEST", "program-snapshot"); assert.ok(["running", "completed"].includes(programSnapshot.payload.snapshot?.state?.program?.program_status ?? "running")); +assert.equal(send("SPINDLE_COMMAND", "spindle-during-motion", { spindle: 0, rpm: 600, enabled: true }, "error").payload.code, "axis_gate"); +assert.equal(send("TOOL_PREPARE", "tool-during-motion", { tool: 1, pocket: 1 }, "error").payload.code, "axis_gate"); +for (let tick = 0; tick < 200 && (session.program.snapshot().program_status !== "completed" || session.wasmMotion.read().state.queue_depth > 0); tick++) send("SIM_STEP", `finish-program-${tick}`, { cycles: 100 }); +assert.equal(session.program.snapshot().program_status, "completed"); +assert.equal(session.wasmMotion.read().state.queue_depth, 0); +send("SET_MODE", "manual-after-program", { mode: "MANUAL" }); assert.equal(send("SPINDLE_COMMAND", "spindle-on", { spindle: 0, rpm: 600, enabled: true }).payload.status, "verified"); assert.equal(send("IO_STEP", "spindle-step", { ticks: 5 }).payload.status, "verified"); diff --git a/web/worker/test_opfs_worker_bridge.mjs b/web/worker/test_opfs_worker_bridge.mjs index e1a7f93..4182d1c 100644 --- a/web/worker/test_opfs_worker_bridge.mjs +++ b/web/worker/test_opfs_worker_bridge.mjs @@ -46,6 +46,20 @@ await handleWorkerMessage(session, { ...base, kind: "LOAD_WORKSPACE", seq: 2, op assert.deepEqual(lastAccepted.payload.recovery_checkpoint, checkpoint); assert.equal(accepted, 2); assert.equal((await loadUiState({ contextId, key: "xyzac-trt-power-off" })).status, "verified"); +session.machineDefinition = { id: "xyzbc-trt" }; +const bcCheckpoint = { ...checkpoint, state_hash: "bc-test" }; +await handleWorkerMessage(session, { ...base, kind: "POWER", seq: 3, operation_id: "bc-off", payload: { active: false, checkpoint: bcCheckpoint } }); +assert.equal((await loadUiState({ contextId, key: "xyzbc-trt-power-off" })).state.checkpoint.state_hash, "bc-test"); +assert.equal((await loadUiState({ contextId, key: "xyzac-trt-power-off" })).state.checkpoint.state_hash, "test"); +await handleWorkerMessage(session, { ...base, kind: "LOAD_WORKSPACE", seq: 4, operation_id: "reload", payload: { root_path: "/workspace" } }); +assert.equal(lastAccepted.payload.recovery_checkpoint, undefined, "active-session configuration reload must not inject old cached state"); +delete session.machineDefinition; +session.core = {}; +session.validateRecoveryGeometry = () => { throw new Error("sim_program_checkpoint_build_mismatch"); }; +const incompatible = await handleWorkerMessage(session, { ...base, kind: "LOAD_WORKSPACE", seq: 5, operation_id: "incompatible", payload: { root_path: "/workspace", configuration: { entrypoint: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini" } } }); +assert.equal(lastAccepted.payload.recovery_checkpoint, undefined); +assert.equal(incompatible.payload.recovery_persistence.status, "ignored"); +assert.equal(incompatible.payload.recovery_persistence.code, "sim_program_checkpoint_build_mismatch"); if (previousNavigator === undefined) delete globalThis.navigator; else Object.defineProperty(globalThis, "navigator", { configurable: true, value: previousNavigator }); diff --git a/web/worker/test_trt_checkpoint_exact.mjs b/web/worker/test_trt_checkpoint_exact.mjs new file mode 100644 index 0000000..bc88939 --- /dev/null +++ b/web/worker/test_trt_checkpoint_exact.mjs @@ -0,0 +1,142 @@ +import assert from "node:assert/strict"; +import { readFile, mkdir, writeFile } from "node:fs/promises"; +import createModule from "../../build-wasm/linuxcnc-sim-core.js"; +import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; +import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs"; +import { MACHINE_DEFINITIONS } from "../workspace/machine-definitions.mjs"; +import { fnv1a64 } from "./replay.mjs"; + +const root = new URL("../../", import.meta.url); +const wasmBinary = await readFile(new URL("build-wasm/linuxcnc-sim-core.wasm", root)); +const fetchImpl = async (url) => { + try { const text = await readFile(new URL(String(url).replace(/^\//, ""), root), "utf8"); return { ok: true, text: async () => text }; } + catch { return { ok: false }; } +}; +async function open(machine) { + const bundle = await loadSimulationCase({ path: machine.entrypoint, fetchImpl }); + const subroutine = machine.subroutines[0]; + const subPath = machine.entrypoint.replace(/[^/]+$/, `remap_subs/${subroutine}.ngc`); + bundle.files[subPath] = `O<${subroutine}> sub\n#31=[#31+#1]\nG1 X#31 Y4 Z2 ${machine.coordinates[3]}8 C12 F500\nO<${subroutine}> endsub\n`; + const core = await createModule({ wasmBinary }); + // Give each module a different heap layout; native restore must rebind pointers. + core._malloc(4096 + rows.length * 128); + const session = new SimSession(core, { sessionId: `exact-${machine.id}`, planner: "linuxcnc-tp" }); + let seq = 0; + const send = (kind, payload = {}, operationId = null) => { + const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: operationId ?? `exact-${seq}`, payload }); + assert.notEqual(result.kind, "ERROR", `${kind}: ${result.payload?.message_key ?? result.payload?.code ?? JSON.stringify({ preview: result.payload?.preview?.code, line: result.payload?.preview?.line, program_error: result.payload?.program?.last_error })}`); + return result; + }; + send("HELLO"); send("SESSION_ATTACH"); send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); + send("POWER", { active: true }); send("HOME", { all: true }); + for (let i = 0; i < 500 && !session.wasmHomeLimit.read().state.homed; i++) send("SIM_STEP", { cycles: 1 }); + assert.ok(session.wasmHomeLimit.read().state.homed); + return { session, core, send, adoptSequence: (value) => { seq = value; } }; +} +const rows = []; +for (const machine of MACHINE_DEFINITIONS) { + const original = await open(machine); + const idle = original.send("CHECKPOINT").payload.checkpoint; + const idleRestored = await open(machine); + assert.equal(idleRestored.session.restoreCheckpoint(idle).status, "recovered"); + assert.equal(fnv1a64(idleRestored.session.workspacePackageState()), fnv1a64(original.session.workspacePackageState()), "idle machine without a loaded program"); + const rotary = machine.coordinates[3]; + const source = `G21 G90 G64\nM428\n#31=3\nG1 X6 Y0 Z2 ${rotary}5 C8 F600\nG3 X0 Y6 I-6 J0 ${rotary}10 C16\nO<${machine.subroutines[0]}> call [3]\nM429\nG1 X2 Y3 Z4 F600\nM2\n`; + original.send("PROGRAM_LOAD", { source, path: machine.program.replace(/[^/]+$/, "checkpoint-exact.ngc") }); original.send("PROGRAM_RUN"); + const seen = new Set(); let samples = 0; + for (let index = 0; index < 500; index++) { + original.send("SIM_STEP", { cycles: 5, response: "motion_delta" }); + const current = original.session.wasmMotion.read().state; + const program = original.session.xyzacProgram.snapshot(); + const phase = program.call_depth > 0 ? "subroutine" : current.motion_type === 3 ? "arc" : current.queue_depth > 0 ? "line" : program.program_status === "completed" ? "completed" : "barrier"; + if (!seen.has(phase)) { + seen.add(phase); + const checkpoint = original.send("CHECKPOINT").payload.checkpoint; + assert.ok(checkpoint?.state.program.native_state); + assert.ok(!JSON.stringify(checkpoint.state.ledger).includes('"checkpoint":'), "durable ledger must not recursively contain earlier checkpoints"); + const restarted = await open(machine); + restarted.session.workspaceFiles[machine.entrypoint.replace(/[^/]+$/, `remap_subs/${machine.subroutines[0]}.ngc`)] = "unrelated new subroutine contents"; + const result = restarted.session.restoreCheckpoint(checkpoint); + assert.equal(result.status, "recovered", JSON.stringify(result.blocked)); + restarted.adoptSequence(checkpoint.event_cursor); + assert.equal(fnv1a64(restarted.session.workspacePackageState()), fnv1a64(original.session.workspacePackageState()), `${machine.id} ${phase}: initial state`); + for (let step = 0; step < 12; step++) { + original.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); restarted.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); + const expected = original.session.workspacePackageState(), actual = restarted.session.workspacePackageState(); + assert.equal(fnv1a64(actual), fnv1a64(expected), `${machine.id} ${phase}: divergent tick ${original.session.tick}`); + samples++; + } + rows.push({ machine: machine.id, phase, checkpoint_tick: checkpoint.simulation_tick, continuation_tick: original.session.tick, state_hash: fnv1a64(original.session.workspacePackageState()), samples: 12, first_divergent_tick: null }); + if (phase === "subroutine") { + original.send("PROGRAM_PAUSE"); + const paused = original.send("CHECKPOINT").payload.checkpoint; + const resumed = await open(machine); + assert.equal(resumed.session.restoreCheckpoint(paused).status, "recovered"); + resumed.adoptSequence(paused.event_cursor); + const transported = await open(machine); + assert.equal(transported.send("RECOVER", { checkpoint: paused }).payload.status, "recovered"); + original.send("SIM_STEP", { cycles: 2 }); resumed.send("SIM_STEP", { cycles: 2 }); + assert.equal(fnv1a64(resumed.session.workspacePackageState()), fnv1a64(original.session.workspacePackageState()), "paused state must stay unchanged"); + original.send("PROGRAM_RESUME"); resumed.send("PROGRAM_RESUME"); transported.send("PROGRAM_RESUME"); + for (let step = 0; step < 12; step++) { + original.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); resumed.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); + transported.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); + assert.equal(fnv1a64(resumed.session.workspacePackageState()), fnv1a64(original.session.workspacePackageState()), `paused resume divergent tick ${original.session.tick}`); + assert.deepEqual(transported.session.wasmMotion.read(), original.session.wasmMotion.read(), "new transport must continue the same physical trajectory"); + } + rows.push({ machine: machine.id, phase: "paused-resumed", checkpoint_tick: paused.simulation_tick, continuation_tick: original.session.tick, state_hash: fnv1a64(original.session.workspacePackageState()), samples: 12, new_transport_recovery: true, first_divergent_tick: null }); + } + } + if (original.session.xyzacProgram.snapshot().program_status === "completed" && original.session.wasmMotion.read().state.queue_depth === 0) break; + } + assert.equal(original.session.xyzacProgram.snapshot().program_status, "completed"); + assert.ok(seen.has("line") && seen.has("arc"), [...seen].join(",")); + assert.ok(samples >= 24); + const trace = original.session.exportTrace(); + const imported = await open(machine); + assert.equal(imported.session.importTrace(trace).status, "verified"); + assert.equal(fnv1a64(imported.session.workspacePackageState()), fnv1a64(original.session.workspacePackageState()), "native trace import"); + const checkpoint = original.send("CHECKPOINT").payload.checkpoint; + const invalid = structuredClone(checkpoint); + invalid.state.wasm_motion.native_state.blocks[0][1] = "invalid"; + invalid.state_hash = fnv1a64(invalid.state); + invalid.checkpoint_hash = fnv1a64(Object.fromEntries(Object.entries(invalid).filter(([key]) => key !== "checkpoint_hash"))); + const unchanged = fnv1a64(original.session.workspacePackageState()); + assert.equal(original.session.restoreCheckpoint(invalid).status, "blocked"); + assert.equal(fnv1a64(original.session.workspacePackageState()), unchanged, "corrupt native blob rejected before mutation"); + const foreignBuild = structuredClone(checkpoint); + foreignBuild.state.wasm_motion.native_state.build_id = "0".repeat(64); + foreignBuild.state_hash = fnv1a64(foreignBuild.state); + foreignBuild.checkpoint_hash = fnv1a64(Object.fromEntries(Object.entries(foreignBuild).filter(([key]) => key !== "checkpoint_hash"))); + const rejectedBuild = original.session.restoreCheckpoint(foreignBuild); + assert.equal(rejectedBuild.status, "blocked"); + assert.match(rejectedBuild.blocked[0].reason, /checkpoint_build_mismatch/); + assert.equal(fnv1a64(original.session.workspacePackageState()), unchanged); + const transported = await open(machine); + const recovery = transported.send("RECOVER", { checkpoint }); + assert.equal(recovery.payload.status, "recovered", JSON.stringify(recovery.payload)); + const next = transported.send("SIM_STEP", { cycles: 1 }); + assert.equal(next.payload.program.program_status, "completed", "transport sequence continues after recovery"); + assert.deepEqual(next.payload.wasm_motion.state.position, original.session.wasmMotion.read().state.position); + rows.push({ machine: machine.id, phase: "trace-and-transport", state_hash: fnv1a64(original.session.workspacePackageState()), first_divergent_tick: null }); + transported.send("SET_MODE", { mode: "MANUAL" }); + transported.send("PROGRAM_LOAD", { source: "G21 G90\nM2\n", path: "jog-after-recovery.ngc" }); + transported.send("JOG_START", { axis: "x", direction: 1, increment: .1 }); + let jogSamples = 0; + while (transported.session.wasmMotion.read().state.queue_depth > 0 && jogSamples < 100) { + const delta = transported.send("SIM_STEP", { cycles: 1, response: "motion_delta" }); + assert.deepEqual(delta.payload.motion_sample.joints, transported.session.wasmMotion.read().state.joint_commanded, "identity motion delta must use the current planner joints"); + jogSamples++; + } + assert.equal(transported.session.wasmMotion.read().state.queue_depth, 0); + rows.push({ machine: machine.id, phase: "post-recovery-jog", samples: jogSamples, first_divergent_tick: null }); + const beforeRewind = transported.session.wasmMotion.read(); + const rewind = transported.send("RECOVER", { checkpoint: idle }); + assert.equal(rewind.payload.status, "blocked"); + assert.equal(rewind.payload.code, "runtime_checkpoint_requires_new_session"); + assert.deepEqual(transported.session.wasmMotion.read(), beforeRewind); +} +const report = { report_id: "trt-checkpoint-exact-v1", status: "verified", safeguards: { corrupt_native_rejected: true, foreign_build_rejected: true, frozen_subroutines: true, heap_relocation_checked: true, receipts_nonrecursive: true, backward_transport_restore_rejected: true }, rows, state_hash: fnv1a64(rows) }; +await mkdir(new URL("coverage/recovery/", root), { recursive: true }); +await writeFile(new URL("coverage/recovery/exact-checkpoint.report.json", root), JSON.stringify(report, null, 2) + "\n"); +console.log(JSON.stringify(report)); diff --git a/web/worker/test_trt_configuration.mjs b/web/worker/test_trt_configuration.mjs new file mode 100644 index 0000000..d4f11e1 --- /dev/null +++ b/web/worker/test_trt_configuration.mjs @@ -0,0 +1,98 @@ +import assert from "node:assert/strict"; +import { readFile, mkdir, writeFile } from "node:fs/promises"; +import createModule from "../../build-wasm/linuxcnc-sim-core.js"; +import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs"; +import { readTrtConfiguration } from "../workspace/trt-configuration.mjs"; +import { MACHINE_DEFINITIONS } from "../workspace/machine-definitions.mjs"; +import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; +import { fnv1a64 } from "./replay.mjs"; + +const root = new URL("../../", import.meta.url); +const fetchImpl = async (url) => { + try { const source = await readFile(new URL(String(url).replace(/^\//, ""), root), "utf8"); return { ok: true, text: async () => source }; } + catch { return { ok: false }; } +}; +const wasmBinary = await readFile(new URL("build-wasm/linuxcnc-sim-core.wasm", root)); +function edit(bundle, values) { + let section = ""; + const content = bundle.files[bundle.entrypoint].split("\n").map((line) => { + const heading = /^\s*\[([^\]]+)\]/.exec(line); if (heading) section = heading[1]; + const assignment = /^\s*([A-Z_]+)\s*=/.exec(line); + const value = values[`${section}.${assignment?.[1]}`]; + return value === undefined ? line : `${assignment[1]} = ${value}`; + }).join("\n"); + return { ...bundle, files: { ...bundle.files, [bundle.entrypoint]: content } }; +} +const rows = []; +for (const machine of MACHINE_DEFINITIONS) { + const original = await loadSimulationCase({ path: machine.entrypoint, fetchImpl }); + const modified = edit(original, { "AXIS_X.MAX_VELOCITY": 4, "AXIS_X.MAX_ACCELERATION": 8, "AXIS_X.MAX_LIMIT": 15, "JOINT_0.MAX_LIMIT": 17, "TRAJ.MAX_LINEAR_VELOCITY": 13, "EMCMOT.SERVO_PERIOD": 2000000, "TASK.CYCLE_TIME": .02, "DISPLAY.MAX_FEED_OVERRIDE": 1.3 }); + const config = toWorkerConfiguration(modified); + const zeroTolerance = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[WEB_RUNTIME]\nSPINDLE_TOLERANCE = 0\n` } }; + assert.equal(readTrtConfiguration(zeroTolerance).kinematics.io.spindle_tolerance, 0); + const blendingCase = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[TRAJ]\nARC_BLEND_ENABLE = 1\nARC_BLEND_OPTIMIZATION_DEPTH = 25\nARC_BLEND_GAP_CYCLES = 3\nARC_BLEND_RAMP_FREQ = 50\nARC_BLEND_TANGENT_KINK_RATIO = 0.2\n` } }; + const configuredBlend = readTrtConfiguration(blendingCase).kinematics.blend; + assert.equal(configuredBlend.optimization_depth, 25); + assert.equal(readTrtConfiguration(edit(original, { "TRAJ.MAX_LINEAR_ACCELERATION": 8 })).kinematics.max_acceleration, 8); + for (const declaration of ["SPINDLE_COUNT = 2.5", "TOOL_CAPACITY = 1002", "ENCODER_COUNT = 9"]) { + const invalid = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[WEB_RUNTIME]\n${declaration}\n` } }; + assert.throws(() => readTrtConfiguration(invalid), /trt_configuration_io_capacity/); + } + assert.deepEqual(config.home_limits.x, [-200, 17]); + assert.deepEqual(config.kinematics.axis_limits.x, [-200, 15]); + assert.equal(config.kinematics.axis_velocity_limits.x, 4); + for (const values of [{ "AXIS_X.MAX_VELOCITY": "NaN" }, { "AXIS_X.MAX_ACCELERATION": 0 }, { "JOINT_0.MAX_LIMIT": -300 }, { "KINS.JOINTS": 4 }, { "TASK.CYCLE_TIME": .0015 }, { "DISPLAY.MAX_FEED_OVERRIDE": .5 }]) assert.throws(() => readTrtConfiguration(edit(original, values)), /trt_configuration/); + const core = await createModule({ wasmBinary }); + const session = new SimSession(core, { sessionId: `configuration-${machine.id}` }); let seq = 0; + const send = (kind, payload = {}, expected = "ok") => { + const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: `cfg-${seq}`, payload }); + if (expected === "error") assert.equal(result.kind, "ERROR"); + else assert.notEqual(result.kind, "ERROR", `${kind}: ${JSON.stringify(result.payload)}`); + return result; + }; + send("HELLO"); send("SESSION_ATTACH"); + send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(original) }); + // Existing adapter values are untrusted duplicates: the files must win. + config.home_limits.x = [-1, 1]; config.kinematics.max_linear_velocity = 99; + send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: config }); + assert.equal(session.wasmMotion.velocityLimit, 13); + assert.equal(session.wasmMotion.cycleTime, .002); + assert.equal(session.wasmHomeLimit.period, .002); + assert.equal(session.xyzacTaskCycleTicks, 10); + assert.equal(readTrtConfiguration(modified).kinematics.io.cycle_time, .02); + assert.equal(session.wasmMotion.jogVelocity("x"), 4); + assert.deepEqual(session.xyzacKinematics.snapshot().limits.x, [-200, 17]); + send("POWER", { active: true }); send("HOME", { all: true }); + for (let i = 0; i < 500 && !session.wasmHomeLimit.read().state.homed; i++) send("SIM_STEP", { cycles: 1 }); + assert.ok(session.wasmHomeLimit.read().state.homed); + send("SET_OVERRIDE", { feed: 1.31 }, "error"); send("SET_OVERRIDE", { feed: 1.3 }); send("SET_OVERRIDE", { feed: 1 }); + send("JOG_START", { axis: "x", direction: 1, increment: 16 }, "error"); + send("MDI_EXECUTE", { block: "G21 G90 G1 X10 F1200" }); + let maxVelocity = 0, maxAcceleration = 0, previousVelocity = 0; + const trace = []; + for (let i = 0; i < 2000 && session.wasmMotion.read().state.queue_depth > 0; i++) { + send("SIM_STEP", { cycles: 1 }); + const state = session.wasmMotion.read().state; + maxVelocity = Math.max(maxVelocity, state.velocity); + maxAcceleration = Math.max(maxAcceleration, Math.abs(state.velocity - previousVelocity) / .02); + previousVelocity = state.velocity; + trace.push({ tick: session.tick, x: state.position.x, velocity: state.velocity }); + } + assert.equal(session.wasmMotion.read().state.queue_depth, 0); + assert.ok(maxVelocity > 3.9 && maxVelocity <= 4.000001, `${machine.id} velocity ${maxVelocity}`); + assert.ok(maxAcceleration <= 8.000001, `${machine.id} acceleration ${maxAcceleration}`); + assert.ok(Math.abs(session.wasmMotion.read().state.position.x - 10) < 1e-8); + rows.push({ machine: machine.id, max_velocity: maxVelocity, max_acceleration: maxAcceleration, period: session.wasmMotion.cycleTime, joint_limit: session.xyzacKinematics.snapshot().limits.x, trace }); + const checkpoint = session.createCheckpoint(); + send("POWER", { active: false }); + const newSpeed = toWorkerConfiguration(edit(modified, { "AXIS_X.MAX_VELOCITY": 5 })); + send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: newSpeed }); + const restored = session.restoreCheckpoint(checkpoint); + assert.equal(restored.status, "blocked"); + assert.equal(restored.blocked[0].reason, "trt_recovery_configuration_mismatch"); + assert.equal(session.wasmMotion.jogVelocity("x"), 5); +} +const report = { report_id: "trt-configuration-v1", status: "verified", rows }; +await mkdir(new URL("coverage/configuration/", root), { recursive: true }); +await writeFile(new URL("coverage/configuration/trt-runtime.report.json", root), JSON.stringify({ ...report, state_hash: fnv1a64(report) }, null, 2) + "\n"); +console.log(JSON.stringify({ status: report.status, report_id: report.report_id, rows: rows.map(({ trace, ...row }) => row) })); diff --git a/web/worker/test_xyzac_recovery.mjs b/web/worker/test_xyzac_recovery.mjs index fd4acb5..4244ebc 100644 --- a/web/worker/test_xyzac_recovery.mjs +++ b/web/worker/test_xyzac_recovery.mjs @@ -30,7 +30,7 @@ async function create(id) { assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); assert.equal(send("HOME", "home-all", { all: true }).payload.homing.phase, "accepted"); assert.equal(send("SIM_STEP", "home-complete", { cycles: 1 }).payload.panel_snapshot.motion.homed, true); - return { core, session, send }; + return { core, session, send, adoptSequence: (value) => { seq = value; } }; } const continuous = await create("xyzac-recovery-continuous"); @@ -42,6 +42,7 @@ continuous.send("PROGRAM_RUN", "program-run"); const stepped = continuous.send("SIM_STEP", "motion-step", { cycles: 2 }); assert.equal(stepped.payload.status, "verified"); const checkpointResponse = continuous.send("CHECKPOINT", "checkpoint"); +assert.equal(checkpointResponse.kind, "CHECKPOINT_READY", checkpointResponse.payload.message_key); const checkpoint = checkpointResponse.payload.checkpoint; assert.equal(checkpoint.state.kinematics.active_type, 1); assert.equal(checkpoint.state.kinematics.coordinates, "XYZAC"); @@ -57,7 +58,10 @@ continuous.send("SIM_STEP", "after-checkpoint", { cycles: 2 }); const endpoint = continuous.session.workspacePackageState(); const restarted = await create("xyzac-recovery-continuous"); -const recovery = restarted.session.restoreCheckpoint(checkpoint, continuous.session.timeline.export(), (operation) => operation); +const rejectedTail = restarted.session.restoreCheckpoint(checkpoint, continuous.session.timeline.export(), (operation) => operation); +assert.equal(rejectedTail.status, "blocked"); +assert.equal(rejectedTail.blocked[0].reason, "trt_checkpoint_tail_requires_explicit_commands"); +const recovery = restarted.session.restoreCheckpoint(checkpoint); if (recovery.status !== "recovered") console.error(JSON.stringify(recovery)); assert.equal(recovery.status, "recovered"); const restored = restarted.session.workspacePackageState(); @@ -68,11 +72,16 @@ assert.equal(restored.kinematics.state_hash, before.kinematics.state_hash); assert.equal(restored.kinematics.switch_count, switchCountBefore); assert.equal(restored.program.source_hash, before.program.source_hash); assert.equal(restored.motion_authority, "wasm"); -if (fnv1a64(before) !== fnv1a64(restored)) console.error(JSON.stringify({ differing: Object.fromEntries(Object.keys(before).filter((key) => fnv1a64(before[key]) !== fnv1a64(restored[key])).map((key) => [key, { before: before[key], restored: restored[key] }])) })); +assert.equal(fnv1a64(restored), fnv1a64(before), "checkpoint must restore complete runtime state"); const replay = restarted.session.ledger.reconcile("switch-once"); assert.equal(replay.status, "committed"); assert.equal(restarted.session.xyzacKinematics.snapshot().switch_count, switchCountBefore); +restarted.adoptSequence(checkpoint.event_cursor); +const replayedSwitch = restarted.send("MDI_EXECUTE", "switch-once", { block: "M428" }); +assert.equal(replayedSwitch.replayed, true); +assert.equal(restarted.session.xyzacKinematics.snapshot().switch_count, switchCountBefore); +assert.deepEqual(replayedSwitch.payload.panel_snapshot.visual.joints, before.motion.joints); const corruptedState = structuredClone(checkpoint); corruptedState.state.kinematics.pose.x += 1; @@ -111,8 +120,10 @@ console.log(JSON.stringify({ status: "verified", case: CASE, checkpoint: { required_fields: ["context_id", "simulation_tick", "event_cursor", "state_hash", "task", "program", "kinematics", "motion", "wasm_motion", "ledger"], active_type: checkpoint.state.kinematics.active_type, state_hash: checkpoint.state.kinematics.state_hash }, - continuous_endpoint_hash: fnv1a64(endpoint), + continuous_endpoint_hash: fnv1a64(before), restored_endpoint_hash: fnv1a64(restored), + checkpoint_tick: checkpoint.simulation_tick, + unreplayed_tail_endpoint_hash: fnv1a64(endpoint), endpoint_at_checkpoint_equal: fnv1a64(before) === fnv1a64(restored), no_duplicate_m428: restarted.session.xyzacKinematics.snapshot().switch_count === switchCountBefore, replayed_operation: replay.status === "committed", diff --git a/web/worker/wasm-linuxcnc-tp.mjs b/web/worker/wasm-linuxcnc-tp.mjs index 3bea4c5..16760c8 100644 --- a/web/worker/wasm-linuxcnc-tp.mjs +++ b/web/worker/wasm-linuxcnc-tp.mjs @@ -1,4 +1,7 @@ import { fnv1a64 } from "./replay.mjs"; +import { exportNativeCheckpoint, restoreNativeCheckpoint } from "./native-checkpoint.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; +const DEFAULTS = WEB_SETTINGS.runtime.planner; const POSE_BYTES = 72; const STATE_BYTES = 112; @@ -15,7 +18,7 @@ function poseArray(value = {}) { return ["x", "y", "z", "a", "b", "c", "u", "v", function toPose(values) { return Object.fromEntries(["x", "y", "z", "a", "b", "c", "u", "v", "w"].map((axis, index) => [axis, Number(values[index] ?? 0)])); } export class WasmLinuxcncTp { - constructor(core, { cycleTime = 0.001, velocityLimit = 35, accelerationLimit = 300, limits = {} } = {}) { + constructor(core, { cycleTime = DEFAULTS.cycle_time, velocityLimit = DEFAULTS.velocity_limit, accelerationLimit = DEFAULTS.acceleration_limit, limits = {}, axisVelocities = {}, axisAccelerations = {}, maxFeedOverride = DEFAULTS.max_feed_override, blend = DEFAULTS.blend } = {}) { this.core = core; this.available = ["sim_linuxcnc_tp_size", "sim_linuxcnc_tp_queue_capacity", "sim_linuxcnc_tp_init", "sim_linuxcnc_tp_read_state", "sim_linuxcnc_tp_step", "sim_linuxcnc_tp_idle", "sim_linuxcnc_tp_add_line", "sim_linuxcnc_tp_set_profile", "sim_linuxcnc_tp_set_feed_override", "sim_linuxcnc_tp_set_velocity_scale", "sim_linuxcnc_tp_set_jog_scale", "sim_linuxcnc_tp_set_axis_limit", "sim_linuxcnc_tp_jog", "sim_linuxcnc_tp_velocity_scale", "sim_linuxcnc_tp_velocity_limit", "sim_linuxcnc_tp_velocity_mm_min", "sim_linuxcnc_tp_jog_scale", "sim_linuxcnc_tp_jog_velocity", "sim_linuxcnc_tp_feed_override", "sim_linuxcnc_tp_set_dynamics", "sim_linuxcnc_tp_set_paused", "sim_linuxcnc_tp_abort", "sim_linuxcnc_tp_set_goal_position", "sim_linuxcnc_tp_queue_depth", "sim_linuxcnc_tp_queue_segment", "sim_linuxcnc_tp_restore_progress"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function"); this.cycleTime = cycleTime; @@ -37,7 +40,8 @@ export class WasmLinuxcncTp { this.lastState = null; this.queueDepthOverride = null; this.kinematics = null; - this.controls = { profile: 0, lookahead: 50, acceleration_limit: accelerationLimit, jerk_limit: 1e9, feed_override: 1, velocity_scale: 1, jog_scale: 1, paused: false }; + this.controls = { profile: 0, lookahead: blend.optimization_depth, acceleration_limit: accelerationLimit, jerk_limit: DEFAULTS.compatibility_jerk_limit, feed_override: 1, velocity_scale: 1, jog_scale: 1, paused: false }; + this.maxFeedOverride = maxFeedOverride; if (!this.available || typeof core?._malloc !== "function") return; this.planner = core._malloc(call(core, "sim_linuxcnc_tp_size", "number", [], [])); this.posePointer = core._malloc(POSE_BYTES); @@ -55,6 +59,17 @@ export class WasmLinuxcncTp { const index = { x: 0, y: 1, z: 2, a: 3, b: 4, c: 5, u: 6, v: 7, w: 8 }[axis]; if (index !== undefined && Array.isArray(limit) && limit.length === 2) this.setAxisLimit(index, Number(limit[0]), Number(limit[1])); } + for (const [axis, velocity] of Object.entries(axisVelocities)) { + const index = "xyzabcuvw".indexOf(axis); + const configured = call(core, "sim_linuxcnc_tp_set_axis_dynamics", "number", ["number", "number", "number", "number"], [this.planner, index, velocity, axisAccelerations[axis]]); + if (configured !== 0) throw new Error(`linuxcnc_tp_axis_dynamics_${axis}_${configured}`); + } + const feedLimit = call(core, "sim_linuxcnc_tp_set_feed_limit", "number", ["number", "number"], [this.planner, maxFeedOverride]); + if (feedLimit !== 0) throw new Error(`linuxcnc_tp_feed_limit_${feedLimit}`); + const scale = call(core, "sim_linuxcnc_tp_set_scale_minimum", "number", ["number", "number"], [this.planner, WEB_SETTINGS.ui.motion_scale_min]); + if (scale !== 0) throw new Error(`linuxcnc_tp_scale_minimum_${scale}`); + const blending = call(core, "sim_linuxcnc_tp_set_blend", "number", Array(7).fill("number"), [this.planner, blend.enable, blend.fallback_enable, blend.optimization_depth, blend.gap_cycles, blend.ramp_frequency, blend.tangent_kink_ratio]); + if (blending !== 0) throw new Error(`linuxcnc_tp_blend_${blending}`); } setPosition(value) { @@ -247,24 +262,19 @@ export class WasmLinuxcncTp { } } finally { this.core._free(pointer); } } - const unsigned = { schema: SOURCE_TP_SCHEMA, cycle_time: this.cycleTime, velocity_limit: this.velocityLimit, controls: { ...this.controls }, execution_state: this.executionState, active_tag: this.activeTag, state: current.state, segments }; + const unsigned = { schema: SOURCE_TP_SCHEMA, cycle_time: this.cycleTime, velocity_limit: this.velocityLimit, controls: { ...this.controls }, execution_state: this.executionState, active_tag: this.activeTag, state: current.state, segments, segment_tags: [...this.segmentTags], last_active_id: this.lastActiveId, queue_depth_override: this.queueDepthOverride, native_state: exportNativeCheckpoint(this.core, "sim_linuxcnc_tp", this.planner) }; return { ...unsigned, state_hash: fnv1a64(unsigned) }; } restoreState(value) { if (!value || value.schema !== SOURCE_TP_SCHEMA || value.cycle_time !== this.cycleTime || value.velocity_limit !== this.velocityLimit || value.state_hash !== fnv1a64(Object.fromEntries(Object.entries(value).filter(([key]) => key !== "state_hash")))) throw new Error("linuxcnc_tp_restore_schema"); - this.reset(); - this.executionState = Number(value.execution_state ?? 3); - this.setProfile(value.controls?.profile ?? 0, value.controls?.tolerance ?? 0); - this.setFeedOverride(value.controls?.feed_override ?? 1); - this.setVelocityScale(value.controls?.velocity_scale ?? 1); - this.setJogScale(value.controls?.jog_scale ?? 1); - this.setPosition(value.state?.position ?? {}); - for (const segment of value.segments ?? []) this.enqueue({ ...segment, kinematics_mode: segment.kinematics_mode ?? value.active_tag ?? 0 }); - if (this.available && value.state) { - this.core.HEAPF64.set(poseArray(value.state.position), this.posePointer / 8); - const restored = call(this.core, "sim_linuxcnc_tp_restore_progress", "number", ["number", "number", "number", "number"], [this.planner, this.posePointer, Number(value.state.velocity ?? 0), Number(value.state.tick ?? 0)]); - if (restored !== 0) throw new Error(`linuxcnc_tp_restore_progress_${restored}`); - } + if (!value.native_state || !Array.isArray(value.segment_tags)) throw new Error("linuxcnc_tp_native_checkpoint_required"); + restoreNativeCheckpoint(this.core, "sim_linuxcnc_tp", value.native_state, this.planner); + this.executionState = value.execution_state; + this.activeTag = value.active_tag; + this.controls = { ...value.controls }; + this.segmentTags = new Map(value.segment_tags); + this.lastActiveId = value.last_active_id; + this.queueDepthOverride = value.queue_depth_override; return this.read(); } destroy() { for (const pointer of [this.planner, this.posePointer, this.centerPointer, this.normalPointer, this.statePointer]) if (pointer && typeof this.core?._free === "function") this.core._free(pointer); this.planner = this.posePointer = this.centerPointer = this.normalPointer = this.statePointer = 0; } diff --git a/web/worker/wasm-native-homing.mjs b/web/worker/wasm-native-homing.mjs index 545f9cb..8561355 100644 --- a/web/worker/wasm-native-homing.mjs +++ b/web/worker/wasm-native-homing.mjs @@ -6,7 +6,7 @@ export class WasmNativeHoming { constructor(core, configuration, kinematics) { this.core = core; this.kinematics = kinematics; - this.jointCount = 5; + this.jointCount = configuration.home_limits.joint_count; this.size = core._sim_native_home_size(); this.pointer = core._malloc(this.size); this.buffer = core._malloc(12 * 8); @@ -17,7 +17,7 @@ export class WasmNativeHoming { if (typeof value !== "string") throw new Error(`home_ini_missing:${path}`); return value; } } }); - this.parameters = Array.from({ length: 5 }, (_, j) => { + this.parameters = Array.from({ length: this.jointCount }, (_, j) => { const section = parsed.sections[`JOINT_${j}`] ?? {}; const n = (key, fallback) => section[key] === undefined ? fallback : Number(section[key]); const flags = [["HOME_IGNORE_LIMITS", 1], ["HOME_USE_INDEX", 2], ["HOME_IS_SHARED", 4], ["LOCKING_INDEXER", 8], ["HOME_INDEX_NO_ENCODER_RESET", 128]].reduce((mask, [key, bit]) => mask | (n(key, 0) ? bit : 0), 0); @@ -25,7 +25,7 @@ export class WasmNativeHoming { if (![0, 1, 2].includes(encoder)) throw new Error("native_home_absolute_encoder_invalid"); return [n("HOME_OFFSET", 0), n("HOME", 0), n("HOME_FINAL_VEL", -1), n("HOME_SEARCH_VEL", 0), n("HOME_LATCH_VEL", 0), flags | (encoder ? 16 | 32 | (encoder === 2 ? 64 : 0) : 0), n("HOME_SEQUENCE", 999), n("VOLATILE_HOME", 0), n("MIN_LIMIT", -1e9), n("MAX_LIMIT", 1e9), n("MAX_VELOCITY", 1), n("MAX_ACCELERATION", 1)]; }); - this.period = Number(parsed.sections.EMCMOT?.SERVO_PERIOD ?? 1000000) / 1e9; + this.period = configuration.kinematics.servo_cycle_time; this.reset(); } invoke(name, ...args) { @@ -41,13 +41,13 @@ export class WasmNativeHoming { step() { this.invoke("step"); return this.read(); } cancel(volatile = false) { this.invoke("cancel", Number(volatile)); return this.read(); } reset() { - this.invoke("init", 5, this.period); + this.invoke("init", this.jointCount, this.period); this.parameters.forEach((p, j) => { this.core.HEAPF64.set(p, this.buffer / 8); this.invoke("config", j, this.buffer); }); return this.read(); } check(joints) { if (!this.active) this.sync(joints); return this.read(); } read() { - const joint = Array.from({ length: 5 }, (_, j) => ({ + const joint = Array.from({ length: this.jointCount }, (_, j) => ({ joint: j, homing: Boolean(this.get(j, 4)), homed: Boolean(this.get(j, 5)), home_state: this.get(j, 6), home_state_name: `HOME_${STATES[this.get(j, 6)]}`, position: this.get(j, 7), motor_position: this.get(j, 8), motor_offset: this.get(j, 9), @@ -56,7 +56,7 @@ export class WasmNativeHoming { const joints = joint.map((j) => j.position); const result = this.kinematics.forward(joints); return { status: result.status, ...(result.code ? { code: result.code } : {}), state: { - schema: "sim_home_limit_state.v1", source: "linuxcnc-homing.c", joint_count: 5, + schema: "sim_home_limit_state.v1", source: "linuxcnc-homing.c", joint_count: this.jointCount, homed: joint.every((j) => j.homed), homed_mask: joint.reduce((mask, j) => mask | (j.homed ? 1 << j.joint : 0), 0), homing: this.active, motion_mode: this.mode, joint, joints, pose: result.pose ?? null, home_sequence: this.get(0, 3), tick: this.get(0, 2), last_error: result.result ?? 0, limit_error: result.result === -702 ? -702 : 0, kinematics_error: result.result ?? 0, diff --git a/web/worker/wasm-program-runtime.mjs b/web/worker/wasm-program-runtime.mjs index 6702b1c..3425cfc 100644 --- a/web/worker/wasm-program-runtime.mjs +++ b/web/worker/wasm-program-runtime.mjs @@ -1,4 +1,5 @@ import { fnv1a64 } from "./replay.mjs"; +import { exportNativeCheckpoint, restoreNativeCheckpoint } from "./native-checkpoint.mjs"; const SCHEMA = "wasm_program_runtime.v1"; const SEGMENT_BYTES = 216; @@ -237,8 +238,15 @@ export class WasmProgramRuntime { seek(line) { return this.invoke("sim_program_seek", [line], "program_seek"); } restoreState(value) { - if (!value || ![SCHEMA, "sim_program.v1"].includes(value.schema) || !Number.isSafeInteger(value.current_line) || value.current_line < 0 || !this.source) throw new Error("wasm_program_restore_schema"); + if (!value || ![SCHEMA, "sim_program.v1"].includes(value.schema) || !Number.isSafeInteger(value.current_line) || value.current_line < 0 || !this.source && !value.native_state) throw new Error("wasm_program_restore_schema"); if (value.source_hash && value.source_hash !== this.sourceHash) throw new Error("wasm_program_restore_source_hash"); + if (value.native_state) { + if (value.execution_source_hash !== fnv1a64(this.executionSource)) throw new Error("wasm_program_restore_execution_source_hash"); + restoreNativeCheckpoint(this.core, "sim_program", value.native_state, this.pointer); + this.synchronizedPosition = structuredClone(value.synchronized_position); + this.rapidVelocity = value.rapid_velocity; + return this.snapshot(); + } const subroutines = [...this.subroutines]; const loaded = this.load(this.source, this.path); if (loaded.status !== "verified") throw new Error(loaded.code); @@ -333,6 +341,10 @@ export class WasmProgramRuntime { return { status: "verified", program_status: state.status, line: state.current_line, control_code: state.control_code ?? 0, has_control: state.has_control === 1, program: state, motion: this.readMotion(state) }; } + checkpoint() { + return { ...this.snapshot(), subroutine_sources: structuredClone(this.subroutines), synchronized_position: structuredClone(this.synchronizedPosition), execution_source_hash: fnv1a64(this.executionSource), native_state: exportNativeCheckpoint(this.core, "sim_program", this.pointer) }; + } + snapshot() { if (!this.available) return { schema: SCHEMA, status: "blocked", code: "wasm_program_unavailable" }; const required = call(this.core, "sim_program_canonical", "number", ["number", "number", "number"], [this.pointer, 0, 0]); diff --git a/web/worker/wasm-xyzac-device-io.mjs b/web/worker/wasm-xyzac-device-io.mjs index 5658f33..2e72629 100644 --- a/web/worker/wasm-xyzac-device-io.mjs +++ b/web/worker/wasm-xyzac-device-io.mjs @@ -1,4 +1,5 @@ import { fnv1a64 } from "./replay.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; const DEVICE_SCHEMA = "wasm_xyzac_device_io.v1"; const DEVICE_ERROR_REASONS = Object.freeze({ @@ -42,7 +43,7 @@ function operationNumber(value) { } export class WasmXyzacDeviceIo { - constructor(core, { encoderCount = 2 } = {}) { + constructor(core, { encoderCount = WEB_SETTINGS.runtime.io.encoder_count, cycleTime = WEB_SETTINGS.runtime.io.cycle_time } = {}) { this.core = core; this.available = ["sim_device_io_size", "sim_device_init", "sim_device_canonical", "sim_device_state_hash"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function"); this.pointer = 0; @@ -50,7 +51,7 @@ export class WasmXyzacDeviceIo { const size = call(core, "sim_device_io_size", "number", [], []); this.pointer = core._malloc(size); if (!this.pointer) throw new Error("wasm_xyzac_device_io_allocation_failed"); - const result = call(core, "sim_device_init", "number", ["number", "number", "number"], [this.pointer, encoderCount, 0.01]); + const result = call(core, "sim_device_init", "number", ["number", "number", "number"], [this.pointer, encoderCount, cycleTime]); if (result !== 0) throw new Error(`wasm_xyzac_device_io_init_${result}`); } diff --git a/web/worker/wasm-xyzac-io.mjs b/web/worker/wasm-xyzac-io.mjs index abf6ca0..5613ac4 100644 --- a/web/worker/wasm-xyzac-io.mjs +++ b/web/worker/wasm-xyzac-io.mjs @@ -1,4 +1,7 @@ import { fnv1a64 } from "./replay.mjs"; +import { exportNativeCheckpoint, restoreNativeCheckpoint } from "./native-checkpoint.mjs"; +import { WEB_SETTINGS } from "../workspace/web-settings.mjs"; +const DEFAULTS = WEB_SETTINGS.runtime.io; const IO_SCHEMA = "wasm_xyzac_io.v1"; const HAL_SCHEMA = "wasm_xyzac_hal.v1"; @@ -44,7 +47,7 @@ function hexToBytes(value) { } export class WasmXyzacIo { - constructor(core, { spindleCount = 2, toolCapacity = 1001 } = {}) { + constructor(core, { spindleCount = DEFAULTS.spindle_count, toolCapacity = DEFAULTS.tool_capacity, cycleTime = DEFAULTS.cycle_time, spindleAcceleration = DEFAULTS.spindle_acceleration, spindleTolerance = DEFAULTS.spindle_tolerance } = {}) { this.core = core; this.available = ["sim_io_size", "sim_io_init", "sim_io_canonical", "sim_io_state_hash"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function"); this.pointer = 0; @@ -58,7 +61,7 @@ export class WasmXyzacIo { this.pointer = core._malloc(this.ioSize); this.posePointer = core._malloc(POSE_BYTES); if (!this.pointer || !this.posePointer) throw new Error("wasm_xyzac_io_allocation_failed"); - const result = call(core, "sim_io_init", "number", ["number", "number", "number", "number", "number", "number"], [this.pointer, spindleCount, toolCapacity, 0.01, 600, 5]); + const result = call(core, "sim_io_init", "number", ["number", "number", "number", "number", "number", "number"], [this.pointer, spindleCount, toolCapacity, cycleTime, spindleAcceleration, spindleTolerance]); if (result !== 0) throw new Error(`wasm_xyzac_io_init_${result}`); } @@ -102,7 +105,7 @@ export class WasmXyzacIo { return this.export(); } - halExport() { + halExport({ includeState = false } = {}) { if (!this.available) return { schema: HAL_SCHEMA, source: "wasm_hal", status: "blocked", code: "wasm_hal_unavailable" }; const tick = call(this.core, "sim_hal_tick", "number", [], []); const unsigned = { @@ -115,10 +118,16 @@ export class WasmXyzacIo { thread_count: call(this.core, "sim_hal_thread_count", "number", [], []), function_count: call(this.core, "sim_hal_function_count", "number", [], []), component_count: call(this.core, "sim_hal_component_count", "number", [], []), + ...(includeState ? { native_state: exportNativeCheckpoint(this.core, "sim_hal") } : {}), }; return { ...unsigned, state_hash: fnv1a64(unsigned) }; } + restoreHalState(value) { + if (!value?.native_state) throw new Error("wasm_hal_native_checkpoint_required"); + restoreNativeCheckpoint(this.core, "sim_hal", value.native_state); + } + halValue(name, value, type = 4, param = false) { if (!this.available) return { status: "blocked", code: "wasm_hal_unavailable" }; if (param || !HAL_WRITE_ALLOWLIST.has(name)) return { status: "error", code: "wasm_hal_write_denied", name, target: param ? "param" : "pin" }; diff --git a/web/worker/wasm-xyzac-kinematics.mjs b/web/worker/wasm-xyzac-kinematics.mjs index 8e36432..e424edc 100644 --- a/web/worker/wasm-xyzac-kinematics.mjs +++ b/web/worker/wasm-xyzac-kinematics.mjs @@ -16,7 +16,6 @@ const SWITCH = Object.freeze({ switching: 16, active_mode: 20, pending_mode: 24, const TYPE = Object.freeze({ identity: 1, xyzac: 2, xyzbc: 3, userk: 1 }); const MODE = Object.freeze({ identity: 0, xyzac: 1, userk: 2 }); const AXES = Object.freeze(["x", "y", "z", "a", "b", "c", "u", "v", "w"]); -const DEFAULT_LIMITS = Object.freeze({ x: [-200, 200], y: [-100, 100], z: [-120, 120], a: [-100, 50], b: [-100, 50], c: [-36000, 36000] }); function call(core, name, returnType, argTypes, args) { const direct = core?.[`_${name}`] ?? core?.[name]; @@ -44,7 +43,8 @@ function configFor(core, pointer, type, options, coordinates) { view.setUint32(CONFIG.extra_joint_count, 0, true); setCoordinates(core, pointer, coordinates); for (const [index, axis] of coordinates.toLowerCase().split("").entries()) { - const limit = options.limits?.[axis] ?? DEFAULT_LIMITS[axis]; + const limit = options.limits?.[axis]; + if (!Array.isArray(limit) || limit.length !== 2 || !limit.every(Number.isFinite) || limit[0] >= limit[1]) throw new Error(`wasm_trt_limit_required:${axis}`); core.HEAPU8[pointer + CONFIG.limit_enabled + index] = 1; view.setFloat64(CONFIG.joint_min + index * 8, Number(limit[0]), true); view.setFloat64(CONFIG.joint_max + index * 8, Number(limit[1]), true); @@ -69,11 +69,10 @@ function resultCode(operation, result) { export class WasmXyzacKinematics { constructor(core, options = {}) { this.core = core; - this.coordinates = String(options.coordinates ?? "XYZAC").toUpperCase(); + this.coordinates = String(options.coordinates ?? options.limits?.coordinates ?? "XYZAC").toUpperCase(); if (!["XYZAC", "XYZBC"].includes(this.coordinates)) throw new Error("wasm_trt_coordinates_invalid"); this.kinematicsType = this.coordinates === "XYZBC" ? "xyzbc-trt-kins" : "xyzac-trt-kins"; - this.options = { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: 0, y_offset: 20, z_offset: 10, tool_offset: 0, limits: DEFAULT_LIMITS, ...options }; - if (this.coordinates === "XYZBC" && options.limits === undefined) this.options.limits = { x: [-200, 200], y: [-100, 100], z: [-120, 120], b: [-100, 50], c: [-36000, 36000] }; + this.options = { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: 0, y_offset: 0, z_offset: 0, tool_offset: 0, ...options }; this.available = ["sim_kinematics_init", "sim_kinematics_switch_init", "sim_kinematics_switch_register", "sim_kinematics_switch_select", "sim_kinematics_switch_select_physical", "sim_kinematics_switch_forward", "sim_kinematics_switch_inverse", "sim_kinematics_switch_inverse_mode", "sim_kinematics_switch_active", "sim_kinematics_switch_state_hash"].every((name) => typeof core?.[`_${name}`] === "function" || typeof core?.[name] === "function"); this.lastCommand = null; this.modules = new Map(); @@ -198,7 +197,7 @@ export class WasmXyzacKinematics { const activeMode = this.activeMode; const nativeType = activeMode === MODE.xyzac ? this.kinematicsType : activeMode === MODE.userk ? "userk-identity-template" : "identity"; const view = new DataView(this.core.HEAPU8.buffer, this.switchPointer, SWITCH_SIZE); - const limits = Object.fromEntries(this.coordinates.toLowerCase().split("").map((axis) => [axis, [...(this.options.limits?.[axis] ?? DEFAULT_LIMITS[axis])]])); + const limits = Object.fromEntries(this.coordinates.toLowerCase().split("").map((axis) => [axis, [...this.options.limits[axis]]])); const unsigned = { schema: "wasm-xyzac-kinematics.v1", active_type: activeMode, native_type: nativeType, coordinates: this.coordinates, joint_count: 5, linear_units: "mm", angular_units: "deg", pose: currentPose, joints: joints.slice(0, 5), offsets: { x_rot_point: this.options.x_rot_point, y_rot_point: this.options.y_rot_point, z_rot_point: this.options.z_rot_point, x_offset: this.options.x_offset, y_offset: this.options.y_offset, z_offset: this.options.z_offset, tool_offset: this.options.tool_offset }, limits, generation: view.getUint32(SWITCH.generation, true), switch_count: view.getUint32(SWITCH.switch_count, true), abort_count: view.getUint32(SWITCH.abort_count, true), last_command: this.lastCommand, capability: { userk: "linked_identity_template", dynamic_modules: "denied" }, source: "linuxcnc-wasm-sim_kinematics_switch" }; const hash = call(this.core, "sim_kinematics_switch_state_hash", "number", ["number", "number", "number", "number"], [this.switchPointer, this.jointPointer, 5, this.posePointer]); return { status: "verified", ...unsigned, wasm_state_hash: `fnv1a64:${BigInt.asUintN(64, BigInt(hash >>> 0)).toString(16).padStart(16, "0")}`, state_hash: fnv1a64(unsigned) }; diff --git a/web/worker/xyzac-session.mjs b/web/worker/xyzac-session.mjs index 88fdd4e..bf27528 100644 --- a/web/worker/xyzac-session.mjs +++ b/web/worker/xyzac-session.mjs @@ -1,6 +1,7 @@ import { SimSession, CONTEXT_ID, PROTOCOL_SCHEMA } from "./sim-session.mjs"; import { XyzacCommandLedger, XYZAC_COMMAND_LEDGER_SCHEMA } from "./xyzac-command-ledger.mjs"; import { XyzacScheduler, XYZAC_SCHEDULER_SCHEMA } from "./xyzac-scheduler.mjs"; +import { DEFAULT_MACHINE } from "../workspace/machine-definitions.mjs"; export const XYZAC_SESSION_SCHEMA = "xyzac-session.v1"; @@ -13,6 +14,7 @@ export class XyzacSession extends SimSession { } authority() { + const machine = this.machineDefinition ?? DEFAULT_MACHINE; return { schema: XYZAC_SESSION_SCHEMA, authority: "linuxcnc-wasm-worker", @@ -22,7 +24,7 @@ export class XyzacSession extends SimSession { protocol_schema: PROTOCOL_SCHEMA, ledger_schema: XYZAC_COMMAND_LEDGER_SCHEMA, scheduler_schema: XYZAC_SCHEDULER_SCHEMA, - machine: { coordinates: "XYZAC", joint_count: 5, kinematics: "xyzac-trt", rotary_units: "degrees" }, + machine: { coordinates: machine.coordinates, joint_count: machine.joint_count, kinematics: machine.id, rotary_units: "degrees" }, }; } diff --git a/web/workspace/machine-catalog.json b/web/workspace/machine-catalog.json new file mode 100644 index 0000000..df2e282 --- /dev/null +++ b/web/workspace/machine-catalog.json @@ -0,0 +1,35 @@ +{ + "default_machine": "xyzac-trt", + "machines": [ + { + "id": "xyzac-trt", + "coordinates": "XYZAC", + "kinematics_type": 2, + "entrypoint": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", + "program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzac.ngc", + "switchkins_program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc", + "source_path": "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py", + "model": "trt-moving-knee", + "subroutines": [ + "xyzac_switchkins_sub", + "helix_ac" + ], + "runtime_fixture": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini" + }, + { + "id": "xyzbc-trt", + "coordinates": "XYZBC", + "kinematics_type": 3, + "entrypoint": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", + "program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc", + "switchkins_program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc", + "source_path": "linuxcnc/src/hal/user_comps/vismach/xyzbc-trt-gui.py", + "model": "trt-moving-head", + "subroutines": [ + "xyzbc_switchkins_sub", + "helix_bc" + ], + "runtime_fixture": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini" + } + ] +} diff --git a/web/workspace/machine-definitions.mjs b/web/workspace/machine-definitions.mjs index 7333546..af5a7ed 100644 --- a/web/workspace/machine-definitions.mjs +++ b/web/workspace/machine-definitions.mjs @@ -1,25 +1,12 @@ -const TRT_ROOT = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting"; +import catalog from "./machine-catalog.json" with { type: "json" }; -function trt(id, coordinates, type, offsets, model) { - const stem = coordinates.toLowerCase(); - return Object.freeze({ - id, label: `${coordinates} TRT`, coordinates, axes: [...stem], joint_count: coordinates.length, - entrypoint: `${TRT_ROOT}/${id}.ini`, program: `${TRT_ROOT}/demos/boat-${stem}.ngc`, - switchkins_program: `${TRT_ROOT}/demos/${stem}_switchkins.ngc`, - kinematics_type: type, runtime: "trt", model, - source_path: `linuxcnc/src/hal/user_comps/vismach/${id}-gui.py`, - subroutines: [`${stem}_switchkins_sub`, `helix_${stem.slice(3)}`], - offsets, - joint_limits: { x: [-200, 200], y: [-100, 100], z: [-120, 120], [stem[3]]: [-100, 50], c: [-36000, 36000] }, - axis_limits: { x: [-200, 200], y: [-100, 100], z: [-120, 120], [stem[3]]: coordinates === "XYZAC" ? [-100, 50] : [-36000, 36000], c: [-36000, 36000] }, - }); -} - -export const MACHINE_DEFINITIONS = Object.freeze([ - trt("xyzac-trt", "XYZAC", 2, { x_rot_point: 0, y_offset: 20, z_offset: 10, tool_offset: 0 }, "trt-moving-knee"), - trt("xyzbc-trt", "XYZBC", 3, { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: -20, y_offset: 0, z_offset: -15, tool_offset: 0 }, "trt-moving-head"), -]); -export const DEFAULT_MACHINE = MACHINE_DEFINITIONS[0]; +export const MACHINE_DEFINITIONS = Object.freeze(catalog.machines.map((entry) => { + if (!entry.id || !entry.entrypoint || !entry.program || !entry.source_path || !["XYZAC", "XYZBC"].includes(entry.coordinates)) throw new Error("machine_catalog_invalid"); + return Object.freeze({ ...entry, label: `${entry.coordinates} TRT`, axes: [...entry.coordinates.toLowerCase()], joint_count: entry.coordinates.length, runtime: "trt" }); +})); +if (new Set(MACHINE_DEFINITIONS.map((entry) => entry.id)).size !== MACHINE_DEFINITIONS.length) throw new Error("machine_catalog_duplicate"); +export const DEFAULT_MACHINE = MACHINE_DEFINITIONS.find((entry) => entry.id === catalog.default_machine); +if (!DEFAULT_MACHINE) throw new Error("machine_catalog_default_missing"); export function machineDefinition(value) { return MACHINE_DEFINITIONS.find((entry) => [entry.id, entry.entrypoint, entry.coordinates].includes(value)) ?? null; } diff --git a/web/workspace/sim-case-loader.mjs b/web/workspace/sim-case-loader.mjs index 57c8200..dc4704a 100644 --- a/web/workspace/sim-case-loader.mjs +++ b/web/workspace/sim-case-loader.mjs @@ -3,7 +3,10 @@ import { runtimeResourceFixture } from "./resource-resolver.mjs"; import { SIM_CASE_CATALOG, SIM_FILE_INDEX, SIM_HAL_COMPONENTS } from "./sim-case-catalog.mjs"; import { isOptionalSourceKey } from "./linuxcnc-source-semantics.mjs"; import { MACHINE_DEFINITIONS, machineDefinition } from "./machine-definitions.mjs"; -import { readTrtGeometry } from "./trt-geometry.mjs"; +import resources from "./simulation-resources.json" with { type: "json" }; +import { parseIni } from "./ini-parser.mjs"; +import { WorkspaceRuntime } from "./runtime.mjs"; +import { readTrtConfiguration } from "./trt-configuration.mjs"; export const SIMULATION_CASE_SCHEMA = "simulation-case-bundle.v1"; const RESOURCE_KEYS = new Set([ @@ -12,53 +15,9 @@ const RESOURCE_KEYS = new Set([ "PYVCP", "NGCGUI_SUBFILE", "INTRO_GRAPHIC", "SUBROUTINE_PATH", "REMAP", ]); const MANIFEST = new Set(SIM_FILE_INDEX); -const PRELOADED_HAL_COMPONENTS = Object.freeze(["corexykins", "lineardeltakins", "motmod", "pid", "rotarydeltakins", "stepgen", "trivkins", "xyzac-trt-kins", "xyzbc-trt-kins", "tpmod", "homemod"]); -const CASE_KINEMATICS = Object.freeze({ - "linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini": Object.freeze({ type: "corexy", path_mode: "kins" }), - "linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini": Object.freeze({ type: "corexy", path_mode: "hal" }), - "linuxcnc/configs/sim/axis/ldelta.ini": Object.freeze({ type: "lineardelta", path_mode: "kins" }), - "linuxcnc/configs/sim/axis/rdelta.ini": Object.freeze({ type: "rotarydelta", path_mode: "kins" }), - "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": Object.freeze({ type: "xyzac-trt", path_mode: "wasm", coordinates: "XYZAC", joint_count: 5, max_linear_velocity: 35, axis_linear_velocity_limit: 20, max_acceleration: 300, max_jerk: 1000000000, servo_cycle_time: 0.001, task_cycle_time: 0.01, cycle_time: 0.001, switchkins: { 0: "identity", 1: "xyzac", 2: "userk" } }), - "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": Object.freeze({ type: "xyzbc-trt", path_mode: "wasm", coordinates: "XYZBC", joint_count: 5, max_linear_velocity: 35, axis_linear_velocity_limit: 20, axis_angular_velocity_limit: 30, max_acceleration: 300, max_jerk: 1000000000, servo_cycle_time: 0.001, task_cycle_time: 0.01, cycle_time: 0.001, switchkins: { 0: "identity", 1: "xyzbc", 2: "userk" } }), -}); -const RUNTIME_FIXTURES = Object.freeze({ - "linuxcnc/configs/sim/axis/minimal_xyz.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/minimal_xyz_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = minimal_xyz_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated HAL command fixture", - }), - "linuxcnc/configs/sim/axis/ja_tests/xz/xz.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = xz_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated XZ HAL command fixture", - }), - "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = xz_both_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated XZ BOTH HAL command fixture", - }), - "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = xz_both_auto_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated XZ BOTH AUTO HAL command fixture", - }), - "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt-headless_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = xyzac-trt-headless_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated XYZAC HAL command fixture", - }), - "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": Object.freeze({ - generated_hal: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal", - replace: "HALFILE = LIB:basic_sim.tcl", - with: "HALFILE = xyzbc-trt_cmds.hal", - reason: "LinuxCNC basic_sim.tcl generated XYZBC HAL command fixture", - }), -}); +const PRELOADED_HAL_COMPONENTS = Object.freeze(resources.preloaded_hal_components); +const CASE_KINEMATICS = Object.freeze(resources.kinematics); +const RUNTIME_FIXTURES = Object.freeze(resources.runtime_fixtures); function clone(value) { return structuredClone(value); } function text(value) { return typeof value === "string" && value.trim() ? value.trim() : null; } @@ -180,7 +139,8 @@ function resourceReferences(source, content) { } export function findSimulationCase(path) { - return SIM_CASE_CATALOG.find((entry) => entry.path === path) ?? null; + const registered = machineDefinition(path); + return SIM_CASE_CATALOG.find((entry) => entry.path === path) ?? (registered ? { path: registered.entrypoint, coordinates: registered.coordinates, label: registered.label, status: "registered" } : null); } export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, baseUrl = "" } = {}) { @@ -243,7 +203,7 @@ export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, b } } const sourceIni = files[catalog.path]; - const fixture = RUNTIME_FIXTURES[catalog.path]; + const fixture = RUNTIME_FIXTURES[catalog.path] ?? RUNTIME_FIXTURES[machine?.runtime_fixture]; if (fixture) { if (!inManifest(fixture.generated_hal)) missing.push(fixture.generated_hal); else if (!files[fixture.generated_hal]) { @@ -280,7 +240,23 @@ export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, b export function toWorkerConfiguration(bundle) { if (!bundle || bundle.schema !== SIMULATION_CASE_SCHEMA) throw new Error("simulation_case_bundle_invalid"); const kinematics = CASE_KINEMATICS[bundle.entrypoint] ? clone(CASE_KINEMATICS[bundle.entrypoint]) : null; - if (machineDefinition(bundle.entrypoint)) kinematics.offsets = readTrtGeometry(bundle).offsets; + const trt = machineDefinition(bundle.entrypoint) ? readTrtConfiguration(bundle) : null; + let homeLimits = null; + if (!trt) { + const parsed = parseIni({ workspace: new WorkspaceRuntime({ files: bundle.files }), entrypoint: bundle.entrypoint }); + const coordinates = parsed.sections.TRAJ?.COORDINATES?.replace(/\s/g, "").toUpperCase(); + if (coordinates && /^[XYZABCUVW]+$/.test(coordinates)) { + const limits = {}; + for (const [index, axis] of [...coordinates].entries()) { + const section = parsed.sections[`JOINT_${index}`] ?? parsed.sections[`AXIS_${axis}`]; + if (!section || section.MIN_LIMIT === undefined || section.MAX_LIMIT === undefined) continue; + const range = [Number(section.MIN_LIMIT), Number(section.MAX_LIMIT)]; + if (!range.every(Number.isFinite) || range[0] >= range[1]) throw new Error(`configuration_joint_limits:${index}`); + limits[axis.toLowerCase()] = range; + } + if (Object.keys(limits).length) homeLimits = { coordinates, joint_count: coordinates.length, ...limits }; + } + } return { entrypoint: bundle.entrypoint, files: clone(bundle.files), @@ -288,9 +264,8 @@ export function toWorkerConfiguration(bundle) { state_hash: bundle.state_hash, optional_missing: clone(bundle.optional_missing ?? []), runtime_fixtures: clone(bundle.runtime_fixtures ?? []), - ...(bundle.entrypoint === "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini" ? { home_limits: { z: [-240, 100] } } : {}), - ...(machineDefinition(bundle.entrypoint) ? { home_limits: { joint_count: 5, kinematics_type: machineDefinition(bundle.entrypoint).kinematics_type, coordinates: machineDefinition(bundle.entrypoint).coordinates, ...machineDefinition(bundle.entrypoint).joint_limits } } : {}), - ...(kinematics ? { kinematics } : {}), + ...(homeLimits ? { home_limits: homeLimits } : {}), + ...(trt ?? (kinematics ? { kinematics } : {})), }; } diff --git a/web/workspace/simulation-resources.json b/web/workspace/simulation-resources.json new file mode 100644 index 0000000..607930b --- /dev/null +++ b/web/workspace/simulation-resources.json @@ -0,0 +1,71 @@ +{ + "kinematics": { + "linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini": { + "type": "corexy", + "path_mode": "kins" + }, + "linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini": { + "type": "corexy", + "path_mode": "hal" + }, + "linuxcnc/configs/sim/axis/ldelta.ini": { + "type": "lineardelta", + "path_mode": "kins" + }, + "linuxcnc/configs/sim/axis/rdelta.ini": { + "type": "rotarydelta", + "path_mode": "kins" + } + }, + "runtime_fixtures": { + "linuxcnc/configs/sim/axis/minimal_xyz.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/minimal_xyz_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = minimal_xyz_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated HAL command fixture" + }, + "linuxcnc/configs/sim/axis/ja_tests/xz/xz.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = xz_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated XZ HAL command fixture" + }, + "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = xz_both_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated XZ BOTH HAL command fixture" + }, + "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = xz_both_auto_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated XZ BOTH AUTO HAL command fixture" + }, + "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt-headless_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = xyzac-trt-headless_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated XYZAC HAL command fixture" + }, + "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": { + "generated_hal": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal", + "replace": "HALFILE = LIB:basic_sim.tcl", + "with": "HALFILE = xyzbc-trt_cmds.hal", + "reason": "LinuxCNC basic_sim.tcl generated XYZBC HAL command fixture" + } + }, + "preloaded_hal_components": [ + "corexykins", + "lineardeltakins", + "motmod", + "pid", + "rotarydeltakins", + "stepgen", + "trivkins", + "xyzac-trt-kins", + "xyzbc-trt-kins", + "tpmod", + "homemod" + ] +} diff --git a/web/workspace/trt-configuration.mjs b/web/workspace/trt-configuration.mjs new file mode 100644 index 0000000..72d17be --- /dev/null +++ b/web/workspace/trt-configuration.mjs @@ -0,0 +1,74 @@ +import { parseIni } from "./ini-parser.mjs"; +import { WorkspaceRuntime } from "./runtime.mjs"; +import { machineDefinition } from "./machine-definitions.mjs"; +import { readTrtGeometry } from "./trt-geometry.mjs"; +import { WEB_SETTINGS } from "./web-settings.mjs"; + +const NUMBER = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i; +// ABI capacity constraints from sim_io.h and sim_device_io.h, not machine defaults. +const IO_CAPACITY = Object.freeze({ spindle_count: 8, tool_capacity: 1001, encoder_count: 8 }); +export function readTrtConfiguration(configuration) { + const machine = machineDefinition(configuration.entrypoint); + if (!machine) throw new Error("trt_configuration_machine_missing"); + const parsed = parseIni({ workspace: new WorkspaceRuntime({ files: configuration.files }), entrypoint: configuration.entrypoint }); + const sections = parsed.sections; + const number = (section, key, { positive = false, fallback } = {}) => { + const raw = sections[section]?.[key]; + if (raw === undefined && fallback !== undefined) return fallback; + if (typeof raw !== "string" || !NUMBER.test(raw) || !Number.isFinite(Number(raw)) || positive && Number(raw) <= 0) throw new Error(`trt_configuration_number:[${section}]${key}`); + return Number(raw); + }; + const geometry = readTrtGeometry(configuration); + const coordinates = sections.TRAJ.COORDINATES.replace(/\s/g, "").toUpperCase(); + const axes = [...coordinates.toLowerCase()]; + const jointCount = number("KINS", "JOINTS", { positive: true }); + if (jointCount !== axes.length) throw new Error("trt_configuration_joint_count"); + if (sections.TRAJ.LINEAR_UNITS !== "mm" || sections.TRAJ.ANGULAR_UNITS !== "deg") throw new Error("trt_configuration_units_unsupported"); + if (!/\bsparm=identityfirst\b/.test(sections.KINS.KINEMATICS)) throw new Error("trt_configuration_requires_identityfirst"); + const limits = (section) => { + const range = [number(section, "MIN_LIMIT"), number(section, "MAX_LIMIT")]; + if (range[0] >= range[1]) throw new Error(`trt_configuration_limits:${section}`); + return range; + }; + const jointLimits = {}, axisLimits = {}, velocity = {}, acceleration = {}; + for (const [joint, axis] of axes.entries()) { + jointLimits[axis] = limits(`JOINT_${joint}`); + axisLimits[axis] = limits(`AXIS_${axis.toUpperCase()}`); + velocity[axis] = number(`AXIS_${axis.toUpperCase()}`, "MAX_VELOCITY", { positive: true }); + acceleration[axis] = number(`AXIS_${axis.toUpperCase()}`, "MAX_ACCELERATION", { positive: true }); + number(`JOINT_${joint}`, "MAX_VELOCITY", { positive: true }); + number(`JOINT_${joint}`, "MAX_ACCELERATION", { positive: true }); + for (const key of ["HOME", "HOME_OFFSET", "HOME_FINAL_VEL", "HOME_SEARCH_VEL", "HOME_LATCH_VEL", "HOME_SEQUENCE"]) if (sections[`JOINT_${joint}`][key] !== undefined) number(`JOINT_${joint}`, key); + } + const servo = number("EMCMOT", "SERVO_PERIOD", { positive: true }) / 1e9; + const task = number("TASK", "CYCLE_TIME", { positive: true }); + const ticks = task / servo; + if (!Number.isSafeInteger(Math.round(ticks)) || ticks < 1 || Math.abs(ticks - Math.round(ticks)) > 1e-9) throw new Error("trt_configuration_task_servo_ratio"); + if (servo > 60 || task > 10) throw new Error("trt_configuration_cycle_capacity"); + const maxFeed = number("DISPLAY", "MAX_FEED_OVERRIDE", { positive: true }); + if (maxFeed < 1) throw new Error("trt_configuration_feed_override"); + const blendKeys = { enable: "ARC_BLEND_ENABLE", fallback_enable: "ARC_BLEND_FALLBACK_ENABLE", optimization_depth: "ARC_BLEND_OPTIMIZATION_DEPTH", gap_cycles: "ARC_BLEND_GAP_CYCLES", ramp_frequency: "ARC_BLEND_RAMP_FREQ", tangent_kink_ratio: "ARC_BLEND_TANGENT_KINK_RATIO" }; + const blend = Object.fromEntries(Object.entries(blendKeys).map(([key, iniKey]) => [key, number("TRAJ", iniKey, { fallback: WEB_SETTINGS.runtime.planner.blend[key] })])); + if (![0, 1].includes(blend.enable) || ![0, 1].includes(blend.fallback_enable) || !Number.isSafeInteger(blend.optimization_depth) || blend.optimization_depth < 1 || !Number.isSafeInteger(blend.gap_cycles) || blend.gap_cycles < 0 || blend.ramp_frequency <= 0 || blend.tangent_kink_ratio <= 0) throw new Error("trt_configuration_blend"); + const io = { ...Object.fromEntries(Object.entries(WEB_SETTINGS.runtime.io).filter(([key]) => key !== "cycle_time").map(([key, fallback]) => [key, number("WEB_RUNTIME", key.toUpperCase(), { positive: key !== "spindle_tolerance", fallback })])), cycle_time: task }; + if (io.spindle_tolerance < 0) throw new Error("trt_configuration_spindle_tolerance"); + for (const [key, maximum] of Object.entries(IO_CAPACITY)) if (!Number.isSafeInteger(io[key]) || io[key] < 1 || io[key] > maximum) throw new Error(`trt_configuration_io_capacity:${key}`); + const defaultAcceleration = number("TRAJ", "DEFAULT_LINEAR_ACCELERATION", { positive: true }); + const maxAcceleration = number("TRAJ", "MAX_LINEAR_ACCELERATION", { positive: true }); + return { + home_limits: { joint_count: jointCount, kinematics_type: machine.kinematics_type, coordinates, ...jointLimits }, + kinematics: { + type: machine.id, path_mode: "wasm", coordinates, joint_count: jointCount, offsets: geometry.offsets, + ini_hash: parsed.state_hash, + servo_cycle_time: servo, cycle_time: servo, task_cycle_time: task, + max_linear_velocity: number("TRAJ", "MAX_LINEAR_VELOCITY", { positive: true }), + max_acceleration: Math.min(defaultAcceleration, maxAcceleration), + max_linear_acceleration: maxAcceleration, + axis_limits: axisLimits, axis_velocity_limits: velocity, axis_acceleration_limits: acceleration, + max_feed_override: maxFeed, + blend, + io, + switchkins: { 0: "identity", 1: coordinates.toLowerCase(), 2: "userk" }, + }, + }; +} diff --git a/web/workspace/trt-geometry.mjs b/web/workspace/trt-geometry.mjs index 3053fb5..498fa9f 100644 --- a/web/workspace/trt-geometry.mjs +++ b/web/workspace/trt-geometry.mjs @@ -12,7 +12,7 @@ export function readTrtGeometry({ entrypoint, files }) { const machine = machineDefinition(entrypoint); if (!machine) fail("trt_geometry_machine", entrypoint); const parsed = parseIni({ workspace: new WorkspaceRuntime({ files }), entrypoint }); - if (parsed.sections.TRAJ?.COORDINATES?.toUpperCase() !== machine.coordinates || parsed.sections.KINS?.KINEMATICS?.split(/\s+/)[0] !== `${machine.id}-kins`) fail("trt_geometry_machine_mismatch", entrypoint); + if (parsed.sections.TRAJ?.COORDINATES?.replace(/\s/g, "").toUpperCase() !== machine.coordinates || parsed.sections.KINS?.KINEMATICS?.split(/\s+/)[0] !== `${machine.id}-kins`) fail("trt_geometry_machine_mismatch", entrypoint); const commands = parsed.entries.filter((e) => e.section.toUpperCase() === "HAL" && e.key.toUpperCase() === "HALCMD") .map((entry) => ({ entry, record: parseHalLine(entry.value, entry.line, parsed.sections) })); const unsupported = machine.coordinates === "XYZAC" ? "x_offset" : "y_offset"; diff --git a/web/workspace/web-settings.json b/web/workspace/web-settings.json new file mode 100644 index 0000000..18aaad3 --- /dev/null +++ b/web/workspace/web-settings.json @@ -0,0 +1,180 @@ +{ + "assets": { + "axis_images": "linuxcnc/share/axis/images" + }, + "ui": { + "poll_ms": 50, + "jog_cycles": 1, + "run_cycles": 5, + "preview_progress_ms": 100, + "jog_increments": [ + "0.1000", + "0.0100", + "0.0010", + "0.0001" + ], + "initial_view": "p", + "zoom": 1, + "zoom_min": 0.5, + "zoom_max": 2, + "zoom_step": 0.2, + "layers": { + "grid": true, + "tool": true, + "path": true, + "live": true, + "limits": false, + "velocity": false, + "offsets": false, + "extents": false + }, + "motion_scale_min": 0.06, + "override_debounce_ms": 80 + }, + "runtime": { + "planner": { + "cycle_time": 0.001, + "velocity_limit": 35, + "acceleration_limit": 300, + "max_feed_override": 2, + "compatibility_jerk_limit": 1000000000, + "blend": { + "enable": 1, + "fallback_enable": 0, + "optimization_depth": 50, + "gap_cycles": 4, + "ramp_frequency": 100, + "tangent_kink_ratio": 0.1 + } + }, + "io": { + "spindle_count": 2, + "tool_capacity": 1001, + "cycle_time": 0.01, + "spindle_acceleration": 600, + "spindle_tolerance": 5, + "encoder_count": 2 + }, + "compatibility": { + "step_fraction": 0.25, + "max_position": 1000000000.0, + "max_speed": 1000000.0, + "cycle_time": 0.001, + "tripod_base": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ], + [ + 5, + 7.071 + ] + ], + "linear_delta": { + "radius": 130.25, + "rod": 269 + }, + "rotary_delta": { + "platform": 10, + "thigh": 10, + "shin": 14, + "foot": 6 + } + } + }, + "viewer": { + "mm_scale": 0.01, + "pixel_ratio_limit": 2, + "camera": { + "fov": 36, + "near": 0.02, + "far": 150, + "min_distance": 0.25, + "max_distance": 65, + "damping": 0.12, + "fit_margin": 1.12, + "pan_fraction": 0.035, + "zoom_in": 0.8, + "zoom_out": 1.25, + "rotate_degrees": 15 + }, + "colors": { + "background": "#252b2c", + "path": "#ffff00", + "live": "#ff9a55", + "rapid": "#5c9d9a", + "limits": "#dd7566", + "extents": "#e6c56b" + }, + "models": { + "XYZAC": { + "datum_z_mm": 200 + }, + "XYZBC": { + "datum_z_mm": 150 + } + }, + "materials": { + "enamel": { + "color": "#d9e1e1", + "roughness": 0.32, + "metalness": 0.25 + }, + "teal": { + "color": "#167f79", + "roughness": 0.3, + "metalness": 0.3 + }, + "dark": { + "color": "#303638", + "roughness": 0.42, + "metalness": 0.35 + }, + "steel": { + "color": "#b2bdc4", + "roughness": 0.27, + "metalness": 0.85 + }, + "polished": { + "color": "#e2e8ec", + "roughness": 0.19, + "metalness": 0.9 + }, + "recess": { + "color": "#111718", + "roughness": 0.66, + "metalness": 0.2 + }, + "rubber": { + "color": "#222729", + "roughness": 0.88 + }, + "carbide": { + "color": "#b9aa79", + "roughness": 0.29, + "metalness": 0.8 + }, + "warning": { + "color": "#e7bd48", + "roughness": 0.48, + "metalness": 0.12 + }, + "lamp": { + "color": "#e2f8ff", + "emissive": "#d4f2ff", + "emissiveIntensity": 1.5 + } + }, + "toolpath_colors": { + "background": "#050a0e", + "completed": "#d8dfe3", + "rapid": "#a85d5d", + "active": "#00ffff", + "live": "#ff6b4a" + } + } +} diff --git a/web/workspace/web-settings.mjs b/web/workspace/web-settings.mjs new file mode 100644 index 0000000..ecb9870 --- /dev/null +++ b/web/workspace/web-settings.mjs @@ -0,0 +1,34 @@ +import values from "./web-settings.json" with { type: "json" }; + +function freeze(value) { + if (value && typeof value === "object") { Object.values(value).forEach(freeze); Object.freeze(value); } + return value; +} +function finitePositive(value, name) { + if (!Number.isFinite(value) || value <= 0) throw new Error(`web_settings_invalid:${name}`); +} +for (const [name, value] of Object.entries(values.runtime.planner)) if (name !== "blend") finitePositive(value, `planner.${name}`); +for (const [name, value] of Object.entries(values.runtime.io)) { + if (name === "spindle_tolerance") { + if (!Number.isFinite(value) || value < 0) throw new Error("web_settings_invalid:io.spindle_tolerance"); + } else finitePositive(value, `io.${name}`); +} +for (const [name, value] of Object.entries(values.viewer.camera)) finitePositive(value, `camera.${name}`); +finitePositive(values.viewer.mm_scale, "mm_scale"); +finitePositive(values.viewer.pixel_ratio_limit, "pixel_ratio_limit"); +for (const key of ["poll_ms", "jog_cycles", "run_cycles", "preview_progress_ms", "zoom", "zoom_min", "zoom_max", "zoom_step"]) finitePositive(values.ui[key], key); +finitePositive(values.ui.override_debounce_ms, "override_debounce_ms"); +if (!(values.ui.motion_scale_min > 0 && values.ui.motion_scale_min <= 1)) throw new Error("web_settings_invalid:motion_scale_min"); +if (!values.ui.jog_increments.every((value) => Number.isFinite(Number(value)) && Number(value) > 0)) throw new Error("web_settings_invalid:jog_increments"); +if (values.ui.zoom_min > values.ui.zoom_max || values.ui.zoom < values.ui.zoom_min || values.ui.zoom > values.ui.zoom_max) throw new Error("web_settings_invalid:zoom_range"); +if (!["x", "y", "y2", "z", "z2", "p"].includes(values.ui.initial_view)) throw new Error("web_settings_invalid:initial_view"); +for (const key of ["jog_cycles", "run_cycles"]) if (!Number.isSafeInteger(values.ui[key])) throw new Error(`web_settings_invalid:${key}`); +if (values.viewer.camera.near >= values.viewer.camera.far || values.viewer.camera.min_distance >= values.viewer.camera.max_distance) throw new Error("web_settings_invalid:camera_range"); +for (const model of Object.values(values.viewer.models)) finitePositive(model.datum_z_mm, "model_datum"); +if (values.runtime.planner.max_feed_override < 1) throw new Error("web_settings_invalid:feed_limit"); +for (const key of ["spindle_count", "tool_capacity", "encoder_count"]) if (!Number.isSafeInteger(values.runtime.io[key])) throw new Error(`web_settings_invalid:io.${key}`); +export const WEB_SETTINGS = freeze(values); + +export function axisImagePath(name, base = ".") { + return `${base.replace(/\/$/, "")}/${WEB_SETTINGS.assets.axis_images}/${name}`; +}