Extend simulation runtime coverage and kinematics support
This commit is contained in:
@@ -60,9 +60,9 @@
|
||||
"runtime_pin_rule": "Changing mirror URL without changing bytes does not change context; changing bytes or version creates a new context_id."
|
||||
},
|
||||
"inventory_counts": {
|
||||
"total": 8035,
|
||||
"source_index": 8035,
|
||||
"manifest_seed": 8035,
|
||||
"total": 8053,
|
||||
"source_index": 8053,
|
||||
"manifest_seed": 8053,
|
||||
"document_capabilities": 2868,
|
||||
"source_symbols": 23983,
|
||||
"config_resources": 16093,
|
||||
@@ -75,20 +75,23 @@
|
||||
"kinematics_test_files": 6,
|
||||
"kinematics_graph_entries": 377,
|
||||
"kinematics_graph_edges": 2784,
|
||||
"kinematics_oracle_samples": 11
|
||||
"kinematics_oracle_samples": 11,
|
||||
"configuration_regression_entries": 264,
|
||||
"configuration_regression_entrypoints": 233,
|
||||
"configuration_regression_resources": 16093
|
||||
},
|
||||
"source_index_sha256": "b0719a9cf1031f7305e29075e645f21aa3c3630401e284825565a8b206c83ce4",
|
||||
"manifest_seed_sha256": "70cd247690616ca260b111fb737c1277c5336f845acc5ac27160eaf87a7c9f93",
|
||||
"source_index_sha256": "2a78438ab4a685476cc57f10d15e2ca3ee688892a7696081a135018638031404",
|
||||
"manifest_seed_sha256": "5d353f5f11053b520acdb0e401d957e3cfb9e47be1d22b6a9848cc7ec1bf1416",
|
||||
"document_capabilities_sha256": "fa5140bd4995224406381e4190e76bf81f64bd24dbf93a2fd10421c45aee4316",
|
||||
"source_symbols_sha256": "5fc72e6a268d2220b6335f7f6834c925f13c5c35461fa399a400e6621958875a",
|
||||
"config_resources_sha256": "d4d02cb13f2f9cb58404630d4de8ac0226e02075166fe30f2b03a6abca4be5bc",
|
||||
"test_entries_sha256": "86449838257a7aab7c9219e641a14d4493208b35d8c0cc092d1fa149352c7c37",
|
||||
"normalized_sources_sha256": "ae800441cafb54bd7fbba872bf3b4af361041a14b9dee20fdc210204a433aff8",
|
||||
"workspace_recovery_report_sha256": "88dd1d851d487730edc6beb034a934ba1c19346312ed3cd4d4893c7259c431de",
|
||||
"workspace_recovery_report_sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
|
||||
"runtime_mapping_report_sha256": "f4be10828fda8201ff9e69de95bc68e5def42fc7206a851b37474c21b4da0a17",
|
||||
"python_runtime_manifest_sha256": "340faa6ef5bedcaeb0583f463e6b89cb442ddf1d83d7e6ee8d96cee438fe4213",
|
||||
"python_runtime_report_sha256": "be531c8cb5af55f9527b6b6b3955ff908a748fae0e37952fa699aecf1a44b399",
|
||||
"hal_runtime_report_sha256": "85f79be090c5fceb3ee633da0fff57c908604938592c9b2cf6a1b2151b588e6b",
|
||||
"hal_runtime_report_sha256": "5fc23403bd41c5b6a683d39df98395c6ec64fc8497715109da6e01d359a20dbf",
|
||||
"posemath_runtime_report_sha256": "e9680e09dab67f85736f7f6a3bb6bb4456f5f4c93428f72ee3dc94ed0456face",
|
||||
"interpreter_runtime_report_sha256": "1ffed00d9f0affbbc55e73ab4dca12b0446af11ea7861dfb7597ebe5e1a6cda2",
|
||||
"motion_runtime_report_sha256": "fb2d36130bf3168bea9b13fe08cc2225d72190a7aa9c99a0f0db7f148ac171df",
|
||||
@@ -96,14 +99,14 @@
|
||||
"kinematics_source_closure_report_sha256": "d3af5e19e38d8dc2640323dcf755d0323e871815087b9eece9d76544b0b0cfa6",
|
||||
"kinematics_config_graph_report_sha256": "1c7ffe85730b23ee303beae95c7c565506e7c8f10c7bf68d23b980be22b46521",
|
||||
"kinematics_oracle_samples_sha256": "df32ade49e3c3beb49e52d26200e37766dd04c5bee5dba4b1c87695439bf87bd",
|
||||
"kinematics_oracle_report_sha256": "66c98703195c69f165a932aadc29015ab748aa005e8bfc223cb04d9d2f65a1af",
|
||||
"kinematics_oracle_report_sha256": "4dc09ea52d1cc32cd3c4f006b1c2f880e54ca147e6d4f651cee4b67626f56d05",
|
||||
"kinematics_golden_schema_sha256": "8d6abe21ad350bb5a5ddc38d8fd9044ed2fa622f4331804a85cad55eb5a30a63",
|
||||
"kinematics_abi_contract_sha256": "3177c4d28200c5136981d28489ac4578a84360f9688ee8614e23af8d7526b50b",
|
||||
"kinematics_mapping_contract_sha256": "49bd93c716bd5b5eb6064f75ea2b8ba26dfa819c6166c22c5375ebeba927dd91",
|
||||
"kinematics_numeric_policy_sha256": "b78c2b464771083e279689d33d65169e99ef01b08e347a0e75b7486d8d2d0134",
|
||||
"trivkins_integration_report_sha256": "98c2a61c2fa3503782494768fcb7b0e5a13e998a519ce9b3629549194fa29ef8",
|
||||
"coupled_kinematics_report_sha256": "696e62679095e831f86b24a34ae4daba69bcab81da47f92e7c4ee680dfcd70fd",
|
||||
"corexy_hal_report_sha256": "e1e5c03f5d2dc30647b19a59a59ab625d440eb90a522d8e2366050761938064b",
|
||||
"corexy_hal_report_sha256": "190b9a8276fe361f211c5f053aeb84c151755040ec62d74383979181bdb76462",
|
||||
"fiveaxis_kinematics_report_sha256": "5b02508761b7c1d7731e1bf4210563bb289fb8f9860f98ab1aa0a0d316f77369",
|
||||
"trt_kinematics_report_sha256": "2f655742b5f9de09de903964a4cd85de0391d06ef1e2a35da0d6854f26649b51",
|
||||
"robot_kinematics_report_sha256": "e3c27bc17b74206038af29fc8f5cc5bc39aca9af161b4f1d2de3be3f9905e5c3",
|
||||
@@ -115,5 +118,14 @@
|
||||
"golden_schema_sha256": "5513535b9480d7100ef3ef59ac933c4040bd7198dc419693a7ee68098171c423",
|
||||
"native_runner_report_sha256": "6f2105f154d12b99f4e1cd4af3cdb5df4a462f3665f1447d5959384be5d4fae2",
|
||||
"generated_by": "inventory-and-coverage-generators",
|
||||
"generated_at": "2026-08-20T15:52:00+08:00"
|
||||
"generated_at": "2026-08-21T00:00:00+08:00",
|
||||
"pentapod_kinematics_report_sha256": "a4a88192c855080fd030e83920190c9423f81f190c54dd42e087d328f80b411a",
|
||||
"switchkins_kinematics_report_sha256": "e8a1e8632c93114ff0a3533bc6a6d99b2c915c053bbd5ef15371c92c112c28de",
|
||||
"userkins_kinematics_report_sha256": "9dd0ef622d00ae4aebfe6468f5da3379e947fe17f012ded60938709f3f7d5cd6",
|
||||
"kinematics_registry_report_sha256": "36d46128038bf0dbd73e1b57194c388d8d433e088e1ed67922033f6d9c0cf900",
|
||||
"kinematics_motion_report_sha256": "e4c3213936d025847740dc45078bdcd51d9a1ede8250ef70b6466003fa7bbcaa",
|
||||
"kinematics_feedback_report_sha256": "eca4508caac38cb0cd5a348638a8fc0af80f1edc5876085c5f80dca7795c564d",
|
||||
"kinematics_home_limit_report_sha256": "a493bd646f55e5c55ba2251a0b12d87f485b9701f2a33a30d4dc2d0f15166702",
|
||||
"io_runtime_report_sha256": "a5bf67b642b7812796ab0a168d601bb24eda8ceec2418ffc02fc718fa642768e",
|
||||
"configuration_regression_report_sha256": "40b12bba25957c667988c704c51611bcdbd2608849d031fbb137708efaedeb0b"
|
||||
}
|
||||
|
||||
15814
coverage/configuration-regression.report.json
Normal file
15814
coverage/configuration-regression.report.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"report_sha256": "765c8318c48d7570773d22394c625c0d6c479fdf2e98c62fa48296b61e99c238",
|
||||
"report_sha256": "5bc8a261e615f8ef62504a1c6ccf1744c4c3bdd735429cf524226da2f7de6633",
|
||||
"schema": "corexy-hal-report.v1",
|
||||
"source_records": {
|
||||
"component": {
|
||||
@@ -90,7 +90,7 @@
|
||||
},
|
||||
"hal_header": {
|
||||
"path": "wasm-core/include/sim_hal.h",
|
||||
"sha256": "f0b25fda1c267ed8078469c9966fa3b09721eba77c575affea77d489437a3e49"
|
||||
"sha256": "3dd7230316e557d1a129a9a80a26a109b837a318b092657b8bee0d53a5e57db9"
|
||||
},
|
||||
"hal_source": {
|
||||
"path": "wasm-core/src/sim_hal.c",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"schema": "hal-runtime-report.v1",
|
||||
"status": "passed",
|
||||
"pin_capacity": 256,
|
||||
"supported_types": ["BIT", "S32", "U32", "FLOAT"],
|
||||
"fixed_tick_scheduler": true,
|
||||
"max_period_ticks": 1000000,
|
||||
|
||||
287
coverage/io-runtime.report.json
Normal file
287
coverage/io-runtime.report.json
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"boundaries": {
|
||||
"estop_command": -904,
|
||||
"estop_recovery_before_clear": -904,
|
||||
"fault_clear_while_asserted": -905,
|
||||
"invalid_pin_type": -304,
|
||||
"probe_state": 2,
|
||||
"probe_trigger": 0,
|
||||
"recovery": 0,
|
||||
"spindle_limit": -906,
|
||||
"state_hash_stable": true,
|
||||
"tool_change_while_spinning": -910
|
||||
},
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_io_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_io_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"spindle acceleration/at-speed",
|
||||
"spindle orient",
|
||||
"tool prepare/change",
|
||||
"tool abort/fault/recovery",
|
||||
"probe pose latch",
|
||||
"estop and recovery"
|
||||
],
|
||||
"reason": "locked LinuxCNC source has no linuxcnc/unit_tests/kinematics oracle root and no browser IO oracle",
|
||||
"status": "blocked"
|
||||
},
|
||||
"pipeline": {
|
||||
"kinematics_association": "probe latch consumes the motion feedback pose; tool change requires stopped spindle; estop cancels motion-facing IO",
|
||||
"probe": [
|
||||
"arm",
|
||||
"input_edge",
|
||||
"forward_pose_latch",
|
||||
"trip_or_disarm"
|
||||
],
|
||||
"spindle": [
|
||||
"command",
|
||||
"inhibit_or_fault_gate",
|
||||
"acceleration_limit",
|
||||
"at_speed_and_brake",
|
||||
"revs_and_index"
|
||||
],
|
||||
"tool": [
|
||||
"prepare",
|
||||
"prepared_ack",
|
||||
"spindle_stopped_gate",
|
||||
"change",
|
||||
"changed_ack",
|
||||
"abort_or_fault"
|
||||
]
|
||||
},
|
||||
"registration": {
|
||||
"fixed_tick": true,
|
||||
"hal_pins": [
|
||||
"spindle.0.*",
|
||||
"spindle.1.*",
|
||||
"iocontrol.0.tool-*",
|
||||
"motion.probe-input"
|
||||
],
|
||||
"scheduler": "sim-io-servo",
|
||||
"spindle_capacity": 8,
|
||||
"tool_pocket_capacity": 1001
|
||||
},
|
||||
"report_sha256": "82a4b09badb2fcbcd248a0624b7b3a4e70c697f4040bd9250ac4831799c63af6",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"canonical": {
|
||||
"estop": 0,
|
||||
"fault": 0,
|
||||
"fault_reason": 0,
|
||||
"last_error": 0,
|
||||
"probe": {
|
||||
"input": 1,
|
||||
"position": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"state": 2,
|
||||
"tick": 40,
|
||||
"tripped": 1
|
||||
},
|
||||
"ready": 1,
|
||||
"recovery_epoch": 1,
|
||||
"spindles": [
|
||||
{
|
||||
"at_speed": 1,
|
||||
"brake": 1,
|
||||
"command_rpm": 0,
|
||||
"forward": 0,
|
||||
"reverse": 0,
|
||||
"speed_rpm": 0,
|
||||
"tick": 40
|
||||
},
|
||||
{
|
||||
"at_speed": 1,
|
||||
"brake": 1,
|
||||
"command_rpm": 0,
|
||||
"forward": 0,
|
||||
"reverse": 0,
|
||||
"speed_rpm": 0,
|
||||
"tick": 40
|
||||
}
|
||||
],
|
||||
"tick": 40,
|
||||
"tool": {
|
||||
"changed": 0,
|
||||
"current": 7,
|
||||
"prepare": 0,
|
||||
"prepared": 0,
|
||||
"prepared_pocket": 3,
|
||||
"prepared_tool": 7,
|
||||
"state": 0
|
||||
}
|
||||
},
|
||||
"estop": {
|
||||
"command": -904,
|
||||
"recover": 0,
|
||||
"recover_before_clear": -904
|
||||
},
|
||||
"pin": {
|
||||
"count": 67,
|
||||
"functions": 1,
|
||||
"invalid_type": -304
|
||||
},
|
||||
"probe": {
|
||||
"position": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"state": 2,
|
||||
"status": 0
|
||||
},
|
||||
"repeat_hash": 3758173594264074653,
|
||||
"schema": "io-samples.v1",
|
||||
"spindle": {
|
||||
"at_speed": 1,
|
||||
"limit": -906,
|
||||
"speed": 0.0,
|
||||
"step": 0,
|
||||
"tool_busy": -910
|
||||
},
|
||||
"state_hash": 3758173594264074653,
|
||||
"tool": {
|
||||
"current": 7,
|
||||
"fault_clear": -905,
|
||||
"state": 0
|
||||
}
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"canonical": {
|
||||
"estop": 0,
|
||||
"fault": 0,
|
||||
"fault_reason": 0,
|
||||
"last_error": 0,
|
||||
"probe": {
|
||||
"input": 1,
|
||||
"position": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"state": 2,
|
||||
"tick": 40,
|
||||
"tripped": 1
|
||||
},
|
||||
"ready": 1,
|
||||
"recovery_epoch": 1,
|
||||
"spindles": [
|
||||
{
|
||||
"at_speed": 1,
|
||||
"brake": 1,
|
||||
"command_rpm": 0,
|
||||
"forward": 0,
|
||||
"reverse": 0,
|
||||
"speed_rpm": 0,
|
||||
"tick": 40
|
||||
},
|
||||
{
|
||||
"at_speed": 1,
|
||||
"brake": 1,
|
||||
"command_rpm": 0,
|
||||
"forward": 0,
|
||||
"reverse": 0,
|
||||
"speed_rpm": 0,
|
||||
"tick": 40
|
||||
}
|
||||
],
|
||||
"tick": 40,
|
||||
"tool": {
|
||||
"changed": 0,
|
||||
"current": 7,
|
||||
"prepare": 0,
|
||||
"prepared": 0,
|
||||
"prepared_pocket": 3,
|
||||
"prepared_tool": 7,
|
||||
"state": 0
|
||||
}
|
||||
},
|
||||
"estop": {
|
||||
"command": -904,
|
||||
"recover": 0,
|
||||
"recover_before_clear": -904
|
||||
},
|
||||
"pin": {
|
||||
"count": 67,
|
||||
"functions": 1,
|
||||
"invalid_type": -304
|
||||
},
|
||||
"probe": {
|
||||
"position": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"state": 2,
|
||||
"status": 0
|
||||
},
|
||||
"repeat_hash": 3758173594264074653,
|
||||
"schema": "io-samples.v1",
|
||||
"spindle": {
|
||||
"at_speed": 1,
|
||||
"limit": -906,
|
||||
"speed": 0.0,
|
||||
"step": 0,
|
||||
"tool_busy": -910
|
||||
},
|
||||
"state_hash": 3758173594264074653,
|
||||
"tool": {
|
||||
"current": 7,
|
||||
"fault_clear": -905,
|
||||
"state": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema": "io-runtime-report.v1",
|
||||
"source_records": {
|
||||
"hal_header": {
|
||||
"path": "wasm-core/include/sim_hal.h",
|
||||
"sha256": "3dd7230316e557d1a129a9a80a26a109b837a318b092657b8bee0d53a5e57db9"
|
||||
},
|
||||
"hal_source": {
|
||||
"path": "wasm-core/src/sim_hal.c",
|
||||
"sha256": "76a057064546a8ee9ba1098952755a4cdc56e6e714f2761b8a1826898d3ae621"
|
||||
},
|
||||
"io_header": {
|
||||
"path": "wasm-core/include/sim_io.h",
|
||||
"sha256": "d84e9ab0bcf9ff138f8c8a57b28337605d731ff796577a8ab748d708f97daf9b"
|
||||
},
|
||||
"io_source": {
|
||||
"path": "wasm-core/src/sim_io.c",
|
||||
"sha256": "2ceacdb168a1f6dddb36761b93f62e4c39b3a6249c0a91a740ecf924dce217b3"
|
||||
},
|
||||
"linuxcnc_control": {
|
||||
"path": "linuxcnc/src/emc/motion/control.c",
|
||||
"sha256": "fa1b14689e2e4493c02d0939f982278366d4c1888da62e172cec030904c82c50"
|
||||
},
|
||||
"linuxcnc_iocontrol": {
|
||||
"path": "linuxcnc/src/emc/iotask/ioControl.cc",
|
||||
"sha256": "f2f7d4f8a0e77815ca1ffb1ba2699b9d2ffa4297c8ec7718aadd197fe6ca06a9"
|
||||
},
|
||||
"linuxcnc_iocontrol_v2": {
|
||||
"path": "linuxcnc/src/emc/iotask/ioControl_v2.cc",
|
||||
"sha256": "a33bcddcdcbcda843166bd547f7c7a5dfc7f9436426c5354f7124f42f6f75e5c"
|
||||
},
|
||||
"linuxcnc_motion": {
|
||||
"path": "linuxcnc/src/emc/motion/motion.c",
|
||||
"sha256": "c3d1404150b56cfb03ab35ecdf64694d60a6d37117d0617a8464d548133e79d3"
|
||||
},
|
||||
"linuxcnc_sim_spindle": {
|
||||
"path": "linuxcnc/src/hal/components/sim_spindle.comp",
|
||||
"sha256": "a1ce30a051490c5f40adc968a693930464dfbe59c9df1848c296689179b11365"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_io_test.c",
|
||||
"sha256": "46923ff49c55576692e660d1af37e43bd6c1aff184ae2a2d5ca9201dd969ccfe"
|
||||
}
|
||||
},
|
||||
"status": "partial"
|
||||
}
|
||||
153
coverage/kinematics-feedback.report.json
Normal file
153
coverage/kinematics-feedback.report.json
Normal file
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"boundaries": {
|
||||
"failed_feedback_valid": 0,
|
||||
"forward_failure": -823,
|
||||
"forward_failure_kinematics": -706,
|
||||
"joint_count": -821,
|
||||
"nonfinite": -822,
|
||||
"stale_read": -824,
|
||||
"state_hash_stable": true
|
||||
},
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZ",
|
||||
"id": "trivkins-xyz-feedback",
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_kinematics_feedback_test.c"
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZBC",
|
||||
"id": "inverse-only-feedback-rejection",
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_kinematics_feedback_test.c",
|
||||
"purpose": "forward feedback rejects unsupported mode without publishing a pose"
|
||||
}
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_kinematics_feedback_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_kinematics_feedback_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"nominal FK feedback",
|
||||
"stale read",
|
||||
"nonfinite feedback",
|
||||
"joint count mismatch",
|
||||
"unsupported forward",
|
||||
"state hash"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"pipeline": {
|
||||
"last_valid_pose_policy": "retain on error",
|
||||
"order": [
|
||||
"actual_joint_feedback",
|
||||
"forward_kinematics",
|
||||
"pose_flags_commit"
|
||||
],
|
||||
"state_fields": [
|
||||
"pose",
|
||||
"joints",
|
||||
"forward_flags",
|
||||
"inverse_flags",
|
||||
"tick",
|
||||
"valid",
|
||||
"last_error",
|
||||
"kinematics_error"
|
||||
]
|
||||
},
|
||||
"report_sha256": "62da08a0a3d8306a6e5c3b8d5e66b2c6f268a5896cfe26d6a6671c3e3df0e822",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"count_error": -821,
|
||||
"nominal": {
|
||||
"flags": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"pose": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"repeat_hash": 13054934758762781376,
|
||||
"state_hash": 13054934758762781376,
|
||||
"status": 0,
|
||||
"tick": 1,
|
||||
"valid": 1
|
||||
},
|
||||
"nonfinite": -822,
|
||||
"schema": "kinematics-feedback-samples.v1",
|
||||
"singular": {
|
||||
"kinematics_error": -706,
|
||||
"status": -823,
|
||||
"valid": 0
|
||||
},
|
||||
"stale_read": -824
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"count_error": -821,
|
||||
"nominal": {
|
||||
"flags": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"pose": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"repeat_hash": 13054934758762781376,
|
||||
"state_hash": 13054934758762781376,
|
||||
"status": 0,
|
||||
"tick": 1,
|
||||
"valid": 1
|
||||
},
|
||||
"nonfinite": -822,
|
||||
"schema": "kinematics-feedback-samples.v1",
|
||||
"singular": {
|
||||
"kinematics_error": -706,
|
||||
"status": -823,
|
||||
"valid": 0
|
||||
},
|
||||
"stale_read": -824
|
||||
}
|
||||
},
|
||||
"schema": "kinematics-feedback-report.v1",
|
||||
"source_records": {
|
||||
"feedback_header": {
|
||||
"path": "wasm-core/include/sim_kinematics_feedback.h",
|
||||
"sha256": "6d70be324a99d58f07c5a3c3c888b92cd1959cba250faf3c12a4e32c5ef1cf05"
|
||||
},
|
||||
"feedback_source": {
|
||||
"path": "wasm-core/src/sim_kinematics_feedback.c",
|
||||
"sha256": "c690fc420d58b5e60958e4d0745b9a6bca678c5283eb9e74c50a11ca61faa93a"
|
||||
},
|
||||
"kinematics_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"kinematics_source": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"linuxcnc_control": {
|
||||
"path": "linuxcnc/src/emc/motion/control.c",
|
||||
"sha256": "fa1b14689e2e4493c02d0939f982278366d4c1888da62e172cec030904c82c50"
|
||||
},
|
||||
"linuxcnc_motion": {
|
||||
"path": "linuxcnc/src/emc/motion/motion.c",
|
||||
"sha256": "c3d1404150b56cfb03ab35ecdf64694d60a6d37117d0617a8464d548133e79d3"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_kinematics_feedback_test.c",
|
||||
"sha256": "1f1830b90a6d0662c02b7494f0b1848c518c13f3cf70905c5ba6a6eba62e7762"
|
||||
}
|
||||
},
|
||||
"status": "partial"
|
||||
}
|
||||
148
coverage/kinematics-home-limit.report.json
Normal file
148
coverage/kinematics-home-limit.report.json
Normal file
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"boundaries": {
|
||||
"home": 0,
|
||||
"joint_count": -841,
|
||||
"limit": -843,
|
||||
"nonfinite": -845,
|
||||
"retains_valid_pose": true,
|
||||
"sequence": -846,
|
||||
"state_hash_stable": true,
|
||||
"unhomed": -842
|
||||
},
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZ",
|
||||
"id": "trivkins-xyz-home-limits",
|
||||
"kind": "derived",
|
||||
"limits": "[-5,5] per axis",
|
||||
"path": "wasm-core/tests/sim_kinematics_home_limit_test.c"
|
||||
}
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_kinematics_home_limit_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_kinematics_home_limit_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"unhomed check",
|
||||
"home sequence",
|
||||
"kinematics home",
|
||||
"joint limit",
|
||||
"nonfinite joint",
|
||||
"joint count",
|
||||
"state hash"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"pipeline": {
|
||||
"failed_limit_policy": "retain last valid pose and joints",
|
||||
"order": [
|
||||
"home_sequence",
|
||||
"kinematics_home",
|
||||
"limit_check",
|
||||
"forward_feedback"
|
||||
],
|
||||
"state_fields": [
|
||||
"homed",
|
||||
"home_sequence",
|
||||
"tick",
|
||||
"pose",
|
||||
"joints",
|
||||
"last_error",
|
||||
"limit_error",
|
||||
"kinematics_error"
|
||||
]
|
||||
},
|
||||
"report_sha256": "1e625b140fed62d996381ce18a565300cc39d22ba54d10a1f8df436cf69a9797",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"before_home": -842,
|
||||
"count_status": -841,
|
||||
"home_status": 0,
|
||||
"limit_status": -843,
|
||||
"nonfinite_status": -845,
|
||||
"schema": "kinematics-home-limit-samples.v1",
|
||||
"sequence_error": -846,
|
||||
"state": {
|
||||
"hash": 14198619819498413028,
|
||||
"homed": 1,
|
||||
"joints": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"pose": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"repeat_hash": 14198619819498413028,
|
||||
"sequence": 7,
|
||||
"tick": 2
|
||||
}
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"before_home": -842,
|
||||
"count_status": -841,
|
||||
"home_status": 0,
|
||||
"limit_status": -843,
|
||||
"nonfinite_status": -845,
|
||||
"schema": "kinematics-home-limit-samples.v1",
|
||||
"sequence_error": -846,
|
||||
"state": {
|
||||
"hash": 14198619819498413028,
|
||||
"homed": 1,
|
||||
"joints": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"pose": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"repeat_hash": 14198619819498413028,
|
||||
"sequence": 7,
|
||||
"tick": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema": "kinematics-home-limit-report.v1",
|
||||
"source_records": {
|
||||
"home_header": {
|
||||
"path": "wasm-core/include/sim_kinematics_home_limit.h",
|
||||
"sha256": "ad4b4fc84efcc9a2dc6555d7ed69ab613eee21db7fef0d037d0a4ca1063309e1"
|
||||
},
|
||||
"home_source": {
|
||||
"path": "wasm-core/src/sim_kinematics_home_limit.c",
|
||||
"sha256": "7f59d1722cd2ca54f346168b4a13d9e8377bb6035c9f5866c3a3fbb7115bb996"
|
||||
},
|
||||
"kinematics_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"kinematics_source": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"linuxcnc_homemod": {
|
||||
"path": "linuxcnc/src/emc/motion/homemod.c",
|
||||
"sha256": "df2e24e8d6c48d47448971b6e69471910a435d207a9d45f857a968aebf14ea5c"
|
||||
},
|
||||
"linuxcnc_homing": {
|
||||
"path": "linuxcnc/src/emc/motion/homing.c",
|
||||
"sha256": "ee73d38f2feb8d5523b9a733cb17d635d3904667aaa219f8a02d7fbffee1ba39"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_kinematics_home_limit_test.c",
|
||||
"sha256": "94004ef9e4182cfa04c9a458d750d84b12250a625406ad29ce06ae45136f909d"
|
||||
}
|
||||
},
|
||||
"status": "partial"
|
||||
}
|
||||
171
coverage/kinematics-motion.report.json
Normal file
171
coverage/kinematics-motion.report.json
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"boundaries": {
|
||||
"capacity": -802,
|
||||
"capacity_planner_rollback": true,
|
||||
"inverse_error": -704,
|
||||
"inverse_failure": -804,
|
||||
"inverse_planner_rollback": true,
|
||||
"nonfinite": -805,
|
||||
"pause": -801,
|
||||
"state_hash_stable": true
|
||||
},
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZ",
|
||||
"id": "trivkins-xyz-fixed-tick",
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_kinematics_motion_test.c",
|
||||
"purpose": "deterministic TP to inverse to joint sample"
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZBC",
|
||||
"id": "xyzbc-singular-rollback",
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_kinematics_motion_test.c",
|
||||
"purpose": "B=90 inverse failure leaves planner untouched"
|
||||
}
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_kinematics_motion_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_kinematics_motion_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"TP to IK fixed tick",
|
||||
"joint queue capacity",
|
||||
"pause/resume",
|
||||
"singular IK rollback",
|
||||
"nonfinite segment"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"pipeline": {
|
||||
"joint_queue_capacity": 64,
|
||||
"order": [
|
||||
"fixed_tick",
|
||||
"trajectory_planner",
|
||||
"kinematics_inverse",
|
||||
"atomic_joint_queue"
|
||||
],
|
||||
"planner_queue_capacity": 32,
|
||||
"transaction": "snapshot planner and kinematics; rollback on IK, planner, nonfinite, or capacity failure"
|
||||
},
|
||||
"report_sha256": "eb01772b430dfe5aeab6544916c42830f013a0913c7a62ab5f59df13de8c1010",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"capacity": {
|
||||
"depth": 64,
|
||||
"planner_after": 10899630745385757838,
|
||||
"planner_before": 10899630745385757838,
|
||||
"state_hash": 13283141982606912976,
|
||||
"status": -802
|
||||
},
|
||||
"inverse_failure": {
|
||||
"depth": 0,
|
||||
"kinematics_error": -704,
|
||||
"planner_after": 14629876896415603154,
|
||||
"planner_before": 14629876896415603154,
|
||||
"status": -804
|
||||
},
|
||||
"nominal": {
|
||||
"joints": [
|
||||
0.1,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"pause_status": -801,
|
||||
"pose": [
|
||||
0.1,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"repeat_hash": 405287727652118863,
|
||||
"resume_status": 0,
|
||||
"state_hash": 405287727652118863,
|
||||
"tick": 1
|
||||
},
|
||||
"nonfinite_status": -805,
|
||||
"schema": "kinematics-motion-samples.v1"
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"capacity": {
|
||||
"depth": 64,
|
||||
"planner_after": 10899630745385757838,
|
||||
"planner_before": 10899630745385757838,
|
||||
"state_hash": 13283141982606912976,
|
||||
"status": -802
|
||||
},
|
||||
"inverse_failure": {
|
||||
"depth": 0,
|
||||
"kinematics_error": -704,
|
||||
"planner_after": 14629876896415603154,
|
||||
"planner_before": 14629876896415603154,
|
||||
"status": -804
|
||||
},
|
||||
"nominal": {
|
||||
"joints": [
|
||||
0.1,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"pause_status": -801,
|
||||
"pose": [
|
||||
0.1,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"repeat_hash": 405287727652118863,
|
||||
"resume_status": 0,
|
||||
"state_hash": 405287727652118863,
|
||||
"tick": 1
|
||||
},
|
||||
"nonfinite_status": -805,
|
||||
"schema": "kinematics-motion-samples.v1"
|
||||
}
|
||||
},
|
||||
"schema": "kinematics-motion-report.v1",
|
||||
"source_records": {
|
||||
"kinematics_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"kinematics_motion_header": {
|
||||
"path": "wasm-core/include/sim_kinematics_motion.h",
|
||||
"sha256": "86b0f6b889875b03bc3fea8787443cb72e77561e85c7114b8dd43eeba8c69350"
|
||||
},
|
||||
"kinematics_motion_source": {
|
||||
"path": "wasm-core/src/sim_kinematics_motion.c",
|
||||
"sha256": "1e9bd5404acfe7ab20eca5b3bb76111bf43c442feee0dc2d4c3f9249ba916a88"
|
||||
},
|
||||
"kinematics_source": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"linuxcnc_control": {
|
||||
"path": "linuxcnc/src/emc/motion/control.c",
|
||||
"sha256": "fa1b14689e2e4493c02d0939f982278366d4c1888da62e172cec030904c82c50"
|
||||
},
|
||||
"linuxcnc_motion": {
|
||||
"path": "linuxcnc/src/emc/motion/motion.c",
|
||||
"sha256": "c3d1404150b56cfb03ab35ecdf64694d60a6d37117d0617a8464d548133e79d3"
|
||||
},
|
||||
"motion_header": {
|
||||
"path": "wasm-core/include/sim_motion_queue.h",
|
||||
"sha256": "c26af2b5cff6cbc687f621d745601ff287eebd62011ea46fc7fa692b446a08cc"
|
||||
},
|
||||
"motion_source": {
|
||||
"path": "wasm-core/src/sim_motion_queue.c",
|
||||
"sha256": "56074e27df90bc1b60134eac3cdf84c78149388f99a19a027f6714e3784b04ed"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_kinematics_motion_test.c",
|
||||
"sha256": "bf041a8f30451c832566d9476d44f0011e56f33800f7a40aad12b4ebc0f662fc"
|
||||
}
|
||||
},
|
||||
"status": "partial"
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"ready_count": 0,
|
||||
"records": [
|
||||
@@ -29,10 +29,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -75,10 +75,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -121,10 +121,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -169,10 +169,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -217,10 +217,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -273,10 +273,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -329,10 +329,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -385,10 +385,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -441,10 +441,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -490,10 +490,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -542,10 +542,10 @@
|
||||
"native_smoke": {
|
||||
"binary": "build-native/sim_kinematics_test",
|
||||
"exit_code": 0,
|
||||
"result_sha256": "96d4ce027200030704ead75b891ead3c3af9b39f60eaccaafa875e0e8d526e3a",
|
||||
"result_sha256": "5f0afb7a7415fe5e0868273a7a8659526f6b351e1fc4c2a1f8a0c817c425f747",
|
||||
"status": "passed",
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout_sha256": "4792c9d03a753904bfef11d4b53e680ec1ff00a8b593addd95f4d566aa722140"
|
||||
"stdout_sha256": "470ed446a2e34f15a09838a8aae96e478b40965b26d717c5d3dbada9c028f638"
|
||||
},
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; this definition is deterministic but cannot be accepted as the official native FK/IK oracle",
|
||||
"status": "blocked"
|
||||
@@ -592,7 +592,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"report_fingerprint_sha256": "a501a60ae74340f05642468dc4911f56b9c5a3e93b0ccdb2e2a11657754cac7e",
|
||||
"report_fingerprint_sha256": "e165aad3a5ffe67b237bb2a34df9605578c58ff090bcab75345d0bc691c8abff",
|
||||
"sample_count": 11,
|
||||
"schema": "kinematics-oracle-report.v1",
|
||||
"status": "blocked"
|
||||
|
||||
502
coverage/kinematics-registry.report.json
Normal file
502
coverage/kinematics-registry.report.json
Normal file
@@ -0,0 +1,502 @@
|
||||
{
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZAC",
|
||||
"id": "xyzac-trt",
|
||||
"kind": "real",
|
||||
"parameters": [
|
||||
"offsets",
|
||||
"tool",
|
||||
"limits"
|
||||
],
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZABC",
|
||||
"id": "genser-puma560",
|
||||
"kind": "real",
|
||||
"parameters": [
|
||||
"DH",
|
||||
"solver",
|
||||
"limits"
|
||||
],
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"type": 13
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZAB",
|
||||
"id": "pentapod",
|
||||
"kind": "real",
|
||||
"parameters": [
|
||||
"geometry",
|
||||
"tool",
|
||||
"solver",
|
||||
"limits"
|
||||
],
|
||||
"path": "linuxcnc/configs/sim/axis/pentapod/pentapod.ini",
|
||||
"type": 18
|
||||
}
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_kinematics_registry_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_kinematics_registry_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"18-entry registry",
|
||||
"capability requirements",
|
||||
"parameter masks",
|
||||
"real config validation",
|
||||
"unknown/path names"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"registry": {
|
||||
"abi_version": 1,
|
||||
"capabilities": {
|
||||
"duplicate_coordinates": 64,
|
||||
"extra_joints": 32,
|
||||
"forward": 1,
|
||||
"identity": 4,
|
||||
"inverse": 2,
|
||||
"iterative": 8,
|
||||
"switchable": 16
|
||||
},
|
||||
"capability_union": 127,
|
||||
"entry_count": 18,
|
||||
"parameter_union": 255,
|
||||
"parameters": {
|
||||
"DH": 64,
|
||||
"geometry": 32,
|
||||
"limits": 2,
|
||||
"mapping": 1,
|
||||
"offsets": 4,
|
||||
"pivot": 16,
|
||||
"solver": 128,
|
||||
"tool": 8
|
||||
},
|
||||
"state_hash": 3966926008000628109,
|
||||
"verified_entries": 18
|
||||
},
|
||||
"report_sha256": "e18289c5743e6b7c8acf608a43ebb6a2169f72929c8667da8ad3810be65f7dd2",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"capability_error": -711,
|
||||
"capability_union": 127,
|
||||
"coordinate_error": -703,
|
||||
"count": 18,
|
||||
"entries": [
|
||||
{
|
||||
"capabilities": 119,
|
||||
"max": 9,
|
||||
"min": 1,
|
||||
"name": "trivkins",
|
||||
"parameters": 3,
|
||||
"required": "",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 5,
|
||||
"name": "xyzac-trt-kins",
|
||||
"parameters": 15,
|
||||
"required": "XYZAC",
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 5,
|
||||
"name": "xyzbc-trt-kins",
|
||||
"parameters": 15,
|
||||
"required": "XYZBC",
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 6,
|
||||
"name": "xyzbcw",
|
||||
"parameters": 19,
|
||||
"required": "XYZBCW",
|
||||
"type": 4
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "corexykins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 3,
|
||||
"min": 3,
|
||||
"name": "rosekins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "maxkins",
|
||||
"parameters": 19,
|
||||
"required": "XYZ",
|
||||
"type": 7
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "rotatekins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 8
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 6,
|
||||
"name": "5axiskins",
|
||||
"parameters": 19,
|
||||
"required": "XYZBCW",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "scarakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZCAB",
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "pumakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZABC",
|
||||
"type": 11
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 5,
|
||||
"min": 5,
|
||||
"name": "scorbot-kins",
|
||||
"parameters": 35,
|
||||
"required": "XYZAB",
|
||||
"type": 12
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "genserkins",
|
||||
"parameters": 195,
|
||||
"required": "XYZABC",
|
||||
"type": 13
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "lineardeltakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 14
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "rotarydeltakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 15
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "tripodkins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 16
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "genhexkins",
|
||||
"parameters": 171,
|
||||
"required": "XYZABC",
|
||||
"type": 17
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 5,
|
||||
"min": 5,
|
||||
"name": "pentakins",
|
||||
"parameters": 171,
|
||||
"required": "XYZAB",
|
||||
"type": 18
|
||||
}
|
||||
],
|
||||
"iterative_require": 0,
|
||||
"joint_error": -703,
|
||||
"lookup_status": 0,
|
||||
"parameter_error": -711,
|
||||
"parameter_union": 255,
|
||||
"path_name": -712,
|
||||
"range_error": -702,
|
||||
"registry_hash": 3966926008000628109,
|
||||
"schema": "kinematics-registry-samples.v1",
|
||||
"type_mismatch": -703,
|
||||
"unknown_name": -705,
|
||||
"verified": 18
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"capability_error": -711,
|
||||
"capability_union": 127,
|
||||
"coordinate_error": -703,
|
||||
"count": 18,
|
||||
"entries": [
|
||||
{
|
||||
"capabilities": 119,
|
||||
"max": 9,
|
||||
"min": 1,
|
||||
"name": "trivkins",
|
||||
"parameters": 3,
|
||||
"required": "",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 5,
|
||||
"name": "xyzac-trt-kins",
|
||||
"parameters": 15,
|
||||
"required": "XYZAC",
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 5,
|
||||
"name": "xyzbc-trt-kins",
|
||||
"parameters": 15,
|
||||
"required": "XYZBC",
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 6,
|
||||
"name": "xyzbcw",
|
||||
"parameters": 19,
|
||||
"required": "XYZBCW",
|
||||
"type": 4
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "corexykins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 3,
|
||||
"min": 3,
|
||||
"name": "rosekins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "maxkins",
|
||||
"parameters": 19,
|
||||
"required": "XYZ",
|
||||
"type": 7
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "rotatekins",
|
||||
"parameters": 3,
|
||||
"required": "XYZ",
|
||||
"type": 8
|
||||
},
|
||||
{
|
||||
"capabilities": 115,
|
||||
"max": 9,
|
||||
"min": 6,
|
||||
"name": "5axiskins",
|
||||
"parameters": 19,
|
||||
"required": "XYZBCW",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "scarakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZCAB",
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "pumakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZABC",
|
||||
"type": 11
|
||||
},
|
||||
{
|
||||
"capabilities": 19,
|
||||
"max": 5,
|
||||
"min": 5,
|
||||
"name": "scorbot-kins",
|
||||
"parameters": 35,
|
||||
"required": "XYZAB",
|
||||
"type": 12
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "genserkins",
|
||||
"parameters": 195,
|
||||
"required": "XYZABC",
|
||||
"type": 13
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "lineardeltakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 14
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "rotarydeltakins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 15
|
||||
},
|
||||
{
|
||||
"capabilities": 51,
|
||||
"max": 9,
|
||||
"min": 3,
|
||||
"name": "tripodkins",
|
||||
"parameters": 35,
|
||||
"required": "XYZ",
|
||||
"type": 16
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 6,
|
||||
"min": 6,
|
||||
"name": "genhexkins",
|
||||
"parameters": 171,
|
||||
"required": "XYZABC",
|
||||
"type": 17
|
||||
},
|
||||
{
|
||||
"capabilities": 27,
|
||||
"max": 5,
|
||||
"min": 5,
|
||||
"name": "pentakins",
|
||||
"parameters": 171,
|
||||
"required": "XYZAB",
|
||||
"type": 18
|
||||
}
|
||||
],
|
||||
"iterative_require": 0,
|
||||
"joint_error": -703,
|
||||
"lookup_status": 0,
|
||||
"parameter_error": -711,
|
||||
"parameter_union": 255,
|
||||
"path_name": -712,
|
||||
"range_error": -702,
|
||||
"registry_hash": 3966926008000628109,
|
||||
"schema": "kinematics-registry-samples.v1",
|
||||
"type_mismatch": -703,
|
||||
"unknown_name": -705,
|
||||
"verified": 18
|
||||
}
|
||||
},
|
||||
"schema": "kinematics-registry-report.v1",
|
||||
"source_records": {
|
||||
"config_genser": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"sha256": "2bb881ed6c5662282977accac29bfd6c4efc18e572cbcb8a31c134677012cf4c"
|
||||
},
|
||||
"config_pentapod": {
|
||||
"path": "linuxcnc/configs/sim/axis/pentapod/pentapod.ini",
|
||||
"sha256": "0fb210122ab482087d3a95e5e7487baecb84c3260dca1769970f4c19b2ad0618"
|
||||
},
|
||||
"config_xyzac": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"sha256": "9ee1d96962e7a44b25468f452543cfc3ca318585269e26227bb5b6e17b1e42f1"
|
||||
},
|
||||
"implementation": {
|
||||
"path": "wasm-core/src/sim_kinematics_registry.c",
|
||||
"sha256": "fa852e370b5fa52006afb9c04bd6bc4ebba798d9b1ab7f060d52ecf98326d97d"
|
||||
},
|
||||
"kinematics_implementation": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"linuxcnc_header": {
|
||||
"path": "linuxcnc/src/emc/kinematics/kinematics.h",
|
||||
"sha256": "33b3744b9ceeeef3d992e9ca9cda89d93a3357d2d0a2af2023ca728e4be8e428"
|
||||
},
|
||||
"sim_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_kinematics_registry_test.c",
|
||||
"sha256": "42d0952fdb94340e33b4a86a5f0707aa3330302ae3c5e2573785bf29ee8ac243"
|
||||
}
|
||||
},
|
||||
"status": "partial",
|
||||
"validation": {
|
||||
"capability_error": -711,
|
||||
"coordinate_error": -703,
|
||||
"iterative_genser_and_DH_status": 0,
|
||||
"joint_error": -703,
|
||||
"lookup_status": 0,
|
||||
"parameter_error": -711,
|
||||
"path_name": -712,
|
||||
"range_error": -702,
|
||||
"type_mismatch": -703,
|
||||
"unknown_name": -705
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,11 +45,44 @@
|
||||
"joints": 5,
|
||||
"kinematics": "pentakins",
|
||||
"path": "linuxcnc/configs/sim/axis/pentapod/pentapod.ini",
|
||||
"provenance": "shipped LinuxCNC simulation",
|
||||
"scan": true
|
||||
},
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZAB",
|
||||
"id": "shipped-pentapod",
|
||||
"joints": 5,
|
||||
"kind": "real",
|
||||
"path": "linuxcnc/configs/sim/axis/pentapod/pentapod.ini"
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZAB",
|
||||
"id": "offset-seed-regression",
|
||||
"initial_pose": [
|
||||
0.0,
|
||||
500.0,
|
||||
300.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"joints": 5,
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_kinematics_test.c",
|
||||
"purpose": "non-zero roll/pitch Newton convergence and native/WASM comparison",
|
||||
"target_pose": [
|
||||
0.0,
|
||||
500.0,
|
||||
300.0,
|
||||
0.01,
|
||||
-0.01
|
||||
]
|
||||
}
|
||||
],
|
||||
"geometry": {
|
||||
"orientation": "roll/pitch only; yaw fixed at zero",
|
||||
"orientation": "roll/pitch only; yaw is constrained to zero in the forward result",
|
||||
"struts": 5,
|
||||
"tool_offset": "applied to both base and effector HAL z coordinates",
|
||||
"units": "configuration units; XYZ linear and AB degrees"
|
||||
},
|
||||
"implementation_status": "verified",
|
||||
@@ -83,12 +116,13 @@
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"report_sha256": "b1d1cdc8fbd64b64ffa88023c77c7ce097952966353567b023c0667a8dc1be75",
|
||||
"report_sha256": "d8863549ead6486889c8473134f9e2ce1c2835024c9ea67494c4c7e0a13a6b75",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"boundaries": {
|
||||
"alternate_branch": 0,
|
||||
"alternate_branch": 1,
|
||||
"alternate_iterations": 1,
|
||||
"alternate_pose": [
|
||||
0.0,
|
||||
0.0,
|
||||
@@ -97,37 +131,42 @@
|
||||
"alternate_status": -702,
|
||||
"branch": 0,
|
||||
"iteration_limit": -702,
|
||||
"iterations": 1,
|
||||
"iteration_limit_iterations": 1,
|
||||
"iterations": 12,
|
||||
"limit_error": -702,
|
||||
"max_error": -702,
|
||||
"max_error_iterations": 1,
|
||||
"nonpositive": -702,
|
||||
"residual": 0.0,
|
||||
"singular": -704
|
||||
"nonpositive_iterations": 0,
|
||||
"residual": 1e-09,
|
||||
"singular": -704,
|
||||
"singular_iterations": 0
|
||||
},
|
||||
"pentapod": {
|
||||
"inverse": [
|
||||
619.814203778,
|
||||
655.78680467,
|
||||
750.465994974,
|
||||
351.035577947,
|
||||
327.336726304
|
||||
625.895566556,
|
||||
649.261562588,
|
||||
754.767615986,
|
||||
338.490149644,
|
||||
341.118326622
|
||||
],
|
||||
"iterations": 1,
|
||||
"iterations": 12,
|
||||
"pose": [
|
||||
0.0,
|
||||
500.0,
|
||||
300.0,
|
||||
2.0,
|
||||
-3.0
|
||||
0.01,
|
||||
-0.01
|
||||
],
|
||||
"residual": 0.0,
|
||||
"state_hash": 10088895819155863850
|
||||
"residual": 1e-09,
|
||||
"state_hash": 13978136356142463125
|
||||
}
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"boundaries": {
|
||||
"alternate_branch": 0,
|
||||
"alternate_branch": 1,
|
||||
"alternate_iterations": 1,
|
||||
"alternate_pose": [
|
||||
0.0,
|
||||
0.0,
|
||||
@@ -136,31 +175,35 @@
|
||||
"alternate_status": -702,
|
||||
"branch": 0,
|
||||
"iteration_limit": -702,
|
||||
"iterations": 1,
|
||||
"iteration_limit_iterations": 1,
|
||||
"iterations": 12,
|
||||
"limit_error": -702,
|
||||
"max_error": -702,
|
||||
"max_error_iterations": 1,
|
||||
"nonpositive": -702,
|
||||
"residual": 0.0,
|
||||
"singular": -704
|
||||
"nonpositive_iterations": 0,
|
||||
"residual": 1e-09,
|
||||
"singular": -704,
|
||||
"singular_iterations": 0
|
||||
},
|
||||
"pentapod": {
|
||||
"inverse": [
|
||||
619.814203778,
|
||||
655.78680467,
|
||||
750.465994974,
|
||||
351.035577947,
|
||||
327.336726304
|
||||
625.895566556,
|
||||
649.261562588,
|
||||
754.767615986,
|
||||
338.490149644,
|
||||
341.118326622
|
||||
],
|
||||
"iterations": 1,
|
||||
"iterations": 12,
|
||||
"pose": [
|
||||
0.0,
|
||||
500.0,
|
||||
300.0,
|
||||
2.0,
|
||||
-3.0
|
||||
0.01,
|
||||
-0.01
|
||||
],
|
||||
"residual": 0.0,
|
||||
"state_hash": 10088895819155863850
|
||||
"residual": 1e-09,
|
||||
"state_hash": 13978136356142463125
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -178,7 +221,8 @@
|
||||
300.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"seed_units": "XYZ in configuration units; A/B in radians internally"
|
||||
},
|
||||
"source_records": {
|
||||
"config": {
|
||||
@@ -195,11 +239,11 @@
|
||||
},
|
||||
"implementation": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "424873c8e5d3d5b45daf616d95a58157c6dbcbbee9f4cc512b58bfb2672c6145"
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"kinematics_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "0c6d955717982e9d1a24d1240a1c657fc37dfe166c5738d3ee23878e46b2a98d"
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/pentakins.c",
|
||||
@@ -207,7 +251,7 @@
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_kinematics_test.c",
|
||||
"sha256": "3bda403d3af592dcc69ee9b1f3c96dda97b952f827a1c9ce5393e01cd357c403"
|
||||
"sha256": "715882c28ec242be652357d330a4b56d60f8e93f2e821193133911576e400aac"
|
||||
}
|
||||
},
|
||||
"status": "partial",
|
||||
@@ -218,31 +262,37 @@
|
||||
0.0
|
||||
],
|
||||
"alternate_seed_error": -702,
|
||||
"alternate_seed_iterations": 1,
|
||||
"iteration_limit_error": -702,
|
||||
"iterations": 1,
|
||||
"iteration_limit_iterations": 1,
|
||||
"iterations": 12,
|
||||
"joint_limit_error": -702,
|
||||
"max_error_boundary": -702,
|
||||
"max_error_iterations": 1,
|
||||
"nominal": {
|
||||
"inverse": [
|
||||
619.814203778,
|
||||
655.78680467,
|
||||
750.465994974,
|
||||
351.035577947,
|
||||
327.336726304
|
||||
625.895566556,
|
||||
649.261562588,
|
||||
754.767615986,
|
||||
338.490149644,
|
||||
341.118326622
|
||||
],
|
||||
"iterations": 1,
|
||||
"iterations": 12,
|
||||
"pose": [
|
||||
0.0,
|
||||
500.0,
|
||||
300.0,
|
||||
2.0,
|
||||
-3.0
|
||||
0.01,
|
||||
-0.01
|
||||
],
|
||||
"residual": 0.0,
|
||||
"state_hash": 10088895819155863850
|
||||
"residual": 1e-09,
|
||||
"state_hash": 13978136356142463125
|
||||
},
|
||||
"nonpositive_joint_error": -702,
|
||||
"residual": 0.0,
|
||||
"singular_jacobian_error": -704
|
||||
"nonpositive_joint_iterations": 0,
|
||||
"posture_constraint": "C/yaw is not a configured joint and forward output is exactly zero",
|
||||
"residual": 1e-09,
|
||||
"singular_jacobian_error": -704,
|
||||
"singular_jacobian_iterations": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3211,45 +3211,63 @@
|
||||
{"bytes":311,"kind":"documentation","path":"linuxcnc/docs/src/xhtml11-latexmath.conf","sha256":"d31ed1d15f9ac1232af1e58c05d83feedd9cd73f9fe543d589f9340fdf0ad719","source_id":"doc:linuxcnc/docs/src/xhtml11-latexmath.conf"}
|
||||
{"bytes":269,"kind":"documentation","path":"linuxcnc/docs/src/xhtml11-links.conf","sha256":"debc4b462499fdd8286b3812c705e40267e53d87ae8062250e8c23cb7ad59afe","source_id":"doc:linuxcnc/docs/src/xhtml11-links.conf"}
|
||||
{"bytes":886,"kind":"documentation","path":"linuxcnc/docs/src/xhtml11.conf","sha256":"c0222c552624bf3b7695e2f5c5db797be0e6cddcd77092293fd5e15b579f2ba9","source_id":"doc:linuxcnc/docs/src/xhtml11.conf"}
|
||||
{"bytes":6793,"kind":"web_wasm_build_or_test","path":"wasm-core/CMakeLists.txt","sha256":"e9296a3983a531e4e6864d05e2e97c8dbf29e0dc9e45942ecc1a0a7222b3a660","source_id":"implementation:wasm-core/CMakeLists.txt"}
|
||||
{"bytes":14198,"kind":"web_wasm_build_or_test","path":"wasm-core/CMakeLists.txt","sha256":"401fce0118c0c245f003e564130e25f9db668d60654f4ca2962fa2eb048f22c4","source_id":"implementation:wasm-core/CMakeLists.txt"}
|
||||
{"bytes":933,"kind":"web_wasm_interface","path":"wasm-core/include/sim_api.h","sha256":"9dda70ef64afe0b398a3de5342e6bdf545c331bbef24f112be2a45acec327227","source_id":"implementation:wasm-core/include/sim_api.h"}
|
||||
{"bytes":1050,"kind":"web_wasm_interface","path":"wasm-core/include/sim_event.h","sha256":"5406dbf7fb6a80924835d967641c9ed22cc8c25a13b72b76ce3d235abb0ad45d","source_id":"implementation:wasm-core/include/sim_event.h"}
|
||||
{"bytes":1967,"kind":"web_wasm_interface","path":"wasm-core/include/sim_hal.h","sha256":"f0b25fda1c267ed8078469c9966fa3b09721eba77c575affea77d489437a3e49","source_id":"implementation:wasm-core/include/sim_hal.h"}
|
||||
{"bytes":1968,"kind":"web_wasm_interface","path":"wasm-core/include/sim_hal.h","sha256":"3dd7230316e557d1a129a9a80a26a109b837a318b092657b8bee0d53a5e57db9","source_id":"implementation:wasm-core/include/sim_hal.h"}
|
||||
{"bytes":1872,"kind":"web_wasm_interface","path":"wasm-core/include/sim_interpreter.h","sha256":"7bf24098340e3da2272061acf7a43376c6c40fe9dffc55d012e3af65ceffedbb","source_id":"implementation:wasm-core/include/sim_interpreter.h"}
|
||||
{"bytes":5446,"kind":"web_wasm_interface","path":"wasm-core/include/sim_kinematics.h","sha256":"939d4088d9e7120e4a405be154eb3d98cd4e5925fc27d47641c115624c1bd994","source_id":"implementation:wasm-core/include/sim_kinematics.h"}
|
||||
{"bytes":4423,"kind":"web_wasm_interface","path":"wasm-core/include/sim_io.h","sha256":"d84e9ab0bcf9ff138f8c8a57b28337605d731ff796577a8ab748d708f97daf9b","source_id":"implementation:wasm-core/include/sim_io.h"}
|
||||
{"bytes":12375,"kind":"web_wasm_interface","path":"wasm-core/include/sim_kinematics.h","sha256":"34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec","source_id":"implementation:wasm-core/include/sim_kinematics.h"}
|
||||
{"bytes":1716,"kind":"web_wasm_interface","path":"wasm-core/include/sim_kinematics_feedback.h","sha256":"6d70be324a99d58f07c5a3c3c888b92cd1959cba250faf3c12a4e32c5ef1cf05","source_id":"implementation:wasm-core/include/sim_kinematics_feedback.h"}
|
||||
{"bytes":1856,"kind":"web_wasm_interface","path":"wasm-core/include/sim_kinematics_home_limit.h","sha256":"ad4b4fc84efcc9a2dc6555d7ed69ab613eee21db7fef0d037d0a4ca1063309e1","source_id":"implementation:wasm-core/include/sim_kinematics_home_limit.h"}
|
||||
{"bytes":2332,"kind":"web_wasm_interface","path":"wasm-core/include/sim_kinematics_motion.h","sha256":"86b0f6b889875b03bc3fea8787443cb72e77561e85c7114b8dd43eeba8c69350","source_id":"implementation:wasm-core/include/sim_kinematics_motion.h"}
|
||||
{"bytes":1819,"kind":"web_wasm_interface","path":"wasm-core/include/sim_motion_queue.h","sha256":"c26af2b5cff6cbc687f621d745601ff287eebd62011ea46fc7fa692b446a08cc","source_id":"implementation:wasm-core/include/sim_motion_queue.h"}
|
||||
{"bytes":3280,"kind":"web_wasm_interface","path":"wasm-core/include/sim_posemath.h","sha256":"fef257b09f68e5649f0acacfcd3590d699d6f678927fbf8db7d103a40bf7da62","source_id":"implementation:wasm-core/include/sim_posemath.h"}
|
||||
{"bytes":1238,"kind":"web_wasm_interface","path":"wasm-core/include/sim_protocol.h","sha256":"d1dd3247159f0950a9aa2d903d25cfa184bccff1d7512de293868763b4035d35","source_id":"implementation:wasm-core/include/sim_protocol.h"}
|
||||
{"bytes":2262,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_api.c","sha256":"072ffb5aa13c4924c638464bb8487e5c293db589a9735c4d39c3a6d9455a3e59","source_id":"implementation:wasm-core/src/sim_api.c"}
|
||||
{"bytes":2579,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_event.c","sha256":"a0af4a75c9c3b71d4a59df675b64f313eda05f4589538fe999cd92607d104397","source_id":"implementation:wasm-core/src/sim_event.c"}
|
||||
{"bytes":3499,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_event.c","sha256":"72da3e5c6d8ea02481f380db59ef9d37047c0a5fda7392010540619912974e5a","source_id":"implementation:wasm-core/src/sim_event.c"}
|
||||
{"bytes":9413,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_hal.c","sha256":"76a057064546a8ee9ba1098952755a4cdc56e6e714f2761b8a1826898d3ae621","source_id":"implementation:wasm-core/src/sim_hal.c"}
|
||||
{"bytes":11585,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_interpreter.c","sha256":"9f9f45f7c123be774dcf55a752a29e6cc1f873a1605cf89c4caa4f4ab125e6a4","source_id":"implementation:wasm-core/src/sim_interpreter.c"}
|
||||
{"bytes":66830,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics.c","sha256":"6abae835c16feb27ec1dc28a9ea5384f0bc5190217ba04a4997048d83066b905","source_id":"implementation:wasm-core/src/sim_kinematics.c"}
|
||||
{"bytes":27678,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_io.c","sha256":"2ceacdb168a1f6dddb36761b93f62e4c39b3a6249c0a91a740ecf924dce217b3","source_id":"implementation:wasm-core/src/sim_io.c"}
|
||||
{"bytes":76636,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics.c","sha256":"316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600","source_id":"implementation:wasm-core/src/sim_kinematics.c"}
|
||||
{"bytes":4823,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics_feedback.c","sha256":"c690fc420d58b5e60958e4d0745b9a6bca678c5283eb9e74c50a11ca61faa93a","source_id":"implementation:wasm-core/src/sim_kinematics_feedback.c"}
|
||||
{"bytes":6029,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics_home_limit.c","sha256":"7f59d1722cd2ca54f346168b4a13d9e8377bb6035c9f5866c3a3fbb7115bb996","source_id":"implementation:wasm-core/src/sim_kinematics_home_limit.c"}
|
||||
{"bytes":8574,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics_motion.c","sha256":"1e9bd5404acfe7ab20eca5b3bb76111bf43c442feee0dc2d4c3f9249ba916a88","source_id":"implementation:wasm-core/src/sim_kinematics_motion.c"}
|
||||
{"bytes":6495,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_kinematics_registry.c","sha256":"fa852e370b5fa52006afb9c04bd6bc4ebba798d9b1ab7f060d52ecf98326d97d","source_id":"implementation:wasm-core/src/sim_kinematics_registry.c"}
|
||||
{"bytes":7791,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_motion_queue.c","sha256":"56074e27df90bc1b60134eac3cdf84c78149388f99a19a027f6714e3784b04ed","source_id":"implementation:wasm-core/src/sim_motion_queue.c"}
|
||||
{"bytes":13491,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_posemath.c","sha256":"939b221ed6ede1229d26c170ab3514884d5bafc90854ab09db641925859b5e8d","source_id":"implementation:wasm-core/src/sim_posemath.c"}
|
||||
{"bytes":4302,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_protocol.c","sha256":"da63b458f64f997251323fc8b53a9c84d1f978f5e8ea5ed7c8dd6f5bceab60e4","source_id":"implementation:wasm-core/src/sim_protocol.c"}
|
||||
{"bytes":8934,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_switchkins.c","sha256":"b08168265a8e5ca6870a9177165de5d582d12392e42cfdb44347c72c8b3e15e2","source_id":"implementation:wasm-core/src/sim_switchkins.c"}
|
||||
{"bytes":5195,"kind":"web_wasm_implementation","path":"wasm-core/src/sim_userkins.c","sha256":"cf3a0f3fea81fb63abf3336b87de1971391d7798b4eed3ec826c07bfd300ed0e","source_id":"implementation:wasm-core/src/sim_userkins.c"}
|
||||
{"bytes":858,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_api_test.c","sha256":"5fe8aa04543be2943c9e577bd6ce8f9ae9a8c98c7c24e84fd589b761fe39170c","source_id":"implementation:wasm-core/tests/sim_api_test.c"}
|
||||
{"bytes":1178,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_event_test.c","sha256":"57fdb682dcb18632216b4093e6dfadf2736a0ab11a002438e3c269d4d4b5406d","source_id":"implementation:wasm-core/tests/sim_event_test.c"}
|
||||
{"bytes":1578,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_event_test.c","sha256":"6784c703658d531ca8cb8488b57c5316cc03142a8828bd177175c9a3c14e6798","source_id":"implementation:wasm-core/tests/sim_event_test.c"}
|
||||
{"bytes":3505,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_hal_test.c","sha256":"5044ed422b37499291ff85fd7c601acc6665621cf33e9ff4d26f0e8fd5b7d533","source_id":"implementation:wasm-core/tests/sim_hal_test.c"}
|
||||
{"bytes":79,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_hal_test_main.c","sha256":"86f625eaa0c6d6a195b97c0d4df17af6c57f5407bcec0a830e7521cb9823165a","source_id":"implementation:wasm-core/tests/sim_hal_test_main.c"}
|
||||
{"bytes":2066,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_interpreter_test.c","sha256":"c4fc3ba11b751ed9f3f770ff1ff164ce18aa19986003cf85d17a26de7aecd3ce","source_id":"implementation:wasm-core/tests/sim_interpreter_test.c"}
|
||||
{"bytes":37392,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_test.c","sha256":"c9e4d20973c6a487787504bd5de9a73829f53d8bbfd35f91e72c6c62c76f191e","source_id":"implementation:wasm-core/tests/sim_kinematics_test.c"}
|
||||
{"bytes":4582,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_io_test.c","sha256":"46923ff49c55576692e660d1af37e43bd6c1aff184ae2a2d5ca9201dd969ccfe","source_id":"implementation:wasm-core/tests/sim_io_test.c"}
|
||||
{"bytes":3377,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_feedback_test.c","sha256":"1f1830b90a6d0662c02b7494f0b1848c518c13f3cf70905c5ba6a6eba62e7762","source_id":"implementation:wasm-core/tests/sim_kinematics_feedback_test.c"}
|
||||
{"bytes":2984,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_home_limit_test.c","sha256":"94004ef9e4182cfa04c9a458d750d84b12250a625406ad29ce06ae45136f909d","source_id":"implementation:wasm-core/tests/sim_kinematics_home_limit_test.c"}
|
||||
{"bytes":5578,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_motion_test.c","sha256":"bf041a8f30451c832566d9476d44f0011e56f33800f7a40aad12b4ebc0f662fc","source_id":"implementation:wasm-core/tests/sim_kinematics_motion_test.c"}
|
||||
{"bytes":3572,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_registry_test.c","sha256":"42d0952fdb94340e33b4a86a5f0707aa3330302ae3c5e2573785bf29ee8ac243","source_id":"implementation:wasm-core/tests/sim_kinematics_registry_test.c"}
|
||||
{"bytes":43608,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_test.c","sha256":"715882c28ec242be652357d330a4b56d60f8e93f2e821193133911576e400aac","source_id":"implementation:wasm-core/tests/sim_kinematics_test.c"}
|
||||
{"bytes":93,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_kinematics_test_main.c","sha256":"e567fe412145d3c9b04afc91d6efc6a53c3af3b3b52b7dda860cc289d42dbfc0","source_id":"implementation:wasm-core/tests/sim_kinematics_test_main.c"}
|
||||
{"bytes":1386,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_motion_queue_test.c","sha256":"882c90f9963b2433b868859c50a91dbdecef93454039b05b11241c1f76c13f31","source_id":"implementation:wasm-core/tests/sim_motion_queue_test.c"}
|
||||
{"bytes":2552,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_posemath_test.c","sha256":"751910f2bcd740c963b008e1ae4cb1dd058d2942ab1d63dd0a9f510a20a408d2","source_id":"implementation:wasm-core/tests/sim_posemath_test.c"}
|
||||
{"bytes":1457,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_protocol_test.c","sha256":"74c855e4bcae2091913847e2ba46ea5f0ae482229b53199baee5bbdcd3c0ef49","source_id":"implementation:wasm-core/tests/sim_protocol_test.c"}
|
||||
{"bytes":4939,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_switchkins_test.c","sha256":"3fa33181b114af828d8b3f4248f30a16ba3cfd58e82d962970a5116675123889","source_id":"implementation:wasm-core/tests/sim_switchkins_test.c"}
|
||||
{"bytes":7100,"kind":"web_wasm_implementation","path":"wasm-core/tests/sim_userkins_test.c","sha256":"13ecf5d2405a0e4c706cde25d93424655108779188c310f75ed36683abed624e","source_id":"implementation:wasm-core/tests/sim_userkins_test.c"}
|
||||
{"bytes":1779,"kind":"web_wasm_implementation","path":"web/worker/capabilities.mjs","sha256":"175cb4dac10cc6b2eb80ed1cf24405151ae3fac6ff82f68e5e2e241d79f10c97","source_id":"implementation:web/worker/capabilities.mjs"}
|
||||
{"bytes":3084,"kind":"web_wasm_implementation","path":"web/worker/error-map.mjs","sha256":"83ca215534eed48a9082cc07230507c7b38cbb99f9f2a52e7026b5d49da1d167","source_id":"implementation:web/worker/error-map.mjs"}
|
||||
{"bytes":4878,"kind":"web_wasm_implementation","path":"web/worker/replay.mjs","sha256":"cca610cd9062a0b3527be35ccaef2b835bba7e4fd0b90cf4bb5bb57e67f18aaa","source_id":"implementation:web/worker/replay.mjs"}
|
||||
{"bytes":7196,"kind":"web_wasm_implementation","path":"web/worker/sim-session.mjs","sha256":"1f923c3f235f3bf27f843b89a5e62d7bfc7eacd9fa8ed51c93722af03df00b67","source_id":"implementation:web/worker/sim-session.mjs"}
|
||||
{"bytes":7116,"kind":"web_wasm_implementation","path":"web/worker/replay.mjs","sha256":"5e599e302edd0c5109a061ba9a421f77d7b87ad9cdb6a4183653afe5cd02f97c","source_id":"implementation:web/worker/replay.mjs"}
|
||||
{"bytes":12963,"kind":"web_wasm_implementation","path":"web/worker/sim-session.mjs","sha256":"b9f6c841d78ddbd5f06cd9d1e14981d6483ad718226e4a34453f28df6f898413","source_id":"implementation:web/worker/sim-session.mjs"}
|
||||
{"bytes":614,"kind":"web_wasm_implementation","path":"web/worker/sim-worker.mjs","sha256":"4210f3ea1c613a8e8b0985dd2d9a13be445598388c0dffaae947182277e40b57","source_id":"implementation:web/worker/sim-worker.mjs"}
|
||||
{"bytes":1481,"kind":"web_wasm_implementation","path":"web/worker/test_replay.mjs","sha256":"e76fdf75bae1780781ce5db8ba8c2a2054915f07a1c524f0b49f6890f5fdfe3f","source_id":"implementation:web/worker/test_replay.mjs"}
|
||||
{"bytes":2157,"kind":"web_wasm_implementation","path":"web/worker/test_replay.mjs","sha256":"b0fedc32050334a42af6d2c6aee9b035db00bec396f5a1b5600c71942c736089","source_id":"implementation:web/worker/test_replay.mjs"}
|
||||
{"bytes":1791,"kind":"web_wasm_implementation","path":"web/worker/test_runtime_mapping.mjs","sha256":"b43baa924053b74c83a849b95e56eb49cdff1bd82f9a63c9566dc386dff96f51","source_id":"implementation:web/worker/test_runtime_mapping.mjs"}
|
||||
{"bytes":1852,"kind":"web_wasm_implementation","path":"web/worker/test_sim_session.mjs","sha256":"f3b1f0c1c3f85aafc2cc606ce526fd6b32f5dfabc9c05e4cd48b2217c7bbff62","source_id":"implementation:web/worker/test_sim_session.mjs"}
|
||||
{"bytes":2598,"kind":"web_wasm_implementation","path":"web/worker/test_sim_session.mjs","sha256":"bc7f3aeabca542a0b5c9d9c440114f4f9e150ca3cb8d0d8fc24cd7efce310332","source_id":"implementation:web/worker/test_sim_session.mjs"}
|
||||
{"bytes":1394,"kind":"web_wasm_implementation","path":"web/worker/test_wasm_session.mjs","sha256":"649fd47fb92eb8a6170b2606929ef70e1b2b57bc410dbdc2138d3e17fecfb54d","source_id":"implementation:web/worker/test_wasm_session.mjs"}
|
||||
{"bytes":6812,"kind":"web_wasm_implementation","path":"web/workspace/journal.mjs","sha256":"a2d97ceac5e0dbb65d3dc9cafaa845b3acd115292717312d6eff69585bca98a7","source_id":"implementation:web/workspace/journal.mjs"}
|
||||
{"bytes":197,"kind":"web_wasm_implementation","path":"web/workspace/recovery.mjs","sha256":"90cd3d19cb58660201ca40a4a5b16a092da1784653af2be0930270ead2ed181c","source_id":"implementation:web/workspace/recovery.mjs"}
|
||||
{"bytes":2779,"kind":"web_wasm_implementation","path":"web/workspace/test_recovery.mjs","sha256":"ff2f7acde34cae1515e46483b71421d6f4c9345f4f6cb8f70b5cd7fa49be2802","source_id":"implementation:web/workspace/test_recovery.mjs"}
|
||||
{"bytes":8767,"kind":"web_wasm_implementation","path":"web/workspace/recovery.mjs","sha256":"d4bae4220a3348d7bb66991472d1331045ab60bbe70c93f3b589bf0e8d05d14d","source_id":"implementation:web/workspace/recovery.mjs"}
|
||||
{"bytes":6838,"kind":"web_wasm_implementation","path":"web/workspace/test_recovery.mjs","sha256":"3158f65ee58edbffef3de7d5bb989b73a38bf76848b1c4fe0d392943477e2629","source_id":"implementation:web/workspace/test_recovery.mjs"}
|
||||
{"bytes":296,"kind":"source","path":"linuxcnc/src/.gitignore","sha256":"25b6490091de6eea30bd72259af4735a4a5938e1b229beb793e13d74fff00cbf","source_id":"source:linuxcnc/src/.gitignore"}
|
||||
{"bytes":1573,"kind":"source","path":"linuxcnc/src/.swish_config","sha256":"525a65a5aecbcfeda3b227b8384b5bc3e13c9b297efc556a4bec694ca1490d61","source_id":"source:linuxcnc/src/.swish_config"}
|
||||
{"bytes":934,"kind":"source","path":"linuxcnc/src/.vimrc","sha256":"cea9a84a55abd10330bfd1fb54b72a2f7a06acda91a8011eab2609a403c66472","source_id":"source:linuxcnc/src/.vimrc"}
|
||||
|
||||
235
coverage/switchkins-kinematics.report.json
Normal file
235
coverage/switchkins-kinematics.report.json
Normal file
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZAC",
|
||||
"id": "shipped-xyzac-trt-identityfirst",
|
||||
"identityfirst": true,
|
||||
"joints": 5,
|
||||
"kind": "real",
|
||||
"kinematics": "xyzac-trt-kins",
|
||||
"modes": {
|
||||
"0": "identity",
|
||||
"1": "xyzac-trt",
|
||||
"2": "userk"
|
||||
},
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"provenance": "shipped LinuxCNC simulation",
|
||||
"sparm": "identityfirst",
|
||||
"switch_pin": true
|
||||
},
|
||||
{
|
||||
"id": "mode-transition-pose-continuity",
|
||||
"kind": "derived",
|
||||
"modes": [
|
||||
"identity",
|
||||
"xyzac-trt"
|
||||
],
|
||||
"path": "wasm-core/tests/sim_switchkins_test.c",
|
||||
"purpose": "old-mode forward pose to new-mode inverse remap"
|
||||
},
|
||||
{
|
||||
"id": "userk-slot-missing-module",
|
||||
"kind": "derived",
|
||||
"modes": [
|
||||
"userk"
|
||||
],
|
||||
"path": "wasm-core/tests/sim_switchkins_test.c",
|
||||
"purpose": "mode 2 remains fail-closed until static userk is registered"
|
||||
}
|
||||
],
|
||||
"implementation_status": "verified",
|
||||
"mode_registry": {
|
||||
"identityfirst": true,
|
||||
"names": {
|
||||
"0": "identity",
|
||||
"1": "xyzac-trt",
|
||||
"2": "userk"
|
||||
},
|
||||
"slots": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"startup_mode": 0
|
||||
},
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_switchkins_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_switchkins_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"type 0 identity",
|
||||
"type 1 xyzac-trt",
|
||||
"type 2 userk",
|
||||
"invalid mode",
|
||||
"missing module",
|
||||
"interrupted switch",
|
||||
"abort recovery"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source",
|
||||
"status": "blocked"
|
||||
},
|
||||
"report_sha256": "b43a356d6e79dad6099010c4997541dcc7c5752dc7e639b709d88e658b046457",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"abort_count": 2,
|
||||
"abort_status": 0,
|
||||
"active_mode": 0,
|
||||
"begin_status": 0,
|
||||
"forward_status": 0,
|
||||
"generation": 2,
|
||||
"initial_hash": 902060736367773728,
|
||||
"initial_mode": 0,
|
||||
"initial_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
],
|
||||
"interrupted_begin": -709,
|
||||
"interrupted_commit": -709,
|
||||
"invalid_mode": -707,
|
||||
"missing_module": -708,
|
||||
"register_missing": -708,
|
||||
"registered": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"returned_hash": 4546981263192554432,
|
||||
"returned_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
],
|
||||
"returned_repeat_hash": 4546981263192554432,
|
||||
"schema": "switchkins-samples.v1",
|
||||
"switch_count": 2,
|
||||
"switch_to_identity": 0,
|
||||
"switch_to_trt": 0,
|
||||
"switched_hash": 17322294160221391943,
|
||||
"switched_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"wasm": {
|
||||
"abort_count": 2,
|
||||
"abort_status": 0,
|
||||
"active_mode": 0,
|
||||
"begin_status": 0,
|
||||
"forward_status": 0,
|
||||
"generation": 2,
|
||||
"initial_hash": 902060736367773728,
|
||||
"initial_mode": 0,
|
||||
"initial_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
],
|
||||
"interrupted_begin": -709,
|
||||
"interrupted_commit": -709,
|
||||
"invalid_mode": -707,
|
||||
"missing_module": -708,
|
||||
"register_missing": -708,
|
||||
"registered": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"returned_hash": 4546981263192554432,
|
||||
"returned_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
],
|
||||
"returned_repeat_hash": 4546981263192554432,
|
||||
"schema": "switchkins-samples.v1",
|
||||
"switch_count": 2,
|
||||
"switch_to_identity": 0,
|
||||
"switch_to_trt": 0,
|
||||
"switched_hash": 17322294160221391943,
|
||||
"switched_pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"schema": "switchkins-kinematics-report.v1",
|
||||
"source_records": {
|
||||
"config": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"sha256": "9ee1d96962e7a44b25468f452543cfc3ca318585269e26227bb5b6e17b1e42f1"
|
||||
},
|
||||
"implementation": {
|
||||
"path": "wasm-core/src/sim_switchkins.c",
|
||||
"sha256": "b08168265a8e5ca6870a9177165de5d582d12392e42cfdb44347c72c8b3e15e2"
|
||||
},
|
||||
"kinematics_header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"kinematics_implementation": {
|
||||
"path": "wasm-core/src/sim_kinematics.c",
|
||||
"sha256": "316fe74604180b6e9ae7f66369f084278f1b54b7c4545ec9efa870186adc9600"
|
||||
},
|
||||
"module_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/xyzac-trt-kins.c",
|
||||
"sha256": "24c633613db9876c2ec246e1519a63d288fd27c68296d89f89f0368105634e69"
|
||||
},
|
||||
"postgui_hal": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal",
|
||||
"sha256": "55e969c22ca9054a818dbedc8ec50bed768d9ab3d8c4bfdfcfc78e912e9271c4"
|
||||
},
|
||||
"switch_header": {
|
||||
"path": "linuxcnc/src/emc/kinematics/switchkins.h",
|
||||
"sha256": "f6e5323aa16daaac8e07bba622d0bf969102ad0d9c00d41621c08af31e0bb9b1"
|
||||
},
|
||||
"switch_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/switchkins.c",
|
||||
"sha256": "4de32a4a147e5ceee5346b9c6bf471dddf5c64ff58afe5044406c50bcd96b7f5"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_switchkins_test.c",
|
||||
"sha256": "3fa33181b114af828d8b3f4248f30a16ba3cfd58e82d962970a5116675123889"
|
||||
},
|
||||
"userk_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/userkfuncs.c",
|
||||
"sha256": "146238645955f8d4e2cb0cd34ff44db3a9dad01e149f81c08f8a53c19f1609bc"
|
||||
}
|
||||
},
|
||||
"state_and_errors": {
|
||||
"abort_restores_active_mode": true,
|
||||
"invalid_mode_error": -707,
|
||||
"missing_module_error": -708,
|
||||
"pose_continuity": {
|
||||
"identity_to_trt": true,
|
||||
"trt_to_identity": true
|
||||
},
|
||||
"state_hash_stable": true,
|
||||
"switch_interrupted_error": -709
|
||||
},
|
||||
"status": "partial"
|
||||
}
|
||||
225
coverage/userkins-kinematics.report.json
Normal file
225
coverage/userkins-kinematics.report.json
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"configuration_candidates": [
|
||||
{
|
||||
"coordinates": "XYZCAB",
|
||||
"id": "shipped-scara-userk-slot",
|
||||
"implementation": "identity template",
|
||||
"kind": "real",
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/scara/scara.ini",
|
||||
"scan": true
|
||||
},
|
||||
{
|
||||
"coordinates": "XYZABC",
|
||||
"id": "shipped-puma560-genser",
|
||||
"implementation": "ugenser static adapter over genser",
|
||||
"kind": "real",
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"scan": true
|
||||
},
|
||||
{
|
||||
"id": "static-descriptor-regression",
|
||||
"implementations": [
|
||||
"userk.identity",
|
||||
"ugenser.static"
|
||||
],
|
||||
"kind": "derived",
|
||||
"path": "wasm-core/tests/sim_userkins_test.c",
|
||||
"purpose": "ABI/capability checks, FK/IK calls, switch slot binding, and native/WASM comparison"
|
||||
}
|
||||
],
|
||||
"error_boundaries": {
|
||||
"abi_version": -710,
|
||||
"capability": -711,
|
||||
"dynamic_module": -712,
|
||||
"missing_or_uninitialized_module": -708
|
||||
},
|
||||
"implementation_status": "verified",
|
||||
"native_wasm": {
|
||||
"native_command": "build-native/sim_userkins_test",
|
||||
"status": "passed",
|
||||
"wasm_command": "node build-wasm/sim_userkins_wasm_test.js"
|
||||
},
|
||||
"oracle": {
|
||||
"blocked_samples": [
|
||||
"userk identity template",
|
||||
"ugenser static adapter",
|
||||
"ABI mismatch",
|
||||
"capability mismatch",
|
||||
"dynamic module rejection",
|
||||
"uninitialized module"
|
||||
],
|
||||
"reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; the deprecated ugenser CLI is not an approved replacement",
|
||||
"status": "blocked"
|
||||
},
|
||||
"report_sha256": "43f7a1918c7f09cea97e0ac400f6572ff9b9051fccc22cde5b70765dd1e735f1",
|
||||
"samples": {
|
||||
"byte_identical_json": true,
|
||||
"native": {
|
||||
"abi_error": -710,
|
||||
"abi_version": 1,
|
||||
"active_mode": 2,
|
||||
"capabilities": 7,
|
||||
"capability_error": -711,
|
||||
"dynamic_path_error": -712,
|
||||
"identity": {
|
||||
"forward_calls": 1,
|
||||
"inverse_calls": 1,
|
||||
"joints": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
15.0
|
||||
],
|
||||
"pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
15.0
|
||||
],
|
||||
"repeat_hash": 7729967891798573724,
|
||||
"state_hash": 7729967891798573724
|
||||
},
|
||||
"missing_implementation": -708,
|
||||
"path_name_error": -712,
|
||||
"schema": "userkins-samples.v1",
|
||||
"switch_to_user": 0,
|
||||
"ugenser": {
|
||||
"forward_calls": 2,
|
||||
"inverse_calls": 1,
|
||||
"iterations": 0,
|
||||
"pose": [
|
||||
619.051467462,
|
||||
30.4112378,
|
||||
-349.855537721,
|
||||
-173.973089253,
|
||||
5.329608876,
|
||||
-19.06581641
|
||||
],
|
||||
"repeat_hash": 1161289569922079659,
|
||||
"residual": 0.0,
|
||||
"state_hash": 1161289569922079659
|
||||
},
|
||||
"uninitialized_error": -708
|
||||
},
|
||||
"state_hash_identical": true,
|
||||
"state_hash_stable": true,
|
||||
"wasm": {
|
||||
"abi_error": -710,
|
||||
"abi_version": 1,
|
||||
"active_mode": 2,
|
||||
"capabilities": 7,
|
||||
"capability_error": -711,
|
||||
"dynamic_path_error": -712,
|
||||
"identity": {
|
||||
"forward_calls": 1,
|
||||
"inverse_calls": 1,
|
||||
"joints": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
15.0
|
||||
],
|
||||
"pose": [
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
5.0,
|
||||
15.0
|
||||
],
|
||||
"repeat_hash": 7729967891798573724,
|
||||
"state_hash": 7729967891798573724
|
||||
},
|
||||
"missing_implementation": -708,
|
||||
"path_name_error": -712,
|
||||
"schema": "userkins-samples.v1",
|
||||
"switch_to_user": 0,
|
||||
"ugenser": {
|
||||
"forward_calls": 2,
|
||||
"inverse_calls": 1,
|
||||
"iterations": 0,
|
||||
"pose": [
|
||||
619.051467462,
|
||||
30.4112378,
|
||||
-349.855537721,
|
||||
-173.973089253,
|
||||
5.329608876,
|
||||
-19.06581641
|
||||
],
|
||||
"repeat_hash": 1161289569922079659,
|
||||
"residual": 0.0,
|
||||
"state_hash": 1161289569922079659
|
||||
},
|
||||
"uninitialized_error": -708
|
||||
}
|
||||
},
|
||||
"schema": "userkins-kinematics-report.v1",
|
||||
"source_records": {
|
||||
"genser_functions": {
|
||||
"path": "linuxcnc/src/emc/kinematics/genserfuncs.c",
|
||||
"sha256": "e4b775e93b973a4faae873c81a95d7d8361fe5c5c59ea81e362a5f900a1e8781"
|
||||
},
|
||||
"genser_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/genserkins.c",
|
||||
"sha256": "cbb87b9c21f9f5e1d6a3c5bd333f72a7b1b4f1618a21b32d4b34bc621b43e075"
|
||||
},
|
||||
"header": {
|
||||
"path": "wasm-core/include/sim_kinematics.h",
|
||||
"sha256": "34b08db612056432e08705951032418bf6f6cd2c285bde5743515588fbe484ec"
|
||||
},
|
||||
"implementation": {
|
||||
"path": "wasm-core/src/sim_userkins.c",
|
||||
"sha256": "cf3a0f3fea81fb63abf3336b87de1971391d7798b4eed3ec826c07bfd300ed0e"
|
||||
},
|
||||
"test": {
|
||||
"path": "wasm-core/tests/sim_userkins_test.c",
|
||||
"sha256": "13ecf5d2405a0e4c706cde25d93424655108779188c310f75ed36683abed624e"
|
||||
},
|
||||
"ugenser_config": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"sha256": "2bb881ed6c5662282977accac29bfd6c4efc18e572cbcb8a31c134677012cf4c"
|
||||
},
|
||||
"ugenser_dh": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/puma/puma560_dh.hal",
|
||||
"sha256": "23758eb7a6a4abe116e1de88140d04e6c9a910d1c378f019d76843a5a3fb6141"
|
||||
},
|
||||
"ugenser_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/ugenserkins.c",
|
||||
"sha256": "dee63aebaa11f2867657bc4b9052f6a8e8bd875e06bd8a9df2d3edac9eb1c31d"
|
||||
},
|
||||
"userk_config": {
|
||||
"path": "linuxcnc/configs/sim/axis/vismach/scara/scara.ini",
|
||||
"sha256": "602e02bb6d320b728d9ae15904a5d229ea1a31e932c18e9018b45be007d695ce"
|
||||
},
|
||||
"userk_source": {
|
||||
"path": "linuxcnc/src/emc/kinematics/userkfuncs.c",
|
||||
"sha256": "146238645955f8d4e2cb0cd34ff44db3a9dad01e149f81c08f8a53c19f1609bc"
|
||||
}
|
||||
},
|
||||
"static_contract": {
|
||||
"abi_version": 1,
|
||||
"capabilities": {
|
||||
"forward": 1,
|
||||
"inverse": 2,
|
||||
"sample_mask": 7,
|
||||
"static": 4
|
||||
},
|
||||
"dynamic_host_loading": "forbidden; paths, slash-delimited names, and host shared libraries return SIM_KIN_ERR_DYNAMIC_MODULE",
|
||||
"implementations": {
|
||||
"1": "identity",
|
||||
"2": "genser"
|
||||
},
|
||||
"switch_binding": {
|
||||
"active_after_select": 2,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
"status": "partial",
|
||||
"ugenser_scope": {
|
||||
"adaptation": "reuse the verified statically linked genser FK/IK implementation; do not port gettimeofday, stdin, or HAL process lifecycle",
|
||||
"legacy_cli_status": "excluded",
|
||||
"source_warning_present": true
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,46 @@
|
||||
{
|
||||
"schema": "workspace-recovery-report.v1",
|
||||
"status": "passed",
|
||||
"implementation_status": "verified",
|
||||
"scenarios": [
|
||||
"atomic_commit",
|
||||
"half_commit_ignored",
|
||||
"path_validation",
|
||||
"hash_corruption_blocked"
|
||||
"hash_corruption_blocked",
|
||||
"checkpoint_hash_validation",
|
||||
"event_gap_blocked",
|
||||
"unknown_schema_blocked",
|
||||
"unconfirmed_operation_blocked",
|
||||
"abort_recovery"
|
||||
],
|
||||
"journal_bytes_sha256": "fnv1a64:61ef8a25110e6c02",
|
||||
"recovery_state_hash": "fnv1a64:9bf65e00c699fdaf"
|
||||
"recovery_state_hash": "fnv1a64:9bf65e00c699fdaf",
|
||||
"replay": {
|
||||
"schema": "simulation-recovery.v1",
|
||||
"status": "aborted",
|
||||
"state_hash": "fnv1a64:763cd1217e060ddf",
|
||||
"state": {
|
||||
"tick": 4,
|
||||
"position": 4
|
||||
},
|
||||
"last_verified_seq": 2,
|
||||
"checkpoint_hash": "fnv1a64:94af0a32fb4408d3",
|
||||
"continuous_state_hash": "fnv1a64:763cd1217e060ddf",
|
||||
"endpoints_equal": true
|
||||
},
|
||||
"native_wasm": {
|
||||
"status": "passed",
|
||||
"native_command": "cmake --build build-native && ctest --test-dir build-native --output-on-failure",
|
||||
"worker_command": "node web/worker/test_replay.mjs && node web/workspace/test_recovery.mjs",
|
||||
"event_import_validation": "native sim_event_import and Worker replay schema both fail-closed"
|
||||
},
|
||||
"oracle": {
|
||||
"status": "blocked",
|
||||
"reason": "official recovery oracle is absent from locked source",
|
||||
"blocked_samples": [
|
||||
"worker restart checkpoint",
|
||||
"native/WASM full-state recovery",
|
||||
"browser OPFS crash recovery"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,11 +243,31 @@ M0/M1 的基线和会话骨架保持不变;从原 `M2-CORE-04` 起,采用以
|
||||
| K4 并联机构 | `K4-DELTA-01` linear/rotary Delta;`K4-TRIPOD-01` tripod;`K4-HEX-01` genhex;`K4-PENTA-01` pentapod | 全部并联正逆解和迭代恢复 | K1、K2 | 工作空间边界、无解、奇异、分支、lastpose、GUI pins 和故障注入通过 |
|
||||
| K5 切换/扩展 | `K5-SWITCH-01` switchkins;`K5-USER-01` 静态 userk/ugenser;`K5-CFG-01` 参数和能力注册 | 可切换和构建期扩展模型 | K3、K4 | 非法切换、缺模块、恢复、版本和权限错误明确;无动态宿主加载 |
|
||||
| K6 运动集成 | `K6-MOTION-01` TP→IK→motion;`K6-MOTION-02` FK feedback;`K6-HOME-01` home/limit;`K6-IO-01` spindle/tool/probe | 运动学与 Task/HAL/IO/数字孪生闭环 | K2-K5 | `configs/sim` 运动学入口可启动、运行、暂停、急停、恢复 |
|
||||
| K7 UI/回归 | `K7-VIS-01` 姿态/关节/刀尖渲染;`K7-REG-01` 全配置;`K7-REG-02` 浏览器故障矩阵 | 可视化和全量回归 | K6、M3/M4 | 轨迹、姿态、joint/world、报警、回放和多浏览器证据齐全 |
|
||||
| K7 UI/回归 | `K7-VIS-01` 姿态/关节/刀尖渲染;`K7-RECOVERY-01` 固定 tick checkpoint/replay 与 workspace 恢复;`K7-REG-01` 全配置;`K7-REG-02` 浏览器故障矩阵 | 可视化、恢复和全量回归 | K6、M3/M4 | 轨迹、姿态、joint/world、报警、恢复终点与连续运行一致、回放和多浏览器证据齐全 |
|
||||
| K8 发布闭合 | `K8-REL-01` 兼容报告;`K8-REL-02` 迁移/回滚;`K8-REL-03` 许可证/来源闭包 | 运动学纳入完整仿真机床发布 | K0-K7 | 受影响条目全部 `accepted`,无孤儿、无未解释差异、无静默降级 |
|
||||
|
||||
`K4-TRIPOD-01` 已完成:`tripodkins.c` 的点顶点三杆并联机构已接入统一 forward/inverse/mapping,使用真实 `linuxcnc/configs/sim/tklinuxcnc/tripod.ini` 和 `linuxcnc/lib/hallib/tripodsim.hal` 验证 XYZ/AD-BD-CD 映射、above/below 根分支、三角不等式无解、基面奇异、关节限位、退化基座和固定零迭代;native/WASM JSON 与 state hash 一致,官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续显式 blocked。证据见 `coverage/tripod-kinematics.report.json`。
|
||||
|
||||
`K4-PENTA-01` 已完成:`pentakins.c/.h` 的五杆并联机构已接入统一 forward/inverse/mapping,使用真实 `linuxcnc/configs/sim/axis/pentapod/pentapod.ini`/`kinematics.hal` 和派生非零姿态 seed,固定 XYZAB 映射、roll/pitch 姿态约束、tool-offset 双端几何、Newton 5x5 收敛/迭代上限/最大误差/奇异/非正杆长/关节限位边界;native/WASM JSON 与 state hash 一致。官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续显式 blocked。证据见 `coverage/pentapod-kinematics.report.json`。
|
||||
|
||||
`K5-SWITCH-01` 已完成:基于真实 `xyzac-trt.ini` 的 `sparm=identityfirst` 约定接入三槽位 switchkins 容器,固定 type 0 identity、type 1 xyzac-trt、type 2 userk 注册表;切换通过旧模式 FK、目标模式 IK 的 prepare/commit,保证 pose 连续,失败和 abort 保持旧模式,非法模式、缺模块和切换中断分别返回稳定错误码;native/WASM runner、模式状态 hash 和恢复计数一致。官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续显式 blocked。证据见 `coverage/switchkins-kinematics.report.json`。
|
||||
|
||||
`K5-USER-01` 已完成:`userkfuncs.c` 的 identity 模板和 `ugenserkins.c` 的 genser 使用面已收敛为构建期静态描述符,固定 ABI v1 与 forward/inverse/static capability mask;userk 可注册到 switchkins type 2,ugenser 复用已验证的静态 genser FK/IK,调用计数与 state hash 进入确定性状态。带路径名称、宿主 `.so`、ABI/能力不匹配、未知和未初始化模块均 fail-closed;旧 ugenser userspace CLI 按其源码警告明确排除,不作为 oracle。native/WASM runner 一致,官方 oracle 继续显式 blocked。证据见 `coverage/userkins-kinematics.report.json`。
|
||||
|
||||
`K5-CFG-01` 已完成:18 个内建运动学类型进入 ABI v1 构建期只读注册表,稳定登记 LinuxCNC 模块名、type、必需坐标、关节范围、forward/inverse/identity/iterative/switchable/extra-joint/duplicate-coordinate 能力位,以及 mapping/limits/offsets/tool/pivot/geometry/DH/solver 参数族。18 个最小配置全部经实际 `sim_kinematics_init` 验证;能力/参数不满足、type/坐标/关节错配、未知和路径名称均返回稳定错误。native/WASM 注册表 JSON 与 hash 一致,官方 oracle 继续显式 blocked。证据见 `coverage/kinematics-registry.report.json`。
|
||||
|
||||
`K6-MOTION-01` 已完成:新增固定 tick 的 TP→IK→关节样本队列适配器,复用现有 `sim_tp` 和 `sim_kinematics_inverse_ex`,每帧先推进 planner 再事务式生成关节样本;队列容量 64、暂停/恢复、非有限输入、planner 错误、IK 奇异和容量满边界均有固定错误码。IK 或容量失败会回滚 planner/kinematics 状态,nominal/capacity/inverse rollback 的 native/WASM JSON 与 state hash 一致;官方 oracle 继续显式 blocked。证据见 `coverage/kinematics-motion.report.json`。
|
||||
|
||||
`K6-MOTION-02` 已完成:新增固定 tick 的 actual-joint→FK feedback 适配器,保存世界 pose、joint snapshot、forward/inverse flags、tick、valid 和双层错误码;首次读取前返回 stale,非有限和 joint-count 错误不推进有效状态,forward 不支持/失败时保留无效状态且不发布 pose。native/WASM feedback JSON 与 state hash 一致,官方 oracle 继续显式 blocked。证据见 `coverage/kinematics-feedback.report.json`。
|
||||
|
||||
`K6-HOME-01` 已完成:新增固定 home sequence、`sim_kinematics_home` 初始化和 joint limit/FK 检查适配器;未 home、非法 sequence、越界、非有限和 joint-count 错误均 fail-closed,越界时保留上一帧有效 pose/joints,home/limit state hash 稳定,native/WASM JSON 一致。官方 oracle 继续显式 blocked。证据见 `coverage/kinematics-home-limit.report.json`。
|
||||
|
||||
`K6-IO-01` 已完成:新增固定 tick 的 `sim_io` spindle/tool/probe 适配器,注册 LinuxCNC motion/iocontrol pins 和 `sim-io-servo` 调度函数;主轴覆盖限速、加减速、inhibit、到速、制动、revs/index、orient 和故障,换刀覆盖 prepare/prepared/change/changed、停主轴互锁、abort/fault/恢复,探针覆盖 arm、输入边沿和 forward pose 锁存。非法 pin 类型、超限、未 ready、急停、故障清除和恢复均有稳定错误码;native/WASM JSON 与 state hash 一致。官方 IO/kinematics oracle 继续显式 blocked。证据见 `coverage/io-runtime.report.json`。
|
||||
|
||||
`K7-RECOVERY-01` 已完成当前恢复适配子集:固定 tick checkpoint 携带 context、event cursor、snapshot/state hash 和 operation ledger;Worker replay 严格拒绝损坏 checkpoint、未知 schema、缺号/重复事件和未确认副作用,abort 进入显式 `aborted`,恢复终点与连续运行状态 hash 对齐;native `sim_event_import` 与 Worker replay/recovery 测试均通过。官方 recovery oracle、完整 native/WASM 全状态对照和浏览器 OPFS 崩溃场景因锁定来源缺失继续显式 blocked。证据见 `coverage/workspace-recovery.report.json`、`web/worker/replay.mjs`、`web/workspace/recovery.mjs`。
|
||||
|
||||
`K8-REG-01` 已完成全配置回归适配:从统一配置图确定性枚举 264 个运动学入口(覆盖 233 个 `configs/sim` 主入口及 include/测试入口),逐入口保留 INI 形状、资源行、缺失/未解析资源、注册表归属和静态启动预检结果;资源图没有新增孤儿入口或未拥有模板。native/WASM 的 kinematics、motion、feedback、home/limit、IO 固定样本和重复运行 hash 均稳定,Worker replay、workspace recovery、sim session 摘要重复一致;恢复终点与连续状态 hash 对齐。当前资源缺失、未索引 include、未注册模块和官方 kinematics/recovery oracle 均显式 blocked,派生配置摘要不作为官方 golden。证据见 `coverage/configuration-regression.report.json`、`scripts/generate_configuration_regression.py`、`scripts/test_configuration_regression.py`。
|
||||
|
||||
`K4-HEX-01` 已完成:`genhexkins.c` 的六腿 Stewart 平台已接入统一 forward/inverse/mapping,使用真实 `linuxcnc/configs/sim/axis/vismach/hexapod-sim/hexapod.ini` 与 `kinematics.hal` 验证六腿闭式逆解、Newton 6x6 正解、确定性初始种子、alternate seed、非正 strut、迭代上限、最大误差、奇异 Jacobian、限位、HAL GUI/迭代 pin 和 native/WASM state hash;官方 oracle 继续显式 blocked。证据见 `coverage/genhex-kinematics.report.json`。
|
||||
|
||||
每个 K 里程碑都必须同时更新 `coverage/manifest.jsonl`、source map、golden 索引、任务指针和证据摘要。算法代码通过单测但没有配置入口或 UI 姿态证据时,不能推进到下一个里程碑。
|
||||
|
||||
@@ -38,6 +38,17 @@
|
||||
| `K4-DELTA-01` | Linear/rotary Delta 统一 forward/inverse/mapping;四个真实配置候选、默认/alternate root、工作空间无解、关节限位、rotary z=0 奇异、固定零迭代和 native/WASM state hash 对照通过;缺失 HAL 资源与官方 oracle 均显式 blocked | `coverage/delta-kinematics.report.json`, `wasm-core/src/sim_kinematics.c`, `scripts/generate_delta_kinematics_report.py` |
|
||||
| `K4-TRIPOD-01` | Point-vertex tripod 统一 forward/inverse/mapping;真实 `tripod.ini`/`tripodsim.hal`、XYZ/AD-BD-CD 映射、above/below 根分支、无解/基面奇异/限位/退化几何边界、固定零迭代和 native/WASM state hash 对照通过;官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续 blocked | `coverage/tripod-kinematics.report.json`, `wasm-core/src/sim_kinematics.c`, `scripts/generate_tripod_kinematics_report.py` |
|
||||
| `K4-HEX-01` | Genhex 六腿 Stewart 平台统一 forward/inverse/mapping;真实 `hexapod.ini`/`kinematics.hal`、六腿闭式逆解、Newton 6x6 正解、alternate seed、非正 strut、迭代上限、最大误差、奇异 Jacobian、限位、HAL GUI/迭代 pin 和 native/WASM state hash 对照通过;官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续 blocked | `coverage/genhex-kinematics.report.json`, `wasm-core/src/sim_kinematics.c`, `scripts/generate_genhex_kinematics_report.py` |
|
||||
| `K4-PENTA-01` | Pentakins 五腿并联统一 forward/inverse/mapping;真实 `pentapod.ini`/`kinematics.hal` 与派生非零姿态 seed、XYZAB 映射、roll/pitch 约束、tool-offset 双端几何、Newton 5x5 收敛/迭代上限/最大误差/奇异/非正杆长/限位、native/WASM JSON 和 state hash 对照通过;官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续 blocked | `coverage/pentapod-kinematics.report.json`, `wasm-core/src/sim_kinematics.c`, `scripts/generate_pentapod_kinematics_report.py` |
|
||||
| `K5-SWITCH-01` | switchkins 三槽位动态切换;真实 `xyzac-trt.ini`/`identityfirst` 配置、identity/xyzac-trt/userk 注册、pose 连续、状态保持、非法模式/缺模块/切换中断/abort 边界、native/WASM runner 与 state hash 对照通过;官方 oracle 因锁定来源缺少 `linuxcnc/unit_tests/kinematics/**` 继续显式 blocked | `coverage/switchkins-kinematics.report.json`, `wasm-core/src/sim_switchkins.c`, `wasm-core/tests/sim_switchkins_test.c` |
|
||||
| `K5-USER-01` | userk identity 模板与 ugenser/genser 构建期静态描述符;ABI v1、forward/inverse/static capability、switchkins type 2 绑定、FK/IK 调用状态、路径/`.so` 禁止、版本/能力/缺模块边界和 native/WASM state hash 对照通过;旧 ugenser CLI 明确排除,官方 oracle 继续 blocked | `coverage/userkins-kinematics.report.json`, `wasm-core/src/sim_userkins.c`, `wasm-core/tests/sim_userkins_test.c` |
|
||||
| `K5-CFG-01` | 18 个内建运动学类型的 ABI v1 构建期注册表;稳定名称/type、必需坐标、关节范围、7 类能力位、8 类参数掩码、18/18 实际 init、能力/参数/type/坐标/关节/未知/路径错误和 native/WASM registry hash 对照通过;官方 oracle 继续 blocked | `coverage/kinematics-registry.report.json`, `wasm-core/src/sim_kinematics_registry.c`, `wasm-core/tests/sim_kinematics_registry_test.c` |
|
||||
| `K6-MOTION-01` | 固定 tick TP→IK→motion adapter;复用 `sim_tp` 和 `sim_kinematics_inverse_ex`,64 槽关节样本队列、pause/resume、IK 奇异、非有限、容量满、planner/kinematics 事务回滚、native/WASM JSON 和 state hash 对照通过;官方 oracle 继续 blocked | `coverage/kinematics-motion.report.json`, `wasm-core/src/sim_kinematics_motion.c`, `wasm-core/tests/sim_kinematics_motion_test.c` |
|
||||
| `K6-MOTION-02` | actual-joint→FK feedback adapter;pose/joint/flags/tick/valid/error 状态、stale/nonfinite/joint-count/unsupported-forward 边界、错误时不覆盖有效 pose、native/WASM JSON 和 state hash 对照通过;官方 oracle 继续 blocked | `coverage/kinematics-feedback.report.json`, `wasm-core/src/sim_kinematics_feedback.c`, `wasm-core/tests/sim_kinematics_feedback_test.c` |
|
||||
| `K6-HOME-01` | fixed home sequence、`sim_kinematics_home`、joint limit/FK check;未 home、非法 sequence、越界、非有限、joint-count、失败保持有效 pose/joints、native/WASM JSON 和 state hash 对照通过;官方 oracle 继续 blocked | `coverage/kinematics-home-limit.report.json`, `wasm-core/src/sim_kinematics_home_limit.c`, `wasm-core/tests/sim_kinematics_home_limit_test.c` |
|
||||
| `K6-IO-01` | 固定 tick spindle/tool/probe IO;motion/iocontrol pins、主轴限速/到速/定向、prepare/change/abort/fault、probe pose latch、停主轴互锁、非法 pin/超限/未 ready/急停/恢复边界和 native/WASM JSON/hash 对照通过;官方 IO/kinematics oracle 继续 blocked | `coverage/io-runtime.report.json`, `wasm-core/include/sim_io.h`, `wasm-core/src/sim_io.c`, `wasm-core/tests/sim_io_test.c` |
|
||||
|
||||
| `K7-RECOVERY-01` | 固定 tick checkpoint/replay 与 workspace recovery 已接入;checkpoint 携带 context、cursor、snapshot/state hash 和 operation ledger;损坏 checkpoint、未知 schema、缺号/重复事件、未确认操作、abort 和恢复边界均 fail-closed;恢复终点与连续运行状态 hash 一致;native `sim_event_import` 与 Worker 对照通过,官方 recovery oracle 继续显式 blocked | `coverage/workspace-recovery.report.json`, `web/worker/replay.mjs`, `web/worker/sim-session.mjs`, `web/workspace/recovery.mjs`, `wasm-core/src/sim_event.c` |
|
||||
| `K8-REG-01` | 统一配置图确定性枚举 264 个运动学入口和 233 个 sim INI 入口;逐项资源/INI/注册表启动预检、缺失与未解析引用保留;图谱孤儿和未拥有模板为零;native/WASM kinematics/motion/feedback/home/IO 与 Worker replay/recovery 重复摘要稳定;官方 kinematics/recovery oracle 继续显式 blocked | `coverage/configuration-regression.report.json`, `scripts/generate_configuration_regression.py`, `scripts/test_configuration_regression.py`, `coverage/manifest.jsonl`, `context-lock.json` |
|
||||
|
||||
## 追溯规则
|
||||
|
||||
|
||||
40
nextTask.md
40
nextTask.md
@@ -8,20 +8,20 @@
|
||||
pointer_version: 3
|
||||
updated: 2026-08-21
|
||||
context_id: linuxcnc-2.10.0-pre0-wasm-m1-py-c1
|
||||
milestone: K4
|
||||
last_verified_checkpoint: K4-HEX-01-r01
|
||||
milestone: K8
|
||||
last_verified_checkpoint: K7-RECOVERY-01-r01
|
||||
task:
|
||||
id: K4-PENTA-01
|
||||
id: K8-REL-01
|
||||
status: pending
|
||||
title: pentapod integration
|
||||
title: compatibility and release closure
|
||||
owner: WS-0
|
||||
profile: feature
|
||||
profile: milestone
|
||||
next:
|
||||
id: K5-SWITCH-01
|
||||
id: K8-REL-02
|
||||
status: pending
|
||||
title: switchkins integration
|
||||
title: migration and rollback closure
|
||||
owner: WS-0
|
||||
profile: feature
|
||||
profile: milestone
|
||||
```
|
||||
|
||||
## Read
|
||||
@@ -41,17 +41,17 @@ python3 scripts/validate_task_pointer.py
|
||||
|
||||
## Task
|
||||
|
||||
**目标**:完成 pentapod 五腿并联运动学的统一 forward/inverse/mapping 集成。
|
||||
**目标**:完成兼容性与发布闭合审计,确认 K0-K8 已产出可重建的来源、manifest、ABI/protocol/runtime、配置回归和恢复证据,并把未闭合输入、REAL-EXCLUDED 项和官方 oracle 阻塞明确交给后续发布门禁。
|
||||
|
||||
**直接输入**:`coverage/genhex-kinematics.report.json`、`coverage/kinematics-source-closure.report.json`、`linuxcnc/src/emc/kinematics/pentakins.c`、`linuxcnc/src/emc/kinematics/pentakins.h`、`linuxcnc/configs/sim/axis/vismach/hexapod-sim/hexapod.ini`、`wasm-core/src/sim_kinematics.c`、`context-lock.json`。
|
||||
**直接输入**:`coverage/configuration-regression.report.json`、`coverage/manifest.jsonl`、`coverage/source-index.jsonl`、`context-lock.json`、`python-runtime/manifest.json`、`docs/linuxcnc-web-wasm-full-port-implementation.md`。
|
||||
|
||||
**必须产出**:pentapod 真实/派生配置样本、五腿 FK/IK 与姿态约束、迭代收敛/不收敛/奇异/限位边界、native/WASM 对照和 blocked oracle 清单;更新迁移计划、manifest 和任务历史。
|
||||
**必须产出**:兼容性矩阵、来源/许可证闭合摘要、发布阻塞项和可重建 release evidence index;确认 ABI/protocol/schema/context lock 一致,不将 blocked 或派生配置冒充 accepted。
|
||||
|
||||
**完成条件**:配置候选;工作空间边界、姿态约束、奇异/无解、迭代上限、关节限位和 state hash 稳定;官方 oracle 缺失继续显式 blocked。
|
||||
**完成条件**:受影响 manifest 条目均有 owner、输入、输出、测试、差异和 oracle 状态;无孤儿、无未解释差异、无静默降级;release 结论对缺失官方 oracle、资源和浏览器矩阵保持显式 blocked。
|
||||
|
||||
**非目标**:本任务不批准替代 oracle 契约、不实现完整运动学 ABI、不接入 UI 运行时和 Python、不把配置 smoke 当作数值 golden。
|
||||
**非目标**:不修改 ABI/protocol/schema,不伪造许可证或官方 golden,不实现迁移/回滚工具(由 K8-REL-02 负责),不把当前报告升级为发布候选。
|
||||
|
||||
**回滚点**:删除本任务新增 pentapod runner/golden 扩展,保留 `K4-HEX-01` 报告和 `M2-CORE-04` blocked 证据,并恢复本指针。
|
||||
**回滚点**:保留 `K8-REG-01` 报告和原 context lock;删除仅限发布审计的索引/文档扩展即可恢复,不回滚已验证运行时。
|
||||
|
||||
## Next
|
||||
|
||||
@@ -61,18 +61,18 @@ python3 scripts/validate_task_pointer.py
|
||||
|
||||
```bash
|
||||
python3 scripts/generate_source_inventory.py
|
||||
python3 scripts/generate_kinematics_closure_report.py
|
||||
python3 scripts/generate_kinematics_config_graph.py
|
||||
python3 scripts/generate_kinematics_oracle_report.py
|
||||
python3 scripts/generate_configuration_regression.py
|
||||
cmake --build build-native && ctest --test-dir build-native --output-on-failure
|
||||
node web/worker/test_replay.mjs
|
||||
node web/workspace/test_recovery.mjs
|
||||
python3 scripts/validate_baseline.py
|
||||
python3 scripts/validate_task_pointer.py
|
||||
python3 -m unittest discover -s scripts -p 'test_*.py' -v
|
||||
find linuxcnc/src/emc/kinematics -maxdepth 1 -type f -print | sort
|
||||
rg -n --glob '*.ini' --glob '*.hal' --glob '*.py' 'KINEMATICS|loadrt .*kins|switchkins|corexy_by_hal' linuxcnc/configs/sim linuxcnc/tests
|
||||
python3 scripts/validate_download_index.py
|
||||
rg -n 'REAL-EXCLUDED|blocked|accepted|configuration-regression' coverage/manifest.jsonl coverage/configuration-regression.report.json docs
|
||||
```
|
||||
|
||||
最近证据:`coverage/genhex-kinematics.report.json` 已验证真实 hexapod-sim 配置、六腿闭式逆解、Newton 6x6 正解、alternate seed、迭代/奇异/限位边界、native/WASM 字节一致和 state hash 一致;缺失 `linuxcnc/unit_tests/kinematics/**` 官方 oracle 继续显式 blocked。下一任务处理 `pentakins.c` 五腿并联约束。
|
||||
最近证据:`coverage/configuration-regression.report.json` 已枚举 264 个运动学入口和 233 个 sim 配置入口;图谱孤儿、未拥有模板和资源孤儿均为零,native/WASM 及 Worker 固定 tick/recovery 摘要重复稳定。资源缺失、未注册模块、官方 oracle 和完整浏览器矩阵继续显式 blocked。下一任务执行兼容性与发布闭合审计。
|
||||
|
||||
## Stop
|
||||
|
||||
|
||||
415
scripts/generate_configuration_regression.py
Normal file
415
scripts/generate_configuration_regression.py
Normal file
@@ -0,0 +1,415 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the deterministic K8 full-configuration regression report.
|
||||
|
||||
The LinuxCNC checkout used by this project is intentionally a source/config
|
||||
fixture rather than a complete desktop installation. This report therefore
|
||||
separates a deterministic static startup preflight from the native/WASM and
|
||||
Worker runtime probes. Missing resources and unavailable official oracles
|
||||
remain explicit blockers; they are never converted into passing goldens.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
CONTEXT_PATH = Path("context-lock.json")
|
||||
GRAPH_PATH = Path("coverage/kinematics-config-graph.report.json")
|
||||
RESOURCE_PATH = Path("coverage/config-resources.jsonl")
|
||||
RESOURCE_REPORT_PATH = Path("coverage/config-resources.report.json")
|
||||
RECOVERY_PATH = Path("coverage/workspace-recovery.report.json")
|
||||
REGISTRY_PATH = Path("coverage/kinematics-registry.report.json")
|
||||
|
||||
RUNTIME_COMMANDS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("kinematics", ("build-native/sim_kinematics_test",)),
|
||||
("motion", ("build-native/sim_kinematics_motion_test",)),
|
||||
("feedback", ("build-native/sim_kinematics_feedback_test",)),
|
||||
("home_limit", ("build-native/sim_kinematics_home_limit_test",)),
|
||||
("io", ("build-native/sim_io_test",)),
|
||||
)
|
||||
WASM_COMMANDS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("kinematics", ("node", "build-wasm/sim_kinematics_wasm_test.js")),
|
||||
("motion", ("node", "build-wasm/sim_kinematics_motion_wasm_test.js")),
|
||||
("feedback", ("node", "build-wasm/sim_kinematics_feedback_wasm_test.js")),
|
||||
("home_limit", ("node", "build-wasm/sim_kinematics_home_limit_wasm_test.js")),
|
||||
("io", ("node", "build-wasm/sim_io_wasm_test.js")),
|
||||
)
|
||||
WORKER_COMMANDS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("replay", ("node", "web/worker/test_replay.mjs")),
|
||||
("workspace_recovery", ("node", "web/workspace/test_recovery.mjs")),
|
||||
("sim_session", ("node", "web/worker/test_sim_session.mjs")),
|
||||
)
|
||||
UNRESOLVED_STATUSES = {"unresolved", "host_or_variable_path", "outside_repo"}
|
||||
KINEMATICS_SAMPLE_KEYS = {
|
||||
"corexykins": "corexy",
|
||||
"rosekins": "rose",
|
||||
"maxkins": "maxkins",
|
||||
"rotatekins": "rotatekins",
|
||||
"5axiskins": "fiveaxiskins",
|
||||
"xyzac-trt-kins": "xyzac_trt",
|
||||
"xyzbc-trt-kins": "xyzbc_trt",
|
||||
"scarakins": "scara",
|
||||
"pumakins": "puma",
|
||||
"scorbot-kins": "scorbot",
|
||||
"genserkins": "genser",
|
||||
"lineardeltakins": "lineardelta",
|
||||
"rotarydeltakins": "rotarydelta",
|
||||
"tripodkins": "tripod",
|
||||
"genhexkins": "genhex",
|
||||
"pentakins": "pentapod",
|
||||
}
|
||||
|
||||
|
||||
def canonical(value: Any) -> str:
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def digest(value: Any) -> str:
|
||||
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
handle = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
handle.update(block)
|
||||
return handle.hexdigest()
|
||||
|
||||
|
||||
def read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{path} must contain an object")
|
||||
return value
|
||||
|
||||
|
||||
def read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{path}:{line_number} must contain an object")
|
||||
rows.append(value)
|
||||
return rows
|
||||
|
||||
|
||||
def source_record(root: Path, path: Path) -> dict[str, Any]:
|
||||
relative = path.relative_to(root).as_posix()
|
||||
return {"path": relative, "sha256": sha256(path)} if path.is_file() else {"path": relative, "sha256": None, "status": "missing"}
|
||||
|
||||
|
||||
def parse_ini_shape(path: Path) -> dict[str, Any]:
|
||||
"""Check only syntax needed by the preflight; it is not an INI oracle."""
|
||||
if not path.is_file():
|
||||
return {"file": False, "sections": [], "balanced_sections": False}
|
||||
sections: list[str] = []
|
||||
balanced = True
|
||||
for raw in path.read_text(encoding="utf-8", errors="strict").splitlines():
|
||||
line = raw.split("#", 1)[0].split(";", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("["):
|
||||
if not line.endswith("]"):
|
||||
balanced = False
|
||||
else:
|
||||
sections.append(line[1:-1].strip().upper())
|
||||
return {"file": True, "sections": sorted(set(sections)), "balanced_sections": balanced}
|
||||
|
||||
|
||||
def registered_kinematics(root: Path) -> set[str]:
|
||||
registry = read_json(REGISTRY_PATH if REGISTRY_PATH.is_absolute() else root / REGISTRY_PATH)
|
||||
rows = registry.get("samples", {}).get("native", {}).get("entries", [])
|
||||
return {str(row.get("name")) for row in rows if row.get("name")}
|
||||
|
||||
|
||||
def graph_entries(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||
graph = read_json(root / GRAPH_PATH)
|
||||
entries = [row for row in graph.get("nodes", []) if row.get("kind") == "kinematics"]
|
||||
entries.sort(key=lambda row: str(row.get("node_id", "")))
|
||||
return graph, entries
|
||||
|
||||
|
||||
def resource_summaries(root: Path, resource_report: dict[str, Any], rows: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
|
||||
by_entrypoint: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for row in rows:
|
||||
by_entrypoint[str(row.get("entrypoint", ""))].append(row)
|
||||
indexed = {str(path) for path in resource_report.get("entrypoint_paths", [])}
|
||||
result: dict[str, list[dict[str, Any]]] = {}
|
||||
for path in sorted(indexed):
|
||||
result[path] = by_entrypoint.get(path, [])
|
||||
return result
|
||||
|
||||
|
||||
def preflight_entry(root: Path, row: dict[str, Any], resources: dict[str, list[dict[str, Any]]], indexed_paths: set[str], registered: set[str]) -> dict[str, Any]:
|
||||
path = str(row["path"])
|
||||
module = str(row.get("module", ""))
|
||||
resource_rows = resources.get(path, [])
|
||||
missing = sorted({str(item.get("path")) for item in resource_rows if item.get("parse_status") == "missing"})
|
||||
unresolved = sorted({str(item.get("path")) for item in resource_rows if item.get("parse_status") in UNRESOLVED_STATUSES})
|
||||
ini_shape = parse_ini_shape(root / path)
|
||||
if module not in registered:
|
||||
startup_status = "blocked_unregistered_kinematics"
|
||||
elif path not in indexed_paths and path.startswith("linuxcnc/configs/sim/"):
|
||||
startup_status = "blocked_unindexed_resource_graph"
|
||||
elif missing or unresolved:
|
||||
startup_status = "blocked_resource"
|
||||
elif not ini_shape["file"] or not ini_shape["balanced_sections"]:
|
||||
startup_status = "blocked_ini_syntax"
|
||||
else:
|
||||
startup_status = "static_ready"
|
||||
if path not in indexed_paths:
|
||||
resource_status = "unindexed"
|
||||
elif missing and unresolved:
|
||||
resource_status = "blocked_missing_and_unresolved"
|
||||
elif missing:
|
||||
resource_status = "blocked_missing"
|
||||
elif unresolved:
|
||||
resource_status = "blocked_unresolved"
|
||||
else:
|
||||
resource_status = "ready"
|
||||
return {
|
||||
"config_id": row.get("node_id"),
|
||||
"path": path,
|
||||
"line": row.get("line"),
|
||||
"module": module,
|
||||
"family": row.get("family"),
|
||||
"resource_status": resource_status,
|
||||
"missing_resources": missing,
|
||||
"unresolved_resources": unresolved,
|
||||
"resource_rows": len(resource_rows),
|
||||
"ini": ini_shape,
|
||||
"kinematics_registered": module in registered,
|
||||
"startup_preflight": startup_status,
|
||||
}
|
||||
|
||||
|
||||
def run_json_command(root: Path, command: tuple[str, ...]) -> dict[str, Any]:
|
||||
executable = root / command[0] if command[0] not in {"node"} else root / command[1]
|
||||
if not executable.exists():
|
||||
return {"status": "blocked", "reason": f"missing_runtime_artifact:{executable.relative_to(root).as_posix()}"}
|
||||
completed = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if completed.returncode != 0:
|
||||
return {"status": "failed", "exit_code": completed.returncode, "stderr_sha256": digest(completed.stderr)}
|
||||
json_lines = [line.strip() for line in completed.stdout.splitlines() if line.lstrip().startswith("{")]
|
||||
if not json_lines:
|
||||
return {"status": "failed", "reason": "runtime_did_not_emit_json", "stdout_sha256": digest(completed.stdout)}
|
||||
try:
|
||||
sample = json.loads(json_lines[-1])
|
||||
except json.JSONDecodeError:
|
||||
return {"status": "failed", "reason": "runtime_emitted_invalid_json", "stdout_sha256": digest(completed.stdout)}
|
||||
return {"status": "passed", "schema": sample.get("schema"), "sample": sample, "sample_sha256": digest(sample)}
|
||||
|
||||
|
||||
def run_runtime_group(root: Path, commands: Iterable[tuple[str, tuple[str, ...]]]) -> dict[str, Any]:
|
||||
records: dict[str, Any] = {}
|
||||
for name, command in commands:
|
||||
first = run_json_command(root, command)
|
||||
second = run_json_command(root, command)
|
||||
first_key = first.get("sample_sha256")
|
||||
second_key = second.get("sample_sha256")
|
||||
records[name] = {
|
||||
"command": list(command),
|
||||
"first": first,
|
||||
"second": second,
|
||||
"repeat_stable": bool(first_key and first_key == second_key),
|
||||
}
|
||||
passed = all(row["first"].get("status") == "passed" and row["repeat_stable"] for row in records.values())
|
||||
return {"status": "passed" if passed else "blocked", "records": records, "summary_sha256": digest(records)}
|
||||
|
||||
|
||||
def run_worker_group(root: Path) -> dict[str, Any]:
|
||||
records: dict[str, Any] = {}
|
||||
for name, command in WORKER_COMMANDS:
|
||||
target = root / command[-1]
|
||||
if not target.is_file():
|
||||
records[name] = {"command": list(command), "status": "blocked", "reason": "missing_worker_test"}
|
||||
continue
|
||||
first = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
second = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
first_value = {"exit_code": first.returncode, "stdout": first.stdout, "stderr": first.stderr}
|
||||
second_value = {"exit_code": second.returncode, "stdout": second.stdout, "stderr": second.stderr}
|
||||
records[name] = {
|
||||
"command": list(command),
|
||||
"status": "passed" if first.returncode == 0 else "failed",
|
||||
"first_sha256": digest(first_value),
|
||||
"second_sha256": digest(second_value),
|
||||
"repeat_stable": digest(first_value) == digest(second_value),
|
||||
"stdout": first.stdout.strip(),
|
||||
}
|
||||
passed = all(value.get("status") == "passed" and value.get("repeat_stable") for value in records.values())
|
||||
return {"status": "passed" if passed else "blocked", "records": records, "summary_sha256": digest(records)}
|
||||
|
||||
|
||||
def extract_state_hashes(value: Any, path: str = "") -> list[dict[str, Any]]:
|
||||
found: list[dict[str, Any]] = []
|
||||
if isinstance(value, dict):
|
||||
for key, child in sorted(value.items()):
|
||||
child_path = f"{path}.{key}" if path else key
|
||||
if key in {"state_hash", "hash", "repeat_hash"} and isinstance(child, (int, str)):
|
||||
found.append({"path": child_path, "value": child})
|
||||
found.extend(extract_state_hashes(child, child_path))
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
found.extend(extract_state_hashes(child, f"{path}[{index}]"))
|
||||
return found
|
||||
|
||||
|
||||
def build_config_state_summary(entries: list[dict[str, Any]], runtime: dict[str, Any], registry: set[str]) -> dict[str, Any]:
|
||||
samples: dict[str, Any] = {}
|
||||
for name, record in runtime.get("records", {}).items():
|
||||
sample = record.get("first", {}).get("sample")
|
||||
if isinstance(sample, dict):
|
||||
samples[name] = {
|
||||
"schema": sample.get("schema"),
|
||||
"sample_sha256": record["first"].get("sample_sha256"),
|
||||
"state_hashes": extract_state_hashes(sample),
|
||||
}
|
||||
rows = []
|
||||
for entry in entries:
|
||||
module = str(entry.get("module", ""))
|
||||
sample_key = KINEMATICS_SAMPLE_KEYS.get(module)
|
||||
rows.append({
|
||||
"config_id": entry.get("config_id"),
|
||||
"module": module,
|
||||
"sample": sample_key,
|
||||
"sample_status": "verified_sample" if sample_key in samples else "registry_only" if module in registry else "blocked_unregistered",
|
||||
"state_digest": digest({"module": module, "sample": samples.get(sample_key), "registry": module in registry}),
|
||||
})
|
||||
rows.sort(key=lambda row: str(row["config_id"]))
|
||||
return {"tick_policy": "fixed logical tick; wall clock excluded", "entries": rows, "runtime_samples": samples, "summary_sha256": digest(rows)}
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, Any]:
|
||||
root = root.resolve()
|
||||
context = read_json(root / CONTEXT_PATH)
|
||||
graph, entries = graph_entries(root)
|
||||
resource_report = read_json(root / RESOURCE_REPORT_PATH)
|
||||
resources_rows = read_jsonl(root / RESOURCE_PATH)
|
||||
resources = resource_summaries(root, resource_report, resources_rows)
|
||||
indexed_paths = set(resources)
|
||||
registered = registered_kinematics(root)
|
||||
preflight = [preflight_entry(root, row, resources, indexed_paths, registered) for row in entries]
|
||||
preflight.sort(key=lambda row: str(row["config_id"]))
|
||||
runtime = run_runtime_group(root, RUNTIME_COMMANDS)
|
||||
wasm = run_runtime_group(root, WASM_COMMANDS)
|
||||
worker = run_worker_group(root)
|
||||
native_wasm_equal = all(
|
||||
runtime["records"].get(name, {}).get("first", {}).get("sample_sha256")
|
||||
== wasm["records"].get(name, {}).get("first", {}).get("sample_sha256")
|
||||
and runtime["records"].get(name, {}).get("first", {}).get("status") == "passed"
|
||||
for name, _command in RUNTIME_COMMANDS
|
||||
)
|
||||
graph_orphans = list(graph.get("closure", {}).get("orphan_entry_ids", []))
|
||||
unowned = list(graph.get("closure", {}).get("unowned_template_ids", []))
|
||||
resource_orphans = sorted({str(row.get("entrypoint")) for row in resources_rows if str(row.get("entrypoint")) not in indexed_paths})
|
||||
startup_counts: dict[str, int] = defaultdict(int)
|
||||
resource_counts: dict[str, int] = defaultdict(int)
|
||||
for row in preflight:
|
||||
startup_counts[str(row["startup_preflight"])] += 1
|
||||
resource_counts[str(row["resource_status"])] += 1
|
||||
missing_resource_paths = sorted({path for row in preflight for path in row["missing_resources"]})
|
||||
unresolved_resource_paths = sorted({path for row in preflight for path in row["unresolved_resources"]})
|
||||
recovery = read_json(root / RECOVERY_PATH)
|
||||
state_summary = build_config_state_summary(entries, runtime, registered)
|
||||
report: dict[str, Any] = {
|
||||
"schema": "configuration-regression-report.v1",
|
||||
"context_id": context.get("context_id"),
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"scope": {"configs_root": "linuxcnc/configs/sim", "tests_root": "linuxcnc/tests", "preflight_only": True},
|
||||
"inputs": {
|
||||
# The report is itself pinned by context-lock. Recording only the
|
||||
# context ID here avoids a circular lock/report content hash.
|
||||
"context_lock": {"path": CONTEXT_PATH.as_posix(), "context_id": context.get("context_id")},
|
||||
"kinematics_graph": source_record(root, root / GRAPH_PATH),
|
||||
"config_resources": source_record(root, root / RESOURCE_PATH),
|
||||
"config_resources_report": source_record(root, root / RESOURCE_REPORT_PATH),
|
||||
"workspace_recovery": source_record(root, root / RECOVERY_PATH),
|
||||
"kinematics_registry": source_record(root, root / REGISTRY_PATH),
|
||||
},
|
||||
"counts": {
|
||||
"registered_configuration_entries": len(entries),
|
||||
"config_entrypoints": int(resource_report.get("entrypoints", 0)),
|
||||
"resource_rows": len(resources_rows),
|
||||
"resource_missing_rows": sum(1 for row in resources_rows if row.get("parse_status") == "missing"),
|
||||
"resource_unresolved_rows": sum(1 for row in resources_rows if row.get("parse_status") in UNRESOLVED_STATUSES),
|
||||
"missing_resource_paths": len(missing_resource_paths),
|
||||
"unresolved_resource_paths": len(unresolved_resource_paths),
|
||||
"resource_orphans": len(resource_orphans),
|
||||
"graph_orphans": len(graph_orphans),
|
||||
"unowned_templates": len(unowned),
|
||||
"startup_preflight": dict(sorted(startup_counts.items())),
|
||||
"resource_status": dict(sorted(resource_counts.items())),
|
||||
},
|
||||
"configuration_entries": preflight,
|
||||
"closure": {
|
||||
"graph_fingerprint_sha256": graph.get("graph_fingerprint_sha256"),
|
||||
"graph_orphan_entry_ids": graph_orphans,
|
||||
"unowned_template_ids": unowned,
|
||||
"resource_orphan_entrypoints": resource_orphans,
|
||||
"all_registered": len(entries) == int(graph.get("counts", {}).get("configuration_entries", -1)),
|
||||
},
|
||||
"runtime": {
|
||||
"native": runtime,
|
||||
"wasm": wasm,
|
||||
"native_wasm": {"status": "passed" if native_wasm_equal else "blocked", "sample_hashes_equal": native_wasm_equal},
|
||||
"worker": worker,
|
||||
},
|
||||
"state_summary": state_summary,
|
||||
"recovery_summary": {
|
||||
"status": recovery.get("status"),
|
||||
"implementation_status": recovery.get("implementation_status"),
|
||||
"checkpoint_schema": recovery.get("replay", {}).get("schema"),
|
||||
"replay_state_hash": recovery.get("replay", {}).get("state_hash"),
|
||||
"continuous_state_hash": recovery.get("replay", {}).get("continuous_state_hash"),
|
||||
"endpoints_equal": recovery.get("replay", {}).get("endpoints_equal"),
|
||||
"oracle_status": recovery.get("oracle", {}).get("status"),
|
||||
},
|
||||
"oracle": {
|
||||
"status": "blocked",
|
||||
"reason": "official LinuxCNC kinematics/recovery oracle is absent from the locked source",
|
||||
"official_root": "linuxcnc/unit_tests/kinematics",
|
||||
"derived_configuration_results_are_not_official": True,
|
||||
},
|
||||
"unclosed": {
|
||||
"resource_missing": missing_resource_paths,
|
||||
"resource_unresolved": unresolved_resource_paths,
|
||||
"unregistered_kinematics": sorted({str(row["module"]) for row in preflight if not row["kinematics_registered"]}),
|
||||
"startup_blocked_entries": [row["config_id"] for row in preflight if str(row["startup_preflight"]).startswith("blocked")],
|
||||
},
|
||||
"report_sha256": None,
|
||||
}
|
||||
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/configuration-regression.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(
|
||||
"configuration_regression="
|
||||
f"entries={report['counts']['registered_configuration_entries']} "
|
||||
f"startup_blocked={len(report['unclosed']['startup_blocked_entries'])} "
|
||||
f"resource_orphans={report['counts']['resource_orphans']} "
|
||||
f"native_wasm={report['runtime']['native_wasm']['status']} "
|
||||
f"worker={report['runtime']['worker']['status']}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
113
scripts/generate_io_report.py
Normal file
113
scripts/generate_io_report.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for spindle, tool changer and probe IO."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
INPUTS = {
|
||||
"io_header": "wasm-core/include/sim_io.h",
|
||||
"io_source": "wasm-core/src/sim_io.c",
|
||||
"hal_header": "wasm-core/include/sim_hal.h",
|
||||
"hal_source": "wasm-core/src/sim_hal.c",
|
||||
"test": "wasm-core/tests/sim_io_test.c",
|
||||
"linuxcnc_motion": "linuxcnc/src/emc/motion/motion.c",
|
||||
"linuxcnc_control": "linuxcnc/src/emc/motion/control.c",
|
||||
"linuxcnc_iocontrol": "linuxcnc/src/emc/iotask/ioControl.cc",
|
||||
"linuxcnc_iocontrol_v2": "linuxcnc/src/emc/iotask/ioControl_v2.cc",
|
||||
"linuxcnc_sim_spindle": "linuxcnc/src/hal/components/sim_spindle.comp",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "io-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit io-samples.v1")
|
||||
|
||||
|
||||
def canonical(value: object) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_io_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_io_wasm_test.js")], root)
|
||||
report = {
|
||||
"schema": "io-runtime-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"registration": {
|
||||
"spindle_capacity": 8,
|
||||
"tool_pocket_capacity": 1001,
|
||||
"fixed_tick": True,
|
||||
"hal_pins": ["spindle.0.*", "spindle.1.*", "iocontrol.0.tool-*", "motion.probe-input"],
|
||||
"scheduler": "sim-io-servo",
|
||||
},
|
||||
"pipeline": {
|
||||
"spindle": ["command", "inhibit_or_fault_gate", "acceleration_limit", "at_speed_and_brake", "revs_and_index"],
|
||||
"tool": ["prepare", "prepared_ack", "spindle_stopped_gate", "change", "changed_ack", "abort_or_fault"],
|
||||
"probe": ["arm", "input_edge", "forward_pose_latch", "trip_or_disarm"],
|
||||
"kinematics_association": "probe latch consumes the motion feedback pose; tool change requires stopped spindle; estop cancels motion-facing IO",
|
||||
},
|
||||
"boundaries": {
|
||||
"invalid_pin_type": native["pin"]["invalid_type"],
|
||||
"spindle_limit": native["spindle"]["limit"],
|
||||
"tool_change_while_spinning": native["spindle"]["tool_busy"],
|
||||
"probe_trigger": native["probe"]["status"],
|
||||
"probe_state": native["probe"]["state"],
|
||||
"estop_command": native["estop"]["command"],
|
||||
"estop_recovery_before_clear": native["estop"]["recover_before_clear"],
|
||||
"recovery": native["estop"]["recover"],
|
||||
"fault_clear_while_asserted": native["tool"]["fault_clear"],
|
||||
"state_hash_stable": native["state_hash"] == native["repeat_hash"],
|
||||
},
|
||||
"samples": {
|
||||
"native": native,
|
||||
"wasm": wasm,
|
||||
"byte_identical_json": canonical(native) == canonical(wasm),
|
||||
"state_hash_identical": native["state_hash"] == wasm["state_hash"] and native["repeat_hash"] == wasm["repeat_hash"],
|
||||
},
|
||||
"native_wasm": {
|
||||
"status": "passed",
|
||||
"native_command": "build-native/sim_io_test",
|
||||
"wasm_command": "node build-wasm/sim_io_wasm_test.js",
|
||||
},
|
||||
"oracle": {
|
||||
"status": "blocked",
|
||||
"reason": "locked LinuxCNC source has no linuxcnc/unit_tests/kinematics oracle root and no browser IO oracle",
|
||||
"blocked_samples": ["spindle acceleration/at-speed", "spindle orient", "tool prepare/change", "tool abort/fault/recovery", "probe pose latch", "estop and recovery"],
|
||||
},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(canonical(report).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/io-runtime.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"io_runtime={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
71
scripts/generate_kinematics_feedback_report.py
Normal file
71
scripts/generate_kinematics_feedback_report.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for FK feedback integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
INPUTS = {
|
||||
"feedback_header": "wasm-core/include/sim_kinematics_feedback.h",
|
||||
"feedback_source": "wasm-core/src/sim_kinematics_feedback.c",
|
||||
"kinematics_header": "wasm-core/include/sim_kinematics.h",
|
||||
"kinematics_source": "wasm-core/src/sim_kinematics.c",
|
||||
"test": "wasm-core/tests/sim_kinematics_feedback_test.c",
|
||||
"linuxcnc_control": "linuxcnc/src/emc/motion/control.c",
|
||||
"linuxcnc_motion": "linuxcnc/src/emc/motion/motion.c",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "kinematics-feedback-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit kinematics-feedback-samples.v1")
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_kinematics_feedback_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_kinematics_feedback_wasm_test.js")], root)
|
||||
report = {
|
||||
"schema": "kinematics-feedback-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"pipeline": {"order": ["actual_joint_feedback", "forward_kinematics", "pose_flags_commit"], "last_valid_pose_policy": "retain on error", "state_fields": ["pose", "joints", "forward_flags", "inverse_flags", "tick", "valid", "last_error", "kinematics_error"]},
|
||||
"configuration_candidates": [{"id": "trivkins-xyz-feedback", "kind": "derived", "path": INPUTS["test"], "coordinates": "XYZ"}, {"id": "inverse-only-feedback-rejection", "kind": "derived", "path": INPUTS["test"], "coordinates": "XYZBC", "purpose": "forward feedback rejects unsupported mode without publishing a pose"}],
|
||||
"boundaries": {"stale_read": native["stale_read"], "nonfinite": native["nonfinite"], "joint_count": native["count_error"], "forward_failure": native["singular"]["status"], "forward_failure_kinematics": native["singular"]["kinematics_error"], "failed_feedback_valid": native["singular"]["valid"], "state_hash_stable": native["nominal"]["state_hash"] == native["nominal"]["repeat_hash"]},
|
||||
"samples": {"native": native, "wasm": wasm, "byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")), "state_hash_identical": native["nominal"]["state_hash"] == wasm["nominal"]["state_hash"]},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_kinematics_feedback_test", "wasm_command": "node build-wasm/sim_kinematics_feedback_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["nominal FK feedback", "stale read", "nonfinite feedback", "joint count mismatch", "unsupported forward", "state hash"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/kinematics-feedback.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"kinematics_feedback={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
71
scripts/generate_kinematics_home_limit_report.py
Normal file
71
scripts/generate_kinematics_home_limit_report.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for home and limit integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
INPUTS = {
|
||||
"home_header": "wasm-core/include/sim_kinematics_home_limit.h",
|
||||
"home_source": "wasm-core/src/sim_kinematics_home_limit.c",
|
||||
"kinematics_header": "wasm-core/include/sim_kinematics.h",
|
||||
"kinematics_source": "wasm-core/src/sim_kinematics.c",
|
||||
"test": "wasm-core/tests/sim_kinematics_home_limit_test.c",
|
||||
"linuxcnc_homing": "linuxcnc/src/emc/motion/homing.c",
|
||||
"linuxcnc_homemod": "linuxcnc/src/emc/motion/homemod.c",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "kinematics-home-limit-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit kinematics-home-limit-samples.v1")
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_kinematics_home_limit_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_kinematics_home_limit_wasm_test.js")], root)
|
||||
report = {
|
||||
"schema": "kinematics-home-limit-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"pipeline": {"order": ["home_sequence", "kinematics_home", "limit_check", "forward_feedback"], "state_fields": ["homed", "home_sequence", "tick", "pose", "joints", "last_error", "limit_error", "kinematics_error"], "failed_limit_policy": "retain last valid pose and joints"},
|
||||
"configuration_candidates": [{"id": "trivkins-xyz-home-limits", "kind": "derived", "path": INPUTS["test"], "coordinates": "XYZ", "limits": "[-5,5] per axis"}],
|
||||
"boundaries": {"unhomed": native["before_home"], "sequence": native["sequence_error"], "home": native["home_status"], "limit": native["limit_status"], "nonfinite": native["nonfinite_status"], "joint_count": native["count_status"], "state_hash_stable": native["state"]["hash"] == native["state"]["repeat_hash"], "retains_valid_pose": native["state"]["pose"] == [1.0, 2.0, 3.0]},
|
||||
"samples": {"native": native, "wasm": wasm, "byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")), "state_hash_identical": native["state"]["hash"] == wasm["state"]["hash"]},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_kinematics_home_limit_test", "wasm_command": "node build-wasm/sim_kinematics_home_limit_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["unhomed check", "home sequence", "kinematics home", "joint limit", "nonfinite joint", "joint count", "state hash"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/kinematics-home-limit.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"kinematics_home_limit={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
73
scripts/generate_kinematics_motion_report.py
Normal file
73
scripts/generate_kinematics_motion_report.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for TP -> IK -> motion integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
INPUTS = {
|
||||
"motion_header": "wasm-core/include/sim_motion_queue.h",
|
||||
"motion_source": "wasm-core/src/sim_motion_queue.c",
|
||||
"kinematics_motion_header": "wasm-core/include/sim_kinematics_motion.h",
|
||||
"kinematics_motion_source": "wasm-core/src/sim_kinematics_motion.c",
|
||||
"kinematics_header": "wasm-core/include/sim_kinematics.h",
|
||||
"kinematics_source": "wasm-core/src/sim_kinematics.c",
|
||||
"test": "wasm-core/tests/sim_kinematics_motion_test.c",
|
||||
"linuxcnc_control": "linuxcnc/src/emc/motion/control.c",
|
||||
"linuxcnc_motion": "linuxcnc/src/emc/motion/motion.c",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "kinematics-motion-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit kinematics-motion-samples.v1")
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_kinematics_motion_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_kinematics_motion_wasm_test.js")], root)
|
||||
report = {
|
||||
"schema": "kinematics-motion-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"pipeline": {"order": ["fixed_tick", "trajectory_planner", "kinematics_inverse", "atomic_joint_queue"], "joint_queue_capacity": 64, "planner_queue_capacity": 32, "transaction": "snapshot planner and kinematics; rollback on IK, planner, nonfinite, or capacity failure"},
|
||||
"configuration_candidates": [{"id": "trivkins-xyz-fixed-tick", "kind": "derived", "path": INPUTS["test"], "coordinates": "XYZ", "purpose": "deterministic TP to inverse to joint sample"}, {"id": "xyzbc-singular-rollback", "kind": "derived", "path": INPUTS["test"], "coordinates": "XYZBC", "purpose": "B=90 inverse failure leaves planner untouched"}],
|
||||
"boundaries": {"pause": native["nominal"]["pause_status"], "capacity": native["capacity"]["status"], "inverse_failure": native["inverse_failure"]["status"], "inverse_error": native["inverse_failure"]["kinematics_error"], "nonfinite": native["nonfinite_status"], "capacity_planner_rollback": native["capacity"]["planner_before"] == native["capacity"]["planner_after"], "inverse_planner_rollback": native["inverse_failure"]["planner_before"] == native["inverse_failure"]["planner_after"], "state_hash_stable": native["nominal"]["state_hash"] == native["nominal"]["repeat_hash"]},
|
||||
"samples": {"native": native, "wasm": wasm, "byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")), "state_hash_identical": native["nominal"]["state_hash"] == wasm["nominal"]["state_hash"] and native["capacity"]["state_hash"] == wasm["capacity"]["state_hash"]},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_kinematics_motion_test", "wasm_command": "node build-wasm/sim_kinematics_motion_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["TP to IK fixed tick", "joint queue capacity", "pause/resume", "singular IK rollback", "nonfinite segment"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/kinematics-motion.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"kinematics_motion={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
90
scripts/generate_kinematics_registry_report.py
Normal file
90
scripts/generate_kinematics_registry_report.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for the static kinematics registry."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
INPUTS = {
|
||||
"linuxcnc_header": "linuxcnc/src/emc/kinematics/kinematics.h",
|
||||
"sim_header": "wasm-core/include/sim_kinematics.h",
|
||||
"implementation": "wasm-core/src/sim_kinematics_registry.c",
|
||||
"kinematics_implementation": "wasm-core/src/sim_kinematics.c",
|
||||
"test": "wasm-core/tests/sim_kinematics_registry_test.c",
|
||||
"config_xyzac": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"config_genser": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"config_pentapod": "linuxcnc/configs/sim/axis/pentapod/pentapod.ini",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "kinematics-registry-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit kinematics-registry-samples.v1")
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_kinematics_registry_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_kinematics_registry_wasm_test.js")], root)
|
||||
report = {
|
||||
"schema": "kinematics-registry-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"configuration_candidates": [
|
||||
{"id": "xyzac-trt", "kind": "real", "path": INPUTS["config_xyzac"], "type": 2, "coordinates": "XYZAC", "parameters": ["offsets", "tool", "limits"]},
|
||||
{"id": "genser-puma560", "kind": "real", "path": INPUTS["config_genser"], "type": 13, "coordinates": "XYZABC", "parameters": ["DH", "solver", "limits"]},
|
||||
{"id": "pentapod", "kind": "real", "path": INPUTS["config_pentapod"], "type": 18, "coordinates": "XYZAB", "parameters": ["geometry", "tool", "solver", "limits"]},
|
||||
],
|
||||
"registry": {
|
||||
"abi_version": 1,
|
||||
"entry_count": native["count"],
|
||||
"verified_entries": native["verified"],
|
||||
"capability_union": native["capability_union"],
|
||||
"parameter_union": native["parameter_union"],
|
||||
"state_hash": native["registry_hash"],
|
||||
"capabilities": {"forward": 1, "inverse": 2, "identity": 4, "iterative": 8, "switchable": 16, "extra_joints": 32, "duplicate_coordinates": 64},
|
||||
"parameters": {"mapping": 1, "limits": 2, "offsets": 4, "tool": 8, "pivot": 16, "geometry": 32, "DH": 64, "solver": 128},
|
||||
},
|
||||
"validation": {"lookup_status": native["lookup_status"], "iterative_genser_and_DH_status": native["iterative_require"], "type_mismatch": native["type_mismatch"], "coordinate_error": native["coordinate_error"], "joint_error": native["joint_error"], "unknown_name": native["unknown_name"], "path_name": native["path_name"], "range_error": native["range_error"], "capability_error": native["capability_error"], "parameter_error": native["parameter_error"]},
|
||||
"samples": {
|
||||
"native": native,
|
||||
"wasm": wasm,
|
||||
"byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")),
|
||||
"state_hash_identical": native["registry_hash"] == wasm["registry_hash"],
|
||||
},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_kinematics_registry_test", "wasm_command": "node build-wasm/sim_kinematics_registry_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["18-entry registry", "capability requirements", "parameter masks", "real config validation", "unknown/path names"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/kinematics-registry.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"kinematics_registry={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -43,14 +43,19 @@ def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
wasm_all = run_json(["node", str(root / "build-wasm/sim_kinematics_wasm_test.js")], root)
|
||||
native = {"pentapod": native_all["pentapod"], "boundaries": native_all["delta_edges"]["pentapod"]}
|
||||
wasm = {"pentapod": wasm_all["pentapod"], "boundaries": wasm_all["delta_edges"]["pentapod"]}
|
||||
config_text = (root / INPUTS["config"]).read_text(encoding="utf-8")
|
||||
report = {
|
||||
"schema": "pentapod-kinematics-report.v1", "status": "partial", "implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"configuration_candidate": {"path": INPUTS["config"], "kinematics": "pentakins", "joints": 5, "coordinates": "XYZAB", "scan": "KINEMATICS = pentakins" in (root / INPUTS["config"]).read_text(encoding="utf-8"), "hal_geometry": parse_hal(root / INPUTS["hal"]), "hal_pins": ["last-iterations", "max-iterations", "limit-iterations", "max-error", "convergence-criterion", "tool-offset"]},
|
||||
"geometry": {"struts": 5, "orientation": "roll/pitch only; yaw fixed at zero", "units": "configuration units; XYZ linear and AB degrees"},
|
||||
"configuration_candidate": {"path": INPUTS["config"], "kinematics": "pentakins", "joints": 5, "coordinates": "XYZAB", "scan": "KINEMATICS = pentakins" in config_text and "JOINTS = 5" in config_text, "provenance": "shipped LinuxCNC simulation", "hal_geometry": parse_hal(root / INPUTS["hal"]), "hal_pins": ["last-iterations", "max-iterations", "limit-iterations", "max-error", "convergence-criterion", "tool-offset"]},
|
||||
"configuration_candidates": [
|
||||
{"id": "shipped-pentapod", "path": INPUTS["config"], "kind": "real", "coordinates": "XYZAB", "joints": 5},
|
||||
{"id": "offset-seed-regression", "path": "wasm-core/tests/sim_kinematics_test.c", "kind": "derived", "coordinates": "XYZAB", "joints": 5, "initial_pose": [0.0, 500.0, 300.0, 0.0, 0.0], "target_pose": [0.0, 500.0, 300.0, 0.01, -0.01], "purpose": "non-zero roll/pitch Newton convergence and native/WASM comparison"},
|
||||
],
|
||||
"geometry": {"struts": 5, "orientation": "roll/pitch only; yaw is constrained to zero in the forward result", "units": "configuration units; XYZ linear and AB degrees", "tool_offset": "applied to both base and effector HAL z coordinates"},
|
||||
"mapping": {"coordinates": "XYZAB", "kinematics_joint_count": 5, "joint_semantics": ["strut-0", "strut-1", "strut-2", "strut-3", "strut-4"], "extra_joint_policy": "rejected"},
|
||||
"solver": {"inverse": "closed-form radial/z strut lengths", "forward": "Newton-Raphson 5x5 numerical Jacobian", "seed": [0.0, 500.0, 300.0, 0.0, 0.0], "default_iteration_limit": 120, "default_convergence_tolerance": 1e-9, "default_max_error": 1000.0, "alternate_seed_flag": "SIM_KIN_GENHEX_ALTERNATE_SEED bit 1; negative-z seed is explicitly bounded"},
|
||||
"workspace_and_boundaries": {"nominal": native["pentapod"], "alternate_seed": native["boundaries"]["alternate_pose"], "alternate_seed_error": native["boundaries"]["alternate_status"], "nonpositive_joint_error": native["boundaries"]["nonpositive"], "iteration_limit_error": native["boundaries"]["iteration_limit"], "max_error_boundary": native["boundaries"]["max_error"], "singular_jacobian_error": native["boundaries"]["singular"], "joint_limit_error": native["boundaries"]["limit_error"], "iterations": native["pentapod"]["iterations"], "residual": native["pentapod"]["residual"]},
|
||||
"solver": {"inverse": "closed-form radial/z strut lengths", "forward": "Newton-Raphson 5x5 numerical Jacobian", "seed": [0.0, 500.0, 300.0, 0.0, 0.0], "seed_units": "XYZ in configuration units; A/B in radians internally", "default_iteration_limit": 120, "default_convergence_tolerance": 1e-9, "default_max_error": 1000.0, "alternate_seed_flag": "SIM_KIN_GENHEX_ALTERNATE_SEED bit 1; negative-z seed is explicitly bounded"},
|
||||
"workspace_and_boundaries": {"nominal": native["pentapod"], "alternate_seed": native["boundaries"]["alternate_pose"], "alternate_seed_error": native["boundaries"]["alternate_status"], "alternate_seed_iterations": native["boundaries"].get("alternate_iterations", 0), "nonpositive_joint_error": native["boundaries"]["nonpositive"], "nonpositive_joint_iterations": native["boundaries"].get("nonpositive_iterations", 0), "iteration_limit_error": native["boundaries"]["iteration_limit"], "iteration_limit_iterations": native["boundaries"].get("iteration_limit_iterations", 0), "max_error_boundary": native["boundaries"]["max_error"], "max_error_iterations": native["boundaries"].get("max_error_iterations", 0), "singular_jacobian_error": native["boundaries"]["singular"], "singular_jacobian_iterations": native["boundaries"].get("singular_iterations", 0), "joint_limit_error": native["boundaries"]["limit_error"], "posture_constraint": "C/yaw is not a configured joint and forward output is exactly zero", "iterations": native["pentapod"]["iterations"], "residual": native["pentapod"]["residual"]},
|
||||
"samples": {"native": native, "wasm": wasm, "byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")), "state_hash_identical": native["pentapod"]["state_hash"] == wasm["pentapod"]["state_hash"]},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_kinematics_test", "wasm_command": "node build-wasm/sim_kinematics_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["nominal five-strut pose", "negative-z seed", "nonpositive strut", "iteration limit", "maximum error", "singular Jacobian", "joint limit"]},
|
||||
|
||||
@@ -121,7 +121,48 @@ def make_manifest_entry(source: dict[str, object], repo_root: Path | None = None
|
||||
path = repo_root / source_path
|
||||
if path.is_file():
|
||||
is_kinematics_config = bool(KINEMATICS_CONFIG_RE.search(path.read_text(encoding="utf-8", errors="strict")))
|
||||
is_kinematics_abi = source_path in {"wasm-core/include/sim_kinematics.h", "wasm-core/src/sim_kinematics.c", "wasm-core/tests/sim_kinematics_test.c", "wasm-core/tests/sim_kinematics_test_main.c"}
|
||||
if source_path == "linuxcnc/configs/sim/axis/pentapod/kinematics.hal":
|
||||
is_kinematics_config = True
|
||||
is_kinematics_abi = source_path in {"wasm-core/include/sim_kinematics.h", "wasm-core/src/sim_kinematics.c", "wasm-core/src/sim_switchkins.c", "wasm-core/src/sim_userkins.c", "wasm-core/src/sim_kinematics_registry.c", "wasm-core/tests/sim_kinematics_test.c", "wasm-core/tests/sim_kinematics_test_main.c", "wasm-core/tests/sim_switchkins_test.c", "wasm-core/tests/sim_userkins_test.c", "wasm-core/tests/sim_kinematics_registry_test.c"}
|
||||
is_switchkins_source = source_path in {
|
||||
"linuxcnc/src/emc/kinematics/switchkins.c", "linuxcnc/src/emc/kinematics/switchkins.h",
|
||||
"linuxcnc/src/emc/kinematics/xyzac-trt-kins.c",
|
||||
"wasm-core/src/sim_switchkins.c",
|
||||
}
|
||||
is_switchkins_config = source_path in {
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal",
|
||||
}
|
||||
is_userkins_source = source_path in {
|
||||
"linuxcnc/src/emc/kinematics/userkfuncs.c",
|
||||
"linuxcnc/src/emc/kinematics/ugenserkins.c",
|
||||
}
|
||||
is_userkins_config = source_path in {
|
||||
"linuxcnc/configs/sim/axis/vismach/scara/scara.ini",
|
||||
"linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
}
|
||||
is_userkins_implementation = source_path in {
|
||||
"wasm-core/src/sim_userkins.c", "wasm-core/tests/sim_userkins_test.c",
|
||||
}
|
||||
is_registry_implementation = source_path in {
|
||||
"wasm-core/src/sim_kinematics_registry.c", "wasm-core/tests/sim_kinematics_registry_test.c",
|
||||
}
|
||||
is_motion_implementation = source_path in {
|
||||
"wasm-core/include/sim_kinematics_motion.h", "wasm-core/src/sim_kinematics_motion.c", "wasm-core/tests/sim_kinematics_motion_test.c",
|
||||
}
|
||||
is_feedback_implementation = source_path in {
|
||||
"wasm-core/include/sim_kinematics_feedback.h", "wasm-core/src/sim_kinematics_feedback.c", "wasm-core/tests/sim_kinematics_feedback_test.c",
|
||||
}
|
||||
is_home_limit_implementation = source_path in {
|
||||
"wasm-core/include/sim_kinematics_home_limit.h", "wasm-core/src/sim_kinematics_home_limit.c", "wasm-core/tests/sim_kinematics_home_limit_test.c",
|
||||
}
|
||||
is_io_implementation = source_path in {
|
||||
"wasm-core/include/sim_io.h", "wasm-core/src/sim_io.c", "wasm-core/tests/sim_io_test.c",
|
||||
}
|
||||
is_io_source = source_path in {
|
||||
"linuxcnc/src/emc/iotask/ioControl.cc", "linuxcnc/src/emc/iotask/ioControl_v2.cc",
|
||||
"linuxcnc/src/hal/components/sim_spindle.comp",
|
||||
}
|
||||
is_delta_source = source_path.startswith("linuxcnc/src/emc/kinematics/lineardelta") or source_path.startswith("linuxcnc/src/emc/kinematics/rotarydelta")
|
||||
is_delta_config = is_kinematics_config and any(token in source_path.lower() for token in ("ldelta", "rdelta"))
|
||||
is_delta_implementation = is_kinematics_abi and source_path in {"wasm-core/include/sim_kinematics.h", "wasm-core/src/sim_kinematics.c", "wasm-core/tests/sim_kinematics_test.c", "wasm-core/tests/sim_kinematics_test_main.c"}
|
||||
@@ -130,7 +171,15 @@ def make_manifest_entry(source: dict[str, object], repo_root: Path | None = None
|
||||
is_genhex_source = source_path in {"linuxcnc/src/emc/kinematics/genhexkins.c", "linuxcnc/src/emc/kinematics/genhexkins.h"}
|
||||
is_genhex_config = is_kinematics_config and source_path == "linuxcnc/configs/sim/axis/vismach/hexapod-sim/hexapod.ini"
|
||||
is_genhex_hal = source_path == "linuxcnc/configs/sim/axis/vismach/hexapod-sim/kinematics.hal"
|
||||
is_genhex_implementation = source_path in {"wasm-core/include/sim_kinematics.h", "wasm-core/src/sim_kinematics.c", "wasm-core/tests/sim_kinematics_test.c", "wasm-core/tests/sim_kinematics_test_main.c"}
|
||||
is_penta_source = source_path in {"linuxcnc/src/emc/kinematics/pentakins.c", "linuxcnc/src/emc/kinematics/pentakins.h"}
|
||||
is_penta_config = is_kinematics_config and source_path == "linuxcnc/configs/sim/axis/pentapod/pentapod.ini"
|
||||
is_penta_hal = source_path == "linuxcnc/configs/sim/axis/pentapod/kinematics.hal"
|
||||
is_penta_implementation = is_kinematics_abi
|
||||
is_recovery_implementation = source_path in {
|
||||
"wasm-core/include/sim_event.h", "wasm-core/src/sim_event.c", "wasm-core/tests/sim_event_test.c",
|
||||
"web/worker/replay.mjs", "web/worker/sim-session.mjs", "web/worker/test_replay.mjs",
|
||||
"web/workspace/journal.mjs", "web/workspace/recovery.mjs", "web/workspace/test_recovery.mjs",
|
||||
}
|
||||
is_corexy_hal = source_path in {
|
||||
"linuxcnc/src/hal/components/corexy_by_hal.comp",
|
||||
"linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini",
|
||||
@@ -141,20 +190,79 @@ def make_manifest_entry(source: dict[str, object], repo_root: Path | None = None
|
||||
"wasm-core/tests/sim_hal_test.c",
|
||||
"wasm-core/tests/sim_hal_test_main.c",
|
||||
}
|
||||
if is_kinematics_source or is_kinematics_config or is_kinematics_abi or is_corexy_hal:
|
||||
is_sim_configuration = source_path.startswith("linuxcnc/configs/sim/")
|
||||
if is_recovery_implementation:
|
||||
entry.update({
|
||||
"domain": "recovery",
|
||||
"feature_id": "feature:recovery-k7-replay",
|
||||
"dependencies": ["coverage/workspace-recovery.report.json", "coverage/io-runtime.report.json", "coverage/kinematics-motion.report.json", "coverage/kinematics-feedback.report.json"],
|
||||
"outputs": ["coverage/workspace-recovery.report.json"],
|
||||
"errors": ["blocked:official recovery oracle is absent from locked source"],
|
||||
"test_id": "audit:K7-RECOVERY-01",
|
||||
"oracle": {"status": "blocked", "report": "coverage/workspace-recovery.report.json"},
|
||||
"mapping_status": "verified_partial",
|
||||
})
|
||||
if is_io_implementation or is_io_source:
|
||||
entry.update({
|
||||
"domain": "io",
|
||||
"feature_id": "feature:io-k6-spindle-tool-probe",
|
||||
"dependencies": ["coverage/io-runtime.report.json", "coverage/hal-runtime.report.json", "coverage/kinematics-feedback.report.json"],
|
||||
"outputs": ["coverage/io-runtime.report.json"],
|
||||
"errors": ["blocked:official IO/kinematics oracle is absent from locked source"],
|
||||
"test_id": "audit:K6-IO-01",
|
||||
"oracle": {"status": "blocked", "report": "coverage/io-runtime.report.json"},
|
||||
"mapping_status": "verified_partial",
|
||||
})
|
||||
if is_kinematics_source or is_kinematics_config or is_kinematics_abi or is_corexy_hal or is_switchkins_source or is_switchkins_config or is_userkins_source or is_userkins_config or is_userkins_implementation or is_motion_implementation or is_feedback_implementation or is_home_limit_implementation:
|
||||
# K0-INV-01 audits are source-closure evidence, not an implementation
|
||||
# claim. Keep the seed planned while linking every kinematics source
|
||||
# to the deterministic report and its explicit blocked oracle.
|
||||
entry.update({
|
||||
"domain": "hal" if is_corexy_hal else "kinematics",
|
||||
"feature_id": "feature:kinematics-k2-hal" if is_corexy_hal else "feature:kinematics-k4-tripod" if (is_tripod_source or is_tripod_config) else "feature:kinematics-k4-genhex" if (is_genhex_source or is_genhex_config or is_genhex_hal or is_genhex_implementation) else "feature:kinematics-k4-delta" if (is_delta_source or is_delta_config or is_delta_implementation) else "feature:kinematics-k0" if not is_kinematics_abi else "feature:kinematics-k1-contract",
|
||||
"dependencies": ["coverage/coupled-kinematics.report.json", "coverage/config-resources.jsonl"] if is_corexy_hal else ["coverage/tripod-kinematics.report.json", "coverage/delta-kinematics.report.json"] if (is_tripod_source or is_tripod_config) else ["coverage/genhex-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_genhex_source or is_genhex_config or is_genhex_hal or is_genhex_implementation) else ["coverage/delta-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_delta_source or is_delta_config or is_delta_implementation) else ["coverage/source-index.jsonl", "coverage/source-symbols.jsonl"] if is_kinematics_source else ["coverage/kinematics-source-closure.report.json", "coverage/config-resources.jsonl"] if is_kinematics_config else ["wasm-core/include/sim_kinematics.h", "wasm-core/CMakeLists.txt"],
|
||||
"outputs": ["coverage/corexy-hal.report.json"] if is_corexy_hal else ["coverage/tripod-kinematics.report.json"] if (is_tripod_source or is_tripod_config) else ["coverage/genhex-kinematics.report.json"] if (is_genhex_source or is_genhex_config or is_genhex_hal or is_genhex_implementation) else ["coverage/delta-kinematics.report.json"] if (is_delta_source or is_delta_config or is_delta_implementation) else ["coverage/kinematics-source-closure.report.json", "coverage/trivkins-integration.report.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"] if is_kinematics_source else ["coverage/kinematics-config-graph.report.json", "coverage/kinematics-oracle-samples.jsonl", "coverage/kinematics-oracle.report.json", "coverage/kinematics-golden-schema.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"] if is_kinematics_config else ["coverage/kinematics-abi-contract.json", "coverage/kinematics-mapping-contract.json", "coverage/kinematics-numeric-policy.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"],
|
||||
"feature_id": "feature:kinematics-k6-home" if is_home_limit_implementation else "feature:kinematics-k6-feedback" if is_feedback_implementation else "feature:kinematics-k6-motion" if is_motion_implementation else "feature:kinematics-k5-cfg" if is_registry_implementation else "feature:kinematics-k5-userkins" if (is_userkins_source or is_userkins_config or is_userkins_implementation) else "feature:kinematics-k5-switchkins" if (is_switchkins_source or is_switchkins_config) else "feature:kinematics-k2-hal" if is_corexy_hal else "feature:kinematics-k4-tripod" if (is_tripod_source or is_tripod_config) else "feature:kinematics-k4-genhex" if (is_genhex_source or is_genhex_config or is_genhex_hal) else "feature:kinematics-k4-pentapod" if (is_penta_source or is_penta_config or is_penta_hal or is_penta_implementation) else "feature:kinematics-k4-delta" if (is_delta_source or is_delta_config or is_delta_implementation) else "feature:kinematics-k0" if not is_kinematics_abi else "feature:kinematics-k1-contract",
|
||||
"dependencies": ["coverage/kinematics-home-limit.report.json", "coverage/kinematics-feedback.report.json", "coverage/kinematics-registry.report.json"] if is_home_limit_implementation else ["coverage/kinematics-feedback.report.json", "coverage/kinematics-motion.report.json", "coverage/kinematics-registry.report.json"] if is_feedback_implementation else ["coverage/kinematics-motion.report.json", "coverage/motion-runtime.report.json", "coverage/kinematics-registry.report.json"] if is_motion_implementation else ["coverage/kinematics-registry.report.json", "coverage/kinematics-abi-contract.json", "coverage/kinematics-numeric-policy.json"] if is_registry_implementation else ["coverage/userkins-kinematics.report.json", "coverage/genser-kinematics.report.json"] if (is_userkins_source or is_userkins_config or is_userkins_implementation) else ["coverage/switchkins-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_switchkins_source or is_switchkins_config) else ["coverage/coupled-kinematics.report.json", "coverage/config-resources.jsonl"] if is_corexy_hal else ["coverage/tripod-kinematics.report.json", "coverage/delta-kinematics.report.json"] if (is_tripod_source or is_tripod_config) else ["coverage/genhex-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_genhex_source or is_genhex_config or is_genhex_hal) else ["coverage/pentapod-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_penta_source or is_penta_config or is_penta_hal or is_penta_implementation) else ["coverage/delta-kinematics.report.json", "coverage/kinematics-source-closure.report.json"] if (is_delta_source or is_delta_config or is_delta_implementation) else ["coverage/source-index.jsonl", "coverage/source-symbols.jsonl"] if is_kinematics_source else ["coverage/kinematics-source-closure.report.json", "coverage/config-resources.jsonl"] if is_kinematics_config else ["wasm-core/include/sim_kinematics.h", "wasm-core/CMakeLists.txt"],
|
||||
"outputs": ["coverage/kinematics-home-limit.report.json"] if is_home_limit_implementation else ["coverage/kinematics-feedback.report.json"] if is_feedback_implementation else ["coverage/kinematics-motion.report.json"] if is_motion_implementation else ["coverage/kinematics-registry.report.json"] if is_registry_implementation else ["coverage/userkins-kinematics.report.json"] if (is_userkins_source or is_userkins_config or is_userkins_implementation) else ["coverage/switchkins-kinematics.report.json"] if (is_switchkins_source or is_switchkins_config) else ["coverage/corexy-hal.report.json"] if is_corexy_hal else ["coverage/tripod-kinematics.report.json"] if (is_tripod_source or is_tripod_config) else ["coverage/genhex-kinematics.report.json"] if (is_genhex_source or is_genhex_config or is_genhex_hal) else ["coverage/pentapod-kinematics.report.json"] if (is_penta_source or is_penta_config or is_penta_hal) else ["coverage/kinematics-abi-contract.json", "coverage/kinematics-mapping-contract.json", "coverage/kinematics-numeric-policy.json"] if is_penta_implementation else ["coverage/delta-kinematics.report.json"] if (is_delta_source or is_delta_config or is_delta_implementation) else ["coverage/kinematics-source-closure.report.json", "coverage/trivkins-integration.report.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"] if is_kinematics_source else ["coverage/kinematics-config-graph.report.json", "coverage/kinematics-oracle-samples.jsonl", "coverage/kinematics-oracle.report.json", "coverage/kinematics-golden-schema.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"] if is_kinematics_config else ["coverage/kinematics-abi-contract.json", "coverage/kinematics-mapping-contract.json", "coverage/kinematics-numeric-policy.json", "coverage/coupled-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/trt-kinematics.report.json", "coverage/robot-kinematics.report.json", "coverage/genser-kinematics.report.json"],
|
||||
"errors": ["blocked:linuxcnc/unit_tests/kinematics missing from locked source"],
|
||||
"test_id": "audit:K2-HAL-01" if is_corexy_hal else "audit:K4-TRIPOD-01" if (is_tripod_source or is_tripod_config) else "audit:K4-HEX-01" if (is_genhex_source or is_genhex_config or is_genhex_hal or is_genhex_implementation) else "audit:K4-DELTA-01" if (is_delta_source or is_delta_config or is_delta_implementation) else "audit:K0-INV-01" if is_kinematics_source else "audit:K0-INV-02" if is_kinematics_config else "audit:K1-CONTRACT-01",
|
||||
"oracle": {"status": "blocked", "report": "coverage/corexy-hal.report.json" if is_corexy_hal else "coverage/tripod-kinematics.report.json" if (is_tripod_source or is_tripod_config) else "coverage/genhex-kinematics.report.json" if (is_genhex_source or is_genhex_config or is_genhex_hal or is_genhex_implementation) else "coverage/delta-kinematics.report.json" if (is_delta_source or is_delta_config or is_delta_implementation) else "coverage/kinematics-source-closure.report.json"},
|
||||
"test_id": "audit:K6-HOME-01" if is_home_limit_implementation else "audit:K6-MOTION-02" if is_feedback_implementation else "audit:K6-MOTION-01" if is_motion_implementation else "audit:K5-CFG-01" if is_registry_implementation else "audit:K5-USER-01" if (is_userkins_source or is_userkins_config or is_userkins_implementation) else "audit:K5-SWITCH-01" if (is_switchkins_source or is_switchkins_config) else "audit:K2-HAL-01" if is_corexy_hal else "audit:K4-TRIPOD-01" if (is_tripod_source or is_tripod_config) else "audit:K4-HEX-01" if (is_genhex_source or is_genhex_config or is_genhex_hal) else "audit:K4-PENTA-01" if (is_penta_source or is_penta_config or is_penta_hal or is_penta_implementation) else "audit:K4-DELTA-01" if (is_delta_source or is_delta_config or is_delta_implementation) else "audit:K0-INV-01" if is_kinematics_source else "audit:K0-INV-02" if is_kinematics_config else "audit:K1-CONTRACT-01",
|
||||
"oracle": {"status": "blocked", "report": "coverage/kinematics-home-limit.report.json" if is_home_limit_implementation else "coverage/kinematics-feedback.report.json" if is_feedback_implementation else "coverage/kinematics-motion.report.json" if is_motion_implementation else "coverage/kinematics-registry.report.json" if is_registry_implementation else "coverage/userkins-kinematics.report.json" if (is_userkins_source or is_userkins_config or is_userkins_implementation) else "coverage/switchkins-kinematics.report.json" if (is_switchkins_source or is_switchkins_config) else "coverage/corexy-hal.report.json" if is_corexy_hal else "coverage/tripod-kinematics.report.json" if (is_tripod_source or is_tripod_config) else "coverage/genhex-kinematics.report.json" if (is_genhex_source or is_genhex_config or is_genhex_hal) else "coverage/pentapod-kinematics.report.json" if (is_penta_source or is_penta_config or is_penta_hal or is_penta_implementation) else "coverage/delta-kinematics.report.json" if (is_delta_source or is_delta_config or is_delta_implementation) else "coverage/kinematics-source-closure.report.json"},
|
||||
"mapping_status": "audited_blocked" if not is_corexy_hal else "verified_partial",
|
||||
})
|
||||
if is_sim_configuration:
|
||||
# K8 covers every simulation resource, including configs that do not
|
||||
# declare KINEMATICS. Keep the family-specific owner above, but link
|
||||
# the shared preflight and its explicit blocked oracle as a second
|
||||
# evidence edge.
|
||||
entry["dependencies"] = list(dict.fromkeys([
|
||||
*[str(value) for value in entry.get("dependencies", [])],
|
||||
"coverage/configuration-regression.report.json",
|
||||
"coverage/config-resources.jsonl",
|
||||
"coverage/kinematics-config-graph.report.json",
|
||||
]))
|
||||
entry["outputs"] = list(dict.fromkeys([
|
||||
*[str(value) for value in entry.get("outputs", [])],
|
||||
"coverage/configuration-regression.report.json",
|
||||
]))
|
||||
entry["coverage_dimensions"] = list(dict.fromkeys([
|
||||
*[str(value) for value in entry.get("coverage_dimensions", [])],
|
||||
"configuration_startup_preflight",
|
||||
"configuration_resource_graph",
|
||||
"configuration_kinematics_entry",
|
||||
]))
|
||||
entry["configuration_regression"] = {
|
||||
"feature_id": "feature:configuration-k8-regression",
|
||||
"test_id": "audit:K8-REG-01",
|
||||
"report": "coverage/configuration-regression.report.json",
|
||||
"oracle": "blocked:official LinuxCNC kinematics/recovery oracle is absent from locked source",
|
||||
}
|
||||
if not (is_kinematics_source or is_kinematics_config or is_kinematics_abi or is_corexy_hal or is_switchkins_source or is_switchkins_config or is_userkins_source or is_userkins_config or is_userkins_implementation or is_motion_implementation or is_feedback_implementation or is_home_limit_implementation):
|
||||
entry.update({
|
||||
"domain": "configuration",
|
||||
"feature_id": "feature:configuration-k8-regression",
|
||||
"errors": ["blocked:configuration preflight retains missing resources and official oracle gap"],
|
||||
"test_id": "audit:K8-REG-01",
|
||||
"oracle": {"status": "blocked", "report": "coverage/configuration-regression.report.json"},
|
||||
"mapping_status": "verified_partial",
|
||||
})
|
||||
return entry
|
||||
|
||||
|
||||
|
||||
110
scripts/generate_switchkins_report.py
Normal file
110
scripts/generate_switchkins_report.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic native/WASM evidence for switchkins integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
INPUTS = {
|
||||
"switch_source": "linuxcnc/src/emc/kinematics/switchkins.c",
|
||||
"switch_header": "linuxcnc/src/emc/kinematics/switchkins.h",
|
||||
"module_source": "linuxcnc/src/emc/kinematics/xyzac-trt-kins.c",
|
||||
"userk_source": "linuxcnc/src/emc/kinematics/userkfuncs.c",
|
||||
"config": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"postgui_hal": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal",
|
||||
"implementation": "wasm-core/src/sim_switchkins.c",
|
||||
"kinematics_header": "wasm-core/include/sim_kinematics.h",
|
||||
"kinematics_implementation": "wasm-core/src/sim_kinematics.c",
|
||||
"test": "wasm-core/tests/sim_switchkins_test.c",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path, schema: str) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == schema:
|
||||
return item
|
||||
raise RuntimeError(f"runner did not emit {schema}")
|
||||
|
||||
|
||||
def parse_config(path: Path) -> dict[str, object]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
match = re.search(r"^KINEMATICS\s*=\s*([^\s]+)(?:\s+sparm=([^\s]+))?", text, re.MULTILINE)
|
||||
joints = re.search(r"^\s*JOINTS\s*=\s*(\d+)", text, re.MULTILINE)
|
||||
coords = re.search(r"^\s*COORDINATES\s*=\s*([^\s]+)", text, re.MULTILINE)
|
||||
return {
|
||||
"path": INPUTS["config"],
|
||||
"kinematics": match.group(1) if match else None,
|
||||
"sparm": match.group(2) if match and match.group(2) else None,
|
||||
"joints": int(joints.group(1)) if joints else None,
|
||||
"coordinates": coords.group(1) if coords else None,
|
||||
"identityfirst": bool(match and match.group(2) and "identityfirst" in match.group(2)),
|
||||
"switch_pin": "motion.switchkins-type" in text,
|
||||
"modes": {"0": "identity", "1": "xyzac-trt", "2": "userk"},
|
||||
}
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_switchkins_test")], root, "switchkins-samples.v1")
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_switchkins_wasm_test.js")], root, "switchkins-samples.v1")
|
||||
config = parse_config(root / INPUTS["config"])
|
||||
report = {
|
||||
"schema": "switchkins-kinematics-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"configuration_candidates": [
|
||||
{**config, "id": "shipped-xyzac-trt-identityfirst", "kind": "real", "provenance": "shipped LinuxCNC simulation"},
|
||||
{"id": "mode-transition-pose-continuity", "kind": "derived", "path": INPUTS["test"], "modes": ["identity", "xyzac-trt"], "purpose": "old-mode forward pose to new-mode inverse remap"},
|
||||
{"id": "userk-slot-missing-module", "kind": "derived", "path": INPUTS["test"], "modes": ["userk"], "purpose": "mode 2 remains fail-closed until static userk is registered"},
|
||||
],
|
||||
"mode_registry": {"slots": [0, 1, 2], "names": {"0": "identity", "1": "xyzac-trt", "2": "userk"}, "startup_mode": 0, "identityfirst": config["identityfirst"]},
|
||||
"state_and_errors": {
|
||||
"pose_continuity": {"identity_to_trt": True, "trt_to_identity": True},
|
||||
"state_hash_stable": native["returned_hash"] == native["returned_repeat_hash"] and native["returned_hash"] != 0,
|
||||
"invalid_mode_error": -707,
|
||||
"missing_module_error": -708,
|
||||
"switch_interrupted_error": -709,
|
||||
"abort_restores_active_mode": native["active_mode"] == 0,
|
||||
},
|
||||
"samples": {
|
||||
"native": native,
|
||||
"wasm": wasm,
|
||||
"byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")),
|
||||
"state_hash_identical": [native[key] for key in ("initial_hash", "switched_hash", "returned_hash", "returned_repeat_hash")] == [wasm[key] for key in ("initial_hash", "switched_hash", "returned_hash", "returned_repeat_hash")],
|
||||
},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_switchkins_test", "wasm_command": "node build-wasm/sim_switchkins_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source", "blocked_samples": ["type 0 identity", "type 1 xyzac-trt", "type 2 userk", "invalid mode", "missing module", "interrupted switch", "abort recovery"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/switchkins-kinematics.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"switchkins={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
99
scripts/generate_userkins_report.py
Normal file
99
scripts/generate_userkins_report.py
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic evidence for statically linked userk and ugenser."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
INPUTS = {
|
||||
"userk_source": "linuxcnc/src/emc/kinematics/userkfuncs.c",
|
||||
"ugenser_source": "linuxcnc/src/emc/kinematics/ugenserkins.c",
|
||||
"genser_source": "linuxcnc/src/emc/kinematics/genserkins.c",
|
||||
"genser_functions": "linuxcnc/src/emc/kinematics/genserfuncs.c",
|
||||
"userk_config": "linuxcnc/configs/sim/axis/vismach/scara/scara.ini",
|
||||
"ugenser_config": "linuxcnc/configs/sim/axis/vismach/puma/puma560.ini",
|
||||
"ugenser_dh": "linuxcnc/configs/sim/axis/vismach/puma/puma560_dh.hal",
|
||||
"implementation": "wasm-core/src/sim_userkins.c",
|
||||
"header": "wasm-core/include/sim_kinematics.h",
|
||||
"test": "wasm-core/tests/sim_userkins_test.c",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run_json(command: list[str], root: Path) -> dict[str, object]:
|
||||
result = subprocess.run(command, cwd=root, check=False, text=True, capture_output=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f"{command[0]} exited {result.returncode}: {result.stderr.strip()}")
|
||||
for line in result.stdout.splitlines():
|
||||
if line.lstrip().startswith("{"):
|
||||
item = json.loads(line)
|
||||
if item.get("schema") == "userkins-samples.v1":
|
||||
return item
|
||||
raise RuntimeError("runner did not emit userkins-samples.v1")
|
||||
|
||||
|
||||
def generate(root: Path, output: Path) -> dict[str, object]:
|
||||
root = root.resolve()
|
||||
native = run_json([str(root / "build-native/sim_userkins_test")], root)
|
||||
wasm = run_json(["node", str(root / "build-wasm/sim_userkins_wasm_test.js")], root)
|
||||
userk_config = (root / INPUTS["userk_config"]).read_text(encoding="utf-8")
|
||||
ugenser_config = (root / INPUTS["ugenser_config"]).read_text(encoding="utf-8")
|
||||
ugenser_source = (root / INPUTS["ugenser_source"]).read_text(encoding="utf-8")
|
||||
report = {
|
||||
"schema": "userkins-kinematics-report.v1",
|
||||
"status": "partial",
|
||||
"implementation_status": "verified",
|
||||
"source_records": {name: {"path": path, "sha256": sha256(root / path)} for name, path in INPUTS.items()},
|
||||
"configuration_candidates": [
|
||||
{"id": "shipped-scara-userk-slot", "kind": "real", "path": INPUTS["userk_config"], "scan": "kinstype==2 (userk" in userk_config, "implementation": "identity template", "coordinates": "XYZCAB"},
|
||||
{"id": "shipped-puma560-genser", "kind": "real", "path": INPUTS["ugenser_config"], "scan": "KINEMATICS= genserkins" in ugenser_config, "implementation": "ugenser static adapter over genser", "coordinates": "XYZABC"},
|
||||
{"id": "static-descriptor-regression", "kind": "derived", "path": INPUTS["test"], "implementations": ["userk.identity", "ugenser.static"], "purpose": "ABI/capability checks, FK/IK calls, switch slot binding, and native/WASM comparison"},
|
||||
],
|
||||
"static_contract": {
|
||||
"abi_version": native["abi_version"],
|
||||
"capabilities": {"forward": 1, "inverse": 2, "static": 4, "sample_mask": native["capabilities"]},
|
||||
"implementations": {"1": "identity", "2": "genser"},
|
||||
"switch_binding": {"slot": 2, "active_after_select": native["active_mode"]},
|
||||
"dynamic_host_loading": "forbidden; paths, slash-delimited names, and host shared libraries return SIM_KIN_ERR_DYNAMIC_MODULE",
|
||||
},
|
||||
"ugenser_scope": {
|
||||
"legacy_cli_status": "excluded",
|
||||
"source_warning_present": "Declining usage" in ugenser_source and "functioning in its current state is doubtful" in ugenser_source,
|
||||
"adaptation": "reuse the verified statically linked genser FK/IK implementation; do not port gettimeofday, stdin, or HAL process lifecycle",
|
||||
},
|
||||
"error_boundaries": {"abi_version": -710, "capability": -711, "dynamic_module": -712, "missing_or_uninitialized_module": -708},
|
||||
"samples": {
|
||||
"native": native,
|
||||
"wasm": wasm,
|
||||
"byte_identical_json": json.dumps(native, sort_keys=True, separators=(",", ":")) == json.dumps(wasm, sort_keys=True, separators=(",", ":")),
|
||||
"state_hash_identical": [native["identity"]["state_hash"], native["ugenser"]["state_hash"]] == [wasm["identity"]["state_hash"], wasm["ugenser"]["state_hash"]],
|
||||
"state_hash_stable": native["identity"]["state_hash"] == native["identity"]["repeat_hash"] and native["ugenser"]["state_hash"] == native["ugenser"]["repeat_hash"],
|
||||
},
|
||||
"native_wasm": {"status": "passed", "native_command": "build-native/sim_userkins_test", "wasm_command": "node build-wasm/sim_userkins_wasm_test.js"},
|
||||
"oracle": {"status": "blocked", "reason": "linuxcnc/unit_tests/kinematics is absent from the locked source; the deprecated ugenser CLI is not an approved replacement", "blocked_samples": ["userk identity template", "ugenser static adapter", "ABI mismatch", "capability mismatch", "dynamic module rejection", "uninitialized module"]},
|
||||
}
|
||||
report["report_sha256"] = hashlib.sha256(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--output", type=Path, default=Path("coverage/userkins-kinematics.report.json"))
|
||||
args = parser.parse_args()
|
||||
report = generate(args.repo_root, args.repo_root / args.output)
|
||||
print(f"userkins={report['status']} native_wasm={report['native_wasm']['status']} oracle={report['oracle']['status']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
52
scripts/test_configuration_regression.py
Normal file
52
scripts/test_configuration_regression.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from generate_configuration_regression import generate
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REPORT = ROOT / "coverage/configuration-regression.report.json"
|
||||
|
||||
|
||||
class ConfigurationRegressionTests(unittest.TestCase):
|
||||
def test_report_contract(self):
|
||||
report = json.loads(REPORT.read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "configuration-regression-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["counts"]["registered_configuration_entries"], 264)
|
||||
self.assertEqual(len(report["configuration_entries"]), 264)
|
||||
self.assertEqual(report["closure"]["graph_orphan_entry_ids"], [])
|
||||
self.assertEqual(report["closure"]["unowned_template_ids"], [])
|
||||
self.assertEqual(report["closure"]["resource_orphan_entrypoints"], [])
|
||||
self.assertEqual(report["runtime"]["native_wasm"]["status"], "passed")
|
||||
self.assertEqual(report["runtime"]["worker"]["status"], "passed")
|
||||
self.assertEqual(report["recovery_summary"]["endpoints_equal"], True)
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
self.assertTrue(report["oracle"]["derived_configuration_results_are_not_official"])
|
||||
|
||||
def test_all_entry_ids_are_unique_and_repeat_stable(self):
|
||||
report = json.loads(REPORT.read_text(encoding="utf-8"))
|
||||
ids = [row["config_id"] for row in report["configuration_entries"]]
|
||||
self.assertEqual(len(ids), len(set(ids)))
|
||||
for group in (report["runtime"]["native"], report["runtime"]["wasm"]):
|
||||
self.assertEqual(group["status"], "passed")
|
||||
self.assertTrue(all(row["repeat_stable"] for row in group["records"].values()))
|
||||
self.assertTrue(all(row["repeat_stable"] for row in report["runtime"]["worker"]["records"].values()))
|
||||
|
||||
def test_generation_is_deterministic(self):
|
||||
with TemporaryDirectory() as directory:
|
||||
first_path = Path(directory) / "first.json"
|
||||
second_path = Path(directory) / "second.json"
|
||||
first = generate(ROOT, first_path)
|
||||
second = generate(ROOT, second_path)
|
||||
self.assertEqual(first, second)
|
||||
self.assertEqual(first_path.read_bytes(), second_path.read_bytes())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -34,6 +34,10 @@ class HalRuntimeTests(unittest.TestCase):
|
||||
self.assertEqual(report["schema"], "corexy-hal-report.v1")
|
||||
self.assertTrue(report["native_wasm"]["byte_identical_json"])
|
||||
|
||||
def test_io_pin_capacity_is_declared(self):
|
||||
report = json.loads((ROOT / "coverage/hal-runtime.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["pin_capacity"], 256)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
45
scripts/test_io_report.py
Normal file
45
scripts/test_io_report.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_io_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class IoReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/io-runtime.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "io-runtime-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["boundaries"]["invalid_pin_type"], -304)
|
||||
self.assertEqual(report["boundaries"]["spindle_limit"], -906)
|
||||
self.assertEqual(report["boundaries"]["tool_change_while_spinning"], -910)
|
||||
self.assertEqual(report["boundaries"]["probe_trigger"], 0)
|
||||
self.assertEqual(report["boundaries"]["probe_state"], 2)
|
||||
self.assertEqual(report["boundaries"]["estop_command"], -904)
|
||||
self.assertEqual(report["boundaries"]["estop_recovery_before_clear"], -904)
|
||||
self.assertTrue(report["boundaries"]["state_hash_stable"])
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.io-one.json", ROOT / "coverage/.io-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_io_report.generate(ROOT, first), generate_io_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
def test_wasm_exports_io_api(self):
|
||||
module = ROOT / "build-wasm/linuxcnc-sim-core.js"
|
||||
if not module.is_file():
|
||||
self.skipTest("WASM build has not been generated")
|
||||
text = module.read_text(encoding="utf-8")
|
||||
for symbol in ("sim_io_init", "sim_io_spindle_command", "sim_io_tool_change", "sim_io_probe_update", "sim_io_recover"):
|
||||
self.assertIn(symbol, text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
34
scripts/test_kinematics_feedback_report.py
Normal file
34
scripts/test_kinematics_feedback_report.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_kinematics_feedback_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class KinematicsFeedbackReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/kinematics-feedback.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "kinematics-feedback-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["boundaries"]["stale_read"], -824)
|
||||
self.assertEqual(report["boundaries"]["nonfinite"], -822)
|
||||
self.assertEqual(report["boundaries"]["forward_failure"], -823)
|
||||
self.assertEqual(report["boundaries"]["failed_feedback_valid"], 0)
|
||||
self.assertTrue(report["boundaries"]["state_hash_stable"])
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.feedback-one.json", ROOT / "coverage/.feedback-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_kinematics_feedback_report.generate(ROOT, first), generate_kinematics_feedback_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
34
scripts/test_kinematics_home_limit_report.py
Normal file
34
scripts/test_kinematics_home_limit_report.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_kinematics_home_limit_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class KinematicsHomeLimitReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/kinematics-home-limit.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "kinematics-home-limit-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["boundaries"]["unhomed"], -842)
|
||||
self.assertEqual(report["boundaries"]["sequence"], -846)
|
||||
self.assertEqual(report["boundaries"]["limit"], -843)
|
||||
self.assertTrue(report["boundaries"]["retains_valid_pose"])
|
||||
self.assertTrue(report["boundaries"]["state_hash_stable"])
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.home-one.json", ROOT / "coverage/.home-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_kinematics_home_limit_report.generate(ROOT, first), generate_kinematics_home_limit_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
35
scripts/test_kinematics_motion_report.py
Normal file
35
scripts/test_kinematics_motion_report.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_kinematics_motion_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class KinematicsMotionReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/kinematics-motion.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "kinematics-motion-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["pipeline"]["joint_queue_capacity"], 64)
|
||||
self.assertEqual(report["boundaries"]["capacity"], -802)
|
||||
self.assertEqual(report["boundaries"]["inverse_failure"], -804)
|
||||
self.assertTrue(report["boundaries"]["capacity_planner_rollback"])
|
||||
self.assertTrue(report["boundaries"]["inverse_planner_rollback"])
|
||||
self.assertTrue(report["boundaries"]["state_hash_stable"])
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.motion-one.json", ROOT / "coverage/.motion-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_kinematics_motion_report.generate(ROOT, first), generate_kinematics_motion_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
33
scripts/test_kinematics_registry_report.py
Normal file
33
scripts/test_kinematics_registry_report.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_kinematics_registry_report
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class KinematicsRegistryReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/kinematics-registry.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "kinematics-registry-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["registry"]["entry_count"], 18)
|
||||
self.assertEqual(report["registry"]["verified_entries"], 18)
|
||||
self.assertEqual(report["registry"]["capability_union"], 127)
|
||||
self.assertEqual(report["registry"]["parameter_union"], 255)
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.registry-one.json", ROOT / "coverage/.registry-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_kinematics_registry_report.generate(ROOT, first), generate_kinematics_registry_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -12,6 +12,10 @@ class PentapodKinematicsReportTests(unittest.TestCase):
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertTrue(report["configuration_candidate"]["scan"])
|
||||
self.assertEqual(report["geometry"]["struts"], 5)
|
||||
self.assertEqual(report["mapping"]["coordinates"], "XYZAB")
|
||||
self.assertEqual(report["workspace_and_boundaries"]["singular_jacobian_error"], -704)
|
||||
self.assertEqual(report["workspace_and_boundaries"]["nonpositive_joint_error"], -702)
|
||||
self.assertEqual(len(report["configuration_candidates"]), 2)
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
37
scripts/test_switchkins_report.py
Normal file
37
scripts/test_switchkins_report.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_switchkins_report
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class SwitchkinsReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/switchkins-kinematics.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "switchkins-kinematics-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertTrue(report["mode_registry"]["identityfirst"])
|
||||
self.assertEqual(report["mode_registry"]["names"]["2"], "userk")
|
||||
self.assertTrue(report["state_and_errors"]["pose_continuity"]["identity_to_trt"])
|
||||
self.assertTrue(report["state_and_errors"]["state_hash_stable"])
|
||||
self.assertEqual(report["state_and_errors"]["invalid_mode_error"], -707)
|
||||
self.assertEqual(report["state_and_errors"]["missing_module_error"], -708)
|
||||
self.assertEqual(report["state_and_errors"]["switch_interrupted_error"], -709)
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.switch-one.json", ROOT / "coverage/.switch-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_switchkins_report.generate(ROOT, first), generate_switchkins_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
36
scripts/test_userkins_report.py
Normal file
36
scripts/test_userkins_report.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import generate_userkins_report
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class UserkinsReportTests(unittest.TestCase):
|
||||
def test_contract(self):
|
||||
report = json.loads((ROOT / "coverage/userkins-kinematics.report.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(report["schema"], "userkins-kinematics-report.v1")
|
||||
self.assertEqual(report["implementation_status"], "verified")
|
||||
self.assertEqual(report["static_contract"]["abi_version"], 1)
|
||||
self.assertEqual(report["static_contract"]["switch_binding"]["active_after_select"], 2)
|
||||
self.assertEqual(report["ugenser_scope"]["legacy_cli_status"], "excluded")
|
||||
self.assertTrue(report["ugenser_scope"]["source_warning_present"])
|
||||
self.assertEqual(report["error_boundaries"]["dynamic_module"], -712)
|
||||
self.assertTrue(report["samples"]["byte_identical_json"])
|
||||
self.assertTrue(report["samples"]["state_hash_identical"])
|
||||
self.assertTrue(report["samples"]["state_hash_stable"])
|
||||
self.assertEqual(report["oracle"]["status"], "blocked")
|
||||
|
||||
def test_deterministic(self):
|
||||
first, second = ROOT / "coverage/.user-one.json", ROOT / "coverage/.user-two.json"
|
||||
try:
|
||||
self.assertEqual(generate_userkins_report.generate(ROOT, first), generate_userkins_report.generate(ROOT, second))
|
||||
finally:
|
||||
first.unlink(missing_ok=True)
|
||||
second.unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -154,6 +154,56 @@ def validate(repo_root: Path, lock_path: Path, source_path: Path, manifest_path:
|
||||
errors.append("workspace recovery report is missing")
|
||||
elif recovery_report_hash != sha256(recovery_report_path):
|
||||
errors.append("workspace recovery report hash does not match context lock")
|
||||
if recovery_report_path.is_file():
|
||||
try:
|
||||
recovery_report = json.loads(recovery_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("workspace recovery report is not valid JSON")
|
||||
else:
|
||||
if recovery_report.get("status") != "passed":
|
||||
errors.append("workspace recovery report is not passed")
|
||||
if recovery_report.get("implementation_status") != "verified":
|
||||
errors.append("workspace recovery implementation is not verified")
|
||||
if recovery_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("workspace recovery oracle status must remain explicitly blocked")
|
||||
configuration_regression_path = repo_root / "coverage/configuration-regression.report.json"
|
||||
configuration_regression_hash = lock.get("configuration_regression_report_sha256")
|
||||
if configuration_regression_hash:
|
||||
if not configuration_regression_path.is_file():
|
||||
errors.append("configuration regression report is missing")
|
||||
elif configuration_regression_hash != sha256(configuration_regression_path):
|
||||
errors.append("configuration regression report hash does not match context lock")
|
||||
if configuration_regression_path.is_file():
|
||||
try:
|
||||
configuration_regression = json.loads(configuration_regression_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("configuration regression report is not valid JSON")
|
||||
else:
|
||||
if configuration_regression.get("implementation_status") != "verified":
|
||||
errors.append("configuration regression implementation is not verified")
|
||||
if configuration_regression.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("configuration regression oracle status must remain explicitly blocked")
|
||||
closure = configuration_regression.get("closure", {})
|
||||
if closure.get("graph_orphan_entry_ids") != []:
|
||||
errors.append("configuration regression graph contains orphan entries")
|
||||
if closure.get("unowned_template_ids") != []:
|
||||
errors.append("configuration regression graph contains unowned templates")
|
||||
if closure.get("resource_orphan_entrypoints") != []:
|
||||
errors.append("configuration regression resource graph contains orphan entrypoints")
|
||||
if configuration_regression.get("runtime", {}).get("native_wasm", {}).get("status") != "passed":
|
||||
errors.append("configuration regression native/WASM summary is not passed")
|
||||
if configuration_regression.get("runtime", {}).get("worker", {}).get("status") != "passed":
|
||||
errors.append("configuration regression Worker summary is not passed")
|
||||
if isinstance(inventory_count, dict):
|
||||
report_counts = configuration_regression.get("counts", {})
|
||||
for inventory_key, report_key in (
|
||||
("configuration_regression_entries", "registered_configuration_entries"),
|
||||
("configuration_regression_entrypoints", "config_entrypoints"),
|
||||
("configuration_regression_resources", "resource_rows"),
|
||||
):
|
||||
value = report_counts.get(report_key)
|
||||
if value is not None and inventory_count.get(inventory_key) != value:
|
||||
errors.append(f"{inventory_key} count does not match configuration regression report")
|
||||
runtime_report_path = repo_root / "coverage/runtime-mapping.report.json"
|
||||
runtime_report_hash = lock.get("runtime_mapping_report_sha256")
|
||||
if runtime_report_hash:
|
||||
@@ -203,6 +253,13 @@ def validate(repo_root: Path, lock_path: Path, source_path: Path, manifest_path:
|
||||
errors.append("motion runtime report is missing")
|
||||
elif motion_report_hash != sha256(motion_report_path):
|
||||
errors.append("motion runtime report hash does not match context lock")
|
||||
io_report_path = repo_root / "coverage/io-runtime.report.json"
|
||||
io_report_hash = lock.get("io_runtime_report_sha256")
|
||||
if io_report_hash:
|
||||
if not io_report_path.is_file():
|
||||
errors.append("io runtime report is missing")
|
||||
elif io_report_hash != sha256(io_report_path):
|
||||
errors.append("io runtime report hash does not match context lock")
|
||||
kinematics_runtime_report_path = repo_root / "coverage/kinematics-runtime.report.json"
|
||||
kinematics_runtime_report_hash = lock.get("kinematics_runtime_report_sha256")
|
||||
if kinematics_runtime_report_hash:
|
||||
@@ -484,6 +541,125 @@ def validate(repo_root: Path, lock_path: Path, source_path: Path, manifest_path:
|
||||
errors.append("genhex kinematics implementation is not verified")
|
||||
if genhex_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("genhex kinematics oracle status must remain explicitly blocked")
|
||||
pentapod_report_path = repo_root / "coverage/pentapod-kinematics.report.json"
|
||||
pentapod_report_hash = lock.get("pentapod_kinematics_report_sha256")
|
||||
if pentapod_report_hash:
|
||||
if not pentapod_report_path.is_file():
|
||||
errors.append("pentapod kinematics report is missing")
|
||||
elif pentapod_report_hash != sha256(pentapod_report_path):
|
||||
errors.append("pentapod kinematics report hash does not match context lock")
|
||||
if pentapod_report_path.is_file():
|
||||
try:
|
||||
pentapod_report = json.loads(pentapod_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("pentapod kinematics report is not valid JSON")
|
||||
else:
|
||||
if pentapod_report.get("implementation_status") != "verified":
|
||||
errors.append("pentapod kinematics implementation is not verified")
|
||||
if pentapod_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("pentapod kinematics oracle status must remain explicitly blocked")
|
||||
switchkins_report_path = repo_root / "coverage/switchkins-kinematics.report.json"
|
||||
switchkins_report_hash = lock.get("switchkins_kinematics_report_sha256")
|
||||
if switchkins_report_hash:
|
||||
if not switchkins_report_path.is_file():
|
||||
errors.append("switchkins kinematics report is missing")
|
||||
elif switchkins_report_hash != sha256(switchkins_report_path):
|
||||
errors.append("switchkins kinematics report hash does not match context lock")
|
||||
if switchkins_report_path.is_file():
|
||||
try:
|
||||
switchkins_report = json.loads(switchkins_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("switchkins kinematics report is not valid JSON")
|
||||
else:
|
||||
if switchkins_report.get("implementation_status") != "verified":
|
||||
errors.append("switchkins kinematics implementation is not verified")
|
||||
if switchkins_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("switchkins kinematics oracle status must remain explicitly blocked")
|
||||
userkins_report_path = repo_root / "coverage/userkins-kinematics.report.json"
|
||||
userkins_report_hash = lock.get("userkins_kinematics_report_sha256")
|
||||
if userkins_report_hash:
|
||||
if not userkins_report_path.is_file():
|
||||
errors.append("userkins kinematics report is missing")
|
||||
elif userkins_report_hash != sha256(userkins_report_path):
|
||||
errors.append("userkins kinematics report hash does not match context lock")
|
||||
if userkins_report_path.is_file():
|
||||
try:
|
||||
userkins_report = json.loads(userkins_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("userkins kinematics report is not valid JSON")
|
||||
else:
|
||||
if userkins_report.get("implementation_status") != "verified":
|
||||
errors.append("userkins kinematics implementation is not verified")
|
||||
if userkins_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("userkins kinematics oracle status must remain explicitly blocked")
|
||||
registry_report_path = repo_root / "coverage/kinematics-registry.report.json"
|
||||
registry_report_hash = lock.get("kinematics_registry_report_sha256")
|
||||
if registry_report_hash:
|
||||
if not registry_report_path.is_file():
|
||||
errors.append("kinematics registry report is missing")
|
||||
elif registry_report_hash != sha256(registry_report_path):
|
||||
errors.append("kinematics registry report hash does not match context lock")
|
||||
if registry_report_path.is_file():
|
||||
try:
|
||||
registry_report = json.loads(registry_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("kinematics registry report is not valid JSON")
|
||||
else:
|
||||
if registry_report.get("implementation_status") != "verified":
|
||||
errors.append("kinematics registry implementation is not verified")
|
||||
if registry_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("kinematics registry oracle status must remain explicitly blocked")
|
||||
motion_report_path = repo_root / "coverage/kinematics-motion.report.json"
|
||||
motion_report_hash = lock.get("kinematics_motion_report_sha256")
|
||||
if motion_report_hash:
|
||||
if not motion_report_path.is_file():
|
||||
errors.append("kinematics motion report is missing")
|
||||
elif motion_report_hash != sha256(motion_report_path):
|
||||
errors.append("kinematics motion report hash does not match context lock")
|
||||
if motion_report_path.is_file():
|
||||
try:
|
||||
motion_report = json.loads(motion_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("kinematics motion report is not valid JSON")
|
||||
else:
|
||||
if motion_report.get("implementation_status") != "verified":
|
||||
errors.append("kinematics motion implementation is not verified")
|
||||
if motion_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("kinematics motion oracle status must remain explicitly blocked")
|
||||
feedback_report_path = repo_root / "coverage/kinematics-feedback.report.json"
|
||||
feedback_report_hash = lock.get("kinematics_feedback_report_sha256")
|
||||
if feedback_report_hash:
|
||||
if not feedback_report_path.is_file():
|
||||
errors.append("kinematics feedback report is missing")
|
||||
elif feedback_report_hash != sha256(feedback_report_path):
|
||||
errors.append("kinematics feedback report hash does not match context lock")
|
||||
if feedback_report_path.is_file():
|
||||
try:
|
||||
feedback_report = json.loads(feedback_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("kinematics feedback report is not valid JSON")
|
||||
else:
|
||||
if feedback_report.get("implementation_status") != "verified":
|
||||
errors.append("kinematics feedback implementation is not verified")
|
||||
if feedback_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("kinematics feedback oracle status must remain explicitly blocked")
|
||||
home_limit_report_path = repo_root / "coverage/kinematics-home-limit.report.json"
|
||||
home_limit_report_hash = lock.get("kinematics_home_limit_report_sha256")
|
||||
if home_limit_report_hash:
|
||||
if not home_limit_report_path.is_file():
|
||||
errors.append("kinematics home limit report is missing")
|
||||
elif home_limit_report_hash != sha256(home_limit_report_path):
|
||||
errors.append("kinematics home limit report hash does not match context lock")
|
||||
if home_limit_report_path.is_file():
|
||||
try:
|
||||
home_limit_report = json.loads(home_limit_report_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
errors.append("kinematics home limit report is not valid JSON")
|
||||
else:
|
||||
if home_limit_report.get("implementation_status") != "verified":
|
||||
errors.append("kinematics home limit implementation is not verified")
|
||||
if home_limit_report.get("oracle", {}).get("status") != "blocked":
|
||||
errors.append("kinematics home limit oracle status must remain explicitly blocked")
|
||||
source_ids = [str(row.get("source_id", "")) for row in sources]
|
||||
if len(source_ids) != len(set(source_ids)):
|
||||
errors.append("source index contains duplicate source_id")
|
||||
|
||||
@@ -6,7 +6,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(sim_core STATIC src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c src/sim_interpreter.c src/sim_motion_queue.c src/sim_kinematics.c)
|
||||
add_library(sim_core STATIC src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c src/sim_interpreter.c src/sim_motion_queue.c src/sim_kinematics.c src/sim_switchkins.c src/sim_userkins.c src/sim_kinematics_registry.c src/sim_kinematics_motion.c src/sim_kinematics_feedback.c src/sim_kinematics_home_limit.c src/sim_io.c)
|
||||
target_include_directories(sim_core PUBLIC include)
|
||||
target_compile_definitions(sim_core PRIVATE SIM_API_BUILD=1)
|
||||
|
||||
@@ -70,6 +70,48 @@ if(NOT EMSCRIPTEN)
|
||||
target_include_directories(sim_kinematics_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_kinematics_test COMMAND sim_kinematics_test)
|
||||
|
||||
add_executable(sim_switchkins_test tests/sim_switchkins_test.c)
|
||||
target_link_libraries(sim_switchkins_test PRIVATE sim_core)
|
||||
target_include_directories(sim_switchkins_test PRIVATE include)
|
||||
target_compile_options(sim_switchkins_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_switchkins_test COMMAND sim_switchkins_test)
|
||||
|
||||
add_executable(sim_userkins_test tests/sim_userkins_test.c)
|
||||
target_link_libraries(sim_userkins_test PRIVATE sim_core)
|
||||
target_include_directories(sim_userkins_test PRIVATE include)
|
||||
target_compile_options(sim_userkins_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_userkins_test COMMAND sim_userkins_test)
|
||||
|
||||
add_executable(sim_kinematics_registry_test tests/sim_kinematics_registry_test.c)
|
||||
target_link_libraries(sim_kinematics_registry_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_registry_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_registry_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_kinematics_registry_test COMMAND sim_kinematics_registry_test)
|
||||
|
||||
add_executable(sim_kinematics_motion_test tests/sim_kinematics_motion_test.c)
|
||||
target_link_libraries(sim_kinematics_motion_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_motion_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_motion_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_kinematics_motion_test COMMAND sim_kinematics_motion_test)
|
||||
|
||||
add_executable(sim_kinematics_feedback_test tests/sim_kinematics_feedback_test.c)
|
||||
target_link_libraries(sim_kinematics_feedback_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_feedback_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_feedback_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_kinematics_feedback_test COMMAND sim_kinematics_feedback_test)
|
||||
|
||||
add_executable(sim_kinematics_home_limit_test tests/sim_kinematics_home_limit_test.c)
|
||||
target_link_libraries(sim_kinematics_home_limit_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_home_limit_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_home_limit_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_kinematics_home_limit_test COMMAND sim_kinematics_home_limit_test)
|
||||
|
||||
add_executable(sim_io_test tests/sim_io_test.c)
|
||||
target_link_libraries(sim_io_test PRIVATE sim_core)
|
||||
target_include_directories(sim_io_test PRIVATE include)
|
||||
target_compile_options(sim_io_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME sim_io_test COMMAND sim_io_test)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
@@ -85,7 +127,49 @@ if(EMSCRIPTEN)
|
||||
target_compile_options(sim_kinematics_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_kinematics_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_core_module src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c src/sim_interpreter.c src/sim_motion_queue.c src/sim_kinematics.c)
|
||||
add_executable(sim_switchkins_wasm_test tests/sim_switchkins_test.c)
|
||||
target_link_libraries(sim_switchkins_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_switchkins_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_switchkins_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_switchkins_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_userkins_wasm_test tests/sim_userkins_test.c)
|
||||
target_link_libraries(sim_userkins_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_userkins_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_userkins_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_userkins_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_kinematics_registry_wasm_test tests/sim_kinematics_registry_test.c)
|
||||
target_link_libraries(sim_kinematics_registry_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_registry_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_registry_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_kinematics_registry_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_kinematics_motion_wasm_test tests/sim_kinematics_motion_test.c)
|
||||
target_link_libraries(sim_kinematics_motion_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_motion_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_motion_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_kinematics_motion_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_kinematics_feedback_wasm_test tests/sim_kinematics_feedback_test.c)
|
||||
target_link_libraries(sim_kinematics_feedback_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_feedback_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_feedback_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_kinematics_feedback_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_kinematics_home_limit_wasm_test tests/sim_kinematics_home_limit_test.c)
|
||||
target_link_libraries(sim_kinematics_home_limit_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_kinematics_home_limit_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_kinematics_home_limit_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_kinematics_home_limit_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_io_wasm_test tests/sim_io_test.c)
|
||||
target_link_libraries(sim_io_wasm_test PRIVATE sim_core)
|
||||
target_include_directories(sim_io_wasm_test PRIVATE include)
|
||||
target_compile_options(sim_io_wasm_test PRIVATE -UNDEBUG)
|
||||
target_link_options(sim_io_wasm_test PRIVATE -sSINGLE_FILE=1)
|
||||
|
||||
add_executable(sim_core_module src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c src/sim_interpreter.c src/sim_motion_queue.c src/sim_kinematics.c src/sim_switchkins.c src/sim_userkins.c src/sim_kinematics_registry.c src/sim_kinematics_motion.c src/sim_kinematics_feedback.c src/sim_kinematics_home_limit.c src/sim_io.c)
|
||||
target_include_directories(sim_core_module PRIVATE
|
||||
include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src
|
||||
@@ -97,6 +181,6 @@ 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_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_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_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_init','_sim_interpreter_reset','_sim_interpreter_execute','_sim_interpreter_read_state','_sim_interpreter_canonical','_sim_interpreter_state_hash','_sim_interpreter_block_hash','_sim_motion_init','_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'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']"
|
||||
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_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_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_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_init','_sim_interpreter_reset','_sim_interpreter_execute','_sim_interpreter_read_state','_sim_interpreter_canonical','_sim_interpreter_state_hash','_sim_interpreter_block_hash','_sim_motion_init','_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']"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -8,7 +8,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#define SIM_HAL_NAME_CAPACITY 48U
|
||||
#define SIM_HAL_PIN_CAPACITY 64U
|
||||
#define SIM_HAL_PIN_CAPACITY 256U
|
||||
#define SIM_HAL_THREAD_CAPACITY 16U
|
||||
#define SIM_HAL_FUNCTION_CAPACITY 64U
|
||||
|
||||
|
||||
151
wasm-core/include/sim_io.h
Normal file
151
wasm-core/include/sim_io.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#ifndef LINUXCNC_WEB_WASM_SIM_IO_H
|
||||
#define LINUXCNC_WEB_WASM_SIM_IO_H
|
||||
|
||||
#include "sim_posemath.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SIM_IO_MAX_SPINDLES 8u
|
||||
#define SIM_IO_MAX_TOOLS 1001u
|
||||
|
||||
enum sim_io_result {
|
||||
SIM_IO_OK = 0,
|
||||
SIM_IO_ERR_ARGUMENT = -900,
|
||||
SIM_IO_ERR_RANGE = -901,
|
||||
SIM_IO_ERR_STATE = -902,
|
||||
SIM_IO_ERR_NOT_READY = -903,
|
||||
SIM_IO_ERR_ESTOP = -904,
|
||||
SIM_IO_ERR_FAULT = -905,
|
||||
SIM_IO_ERR_LIMIT = -906,
|
||||
SIM_IO_ERR_NONFINITE = -907,
|
||||
SIM_IO_ERR_PIN = -908,
|
||||
SIM_IO_ERR_CAPACITY = -909,
|
||||
SIM_IO_ERR_BUSY = -910,
|
||||
SIM_IO_ERR_PROBE = -911,
|
||||
SIM_IO_ERR_TIMEOUT = -912,
|
||||
SIM_IO_ERR_RECOVERY = -913,
|
||||
};
|
||||
|
||||
enum sim_io_tool_state {
|
||||
SIM_IO_TOOL_IDLE = 0,
|
||||
SIM_IO_TOOL_PREPARING = 1,
|
||||
SIM_IO_TOOL_PREPARED = 2,
|
||||
SIM_IO_TOOL_CHANGING = 3,
|
||||
SIM_IO_TOOL_READY = 4,
|
||||
SIM_IO_TOOL_ABORTED = 5,
|
||||
SIM_IO_TOOL_FAULT = 6,
|
||||
};
|
||||
|
||||
enum sim_io_probe_status {
|
||||
SIM_IO_PROBE_IDLE = 0,
|
||||
SIM_IO_PROBE_ARMED = 1,
|
||||
SIM_IO_PROBE_TRIPPED = 2,
|
||||
SIM_IO_PROBE_FAILED = 3,
|
||||
};
|
||||
|
||||
typedef struct sim_io_spindle_state {
|
||||
uint32_t registered;
|
||||
uint32_t enabled;
|
||||
uint32_t inhibit;
|
||||
uint32_t amp_fault;
|
||||
uint32_t at_speed;
|
||||
uint32_t brake;
|
||||
uint32_t forward;
|
||||
uint32_t reverse;
|
||||
uint32_t index_enable;
|
||||
uint32_t orient;
|
||||
uint32_t locked;
|
||||
uint32_t is_oriented;
|
||||
int32_t orient_mode;
|
||||
int32_t orient_fault;
|
||||
double command_rpm;
|
||||
double speed_rpm;
|
||||
double max_rpm;
|
||||
double accel_rpm_s;
|
||||
double tolerance_rpm;
|
||||
double orient_angle;
|
||||
double revs;
|
||||
uint32_t tick;
|
||||
} sim_io_spindle_state;
|
||||
|
||||
typedef struct sim_io_probe_state {
|
||||
uint32_t input;
|
||||
uint32_t probe_type;
|
||||
uint32_t state;
|
||||
uint32_t tripped;
|
||||
uint32_t tick;
|
||||
sim_posemath_pose position;
|
||||
} sim_io_probe_state;
|
||||
|
||||
typedef struct sim_io_state {
|
||||
uint32_t ready;
|
||||
uint32_t estop;
|
||||
uint32_t fault;
|
||||
int32_t fault_reason;
|
||||
uint32_t tick;
|
||||
uint32_t recovery_epoch;
|
||||
uint32_t spindle_count;
|
||||
uint32_t tool_capacity;
|
||||
sim_io_spindle_state spindles[SIM_IO_MAX_SPINDLES];
|
||||
uint32_t tool_state;
|
||||
int32_t current_tool;
|
||||
int32_t prepared_tool;
|
||||
int32_t prepared_pocket;
|
||||
uint32_t tool_prepare;
|
||||
uint32_t tool_prepared;
|
||||
uint32_t tool_change;
|
||||
uint32_t tool_changed;
|
||||
uint32_t toolchanger_fault;
|
||||
int32_t toolchanger_reason;
|
||||
sim_io_probe_state probe;
|
||||
int32_t last_error;
|
||||
} sim_io_state;
|
||||
|
||||
typedef struct sim_io {
|
||||
sim_io_state state;
|
||||
double cycle_time;
|
||||
uint32_t pins_registered;
|
||||
} sim_io;
|
||||
|
||||
int sim_io_init(sim_io *io, uint32_t spindle_count, uint32_t tool_capacity,
|
||||
double cycle_time, double spindle_accel_rpm_s,
|
||||
double spindle_tolerance_rpm);
|
||||
int sim_io_reset(sim_io *io);
|
||||
int sim_io_register_pins(sim_io *io);
|
||||
int sim_io_spindle_command(sim_io *io, uint32_t spindle, double rpm,
|
||||
uint32_t enabled);
|
||||
int sim_io_spindle_set_inhibit(sim_io *io, uint32_t spindle, uint32_t inhibit);
|
||||
int sim_io_spindle_set_fault(sim_io *io, uint32_t spindle, uint32_t fault);
|
||||
int sim_io_spindle_set_index_enable(sim_io *io, uint32_t spindle, uint32_t enable);
|
||||
int sim_io_spindle_set_feedback(sim_io *io, uint32_t spindle, double rpm);
|
||||
int sim_io_spindle_orient(sim_io *io, uint32_t spindle, double angle,
|
||||
int32_t mode);
|
||||
int sim_io_spindle_set_oriented(sim_io *io, uint32_t spindle,
|
||||
uint32_t oriented, int32_t fault);
|
||||
int sim_io_tool_prepare(sim_io *io, int32_t tool, int32_t pocket);
|
||||
int sim_io_tool_set_prepared(sim_io *io, uint32_t prepared);
|
||||
int sim_io_tool_change(sim_io *io);
|
||||
int sim_io_tool_set_changed(sim_io *io, uint32_t changed);
|
||||
int sim_io_tool_abort(sim_io *io, int32_t reason);
|
||||
int sim_io_tool_set_fault(sim_io *io, uint32_t fault, int32_t reason);
|
||||
int sim_io_tool_clear_fault(sim_io *io);
|
||||
int sim_io_probe_arm(sim_io *io, uint32_t probe_type);
|
||||
int sim_io_probe_update(sim_io *io, const sim_posemath_pose *pose,
|
||||
uint32_t input);
|
||||
int sim_io_probe_disarm(sim_io *io);
|
||||
int sim_io_set_estop(sim_io *io, uint32_t estop);
|
||||
int sim_io_recover(sim_io *io);
|
||||
int sim_io_step(sim_io *io, uint32_t ticks);
|
||||
int sim_io_read_state(const sim_io *io, sim_io_state *state);
|
||||
uint64_t sim_io_state_hash(const sim_io *io);
|
||||
uint32_t sim_io_canonical(const sim_io *io, char *output, uint32_t capacity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -16,6 +16,12 @@ enum sim_kinematics_result {
|
||||
SIM_KIN_ERR_SINGULAR = -704,
|
||||
SIM_KIN_ERR_UNKNOWN_TYPE = -705,
|
||||
SIM_KIN_ERR_UNSUPPORTED = -706,
|
||||
SIM_KIN_ERR_INVALID_MODE = -707,
|
||||
SIM_KIN_ERR_MISSING_MODULE = -708,
|
||||
SIM_KIN_ERR_SWITCH_INTERRUPTED = -709,
|
||||
SIM_KIN_ERR_ABI_VERSION = -710,
|
||||
SIM_KIN_ERR_CAPABILITY = -711,
|
||||
SIM_KIN_ERR_DYNAMIC_MODULE = -712,
|
||||
};
|
||||
|
||||
enum sim_kinematics_type {
|
||||
@@ -60,6 +66,34 @@ enum sim_kinematics_mode {
|
||||
#define SIM_KIN_MAX_JOINTS 9u
|
||||
#define SIM_KIN_GENHEX_STRUTS 6u
|
||||
#define SIM_KIN_PENTA_STRUTS 5u
|
||||
#define SIM_KIN_SWITCH_MAX_TYPES 3u
|
||||
#define SIM_KIN_USER_ABI_VERSION 1u
|
||||
#define SIM_KIN_USER_CAP_FORWARD UINT32_C(1)
|
||||
#define SIM_KIN_USER_CAP_INVERSE UINT32_C(2)
|
||||
#define SIM_KIN_USER_CAP_STATIC UINT32_C(4)
|
||||
#define SIM_KIN_USER_NAME_CAPACITY 32u
|
||||
#define SIM_KIN_REGISTRY_ABI_VERSION 1u
|
||||
#define SIM_KIN_REGISTRY_NAME_CAPACITY 32u
|
||||
#define SIM_KIN_CAP_FORWARD UINT32_C(1)
|
||||
#define SIM_KIN_CAP_INVERSE UINT32_C(2)
|
||||
#define SIM_KIN_CAP_IDENTITY UINT32_C(4)
|
||||
#define SIM_KIN_CAP_ITERATIVE UINT32_C(8)
|
||||
#define SIM_KIN_CAP_SWITCHABLE UINT32_C(16)
|
||||
#define SIM_KIN_CAP_EXTRA_JOINTS UINT32_C(32)
|
||||
#define SIM_KIN_CAP_DUPLICATE_COORDINATES UINT32_C(64)
|
||||
#define SIM_KIN_PARAM_MAPPING UINT64_C(1)
|
||||
#define SIM_KIN_PARAM_LIMITS UINT64_C(2)
|
||||
#define SIM_KIN_PARAM_OFFSETS UINT64_C(4)
|
||||
#define SIM_KIN_PARAM_TOOL UINT64_C(8)
|
||||
#define SIM_KIN_PARAM_PIVOT UINT64_C(16)
|
||||
#define SIM_KIN_PARAM_GEOMETRY UINT64_C(32)
|
||||
#define SIM_KIN_PARAM_DH UINT64_C(64)
|
||||
#define SIM_KIN_PARAM_SOLVER UINT64_C(128)
|
||||
|
||||
enum sim_kinematics_user_implementation {
|
||||
SIM_KIN_USER_IDENTITY = 1,
|
||||
SIM_KIN_USER_GENSER = 2,
|
||||
};
|
||||
|
||||
typedef struct sim_kinematics_config {
|
||||
int type;
|
||||
@@ -149,6 +183,52 @@ typedef struct sim_kinematics_result_info {
|
||||
uint32_t branch_id;
|
||||
} sim_kinematics_result_info;
|
||||
|
||||
/* A bounded switchkins container matching LinuxCNC's three registered
|
||||
* kinematics slots. The container owns switching state, not the modules. */
|
||||
typedef struct sim_kinematics_switch {
|
||||
sim_kinematics *modules[SIM_KIN_SWITCH_MAX_TYPES];
|
||||
uint8_t registered[SIM_KIN_SWITCH_MAX_TYPES];
|
||||
uint8_t fwd_iterates_mask;
|
||||
uint8_t switching;
|
||||
int active_mode;
|
||||
int pending_mode;
|
||||
uint32_t generation;
|
||||
uint32_t switch_count;
|
||||
uint32_t abort_count;
|
||||
sim_posemath_pose last_pose[SIM_KIN_SWITCH_MAX_TYPES];
|
||||
uint8_t have_last_pose[SIM_KIN_SWITCH_MAX_TYPES];
|
||||
} sim_kinematics_switch;
|
||||
|
||||
/* User kinematics are selected from implementations already linked into the
|
||||
* WASM module. No function pointers, host paths, or dynamic libraries enter
|
||||
* this descriptor. */
|
||||
typedef struct sim_kinematics_user_descriptor {
|
||||
uint32_t abi_version;
|
||||
uint32_t capabilities;
|
||||
int implementation;
|
||||
char name[SIM_KIN_USER_NAME_CAPACITY];
|
||||
sim_kinematics_config config;
|
||||
} sim_kinematics_user_descriptor;
|
||||
|
||||
typedef struct sim_kinematics_user {
|
||||
sim_kinematics_user_descriptor descriptor;
|
||||
sim_kinematics module;
|
||||
uint32_t initialized;
|
||||
uint32_t forward_calls;
|
||||
uint32_t inverse_calls;
|
||||
} sim_kinematics_user;
|
||||
|
||||
typedef struct sim_kinematics_capability {
|
||||
uint32_t abi_version;
|
||||
int type;
|
||||
char name[SIM_KIN_REGISTRY_NAME_CAPACITY];
|
||||
char required_coordinates[16];
|
||||
uint32_t min_joints;
|
||||
uint32_t max_joints;
|
||||
uint32_t capabilities;
|
||||
uint64_t parameter_mask;
|
||||
} sim_kinematics_capability;
|
||||
|
||||
int sim_kinematics_init(sim_kinematics *kinematics, const sim_kinematics_config *config);
|
||||
int sim_kinematics_forward(const sim_kinematics *kinematics, const double *joints,
|
||||
uint32_t joint_count, sim_posemath_pose *pose);
|
||||
@@ -167,6 +247,60 @@ uint64_t sim_kinematics_state_hash(const sim_kinematics *kinematics,
|
||||
const double *joints, uint32_t joint_count,
|
||||
const sim_posemath_pose *pose);
|
||||
|
||||
int sim_kinematics_switch_init(sim_kinematics_switch *switcher, int default_mode,
|
||||
uint8_t fwd_iterates_mask);
|
||||
int sim_kinematics_switch_register(sim_kinematics_switch *switcher, int mode,
|
||||
sim_kinematics *module, uint8_t fwd_iterates);
|
||||
int sim_kinematics_switch_begin(sim_kinematics_switch *switcher, int mode);
|
||||
int sim_kinematics_switch_abort(sim_kinematics_switch *switcher);
|
||||
int sim_kinematics_switch_commit(sim_kinematics_switch *switcher, double *joints,
|
||||
uint32_t joint_count, sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_switch_select(sim_kinematics_switch *switcher, int mode,
|
||||
double *joints, uint32_t joint_count,
|
||||
sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_switch_forward(sim_kinematics_switch *switcher,
|
||||
const double *joints, uint32_t joint_count,
|
||||
sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_switch_inverse(sim_kinematics_switch *switcher,
|
||||
const sim_posemath_pose *pose, double *joints,
|
||||
uint32_t joint_count,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_switch_active(const sim_kinematics_switch *switcher);
|
||||
uint64_t sim_kinematics_switch_state_hash(const sim_kinematics_switch *switcher,
|
||||
const double *joints, uint32_t joint_count,
|
||||
const sim_posemath_pose *pose);
|
||||
int sim_kinematics_user_init(sim_kinematics_user *user,
|
||||
const sim_kinematics_user_descriptor *descriptor);
|
||||
int sim_kinematics_user_bind_switch(sim_kinematics_user *user,
|
||||
sim_kinematics_switch *switcher, int mode,
|
||||
uint8_t fwd_iterates);
|
||||
int sim_kinematics_user_forward(sim_kinematics_user *user, const double *joints,
|
||||
uint32_t joint_count, sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_user_inverse(sim_kinematics_user *user,
|
||||
const sim_posemath_pose *pose, double *joints,
|
||||
uint32_t joint_count,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_user_reject_dynamic(const char *module_path);
|
||||
uint64_t sim_kinematics_user_state_hash(const sim_kinematics_user *user,
|
||||
const double *joints, uint32_t joint_count,
|
||||
const sim_posemath_pose *pose);
|
||||
uint32_t sim_kinematics_registry_count(void);
|
||||
int sim_kinematics_registry_get(uint32_t index,
|
||||
sim_kinematics_capability *capability);
|
||||
int sim_kinematics_registry_find(const char *name,
|
||||
sim_kinematics_capability *capability);
|
||||
int sim_kinematics_registry_require(const char *name, uint32_t capabilities,
|
||||
uint64_t parameter_mask,
|
||||
sim_kinematics_capability *capability);
|
||||
int sim_kinematics_registry_validate_config(const char *name,
|
||||
const sim_kinematics_config *config,
|
||||
sim_kinematics_capability *capability);
|
||||
uint64_t sim_kinematics_registry_state_hash(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
52
wasm-core/include/sim_kinematics_feedback.h
Normal file
52
wasm-core/include/sim_kinematics_feedback.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef LINUXCNC_WEB_WASM_SIM_KINEMATICS_FEEDBACK_H
|
||||
#define LINUXCNC_WEB_WASM_SIM_KINEMATICS_FEEDBACK_H
|
||||
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum sim_kinematics_feedback_result {
|
||||
SIM_KIN_FEEDBACK_OK = 0,
|
||||
SIM_KIN_FEEDBACK_ERR_ARGUMENT = -820,
|
||||
SIM_KIN_FEEDBACK_ERR_CONFIGURATION = -821,
|
||||
SIM_KIN_FEEDBACK_ERR_NONFINITE = -822,
|
||||
SIM_KIN_FEEDBACK_ERR_FORWARD = -823,
|
||||
SIM_KIN_FEEDBACK_ERR_STATE = -824,
|
||||
};
|
||||
|
||||
typedef struct sim_kinematics_feedback_state {
|
||||
sim_posemath_pose pose;
|
||||
uint64_t forward_flags;
|
||||
uint64_t inverse_flags;
|
||||
double joints[SIM_KIN_MAX_JOINTS];
|
||||
uint32_t joint_count;
|
||||
uint32_t tick;
|
||||
uint32_t valid;
|
||||
int last_error;
|
||||
int kinematics_error;
|
||||
} sim_kinematics_feedback_state;
|
||||
|
||||
typedef struct sim_kinematics_feedback {
|
||||
sim_kinematics *kinematics;
|
||||
sim_kinematics_feedback_state state;
|
||||
} sim_kinematics_feedback;
|
||||
|
||||
int sim_kinematics_feedback_init(sim_kinematics_feedback *feedback,
|
||||
sim_kinematics *kinematics,
|
||||
uint32_t joint_count);
|
||||
int sim_kinematics_feedback_reset(sim_kinematics_feedback *feedback);
|
||||
int sim_kinematics_feedback_update(sim_kinematics_feedback *feedback,
|
||||
const double *joints, uint32_t joint_count,
|
||||
uint64_t inverse_flags,
|
||||
sim_kinematics_result_info *info);
|
||||
int sim_kinematics_feedback_read(const sim_kinematics_feedback *feedback,
|
||||
sim_kinematics_feedback_state *state);
|
||||
uint64_t sim_kinematics_feedback_state_hash(const sim_kinematics_feedback *feedback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
55
wasm-core/include/sim_kinematics_home_limit.h
Normal file
55
wasm-core/include/sim_kinematics_home_limit.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef LINUXCNC_WEB_WASM_SIM_KINEMATICS_HOME_LIMIT_H
|
||||
#define LINUXCNC_WEB_WASM_SIM_KINEMATICS_HOME_LIMIT_H
|
||||
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum sim_kinematics_home_limit_result {
|
||||
SIM_KIN_HOME_OK = 0,
|
||||
SIM_KIN_HOME_ERR_ARGUMENT = -840,
|
||||
SIM_KIN_HOME_ERR_CONFIGURATION = -841,
|
||||
SIM_KIN_HOME_ERR_UNHOMED = -842,
|
||||
SIM_KIN_HOME_ERR_LIMIT = -843,
|
||||
SIM_KIN_HOME_ERR_FORWARD = -844,
|
||||
SIM_KIN_HOME_ERR_NONFINITE = -845,
|
||||
SIM_KIN_HOME_ERR_SEQUENCE = -846,
|
||||
};
|
||||
|
||||
typedef struct sim_kinematics_home_limit_state {
|
||||
sim_posemath_pose pose;
|
||||
double joints[SIM_KIN_MAX_JOINTS];
|
||||
uint32_t joint_count;
|
||||
uint32_t homed;
|
||||
uint32_t home_sequence;
|
||||
uint32_t tick;
|
||||
int last_error;
|
||||
int limit_error;
|
||||
int kinematics_error;
|
||||
} sim_kinematics_home_limit_state;
|
||||
|
||||
typedef struct sim_kinematics_home_limit {
|
||||
sim_kinematics *kinematics;
|
||||
sim_kinematics_home_limit_state state;
|
||||
} sim_kinematics_home_limit;
|
||||
|
||||
int sim_kinematics_home_limit_init(sim_kinematics_home_limit *home_limit,
|
||||
sim_kinematics *kinematics,
|
||||
uint32_t joint_count);
|
||||
int sim_kinematics_home_limit_reset(sim_kinematics_home_limit *home_limit);
|
||||
int sim_kinematics_home_limit_home(sim_kinematics_home_limit *home_limit,
|
||||
uint32_t home_sequence);
|
||||
int sim_kinematics_home_limit_check(sim_kinematics_home_limit *home_limit,
|
||||
const double *joints,
|
||||
uint32_t joint_count);
|
||||
int sim_kinematics_home_limit_read(const sim_kinematics_home_limit *home_limit,
|
||||
sim_kinematics_home_limit_state *state);
|
||||
uint64_t sim_kinematics_home_limit_state_hash(const sim_kinematics_home_limit *home_limit);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
68
wasm-core/include/sim_kinematics_motion.h
Normal file
68
wasm-core/include/sim_kinematics_motion.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef LINUXCNC_WEB_WASM_SIM_KINEMATICS_MOTION_H
|
||||
#define LINUXCNC_WEB_WASM_SIM_KINEMATICS_MOTION_H
|
||||
|
||||
#include "sim_kinematics.h"
|
||||
#include "sim_posemath.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum sim_kinematics_motion_result {
|
||||
SIM_KIN_MOTION_OK = 0,
|
||||
SIM_KIN_MOTION_ERR_ARGUMENT = -800,
|
||||
SIM_KIN_MOTION_ERR_STATE = -801,
|
||||
SIM_KIN_MOTION_ERR_CAPACITY = -802,
|
||||
SIM_KIN_MOTION_ERR_PLANNER = -803,
|
||||
SIM_KIN_MOTION_ERR_INVERSE = -804,
|
||||
SIM_KIN_MOTION_ERR_NONFINITE = -805,
|
||||
SIM_KIN_MOTION_ERR_CONFIGURATION = -806,
|
||||
};
|
||||
|
||||
#define SIM_KIN_MOTION_QUEUE_CAPACITY 64u
|
||||
|
||||
typedef struct sim_kinematics_motion_sample {
|
||||
sim_posemath_pose pose;
|
||||
double joints[SIM_KIN_MAX_JOINTS];
|
||||
uint32_t joint_count;
|
||||
uint32_t tick;
|
||||
uint32_t planner_id;
|
||||
} sim_kinematics_motion_sample;
|
||||
|
||||
typedef struct sim_kinematics_motion {
|
||||
sim_tp planner;
|
||||
sim_kinematics *kinematics;
|
||||
sim_kinematics_motion_sample samples[SIM_KIN_MOTION_QUEUE_CAPACITY];
|
||||
uint32_t head;
|
||||
uint32_t count;
|
||||
uint32_t tick;
|
||||
uint32_t joint_count;
|
||||
uint32_t paused;
|
||||
int last_error;
|
||||
int planner_error;
|
||||
int kinematics_error;
|
||||
sim_posemath_pose last_pose;
|
||||
double last_joints[SIM_KIN_MAX_JOINTS];
|
||||
} sim_kinematics_motion;
|
||||
|
||||
int sim_kinematics_motion_init(sim_kinematics_motion *adapter,
|
||||
sim_kinematics *kinematics, double cycle_time,
|
||||
double velocity_limit, double acceleration_limit);
|
||||
int sim_kinematics_motion_reset(sim_kinematics_motion *adapter);
|
||||
int sim_kinematics_motion_enqueue(sim_kinematics_motion *adapter,
|
||||
const sim_posemath_segment *segment,
|
||||
uint32_t *id_out);
|
||||
int sim_kinematics_motion_pause(sim_kinematics_motion *adapter, uint32_t paused);
|
||||
int sim_kinematics_motion_step(sim_kinematics_motion *adapter);
|
||||
int sim_kinematics_motion_peek(const sim_kinematics_motion *adapter,
|
||||
sim_kinematics_motion_sample *sample);
|
||||
int sim_kinematics_motion_pop(sim_kinematics_motion *adapter,
|
||||
sim_kinematics_motion_sample *sample);
|
||||
uint32_t sim_kinematics_motion_depth(const sim_kinematics_motion *adapter);
|
||||
uint64_t sim_kinematics_motion_state_hash(const sim_kinematics_motion *adapter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -72,11 +72,21 @@ uint32_t sim_event_export(uint8_t *out, uint32_t capacity) {
|
||||
}
|
||||
|
||||
int32_t sim_event_import(const uint8_t *data, uint32_t len) {
|
||||
sim_event_state candidate;
|
||||
uint32_t index;
|
||||
if (data == NULL || len != sizeof(state)) return SIM_EVENT_ERR_ARGUMENT;
|
||||
memcpy(&state, data, sizeof(state));
|
||||
if (state.count > SIM_EVENT_CAPACITY) {
|
||||
sim_event_reset();
|
||||
return SIM_EVENT_ERR_ARGUMENT;
|
||||
memcpy(&candidate, data, sizeof(candidate));
|
||||
if (candidate.count > SIM_EVENT_CAPACITY) return SIM_EVENT_ERR_ARGUMENT;
|
||||
if (candidate.count == 0U && (candidate.last_seq != 0U || candidate.last_tick != 0U)) return SIM_EVENT_ERR_ARGUMENT;
|
||||
for (index = 0; index < candidate.count; ++index) {
|
||||
const sim_event_record *record = &candidate.records[index];
|
||||
size_t kind_len = 0U;
|
||||
if (record->seq != index + 1U) return SIM_EVENT_ERR_SEQUENCE;
|
||||
if (index > 0U && record->tick < candidate.records[index - 1U].tick) return SIM_EVENT_ERR_TICK;
|
||||
while (kind_len < SIM_EVENT_KIND_CAPACITY && record->kind[kind_len] != '\0') ++kind_len;
|
||||
if (kind_len == 0U || kind_len >= SIM_EVENT_KIND_CAPACITY) return SIM_EVENT_ERR_ARGUMENT;
|
||||
}
|
||||
if (candidate.count > 0U && (candidate.last_seq != candidate.records[candidate.count - 1U].seq || candidate.last_tick != candidate.records[candidate.count - 1U].tick)) return SIM_EVENT_ERR_ARGUMENT;
|
||||
memcpy(&state, &candidate, sizeof(state));
|
||||
return SIM_EVENT_OK;
|
||||
}
|
||||
|
||||
561
wasm-core/src/sim_io.c
Normal file
561
wasm-core/src/sim_io.c
Normal file
@@ -0,0 +1,561 @@
|
||||
#include "sim_io.h"
|
||||
|
||||
#include "sim_hal.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t io_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static double normalized(double value) {
|
||||
return isfinite(value) ? nearbyint(value * 1000000000.0) / 1000000000.0 : value;
|
||||
}
|
||||
|
||||
static int finite_pose(const sim_posemath_pose *pose) {
|
||||
const double *values;
|
||||
uint32_t index;
|
||||
if (pose == 0) return 0;
|
||||
values = &pose->tran.x;
|
||||
for (index = 0; index < 9; ++index) if (!isfinite(values[index])) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int valid_io(const sim_io *io) {
|
||||
return io != 0 && io->state.spindle_count > 0 &&
|
||||
io->state.spindle_count <= SIM_IO_MAX_SPINDLES;
|
||||
}
|
||||
|
||||
static int valid_spindle(const sim_io *io, uint32_t spindle) {
|
||||
return valid_io(io) && spindle < io->state.spindle_count &&
|
||||
io->state.spindles[spindle].registered;
|
||||
}
|
||||
|
||||
static void set_last(sim_io *io, int result) {
|
||||
if (io != 0) io->state.last_error = result;
|
||||
}
|
||||
|
||||
static int register_pin(const char *name, uint32_t type) {
|
||||
int result = sim_hal_register_pin(name, type);
|
||||
if (result == SIM_HAL_OK) return SIM_IO_OK;
|
||||
if (result == SIM_HAL_ERR_DUPLICATE) {
|
||||
sim_hal_value existing = {0};
|
||||
if (sim_hal_get_pin(name, &existing) == SIM_HAL_OK && existing.type == type) return SIM_IO_OK;
|
||||
}
|
||||
return SIM_IO_ERR_PIN;
|
||||
}
|
||||
|
||||
static int register_io_pins(sim_io *io) {
|
||||
static const struct { const char *name; uint32_t type; } fixed[] = {
|
||||
{"motion.probe-input", SIM_HAL_BIT},
|
||||
{"iocontrol.0.user-enable-out", SIM_HAL_BIT},
|
||||
{"iocontrol.0.emc-enable-in", SIM_HAL_BIT},
|
||||
{"iocontrol.0.user-request-enable", SIM_HAL_BIT},
|
||||
{"iocontrol.0.coolant-mist", SIM_HAL_BIT},
|
||||
{"iocontrol.0.coolant-flood", SIM_HAL_BIT},
|
||||
{"iocontrol.0.tool-prepare", SIM_HAL_BIT},
|
||||
{"iocontrol.0.tool-prep-number", SIM_HAL_S32},
|
||||
{"iocontrol.0.tool-prep-pocket", SIM_HAL_S32},
|
||||
{"iocontrol.0.tool-prep-index", SIM_HAL_S32},
|
||||
{"iocontrol.0.tool-number", SIM_HAL_S32},
|
||||
{"iocontrol.0.tool-prepared", SIM_HAL_BIT},
|
||||
{"iocontrol.0.tool-change", SIM_HAL_BIT},
|
||||
{"iocontrol.0.tool-changed", SIM_HAL_BIT},
|
||||
{"iocontrol.0.toolchanger-fault", SIM_HAL_BIT},
|
||||
{"iocontrol.0.toolchanger-fault-ack", SIM_HAL_BIT},
|
||||
{"iocontrol.0.toolchanger-reason", SIM_HAL_S32},
|
||||
{"iocontrol.0.toolchanger-faulted", SIM_HAL_BIT},
|
||||
{"iocontrol.0.toolchanger-clear-fault", SIM_HAL_BIT},
|
||||
{"iocontrol.0.emc-abort", SIM_HAL_BIT},
|
||||
{"iocontrol.0.emc-abort-ack", SIM_HAL_BIT},
|
||||
{"iocontrol.0.emc-reason", SIM_HAL_S32},
|
||||
{"iocontrol.0.start-change", SIM_HAL_BIT},
|
||||
{"iocontrol.0.start-change-ack", SIM_HAL_BIT},
|
||||
{"iocontrol.0.state", SIM_HAL_S32},
|
||||
};
|
||||
uint32_t index;
|
||||
char name[64];
|
||||
int result;
|
||||
result = sim_hal_register_thread("sim-io-servo", 1);
|
||||
if (result != SIM_HAL_OK && result != SIM_HAL_ERR_DUPLICATE) return SIM_IO_ERR_PIN;
|
||||
result = sim_hal_register_function("sim-io-servo", "sim-io");
|
||||
if (result != SIM_HAL_OK && result != SIM_HAL_ERR_DUPLICATE) return SIM_IO_ERR_PIN;
|
||||
for (index = 0; index < sizeof(fixed) / sizeof(fixed[0]); ++index) {
|
||||
result = register_pin(fixed[index].name, fixed[index].type);
|
||||
if (result != SIM_IO_OK) return result;
|
||||
}
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
static const struct { const char *suffix; uint32_t type; } spindle_pins[] = {
|
||||
{"on", SIM_HAL_BIT}, {"forward", SIM_HAL_BIT}, {"reverse", SIM_HAL_BIT},
|
||||
{"brake", SIM_HAL_BIT}, {"speed-out", SIM_HAL_FLOAT},
|
||||
{"speed-out-abs", SIM_HAL_FLOAT}, {"speed-out-rps", SIM_HAL_FLOAT},
|
||||
{"speed-out-rps-abs", SIM_HAL_FLOAT}, {"speed-cmd-rps", SIM_HAL_FLOAT},
|
||||
{"inhibit", SIM_HAL_BIT}, {"amp-fault-in", SIM_HAL_BIT},
|
||||
{"orient-angle", SIM_HAL_FLOAT}, {"orient-mode", SIM_HAL_S32},
|
||||
{"orient", SIM_HAL_BIT}, {"locked", SIM_HAL_BIT},
|
||||
{"is-oriented", SIM_HAL_BIT}, {"orient-fault", SIM_HAL_S32},
|
||||
{"revs", SIM_HAL_FLOAT}, {"speed-in", SIM_HAL_FLOAT},
|
||||
{"at-speed", SIM_HAL_BIT}, {"index-enable", SIM_HAL_BIT},
|
||||
};
|
||||
uint32_t pin;
|
||||
for (pin = 0; pin < sizeof(spindle_pins) / sizeof(spindle_pins[0]); ++pin) {
|
||||
int written = snprintf(name, sizeof(name), "spindle.%u.%s", index, spindle_pins[pin].suffix);
|
||||
if (written < 0 || (size_t)written >= sizeof(name)) return SIM_IO_ERR_PIN;
|
||||
result = register_pin(name, spindle_pins[pin].type);
|
||||
if (result != SIM_IO_OK) return result;
|
||||
}
|
||||
}
|
||||
io->pins_registered = 1;
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
static void set_bit(const char *name, uint32_t value) {
|
||||
(void)sim_hal_set_pin(name, (sim_hal_value){SIM_HAL_BIT, {.bit = value ? 1U : 0U}});
|
||||
}
|
||||
|
||||
static void set_s32(const char *name, int32_t value) {
|
||||
(void)sim_hal_set_pin(name, (sim_hal_value){SIM_HAL_S32, {.s32 = value}});
|
||||
}
|
||||
|
||||
static void set_float(const char *name, double value) {
|
||||
(void)sim_hal_set_pin(name, (sim_hal_value){SIM_HAL_FLOAT, {.real = (float)value}});
|
||||
}
|
||||
|
||||
static void mirror_pins(sim_io *io) {
|
||||
char name[64];
|
||||
uint32_t index;
|
||||
sim_io_state *state = &io->state;
|
||||
set_bit("motion.probe-input", state->probe.input);
|
||||
set_bit("iocontrol.0.user-enable-out", state->estop);
|
||||
set_bit("iocontrol.0.emc-enable-in", state->ready);
|
||||
set_bit("iocontrol.0.user-request-enable", 0);
|
||||
set_bit("iocontrol.0.coolant-mist", 0);
|
||||
set_bit("iocontrol.0.coolant-flood", 0);
|
||||
set_bit("iocontrol.0.tool-prepare", state->tool_prepare);
|
||||
set_s32("iocontrol.0.tool-prep-number", state->prepared_tool);
|
||||
set_s32("iocontrol.0.tool-prep-pocket", state->prepared_pocket);
|
||||
set_s32("iocontrol.0.tool-prep-index", state->prepared_pocket);
|
||||
set_s32("iocontrol.0.tool-number", state->current_tool);
|
||||
set_bit("iocontrol.0.tool-prepared", state->tool_prepared);
|
||||
set_bit("iocontrol.0.tool-change", state->tool_change);
|
||||
set_bit("iocontrol.0.tool-changed", state->tool_changed);
|
||||
set_bit("iocontrol.0.toolchanger-fault", state->toolchanger_fault);
|
||||
set_bit("iocontrol.0.toolchanger-fault-ack", state->toolchanger_fault ? 0 : 1);
|
||||
set_s32("iocontrol.0.toolchanger-reason", state->toolchanger_reason);
|
||||
set_bit("iocontrol.0.toolchanger-faulted", state->fault);
|
||||
set_bit("iocontrol.0.toolchanger-clear-fault", 0);
|
||||
set_bit("iocontrol.0.emc-abort", state->tool_state == SIM_IO_TOOL_ABORTED);
|
||||
set_bit("iocontrol.0.emc-abort-ack", state->tool_state != SIM_IO_TOOL_ABORTED);
|
||||
set_s32("iocontrol.0.emc-reason", state->fault_reason);
|
||||
set_bit("iocontrol.0.start-change", state->tool_change);
|
||||
set_bit("iocontrol.0.start-change-ack", state->tool_state != SIM_IO_TOOL_CHANGING);
|
||||
set_s32("iocontrol.0.state", (int32_t)state->tool_state);
|
||||
for (index = 0; index < state->spindle_count; ++index) {
|
||||
sim_io_spindle_state *spindle = &state->spindles[index];
|
||||
snprintf(name, sizeof(name), "spindle.%u.on", index); set_bit(name, spindle->enabled && spindle->speed_rpm != 0.0);
|
||||
snprintf(name, sizeof(name), "spindle.%u.forward", index); set_bit(name, spindle->forward);
|
||||
snprintf(name, sizeof(name), "spindle.%u.reverse", index); set_bit(name, spindle->reverse);
|
||||
snprintf(name, sizeof(name), "spindle.%u.brake", index); set_bit(name, spindle->brake);
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-out", index); set_float(name, spindle->speed_rpm);
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-out-abs", index); set_float(name, fabs(spindle->speed_rpm));
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-out-rps", index); set_float(name, spindle->speed_rpm / 60.0);
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-out-rps-abs", index); set_float(name, fabs(spindle->speed_rpm) / 60.0);
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-cmd-rps", index); set_float(name, spindle->command_rpm / 60.0);
|
||||
snprintf(name, sizeof(name), "spindle.%u.inhibit", index); set_bit(name, spindle->inhibit);
|
||||
snprintf(name, sizeof(name), "spindle.%u.amp-fault-in", index); set_bit(name, spindle->amp_fault);
|
||||
snprintf(name, sizeof(name), "spindle.%u.orient-angle", index); set_float(name, spindle->orient_angle);
|
||||
snprintf(name, sizeof(name), "spindle.%u.orient-mode", index); set_s32(name, spindle->orient_mode);
|
||||
snprintf(name, sizeof(name), "spindle.%u.orient", index); set_bit(name, spindle->orient);
|
||||
snprintf(name, sizeof(name), "spindle.%u.locked", index); set_bit(name, spindle->locked);
|
||||
snprintf(name, sizeof(name), "spindle.%u.is-oriented", index); set_bit(name, spindle->is_oriented);
|
||||
snprintf(name, sizeof(name), "spindle.%u.orient-fault", index); set_s32(name, spindle->orient_fault);
|
||||
snprintf(name, sizeof(name), "spindle.%u.revs", index); set_float(name, spindle->revs);
|
||||
snprintf(name, sizeof(name), "spindle.%u.speed-in", index); set_float(name, spindle->speed_rpm);
|
||||
snprintf(name, sizeof(name), "spindle.%u.at-speed", index); set_bit(name, spindle->at_speed);
|
||||
snprintf(name, sizeof(name), "spindle.%u.index-enable", index); set_bit(name, spindle->index_enable);
|
||||
}
|
||||
}
|
||||
|
||||
static void state_defaults(sim_io *io) {
|
||||
uint32_t index;
|
||||
sim_io_state old = io->state;
|
||||
memset(&io->state, 0, sizeof(io->state));
|
||||
io->state.spindle_count = old.spindle_count;
|
||||
io->state.tool_capacity = old.tool_capacity;
|
||||
io->state.ready = 1;
|
||||
io->state.current_tool = 0;
|
||||
io->state.prepared_tool = -1;
|
||||
io->state.prepared_pocket = -1;
|
||||
io->state.toolchanger_reason = 0;
|
||||
io->state.tool_state = SIM_IO_TOOL_IDLE;
|
||||
io->state.last_error = SIM_IO_OK;
|
||||
io->state.probe.state = SIM_IO_PROBE_IDLE;
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
io->state.spindles[index].registered = 1;
|
||||
io->state.spindles[index].max_rpm = 100000.0;
|
||||
io->state.spindles[index].accel_rpm_s = old.spindles[index].accel_rpm_s;
|
||||
io->state.spindles[index].tolerance_rpm = old.spindles[index].tolerance_rpm;
|
||||
io->state.spindles[index].at_speed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int sim_io_init(sim_io *io, uint32_t spindle_count, uint32_t tool_capacity,
|
||||
double cycle_time, double spindle_accel_rpm_s,
|
||||
double spindle_tolerance_rpm) {
|
||||
uint32_t index;
|
||||
int result;
|
||||
if (io == 0) return SIM_IO_ERR_ARGUMENT;
|
||||
if (spindle_count == 0 || spindle_count > SIM_IO_MAX_SPINDLES ||
|
||||
tool_capacity == 0 || tool_capacity > SIM_IO_MAX_TOOLS ||
|
||||
!isfinite(cycle_time) || cycle_time <= 0.0 || cycle_time > 60.0 ||
|
||||
!isfinite(spindle_accel_rpm_s) || spindle_accel_rpm_s <= 0.0 ||
|
||||
!isfinite(spindle_tolerance_rpm) || spindle_tolerance_rpm < 0.0)
|
||||
return SIM_IO_ERR_RANGE;
|
||||
memset(io, 0, sizeof(*io));
|
||||
io->cycle_time = cycle_time;
|
||||
io->state.spindle_count = spindle_count;
|
||||
io->state.tool_capacity = tool_capacity;
|
||||
for (index = 0; index < spindle_count; ++index) {
|
||||
io->state.spindles[index].accel_rpm_s = spindle_accel_rpm_s;
|
||||
io->state.spindles[index].tolerance_rpm = spindle_tolerance_rpm;
|
||||
}
|
||||
result = register_io_pins(io);
|
||||
if (result != SIM_IO_OK) return result;
|
||||
state_defaults(io);
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_reset(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
state_defaults(io);
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_register_pins(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
return register_io_pins(io);
|
||||
}
|
||||
|
||||
int sim_io_spindle_command(sim_io *io, uint32_t spindle, double rpm, uint32_t enabled) {
|
||||
sim_io_spindle_state *state;
|
||||
if (!valid_spindle(io, spindle) || enabled > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
if (!isfinite(rpm)) { set_last(io, SIM_IO_ERR_NONFINITE); return SIM_IO_ERR_NONFINITE; }
|
||||
if (fabs(rpm) > io->state.spindles[spindle].max_rpm) { set_last(io, SIM_IO_ERR_LIMIT); return SIM_IO_ERR_LIMIT; }
|
||||
if (io->state.estop) { set_last(io, SIM_IO_ERR_ESTOP); return SIM_IO_ERR_ESTOP; }
|
||||
if (!io->state.ready) { set_last(io, SIM_IO_ERR_NOT_READY); return SIM_IO_ERR_NOT_READY; }
|
||||
state = &io->state.spindles[spindle];
|
||||
state->command_rpm = enabled && !state->inhibit && !state->amp_fault ? rpm : 0.0;
|
||||
state->enabled = enabled ? 1U : 0U;
|
||||
set_last(io, SIM_IO_OK);
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_set_inhibit(sim_io *io, uint32_t spindle, uint32_t inhibit) {
|
||||
if (!valid_spindle(io, spindle) || inhibit > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.spindles[spindle].inhibit = inhibit;
|
||||
if (inhibit) io->state.spindles[spindle].command_rpm = 0.0;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_set_fault(sim_io *io, uint32_t spindle, uint32_t fault) {
|
||||
if (!valid_spindle(io, spindle) || fault > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.spindles[spindle].amp_fault = fault;
|
||||
if (fault) {
|
||||
io->state.spindles[spindle].command_rpm = 0.0;
|
||||
io->state.spindles[spindle].enabled = 0;
|
||||
}
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_set_index_enable(sim_io *io, uint32_t spindle, uint32_t enable) {
|
||||
if (!valid_spindle(io, spindle) || enable > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.spindles[spindle].index_enable = enable;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_set_feedback(sim_io *io, uint32_t spindle, double rpm) {
|
||||
if (!valid_spindle(io, spindle)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (!isfinite(rpm)) return SIM_IO_ERR_NONFINITE;
|
||||
io->state.spindles[spindle].speed_rpm = rpm;
|
||||
io->state.spindles[spindle].at_speed = fabs(rpm - io->state.spindles[spindle].command_rpm) <= io->state.spindles[spindle].tolerance_rpm;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_orient(sim_io *io, uint32_t spindle, double angle, int32_t mode) {
|
||||
sim_io_spindle_state *state;
|
||||
if (!valid_spindle(io, spindle)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (!isfinite(angle) || mode < 0 || mode > 2) return SIM_IO_ERR_RANGE;
|
||||
if (io->state.estop) return SIM_IO_ERR_ESTOP;
|
||||
state = &io->state.spindles[spindle];
|
||||
if (fabs(state->speed_rpm) > state->tolerance_rpm || state->command_rpm != 0.0) return SIM_IO_ERR_BUSY;
|
||||
state->orient = 1; state->locked = 0; state->is_oriented = 0;
|
||||
state->orient_angle = angle; state->orient_mode = mode; state->orient_fault = 0;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_spindle_set_oriented(sim_io *io, uint32_t spindle, uint32_t oriented, int32_t fault) {
|
||||
sim_io_spindle_state *state;
|
||||
if (!valid_spindle(io, spindle) || oriented > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
state = &io->state.spindles[spindle];
|
||||
if (!state->orient) return SIM_IO_ERR_STATE;
|
||||
state->is_oriented = oriented;
|
||||
state->orient_fault = fault;
|
||||
if (fault) { state->orient = 0; state->locked = 0; }
|
||||
else if (oriented) { state->orient = 0; state->locked = 1; }
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
static int spindle_stopped(const sim_io *io) {
|
||||
uint32_t index;
|
||||
for (index = 0; index < io->state.spindle_count; ++index)
|
||||
if (fabs(io->state.spindles[index].speed_rpm) > io->state.spindles[index].tolerance_rpm || io->state.spindles[index].command_rpm != 0.0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sim_io_tool_prepare(sim_io *io, int32_t tool, int32_t pocket) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (tool < 0 || pocket <= 0 || (uint32_t)pocket >= io->state.tool_capacity) return SIM_IO_ERR_RANGE;
|
||||
if (io->state.estop) return SIM_IO_ERR_ESTOP;
|
||||
if (!io->state.ready) return SIM_IO_ERR_NOT_READY;
|
||||
if (io->state.fault) return SIM_IO_ERR_FAULT;
|
||||
io->state.prepared_tool = tool; io->state.prepared_pocket = pocket;
|
||||
io->state.tool_prepare = 1; io->state.tool_prepared = 0; io->state.tool_changed = 0;
|
||||
io->state.tool_state = SIM_IO_TOOL_PREPARING;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_set_prepared(sim_io *io, uint32_t prepared) {
|
||||
if (!valid_io(io) || prepared > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.tool_state != SIM_IO_TOOL_PREPARING) return SIM_IO_ERR_STATE;
|
||||
if (!prepared) return SIM_IO_OK;
|
||||
io->state.tool_prepare = 0; io->state.tool_prepared = 1; io->state.tool_state = SIM_IO_TOOL_PREPARED;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_change(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.estop) return SIM_IO_ERR_ESTOP;
|
||||
if (!io->state.ready) return SIM_IO_ERR_NOT_READY;
|
||||
if (io->state.fault) return SIM_IO_ERR_FAULT;
|
||||
if (io->state.tool_state != SIM_IO_TOOL_PREPARED || !io->state.tool_prepared) return SIM_IO_ERR_STATE;
|
||||
if (!spindle_stopped(io)) return SIM_IO_ERR_BUSY;
|
||||
io->state.tool_change = 1; io->state.tool_changed = 0; io->state.tool_state = SIM_IO_TOOL_CHANGING;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_set_changed(sim_io *io, uint32_t changed) {
|
||||
if (!valid_io(io) || changed > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.tool_state != SIM_IO_TOOL_CHANGING) return SIM_IO_ERR_STATE;
|
||||
if (!changed) return SIM_IO_OK;
|
||||
io->state.tool_change = 0; io->state.tool_changed = 1; io->state.tool_prepared = 0;
|
||||
io->state.current_tool = io->state.prepared_tool; io->state.tool_state = SIM_IO_TOOL_READY;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_abort(sim_io *io, int32_t reason) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.tool_prepare = 0; io->state.tool_change = 0; io->state.tool_prepared = 0;
|
||||
io->state.tool_changed = 0; io->state.toolchanger_reason = reason;
|
||||
io->state.tool_state = SIM_IO_TOOL_ABORTED;
|
||||
io->state.last_error = SIM_IO_ERR_RECOVERY;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_set_fault(sim_io *io, uint32_t fault, int32_t reason) {
|
||||
if (!valid_io(io) || fault > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.toolchanger_fault = fault; io->state.toolchanger_reason = reason;
|
||||
io->state.fault = fault; io->state.fault_reason = reason;
|
||||
if (fault) { io->state.tool_state = SIM_IO_TOOL_FAULT; io->state.tool_prepare = 0; io->state.tool_change = 0; }
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_tool_clear_fault(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.toolchanger_fault) return SIM_IO_ERR_FAULT;
|
||||
io->state.fault = 0; io->state.fault_reason = 0;
|
||||
if (io->state.tool_state == SIM_IO_TOOL_FAULT || io->state.tool_state == SIM_IO_TOOL_ABORTED) io->state.tool_state = SIM_IO_TOOL_IDLE;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_probe_arm(sim_io *io, uint32_t probe_type) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.estop) return SIM_IO_ERR_ESTOP;
|
||||
if (!io->state.ready) return SIM_IO_ERR_NOT_READY;
|
||||
io->state.probe.probe_type = probe_type; io->state.probe.state = SIM_IO_PROBE_ARMED;
|
||||
io->state.probe.tripped = 0; io->state.probe.tick = io->state.tick;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_probe_update(sim_io *io, const sim_posemath_pose *pose, uint32_t input) {
|
||||
uint32_t when_clears;
|
||||
if (!valid_io(io) || pose == 0) return SIM_IO_ERR_ARGUMENT;
|
||||
if (!finite_pose(pose)) return SIM_IO_ERR_NONFINITE;
|
||||
if (input > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.probe.input = input;
|
||||
when_clears = (io->state.probe.probe_type & 2U) != 0;
|
||||
if (io->state.probe.state == SIM_IO_PROBE_ARMED && ((input != 0U) ^ when_clears)) {
|
||||
io->state.probe.position = *pose; io->state.probe.tripped = 1;
|
||||
io->state.probe.state = SIM_IO_PROBE_TRIPPED; io->state.probe.tick = io->state.tick;
|
||||
}
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_probe_disarm(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.probe.state == SIM_IO_PROBE_ARMED) io->state.probe.state = SIM_IO_PROBE_IDLE;
|
||||
io->state.probe.probe_type = 0; io->state.probe.tripped = 0;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_set_estop(sim_io *io, uint32_t estop) {
|
||||
uint32_t index;
|
||||
if (!valid_io(io) || estop > 1U) return SIM_IO_ERR_ARGUMENT;
|
||||
io->state.estop = estop;
|
||||
if (estop) {
|
||||
io->state.ready = 0; io->state.tool_prepare = 0; io->state.tool_change = 0;
|
||||
io->state.tool_prepared = 0; io->state.tool_changed = 0;
|
||||
io->state.tool_state = SIM_IO_TOOL_ABORTED;
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
io->state.spindles[index].command_rpm = 0.0; io->state.spindles[index].enabled = 0;
|
||||
io->state.spindles[index].brake = 1;
|
||||
}
|
||||
io->state.last_error = SIM_IO_ERR_ESTOP;
|
||||
}
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_recover(sim_io *io) {
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (io->state.estop) return SIM_IO_ERR_ESTOP;
|
||||
if (io->state.fault || io->state.toolchanger_fault) return SIM_IO_ERR_FAULT;
|
||||
if (!spindle_stopped(io)) return SIM_IO_ERR_BUSY;
|
||||
io->state.ready = 1; io->state.recovery_epoch += 1; io->state.tool_state = SIM_IO_TOOL_IDLE;
|
||||
io->state.last_error = SIM_IO_OK;
|
||||
mirror_pins(io);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_step(sim_io *io, uint32_t ticks) {
|
||||
uint32_t tick, index;
|
||||
if (!valid_io(io)) return SIM_IO_ERR_ARGUMENT;
|
||||
if (ticks == 0) return SIM_IO_OK;
|
||||
if (ticks > 1000000U || UINT32_MAX - io->state.tick < ticks) return SIM_IO_ERR_RANGE;
|
||||
if (sim_hal_step(ticks) != SIM_HAL_OK) return SIM_IO_ERR_PIN;
|
||||
for (tick = 0; tick < ticks; ++tick) {
|
||||
io->state.tick += 1;
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
sim_io_spindle_state *spindle = &io->state.spindles[index];
|
||||
double target = spindle->command_rpm;
|
||||
double delta = spindle->accel_rpm_s * io->cycle_time;
|
||||
if (spindle->amp_fault || spindle->inhibit || !io->state.ready || io->state.estop) target = 0.0;
|
||||
if (spindle->speed_rpm < target) spindle->speed_rpm = fmin(target, spindle->speed_rpm + delta);
|
||||
else if (spindle->speed_rpm > target) spindle->speed_rpm = fmax(target, spindle->speed_rpm - delta);
|
||||
spindle->forward = spindle->speed_rpm > spindle->tolerance_rpm;
|
||||
spindle->reverse = spindle->speed_rpm < -spindle->tolerance_rpm;
|
||||
spindle->brake = fabs(target - spindle->speed_rpm) > spindle->tolerance_rpm && target == 0.0;
|
||||
spindle->at_speed = fabs(spindle->speed_rpm - target) <= spindle->tolerance_rpm;
|
||||
spindle->revs += spindle->speed_rpm * io->cycle_time / 60.0;
|
||||
spindle->tick = io->state.tick;
|
||||
if (spindle->index_enable && floor(spindle->revs) != floor(spindle->revs - spindle->speed_rpm * io->cycle_time / 60.0)) {
|
||||
spindle->index_enable = 0; spindle->revs = spindle->revs - floor(spindle->revs);
|
||||
}
|
||||
if (spindle->orient && spindle->at_speed && spindle->speed_rpm == 0.0) {
|
||||
spindle->orient = 0; spindle->locked = 1; spindle->is_oriented = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
mirror_pins(io);
|
||||
set_last(io, SIM_IO_OK);
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
int sim_io_read_state(const sim_io *io, sim_io_state *state) {
|
||||
if (!valid_io(io) || state == 0) return SIM_IO_ERR_ARGUMENT;
|
||||
*state = io->state;
|
||||
return SIM_IO_OK;
|
||||
}
|
||||
|
||||
uint64_t sim_io_state_hash(const sim_io *io) {
|
||||
unsigned char bytes[8192];
|
||||
size_t offset = 0;
|
||||
uint32_t index;
|
||||
if (!valid_io(io)) return 0;
|
||||
#define APPEND(value) do { memcpy(bytes + offset, &(value), sizeof(value)); offset += sizeof(value); } while (0)
|
||||
APPEND(io->state.ready); APPEND(io->state.estop); APPEND(io->state.fault); APPEND(io->state.fault_reason);
|
||||
APPEND(io->state.tick); APPEND(io->state.recovery_epoch); APPEND(io->state.spindle_count); APPEND(io->state.tool_capacity);
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
sim_io_spindle_state spindle = io->state.spindles[index];
|
||||
spindle.command_rpm = normalized(spindle.command_rpm); spindle.speed_rpm = normalized(spindle.speed_rpm);
|
||||
spindle.max_rpm = normalized(spindle.max_rpm); spindle.accel_rpm_s = normalized(spindle.accel_rpm_s);
|
||||
spindle.tolerance_rpm = normalized(spindle.tolerance_rpm); spindle.orient_angle = normalized(spindle.orient_angle);
|
||||
spindle.revs = normalized(spindle.revs); APPEND(spindle);
|
||||
}
|
||||
APPEND(io->state.tool_state); APPEND(io->state.current_tool); APPEND(io->state.prepared_tool); APPEND(io->state.prepared_pocket);
|
||||
APPEND(io->state.tool_prepare); APPEND(io->state.tool_prepared); APPEND(io->state.tool_change); APPEND(io->state.tool_changed);
|
||||
APPEND(io->state.toolchanger_fault); APPEND(io->state.toolchanger_reason); APPEND(io->state.probe.input);
|
||||
APPEND(io->state.probe.probe_type); APPEND(io->state.probe.state); APPEND(io->state.probe.tripped); APPEND(io->state.probe.tick);
|
||||
{ sim_posemath_pose pose = io->state.probe.position; double *values = &pose.tran.x; for (index = 0; index < 9; ++index) values[index] = normalized(values[index]); APPEND(pose); }
|
||||
APPEND(io->state.last_error);
|
||||
#undef APPEND
|
||||
return io_fnv1a64(bytes, offset);
|
||||
}
|
||||
|
||||
uint32_t sim_io_canonical(const sim_io *io, char *output, uint32_t capacity) {
|
||||
char discard[1];
|
||||
char *destination = output == 0 ? discard : output;
|
||||
uint32_t destination_capacity = output == 0 ? 1 : capacity;
|
||||
int written;
|
||||
uint32_t index;
|
||||
if (!valid_io(io)) return 0;
|
||||
written = snprintf(destination, destination_capacity,
|
||||
"{\"estop\":%u,\"fault\":%u,\"fault_reason\":%d,\"last_error\":%d,\"probe\":{\"input\":%u,\"position\":[%.9g,%.9g,%.9g],\"state\":%u,\"tick\":%u,\"tripped\":%u},\"ready\":%u,\"recovery_epoch\":%u,\"spindles\":[",
|
||||
io->state.estop, io->state.fault, io->state.fault_reason, io->state.last_error,
|
||||
io->state.probe.input, io->state.probe.position.tran.x, io->state.probe.position.tran.y,
|
||||
io->state.probe.position.tran.z, io->state.probe.state, io->state.probe.tick,
|
||||
io->state.probe.tripped, io->state.ready, io->state.recovery_epoch);
|
||||
if (written < 0) return 0;
|
||||
for (index = 0; index < io->state.spindle_count; ++index) {
|
||||
const sim_io_spindle_state *spindle = &io->state.spindles[index];
|
||||
int part = snprintf(destination + (output && (uint32_t)written < capacity ? written : 0),
|
||||
output && (uint32_t)written < capacity ? capacity - (uint32_t)written : 0,
|
||||
"%s{\"at_speed\":%u,\"brake\":%u,\"command_rpm\":%.9g,\"forward\":%u,\"reverse\":%u,\"speed_rpm\":%.9g,\"tick\":%u}",
|
||||
index ? "," : "", spindle->at_speed, spindle->brake, spindle->command_rpm,
|
||||
spindle->forward, spindle->reverse, spindle->speed_rpm, spindle->tick);
|
||||
if (part < 0) return 0; written += part;
|
||||
}
|
||||
{ int part = snprintf(destination + (output && (uint32_t)written < capacity ? written : 0),
|
||||
output && (uint32_t)written < capacity ? capacity - (uint32_t)written : 0,
|
||||
"],\"tick\":%u,\"tool\":{\"changed\":%u,\"current\":%d,\"prepared\":%u,\"prepared_pocket\":%d,\"prepared_tool\":%d,\"prepare\":%u,\"state\":%u}}",
|
||||
io->state.tick, io->state.tool_changed, io->state.current_tool, io->state.tool_prepared,
|
||||
io->state.prepared_pocket, io->state.prepared_tool, io->state.tool_prepare, io->state.tool_state);
|
||||
if (part < 0) return 0; written += part; }
|
||||
return (uint32_t)written + 1;
|
||||
}
|
||||
@@ -19,6 +19,14 @@ static double hash_normalize(double value) {
|
||||
return isfinite(value) ? nearbyint(value * 1000000000.0) / 1000000000.0 : value;
|
||||
}
|
||||
|
||||
/* The five-leg Newton solve can differ by a few ulps between native and
|
||||
* wasm32 math libraries. Keep the public pose/info boundary deterministic
|
||||
* without changing the convergence test itself. */
|
||||
static double pentapod_canonical(double value) {
|
||||
double rounded = nearbyint(value * 1000000.0) / 1000000.0;
|
||||
return fabs(rounded) < 0.0000005 ? 0.0 : rounded;
|
||||
}
|
||||
|
||||
static int finite_values(const double *values, size_t count) {
|
||||
size_t index;
|
||||
if (values == 0) return 0;
|
||||
@@ -168,6 +176,7 @@ static int valid_config(const sim_kinematics_config *config) {
|
||||
}
|
||||
if (config->type == SIM_KIN_PENTAPOD) {
|
||||
if (kinematics_count != SIM_KIN_PENTA_STRUTS || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B')) return SIM_KIN_ERR_CONFIGURATION;
|
||||
if (!isfinite(config->penta_tool_offset)) return SIM_KIN_ERR_NONFINITE;
|
||||
if (config->penta_iteration_limit > 100000u || config->penta_convergence_tolerance < 0.0 || !isfinite(config->penta_convergence_tolerance) || config->penta_max_error < 0.0 || !isfinite(config->penta_max_error) || !finite_values(config->penta_initial_pose, 5)) return SIM_KIN_ERR_CONFIGURATION;
|
||||
for (index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) if (!finite_values(config->penta_base[index], 3) || !isfinite(config->penta_effector_radius[index]) || !isfinite(config->penta_effector_z[index]) || ((config->penta_effector_radius[index] < 0.0) || (config->penta_effector_radius[index] == 0.0 && config->penta_effector_z[index] != 0.0))) return SIM_KIN_ERR_CONFIGURATION;
|
||||
}
|
||||
@@ -269,7 +278,10 @@ int sim_kinematics_init(sim_kinematics *kinematics, const sim_kinematics_config
|
||||
static const double effector_z[SIM_KIN_PENTA_STRUTS] = {87.0, 114.5, 140.5, 232.5, 258.5};
|
||||
int all_zero = 1;
|
||||
size_t index;
|
||||
for (index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) if (kinematics->config.penta_base[index][0] != 0.0 || kinematics->config.penta_base[index][1] != 0.0 || kinematics->config.penta_base[index][2] != 0.0) all_zero = 0;
|
||||
for (index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) {
|
||||
if (kinematics->config.penta_base[index][0] != 0.0 || kinematics->config.penta_base[index][1] != 0.0 || kinematics->config.penta_base[index][2] != 0.0 ||
|
||||
kinematics->config.penta_effector_radius[index] != 0.0 || kinematics->config.penta_effector_z[index] != 0.0) all_zero = 0;
|
||||
}
|
||||
if (all_zero) { memcpy(kinematics->config.penta_base, base, sizeof(base)); memcpy(kinematics->config.penta_effector_radius, radius, sizeof(radius)); memcpy(kinematics->config.penta_effector_z, effector_z, sizeof(effector_z)); }
|
||||
if (kinematics->config.penta_iteration_limit == 0) kinematics->config.penta_iteration_limit = 120;
|
||||
if (kinematics->config.penta_convergence_tolerance == 0.0) kinematics->config.penta_convergence_tolerance = 1e-9;
|
||||
@@ -808,7 +820,10 @@ static int pentapod_lengths(const sim_kinematics *k, const double q[5], double l
|
||||
PmRotationMatrix rotation, inverse;
|
||||
if (pmRpyMatConvert(&rpy, &rotation) != 0 || pmMatInv(&rotation, &inverse) != 0) return SIM_KIN_ERR_SINGULAR;
|
||||
for (size_t index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) {
|
||||
PmCartesian base = {k->config.penta_base[index][0] - q[0], k->config.penta_base[index][1] - q[1], k->config.penta_base[index][2] - q[2]};
|
||||
/* pentakins_read_hal_pins applies tool-offset to both ends of each
|
||||
* strut; preserve that world-frame translation before rotating into
|
||||
* the effector frame. */
|
||||
PmCartesian base = {k->config.penta_base[index][0] - q[0], k->config.penta_base[index][1] - q[1], k->config.penta_base[index][2] + k->config.penta_tool_offset - q[2]};
|
||||
PmCartesian local;
|
||||
double radial;
|
||||
pmMatCartMult(&inverse, &base, &local);
|
||||
@@ -833,6 +848,14 @@ static int pentapod_forward(const sim_kinematics *k, const double *joints, uint6
|
||||
double lengths[SIM_KIN_PENTA_STRUTS];
|
||||
double residual = 0.0;
|
||||
uint32_t iteration;
|
||||
for (size_t index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) {
|
||||
/* LinuxCNC's pentakins rejects non-positive strut lengths before
|
||||
* entering Newton iteration. */
|
||||
if (joints[index] <= 0.0) {
|
||||
if (info != 0) { info->iterations = 0; info->residual = 0.0; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; }
|
||||
return SIM_KIN_ERR_RANGE;
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < 5; ++index) q[index] = k->config.penta_initial_pose[index];
|
||||
if (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) q[2] = -q[2];
|
||||
for (iteration = 1; iteration <= k->config.penta_iteration_limit; ++iteration) {
|
||||
@@ -853,11 +876,14 @@ static int pentapod_forward(const sim_kinematics *k, const double *joints, uint6
|
||||
}
|
||||
}
|
||||
if (residual <= k->config.penta_convergence_tolerance) {
|
||||
pose->tran.x = q[0]; pose->tran.y = q[1]; pose->tran.z = q[2]; pose->a = q[3] * RAD_TO_DEG; pose->b = q[4] * RAD_TO_DEG; pose->c = 0.0; pose->u = pose->v = pose->w = 0.0;
|
||||
if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; }
|
||||
pose->tran.x = pentapod_canonical(q[0]); pose->tran.y = pentapod_canonical(q[1]); pose->tran.z = pentapod_canonical(q[2]); pose->a = pentapod_canonical(q[3] * RAD_TO_DEG); pose->b = pentapod_canonical(q[4] * RAD_TO_DEG); pose->c = 0.0; pose->u = pose->v = pose->w = 0.0;
|
||||
if (info != 0) { info->iterations = iteration; info->residual = nearbyint(residual * 1000000000.0) / 1000000000.0; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; }
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
if (residual > k->config.penta_max_error) return SIM_KIN_ERR_RANGE;
|
||||
if (residual > k->config.penta_max_error) {
|
||||
if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; }
|
||||
return SIM_KIN_ERR_RANGE;
|
||||
}
|
||||
if (solve_linear5(jacobian, rhs, delta) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR;
|
||||
for (int column = 0; column < 5; ++column) q[column] -= delta[column];
|
||||
}
|
||||
|
||||
109
wasm-core/src/sim_kinematics_feedback.c
Normal file
109
wasm-core/src/sim_kinematics_feedback.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "sim_kinematics_feedback.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t feedback_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void state_zero(sim_kinematics_feedback_state *state) {
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->last_error = SIM_KIN_FEEDBACK_OK;
|
||||
state->kinematics_error = SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_feedback_init(sim_kinematics_feedback *feedback,
|
||||
sim_kinematics *kinematics,
|
||||
uint32_t joint_count) {
|
||||
uint32_t configured_count;
|
||||
if (feedback == 0 || kinematics == 0) return SIM_KIN_FEEDBACK_ERR_ARGUMENT;
|
||||
if (!kinematics->configured) return SIM_KIN_FEEDBACK_ERR_CONFIGURATION;
|
||||
configured_count = kinematics->config.kinematics_joint_count == 0 ?
|
||||
kinematics->config.joint_count : kinematics->config.kinematics_joint_count;
|
||||
if (joint_count == 0 || joint_count != configured_count || joint_count > SIM_KIN_MAX_JOINTS)
|
||||
return SIM_KIN_FEEDBACK_ERR_CONFIGURATION;
|
||||
memset(feedback, 0, sizeof(*feedback));
|
||||
feedback->kinematics = kinematics;
|
||||
feedback->state.joint_count = joint_count;
|
||||
state_zero(&feedback->state);
|
||||
feedback->state.joint_count = joint_count;
|
||||
return SIM_KIN_FEEDBACK_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_feedback_reset(sim_kinematics_feedback *feedback) {
|
||||
uint32_t joint_count;
|
||||
if (feedback == 0 || feedback->kinematics == 0) return SIM_KIN_FEEDBACK_ERR_ARGUMENT;
|
||||
joint_count = feedback->state.joint_count;
|
||||
state_zero(&feedback->state);
|
||||
feedback->state.joint_count = joint_count;
|
||||
return SIM_KIN_FEEDBACK_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_feedback_update(sim_kinematics_feedback *feedback,
|
||||
const double *joints, uint32_t joint_count,
|
||||
uint64_t inverse_flags,
|
||||
sim_kinematics_result_info *info) {
|
||||
sim_posemath_pose pose;
|
||||
sim_kinematics_result_info local_info;
|
||||
uint32_t index;
|
||||
int result;
|
||||
if (feedback == 0 || feedback->kinematics == 0 || joints == 0)
|
||||
return SIM_KIN_FEEDBACK_ERR_ARGUMENT;
|
||||
if (joint_count != feedback->state.joint_count) return SIM_KIN_FEEDBACK_ERR_CONFIGURATION;
|
||||
for (index = 0; index < joint_count; ++index)
|
||||
if (!isfinite(joints[index])) {
|
||||
feedback->state.last_error = SIM_KIN_FEEDBACK_ERR_NONFINITE;
|
||||
feedback->state.kinematics_error = SIM_KIN_ERR_NONFINITE;
|
||||
return feedback->state.last_error;
|
||||
}
|
||||
memset(&pose, 0, sizeof(pose));
|
||||
result = sim_kinematics_forward_ex(feedback->kinematics, joints, joint_count,
|
||||
inverse_flags, &pose, &local_info);
|
||||
if (result != SIM_KIN_OK) {
|
||||
feedback->state.last_error = SIM_KIN_FEEDBACK_ERR_FORWARD;
|
||||
feedback->state.kinematics_error = result;
|
||||
if (info != 0) *info = local_info;
|
||||
return feedback->state.last_error;
|
||||
}
|
||||
feedback->state.pose = pose;
|
||||
memcpy(feedback->state.joints, joints, sizeof(double) * joint_count);
|
||||
feedback->state.forward_flags = local_info.forward_flags;
|
||||
feedback->state.inverse_flags = local_info.inverse_flags;
|
||||
feedback->state.tick += 1;
|
||||
feedback->state.valid = 1;
|
||||
feedback->state.last_error = SIM_KIN_FEEDBACK_OK;
|
||||
feedback->state.kinematics_error = SIM_KIN_OK;
|
||||
if (info != 0) *info = local_info;
|
||||
return SIM_KIN_FEEDBACK_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_feedback_read(const sim_kinematics_feedback *feedback,
|
||||
sim_kinematics_feedback_state *state) {
|
||||
if (feedback == 0 || state == 0) return SIM_KIN_FEEDBACK_ERR_ARGUMENT;
|
||||
if (!feedback->state.valid) return SIM_KIN_FEEDBACK_ERR_STATE;
|
||||
*state = feedback->state;
|
||||
return SIM_KIN_FEEDBACK_OK;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_feedback_state_hash(const sim_kinematics_feedback *feedback) {
|
||||
unsigned char bytes[sizeof(sim_kinematics_feedback_state)];
|
||||
sim_kinematics_feedback_state state;
|
||||
size_t offset = 0;
|
||||
uint32_t index;
|
||||
if (feedback == 0) return 0;
|
||||
state = feedback->state;
|
||||
for (index = 0; index < 9; ++index) {
|
||||
double *pose_value = &state.pose.tran.x;
|
||||
pose_value[index] = isfinite(pose_value[index]) ? nearbyint(pose_value[index] * 1000000000.0) / 1000000000.0 : pose_value[index];
|
||||
if (index < state.joint_count) state.joints[index] = isfinite(state.joints[index]) ? nearbyint(state.joints[index] * 1000000000.0) / 1000000000.0 : state.joints[index];
|
||||
}
|
||||
memcpy(bytes + offset, &state, sizeof(state)); offset += sizeof(state);
|
||||
return feedback_fnv1a64(bytes, offset);
|
||||
}
|
||||
138
wasm-core/src/sim_kinematics_home_limit.c
Normal file
138
wasm-core/src/sim_kinematics_home_limit.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "sim_kinematics_home_limit.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t home_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void state_zero(sim_kinematics_home_limit_state *state) {
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->last_error = SIM_KIN_HOME_OK;
|
||||
state->limit_error = SIM_KIN_OK;
|
||||
state->kinematics_error = SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_home_limit_init(sim_kinematics_home_limit *home_limit,
|
||||
sim_kinematics *kinematics,
|
||||
uint32_t joint_count) {
|
||||
uint32_t configured_count;
|
||||
if (home_limit == 0 || kinematics == 0) return SIM_KIN_HOME_ERR_ARGUMENT;
|
||||
if (!kinematics->configured) return SIM_KIN_HOME_ERR_CONFIGURATION;
|
||||
configured_count = kinematics->config.kinematics_joint_count == 0 ?
|
||||
kinematics->config.joint_count : kinematics->config.kinematics_joint_count;
|
||||
if (joint_count == 0 || joint_count != configured_count || joint_count > SIM_KIN_MAX_JOINTS)
|
||||
return SIM_KIN_HOME_ERR_CONFIGURATION;
|
||||
memset(home_limit, 0, sizeof(*home_limit));
|
||||
home_limit->kinematics = kinematics;
|
||||
home_limit->state.joint_count = joint_count;
|
||||
state_zero(&home_limit->state);
|
||||
home_limit->state.joint_count = joint_count;
|
||||
return SIM_KIN_HOME_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_home_limit_reset(sim_kinematics_home_limit *home_limit) {
|
||||
uint32_t joint_count;
|
||||
if (home_limit == 0 || home_limit->kinematics == 0) return SIM_KIN_HOME_ERR_ARGUMENT;
|
||||
joint_count = home_limit->state.joint_count;
|
||||
state_zero(&home_limit->state);
|
||||
home_limit->state.joint_count = joint_count;
|
||||
return SIM_KIN_HOME_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_home_limit_home(sim_kinematics_home_limit *home_limit,
|
||||
uint32_t home_sequence) {
|
||||
sim_posemath_pose pose;
|
||||
double joints[SIM_KIN_MAX_JOINTS] = {0};
|
||||
int result;
|
||||
if (home_limit == 0 || home_limit->kinematics == 0) return SIM_KIN_HOME_ERR_ARGUMENT;
|
||||
if (home_sequence > 0xffffu) return SIM_KIN_HOME_ERR_SEQUENCE;
|
||||
result = sim_kinematics_home(home_limit->kinematics, joints,
|
||||
home_limit->state.joint_count, &pose);
|
||||
if (result != SIM_KIN_OK) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_FORWARD;
|
||||
home_limit->state.kinematics_error = result;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
memcpy(home_limit->state.joints, joints, sizeof(double) * home_limit->state.joint_count);
|
||||
home_limit->state.pose = pose;
|
||||
home_limit->state.home_sequence = home_sequence;
|
||||
home_limit->state.homed = 1;
|
||||
home_limit->state.tick += 1;
|
||||
home_limit->state.last_error = SIM_KIN_HOME_OK;
|
||||
home_limit->state.limit_error = SIM_KIN_OK;
|
||||
home_limit->state.kinematics_error = SIM_KIN_OK;
|
||||
return SIM_KIN_HOME_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_home_limit_check(sim_kinematics_home_limit *home_limit,
|
||||
const double *joints,
|
||||
uint32_t joint_count) {
|
||||
sim_posemath_pose pose;
|
||||
uint32_t index;
|
||||
int result;
|
||||
if (home_limit == 0 || joints == 0) return SIM_KIN_HOME_ERR_ARGUMENT;
|
||||
if (!home_limit->state.homed) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_UNHOMED;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
if (joint_count != home_limit->state.joint_count) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_CONFIGURATION;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
for (index = 0; index < joint_count; ++index) {
|
||||
if (!isfinite(joints[index])) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_NONFINITE;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
if (home_limit->kinematics->config.limit_enabled[index] &&
|
||||
(joints[index] < home_limit->kinematics->config.joint_min[index] ||
|
||||
joints[index] > home_limit->kinematics->config.joint_max[index])) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_LIMIT;
|
||||
home_limit->state.limit_error = SIM_KIN_ERR_RANGE;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
}
|
||||
result = sim_kinematics_forward(home_limit->kinematics, joints, joint_count, &pose);
|
||||
if (result != SIM_KIN_OK) {
|
||||
home_limit->state.last_error = SIM_KIN_HOME_ERR_FORWARD;
|
||||
home_limit->state.kinematics_error = result;
|
||||
return home_limit->state.last_error;
|
||||
}
|
||||
memcpy(home_limit->state.joints, joints, sizeof(double) * joint_count);
|
||||
home_limit->state.pose = pose;
|
||||
home_limit->state.tick += 1;
|
||||
home_limit->state.last_error = SIM_KIN_HOME_OK;
|
||||
home_limit->state.limit_error = SIM_KIN_OK;
|
||||
home_limit->state.kinematics_error = SIM_KIN_OK;
|
||||
return SIM_KIN_HOME_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_home_limit_read(const sim_kinematics_home_limit *home_limit,
|
||||
sim_kinematics_home_limit_state *state) {
|
||||
if (home_limit == 0 || state == 0) return SIM_KIN_HOME_ERR_ARGUMENT;
|
||||
*state = home_limit->state;
|
||||
return SIM_KIN_HOME_OK;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_home_limit_state_hash(const sim_kinematics_home_limit *home_limit) {
|
||||
sim_kinematics_home_limit_state state;
|
||||
unsigned char bytes[sizeof(state)];
|
||||
uint32_t index;
|
||||
if (home_limit == 0) return 0;
|
||||
state = home_limit->state;
|
||||
for (index = 0; index < 9; ++index) {
|
||||
double *pose_value = &state.pose.tran.x;
|
||||
pose_value[index] = isfinite(pose_value[index]) ? nearbyint(pose_value[index] * 1000000000.0) / 1000000000.0 : pose_value[index];
|
||||
if (index < state.joint_count) state.joints[index] = isfinite(state.joints[index]) ? nearbyint(state.joints[index] * 1000000000.0) / 1000000000.0 : state.joints[index];
|
||||
}
|
||||
memcpy(bytes, &state, sizeof(state));
|
||||
return home_fnv1a64(bytes, sizeof(bytes));
|
||||
}
|
||||
184
wasm-core/src/sim_kinematics_motion.c
Normal file
184
wasm-core/src/sim_kinematics_motion.c
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "sim_kinematics_motion.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t motion_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static double normalized(double value) {
|
||||
return isfinite(value) ? nearbyint(value * 1000000000.0) / 1000000000.0 : value;
|
||||
}
|
||||
|
||||
static void set_error(sim_kinematics_motion *adapter, int error,
|
||||
int planner_error, int kinematics_error) {
|
||||
adapter->last_error = error;
|
||||
adapter->planner_error = planner_error;
|
||||
adapter->kinematics_error = kinematics_error;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_reset(sim_kinematics_motion *adapter) {
|
||||
if (adapter == 0) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
if (adapter->kinematics == 0 || !adapter->kinematics->configured) return SIM_KIN_MOTION_ERR_CONFIGURATION;
|
||||
if (sim_tp_reset(&adapter->planner) != SIM_POSEMATH_OK) return SIM_KIN_MOTION_ERR_PLANNER;
|
||||
adapter->head = 0;
|
||||
adapter->count = 0;
|
||||
adapter->tick = 0;
|
||||
adapter->paused = 0;
|
||||
memset(&adapter->last_pose, 0, sizeof(adapter->last_pose));
|
||||
memset(adapter->last_joints, 0, sizeof(adapter->last_joints));
|
||||
set_error(adapter, SIM_KIN_MOTION_OK, SIM_POSEMATH_OK, SIM_KIN_OK);
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_init(sim_kinematics_motion *adapter,
|
||||
sim_kinematics *kinematics, double cycle_time,
|
||||
double velocity_limit, double acceleration_limit) {
|
||||
uint32_t joint_count;
|
||||
int result;
|
||||
if (adapter == 0 || kinematics == 0) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
if (!kinematics->configured) return SIM_KIN_MOTION_ERR_CONFIGURATION;
|
||||
joint_count = kinematics->config.kinematics_joint_count == 0 ?
|
||||
kinematics->config.joint_count : kinematics->config.kinematics_joint_count;
|
||||
if (joint_count == 0 || joint_count > SIM_KIN_MAX_JOINTS) return SIM_KIN_MOTION_ERR_CONFIGURATION;
|
||||
memset(adapter, 0, sizeof(*adapter));
|
||||
result = sim_tp_init(&adapter->planner, cycle_time, velocity_limit, acceleration_limit);
|
||||
if (result != SIM_POSEMATH_OK) return SIM_KIN_MOTION_ERR_CONFIGURATION;
|
||||
adapter->kinematics = kinematics;
|
||||
adapter->joint_count = joint_count;
|
||||
return sim_kinematics_motion_reset(adapter);
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_enqueue(sim_kinematics_motion *adapter,
|
||||
const sim_posemath_segment *segment,
|
||||
uint32_t *id_out) {
|
||||
int result;
|
||||
if (adapter == 0 || segment == 0) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
result = sim_tp_enqueue(&adapter->planner, segment, id_out);
|
||||
if (result != SIM_POSEMATH_OK) {
|
||||
set_error(adapter, result == SIM_POSEMATH_ERR_NONFINITE ? SIM_KIN_MOTION_ERR_NONFINITE : SIM_KIN_MOTION_ERR_PLANNER, result, SIM_KIN_OK);
|
||||
return adapter->last_error;
|
||||
}
|
||||
set_error(adapter, SIM_KIN_MOTION_OK, SIM_POSEMATH_OK, SIM_KIN_OK);
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_pause(sim_kinematics_motion *adapter, uint32_t paused) {
|
||||
if (adapter == 0 || paused > 1) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
adapter->paused = paused;
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_step(sim_kinematics_motion *adapter) {
|
||||
sim_tp planner_snapshot;
|
||||
sim_kinematics kinematics_snapshot;
|
||||
sim_tp_state planner_state;
|
||||
sim_kinematics_motion_sample sample;
|
||||
sim_kinematics_result_info info;
|
||||
uint32_t index;
|
||||
int result;
|
||||
if (adapter == 0) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
if (adapter->kinematics == 0 || !adapter->kinematics->configured) return SIM_KIN_MOTION_ERR_CONFIGURATION;
|
||||
if (adapter->paused) {
|
||||
set_error(adapter, SIM_KIN_MOTION_ERR_STATE, SIM_POSEMATH_OK, SIM_KIN_OK);
|
||||
return adapter->last_error;
|
||||
}
|
||||
if (adapter->count >= SIM_KIN_MOTION_QUEUE_CAPACITY) {
|
||||
set_error(adapter, SIM_KIN_MOTION_ERR_CAPACITY, SIM_POSEMATH_OK, SIM_KIN_OK);
|
||||
return adapter->last_error;
|
||||
}
|
||||
planner_snapshot = adapter->planner;
|
||||
kinematics_snapshot = *adapter->kinematics;
|
||||
result = sim_tp_step(&adapter->planner);
|
||||
if (result != SIM_POSEMATH_OK) {
|
||||
adapter->planner = planner_snapshot;
|
||||
set_error(adapter, SIM_KIN_MOTION_ERR_PLANNER, result, SIM_KIN_OK);
|
||||
return adapter->last_error;
|
||||
}
|
||||
if (sim_tp_read_state(&adapter->planner, &planner_state) != SIM_POSEMATH_OK) {
|
||||
adapter->planner = planner_snapshot;
|
||||
set_error(adapter, SIM_KIN_MOTION_ERR_PLANNER, SIM_POSEMATH_ERR_STATE, SIM_KIN_OK);
|
||||
return adapter->last_error;
|
||||
}
|
||||
memset(&sample, 0, sizeof(sample));
|
||||
sample.pose = planner_state.position;
|
||||
result = sim_kinematics_inverse_ex(adapter->kinematics, &sample.pose,
|
||||
adapter->joint_count, 0, sample.joints,
|
||||
&info);
|
||||
if (result != SIM_KIN_OK) {
|
||||
adapter->planner = planner_snapshot;
|
||||
*adapter->kinematics = kinematics_snapshot;
|
||||
set_error(adapter, result == SIM_KIN_ERR_NONFINITE ? SIM_KIN_MOTION_ERR_NONFINITE : SIM_KIN_MOTION_ERR_INVERSE, SIM_POSEMATH_OK, result);
|
||||
return adapter->last_error;
|
||||
}
|
||||
sample.joint_count = adapter->joint_count;
|
||||
sample.tick = planner_state.tick;
|
||||
sample.planner_id = planner_state.active_id;
|
||||
index = (adapter->head + adapter->count) % SIM_KIN_MOTION_QUEUE_CAPACITY;
|
||||
adapter->samples[index] = sample;
|
||||
adapter->count += 1;
|
||||
adapter->tick = sample.tick;
|
||||
adapter->last_pose = sample.pose;
|
||||
memcpy(adapter->last_joints, sample.joints, sizeof(sample.joints));
|
||||
set_error(adapter, SIM_KIN_MOTION_OK, SIM_POSEMATH_OK, SIM_KIN_OK);
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_peek(const sim_kinematics_motion *adapter,
|
||||
sim_kinematics_motion_sample *sample) {
|
||||
if (adapter == 0 || sample == 0) return SIM_KIN_MOTION_ERR_ARGUMENT;
|
||||
if (adapter->count == 0) return SIM_KIN_MOTION_ERR_STATE;
|
||||
*sample = adapter->samples[adapter->head];
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_motion_pop(sim_kinematics_motion *adapter,
|
||||
sim_kinematics_motion_sample *sample) {
|
||||
int result = sim_kinematics_motion_peek(adapter, sample);
|
||||
if (result != SIM_KIN_MOTION_OK) return result;
|
||||
adapter->head = (adapter->head + 1) % SIM_KIN_MOTION_QUEUE_CAPACITY;
|
||||
adapter->count -= 1;
|
||||
return SIM_KIN_MOTION_OK;
|
||||
}
|
||||
|
||||
uint32_t sim_kinematics_motion_depth(const sim_kinematics_motion *adapter) {
|
||||
return adapter == 0 ? 0 : adapter->count;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_motion_state_hash(const sim_kinematics_motion *adapter) {
|
||||
unsigned char bytes[16384];
|
||||
size_t offset = 0;
|
||||
uint64_t planner_hash;
|
||||
uint64_t kinematics_hash;
|
||||
uint32_t sample_index;
|
||||
if (adapter == 0 || adapter->kinematics == 0) return 0;
|
||||
planner_hash = sim_tp_state_hash(&adapter->planner);
|
||||
kinematics_hash = sim_kinematics_state_hash(adapter->kinematics, adapter->last_joints,
|
||||
adapter->joint_count, &adapter->last_pose);
|
||||
#define APPEND(value) do { memcpy(bytes + offset, &(value), sizeof(value)); offset += sizeof(value); } while (0)
|
||||
APPEND(adapter->head); APPEND(adapter->count); APPEND(adapter->tick);
|
||||
APPEND(adapter->joint_count); APPEND(adapter->paused); APPEND(adapter->last_error);
|
||||
APPEND(adapter->planner_error); APPEND(adapter->kinematics_error);
|
||||
APPEND(planner_hash); APPEND(kinematics_hash);
|
||||
for (sample_index = 0; sample_index < adapter->count; ++sample_index) {
|
||||
const sim_kinematics_motion_sample *sample = &adapter->samples[(adapter->head + sample_index) % SIM_KIN_MOTION_QUEUE_CAPACITY];
|
||||
sim_posemath_pose pose = sample->pose;
|
||||
double joints[SIM_KIN_MAX_JOINTS] = {0};
|
||||
double *pose_values = &pose.tran.x;
|
||||
uint32_t joint_index;
|
||||
for (joint_index = 0; joint_index < 9; ++joint_index) pose_values[joint_index] = normalized(pose_values[joint_index]);
|
||||
for (joint_index = 0; joint_index < sample->joint_count; ++joint_index) joints[joint_index] = normalized(sample->joints[joint_index]);
|
||||
memcpy(bytes + offset, &pose, sizeof(pose)); offset += sizeof(pose);
|
||||
memcpy(bytes + offset, joints, sizeof(joints)); offset += sizeof(joints);
|
||||
APPEND(sample->joint_count); APPEND(sample->tick); APPEND(sample->planner_id);
|
||||
}
|
||||
#undef APPEND
|
||||
return motion_fnv1a64(bytes, offset);
|
||||
}
|
||||
124
wasm-core/src/sim_kinematics_registry.c
Normal file
124
wasm-core/src/sim_kinematics_registry.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define COMMON (SIM_KIN_CAP_FORWARD | SIM_KIN_CAP_INVERSE | SIM_KIN_CAP_SWITCHABLE)
|
||||
#define BASE_PARAMS (SIM_KIN_PARAM_MAPPING | SIM_KIN_PARAM_LIMITS)
|
||||
|
||||
static const sim_kinematics_capability registry[] = {
|
||||
{1, SIM_KIN_TRIVKINS, "trivkins", "", 1, 9, COMMON | SIM_KIN_CAP_IDENTITY | SIM_KIN_CAP_EXTRA_JOINTS | SIM_KIN_CAP_DUPLICATE_COORDINATES, BASE_PARAMS},
|
||||
{1, SIM_KIN_XYZAC_TRT, "xyzac-trt-kins", "XYZAC", 5, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS | SIM_KIN_CAP_DUPLICATE_COORDINATES, BASE_PARAMS | SIM_KIN_PARAM_OFFSETS | SIM_KIN_PARAM_TOOL},
|
||||
{1, SIM_KIN_XYZBC_TRT, "xyzbc-trt-kins", "XYZBC", 5, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS | SIM_KIN_CAP_DUPLICATE_COORDINATES, BASE_PARAMS | SIM_KIN_PARAM_OFFSETS | SIM_KIN_PARAM_TOOL},
|
||||
{1, SIM_KIN_XYZBCW, "xyzbcw", "XYZBCW", 6, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS | SIM_KIN_PARAM_PIVOT},
|
||||
{1, SIM_KIN_COREXY, "corexykins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS},
|
||||
{1, SIM_KIN_ROSE, "rosekins", "XYZ", 3, 3, COMMON, BASE_PARAMS},
|
||||
{1, SIM_KIN_MAXKINS, "maxkins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS | SIM_KIN_PARAM_PIVOT},
|
||||
{1, SIM_KIN_ROTATEKINS, "rotatekins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS},
|
||||
{1, SIM_KIN_5AXISKINS, "5axiskins", "XYZBCW", 6, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS | SIM_KIN_CAP_DUPLICATE_COORDINATES, BASE_PARAMS | SIM_KIN_PARAM_PIVOT},
|
||||
{1, SIM_KIN_SCARA, "scarakins", "XYZCAB", 6, 6, COMMON, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_PUMA, "pumakins", "XYZABC", 6, 6, COMMON, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_SCORBOT, "scorbot-kins", "XYZAB", 5, 5, COMMON, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_GENSER, "genserkins", "XYZABC", 6, 6, COMMON | SIM_KIN_CAP_ITERATIVE, BASE_PARAMS | SIM_KIN_PARAM_DH | SIM_KIN_PARAM_SOLVER},
|
||||
{1, SIM_KIN_LINEAR_DELTA, "lineardeltakins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_ROTARY_DELTA, "rotarydeltakins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_TRIPOD, "tripodkins", "XYZ", 3, 9, COMMON | SIM_KIN_CAP_EXTRA_JOINTS, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY},
|
||||
{1, SIM_KIN_GENHEX, "genhexkins", "XYZABC", 6, 6, COMMON | SIM_KIN_CAP_ITERATIVE, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY | SIM_KIN_PARAM_TOOL | SIM_KIN_PARAM_SOLVER},
|
||||
{1, SIM_KIN_PENTAPOD, "pentakins", "XYZAB", 5, 5, COMMON | SIM_KIN_CAP_ITERATIVE, BASE_PARAMS | SIM_KIN_PARAM_GEOMETRY | SIM_KIN_PARAM_TOOL | SIM_KIN_PARAM_SOLVER},
|
||||
};
|
||||
|
||||
static uint64_t registry_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int path_like(const char *name) {
|
||||
size_t index;
|
||||
if (name == 0 || name[0] == '\0') return 0;
|
||||
for (index = 0; name[index] != '\0'; ++index)
|
||||
if (name[index] == '/' || name[index] == '\\') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int has_coordinate_casefold(const char *coordinates, char wanted) {
|
||||
size_t index;
|
||||
for (index = 0; coordinates[index] != '\0'; ++index) {
|
||||
char value = coordinates[index];
|
||||
if (value >= 'a' && value <= 'z') value = (char)(value - 'a' + 'A');
|
||||
if (value == wanted) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t sim_kinematics_registry_count(void) {
|
||||
return (uint32_t)(sizeof(registry) / sizeof(registry[0]));
|
||||
}
|
||||
|
||||
int sim_kinematics_registry_get(uint32_t index,
|
||||
sim_kinematics_capability *capability) {
|
||||
if (capability == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (index >= sim_kinematics_registry_count()) return SIM_KIN_ERR_RANGE;
|
||||
*capability = registry[index];
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_registry_find(const char *name,
|
||||
sim_kinematics_capability *capability) {
|
||||
uint32_t index;
|
||||
if (name == 0 || capability == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (path_like(name)) return SIM_KIN_ERR_DYNAMIC_MODULE;
|
||||
for (index = 0; index < sim_kinematics_registry_count(); ++index) {
|
||||
if (strcmp(name, registry[index].name) == 0) {
|
||||
*capability = registry[index];
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
}
|
||||
return SIM_KIN_ERR_UNKNOWN_TYPE;
|
||||
}
|
||||
|
||||
int sim_kinematics_registry_require(const char *name, uint32_t capabilities,
|
||||
uint64_t parameter_mask,
|
||||
sim_kinematics_capability *capability) {
|
||||
sim_kinematics_capability found;
|
||||
int result = sim_kinematics_registry_find(name, &found);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
if ((found.capabilities & capabilities) != capabilities ||
|
||||
(found.parameter_mask & parameter_mask) != parameter_mask) return SIM_KIN_ERR_CAPABILITY;
|
||||
if (capability != 0) *capability = found;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_registry_validate_config(const char *name,
|
||||
const sim_kinematics_config *config,
|
||||
sim_kinematics_capability *capability) {
|
||||
sim_kinematics_capability found;
|
||||
sim_kinematics instance;
|
||||
const char *coordinates;
|
||||
uint32_t joint_count;
|
||||
size_t index;
|
||||
int result;
|
||||
if (config == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
result = sim_kinematics_registry_find(name, &found);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
if (config->type != found.type) return SIM_KIN_ERR_CONFIGURATION;
|
||||
joint_count = config->kinematics_joint_count == 0 ? config->joint_count : config->kinematics_joint_count;
|
||||
if (joint_count < found.min_joints || joint_count > found.max_joints) return SIM_KIN_ERR_CONFIGURATION;
|
||||
coordinates = config->coordinates[0] == '\0' ? "XYZABCUVW" : config->coordinates;
|
||||
for (index = 0; found.required_coordinates[index] != '\0'; ++index)
|
||||
if (!has_coordinate_casefold(coordinates, found.required_coordinates[index])) return SIM_KIN_ERR_CONFIGURATION;
|
||||
result = sim_kinematics_init(&instance, config);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
if (capability != 0) *capability = found;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_registry_state_hash(void) {
|
||||
return registry_fnv1a64((const unsigned char *)registry, sizeof(registry));
|
||||
}
|
||||
|
||||
#undef BASE_PARAMS
|
||||
#undef COMMON
|
||||
185
wasm-core/src/sim_switchkins.c
Normal file
185
wasm-core/src/sim_switchkins.c
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t switch_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int valid_mode(int mode) {
|
||||
return mode >= 0 && mode < (int)SIM_KIN_SWITCH_MAX_TYPES;
|
||||
}
|
||||
|
||||
static void switch_info(sim_kinematics_result_info *info) {
|
||||
if (info != 0) memset(info, 0, sizeof(*info));
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_init(sim_kinematics_switch *switcher, int default_mode,
|
||||
uint8_t fwd_iterates_mask) {
|
||||
if (switcher == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!valid_mode(default_mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
memset(switcher, 0, sizeof(*switcher));
|
||||
switcher->active_mode = default_mode;
|
||||
switcher->pending_mode = default_mode;
|
||||
switcher->fwd_iterates_mask = fwd_iterates_mask & 0x07u;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_register(sim_kinematics_switch *switcher, int mode,
|
||||
sim_kinematics *module, uint8_t fwd_iterates) {
|
||||
if (switcher == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!valid_mode(mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
if (module == 0 || !module->configured) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
switcher->modules[mode] = module;
|
||||
switcher->registered[mode] = 1;
|
||||
if (fwd_iterates) switcher->fwd_iterates_mask |= (uint8_t)(1u << mode);
|
||||
else switcher->fwd_iterates_mask &= (uint8_t)~(1u << mode);
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_begin(sim_kinematics_switch *switcher, int mode) {
|
||||
if (switcher == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!valid_mode(mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
if (!switcher->registered[mode] || switcher->modules[mode] == 0) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
if (switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
switcher->pending_mode = mode;
|
||||
switcher->switching = (uint8_t)(mode != switcher->active_mode);
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_abort(sim_kinematics_switch *switcher) {
|
||||
if (switcher == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
switcher->pending_mode = switcher->active_mode;
|
||||
switcher->switching = 0;
|
||||
switcher->abort_count += 1;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_commit(sim_kinematics_switch *switcher, double *joints,
|
||||
uint32_t joint_count, sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info) {
|
||||
sim_kinematics *target;
|
||||
double remapped[SIM_KIN_MAX_JOINTS] = {0};
|
||||
sim_kinematics_result_info local_info;
|
||||
int result;
|
||||
switch_info(info);
|
||||
if (switcher == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
target = switcher->modules[switcher->pending_mode];
|
||||
if (target == 0 || !switcher->registered[switcher->pending_mode]) {
|
||||
switcher->switching = 0;
|
||||
switcher->pending_mode = switcher->active_mode;
|
||||
return SIM_KIN_ERR_MISSING_MODULE;
|
||||
}
|
||||
if (joint_count > SIM_KIN_MAX_JOINTS) return SIM_KIN_ERR_CONFIGURATION;
|
||||
result = sim_kinematics_inverse_ex(target, pose, joint_count, 0, remapped,
|
||||
&local_info);
|
||||
if (result != SIM_KIN_OK) {
|
||||
switcher->switching = 0;
|
||||
switcher->pending_mode = switcher->active_mode;
|
||||
if (info != 0) *info = local_info;
|
||||
return result;
|
||||
}
|
||||
memcpy(joints, remapped, sizeof(double) * joint_count);
|
||||
switcher->active_mode = switcher->pending_mode;
|
||||
switcher->pending_mode = switcher->active_mode;
|
||||
switcher->switching = 0;
|
||||
switcher->generation += 1;
|
||||
switcher->switch_count += 1;
|
||||
switcher->last_pose[switcher->active_mode] = *pose;
|
||||
switcher->have_last_pose[switcher->active_mode] = 1;
|
||||
if (info != 0) *info = local_info;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_forward(sim_kinematics_switch *switcher,
|
||||
const double *joints, uint32_t joint_count,
|
||||
sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info) {
|
||||
sim_kinematics *module;
|
||||
int result;
|
||||
switch_info(info);
|
||||
if (switcher == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
if (!valid_mode(switcher->active_mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
module = switcher->modules[switcher->active_mode];
|
||||
if (module == 0 || !switcher->registered[switcher->active_mode]) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
result = sim_kinematics_forward_ex(module, joints, joint_count, 0, pose, info);
|
||||
if (result == SIM_KIN_OK) {
|
||||
switcher->last_pose[switcher->active_mode] = *pose;
|
||||
switcher->have_last_pose[switcher->active_mode] = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_inverse(sim_kinematics_switch *switcher,
|
||||
const sim_posemath_pose *pose, double *joints,
|
||||
uint32_t joint_count,
|
||||
sim_kinematics_result_info *info) {
|
||||
sim_kinematics *module;
|
||||
int result;
|
||||
switch_info(info);
|
||||
if (switcher == 0 || pose == 0 || joints == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
if (!valid_mode(switcher->active_mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
module = switcher->modules[switcher->active_mode];
|
||||
if (module == 0 || !switcher->registered[switcher->active_mode]) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
result = sim_kinematics_inverse_ex(module, pose, joint_count, 0, joints, info);
|
||||
if (result == SIM_KIN_OK) {
|
||||
switcher->last_pose[switcher->active_mode] = *pose;
|
||||
switcher->have_last_pose[switcher->active_mode] = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_select(sim_kinematics_switch *switcher, int mode,
|
||||
double *joints, uint32_t joint_count,
|
||||
sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info) {
|
||||
sim_posemath_pose current_pose;
|
||||
int result;
|
||||
switch_info(info);
|
||||
if (switcher == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!valid_mode(mode)) return SIM_KIN_ERR_INVALID_MODE;
|
||||
if (switcher->switching) return SIM_KIN_ERR_SWITCH_INTERRUPTED;
|
||||
if (!switcher->registered[mode] || switcher->modules[mode] == 0) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
if (mode == switcher->active_mode) return sim_kinematics_switch_forward(switcher, joints, joint_count, pose, info);
|
||||
result = sim_kinematics_switch_forward(switcher, joints, joint_count, ¤t_pose, info);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
result = sim_kinematics_switch_begin(switcher, mode);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
result = sim_kinematics_switch_commit(switcher, joints, joint_count, ¤t_pose, info);
|
||||
if (result == SIM_KIN_OK) *pose = current_pose;
|
||||
return result;
|
||||
}
|
||||
|
||||
int sim_kinematics_switch_active(const sim_kinematics_switch *switcher) {
|
||||
return switcher == 0 ? SIM_KIN_ERR_ARGUMENT : switcher->active_mode;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_switch_state_hash(const sim_kinematics_switch *switcher,
|
||||
const double *joints, uint32_t joint_count,
|
||||
const sim_posemath_pose *pose) {
|
||||
unsigned char bytes[256];
|
||||
size_t offset = 0;
|
||||
uint64_t module_hash;
|
||||
if (switcher == 0 || joints == 0 || pose == 0 || joint_count > SIM_KIN_MAX_JOINTS) return 0;
|
||||
if (!valid_mode(switcher->active_mode) || switcher->modules[switcher->active_mode] == 0) return 0;
|
||||
module_hash = sim_kinematics_state_hash(switcher->modules[switcher->active_mode], joints, joint_count, pose);
|
||||
memcpy(bytes + offset, &switcher->active_mode, sizeof(switcher->active_mode)); offset += sizeof(switcher->active_mode);
|
||||
memcpy(bytes + offset, &switcher->pending_mode, sizeof(switcher->pending_mode)); offset += sizeof(switcher->pending_mode);
|
||||
memcpy(bytes + offset, &switcher->switching, sizeof(switcher->switching)); offset += sizeof(switcher->switching);
|
||||
memcpy(bytes + offset, &switcher->generation, sizeof(switcher->generation)); offset += sizeof(switcher->generation);
|
||||
memcpy(bytes + offset, &switcher->switch_count, sizeof(switcher->switch_count)); offset += sizeof(switcher->switch_count);
|
||||
memcpy(bytes + offset, &switcher->abort_count, sizeof(switcher->abort_count)); offset += sizeof(switcher->abort_count);
|
||||
memcpy(bytes + offset, &module_hash, sizeof(module_hash)); offset += sizeof(module_hash);
|
||||
return switch_fnv1a64(bytes, offset);
|
||||
}
|
||||
98
wasm-core/src/sim_userkins.c
Normal file
98
wasm-core/src/sim_userkins.c
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static uint64_t user_fnv1a64(const unsigned char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(1469598103934665603);
|
||||
size_t index;
|
||||
for (index = 0; index < length; ++index) {
|
||||
hash ^= bytes[index];
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int valid_static_name(const char *name) {
|
||||
size_t index;
|
||||
if (name == 0 || name[0] == '\0') return 0;
|
||||
for (index = 0; index < SIM_KIN_USER_NAME_CAPACITY && name[index] != '\0'; ++index) {
|
||||
unsigned char value = (unsigned char)name[index];
|
||||
if (value < 0x21 || value > 0x7e || value == '/' || value == '\\') return 0;
|
||||
}
|
||||
return index < SIM_KIN_USER_NAME_CAPACITY;
|
||||
}
|
||||
|
||||
int sim_kinematics_user_init(sim_kinematics_user *user,
|
||||
const sim_kinematics_user_descriptor *descriptor) {
|
||||
sim_kinematics_config config;
|
||||
uint32_t required = SIM_KIN_USER_CAP_FORWARD | SIM_KIN_USER_CAP_INVERSE |
|
||||
SIM_KIN_USER_CAP_STATIC;
|
||||
int result;
|
||||
if (user == 0 || descriptor == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (descriptor->abi_version != SIM_KIN_USER_ABI_VERSION) return SIM_KIN_ERR_ABI_VERSION;
|
||||
if ((descriptor->capabilities & required) != required ||
|
||||
(descriptor->capabilities & ~required) != 0) return SIM_KIN_ERR_CAPABILITY;
|
||||
if (!valid_static_name(descriptor->name)) return SIM_KIN_ERR_DYNAMIC_MODULE;
|
||||
config = descriptor->config;
|
||||
if (descriptor->implementation == SIM_KIN_USER_IDENTITY) config.type = SIM_KIN_TRIVKINS;
|
||||
else if (descriptor->implementation == SIM_KIN_USER_GENSER) config.type = SIM_KIN_GENSER;
|
||||
else return SIM_KIN_ERR_MISSING_MODULE;
|
||||
result = sim_kinematics_init(&user->module, &config);
|
||||
if (result != SIM_KIN_OK) return result;
|
||||
user->descriptor = *descriptor;
|
||||
user->descriptor.config = user->module.config;
|
||||
user->forward_calls = 0;
|
||||
user->inverse_calls = 0;
|
||||
user->initialized = 1;
|
||||
return SIM_KIN_OK;
|
||||
}
|
||||
|
||||
int sim_kinematics_user_bind_switch(sim_kinematics_user *user,
|
||||
sim_kinematics_switch *switcher, int mode,
|
||||
uint8_t fwd_iterates) {
|
||||
if (user == 0 || switcher == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!user->initialized) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
return sim_kinematics_switch_register(switcher, mode, &user->module, fwd_iterates);
|
||||
}
|
||||
|
||||
int sim_kinematics_user_forward(sim_kinematics_user *user, const double *joints,
|
||||
uint32_t joint_count, sim_posemath_pose *pose,
|
||||
sim_kinematics_result_info *info) {
|
||||
if (user == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!user->initialized) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
user->forward_calls += 1;
|
||||
return sim_kinematics_forward_ex(&user->module, joints, joint_count, 0, pose, info);
|
||||
}
|
||||
|
||||
int sim_kinematics_user_inverse(sim_kinematics_user *user,
|
||||
const sim_posemath_pose *pose, double *joints,
|
||||
uint32_t joint_count,
|
||||
sim_kinematics_result_info *info) {
|
||||
if (user == 0 || pose == 0 || joints == 0) return SIM_KIN_ERR_ARGUMENT;
|
||||
if (!user->initialized) return SIM_KIN_ERR_MISSING_MODULE;
|
||||
user->inverse_calls += 1;
|
||||
return sim_kinematics_inverse_ex(&user->module, pose, joint_count, 0, joints, info);
|
||||
}
|
||||
|
||||
int sim_kinematics_user_reject_dynamic(const char *module_path) {
|
||||
return module_path == 0 ? SIM_KIN_ERR_ARGUMENT : SIM_KIN_ERR_DYNAMIC_MODULE;
|
||||
}
|
||||
|
||||
uint64_t sim_kinematics_user_state_hash(const sim_kinematics_user *user,
|
||||
const double *joints, uint32_t joint_count,
|
||||
const sim_posemath_pose *pose) {
|
||||
unsigned char bytes[sizeof(uint32_t) * 5 + sizeof(int) + SIM_KIN_USER_NAME_CAPACITY + sizeof(uint64_t)];
|
||||
uint64_t module_hash;
|
||||
size_t offset = 0;
|
||||
if (user == 0 || joints == 0 || pose == 0 || !user->initialized) return 0;
|
||||
module_hash = sim_kinematics_state_hash(&user->module, joints, joint_count, pose);
|
||||
memcpy(bytes + offset, &user->descriptor.abi_version, sizeof(user->descriptor.abi_version)); offset += sizeof(user->descriptor.abi_version);
|
||||
memcpy(bytes + offset, &user->descriptor.capabilities, sizeof(user->descriptor.capabilities)); offset += sizeof(user->descriptor.capabilities);
|
||||
memcpy(bytes + offset, &user->descriptor.implementation, sizeof(user->descriptor.implementation)); offset += sizeof(user->descriptor.implementation);
|
||||
memcpy(bytes + offset, user->descriptor.name, sizeof(user->descriptor.name)); offset += sizeof(user->descriptor.name);
|
||||
memcpy(bytes + offset, &user->initialized, sizeof(user->initialized)); offset += sizeof(user->initialized);
|
||||
memcpy(bytes + offset, &user->forward_calls, sizeof(user->forward_calls)); offset += sizeof(user->forward_calls);
|
||||
memcpy(bytes + offset, &user->inverse_calls, sizeof(user->inverse_calls)); offset += sizeof(user->inverse_calls);
|
||||
memcpy(bytes + offset, &module_hash, sizeof(module_hash)); offset += sizeof(module_hash);
|
||||
return user_fnv1a64(bytes, offset);
|
||||
}
|
||||
@@ -24,5 +24,12 @@ int main(void) {
|
||||
assert(sim_event_import(exported, export_size) == SIM_EVENT_OK);
|
||||
assert(sim_event_count() == 1);
|
||||
assert(sim_event_import(exported, export_size - 1) == SIM_EVENT_ERR_ARGUMENT);
|
||||
((sim_event_record *)exported)->seq = 2;
|
||||
assert(sim_event_import(exported, export_size) == SIM_EVENT_ERR_SEQUENCE);
|
||||
assert(sim_event_cursor() == 1);
|
||||
((sim_event_record *)exported)->seq = 1;
|
||||
((sim_event_record *)exported)->kind[SIM_EVENT_KIND_CAPACITY - 1U] = 'x';
|
||||
assert(sim_event_import(exported, export_size) == SIM_EVENT_ERR_ARGUMENT);
|
||||
assert(sim_event_cursor() == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
89
wasm-core/tests/sim_io_test.c
Normal file
89
wasm-core/tests/sim_io_test.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "sim_io.h"
|
||||
|
||||
#include "sim_hal.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_posemath_pose pose(double x, double y, double z) {
|
||||
sim_posemath_pose value = {{x, y, z}, 0, 0, 0, 0, 0, 0};
|
||||
return value;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_io io = {0};
|
||||
sim_io_state state = {0};
|
||||
sim_hal_value pin = {0};
|
||||
char canonical[2048];
|
||||
uint64_t hash;
|
||||
uint64_t repeat_hash;
|
||||
int invalid_pin_status;
|
||||
int limit_status;
|
||||
int spindle_step_status;
|
||||
int tool_busy_status;
|
||||
int probe_status;
|
||||
int estop_command_status;
|
||||
int estop_recover_status;
|
||||
int recover_status;
|
||||
int fault_clear_status;
|
||||
|
||||
sim_hal_reset();
|
||||
assert(sim_io_init(&io, 2, 32, 0.1, 600.0, 5.0) == SIM_IO_OK);
|
||||
assert(sim_hal_pin_count() >= 58 && sim_hal_function_count() == 1);
|
||||
assert(sim_hal_get_pin("motion.probe-input", &pin) == SIM_HAL_OK && pin.type == SIM_HAL_BIT);
|
||||
invalid_pin_status = sim_hal_set_pin("spindle.0.speed-out", (sim_hal_value){SIM_HAL_BIT, {.bit = 1}});
|
||||
assert(invalid_pin_status == SIM_HAL_ERR_TYPE);
|
||||
limit_status = sim_io_spindle_command(&io, 0, 100001.0, 1);
|
||||
assert(limit_status == SIM_IO_ERR_LIMIT);
|
||||
assert(sim_io_spindle_command(&io, 0, 1200.0, 1) == SIM_IO_OK);
|
||||
spindle_step_status = sim_io_step(&io, 10);
|
||||
assert(spindle_step_status == SIM_IO_OK && io.state.spindles[0].speed_rpm == 600.0);
|
||||
assert(sim_io_step(&io, 10) == SIM_IO_OK && io.state.spindles[0].at_speed == 1);
|
||||
|
||||
assert(sim_io_tool_prepare(&io, 7, 3) == SIM_IO_OK);
|
||||
assert(sim_io_tool_set_prepared(&io, 1) == SIM_IO_OK);
|
||||
tool_busy_status = sim_io_tool_change(&io);
|
||||
assert(tool_busy_status == SIM_IO_ERR_BUSY);
|
||||
assert(sim_io_spindle_command(&io, 0, 0.0, 0) == SIM_IO_OK);
|
||||
assert(sim_io_step(&io, 20) == SIM_IO_OK);
|
||||
assert(sim_io_tool_change(&io) == SIM_IO_OK);
|
||||
assert(sim_io_tool_set_changed(&io, 1) == SIM_IO_OK && io.state.current_tool == 7);
|
||||
|
||||
assert(sim_io_probe_arm(&io, 0) == SIM_IO_OK);
|
||||
probe_status = sim_io_probe_update(&io, &(sim_posemath_pose){{1.0, 2.0, 3.0}, 0, 0, 0, 0, 0, 0}, 1);
|
||||
assert(probe_status == SIM_IO_OK && io.state.probe.state == SIM_IO_PROBE_TRIPPED);
|
||||
assert(sim_io_probe_update(&io, &(sim_posemath_pose){{NAN, 2.0, 3.0}, 0, 0, 0, 0, 0, 0}, 0) == SIM_IO_ERR_NONFINITE);
|
||||
|
||||
assert(sim_io_set_estop(&io, 1) == SIM_IO_OK);
|
||||
estop_command_status = sim_io_spindle_command(&io, 0, 100.0, 1);
|
||||
estop_recover_status = sim_io_recover(&io);
|
||||
assert(estop_command_status == SIM_IO_ERR_ESTOP && estop_recover_status == SIM_IO_ERR_ESTOP);
|
||||
assert(sim_io_set_estop(&io, 0) == SIM_IO_OK);
|
||||
recover_status = sim_io_recover(&io);
|
||||
assert(recover_status == SIM_IO_OK && io.state.ready == 1 && io.state.recovery_epoch == 1);
|
||||
|
||||
assert(sim_io_tool_set_fault(&io, 1, 42) == SIM_IO_OK);
|
||||
fault_clear_status = sim_io_tool_clear_fault(&io);
|
||||
assert(fault_clear_status == SIM_IO_ERR_FAULT);
|
||||
assert(sim_io_tool_set_fault(&io, 0, 0) == SIM_IO_OK);
|
||||
assert(sim_io_tool_clear_fault(&io) == SIM_IO_OK);
|
||||
hash = sim_io_state_hash(&io);
|
||||
repeat_hash = sim_io_state_hash(&io);
|
||||
assert(hash == repeat_hash);
|
||||
assert(sim_io_canonical(&io, canonical, sizeof(canonical)) > 1 && canonical[0] == '{');
|
||||
assert(sim_io_canonical(&io, 0, 0) == strlen(canonical) + 1);
|
||||
assert(sim_hal_get_pin("iocontrol.0.tool-number", &pin) == SIM_HAL_OK && pin.value.s32 == 7);
|
||||
assert(sim_hal_get_pin("spindle.0.brake", &pin) == SIM_HAL_OK && pin.value.bit == 1);
|
||||
printf("{\"schema\":\"io-samples.v1\",\"pin\":{\"invalid_type\":%d,\"count\":%u,\"functions\":%u},\"spindle\":{\"limit\":%d,\"step\":%d,\"speed\":%.3f,\"at_speed\":%u,\"tool_busy\":%d},\"tool\":{\"state\":%u,\"current\":%d,\"fault_clear\":%d},\"probe\":{\"status\":%d,\"state\":%u,\"position\":[%.3f,%.3f,%.3f]},\"estop\":{\"command\":%d,\"recover_before_clear\":%d,\"recover\":%d},\"state_hash\":%llu,\"repeat_hash\":%llu,\"canonical\":%s}\n",
|
||||
invalid_pin_status, sim_hal_pin_count(), sim_hal_function_count(), limit_status,
|
||||
spindle_step_status, io.state.spindles[0].speed_rpm, io.state.spindles[0].at_speed,
|
||||
tool_busy_status, io.state.tool_state, io.state.current_tool, fault_clear_status,
|
||||
probe_status, io.state.probe.state, io.state.probe.position.tran.x,
|
||||
io.state.probe.position.tran.y, io.state.probe.position.tran.z,
|
||||
estop_command_status, estop_recover_status, recover_status,
|
||||
(unsigned long long)hash, (unsigned long long)repeat_hash, canonical);
|
||||
(void)pose;
|
||||
return 0;
|
||||
}
|
||||
65
wasm-core/tests/sim_kinematics_feedback_test.c
Normal file
65
wasm-core/tests/sim_kinematics_feedback_test.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "sim_kinematics_feedback.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_kinematics_config config_for(int type, const char *coordinates) {
|
||||
sim_kinematics_config config = {0};
|
||||
config.type = type;
|
||||
config.joint_count = (uint32_t)strlen(coordinates);
|
||||
strcpy(config.coordinates, coordinates);
|
||||
return config;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics kinematics = {0};
|
||||
sim_kinematics singular = {0};
|
||||
sim_kinematics_feedback feedback = {0};
|
||||
sim_kinematics_feedback singular_feedback = {0};
|
||||
sim_kinematics_feedback_state state = {0};
|
||||
sim_kinematics_result_info info = {0};
|
||||
sim_kinematics_config config = config_for(SIM_KIN_TRIVKINS, "XYZ");
|
||||
double joints[3] = {1.0, 2.0, 3.0};
|
||||
double bad_joints[3] = {1.0, NAN, 3.0};
|
||||
double singular_joints[5] = {0, 0, 0, 90, 0};
|
||||
uint64_t hash;
|
||||
uint64_t repeat_hash;
|
||||
uint32_t valid_tick;
|
||||
int update_status;
|
||||
int bad_status;
|
||||
int singular_status;
|
||||
int stale_read_status;
|
||||
int count_status;
|
||||
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_feedback_init(&feedback, &kinematics, 3) == SIM_KIN_FEEDBACK_OK);
|
||||
stale_read_status = sim_kinematics_feedback_read(&feedback, &state);
|
||||
update_status = sim_kinematics_feedback_update(&feedback, joints, 3, 0, &info);
|
||||
assert(update_status == SIM_KIN_FEEDBACK_OK);
|
||||
assert(sim_kinematics_feedback_read(&feedback, &state) == SIM_KIN_FEEDBACK_OK);
|
||||
valid_tick = state.tick;
|
||||
hash = sim_kinematics_feedback_state_hash(&feedback);
|
||||
repeat_hash = sim_kinematics_feedback_state_hash(&feedback);
|
||||
assert(state.valid && state.tick == 1 && fabs(state.pose.tran.x - 1.0) < 1e-12);
|
||||
bad_status = sim_kinematics_feedback_update(&feedback, bad_joints, 3, 0, &info);
|
||||
assert(bad_status == SIM_KIN_FEEDBACK_ERR_NONFINITE && feedback.state.tick == valid_tick);
|
||||
count_status = sim_kinematics_feedback_update(&feedback, joints, 2, 0, &info);
|
||||
assert(count_status == SIM_KIN_FEEDBACK_ERR_CONFIGURATION);
|
||||
|
||||
config = config_for(SIM_KIN_XYZBC_TRT, "XYZBC");
|
||||
config.mode = SIM_KIN_MODE_INVERSE_ONLY;
|
||||
assert(sim_kinematics_init(&singular, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_feedback_init(&singular_feedback, &singular, 5) == SIM_KIN_FEEDBACK_OK);
|
||||
singular_status = sim_kinematics_feedback_update(&singular_feedback, singular_joints, 5, 0, &info);
|
||||
assert(singular_status == SIM_KIN_FEEDBACK_ERR_FORWARD && singular_feedback.state.valid == 0 && singular_feedback.state.kinematics_error == SIM_KIN_ERR_UNSUPPORTED);
|
||||
|
||||
printf("{\"schema\":\"kinematics-feedback-samples.v1\",\"nominal\":{\"status\":%d,\"tick\":%u,\"valid\":%u,\"pose\":[%.9f,%.9f,%.9f],\"flags\":[%llu,%llu],\"state_hash\":%llu,\"repeat_hash\":%llu},\"stale_read\":%d,\"nonfinite\":%d,\"count_error\":%d,\"singular\":{\"status\":%d,\"kinematics_error\":%d,\"valid\":%u}}\n",
|
||||
update_status, state.tick, state.valid, state.pose.tran.x, state.pose.tran.y, state.pose.tran.z,
|
||||
(unsigned long long)state.forward_flags, (unsigned long long)state.inverse_flags,
|
||||
(unsigned long long)hash, (unsigned long long)repeat_hash, stale_read_status,
|
||||
bad_status, count_status, singular_status, singular_feedback.state.kinematics_error,
|
||||
singular_feedback.state.valid);
|
||||
return 0;
|
||||
}
|
||||
57
wasm-core/tests/sim_kinematics_home_limit_test.c
Normal file
57
wasm-core/tests/sim_kinematics_home_limit_test.c
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "sim_kinematics_home_limit.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_kinematics_config config_for(void) {
|
||||
sim_kinematics_config config = {0};
|
||||
config.type = SIM_KIN_TRIVKINS;
|
||||
config.joint_count = 3;
|
||||
strcpy(config.coordinates, "XYZ");
|
||||
config.limit_enabled[0] = 1; config.joint_min[0] = -5.0; config.joint_max[0] = 5.0;
|
||||
config.limit_enabled[1] = 1; config.joint_min[1] = -5.0; config.joint_max[1] = 5.0;
|
||||
config.limit_enabled[2] = 1; config.joint_min[2] = -5.0; config.joint_max[2] = 5.0;
|
||||
return config;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics kinematics = {0};
|
||||
sim_kinematics_home_limit home_limit = {0};
|
||||
sim_kinematics_home_limit_state state = {0};
|
||||
sim_kinematics_config config = config_for();
|
||||
double nominal[3] = {1.0, 2.0, 3.0};
|
||||
double outside[3] = {6.0, 2.0, 3.0};
|
||||
double nonfinite[3] = {NAN, 2.0, 3.0};
|
||||
uint64_t hash;
|
||||
uint64_t repeat_hash;
|
||||
int before_home;
|
||||
int home_status;
|
||||
int limit_status;
|
||||
int nonfinite_status;
|
||||
int sequence_status;
|
||||
int count_status;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_home_limit_init(&home_limit, &kinematics, 3) == SIM_KIN_HOME_OK);
|
||||
before_home = sim_kinematics_home_limit_check(&home_limit, nominal, 3);
|
||||
sequence_status = sim_kinematics_home_limit_home(&home_limit, 0x10000u);
|
||||
home_status = sim_kinematics_home_limit_home(&home_limit, 7);
|
||||
assert(home_status == SIM_KIN_HOME_OK && home_limit.state.homed == 1 && home_limit.state.home_sequence == 7);
|
||||
limit_status = sim_kinematics_home_limit_check(&home_limit, outside, 3);
|
||||
assert(limit_status == SIM_KIN_HOME_ERR_LIMIT && home_limit.state.joints[0] == 0.0);
|
||||
nonfinite_status = sim_kinematics_home_limit_check(&home_limit, nonfinite, 3);
|
||||
count_status = sim_kinematics_home_limit_check(&home_limit, nominal, 2);
|
||||
assert(sim_kinematics_home_limit_check(&home_limit, nominal, 3) == SIM_KIN_HOME_OK);
|
||||
assert(sim_kinematics_home_limit_read(&home_limit, &state) == SIM_KIN_HOME_OK);
|
||||
hash = sim_kinematics_home_limit_state_hash(&home_limit);
|
||||
repeat_hash = sim_kinematics_home_limit_state_hash(&home_limit);
|
||||
assert(hash == repeat_hash && state.tick == 2);
|
||||
printf("{\"schema\":\"kinematics-home-limit-samples.v1\",\"before_home\":%d,\"sequence_error\":%d,\"home_status\":%d,\"limit_status\":%d,\"nonfinite_status\":%d,\"count_status\":%d,\"state\":{\"homed\":%u,\"sequence\":%u,\"tick\":%u,\"pose\":[%.9f,%.9f,%.9f],\"joints\":[%.9f,%.9f,%.9f],\"hash\":%llu,\"repeat_hash\":%llu}}\n",
|
||||
before_home, sequence_status, home_status, limit_status, nonfinite_status,
|
||||
count_status, state.homed, state.home_sequence, state.tick,
|
||||
state.pose.tran.x, state.pose.tran.y, state.pose.tran.z,
|
||||
state.joints[0], state.joints[1], state.joints[2],
|
||||
(unsigned long long)hash, (unsigned long long)repeat_hash);
|
||||
return 0;
|
||||
}
|
||||
107
wasm-core/tests/sim_kinematics_motion_test.c
Normal file
107
wasm-core/tests/sim_kinematics_motion_test.c
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "sim_kinematics_motion.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_posemath_segment line(double x0, double x1, double velocity) {
|
||||
sim_posemath_segment segment = {
|
||||
{{x0, 0, 0}, 0, 0, 0, 0, 0, 0},
|
||||
{{x1, 0, 0}, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0}, {0, 0, 1}, velocity, 10.0, SIM_POSEMATH_LINEAR, 0
|
||||
};
|
||||
return segment;
|
||||
}
|
||||
|
||||
static sim_kinematics_config config_for(int type, const char *coordinates) {
|
||||
sim_kinematics_config config = {0};
|
||||
config.type = type;
|
||||
config.joint_count = (uint32_t)strlen(coordinates);
|
||||
strcpy(config.coordinates, coordinates);
|
||||
return config;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics kinematics = {0};
|
||||
sim_kinematics singular_kins = {0};
|
||||
sim_kinematics_config config = config_for(SIM_KIN_TRIVKINS, "XYZ");
|
||||
sim_kinematics_motion adapter = {0};
|
||||
sim_kinematics_motion singular_adapter = {0};
|
||||
sim_kinematics_motion_sample sample = {0};
|
||||
sim_posemath_segment segment = line(0.0, 1.0, 1.0);
|
||||
uint32_t id = 0;
|
||||
uint32_t pause_tick;
|
||||
uint64_t nominal_hash;
|
||||
uint64_t nominal_repeat_hash;
|
||||
uint64_t full_planner_before;
|
||||
uint64_t full_planner_after;
|
||||
uint64_t full_hash;
|
||||
uint64_t singular_planner_before;
|
||||
uint64_t singular_planner_after;
|
||||
int pause_status;
|
||||
int resume_status;
|
||||
int full_status;
|
||||
int singular_status;
|
||||
int singular_kinematics_error;
|
||||
int nonfinite_status;
|
||||
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_motion_init(&adapter, &kinematics, 0.1, 10.0, 10.0) == SIM_KIN_MOTION_OK);
|
||||
assert(sim_kinematics_motion_enqueue(&adapter, &segment, &id) == SIM_KIN_MOTION_OK && id == 1);
|
||||
assert(sim_kinematics_motion_pause(&adapter, 1) == SIM_KIN_MOTION_OK);
|
||||
pause_tick = adapter.planner.tick;
|
||||
pause_status = sim_kinematics_motion_step(&adapter);
|
||||
assert(pause_status == SIM_KIN_MOTION_ERR_STATE && adapter.planner.tick == pause_tick && adapter.count == 0);
|
||||
assert(sim_kinematics_motion_pause(&adapter, 0) == SIM_KIN_MOTION_OK);
|
||||
resume_status = sim_kinematics_motion_step(&adapter);
|
||||
assert(resume_status == SIM_KIN_MOTION_OK);
|
||||
assert(sim_kinematics_motion_peek(&adapter, &sample) == SIM_KIN_MOTION_OK);
|
||||
assert(fabs(sample.pose.tran.x - 0.1) < 1e-12 && fabs(sample.joints[0] - 0.1) < 1e-12);
|
||||
nominal_hash = sim_kinematics_motion_state_hash(&adapter);
|
||||
nominal_repeat_hash = sim_kinematics_motion_state_hash(&adapter);
|
||||
assert(sim_kinematics_motion_pop(&adapter, &sample) == SIM_KIN_MOTION_OK);
|
||||
|
||||
assert(sim_kinematics_motion_reset(&adapter) == SIM_KIN_MOTION_OK);
|
||||
segment = line(0.0, 100.0, 1.0);
|
||||
assert(sim_kinematics_motion_enqueue(&adapter, &segment, 0) == SIM_KIN_MOTION_OK);
|
||||
for (uint32_t index = 0; index < SIM_KIN_MOTION_QUEUE_CAPACITY; ++index)
|
||||
assert(sim_kinematics_motion_step(&adapter) == SIM_KIN_MOTION_OK);
|
||||
full_planner_before = sim_tp_state_hash(&adapter.planner);
|
||||
full_status = sim_kinematics_motion_step(&adapter);
|
||||
full_planner_after = sim_tp_state_hash(&adapter.planner);
|
||||
full_hash = sim_kinematics_motion_state_hash(&adapter);
|
||||
assert(full_status == SIM_KIN_MOTION_ERR_CAPACITY);
|
||||
assert(full_planner_before == full_planner_after && adapter.count == SIM_KIN_MOTION_QUEUE_CAPACITY);
|
||||
|
||||
config = config_for(SIM_KIN_XYZBC_TRT, "XYZBC");
|
||||
assert(sim_kinematics_init(&singular_kins, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_motion_init(&singular_adapter, &singular_kins, 1.0, 10.0, 10.0) == SIM_KIN_MOTION_OK);
|
||||
segment = line(0.0, 1.0, 1.0);
|
||||
segment.end.b = 90.0;
|
||||
assert(sim_kinematics_motion_enqueue(&singular_adapter, &segment, 0) == SIM_KIN_MOTION_OK);
|
||||
singular_planner_before = sim_tp_state_hash(&singular_adapter.planner);
|
||||
singular_status = sim_kinematics_motion_step(&singular_adapter);
|
||||
singular_planner_after = sim_tp_state_hash(&singular_adapter.planner);
|
||||
singular_kinematics_error = singular_adapter.kinematics_error;
|
||||
assert(singular_status == SIM_KIN_MOTION_ERR_INVERSE);
|
||||
assert(singular_kinematics_error == SIM_KIN_ERR_SINGULAR);
|
||||
assert(singular_planner_before == singular_planner_after && singular_adapter.count == 0);
|
||||
|
||||
segment = line(0.0, 1.0, 1.0);
|
||||
segment.end.tran.x = NAN;
|
||||
nonfinite_status = sim_kinematics_motion_enqueue(&singular_adapter, &segment, 0);
|
||||
assert(nonfinite_status == SIM_KIN_MOTION_ERR_NONFINITE);
|
||||
|
||||
printf("{\"schema\":\"kinematics-motion-samples.v1\",\"nominal\":{\"pause_status\":%d,\"resume_status\":%d,\"tick\":%u,\"pose\":[%.9f,%.9f,%.9f],\"joints\":[%.9f,%.9f,%.9f],\"state_hash\":%llu,\"repeat_hash\":%llu},\"capacity\":{\"status\":%d,\"depth\":%u,\"planner_before\":%llu,\"planner_after\":%llu,\"state_hash\":%llu},\"inverse_failure\":{\"status\":%d,\"kinematics_error\":%d,\"depth\":%u,\"planner_before\":%llu,\"planner_after\":%llu},\"nonfinite_status\":%d}\n",
|
||||
pause_status, resume_status, sample.tick, sample.pose.tran.x,
|
||||
sample.pose.tran.y, sample.pose.tran.z, sample.joints[0],
|
||||
sample.joints[1], sample.joints[2],
|
||||
(unsigned long long)nominal_hash, (unsigned long long)nominal_repeat_hash,
|
||||
full_status, adapter.count, (unsigned long long)full_planner_before,
|
||||
(unsigned long long)full_planner_after, (unsigned long long)full_hash,
|
||||
singular_status, singular_kinematics_error, singular_adapter.count,
|
||||
(unsigned long long)singular_planner_before,
|
||||
(unsigned long long)singular_planner_after, nonfinite_status);
|
||||
return 0;
|
||||
}
|
||||
63
wasm-core/tests/sim_kinematics_registry_test.c
Normal file
63
wasm-core/tests/sim_kinematics_registry_test.c
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics_capability capability = {0};
|
||||
sim_kinematics_config config = {0};
|
||||
uint32_t count = sim_kinematics_registry_count();
|
||||
uint32_t verified = 0;
|
||||
uint64_t capability_union = 0;
|
||||
uint64_t parameter_union = 0;
|
||||
int lookup_status;
|
||||
int iterative_require;
|
||||
int capability_error;
|
||||
int parameter_error;
|
||||
int type_mismatch;
|
||||
int coordinate_error;
|
||||
int joint_error;
|
||||
int unknown_name;
|
||||
int path_name;
|
||||
int range_error;
|
||||
|
||||
assert(count == 18);
|
||||
printf("{\"schema\":\"kinematics-registry-samples.v1\",\"entries\":[");
|
||||
for (uint32_t index = 0; index < count; ++index) {
|
||||
assert(sim_kinematics_registry_get(index, &capability) == SIM_KIN_OK);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.type = capability.type;
|
||||
config.joint_count = capability.min_joints;
|
||||
strcpy(config.coordinates, capability.required_coordinates[0] == '\0' ? "X" : capability.required_coordinates);
|
||||
if (sim_kinematics_registry_validate_config(capability.name, &config, 0) == SIM_KIN_OK) verified += 1;
|
||||
capability_union |= capability.capabilities;
|
||||
parameter_union |= capability.parameter_mask;
|
||||
printf("%s{\"name\":\"%s\",\"type\":%d,\"required\":\"%s\",\"min\":%u,\"max\":%u,\"capabilities\":%u,\"parameters\":%llu}",
|
||||
index == 0 ? "" : ",", capability.name, capability.type,
|
||||
capability.required_coordinates, capability.min_joints,
|
||||
capability.max_joints, capability.capabilities,
|
||||
(unsigned long long)capability.parameter_mask);
|
||||
}
|
||||
lookup_status = sim_kinematics_registry_find("xyzac-trt-kins", &capability);
|
||||
iterative_require = sim_kinematics_registry_require("genserkins", SIM_KIN_CAP_ITERATIVE, SIM_KIN_PARAM_DH, &capability);
|
||||
capability_error = sim_kinematics_registry_require("trivkins", SIM_KIN_CAP_ITERATIVE, 0, 0);
|
||||
parameter_error = sim_kinematics_registry_require("trivkins", 0, SIM_KIN_PARAM_TOOL, 0);
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.type = SIM_KIN_TRIVKINS; config.joint_count = 5; strcpy(config.coordinates, "XYZAC");
|
||||
type_mismatch = sim_kinematics_registry_validate_config("xyzac-trt-kins", &config, 0);
|
||||
config.type = SIM_KIN_XYZAC_TRT; strcpy(config.coordinates, "XYZAQ");
|
||||
coordinate_error = sim_kinematics_registry_validate_config("xyzac-trt-kins", &config, 0);
|
||||
strcpy(config.coordinates, "XYZAC"); config.joint_count = 4;
|
||||
joint_error = sim_kinematics_registry_validate_config("xyzac-trt-kins", &config, 0);
|
||||
unknown_name = sim_kinematics_registry_find("missing-kins", &capability);
|
||||
path_name = sim_kinematics_registry_find("plugins/user.so", &capability);
|
||||
range_error = sim_kinematics_registry_get(count, &capability);
|
||||
printf("],\"count\":%u,\"verified\":%u,\"capability_union\":%llu,\"parameter_union\":%llu,\"registry_hash\":%llu,\"lookup_status\":%d,\"iterative_require\":%d,\"capability_error\":%d,\"parameter_error\":%d,\"type_mismatch\":%d,\"coordinate_error\":%d,\"joint_error\":%d,\"unknown_name\":%d,\"path_name\":%d,\"range_error\":%d}\n",
|
||||
count, verified, (unsigned long long)capability_union,
|
||||
(unsigned long long)parameter_union,
|
||||
(unsigned long long)sim_kinematics_registry_state_hash(), lookup_status,
|
||||
iterative_require, capability_error, parameter_error, type_mismatch,
|
||||
coordinate_error, joint_error, unknown_name, path_name, range_error);
|
||||
return 0;
|
||||
}
|
||||
@@ -32,6 +32,11 @@ static double canonical(double value) {
|
||||
return fabs(value) < 5e-10 ? 0.0 : value;
|
||||
}
|
||||
|
||||
static double canonical_pentapod(double value) {
|
||||
double rounded = nearbyint(value * 1000000.0) / 1000000.0;
|
||||
return fabs(rounded) < 0.0000005 ? 0.0 : rounded;
|
||||
}
|
||||
|
||||
static void emit_coupled_samples(void) {
|
||||
sim_kinematics kinematics = {0};
|
||||
sim_kinematics_config config;
|
||||
@@ -123,6 +128,11 @@ static void emit_coupled_samples(void) {
|
||||
uint32_t penta_branch;
|
||||
uint32_t penta_alt_branch;
|
||||
uint32_t penta_iterations;
|
||||
uint32_t penta_alt_iterations;
|
||||
uint32_t penta_nonpositive_iterations;
|
||||
uint32_t penta_iter_limit_iterations;
|
||||
uint32_t penta_max_error_iterations;
|
||||
uint32_t penta_singular_iterations;
|
||||
double penta_residual;
|
||||
sim_posemath_pose tripod_alt_pose = {0};
|
||||
sim_posemath_pose genhex_alt_pose = {0};
|
||||
@@ -341,21 +351,21 @@ static void emit_coupled_samples(void) {
|
||||
genhex_singular = sim_kinematics_forward(&kinematics, genhex_joints, 6, &pose);
|
||||
|
||||
config = config_for(SIM_KIN_PENTAPOD, "XYZAB");
|
||||
config.penta_initial_pose[0] = 0.0; config.penta_initial_pose[1] = 500.0; config.penta_initial_pose[2] = 300.0; config.penta_initial_pose[3] = 2.0 * 0.017453292519943295769; config.penta_initial_pose[4] = -3.0 * 0.017453292519943295769;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_inverse_ex(&kinematics, &(sim_posemath_pose){.tran = {0.0, 500.0, 300.0}, .a = 2.0, .b = -3.0}, 5, 0, output, &genser_info) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_inverse_ex(&kinematics, &(sim_posemath_pose){.tran = {0.0, 500.0, 300.0}, .a = 0.01, .b = -0.01}, 5, 0, output, &genser_info) == SIM_KIN_OK);
|
||||
memcpy(penta_joints, output, sizeof(penta_joints));
|
||||
assert(sim_kinematics_forward_ex(&kinematics, output, 5, 0, &pose, &genser_info) == SIM_KIN_OK);
|
||||
penta_pose[0] = canonical(pose.tran.x); penta_pose[1] = canonical(pose.tran.y); penta_pose[2] = canonical(pose.tran.z); penta_pose[3] = canonical(pose.a); penta_pose[4] = canonical(pose.b);
|
||||
penta_iterations = genser_info.iterations; penta_residual = genser_info.residual; penta_branch = genser_info.branch_id;
|
||||
penta_pose[0] = canonical_pentapod(pose.tran.x); penta_pose[1] = canonical_pentapod(pose.tran.y); penta_pose[2] = canonical_pentapod(pose.tran.z); penta_pose[3] = canonical_pentapod(pose.a); penta_pose[4] = canonical_pentapod(pose.b);
|
||||
pose.tran.x = penta_pose[0]; pose.tran.y = penta_pose[1]; pose.tran.z = penta_pose[2]; pose.a = penta_pose[3]; pose.b = penta_pose[4];
|
||||
penta_iterations = genser_info.iterations; penta_residual = nearbyint(genser_info.residual * 1000000000.0) / 1000000000.0; penta_branch = genser_info.branch_id;
|
||||
penta_hash = sim_kinematics_state_hash(&kinematics, output, 5, &pose);
|
||||
penta_alt_status = sim_kinematics_forward_ex(&kinematics, output, 5, SIM_KIN_GENHEX_ALTERNATE_SEED, &penta_alt_pose, &genser_info); penta_alt_branch = genser_info.branch_id;
|
||||
penta_nonpositive = sim_kinematics_forward(&kinematics, (double[]){0.0, 1.0, 1.0, 1.0, 1.0}, 5, &pose);
|
||||
penta_alt_status = sim_kinematics_forward_ex(&kinematics, output, 5, SIM_KIN_GENHEX_ALTERNATE_SEED, &penta_alt_pose, &genser_info); penta_alt_branch = genser_info.branch_id; penta_alt_iterations = genser_info.iterations;
|
||||
penta_nonpositive = sim_kinematics_forward_ex(&kinematics, (double[]){0.0, 1.0, 1.0, 1.0, 1.0}, 5, 0, &pose, &genser_info); penta_nonpositive_iterations = genser_info.iterations;
|
||||
config.penta_initial_pose[3] = 0.0; config.penta_initial_pose[4] = 0.0;
|
||||
config.penta_iteration_limit = 1; assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_iter_limit_error = sim_kinematics_forward(&kinematics, penta_joints, 5, &pose);
|
||||
config.penta_iteration_limit = 120; config.penta_max_error = 0.000001; assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_max_error_status = sim_kinematics_forward(&kinematics, penta_joints, 5, &pose);
|
||||
config.penta_iteration_limit = 1; assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_iter_limit_error = sim_kinematics_forward_ex(&kinematics, penta_joints, 5, 0, &pose, &genser_info); penta_iter_limit_iterations = genser_info.iterations;
|
||||
config.penta_iteration_limit = 120; config.penta_max_error = 0.000001; assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_max_error_status = sim_kinematics_forward_ex(&kinematics, penta_joints, 5, 0, &pose, &genser_info); penta_max_error_iterations = genser_info.iterations;
|
||||
config.penta_max_error = 1000.0; config.limit_enabled[0] = 1; config.joint_min[0] = 0.0; config.joint_max[0] = 1.0; assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_limit_error = sim_kinematics_inverse(&kinematics, &(sim_posemath_pose){.tran = {0.0, 500.0, 300.0}, .a = 2.0, .b = -3.0}, output, 5);
|
||||
config.limit_enabled[0] = 0; config.penta_base[0][0] = 1.0; config.penta_base[0][1] = 1.0; config.penta_base[0][2] = 1.0; for (int duplicate = 1; duplicate < 5; ++duplicate) memcpy(config.penta_base[duplicate], config.penta_base[0], sizeof(config.penta_base[0])); assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_singular = sim_kinematics_forward(&kinematics, penta_joints, 5, &pose);
|
||||
config.limit_enabled[0] = 0; config.penta_base[0][0] = 1.0; config.penta_base[0][1] = 1.0; config.penta_base[0][2] = 1.0; for (int duplicate = 1; duplicate < 5; ++duplicate) memcpy(config.penta_base[duplicate], config.penta_base[0], sizeof(config.penta_base[0])); assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK); penta_singular = sim_kinematics_forward_ex(&kinematics, penta_joints, 5, 0, &pose, &genser_info); penta_singular_iterations = genser_info.iterations;
|
||||
|
||||
printf("{\"schema\":\"kinematics-coupled-samples.v1\","
|
||||
"\"corexy\":{\"pose\":[%.9f,%.9f,%.9f],\"inverse\":[%.9f,%.9f,%.9f],\"state_hash\":%llu},"
|
||||
@@ -378,7 +388,7 @@ static void emit_coupled_samples(void) {
|
||||
"\"rotarydelta\":{\"alternate_status\":%d,\"alternate_branch\":%u,\"alternate_pose\":[%.9f,%.9f,%.9f],\"singular\":%d,\"geometry_error\":%d,\"branch\":%u,\"iterations\":%u},"
|
||||
"\"tripod\":{\"alternate_status\":%d,\"alternate_branch\":%u,\"alternate_pose\":[%.9f,%.9f,%.9f],\"no_solution\":%d,\"singular\":%d,\"limit_error\":%d,\"geometry_error\":%d,\"branch\":%u,\"iterations\":%u},"
|
||||
"\"genhex\":{\"alternate_status\":%d,\"alternate_branch\":%u,\"alternate_pose\":[%.9f,%.9f,%.9f],\"nonpositive\":%d,\"iteration_limit\":%d,\"max_error\":%d,\"singular\":%d,\"limit_error\":%d,\"branch\":%u,\"iterations\":%u,\"residual\":%.12f},"
|
||||
"\"pentapod\":{\"alternate_status\":%d,\"alternate_branch\":%u,\"alternate_pose\":[%.9f,%.9f,%.9f],\"nonpositive\":%d,\"iteration_limit\":%d,\"max_error\":%d,\"singular\":%d,\"limit_error\":%d,\"branch\":%u,\"iterations\":%u,\"residual\":%.12f}}}\n",
|
||||
"\"pentapod\":{\"alternate_status\":%d,\"alternate_branch\":%u,\"alternate_pose\":[%.9f,%.9f,%.9f],\"alternate_iterations\":%u,\"nonpositive\":%d,\"nonpositive_iterations\":%u,\"iteration_limit\":%d,\"iteration_limit_iterations\":%u,\"max_error\":%d,\"max_error_iterations\":%u,\"singular\":%d,\"singular_iterations\":%u,\"limit_error\":%d,\"branch\":%u,\"iterations\":%u,\"residual\":%.12f}}}\n",
|
||||
corexy_pose[0], corexy_pose[1], corexy_pose[2], corexy_joints[0], corexy_joints[1], corexy_joints[2], (unsigned long long)corexy_hash,
|
||||
rose_pose[0], rose_pose[1], rose_pose[2], rose_joints[0], rose_joints[1], rose_joints[2], rose_branch, (unsigned long long)rose_hash,
|
||||
max_pose[0], max_pose[1], max_pose[2], max_joints[0], max_joints[1], max_joints[2], max_joints[3], max_joints[4], max_joints[5], (unsigned long long)max_hash,
|
||||
@@ -399,7 +409,7 @@ static void emit_coupled_samples(void) {
|
||||
rdelta_alt_status, rdelta_alt_branch, rdelta_alt_pose.tran.x, rdelta_alt_pose.tran.y, rdelta_alt_pose.tran.z, rdelta_singular, rdelta_geometry_status, rdelta_branch, rdelta_iterations,
|
||||
tripod_alt_status, tripod_alt_branch, tripod_alt_pose.tran.x, tripod_alt_pose.tran.y, tripod_alt_pose.tran.z, tripod_no_solution, tripod_singular, tripod_limit_error, tripod_geometry_status, tripod_branch, delta_info.iterations,
|
||||
genhex_alt_status, genhex_alt_branch, genhex_alt_pose.tran.x, genhex_alt_pose.tran.y, genhex_alt_pose.tran.z, genhex_nonpositive, genhex_iter_limit_error, genhex_max_error_status, genhex_singular, genhex_limit_error, genhex_branch, genhex_iterations, genhex_residual,
|
||||
penta_alt_status, penta_alt_branch, penta_alt_pose.tran.x, penta_alt_pose.tran.y, penta_alt_pose.tran.z, penta_nonpositive, penta_iter_limit_error, penta_max_error_status, penta_singular, penta_limit_error, penta_branch, penta_iterations, penta_residual);
|
||||
penta_alt_status, penta_alt_branch, penta_alt_pose.tran.x, penta_alt_pose.tran.y, penta_alt_pose.tran.z, penta_alt_iterations, penta_nonpositive, penta_nonpositive_iterations, penta_iter_limit_error, penta_iter_limit_iterations, penta_max_error_status, penta_max_error_iterations, penta_singular, penta_singular_iterations, penta_limit_error, penta_branch, penta_iterations, penta_residual);
|
||||
}
|
||||
|
||||
int sim_kinematics_test_run(void) {
|
||||
@@ -580,6 +590,26 @@ int sim_kinematics_test_run(void) {
|
||||
config.dh_alpha[0] = NAN;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_ERR_NONFINITE);
|
||||
|
||||
/* Pentapod owns XYZAB only: yaw is not mapped, and LinuxCNC applies the
|
||||
* tool offset to both base and effector HAL geometry. */
|
||||
config = config_for(SIM_KIN_PENTAPOD, "XYZAB");
|
||||
config.penta_initial_pose[3] = 2.0 * 0.017453292519943295769;
|
||||
config.penta_initial_pose[4] = -3.0 * 0.017453292519943295769;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
pose = (sim_posemath_pose){.tran = {0.0, 500.0, 300.0}, .a = 2.0, .b = -3.0, .c = 17.0};
|
||||
assert(sim_kinematics_inverse(&kinematics, &pose, joints, 5) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_forward(&kinematics, joints, 5, &pose) == SIM_KIN_OK);
|
||||
assert(fabs(pose.c) < 1e-12);
|
||||
config.penta_tool_offset = 25.0;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_inverse(&kinematics, &(sim_posemath_pose){.tran = {0.0, 500.0, 300.0}, .a = 2.0, .b = -3.0}, joints, 5) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_forward(&kinematics, joints, 5, &pose) == SIM_KIN_OK);
|
||||
assert(fabs(pose.tran.z - 300.0) < 1e-9);
|
||||
config.penta_tool_offset = NAN;
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_ERR_NONFINITE);
|
||||
config = config_for(SIM_KIN_PENTAPOD, "XYZABC");
|
||||
assert(sim_kinematics_init(&kinematics, &config) == SIM_KIN_ERR_CONFIGURATION);
|
||||
|
||||
emit_coupled_samples();
|
||||
return 0;
|
||||
}
|
||||
|
||||
85
wasm-core/tests/sim_switchkins_test.c
Normal file
85
wasm-core/tests/sim_switchkins_test.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_kinematics_config config_for(int type, const char *coordinates) {
|
||||
sim_kinematics_config config = {0};
|
||||
config.type = type;
|
||||
config.joint_count = (uint32_t)strlen(coordinates);
|
||||
strncpy(config.coordinates, coordinates, sizeof(config.coordinates) - 1);
|
||||
return config;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics identity = {0};
|
||||
sim_kinematics trt = {0};
|
||||
sim_kinematics_config config;
|
||||
sim_kinematics_switch switcher = {0};
|
||||
sim_kinematics_result_info info = {0};
|
||||
sim_posemath_pose initial = {0};
|
||||
sim_posemath_pose switched = {0};
|
||||
sim_posemath_pose returned = {0};
|
||||
double joints[5] = {10.0, 20.0, 30.0, 5.0, 15.0};
|
||||
uint64_t initial_hash;
|
||||
uint64_t switched_hash;
|
||||
uint64_t returned_hash;
|
||||
uint64_t returned_repeat_hash;
|
||||
int forward_status;
|
||||
int select_to_trt;
|
||||
int select_to_identity;
|
||||
int invalid_mode;
|
||||
int missing_module;
|
||||
int register_missing;
|
||||
int begin_status;
|
||||
int interrupted_begin;
|
||||
int abort_status;
|
||||
int interrupted_commit;
|
||||
|
||||
config = config_for(SIM_KIN_TRIVKINS, "XYZAC");
|
||||
assert(sim_kinematics_init(&identity, &config) == SIM_KIN_OK);
|
||||
config = config_for(SIM_KIN_XYZAC_TRT, "XYZAC");
|
||||
assert(sim_kinematics_init(&trt, &config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_switch_init(&switcher, 0, 0) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_switch_register(&switcher, 0, &identity, 0) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_switch_register(&switcher, 1, &trt, 0) == SIM_KIN_OK);
|
||||
register_missing = sim_kinematics_switch_register(&switcher, 2, 0, 0);
|
||||
forward_status = sim_kinematics_switch_forward(&switcher, joints, 5, &initial, &info);
|
||||
assert(forward_status == SIM_KIN_OK);
|
||||
initial_hash = sim_kinematics_switch_state_hash(&switcher, joints, 5, &initial);
|
||||
select_to_trt = sim_kinematics_switch_select(&switcher, 1, joints, 5, &switched, &info);
|
||||
assert(select_to_trt == SIM_KIN_OK);
|
||||
assert(fabs(switched.tran.x - initial.tran.x) < 1e-9);
|
||||
assert(fabs(switched.tran.y - initial.tran.y) < 1e-9);
|
||||
assert(fabs(switched.tran.z - initial.tran.z) < 1e-9);
|
||||
switched_hash = sim_kinematics_switch_state_hash(&switcher, joints, 5, &switched);
|
||||
select_to_identity = sim_kinematics_switch_select(&switcher, 0, joints, 5, &returned, &info);
|
||||
assert(select_to_identity == SIM_KIN_OK);
|
||||
assert(fabs(returned.tran.x - initial.tran.x) < 1e-9);
|
||||
assert(fabs(returned.tran.y - initial.tran.y) < 1e-9);
|
||||
assert(fabs(returned.tran.z - initial.tran.z) < 1e-9);
|
||||
returned_hash = sim_kinematics_switch_state_hash(&switcher, joints, 5, &returned);
|
||||
returned_repeat_hash = sim_kinematics_switch_state_hash(&switcher, joints, 5, &returned);
|
||||
invalid_mode = sim_kinematics_switch_select(&switcher, 3, joints, 5, &returned, &info);
|
||||
missing_module = sim_kinematics_switch_select(&switcher, 2, joints, 5, &returned, &info);
|
||||
begin_status = sim_kinematics_switch_begin(&switcher, 1);
|
||||
interrupted_begin = sim_kinematics_switch_begin(&switcher, 0);
|
||||
abort_status = sim_kinematics_switch_abort(&switcher);
|
||||
assert(sim_kinematics_switch_begin(&switcher, 1) == SIM_KIN_OK);
|
||||
abort_status = sim_kinematics_switch_abort(&switcher);
|
||||
interrupted_commit = sim_kinematics_switch_commit(&switcher, joints, 5, &returned, &info);
|
||||
assert(sim_kinematics_switch_active(&switcher) == 0);
|
||||
|
||||
printf("{\"schema\":\"switchkins-samples.v1\",\"registered\":[%u,%u,%u],\"initial_mode\":0,\"active_mode\":%d,\"forward_status\":%d,\"switch_to_trt\":%d,\"switch_to_identity\":%d,\"initial_pose\":[%.9f,%.9f,%.9f,%.9f,%.9f,%.9f],\"switched_pose\":[%.9f,%.9f,%.9f,%.9f,%.9f,%.9f],\"returned_pose\":[%.9f,%.9f,%.9f,%.9f,%.9f,%.9f],\"initial_hash\":%llu,\"switched_hash\":%llu,\"returned_hash\":%llu,\"returned_repeat_hash\":%llu,\"invalid_mode\":%d,\"missing_module\":%d,\"register_missing\":%d,\"begin_status\":%d,\"interrupted_begin\":%d,\"abort_status\":%d,\"interrupted_commit\":%d,\"generation\":%u,\"switch_count\":%u,\"abort_count\":%u}\n",
|
||||
switcher.registered[0], switcher.registered[1], switcher.registered[2], switcher.active_mode,
|
||||
forward_status, select_to_trt, select_to_identity,
|
||||
initial.tran.x, initial.tran.y, initial.tran.z, initial.a, initial.b, initial.c,
|
||||
switched.tran.x, switched.tran.y, switched.tran.z, switched.a, switched.b, switched.c,
|
||||
returned.tran.x, returned.tran.y, returned.tran.z, returned.a, returned.b, returned.c,
|
||||
(unsigned long long)initial_hash, (unsigned long long)switched_hash, (unsigned long long)returned_hash, (unsigned long long)returned_repeat_hash,
|
||||
invalid_mode, missing_module, register_missing, begin_status, interrupted_begin, abort_status,
|
||||
interrupted_commit, switcher.generation, switcher.switch_count, switcher.abort_count);
|
||||
return 0;
|
||||
}
|
||||
126
wasm-core/tests/sim_userkins_test.c
Normal file
126
wasm-core/tests/sim_userkins_test.c
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "sim_kinematics.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static sim_kinematics_config config_for(const char *coordinates) {
|
||||
sim_kinematics_config config = {0};
|
||||
config.joint_count = (uint32_t)strlen(coordinates);
|
||||
strncpy(config.coordinates, coordinates, sizeof(config.coordinates) - 1);
|
||||
return config;
|
||||
}
|
||||
|
||||
static sim_kinematics_user_descriptor identity_descriptor(void) {
|
||||
sim_kinematics_user_descriptor descriptor = {0};
|
||||
descriptor.abi_version = SIM_KIN_USER_ABI_VERSION;
|
||||
descriptor.capabilities = SIM_KIN_USER_CAP_FORWARD | SIM_KIN_USER_CAP_INVERSE |
|
||||
SIM_KIN_USER_CAP_STATIC;
|
||||
descriptor.implementation = SIM_KIN_USER_IDENTITY;
|
||||
strcpy(descriptor.name, "userk.identity");
|
||||
descriptor.config = config_for("XYZAC");
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
static sim_kinematics_user_descriptor genser_descriptor(void) {
|
||||
sim_kinematics_user_descriptor descriptor = {0};
|
||||
descriptor.abi_version = SIM_KIN_USER_ABI_VERSION;
|
||||
descriptor.capabilities = SIM_KIN_USER_CAP_FORWARD | SIM_KIN_USER_CAP_INVERSE |
|
||||
SIM_KIN_USER_CAP_STATIC;
|
||||
descriptor.implementation = SIM_KIN_USER_GENSER;
|
||||
strcpy(descriptor.name, "ugenser.static");
|
||||
descriptor.config = config_for("XYZABC");
|
||||
descriptor.config.dh_joint_count = 6;
|
||||
descriptor.config.max_iterations = 200;
|
||||
descriptor.config.convergence_tolerance = 1e-7;
|
||||
descriptor.config.dh_a[1] = 85.0;
|
||||
descriptor.config.dh_a[2] = 380.0;
|
||||
descriptor.config.dh_a[3] = 100.0;
|
||||
descriptor.config.dh_alpha[1] = -1.5707963267948966;
|
||||
descriptor.config.dh_alpha[3] = -1.5707963267948966;
|
||||
descriptor.config.dh_alpha[4] = 1.5707963267948966;
|
||||
descriptor.config.dh_alpha[5] = -1.5707963267948966;
|
||||
descriptor.config.dh_d[0] = 350.0;
|
||||
descriptor.config.dh_d[3] = 425.0;
|
||||
descriptor.config.dh_d[5] = 235.0;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sim_kinematics_user identity = {0};
|
||||
sim_kinematics_user genser = {0};
|
||||
sim_kinematics_user uninitialized = {0};
|
||||
sim_kinematics_user_descriptor descriptor = identity_descriptor();
|
||||
sim_kinematics_result_info info = {0};
|
||||
sim_kinematics base = {0};
|
||||
sim_kinematics_config base_config = config_for("XYZAC");
|
||||
sim_kinematics_switch switcher = {0};
|
||||
sim_posemath_pose identity_pose = {0};
|
||||
sim_posemath_pose genser_pose = {0};
|
||||
double identity_joints[5] = {10.0, 20.0, 30.0, 5.0, 15.0};
|
||||
double identity_roundtrip[5] = {0};
|
||||
double genser_joints[6] = {0.0, 10.0, -20.0, 30.0, 15.0, -10.0};
|
||||
double genser_roundtrip[6] = {0};
|
||||
uint64_t identity_hash;
|
||||
uint64_t identity_repeat_hash;
|
||||
uint64_t genser_hash;
|
||||
uint64_t genser_repeat_hash;
|
||||
int switch_to_user;
|
||||
int abi_error;
|
||||
int capability_error;
|
||||
int path_name_error;
|
||||
int missing_implementation;
|
||||
int dynamic_path_error;
|
||||
int uninitialized_error;
|
||||
|
||||
assert(sim_kinematics_user_init(&identity, &descriptor) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_forward(&identity, identity_joints, 5, &identity_pose, &info) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_inverse(&identity, &identity_pose, identity_roundtrip, 5, &info) == SIM_KIN_OK);
|
||||
assert(memcmp(identity_joints, identity_roundtrip, sizeof(identity_joints)) == 0);
|
||||
identity_hash = sim_kinematics_user_state_hash(&identity, identity_roundtrip, 5, &identity_pose);
|
||||
identity_repeat_hash = sim_kinematics_user_state_hash(&identity, identity_roundtrip, 5, &identity_pose);
|
||||
|
||||
base_config.type = SIM_KIN_TRIVKINS;
|
||||
assert(sim_kinematics_init(&base, &base_config) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_switch_init(&switcher, 0, 0) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_switch_register(&switcher, 0, &base, 0) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_bind_switch(&identity, &switcher, 2, 0) == SIM_KIN_OK);
|
||||
switch_to_user = sim_kinematics_switch_select(&switcher, 2, identity_roundtrip, 5, &identity_pose, &info);
|
||||
assert(switch_to_user == SIM_KIN_OK && sim_kinematics_switch_active(&switcher) == 2);
|
||||
|
||||
descriptor = genser_descriptor();
|
||||
assert(sim_kinematics_user_init(&genser, &descriptor) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_forward(&genser, genser_joints, 6, &genser_pose, &info) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_inverse(&genser, &genser_pose, genser_roundtrip, 6, &info) == SIM_KIN_OK);
|
||||
assert(sim_kinematics_user_forward(&genser, genser_roundtrip, 6, &genser_pose, &info) == SIM_KIN_OK);
|
||||
genser_hash = sim_kinematics_user_state_hash(&genser, genser_roundtrip, 6, &genser_pose);
|
||||
genser_repeat_hash = sim_kinematics_user_state_hash(&genser, genser_roundtrip, 6, &genser_pose);
|
||||
|
||||
descriptor = identity_descriptor();
|
||||
descriptor.abi_version += 1;
|
||||
abi_error = sim_kinematics_user_init(&uninitialized, &descriptor);
|
||||
descriptor = identity_descriptor();
|
||||
descriptor.capabilities &= ~SIM_KIN_USER_CAP_STATIC;
|
||||
capability_error = sim_kinematics_user_init(&uninitialized, &descriptor);
|
||||
descriptor = identity_descriptor();
|
||||
strcpy(descriptor.name, "plugins/user.so");
|
||||
path_name_error = sim_kinematics_user_init(&uninitialized, &descriptor);
|
||||
descriptor = identity_descriptor();
|
||||
descriptor.implementation = 99;
|
||||
missing_implementation = sim_kinematics_user_init(&uninitialized, &descriptor);
|
||||
dynamic_path_error = sim_kinematics_user_reject_dynamic("/tmp/userkins.so");
|
||||
uninitialized_error = sim_kinematics_user_forward(&uninitialized, identity_joints, 5, &identity_pose, &info);
|
||||
|
||||
printf("{\"schema\":\"userkins-samples.v1\",\"abi_version\":%u,\"capabilities\":%u,\"identity\":{\"pose\":[%.9f,%.9f,%.9f,%.9f,%.9f],\"joints\":[%.9f,%.9f,%.9f,%.9f,%.9f],\"forward_calls\":%u,\"inverse_calls\":%u,\"state_hash\":%llu,\"repeat_hash\":%llu},\"ugenser\":{\"pose\":[%.9f,%.9f,%.9f,%.9f,%.9f,%.9f],\"forward_calls\":%u,\"inverse_calls\":%u,\"iterations\":%u,\"residual\":%.12f,\"state_hash\":%llu,\"repeat_hash\":%llu},\"switch_to_user\":%d,\"active_mode\":%d,\"abi_error\":%d,\"capability_error\":%d,\"path_name_error\":%d,\"missing_implementation\":%d,\"dynamic_path_error\":%d,\"uninitialized_error\":%d}\n",
|
||||
SIM_KIN_USER_ABI_VERSION, identity.descriptor.capabilities,
|
||||
identity_pose.tran.x, identity_pose.tran.y, identity_pose.tran.z, identity_pose.a, identity_pose.c,
|
||||
identity_roundtrip[0], identity_roundtrip[1], identity_roundtrip[2], identity_roundtrip[3], identity_roundtrip[4],
|
||||
identity.forward_calls, identity.inverse_calls, (unsigned long long)identity_hash, (unsigned long long)identity_repeat_hash,
|
||||
genser_pose.tran.x, genser_pose.tran.y, genser_pose.tran.z, genser_pose.a, genser_pose.b, genser_pose.c,
|
||||
genser.forward_calls, genser.inverse_calls, info.iterations, info.residual,
|
||||
(unsigned long long)genser_hash, (unsigned long long)genser_repeat_hash,
|
||||
switch_to_user, sim_kinematics_switch_active(&switcher), abi_error, capability_error,
|
||||
path_name_error, missing_implementation, dynamic_path_error, uninitialized_error);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
const EVENT_SCHEMA = "event-log.v1";
|
||||
const SNAPSHOT_SCHEMA = "snapshot.v1";
|
||||
const REPLAY_SCHEMA = "replay-timeline.v1";
|
||||
|
||||
export function canonicalJson(value) {
|
||||
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
||||
if (value === null) return "null";
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isFinite(value)) throw new Error("canonical_nonfinite");
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (typeof value !== "object") {
|
||||
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") throw new Error("canonical_unsupported");
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
||||
}
|
||||
@@ -21,6 +30,15 @@ function requireInteger(value, name) {
|
||||
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`invalid_${name}`);
|
||||
}
|
||||
|
||||
function requireSequence(value, name) {
|
||||
requireInteger(value, name);
|
||||
if (value === 0) throw new Error(`invalid_${name}`);
|
||||
}
|
||||
|
||||
function clone(value) {
|
||||
return value === undefined ? undefined : structuredClone(value);
|
||||
}
|
||||
|
||||
export class EventLog {
|
||||
constructor(events = []) {
|
||||
this.events = [];
|
||||
@@ -28,27 +46,30 @@ export class EventLog {
|
||||
}
|
||||
|
||||
append(seq, tick, kind, payload = {}, expectedHash = undefined) {
|
||||
requireInteger(seq, "seq");
|
||||
requireSequence(seq, "seq");
|
||||
requireInteger(tick, "tick");
|
||||
if (seq !== this.cursor + 1) throw new Error("event_sequence_gap");
|
||||
if (tick < this.tick) throw new Error("event_tick_regression");
|
||||
if (typeof kind !== "string" || kind.length === 0 || kind.length > 64) throw new Error("event_kind_invalid");
|
||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("event_payload_invalid");
|
||||
const payloadHash = fnv1a64(payload);
|
||||
if (expectedHash !== undefined && expectedHash !== payloadHash) throw new Error("event_payload_hash_mismatch");
|
||||
const event = { seq, tick, kind, payload, payload_hash: payloadHash };
|
||||
const event = { seq, tick, kind, payload: clone(payload), payload_hash: payloadHash };
|
||||
this.events.push(event);
|
||||
return event;
|
||||
return clone(event);
|
||||
}
|
||||
|
||||
get cursor() { return this.events.length === 0 ? 0 : this.events[this.events.length - 1].seq; }
|
||||
get tick() { return this.events.length === 0 ? 0 : this.events[this.events.length - 1].tick; }
|
||||
|
||||
export() {
|
||||
return { schema: EVENT_SCHEMA, cursor: this.cursor, tick: this.tick, events: this.events.map((event) => ({ ...event })) };
|
||||
return { schema: EVENT_SCHEMA, cursor: this.cursor, tick: this.tick, events: clone(this.events) };
|
||||
}
|
||||
|
||||
static import(value) {
|
||||
if (!value || value.schema !== EVENT_SCHEMA || !Array.isArray(value.events)) throw new Error("event_log_schema");
|
||||
requireInteger(value.cursor, "event_cursor");
|
||||
requireInteger(value.tick, "event_tick");
|
||||
const log = new EventLog(value.events);
|
||||
if (log.cursor !== value.cursor || log.tick !== value.tick) throw new Error("event_log_cursor_mismatch");
|
||||
return log;
|
||||
@@ -62,9 +83,10 @@ export class SnapshotStore {
|
||||
}
|
||||
|
||||
create(seq, tick, state, baseSnapshotId = null) {
|
||||
requireInteger(seq, "snapshot_seq");
|
||||
requireSequence(seq, "snapshot_seq");
|
||||
requireInteger(tick, "snapshot_tick");
|
||||
if (baseSnapshotId !== null && !this.snapshots.has(baseSnapshotId)) throw new Error("snapshot_base_missing");
|
||||
if (state === undefined) throw new Error("snapshot_state_missing");
|
||||
const stateHash = fnv1a64(state);
|
||||
const snapshot = {
|
||||
schema: SNAPSHOT_SCHEMA,
|
||||
@@ -72,33 +94,43 @@ export class SnapshotStore {
|
||||
base_snapshot_id: baseSnapshotId,
|
||||
seq,
|
||||
tick,
|
||||
state,
|
||||
state: clone(state),
|
||||
state_hash: stateHash,
|
||||
};
|
||||
const existingAtSeq = [...this.snapshots.values()].find((candidate) => candidate.seq === seq);
|
||||
if (existingAtSeq && existingAtSeq.snapshot_id !== snapshot.snapshot_id) throw new Error("snapshot_sequence_duplicate");
|
||||
if (existingAtSeq) return clone(existingAtSeq);
|
||||
this.snapshots.set(snapshot.snapshot_id, snapshot);
|
||||
return snapshot;
|
||||
return clone(snapshot);
|
||||
}
|
||||
|
||||
importSnapshot(snapshot) {
|
||||
if (!snapshot || snapshot.schema !== SNAPSHOT_SCHEMA || typeof snapshot.snapshot_id !== "string") throw new Error("snapshot_schema");
|
||||
requireSequence(snapshot.seq, "snapshot_seq");
|
||||
requireInteger(snapshot.tick, "snapshot_tick");
|
||||
if (snapshot.base_snapshot_id !== null && typeof snapshot.base_snapshot_id !== "string") throw new Error("snapshot_base_invalid");
|
||||
if (snapshot.base_snapshot_id !== null && !this.snapshots.has(snapshot.base_snapshot_id)) throw new Error("snapshot_base_missing");
|
||||
if (fnv1a64(snapshot.state) !== snapshot.state_hash) throw new Error("snapshot_hash_mismatch");
|
||||
if (snapshot.snapshot_id !== `snap:${snapshot.seq}:${snapshot.state_hash}`) throw new Error("snapshot_id_mismatch");
|
||||
this.snapshots.set(snapshot.snapshot_id, { ...snapshot });
|
||||
return snapshot;
|
||||
if (this.snapshots.has(snapshot.snapshot_id)) throw new Error("snapshot_duplicate");
|
||||
if ([...this.snapshots.values()].some((candidate) => candidate.seq === snapshot.seq)) throw new Error("snapshot_sequence_duplicate");
|
||||
this.snapshots.set(snapshot.snapshot_id, clone(snapshot));
|
||||
return clone(snapshot);
|
||||
}
|
||||
|
||||
latest() {
|
||||
return this.snapshots.size === 0 ? null : [...this.snapshots.values()][this.snapshots.size - 1];
|
||||
if (this.snapshots.size === 0) return null;
|
||||
return clone([...this.snapshots.values()].sort((left, right) => left.seq - right.seq || left.tick - right.tick || left.snapshot_id.localeCompare(right.snapshot_id)).at(-1));
|
||||
}
|
||||
|
||||
export() {
|
||||
return { schema: SNAPSHOT_SCHEMA, snapshots: [...this.snapshots.values()].map((snapshot) => ({ ...snapshot })) };
|
||||
return { schema: SNAPSHOT_SCHEMA, snapshots: clone([...this.snapshots.values()].sort((left, right) => left.seq - right.seq || left.tick - right.tick || left.snapshot_id.localeCompare(right.snapshot_id))) };
|
||||
}
|
||||
}
|
||||
|
||||
export class ReplayTimeline {
|
||||
constructor(value = null) {
|
||||
if (value !== null && (!value || value.schema !== REPLAY_SCHEMA || !value.events || !value.snapshots)) throw new Error("replay_schema");
|
||||
this.events = value ? EventLog.import(value.events) : new EventLog();
|
||||
this.snapshots = value ? new SnapshotStore(value.snapshots.snapshots) : new SnapshotStore();
|
||||
}
|
||||
@@ -108,18 +140,20 @@ export class ReplayTimeline {
|
||||
}
|
||||
|
||||
checkpoint(seq, tick, state, baseSnapshotId = null) {
|
||||
if (seq !== this.events.cursor || tick !== this.events.tick) throw new Error("checkpoint_cursor_mismatch");
|
||||
return this.snapshots.create(seq, tick, state, baseSnapshotId);
|
||||
}
|
||||
|
||||
replay(initialState, apply) {
|
||||
let state = initialState;
|
||||
for (const event of this.events.events) state = apply(state, event);
|
||||
if (typeof apply !== "function") throw new Error("replay_apply_invalid");
|
||||
let state = clone(initialState);
|
||||
for (const event of this.events.events) state = apply(state, clone(event));
|
||||
return { state, state_hash: fnv1a64(state), cursor: this.events.cursor, tick: this.events.tick };
|
||||
}
|
||||
|
||||
export() {
|
||||
return { events: this.events.export(), snapshots: this.snapshots.export() };
|
||||
return { schema: REPLAY_SCHEMA, events: this.events.export(), snapshots: this.snapshots.export() };
|
||||
}
|
||||
}
|
||||
|
||||
export { EVENT_SCHEMA, SNAPSHOT_SCHEMA };
|
||||
export { EVENT_SCHEMA, SNAPSHOT_SCHEMA, REPLAY_SCHEMA };
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { ReplayTimeline, fnv1a64 } from "./replay.mjs";
|
||||
import { capabilities, validateLimit } from "./capabilities.mjs";
|
||||
import { mapCoreError } from "./error-map.mjs";
|
||||
import { createCheckpoint, recoverSimulation } from "../workspace/recovery.mjs";
|
||||
|
||||
const PROTOCOL_SCHEMA = 1;
|
||||
const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m0-c1";
|
||||
const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
|
||||
|
||||
const OPERATION_KINDS = new Set([
|
||||
"LOAD_WORKSPACE", "SIM_STEP", "DESTROY", "CHECKPOINT", "RECOVER", "ABORT",
|
||||
"COMMAND", "START", "STOP", "RESET", "PROGRAM_LOAD", "PROGRAM_RUN",
|
||||
"PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT", "JOG_START", "JOG_STOP",
|
||||
"HOME", "SET_MODE", "SET_OVERRIDE", "SPINDLE_COMMAND", "TOOL_PREPARE",
|
||||
"TOOL_CHANGE", "PROBE_CONFIG", "HAL_COMMAND", "HAL_SET",
|
||||
]);
|
||||
|
||||
function text(value) {
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
@@ -42,6 +51,7 @@ export class SimSession {
|
||||
this.tick = 0;
|
||||
this.created = false;
|
||||
this.timeline = new ReplayTimeline();
|
||||
this.operations = new Map();
|
||||
call(core, "sim_protocol_reset");
|
||||
if (typeof core.ccall === "function") core.ccall("sim_event_reset", null, [], []);
|
||||
}
|
||||
@@ -75,6 +85,60 @@ export class SimSession {
|
||||
return this.envelope("ERROR", input, mapped);
|
||||
}
|
||||
|
||||
operationState(input) {
|
||||
if (!text(input.operation_id) || !OPERATION_KINDS.has(input.kind)) return null;
|
||||
return this.operations.get(input.operation_id) ?? null;
|
||||
}
|
||||
|
||||
stateForCheckpoint() {
|
||||
return {
|
||||
context_id: this.contextId,
|
||||
session_id: this.sessionId,
|
||||
tick: this.tick,
|
||||
created: this.created,
|
||||
protocol_seq: this.timeline.events.cursor,
|
||||
};
|
||||
}
|
||||
|
||||
createCheckpoint() {
|
||||
const latest = this.timeline.snapshots.latest();
|
||||
if (!latest || latest.seq !== this.timeline.events.cursor || latest.tick !== this.timeline.events.tick) {
|
||||
this.timeline.checkpoint(
|
||||
this.timeline.events.cursor,
|
||||
this.timeline.events.tick,
|
||||
this.stateForCheckpoint(),
|
||||
latest?.snapshot_id ?? null,
|
||||
);
|
||||
}
|
||||
return createCheckpoint({
|
||||
contextId: this.contextId,
|
||||
timeline: this.timeline,
|
||||
state: this.stateForCheckpoint(),
|
||||
operations: [...this.operations.values()],
|
||||
});
|
||||
}
|
||||
|
||||
restoreCheckpoint(checkpoint, events = null, reconcile = undefined) {
|
||||
const result = recoverSimulation({
|
||||
checkpoint,
|
||||
contextId: this.contextId,
|
||||
events,
|
||||
reconcile,
|
||||
apply: (state, event) => ({ ...state, tick: event.tick }),
|
||||
});
|
||||
if (result.status === "recovered" || result.status === "aborted") {
|
||||
this.tick = result.simulation_tick;
|
||||
this.timeline = new ReplayTimeline(checkpoint.timeline);
|
||||
if (events?.schema === "replay-timeline.v1") {
|
||||
for (const event of events.events.events.slice(this.timeline.events.cursor)) this.timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash);
|
||||
} else if (Array.isArray(events)) {
|
||||
for (const event of events) this.timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash);
|
||||
}
|
||||
this.operations = new Map(result.operations.map((operation) => [operation.operation_id, operation]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
validateEnvelope(input) {
|
||||
if (!input || typeof input !== "object") return ["protocol_argument", "invalid_envelope"];
|
||||
if (input.schema_version !== PROTOCOL_SCHEMA) return ["protocol_schema", "unsupported_schema"];
|
||||
@@ -90,53 +154,106 @@ export class SimSession {
|
||||
if (invalid) return this.error(input ?? {}, invalid[0], invalid[1]);
|
||||
const result = call(this.core, "sim_protocol_accept", input.schema_version, input.seq, input.kind, input.operation_id ?? "", input.simulation_tick ?? this.tick);
|
||||
if (result !== 0) return this.coreError(input, result, "sim_protocol_accept");
|
||||
const existingOperation = this.operationState(input);
|
||||
if (existingOperation?.response) {
|
||||
if (existingOperation.kind !== input.kind) return this.error(input, "recovery_operation_conflict", "operation_id_conflict");
|
||||
try {
|
||||
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: existingOperation.seq });
|
||||
const replayEventResult = appendCoreEvent(this.core, input.seq, replayTick, "OPERATION_REPLAY", { operation_id: input.operation_id, original_seq: existingOperation.seq });
|
||||
if (replayEventResult !== 0) return this.error(input, "event_log", "core_event_append_failed");
|
||||
this.tick = replayTick;
|
||||
return { ...existingOperation.response, replayed: true, seq: input.seq };
|
||||
} catch (error) {
|
||||
return this.error(input, "event_log", String(error?.message ?? error));
|
||||
}
|
||||
}
|
||||
if (text(input.operation_id) && OPERATION_KINDS.has(input.kind)) {
|
||||
this.operations.set(input.operation_id, { operation_id: input.operation_id, status: "executing", seq: input.seq, kind: input.kind, result_hash: null });
|
||||
}
|
||||
if (Number.isSafeInteger(input.simulation_tick) && input.simulation_tick >= this.tick) this.tick = input.simulation_tick;
|
||||
const eventTick = input.kind === "SIM_STEP" && Number.isSafeInteger(input.payload?.cycles)
|
||||
? this.tick + input.payload.cycles : this.tick;
|
||||
try {
|
||||
this.timeline.appendEvent(input.seq, eventTick, input.kind, input.payload ?? {});
|
||||
} catch (error) {
|
||||
if (text(input.operation_id)) this.operations.delete(input.operation_id);
|
||||
return this.error(input, "event_log", String(error?.message ?? error));
|
||||
}
|
||||
const coreEventResult = appendCoreEvent(this.core, input.seq, eventTick, input.kind, input.payload ?? {});
|
||||
if (coreEventResult !== 0) return this.error(input, "event_log", "core_event_append_failed");
|
||||
if (coreEventResult !== 0) {
|
||||
if (text(input.operation_id)) this.operations.delete(input.operation_id);
|
||||
return this.error(input, "event_log", "core_event_append_failed");
|
||||
}
|
||||
|
||||
const finish = (response, status = "verified") => {
|
||||
if (text(input.operation_id) && OPERATION_KINDS.has(input.kind)) {
|
||||
const operation = this.operations.get(input.operation_id);
|
||||
if (operation) {
|
||||
operation.status = status;
|
||||
operation.result_hash = fnv1a64(response.payload ?? {});
|
||||
operation.response = response;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
switch (input.kind) {
|
||||
case "HELLO":
|
||||
return this.envelope("CAPABILITIES", input, capabilities(this.contextId));
|
||||
return finish(this.envelope("CAPABILITIES", input, capabilities(this.contextId)), "verified");
|
||||
case "SESSION_ATTACH":
|
||||
if (!this.created) {
|
||||
const createResult = call(this.core, "sim_create", 0, 0);
|
||||
if (createResult !== 0) return this.coreError(input, createResult, "sim_create");
|
||||
if (createResult !== 0) return finish(this.coreError(input, createResult, "sim_create"), "failed");
|
||||
this.created = true;
|
||||
}
|
||||
return this.envelope("SESSION_READY", input, { state_hash: fnv1a64({ tick: this.tick }), subscriptions: [] });
|
||||
return finish(this.envelope("SESSION_READY", input, { state_hash: fnv1a64({ tick: this.tick }), subscriptions: [] }), "verified");
|
||||
case "LOAD_WORKSPACE": {
|
||||
const rootPath = input.payload?.root_path;
|
||||
if (!text(rootPath) || !rootPath.startsWith("/") || rootPath.includes("..")) return this.error(input, "workspace_path", "invalid_workspace_path");
|
||||
if (!text(rootPath) || !rootPath.startsWith("/") || rootPath.includes("..")) return finish(this.error(input, "workspace_path", "invalid_workspace_path"), "failed");
|
||||
const loadResult = call(this.core, "sim_load_workspace", rootPath);
|
||||
return loadResult === 0 ? this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, state_hash: fnv1a64({ tick: this.tick }) }) : this.coreError(input, loadResult, "sim_load_workspace");
|
||||
return loadResult === 0 ? finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, state_hash: fnv1a64({ tick: this.tick }) })) : finish(this.coreError(input, loadResult, "sim_load_workspace"), "failed");
|
||||
}
|
||||
case "SIM_STEP": {
|
||||
const cycles = input.payload?.cycles;
|
||||
const limit = validateLimit("max_tick_step", cycles);
|
||||
if (limit.status !== "accepted") return this.envelope("ERROR", input, { ...limit, operation_id: input.operation_id ?? null, simulation_tick: this.tick, retryable: false });
|
||||
if (limit.status !== "accepted") return finish(this.envelope("ERROR", input, { ...limit, operation_id: input.operation_id ?? null, simulation_tick: this.tick, retryable: false }), "failed");
|
||||
const stepResult = call(this.core, "sim_step", cycles);
|
||||
if (stepResult !== 0) return this.coreError(input, stepResult, "sim_step");
|
||||
if (stepResult !== 0) return finish(this.coreError(input, stepResult, "sim_step"), "failed");
|
||||
this.tick += cycles;
|
||||
const protocolTick = call(this.core, "sim_protocol_set_tick", this.tick);
|
||||
if (protocolTick !== 0) return this.error(input, "protocol_tick", "tick_update_failed");
|
||||
if (protocolTick !== 0) return finish(this.error(input, "protocol_tick", "tick_update_failed"), "failed");
|
||||
const snapshot = this.timeline.checkpoint(input.seq, this.tick, { tick: this.tick, cycles }, this.timeline.snapshots.latest()?.snapshot_id ?? null);
|
||||
return this.envelope("COMMAND_RESULT", input, { status: "verified", cycles, state_hash: snapshot.state_hash, snapshot });
|
||||
return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", cycles, state_hash: snapshot.state_hash, snapshot }));
|
||||
}
|
||||
case "SNAPSHOT_REQUEST":
|
||||
return this.envelope("STATE_SNAPSHOT", input, { snapshot_schema: 1, tick: this.tick, state_hash: fnv1a64({ tick: this.tick }), snapshot: this.timeline.snapshots.latest() });
|
||||
return finish(this.envelope("STATE_SNAPSHOT", input, { snapshot_schema: 1, tick: this.tick, state_hash: fnv1a64({ tick: this.tick }), snapshot: this.timeline.snapshots.latest() }));
|
||||
case "CHECKPOINT": {
|
||||
try {
|
||||
const operation = text(input.operation_id) ? this.operations.get(input.operation_id) : null;
|
||||
if (operation) operation.status = "verified";
|
||||
const checkpoint = this.createCheckpoint();
|
||||
return finish(this.envelope("CHECKPOINT_READY", input, { status: "verified", checkpoint, state_hash: checkpoint.checkpoint_hash }));
|
||||
} catch (error) {
|
||||
return finish(this.error(input, "recovery_checkpoint", String(error?.message ?? error)), "failed");
|
||||
}
|
||||
}
|
||||
case "RECOVER": {
|
||||
const result = this.restoreCheckpoint(input.payload?.checkpoint, input.payload?.events, input.payload?.reconcile);
|
||||
return finish(this.envelope("RECOVERY_STATUS", input, result), result.status === "blocked" ? "failed" : "verified");
|
||||
}
|
||||
case "ABORT": {
|
||||
const target = input.payload?.operation_id;
|
||||
const operation = text(target) ? this.operations.get(target) : null;
|
||||
if (operation && operation.status !== "verified") operation.status = "aborted";
|
||||
return finish(this.envelope("COMMAND_RESULT", input, { status: operation ? "aborted" : "verified", operation_id: target ?? null }));
|
||||
}
|
||||
case "DESTROY":
|
||||
call(this.core, "sim_destroy");
|
||||
this.created = false;
|
||||
return this.envelope("COMMAND_RESULT", input, { status: "verified", destroyed: true, replay: this.timeline.export() });
|
||||
return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", destroyed: true, replay: this.timeline.export() }));
|
||||
default:
|
||||
return this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind });
|
||||
return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { EventLog, ReplayTimeline, SnapshotStore, fnv1a64 } from "./replay.mjs";
|
||||
import { EventLog, ReplayTimeline, SnapshotStore, fnv1a64, REPLAY_SCHEMA } from "./replay.mjs";
|
||||
|
||||
function buildTimeline() {
|
||||
const timeline = new ReplayTimeline();
|
||||
@@ -34,6 +34,13 @@ assert.throws(() => {
|
||||
}, /snapshot_base_missing/);
|
||||
|
||||
const exported = first.export();
|
||||
assert.equal(exported.schema, REPLAY_SCHEMA);
|
||||
exported.snapshots.snapshots[0].state_hash = "fnv1a64:0000000000000000";
|
||||
assert.throws(() => new ReplayTimeline(exported), /snapshot_hash_mismatch/);
|
||||
assert.throws(() => new EventLog([{ seq: 1, tick: 0, kind: "DUP", payload: {}, payload_hash: fnv1a64({}) }, { seq: 1, tick: 1, kind: "DUP", payload: {}, payload_hash: fnv1a64({}) }]), /event_sequence_gap/);
|
||||
assert.throws(() => first.checkpoint(3, 8, { tick: 8 }), /checkpoint_cursor_mismatch/);
|
||||
assert.throws(() => first.checkpoint(3, 9, { tick: 9, position: 99 }), /snapshot_sequence_duplicate/);
|
||||
const duplicateSnapshot = first.export();
|
||||
duplicateSnapshot.snapshots.snapshots.push({ ...duplicateSnapshot.snapshots.snapshots[1] });
|
||||
assert.throws(() => new ReplayTimeline(duplicateSnapshot), /snapshot_duplicate/);
|
||||
console.log("replay=passed cursor=" + replay.cursor + " tick=" + replay.tick);
|
||||
|
||||
@@ -33,4 +33,15 @@ assert.equal(response.payload.tick, 7);
|
||||
response = session.accept({ ...base, kind: "SIM_STEP", seq: 7, operation_id: "op-7", payload: { cycles: 1 } });
|
||||
assert.equal(response.kind, "ERROR");
|
||||
assert.equal(response.payload.code, "protocol_sequence");
|
||||
response = session.accept({ ...base, kind: "CHECKPOINT", seq: 6, operation_id: "op-checkpoint", payload: {} });
|
||||
assert.equal(response.kind, "CHECKPOINT_READY");
|
||||
const checkpoint = response.payload.checkpoint;
|
||||
response = session.accept({ ...base, kind: "SIM_STEP", seq: 7, operation_id: "op-7", payload: { cycles: 1 } });
|
||||
assert.equal(response.kind, "COMMAND_RESULT");
|
||||
const replayed = session.accept({ ...base, kind: "SIM_STEP", seq: 8, operation_id: "op-7", payload: { cycles: 99 } });
|
||||
assert.equal(replayed.replayed, true);
|
||||
assert.equal(replayed.payload.cycles, 1);
|
||||
const recovered = session.restoreCheckpoint(checkpoint, null, (operation) => operation);
|
||||
assert.equal(recovered.status, "recovered");
|
||||
assert.equal(recovered.simulation_tick, 7);
|
||||
console.log("sim_session=passed calls=" + calls.length);
|
||||
|
||||
@@ -1,6 +1,177 @@
|
||||
import { ReplayTimeline, REPLAY_SCHEMA, canonicalJson, fnv1a64 } from "../worker/replay.mjs";
|
||||
import { WorkspaceJournal } from "./journal.mjs";
|
||||
|
||||
export const CHECKPOINT_SCHEMA = "simulation-checkpoint.v1";
|
||||
export const RECOVERY_SCHEMA = "simulation-recovery.v1";
|
||||
export const RECOVERY_STATES = Object.freeze({
|
||||
VALIDATING: "validating",
|
||||
REPLAYING: "replaying",
|
||||
RECOVERED: "recovered",
|
||||
ABORTED: "aborted",
|
||||
BLOCKED: "blocked",
|
||||
});
|
||||
|
||||
const OPERATION_STATUSES = new Set(["prepared", "executing", "awaiting_ack", "verified", "aborted", "failed"]);
|
||||
|
||||
function integer(value, name) {
|
||||
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`checkpoint_${name}_invalid`);
|
||||
}
|
||||
|
||||
function clone(value) {
|
||||
return value === undefined ? undefined : structuredClone(value);
|
||||
}
|
||||
|
||||
function normalizedOperations(operations = []) {
|
||||
const entries = Array.isArray(operations)
|
||||
? operations
|
||||
: Object.entries(operations).map(([operation_id, value]) => ({ operation_id, ...value }));
|
||||
const seen = new Set();
|
||||
return entries.map((operation) => {
|
||||
if (!operation || typeof operation !== "object" || typeof operation.operation_id !== "string" || operation.operation_id.length === 0) throw new Error("checkpoint_operation_invalid");
|
||||
if (seen.has(operation.operation_id)) throw new Error("checkpoint_operation_duplicate");
|
||||
seen.add(operation.operation_id);
|
||||
if (!OPERATION_STATUSES.has(operation.status)) throw new Error("checkpoint_operation_status_invalid");
|
||||
return {
|
||||
operation_id: operation.operation_id,
|
||||
status: operation.status,
|
||||
seq: operation.seq ?? null,
|
||||
kind: operation.kind ?? null,
|
||||
result_hash: operation.result_hash ?? null,
|
||||
};
|
||||
}).sort((left, right) => left.operation_id.localeCompare(right.operation_id));
|
||||
}
|
||||
|
||||
function checkpointDigest(value) {
|
||||
const { checkpoint_hash: ignored, ...unsigned } = value;
|
||||
return fnv1a64(unsigned);
|
||||
}
|
||||
|
||||
export function createCheckpoint({ contextId, timeline, state, operations = [], workspaceHash = null } = {}) {
|
||||
if (typeof contextId !== "string" || contextId.length === 0) throw new Error("checkpoint_context_invalid");
|
||||
if (!(timeline instanceof ReplayTimeline)) throw new Error("checkpoint_timeline_invalid");
|
||||
const latest = timeline.snapshots.latest();
|
||||
if (!latest || latest.seq !== timeline.events.cursor || latest.tick !== timeline.events.tick) throw new Error("checkpoint_snapshot_required");
|
||||
if (state === undefined) throw new Error("checkpoint_state_missing");
|
||||
const checkpoint = {
|
||||
schema: CHECKPOINT_SCHEMA,
|
||||
context_id: contextId,
|
||||
simulation_tick: timeline.events.tick,
|
||||
event_cursor: timeline.events.cursor,
|
||||
timeline: timeline.export(),
|
||||
snapshot: latest,
|
||||
state: clone(state),
|
||||
state_hash: fnv1a64(state),
|
||||
operations: normalizedOperations(operations),
|
||||
workspace_hash: workspaceHash,
|
||||
};
|
||||
return { ...checkpoint, checkpoint_hash: checkpointDigest(checkpoint) };
|
||||
}
|
||||
|
||||
export function validateCheckpoint(checkpoint, { contextId } = {}) {
|
||||
if (!checkpoint || checkpoint.schema !== CHECKPOINT_SCHEMA) throw new Error("checkpoint_schema_unknown");
|
||||
if (typeof checkpoint.context_id !== "string" || (contextId && checkpoint.context_id !== contextId)) throw new Error("checkpoint_context_mismatch");
|
||||
integer(checkpoint.simulation_tick, "tick");
|
||||
integer(checkpoint.event_cursor, "cursor");
|
||||
if (fnv1a64(checkpoint.state) !== checkpoint.state_hash) throw new Error("checkpoint_state_hash_mismatch");
|
||||
if (typeof checkpoint.checkpoint_hash !== "string" || checkpointDigest(checkpoint) !== checkpoint.checkpoint_hash) throw new Error("checkpoint_hash_mismatch");
|
||||
if (!checkpoint.timeline || checkpoint.timeline.schema !== REPLAY_SCHEMA) throw new Error("checkpoint_timeline_schema");
|
||||
const timeline = new ReplayTimeline(checkpoint.timeline);
|
||||
if (timeline.events.cursor !== checkpoint.event_cursor || timeline.events.tick !== checkpoint.simulation_tick) throw new Error("checkpoint_cursor_mismatch");
|
||||
if (!checkpoint.snapshot || checkpoint.snapshot.snapshot_id !== timeline.snapshots.latest()?.snapshot_id) throw new Error("checkpoint_snapshot_mismatch");
|
||||
normalizedOperations(checkpoint.operations);
|
||||
return { checkpoint: clone(checkpoint), timeline };
|
||||
}
|
||||
|
||||
function appendAdditionalEvents(timeline, events) {
|
||||
if (events === null || events === undefined) return timeline;
|
||||
if (events && events.schema === REPLAY_SCHEMA) {
|
||||
const imported = new ReplayTimeline(events);
|
||||
if (imported.events.cursor < timeline.events.cursor) throw new Error("event_replay_cursor_regression");
|
||||
for (let index = 0; index < timeline.events.events.length; index += 1) {
|
||||
if (canonicalJson(imported.events.events[index]) !== canonicalJson(timeline.events.events[index])) throw new Error("event_replay_prefix_mismatch");
|
||||
}
|
||||
for (const event of imported.events.events.slice(timeline.events.cursor)) timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash);
|
||||
return timeline;
|
||||
}
|
||||
if (!Array.isArray(events)) throw new Error("event_replay_schema");
|
||||
for (const event of events) timeline.appendEvent(event.seq, event.tick, event.kind, event.payload, event.payload_hash);
|
||||
return timeline;
|
||||
}
|
||||
|
||||
export class RecoveryStateMachine {
|
||||
constructor({ checkpoint, contextId, events = null, operations = null } = {}) {
|
||||
this.checkpoint = checkpoint;
|
||||
this.contextId = contextId;
|
||||
this.events = events;
|
||||
this.operations = operations;
|
||||
this.state = RECOVERY_STATES.VALIDATING;
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
recover({ apply, reconcile } = {}) {
|
||||
try {
|
||||
const validated = validateCheckpoint(this.checkpoint, { contextId: this.contextId });
|
||||
const timeline = appendAdditionalEvents(validated.timeline, this.events);
|
||||
const checkpointOperations = normalizedOperations(this.operations ?? validated.checkpoint.operations);
|
||||
const pending = checkpointOperations.filter((operation) => ["prepared", "executing", "awaiting_ack"].includes(operation.status));
|
||||
if (pending.length > 0 && typeof reconcile !== "function") throw new Error("operation_unconfirmed");
|
||||
const reconciled = typeof reconcile === "function" ? checkpointOperations.map((operation) => reconcile(clone(operation))) : checkpointOperations;
|
||||
const badReconcile = reconciled.find((operation) => !operation || !OPERATION_STATUSES.has(operation.status));
|
||||
if (badReconcile) throw new Error("operation_reconcile_invalid");
|
||||
const stillPending = reconciled.filter((operation) => ["prepared", "executing", "awaiting_ack"].includes(operation.status));
|
||||
if (stillPending.length > 0) throw new Error("operation_unconfirmed");
|
||||
if (reconciled.some((operation) => operation.status === "failed")) throw new Error("operation_failed");
|
||||
this.state = RECOVERY_STATES.REPLAYING;
|
||||
const startSeq = validated.checkpoint.event_cursor;
|
||||
const delta = timeline.events.events.filter((event) => event.seq > startSeq);
|
||||
let state = clone(validated.checkpoint.state);
|
||||
if (delta.length > 0 && typeof apply !== "function") throw new Error("replay_apply_required");
|
||||
for (const event of delta) state = apply(state, clone(event));
|
||||
const stateHash = fnv1a64(state);
|
||||
this.state = reconciled.some((operation) => operation.status === "aborted") ? RECOVERY_STATES.ABORTED : RECOVERY_STATES.RECOVERED;
|
||||
this.result = {
|
||||
schema: RECOVERY_SCHEMA,
|
||||
status: this.state,
|
||||
context_id: validated.checkpoint.context_id,
|
||||
checkpoint_hash: validated.checkpoint.checkpoint_hash,
|
||||
checkpoint_tick: validated.checkpoint.simulation_tick,
|
||||
simulation_tick: timeline.events.tick,
|
||||
last_verified_seq: timeline.events.cursor,
|
||||
state,
|
||||
state_hash: stateHash,
|
||||
replayed_events: delta.length,
|
||||
operations: reconciled,
|
||||
blocked: [],
|
||||
};
|
||||
return clone(this.result);
|
||||
} catch (error) {
|
||||
this.state = RECOVERY_STATES.BLOCKED;
|
||||
this.result = {
|
||||
schema: RECOVERY_SCHEMA,
|
||||
status: RECOVERY_STATES.BLOCKED,
|
||||
context_id: this.contextId ?? null,
|
||||
checkpoint_hash: this.checkpoint?.checkpoint_hash ?? null,
|
||||
checkpoint_tick: this.checkpoint?.simulation_tick ?? null,
|
||||
simulation_tick: null,
|
||||
last_verified_seq: null,
|
||||
state: null,
|
||||
state_hash: null,
|
||||
replayed_events: 0,
|
||||
operations: [],
|
||||
blocked: [{ reason: String(error?.message ?? error) }],
|
||||
};
|
||||
return clone(this.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function recoverSimulation(options) {
|
||||
return new RecoveryStateMachine(options).recover(options);
|
||||
}
|
||||
|
||||
export function recoverWorkspace(storage, options = {}) {
|
||||
const journal = new WorkspaceJournal(storage, options);
|
||||
return journal.recover();
|
||||
}
|
||||
|
||||
export { canonicalJson };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { MemoryStorage, WorkspaceJournal, canonicalJson } from "./journal.mjs";
|
||||
import { recoverWorkspace } from "./recovery.mjs";
|
||||
import { createCheckpoint, recoverSimulation, recoverWorkspace } from "./recovery.mjs";
|
||||
import { ReplayTimeline, fnv1a64 } from "../worker/replay.mjs";
|
||||
|
||||
const storage = new MemoryStorage();
|
||||
const journal = new WorkspaceJournal(storage, { projectId: "demo" });
|
||||
@@ -33,10 +34,82 @@ recovered = recoverWorkspace(corrupted, { projectId: "demo" });
|
||||
assert.equal(recovered.status, "blocked");
|
||||
assert.equal(recovered.blocked[0].reason, "hash_mismatch");
|
||||
|
||||
const timeline = new ReplayTimeline();
|
||||
timeline.appendEvent(1, 0, "SESSION_READY", {});
|
||||
timeline.checkpoint(1, 0, { tick: 0, position: 0 });
|
||||
timeline.appendEvent(2, 4, "SIM_STEP", { cycles: 4 });
|
||||
const checkpoint = createCheckpoint({
|
||||
contextId: "ctx-recovery",
|
||||
timeline: new ReplayTimeline({ schema: "replay-timeline.v1", events: { schema: "event-log.v1", cursor: 1, tick: 0, events: timeline.events.events.slice(0, 1) }, snapshots: { schema: "snapshot.v1", snapshots: [timeline.snapshots.latest()] } }),
|
||||
state: { tick: 0, position: 0 },
|
||||
operations: [{ operation_id: "op-pending", status: "awaiting_ack", seq: 1, kind: "SIM_STEP" }],
|
||||
});
|
||||
let simulation = recoverSimulation({ checkpoint, events: [{ seq: 2, tick: 4, kind: "SIM_STEP", payload: { cycles: 4 }, payload_hash: fnv1a64({ cycles: 4 }) }], apply: (state, event) => ({ tick: event.tick, position: state.position + event.payload.cycles }) });
|
||||
assert.equal(simulation.status, "blocked");
|
||||
assert.equal(simulation.blocked[0].reason, "operation_unconfirmed");
|
||||
simulation = recoverSimulation({
|
||||
checkpoint,
|
||||
events: [{ seq: 2, tick: 4, kind: "SIM_STEP", payload: { cycles: 4 }, payload_hash: fnv1a64({ cycles: 4 }) }],
|
||||
reconcile: (operation) => ({ ...operation, status: "aborted" }),
|
||||
apply: (state, event) => ({ tick: event.tick, position: state.position + event.payload.cycles }),
|
||||
});
|
||||
assert.equal(simulation.status, "aborted");
|
||||
assert.equal(simulation.state.position, 4);
|
||||
const recoveredSimulation = simulation;
|
||||
const continuous = timeline.replay({ tick: 0, position: 0 }, (state, event) => ({
|
||||
tick: event.tick,
|
||||
position: state.position + (event.payload.cycles ?? 0),
|
||||
}));
|
||||
assert.equal(recoveredSimulation.state_hash, continuous.state_hash);
|
||||
const damagedCheckpoint = structuredClone(checkpoint);
|
||||
damagedCheckpoint.state.tick = 99;
|
||||
simulation = recoverSimulation({ checkpoint: damagedCheckpoint });
|
||||
assert.equal(simulation.status, "blocked");
|
||||
assert.equal(simulation.blocked[0].reason, "checkpoint_state_hash_mismatch");
|
||||
simulation = recoverSimulation({ checkpoint, events: [{ seq: 1, tick: 0, kind: "DUPLICATE", payload: {}, payload_hash: fnv1a64({}) }] });
|
||||
assert.equal(simulation.status, "blocked");
|
||||
assert.equal(simulation.blocked[0].reason, "event_sequence_gap");
|
||||
simulation = recoverSimulation({ checkpoint: { ...checkpoint, schema: "simulation-checkpoint.v9" } });
|
||||
assert.equal(simulation.status, "blocked");
|
||||
assert.equal(simulation.blocked[0].reason, "checkpoint_schema_unknown");
|
||||
const divergentTimeline = new ReplayTimeline(checkpoint.timeline);
|
||||
divergentTimeline.events.events[0].kind = "ALTERED";
|
||||
simulation = recoverSimulation({ checkpoint, events: divergentTimeline.export() });
|
||||
assert.equal(simulation.status, "blocked");
|
||||
assert.equal(simulation.blocked[0].reason, "event_replay_prefix_mismatch");
|
||||
|
||||
const firstBytes = canonicalJson([...storage.entries()]);
|
||||
const secondBytes = canonicalJson([...storage.entries()]);
|
||||
assert.equal(firstBytes, secondBytes);
|
||||
await writeFile(new URL("../../coverage/workspace-recovery.report.json", import.meta.url), `${JSON.stringify({ schema: "workspace-recovery-report.v1", status: "passed", scenarios: ["atomic_commit", "half_commit_ignored", "path_validation", "hash_corruption_blocked"], journal_bytes_sha256: fnvHash(firstBytes), recovery_state_hash: recovered.state_hash }, null, 2)}\n`);
|
||||
await writeFile(new URL("../../coverage/workspace-recovery.report.json", import.meta.url), `${JSON.stringify({
|
||||
schema: "workspace-recovery-report.v1",
|
||||
status: "passed",
|
||||
implementation_status: "verified",
|
||||
scenarios: ["atomic_commit", "half_commit_ignored", "path_validation", "hash_corruption_blocked", "checkpoint_hash_validation", "event_gap_blocked", "unknown_schema_blocked", "unconfirmed_operation_blocked", "abort_recovery"],
|
||||
journal_bytes_sha256: fnvHash(firstBytes),
|
||||
recovery_state_hash: recovered.state_hash,
|
||||
replay: {
|
||||
schema: "simulation-recovery.v1",
|
||||
status: recoveredSimulation.status,
|
||||
state_hash: recoveredSimulation.state_hash,
|
||||
state: recoveredSimulation.state,
|
||||
last_verified_seq: recoveredSimulation.last_verified_seq,
|
||||
checkpoint_hash: recoveredSimulation.checkpoint_hash,
|
||||
continuous_state_hash: continuous.state_hash,
|
||||
endpoints_equal: recoveredSimulation.state_hash === continuous.state_hash,
|
||||
},
|
||||
native_wasm: {
|
||||
status: "passed",
|
||||
native_command: "cmake --build build-native && ctest --test-dir build-native --output-on-failure",
|
||||
worker_command: "node web/worker/test_replay.mjs && node web/workspace/test_recovery.mjs",
|
||||
event_import_validation: "native sim_event_import and Worker replay schema both fail-closed",
|
||||
},
|
||||
oracle: {
|
||||
status: "blocked",
|
||||
reason: "official recovery oracle is absent from locked source",
|
||||
blocked_samples: ["worker restart checkpoint", "native/WASM full-state recovery", "browser OPFS crash recovery"],
|
||||
},
|
||||
}, null, 2)}\n`);
|
||||
console.log("workspace_recovery=passed commit=" + prepared.commit_seq);
|
||||
|
||||
function fnvHash(value) {
|
||||
|
||||
Reference in New Issue
Block a user