rosotacom-dev 2.5.dev42__tar.gz → 2.5.dev47__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/PKG-INFO +1 -1
  2. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/ffmpeg-keyframes.md +24 -5
  3. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/README.md +1 -0
  4. rosotacom_dev-2.5.dev47/docs/findings/reorder-becomes-reader-loss.md +69 -0
  5. rosotacom_dev-2.5.dev47/docs/findings/repro/reorder_drop_repro.py +170 -0
  6. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/forensics-report.md +16 -0
  7. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/session-configuration.md +21 -0
  8. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/forensics.py +143 -24
  9. rosotacom_dev-2.5.dev47/src/rosotacom/resources/examples/dist/field-20260817.dist +513 -0
  10. rosotacom_dev-2.5.dev47/src/rosotacom/resources/examples/profiles.yaml +171 -0
  11. rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +58 -0
  12. rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +100 -0
  13. rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +83 -0
  14. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +8 -0
  15. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +35 -30
  16. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +1 -0
  17. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/stream_stats.py +8 -1
  18. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
  19. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/SOURCES.txt +9 -0
  20. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/scm_file_list.json +9 -0
  21. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
  22. rosotacom_dev-2.5.dev47/tests/unit/test_ffmpeg_flags.py +130 -0
  23. rosotacom_dev-2.5.dev47/tests/unit/test_field_profiles.py +51 -0
  24. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_forensics.py +153 -0
  25. rosotacom_dev-2.5.dev47/tests/unit/test_playout_pacer.py +81 -0
  26. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_overview.py +27 -0
  27. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_overview_observer.py +106 -5
  28. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_stream_stats.py +12 -0
  29. rosotacom_dev-2.5.dev42/src/rosotacom/resources/examples/profiles.yaml +0 -85
  30. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.devcontainer/devcontainer.json.template +0 -0
  31. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.devcontainer/re_generate_devcontainer_json.py +0 -0
  32. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/CODEOWNERS +0 -0
  33. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
  34. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
  35. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
  36. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
  37. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
  38. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
  39. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
  40. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
  41. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/actions/setup-just/action.yml +0 -0
  43. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/dependabot.yml +0 -0
  44. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/benchmark-calibrate.yml +0 -0
  45. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/benchmark-gate.yml +0 -0
  46. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/dev-release.yml +0 -0
  47. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/image-scan.yml +0 -0
  48. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/nightly-e2e.yml +0 -0
  49. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/pr-lightweight.yml +0 -0
  50. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/pr-merge-gate.yml +0 -0
  51. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/release.yml +0 -0
  52. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.pre-commit-config.yaml +0 -0
  53. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/AGENTS.md +0 -0
  54. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/CONCURRENCY.md +0 -0
  55. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/CONTRIBUTING.md +0 -0
  56. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/DEVELOPMENT_PRINCIPLES.md +0 -0
  57. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/LICENSE.txt +0 -0
  58. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/MANIFEST.in +0 -0
  59. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/README.md +0 -0
  60. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/budgets.jsonl +0 -0
  61. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/README.md +0 -0
  62. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/capacity-size-tight-cyclone.json +0 -0
  63. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
  64. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-nominal-cyclone.json +0 -0
  65. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-cyclone.json +0 -0
  66. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-fastdds.json +0 -0
  67. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-zenoh.json +0 -0
  68. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/codecov.yml +0 -0
  69. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/deployment-configuration.md +0 -0
  70. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/benchmark-ab.md +0 -0
  71. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/benchmark-bag-as-load.md +0 -0
  72. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/ci.md +0 -0
  73. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
  74. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/delay-alone-no-loss.md +0 -0
  75. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/head-of-line-irregularity.md +0 -0
  76. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
  77. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/lighter-message-loss.md +0 -0
  78. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
  79. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/geo-link-map.md +0 -0
  80. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/github-repository-settings.md +0 -0
  81. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/link-bandwidth.md +0 -0
  82. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/link-trace.md +0 -0
  83. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/owner-runbook.md +0 -0
  84. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/performance-bands.md +0 -0
  85. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/playbooks/README.md +0 -0
  86. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/quality-model.md +0 -0
  87. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/TEMPLATE.md +0 -0
  88. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.1.md +0 -0
  89. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.2.md +0 -0
  90. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.3.md +0 -0
  91. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.4.md +0 -0
  92. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release.md +0 -0
  93. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
  94. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0002-expectation-concepts.md +0 -0
  95. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0003-metric-backbone.md +0 -0
  96. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0004-network-profiles.md +0 -0
  97. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
  98. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0006-dynamic-qos.md +0 -0
  99. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0007-regression-gate.md +0 -0
  100. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/README.md +0 -0
  101. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/testing.md +0 -0
  102. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/work-items.md +0 -0
  103. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/install.sh +0 -0
  104. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/justfile +0 -0
  105. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/control_center.png +0 -0
  106. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/echo_test.png +0 -0
  107. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/forensics-report-example.png +0 -0
  108. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/host.png +0 -0
  109. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/vehicle.png +0 -0
  110. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/pyproject.toml +0 -0
  111. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/requirements.txt +0 -0
  112. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/rosotacom.yaml +0 -0
  113. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/setup.cfg +0 -0
  114. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/__init__.py +0 -0
  115. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/__main__.py +0 -0
  116. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/anonymize.py +0 -0
  117. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/bag_ground_truth.py +0 -0
  118. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benched_set.py +0 -0
  119. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benchmark.py +0 -0
  120. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benchmark_delta.py +0 -0
  121. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/bundle_check.py +0 -0
  122. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/cli.py +0 -0
  123. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/cli_benchmark.py +0 -0
  124. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/deployment.py +0 -0
  125. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/ffmpeg_packet.py +0 -0
  126. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/geomap.py +0 -0
  127. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/image_cache.py +0 -0
  128. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/netem_dist.py +0 -0
  129. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/network_profiles.py +0 -0
  130. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/network_shaper.py +0 -0
  131. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/plots.py +0 -0
  132. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/py.typed +0 -0
  133. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/benched-set.yaml +0 -0
  134. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/.gitignore +0 -0
  135. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/README.md +0 -0
  136. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/__init__.py +0 -0
  137. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
  138. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
  139. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/ros2docker.json +0 -0
  140. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
  141. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
  142. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
  143. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
  144. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
  145. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
  146. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
  147. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
  148. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
  149. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
  150. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
  151. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
  152. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
  153. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
  154. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
  155. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
  156. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  157. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
  158. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
  159. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
  160. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
  161. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
  162. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
  163. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
  164. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
  165. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
  166. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
  167. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
  168. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
  169. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
  170. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
  171. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
  172. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
  173. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
  174. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
  175. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
  176. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
  177. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
  178. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
  179. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
  180. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
  181. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
  182. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
  183. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
  184. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
  185. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
  186. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
  187. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
  188. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
  189. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
  190. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
  191. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
  192. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
  193. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
  194. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
  195. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
  196. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
  197. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
  198. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
  199. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
  200. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
  201. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
  202. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
  203. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
  204. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
  205. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
  206. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
  207. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
  208. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
  209. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
  210. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ros2docker.json.example +0 -0
  211. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
  212. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
  213. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
  214. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
  215. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
  216. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
  217. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
  218. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
  219. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
  220. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
  221. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
  222. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
  223. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
  224. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
  225. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
  226. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
  227. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
  228. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
  229. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
  230. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
  231. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
  232. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
  233. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
  234. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
  235. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
  236. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
  237. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
  238. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
  239. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
  240. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
  241. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
  242. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
  243. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
  244. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
  245. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
  246. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
  247. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
  248. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
  249. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
  250. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
  251. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
  252. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
  253. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
  254. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
  255. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
  256. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
  257. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
  258. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
  259. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
  260. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
  261. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
  262. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
  263. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
  264. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
  265. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
  266. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
  267. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
  268. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
  269. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
  270. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
  271. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
  272. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
  273. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
  274. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
  275. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
  276. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
  277. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
  278. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
  279. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
  280. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
  281. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
  282. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
  283. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
  284. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
  285. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
  286. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
  287. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
  288. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
  289. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
  290. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
  291. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
  292. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
  293. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
  294. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
  295. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
  296. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
  297. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/status_eval.py +0 -0
  298. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/trace_profiles.py +0 -0
  299. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/transit.py +0 -0
  300. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/video_quality.py +0 -0
  301. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
  302. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
  303. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/requires.txt +0 -0
  304. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
  305. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/terminology.md +0 -0
  306. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_benched_set_registry.py +0 -0
  307. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_findings.py +0 -0
  308. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_markdown_links.py +0 -0
  309. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_ota_source_bundle.py +0 -0
  310. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_package_metadata.py +0 -0
  311. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_packaged_resources.py +0 -0
  312. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_profile_covers_generated_commands.py +0 -0
  313. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_pytest_policy.py +0 -0
  314. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_readme_examples.py +0 -0
  315. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_security_maintenance_config.py +0 -0
  316. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_session_configuration_doc.py +0 -0
  317. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_session_template_substitution.py +0 -0
  318. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_workflow_contracts.py +0 -0
  319. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/conftest.py +0 -0
  320. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_anonymize_e2e.py +0 -0
  321. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_ab.py +0 -0
  322. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_capacity.py +0 -0
  323. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_replay.py +0 -0
  324. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_parallel_smoke.py +0 -0
  325. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_smoke.py +0 -0
  326. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_timeline_stepping.py +0 -0
  327. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_video_quality_e2e.py +0 -0
  328. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/generate_rmw_matrix.py +0 -0
  329. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
  330. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
  331. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
  332. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
  333. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
  334. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_anonymize.py +0 -0
  335. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_bag_ground_truth.py +0 -0
  336. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benched_set.py +0 -0
  337. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benchmark.py +0 -0
  338. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benchmark_delta.py +0 -0
  339. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_bundle_check.py +0 -0
  340. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_catmux_pane_logging.py +0 -0
  341. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_cli.py +0 -0
  342. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_cli_benchmark.py +0 -0
  343. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_concurrency.py +0 -0
  344. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_deployment.py +0 -0
  345. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ffmpeg_packet.py +0 -0
  346. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_geomap.py +0 -0
  347. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_image_cache.py +0 -0
  348. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_link_bytes.py +0 -0
  349. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_link_trace.py +0 -0
  350. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_netem_dist.py +0 -0
  351. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_network_profiles.py +0 -0
  352. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_network_shaper.py +0 -0
  353. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_unwrapper.py +0 -0
  354. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_wrapper.py +0 -0
  355. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_xml.py +0 -0
  356. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_plots.py +0 -0
  357. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_qos_fallback_logging.py +0 -0
  358. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_relay_optional_parameters.py +0 -0
  359. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_session_pipeline.py +0 -0
  360. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_stage_latency.py +0 -0
  361. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_eval.py +0 -0
  362. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_trace_profiles.py +0 -0
  363. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_transit.py +0 -0
  364. {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_video_quality.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev42
3
+ Version: 2.5.dev47
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -45,6 +45,18 @@ Grouping a stream into GOPs is then just splitting at keyframes. Note the
45
45
  encoder may emit keyframes *early* (scene cuts), so GOPs can be shorter than
46
46
  `gop_size` — assert spacing `<= gop_size`, not `== gop_size`.
47
47
 
48
+ ## Transit records carry the flag
49
+
50
+ The receiving status overview applies the same parse live: when an inbound
51
+ `OtaStamped` envelope's `msg_type` names an FFMPEGPacket, it walks the wrapped
52
+ CDR bytes to `flags` (`com_py/ffmpeg_flags.py`, a dependency-free mirror of the
53
+ host parser) and writes `keyframe: true/false` on every delivered RFC 0003
54
+ transit row in `events.jsonl`. Synthesized lost rows have no payload and carry
55
+ no such field; a payload the walk cannot parse simply omits it. Downstream,
56
+ `rosotacom report` annotates a stream from these real flags whenever they cover
57
+ more than 90% of its delivered rows (provenance `ffmpeg_flags (transit
58
+ records)`), and only recordings without the field fall back to size bimodality.
59
+
48
60
  ## Automated stream-stage stats
49
61
 
50
62
  `rosotacom stream-stats` applies the same packet parser and GOP grouping to
@@ -69,8 +81,9 @@ Inputs are explicit and repeatable:
69
81
  rosbag2 storage backends fall back to `rosbag2_py` in a ROS environment.
70
82
  - `--events LABEL=PATH:/topic` reads delivered RFC 0003 transit rows from
71
83
  `events.jsonl`, using receiver-side `t_com_in` timestamps and `size_bytes`.
72
- Transit rows do not carry raw CDR flags, so GOP annotation uses the documented
73
- size-bimodality fallback when the keyframe share looks like a real GOP.
84
+ Rows carrying the per-message `keyframe` field annotate GOPs from the real
85
+ flags; older recordings without it use the documented size-bimodality
86
+ fallback when the keyframe share looks like a real GOP.
74
87
 
75
88
  The Markdown output starts with the comparison table:
76
89
 
@@ -105,9 +118,9 @@ averages 43,723 B, while positions 1-4 average 3,738 B, 4,512 B, 4,834 B, and
105
118
 
106
119
  ## Fallback: size bimodality
107
120
 
108
- If `flags` is unavailable (foreign recordings, or bags anonymized before
109
- flag preservation existed), the size distribution still separates the two
110
- classes: delta frames dominate the stream (a GOP of N contributes N−1), so
121
+ If `flags` is unavailable (foreign recordings, bags anonymized before flag
122
+ preservation existed, or `events.jsonl` written before transit rows carried
123
+ `keyframe`), the size distribution still separates the two classes: delta frames dominate the stream (a GOP of N contributes N−1), so
111
124
  the median frame size sits on the delta mode, while keyframes are typically
112
125
  5–100× larger. `rosotacom.ffmpeg_packet.keyframes_by_size` marks frames
113
126
  above `3 × median` as keyframes. This misclassifies only when delta frames
@@ -119,6 +132,12 @@ scene at a very low bitrate) — prefer `flags` whenever present.
119
132
  - `tests/unit/test_ffmpeg_packet.py` — CDR parsing (alignment across
120
133
  odd-length strings), flag semantics, and the size fallback on a synthetic
121
134
  GOP pattern.
135
+ - `tests/unit/test_ffmpeg_flags.py` — the receiver-side keyframe-bit walk in
136
+ `com_py/ffmpeg_flags.py`: agreement with the host parser on hand-built
137
+ packets, and `None` (never an exception) on truncated or foreign bytes.
138
+ - `tests/unit/test_forensics.py` — `rosotacom report` prefers transit-row
139
+ `keyframe` flags over size bimodality, and falls back below the coverage
140
+ gate.
122
141
  - `tests/unit/test_stream_stats.py` — exact stream statistics, per-GOP-position
123
142
  tables from hand-built CDR packets, events-row source loading, comparison
124
143
  table shape, and CLI output writing.
@@ -28,6 +28,7 @@ living verification hook.
28
28
 
29
29
  ## Findings
30
30
 
31
+ - [Best-effort reordering becomes reader-side loss, independent of history depth](reorder-becomes-reader-loss.md) - under pure delay jitter a Cyclone best-effort reader discards every overtaken sample (57% at 100 Hz under 50+-45 ms), delivery stays strictly monotonic, and depth 50 loses the same as depth 1.
31
32
  - [Jitter causes loss while bandwidth shortage builds latency](jitter-loss-bandwidth-latency.md) - 18 KB at 20 Hz separates jitter-induced loss from bandwidth-induced queueing.
32
33
  - [Lighter alternating messages can lose more than steady messages](lighter-message-loss.md) - a 1x18KB+1x0KB pattern lost under a tight emulated profile while the steady 18 KB comparison did not.
33
34
  - [Delay alone was not the loss boundary for the 18 KB at 20 Hz probe](delay-alone-no-loss.md) - pure delay profiles showed no material loss, and the 300 ms delay-only probe delivered all messages.
@@ -0,0 +1,69 @@
1
+ # Best-Effort Reordering Becomes Reader-Side Loss, Independent Of History Depth
2
+
3
+ ## Claim
4
+
5
+ Under delay jitter large enough to reorder packets — with ZERO configured
6
+ loss — a CycloneDDS best-effort reader discards every overtaken sample to
7
+ preserve per-writer order: delivery is strictly monotonic, the discarded
8
+ samples count as loss, and subscription history depth does not recover any of
9
+ it (the drop happens in the reader proxy, before history). Variance is
10
+ therefore a loss mechanism of its own, and it is the one QoS depth cannot
11
+ help against — unlike bundled-publication overwrite, where depth does help.
12
+
13
+ ## Setup
14
+
15
+ - Host pair / topology: two Docker containers on one machine (the repository's
16
+ communication image; any image with `rclpy`, `rmw_cyclonedds_cpp` and `tc`
17
+ works), unicast Cyclone peers (`AllowMulticast=false`, static IPs), one
18
+ `std_msgs/Int64` stream at 100 Hz, publisher history depth 10, best-effort;
19
+ the receiver subscribes the SAME topic twice: depth 1 and depth 50. netem on
20
+ the publisher's egress only, delay+jitter, no loss, no rate limit.
21
+ - rosotacom SHA: measured at `3227daf`; re-runs record theirs.
22
+ - Profile: inline netem args (`delay 50ms 45ms` and `delay 50ms 20ms`), not a
23
+ packaged profile — the experiment is below rosotacom, at the RMW layer.
24
+ - Seed policy: seedless single runs (`n=1` per jitter level); the effect size
25
+ (tens of percent) dwarfs run-to-run noise.
26
+
27
+ ## Evidence
28
+
29
+ Evidence grade: scripted container reproduction, committed next to this file.
30
+
31
+ ```bash
32
+ python3 docs/findings/repro/reorder_drop_repro.py orchestrate --rate 100 --duration 40
33
+ ```
34
+
35
+ 2026-08-18 run (4000 messages sent per scenario, zero configured loss):
36
+
37
+ | netem | depth 1 delivered | depth 50 delivered | inversions | loss of span |
38
+ |---|---|---|---|---|
39
+ | `delay 50ms 45ms` | 1709 | 1727 | 0 | **57%** |
40
+ | `delay 50ms 20ms` | 2626 | 2659 | 0 | **34%** |
41
+
42
+ Three readings, one per column group: delivery is strictly monotonic (zero
43
+ inversions in every subscription), depth 50 loses the same as depth 1 (the
44
+ 0.5–1 pp difference is executor noise, not recovery), and the loss tracks the
45
+ overtake probability — at 100 Hz the inter-message gap is 10 ms, so jitter
46
+ ±45 ms makes "the next message arrives first" the common case.
47
+
48
+ Field context (2026-08-17 CCNG drive): the operational 10 Hz streams have a
49
+ 100 ms gap against ~18 ms steady jitter, so this channel is minor there
50
+ (neighbour-delay overtake proxy: 9.6% around steady losses vs 6.0% baseline).
51
+ It grows quadratically relevant as rates rise or jitter grows — any stream
52
+ whose inter-message gap approaches the link's jitter pays it.
53
+
54
+ Verification: manual: run the command above from a source checkout with
55
+ Docker (build the image via the packaged `ros2docker.json.example` or pass
56
+ `--image`); the script prints and writes `out/reorder_results.json`.
57
+
58
+ ## Status
59
+
60
+ confirmed, 2026-08-18.
61
+
62
+ ## Publication notes
63
+
64
+ This is the mechanism behind the jitter side of
65
+ [jitter-loss-bandwidth-latency.md](jitter-loss-bandwidth-latency.md): the
66
+ jitter boundary is not radio noise but the reader's monotonicity rule. For
67
+ papers: plot delivered share vs jitter/gap ratio, and state explicitly that
68
+ history depth is not a mitigation here — pacing (larger gaps), FEC, or
69
+ reliable QoS are.
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """Does jitter-induced reordering become LOSS at a CycloneDDS best-effort
3
+ reader, and does history depth help?
4
+
5
+ Two containers (rcd image, cyclone RMW). Publisher shapes its egress with
6
+ pure delay+jitter (NO configured loss, no rate limit). Receiver subscribes
7
+ the same topic twice: depth 1 and depth 50.
8
+
9
+ Discriminates two mechanisms:
10
+ - loss seen by depth-50 sub = reader-proxy drop of overtaken samples
11
+ (depth cannot help) (or nothing, if cyclone delivers OOO)
12
+ - extra loss of depth-1 sub = history overwrite from arrival bunching
13
+ (depth DOES help)
14
+ - delivered inversions = out-of-order delivery (would mean no drop)
15
+
16
+ Modes: orchestrate (host) | pub | sub (in container).
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import subprocess
22
+ import sys
23
+ import time
24
+ from pathlib import Path
25
+
26
+ IMG = "ros-communication:latest" # any image with rclpy + rmw_cyclonedds + tc (--image overrides)
27
+ NET = "claude_reorder_net"
28
+ TOPIC = "/reorder_probe"
29
+
30
+
31
+ def mode_pub(args):
32
+ import rclpy
33
+ from rclpy.node import Node
34
+ from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
35
+ from std_msgs.msg import Int64
36
+
37
+ rclpy.init()
38
+ node = Node("reorder_pub")
39
+ qos = QoSProfile(reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=10)
40
+ pub = node.create_publisher(Int64, TOPIC, qos)
41
+ time.sleep(5.0) # discovery
42
+ n = int(args.rate * args.duration)
43
+ period = 1.0 / args.rate
44
+ t0 = time.monotonic()
45
+ for i in range(n):
46
+ while time.monotonic() < t0 + i * period:
47
+ time.sleep(0.0005)
48
+ m = Int64()
49
+ m.data = i
50
+ pub.publish(m)
51
+ time.sleep(3.0)
52
+ print(f"published {n}")
53
+ node.destroy_node()
54
+ rclpy.shutdown()
55
+
56
+
57
+ def mode_sub(args):
58
+ import rclpy
59
+ from rclpy.node import Node
60
+ from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
61
+ from std_msgs.msg import Int64
62
+
63
+ rclpy.init()
64
+ node = Node("reorder_sub")
65
+ got = {1: [], 50: []}
66
+
67
+ def make_cb(depth):
68
+ def cb(msg):
69
+ got[depth].append(msg.data)
70
+
71
+ return cb
72
+
73
+ subs = []
74
+ for depth in (1, 50):
75
+ qos = QoSProfile(reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=depth)
76
+ subs.append(node.create_subscription(Int64, TOPIC, make_cb(depth), qos))
77
+ t_end = time.monotonic() + args.duration + 20
78
+ while time.monotonic() < t_end:
79
+ rclpy.spin_once(node, timeout_sec=0.05)
80
+ out = {}
81
+ for depth, seqs in got.items():
82
+ inv = sum(1 for a, b in zip(seqs, seqs[1:], strict=False) if b < a)
83
+ out[f"depth{depth}"] = {
84
+ "received": len(seqs),
85
+ "unique": len(set(seqs)),
86
+ "inversions": inv,
87
+ "first": seqs[0] if seqs else None,
88
+ "last": max(seqs) if seqs else None,
89
+ }
90
+ Path(args.out).write_text(json.dumps(out, indent=2))
91
+ print(json.dumps(out))
92
+ node.destroy_node()
93
+ rclpy.shutdown()
94
+
95
+
96
+ def run(cmd):
97
+ r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
98
+ if r.returncode != 0:
99
+ raise RuntimeError(f"{cmd}\n{r.stderr or r.stdout}")
100
+ return r.stdout
101
+
102
+
103
+ def mode_orchestrate(args):
104
+ global IMG
105
+ IMG = args.image
106
+ me = Path(__file__).resolve()
107
+ outdir = me.parent / "out"
108
+ scenarios = [
109
+ ("jitter45", "tc qdisc replace dev eth0 root netem delay 50ms 45ms"),
110
+ ("jitter20", "tc qdisc replace dev eth0 root netem delay 50ms 20ms"),
111
+ ]
112
+ ip_sub, ip_pub = "10.97.0.2", "10.97.0.3"
113
+ ddsuri = (
114
+ "<CycloneDDS><Domain><General><AllowMulticast>false</AllowMulticast>"
115
+ '</General><Discovery><Peers><Peer address=\\"' + ip_sub + '\\"/>'
116
+ '<Peer address=\\"' + ip_pub + '\\"/></Peers>'
117
+ "<ParticipantIndex>auto</ParticipantIndex></Discovery></Domain></CycloneDDS>"
118
+ )
119
+ subprocess.run(f"docker network rm {NET}", shell=True, capture_output=True)
120
+ run(f"docker network create --subnet 10.97.0.0/24 {NET}")
121
+ results = {}
122
+ try:
123
+ for name, netem in scenarios:
124
+ subprocess.run("docker rm -f claude_reorder_pub claude_reorder_sub", shell=True, capture_output=True)
125
+ env = f'-e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp -e ROS_DOMAIN_ID=77 -e CYCLONEDDS_URI="{ddsuri}"'
126
+ run(
127
+ f"docker run -d --name claude_reorder_sub --network {NET} --ip {ip_sub} {env} "
128
+ f"-v {me.parent}:/w -v {outdir}:/out {IMG} bash -lc "
129
+ f"'python3 /w/{me.name} sub --duration {args.duration} "
130
+ f"--out /out/reorder_{name}.json'"
131
+ )
132
+ time.sleep(2)
133
+ run(
134
+ f"docker run -d --name claude_reorder_pub --network {NET} --ip {ip_pub} "
135
+ f"--cap-add NET_ADMIN {env} -v {me.parent}:/w {IMG} bash -lc "
136
+ f"'{netem} && python3 /w/{me.name} pub --rate {args.rate} "
137
+ f"--duration {args.duration}'"
138
+ )
139
+ run("docker wait claude_reorder_pub")
140
+ run("docker wait claude_reorder_sub")
141
+ data = json.loads((outdir / f"reorder_{name}.json").read_text())
142
+ sent = int(args.rate * args.duration)
143
+ for d in ("depth1", "depth50"):
144
+ span = (data[d]["last"] or 0) - (data[d]["first"] or 0) + 1
145
+ data[d]["loss_pct_of_span"] = round(100 * (1 - data[d]["unique"] / max(span, 1)), 2)
146
+ data["sent"] = sent
147
+ results[name] = data
148
+ print(f"== {name}: {json.dumps(data)}")
149
+ finally:
150
+ subprocess.run("docker rm -f claude_reorder_pub claude_reorder_sub", shell=True, capture_output=True)
151
+ subprocess.run(f"docker network rm {NET}", shell=True, capture_output=True)
152
+ (outdir / "reorder_results.json").write_text(json.dumps(results, indent=2))
153
+
154
+
155
+ def main():
156
+ ap = argparse.ArgumentParser()
157
+ sub = ap.add_subparsers(dest="mode", required=True)
158
+ for m in ("orchestrate", "pub", "sub"):
159
+ p = sub.add_parser(m)
160
+ p.add_argument("--rate", type=float, default=100.0)
161
+ p.add_argument("--duration", type=float, default=40.0)
162
+ p.add_argument("--image", default=IMG)
163
+ if m == "sub":
164
+ p.add_argument("--out", required=True)
165
+ args = ap.parse_args()
166
+ {"pub": mode_pub, "sub": mode_sub, "orchestrate": mode_orchestrate}[args.mode](args)
167
+
168
+
169
+ if __name__ == "__main__":
170
+ sys.exit(main() or 0)
@@ -77,6 +77,22 @@ missing tail after the last record is not judged either — without a sender-sid
77
77
  end marker it is indistinguishable from a normal shutdown (RFC 0003, honest
78
78
  limits).
79
79
 
80
+ A receiver-only view also cannot tell a broken link from a source that simply
81
+ stopped publishing — on the 2026-08-17 CCNG drive, "0 Hz collapses" on the
82
+ planner visualization were the source pausing during manual interventions while
83
+ the link delivered everything offered. When the instance also contains the
84
+ sending peer's own transit records for a stream (`direction: outbound`, stage
85
+ `com_out`), the report bins their `t_wrap` stamps into a per-bin **offered**
86
+ rate on the same publish timeline and each collapse event names its culprit in
87
+ `details.cause`: `source` when every collapsed bin was offered below the same
88
+ threshold — the collapse is fully explained by the source, and the markdown
89
+ line reads "source paused" (0 Hz offered) or "source slowed"; `link` when at
90
+ least one collapsed bin was offered at/above threshold and the link still
91
+ delivered below it; `unknown` without sender-side records. `min_offered_hz` /
92
+ `max_offered_hz` accompany the verdict. Sender-side rows feed only the offered
93
+ rate — they never enter the delivered view, where the cross-peer join would
94
+ upgrade receiver-inferred lost rows to delivered.
95
+
80
96
  Events across streams and kinds are grouped into **incidents** when their
81
97
  windows touch (merge gap defaults to one bin): one degradation moment, several
82
98
  lenses on it.
@@ -568,6 +568,27 @@ number being reported.
568
568
  - `bit_rate` (int)
569
569
  - `encoder_av_options` (string)
570
570
 
571
+ #### Receiver-side playout pacing (manual node)
572
+
573
+ A receiver that hands every packet to the decoder the moment it arrives turns
574
+ network delay jitter into display stutter. `com_py playout_pacer` re-times a
575
+ received packet stream to `stamp + budget` (budget adaptive above the fastest
576
+ observed path, clock-offset-proof; late packets pass through immediately, order
577
+ always preserved — the decoder chain sees every packet):
578
+
579
+ ```bash
580
+ ros2 run com_py playout_pacer --ros-args \
581
+ -p topic:=/cam/image/compressed/drop1of2/ffmpeg \
582
+ -p target_ms:=350.0 -p adaptive:=true
583
+ ```
584
+
585
+ It republishes on `<topic>/paced` plus `.../paced/budget_ms` and
586
+ `.../paced/queue_depth` debug topics; point the reverse republish (or any
587
+ decoder) at the paced name. On the 2026-08-17 CCNG field trace this removes
588
+ ~95 % of delay-caused >200 ms display gaps at ~100 ms median added age
589
+ (adaptive mode). Declarative wiring as a `transport.playout` block is tracked
590
+ in issue #284.
591
+
571
592
  `type: foxglove` supports (CompressedVideo):
572
593
  - `gop_size` (int)
573
594
  - `bit_rate` (int)
@@ -42,6 +42,11 @@ RATE_COLLAPSE = "rate_collapse"
42
42
  KEYFRAME_SHARE_MIN = 0.02
43
43
  KEYFRAME_SHARE_MAX = 0.4
44
44
 
45
+ # Real per-message FFMPEG flags win over the size heuristic once they cover
46
+ # most delivered rows; the slack tolerates isolated parse failures in a
47
+ # recording that otherwise carries the field.
48
+ KEYFRAME_FLAG_COVERAGE_MIN = 0.9
49
+
45
50
  CAVEAT = (
46
51
  "Context is correlation, not causation: a link-trace sample or profile step that overlaps an event "
47
52
  "does not prove it caused the event. The causal test is reproduction under an emulated profile."
@@ -228,13 +233,27 @@ class Stream:
228
233
 
229
234
 
230
235
  def _annotate_keyframes(stream: Stream, *, declared: bool) -> None:
231
- """Mark keyframes by size bimodality (transit records carry sizes, not FFMPEG flags).
232
-
233
- ``rosotacom.ffmpeg_packet.keyframes_by_size`` is the documented fallback for
234
- exactly this case. Declared FFMPEGPacket streams (type from status.json) are
235
- always annotated; other streams only when the flagged share looks like a real
236
- GOP structure, so uniform or spiky non-video streams are not mislabeled.
236
+ """Mark keyframes, preferring the real FFMPEG flags in the transit records.
237
+
238
+ The receiver parses ``AV_PKT_FLAG_KEY`` out of the wrapped FFMPEGPacket CDR
239
+ at com_in and writes it as ``keyframe`` on delivered rows, so when the field
240
+ covers (nearly) all delivered rows the real flags are authoritative the
241
+ field only exists where the wrapper's ``msg_type`` named an FFMPEGPacket,
242
+ which is a stronger declaration than status.json, so no share gate applies.
243
+
244
+ Recordings from before the field existed fall back to
245
+ ``rosotacom.ffmpeg_packet.keyframes_by_size``: declared FFMPEGPacket streams
246
+ (type from status.json) are always annotated; other streams only when the
247
+ flagged share looks like a real GOP structure, so uniform or spiky
248
+ non-video streams are not mislabeled.
237
249
  """
250
+ delivered = [record for record in stream.records if record.get("status") != "lost"]
251
+ flagged = [record for record in delivered if isinstance(record.get("keyframe"), bool)]
252
+ if delivered and len(flagged) / len(delivered) > KEYFRAME_FLAG_COVERAGE_MIN:
253
+ stream.keyframe_seqs = frozenset(int(record["seq"]) for record in flagged if record["keyframe"])
254
+ stream.keyframe_provenance = "ffmpeg_flags (transit records)"
255
+ return
256
+
238
257
  sized = [record for record in stream.records if record.get("size_bytes") is not None]
239
258
  if not sized:
240
259
  return
@@ -246,7 +265,7 @@ def _annotate_keyframes(stream: Stream, *, declared: bool) -> None:
246
265
  return
247
266
  stream.keyframe_seqs = frozenset(int(record["seq"]) for record, flag in zip(sized, flags, strict=True) if flag)
248
267
  origin = "declared FFMPEGPacket stream" if declared else f"size share {share * 100.0:.1f}%"
249
- stream.keyframe_provenance = f"size_bimodality ({origin}); transit records carry no FFMPEG flags"
268
+ stream.keyframe_provenance = f"size_bimodality ({origin}); these transit records carry no FFMPEG flags"
250
269
 
251
270
 
252
271
  def build_streams(records: list[dict[str, Any]], *, declared_ffmpeg_topics: Collection[str] = ()) -> list[Stream]:
@@ -342,6 +361,52 @@ def build_stream_bins(stream: Stream, *, run_start: float, bin_s: float) -> list
342
361
  return bins
343
362
 
344
363
 
364
+ def build_offered_timelines(
365
+ offered_records: list[dict[str, Any]],
366
+ streams: list[Stream],
367
+ timelines: dict[str, list[dict[str, Any]]],
368
+ *,
369
+ run_start: float,
370
+ bin_s: float,
371
+ ) -> dict[str, list[float]]:
372
+ """Per-bin offered rate per stream, from the sending peer's own transit records.
373
+
374
+ ``offered_records`` are ``direction: outbound`` rows (stage ``com_out``):
375
+ what the source handed to the link. Their ``t_wrap`` is the same header
376
+ stamp the receiving side records, so offered and delivered bins share one
377
+ publish timeline with no clock join. Streams without a single stamped
378
+ sender-side row are absent from the result — their collapse cause stays
379
+ unknowable.
380
+ """
381
+ grouped: dict[tuple[str, str, str], list[float]] = {}
382
+ for record in join_transit_records(offered_records):
383
+ if record.get("t_wrap") is None:
384
+ continue
385
+ key = (
386
+ str(record.get("source") or ""),
387
+ str(record.get("target") or ""),
388
+ str(record.get("topic") or ""),
389
+ )
390
+ grouped.setdefault(key, []).append(float(record["t_wrap"]))
391
+
392
+ offered: dict[str, list[float]] = {}
393
+ for stream in streams:
394
+ stamps = grouped.get((stream.source, stream.target, stream.topic))
395
+ bins = timelines[stream.label]
396
+ if not stamps or not bins:
397
+ continue
398
+ # bin_start_epoch is run_start + index * bin_s by construction; round
399
+ # instead of floor so float error cannot shift the alignment by one.
400
+ first_index = round((float(bins[0]["bin_start_epoch"]) - run_start) / bin_s)
401
+ counts = [0] * len(bins)
402
+ for stamp in stamps:
403
+ index = math.floor((stamp - run_start) / bin_s) - first_index
404
+ if 0 <= index < len(counts):
405
+ counts[index] += 1
406
+ offered[stream.label] = [round(count / bin_s, 3) for count in counts]
407
+ return offered
408
+
409
+
345
410
  # --------------------------------------------------------------------------- #
346
411
  # Event detection
347
412
  # --------------------------------------------------------------------------- #
@@ -470,7 +535,11 @@ def detect_latency_excursions(stream: Stream, config: DetectionConfig) -> list[D
470
535
 
471
536
 
472
537
  def detect_rate_collapses(
473
- stream: Stream, bins: list[dict[str, Any]], config: DetectionConfig
538
+ stream: Stream,
539
+ bins: list[dict[str, Any]],
540
+ config: DetectionConfig,
541
+ *,
542
+ offered_hz: list[float] | None = None,
474
543
  ) -> list[DegradationEvent]:
475
544
  """Runs of >= ``rate_collapse_min_bins`` interior bins delivering far below nominal.
476
545
 
@@ -478,45 +547,75 @@ def detect_rate_collapses(
478
547
  becomes lost rows when a later message arrives) and throttled-but-delivering
479
548
  regimes. Needs a nominal rate; the first and last bin of a stream are never
480
549
  judged (they are partial by construction).
550
+
551
+ ``offered_hz`` — one rate per entry of ``bins``, from the sending peer's own
552
+ records (``build_offered_timelines``) — names each event's culprit as
553
+ ``cause``: ``source`` when every collapsed bin was offered below the same
554
+ threshold (the collapse is fully explained by the source pausing or slowing;
555
+ the link delivered what it was given), ``link`` when at least one collapsed
556
+ bin was offered at/above threshold and the link still delivered below it,
557
+ and ``unknown`` without sender-side records. The receiver alone cannot tell
558
+ these apart: on the 2026-08-17 CCNG drive, 0 Hz "collapses" on the planner
559
+ visualization were the source pausing during manual interventions, not the
560
+ link.
481
561
  """
482
562
  nominal_hz = stream.nominal_hz
483
563
  if nominal_hz is None or len(bins) < 3:
484
564
  return []
565
+ threshold_hz = config.rate_collapse_fraction * nominal_hz
485
566
  events: list[DegradationEvent] = []
486
- run: list[dict[str, Any]] = []
567
+ run: list[tuple[int, dict[str, Any]]] = []
568
+
569
+ def cause_details() -> dict[str, Any]:
570
+ if offered_hz is None or len(offered_hz) != len(bins):
571
+ return {"cause": "unknown"}
572
+ rates = [float(offered_hz[index]) for index, _entry in run]
573
+ return {
574
+ "cause": "source" if max(rates) < threshold_hz else "link",
575
+ "min_offered_hz": round(min(rates), 3),
576
+ "max_offered_hz": round(max(rates), 3),
577
+ }
487
578
 
488
579
  def flush() -> None:
489
580
  if len(run) >= config.rate_collapse_min_bins:
581
+ entries = [entry for _index, entry in run]
490
582
  events.append(
491
583
  DegradationEvent(
492
584
  kind=RATE_COLLAPSE,
493
585
  stream=stream.label,
494
- start_epoch=float(run[0]["bin_start_epoch"]),
495
- end_epoch=float(run[-1]["bin_start_epoch"]) + config.bin_s,
496
- count=len(run),
586
+ start_epoch=float(entries[0]["bin_start_epoch"]),
587
+ end_epoch=float(entries[-1]["bin_start_epoch"]) + config.bin_s,
588
+ count=len(entries),
497
589
  details={
498
590
  "nominal_hz": round(nominal_hz, 3),
499
- "min_delivered_hz": min(float(entry["delivered_hz"]) for entry in run),
500
- "threshold_hz": round(config.rate_collapse_fraction * nominal_hz, 3),
591
+ "min_delivered_hz": min(float(entry["delivered_hz"]) for entry in entries),
592
+ "threshold_hz": round(threshold_hz, 3),
593
+ **cause_details(),
501
594
  },
502
595
  )
503
596
  )
504
597
  run.clear()
505
598
 
506
- for entry in bins[1:-1]:
507
- if float(entry["delivered_hz"]) < config.rate_collapse_fraction * nominal_hz:
508
- run.append(entry)
599
+ for index, entry in enumerate(bins[1:-1], start=1):
600
+ if float(entry["delivered_hz"]) < threshold_hz:
601
+ run.append((index, entry))
509
602
  else:
510
603
  flush()
511
604
  flush()
512
605
  return events
513
606
 
514
607
 
515
- def detect_events(stream: Stream, bins: list[dict[str, Any]], config: DetectionConfig) -> list[DegradationEvent]:
608
+ def detect_events(
609
+ stream: Stream,
610
+ bins: list[dict[str, Any]],
611
+ config: DetectionConfig,
612
+ *,
613
+ offered_hz: list[float] | None = None,
614
+ ) -> list[DegradationEvent]:
516
615
  events = [
517
616
  *detect_loss_bursts(stream, config),
518
617
  *detect_latency_excursions(stream, config),
519
- *detect_rate_collapses(stream, bins, config),
618
+ *detect_rate_collapses(stream, bins, config, offered_hz=offered_hz),
520
619
  ]
521
620
  events.sort(key=lambda event: (event.start_epoch, event.end_epoch, event.kind, event.stream))
522
621
  return events
@@ -843,8 +942,15 @@ def build_report(
843
942
  config = config or DetectionConfig()
844
943
  inputs = discover_inputs(instance_dir, peers=peers)
845
944
  records = load_transit_records(inputs.events_paths)
945
+ # The sending peer's own rows (direction=outbound, stage com_out) carry the
946
+ # offered rate. They must stay out of the delivered view: the cross-peer
947
+ # join keys on (source, topic, epoch, seq), so a sender row would upgrade
948
+ # the receiver's lost row to delivered and flatten delivered_hz to the
949
+ # offered rate.
950
+ offered_records = [record for record in records if record.get("direction") == "outbound"]
951
+ delivered_records = [record for record in records if record.get("direction") != "outbound"]
846
952
  declared = load_declared_ffmpeg_topics(inputs.status_paths)
847
- streams = build_streams(records, declared_ffmpeg_topics=declared)
953
+ streams = build_streams(delivered_records, declared_ffmpeg_topics=declared)
848
954
 
849
955
  send_spans = [(stream.send_time(stream.records[0]), stream.send_time(stream.records[-1])) for stream in streams]
850
956
  run_start = min((span[0] for span in send_spans), default=0.0)
@@ -859,9 +965,10 @@ def build_report(
859
965
  )
860
966
 
861
967
  timelines = {stream.label: build_stream_bins(stream, run_start=run_start, bin_s=config.bin_s) for stream in streams}
968
+ offered = build_offered_timelines(offered_records, streams, timelines, run_start=run_start, bin_s=config.bin_s)
862
969
  events: list[DegradationEvent] = []
863
970
  for stream in streams:
864
- events.extend(detect_events(stream, timelines[stream.label], config))
971
+ events.extend(detect_events(stream, timelines[stream.label], config, offered_hz=offered.get(stream.label)))
865
972
  events.sort(key=lambda event: (event.start_epoch, event.end_epoch, event.kind, event.stream))
866
973
 
867
974
  incidents = group_incidents(events, merge_gap_s=config.merge_gap_s)
@@ -881,7 +988,7 @@ def build_report(
881
988
 
882
989
  from .transit import summarize_transit_records
883
990
 
884
- summary = summarize_transit_records(records)["topics"]
991
+ summary = summarize_transit_records(delivered_records)["topics"]
885
992
  stream_entries: dict[str, Any] = {}
886
993
  for stream in streams:
887
994
  entry = dict(summary.get(stream.label, {}))
@@ -984,9 +1091,21 @@ def _event_line(event: dict[str, Any]) -> str:
984
1091
  f"baseline {_format_ms(details.get('baseline_ms'))} (threshold {_format_ms(details.get('threshold_ms'))}), "
985
1092
  f"{window}"
986
1093
  )
1094
+ cause = details.get("cause")
1095
+ if cause == "source":
1096
+ name = (
1097
+ "rate collapse (source paused)" if details.get("max_offered_hz") == 0.0 else "rate collapse (source slowed)"
1098
+ )
1099
+ elif cause == "link":
1100
+ name = "rate collapse (link)"
1101
+ else:
1102
+ name = "rate collapse"
1103
+ low, high = details.get("min_offered_hz"), details.get("max_offered_hz")
1104
+ offered = "" if low is None else (f", offered {low}Hz" if low == high else f", offered {low}-{high}Hz")
1105
+ suffix = "" if cause in ("source", "link") else "; cause unknown (no sender-side records)"
987
1106
  return (
988
- f"**rate collapse** on `{event['stream']}` — {event['count']} bins, delivered rate down to "
989
- f"{details.get('min_delivered_hz')}Hz vs nominal {details.get('nominal_hz')}Hz, {window}"
1107
+ f"**{name}** on `{event['stream']}` — {event['count']} bins, delivered rate down to "
1108
+ f"{details.get('min_delivered_hz')}Hz vs nominal {details.get('nominal_hz')}Hz{offered}, {window}{suffix}"
990
1109
  )
991
1110
 
992
1111