gps-plus-slam-app-framework 1.4.0 → 1.8.0

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 (243) hide show
  1. package/dist/absolute-orientation-BAxc15J6.d.ts +54 -0
  2. package/dist/{accuracy-circles-Pi2YeCrM.d.ts → accuracy-circles-DkWBkF5g.d.ts} +16 -6
  3. package/dist/{app-selectors-CrOucL00.d.ts → app-selectors-JqydaUMN.d.ts} +1 -1
  4. package/dist/ar/bresenham3d.d.ts +1 -1
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/capability-checker.d.ts +1 -1
  7. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  8. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  9. package/dist/ar/depth-grid-lookup.d.ts +1 -1
  10. package/dist/ar/depth-occluder.d.ts +127 -0
  11. package/dist/ar/depth-occluder.js +299 -0
  12. package/dist/ar/depth-sampler.d.ts +2 -2
  13. package/dist/ar/depth-sampler.js +24 -4
  14. package/dist/ar/depth-unprojection.d.ts +1 -1
  15. package/dist/ar/depth-unprojection.js +52 -21
  16. package/dist/ar/enable-gps-ar.d.ts +1 -1
  17. package/dist/ar/enable-gps-ar.js +1 -1
  18. package/dist/ar/frame-loop.d.ts +1 -1
  19. package/dist/ar/frame-loop.js +12 -2
  20. package/dist/ar/image-capture.d.ts +2 -2
  21. package/dist/ar/image-capture.js +1 -154
  22. package/dist/ar/image-quality.d.ts +2 -0
  23. package/dist/ar/image-quality.js +169 -0
  24. package/dist/ar/index.d.ts +32 -30
  25. package/dist/ar/index.js +7 -5
  26. package/dist/ar/occlusion-mesh-worker.d.ts +58 -0
  27. package/dist/ar/occlusion-mesh-worker.js +129 -0
  28. package/dist/ar/occupancy-grid.d.ts +2 -2
  29. package/dist/ar/occupancy-grid.js +287 -31
  30. package/dist/ar/occupancy-mesher.d.ts +2 -0
  31. package/dist/ar/occupancy-mesher.js +638 -0
  32. package/dist/ar/planar-pnp.d.ts +1 -1
  33. package/dist/ar/qr-debug-view.d.ts +1 -1
  34. package/dist/ar/qr-derived-pose.d.ts +1 -1
  35. package/dist/ar/qr-detection-controller.d.ts +1 -1
  36. package/dist/ar/qr-frontend.d.ts +1 -1
  37. package/dist/ar/qr-pose.d.ts +1 -1
  38. package/dist/ar/qr-size-depth-context.d.ts +2 -2
  39. package/dist/ar/qr-size-measurer.d.ts +1 -1
  40. package/dist/ar/replay-scene.d.ts +1 -1
  41. package/dist/ar/replay-scene.js +1 -1
  42. package/dist/ar/scene-node-names.d.ts +1 -1
  43. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  44. package/dist/ar/webxr-session.d.ts +4 -4
  45. package/dist/ar/webxr-session.js +2 -2
  46. package/dist/ar/xr-camera-texture.d.ts +1 -1
  47. package/dist/ar/xr-error-handler.d.ts +1 -1
  48. package/dist/ar/xr-frame-loop.d.ts +1 -1
  49. package/dist/ar/xr-frame-loop.js +12 -2
  50. package/dist/{ar-Dc72Ksiu.js → ar-BqCudtqE.js} +4 -3
  51. package/dist/{ar-world-group-alignment-DDLkgNf_.d.ts → ar-world-group-alignment-BlvGcIAk.d.ts} +1 -1
  52. package/dist/capture-motion-gate-BGoAMkmf.d.ts +24 -0
  53. package/dist/capture-motion-gate-_NdTSwKi.js +106 -0
  54. package/dist/cell-key-DIB4rOTV.js +51 -0
  55. package/dist/core/index.d.ts +2 -2
  56. package/dist/core/index.js +2 -2
  57. package/dist/{create-slam-app-store-Ce2FMKCp.d.ts → create-slam-app-store-D3nzh9Wb.d.ts} +50 -40
  58. package/dist/create-slam-app-store-DXiAnJwR.js +156 -0
  59. package/dist/{depth-grid-lookup-vMDrGTk_.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
  60. package/dist/{depth-sampler-DwfPYzS7.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
  61. package/dist/{depth-unprojection-CK3b9htH.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
  62. package/dist/{enable-gps-ar-CjZ1nEdu.d.ts → enable-gps-ar-DvGzBOrp.d.ts} +4 -4
  63. package/dist/{frame-conversions-D_Af8Dt3.d.ts → frame-conversions-C4bnNOEY.d.ts} +1 -1
  64. package/dist/{fused-path-C0FHe67t.d.ts → fused-path-C5pp0PJU.d.ts} +1 -1
  65. package/dist/geo/h3-proximity.d.ts +1 -1
  66. package/dist/geo/index.d.ts +1 -1
  67. package/dist/geodesic-angle-BTldfFgs.js +39 -0
  68. package/dist/{gps-anchor-C6I9NDHA.d.ts → gps-anchor-BjECcYnD.d.ts} +1 -1
  69. package/dist/{gps-ar-pose-sampler-DaHg2aPZ.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
  70. package/dist/{gps-event-coordinator-C7hwHyLA.d.ts → gps-event-coordinator-CHtIkhb7.d.ts} +21 -11
  71. package/dist/{image-capture-C2Feywet.d.ts → image-capture-Bp6Pd4Md.d.ts} +103 -3
  72. package/dist/image-capture-DVcuqMtX.js +374 -0
  73. package/dist/image-quality-BMx2ko9-.d.ts +158 -0
  74. package/dist/index-D4vrgV4b.d.ts +2 -0
  75. package/dist/index-D74JTX4I.d.ts +22 -0
  76. package/dist/{index-m_LneUSi.d.ts → index-DlsOo2IB.d.ts} +1 -1
  77. package/dist/{index-BvLo2poy.d.ts → index-cCQWuYqy.d.ts} +3 -3
  78. package/dist/index.d.ts +82 -77
  79. package/dist/index.js +15 -10
  80. package/dist/{leaflet-map-overlay-bnb2U1NQ.d.ts → leaflet-map-overlay-BOZFW5kT.d.ts} +64 -10
  81. package/dist/leaflet-map-overlay-CBWPvaX9.js +452 -0
  82. package/dist/lerp-utils-Qs8OhumS.d.ts +42 -0
  83. package/dist/map-data-D7rpMC6f.js +120 -0
  84. package/dist/{map-data-DdjPiSav.d.ts → map-data-PNfGKGWx.d.ts} +22 -2
  85. package/dist/{map-overlay-draw-CPVdAsFq.d.ts → map-overlay-draw-DiroaJed.d.ts} +1 -1
  86. package/dist/{null-storage-backend-CuSdYFsM.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
  87. package/dist/occlusion-mesh-b28lRJ6S.d.ts +194 -0
  88. package/dist/occupancy-grid-BV7RhB7I.d.ts +194 -0
  89. package/dist/occupancy-mesher-C2ecWhNp.d.ts +100 -0
  90. package/dist/{opfs-storage-DMtlt57S.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
  91. package/dist/{opfs-storage-backend-CeQAb0SD.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
  92. package/dist/{persistence-middleware-5eearhSV.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
  93. package/dist/{planar-pnp-cjthe7Be.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
  94. package/dist/{qr-debug-view-B8gTzjMm.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
  95. package/dist/{qr-derived-pose-DinKE600.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
  96. package/dist/{qr-detected-slice-Cpv-dg82.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
  97. package/dist/{qr-detection-controller-Cd2Ih4cC.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
  98. package/dist/{qr-frontend-DufZp2xS.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
  99. package/dist/{qr-pose-aggregation-CFlmLm6w.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
  100. package/dist/{qr-pose-aggregation-DXR9STHs.js → qr-pose-aggregation-DBiaLW0O.js} +1 -10
  101. package/dist/{qr-size-depth-context-onu36E9s.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
  102. package/dist/{qr-size-measurer-D7739ZpB.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
  103. package/dist/recording-options-vASCFzc5.d.ts +667 -0
  104. package/dist/{recording-replayer-CUEK82tM.d.ts → recording-replayer-D0V9WkmY.d.ts} +2 -2
  105. package/dist/{recording-slice-B-uCgGoh.d.ts → recording-slice-DTUtSVgd.d.ts} +2 -2
  106. package/dist/{replay-engine-BSVd7Zzb.d.ts → replay-engine-xdrUgWJi.d.ts} +1 -1
  107. package/dist/{replay-scene-CPt56pps.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
  108. package/dist/sensors/absolute-orientation.d.ts +2 -0
  109. package/dist/sensors/absolute-orientation.js +162 -0
  110. package/dist/sensors/gps-error-handler.d.ts +1 -1
  111. package/dist/sensors/gps.d.ts +1 -1
  112. package/dist/sensors/index.d.ts +5 -4
  113. package/dist/sensors/index.js +2 -1
  114. package/dist/sensors/permission-checker.d.ts +1 -1
  115. package/dist/state/app-selectors.d.ts +1 -1
  116. package/dist/state/combined-root-state.d.ts +1 -1
  117. package/dist/state/create-slam-app-store.d.ts +1 -1
  118. package/dist/state/create-slam-app-store.js +1 -74
  119. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  120. package/dist/state/gps-event-coordinator.d.ts +2 -2
  121. package/dist/state/gps-event-coordinator.js +27 -17
  122. package/dist/state/index.d.ts +20 -20
  123. package/dist/state/index.js +4 -4
  124. package/dist/state/persistence-middleware.d.ts +1 -1
  125. package/dist/state/persistence-middleware.js +30 -19
  126. package/dist/state/qr-detected-slice.d.ts +3 -3
  127. package/dist/state/qr-detected-slice.js +1 -1
  128. package/dist/state/recording-options.d.ts +2 -2
  129. package/dist/state/recording-options.js +286 -25
  130. package/dist/state/recording-replayer.d.ts +2 -2
  131. package/dist/state/recording-replayer.js +1 -1
  132. package/dist/state/recording-slice.d.ts +1 -1
  133. package/dist/state/replay-engine.d.ts +1 -1
  134. package/dist/state/store-subscribers.d.ts +2 -2
  135. package/dist/state/store-subscribers.js +2 -1
  136. package/dist/state/subscribe-to-selector.d.ts +1 -1
  137. package/dist/state/tracking-quality.d.ts +2 -2
  138. package/dist/state/tracking-quality.js +49 -128
  139. package/dist/state/tracking-slice.d.ts +1 -1
  140. package/dist/{state-IEw0ZsBu.js → state-BeKvn4B-.js} +1 -1
  141. package/dist/storage/file-system-utils.d.ts +1 -1
  142. package/dist/storage/index.d.ts +8 -8
  143. package/dist/storage/null-storage-backend.d.ts +1 -1
  144. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  145. package/dist/storage/opfs-storage.d.ts +1 -1
  146. package/dist/storage/opfs-storage.js +25 -1
  147. package/dist/storage/storage-backend.d.ts +1 -1
  148. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  149. package/dist/storage/zip-export.d.ts +1 -1
  150. package/dist/storage/zip-reader.d.ts +1 -1
  151. package/dist/{storage-backend-CTJHNdMp.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
  152. package/dist/{store-subscribers-BZP81hjh.d.ts → store-subscribers-DScA7tdT.d.ts} +2 -2
  153. package/dist/{subscribe-to-selector-Bp3sLx8L.d.ts → subscribe-to-selector-B9ylM5Oh.d.ts} +1 -1
  154. package/dist/test-utils/browser-mocks.d.ts +1 -1
  155. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  156. package/dist/{tracking-slice-DJZ9qeGg.d.ts → tracking-slice-DMkHlbny.d.ts} +1 -1
  157. package/dist/types/ar-types.d.ts +1 -1
  158. package/dist/types/geo-types.d.ts +1 -1
  159. package/dist/types/index.d.ts +2 -2
  160. package/dist/utils/concurrency.d.ts +1 -1
  161. package/dist/utils/failure-tracker.d.ts +1 -1
  162. package/dist/utils/format-file-size.d.ts +1 -1
  163. package/dist/utils/fused-path.d.ts +1 -1
  164. package/dist/utils/index.d.ts +8 -7
  165. package/dist/utils/index.js +2 -1
  166. package/dist/utils/list-formatter.d.ts +1 -1
  167. package/dist/utils/logger.d.ts +1 -1
  168. package/dist/visualization/accuracy-circles.d.ts +1 -1
  169. package/dist/visualization/accuracy-circles.js +17 -8
  170. package/dist/visualization/alignment-lerper.d.ts +1 -1
  171. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  172. package/dist/visualization/ar-world-group-alignment.js +1 -1
  173. package/dist/visualization/camera-follower.d.ts +1 -1
  174. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  175. package/dist/visualization/frame-conversions.d.ts +1 -1
  176. package/dist/visualization/frustum-visibility.d.ts +1 -1
  177. package/dist/visualization/gps-anchor.d.ts +1 -1
  178. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  179. package/dist/visualization/gps-event-markers.d.ts +1 -1
  180. package/dist/visualization/gps-event-markers.js +1 -1
  181. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  182. package/dist/visualization/index.d.ts +20 -19
  183. package/dist/visualization/index.js +4 -3
  184. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  185. package/dist/visualization/leaflet-map-overlay.js +1 -282
  186. package/dist/visualization/lerp-utils.d.ts +2 -2
  187. package/dist/visualization/lerp-utils.js +27 -3
  188. package/dist/visualization/map-data.d.ts +1 -1
  189. package/dist/visualization/map-data.js +1 -32
  190. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  191. package/dist/visualization/map-overlay-draw.js +24 -2
  192. package/dist/visualization/map-overlay.d.ts +1 -1
  193. package/dist/visualization/occluder-mesh-driver.d.ts +130 -0
  194. package/dist/visualization/occluder-mesh-driver.js +219 -0
  195. package/dist/visualization/occlusion-mesh.d.ts +2 -0
  196. package/dist/visualization/occlusion-mesh.js +437 -0
  197. package/dist/visualization/three-dispose.d.ts +1 -1
  198. package/dist/visualization/vis-colors.d.ts +1 -1
  199. package/dist/{webxr-session-CJF9Nijp.js → webxr-session-BCP2XK7k.js} +110 -14
  200. package/dist/{webxr-session-COsOjJwn.d.ts → webxr-session-cRG2SYAQ.d.ts} +83 -10
  201. package/package.json +4 -4
  202. package/dist/index-eaI1Z1KR.d.ts +0 -2
  203. package/dist/lerp-utils-kLxMJ2Xj.d.ts +0 -20
  204. package/dist/occupancy-grid-Bvi2Gw1U.d.ts +0 -82
  205. package/dist/recording-options-D93kksJU.d.ts +0 -369
  206. /package/dist/{alignment-lerper-BUTsQ2yu.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
  207. /package/dist/{ar-types-BckI1E-O.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
  208. /package/dist/{bresenham3d-ByjcmEgM.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
  209. /package/dist/{camera-blit-capture-CqWe1G7s.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
  210. /package/dist/{camera-follower-BTb2Yp12.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
  211. /package/dist/{capability-checker-CXqoJYtb.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
  212. /package/dist/{capture-failure-tracker-BL5SoG2a.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
  213. /package/dist/{chromium-camera-access-workaround-BNK189P0.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
  214. /package/dist/{concurrency-DqAlKX4j.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
  215. /package/dist/{css3d-renderer-manager-m-ab19vp.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
  216. /package/dist/{failure-tracker-Cnc_xWUw.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
  217. /package/dist/{file-system-utils-Bs7lpeVP.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
  218. /package/dist/{format-file-size-BFUWvir1.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
  219. /package/dist/{frame-loop-BEO9_q3k.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
  220. /package/dist/{frustum-visibility-D_RGmH98.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
  221. /package/dist/{geo-types-DqiBzPO9.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
  222. /package/dist/{gps-C5inWeNV.d.ts → gps-CDKDQokE.d.ts} +0 -0
  223. /package/dist/{gps-compass-cubes-BExxrWJ-.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
  224. /package/dist/{gps-error-handler-C2bS69oJ.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
  225. /package/dist/{gps-event-markers-Dd0G2bD0.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
  226. /package/dist/{h3-proximity-B57C6rYL.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
  227. /package/dist/{hit-test-reticle-Dqak9hiR.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
  228. /package/dist/{list-formatter-ct8txwvR.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
  229. /package/dist/{logger-CzQrCe6C.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
  230. /package/dist/{map-overlay-Dci4yvm3.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
  231. /package/dist/{permission-checker-CtPtPQRq.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
  232. /package/dist/{qr-pose-Bt2reSw7.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
  233. /package/dist/{scene-node-names-CdNeQjOE.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
  234. /package/dist/{session-disposers-D8wnO-uW.js → session-disposers-BPrMplIC.js} +0 -0
  235. /package/dist/{three-dispose-BCZkSJp2.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
  236. /package/dist/{vis-colors-BZsSLI4i.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
  237. /package/dist/{webxr-nue-basis-CW1wsbm6.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
  238. /package/dist/{xr-camera-texture-BmrP7iNZ.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
  239. /package/dist/{xr-error-handler-BEjRgZjt.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
  240. /package/dist/{xr-frame-loop-K8ebdjLw.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
  241. /package/dist/{zip-coverage-embed-tFohI2J4.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
  242. /package/dist/{zip-export-DDWXInTR.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
  243. /package/dist/{zip-reader-Q2ue6zlC.d.ts → zip-reader-CvruaGvD.d.ts} +0 -0
@@ -1,4 +1,5 @@
1
- import { selectLastSensorOrientation, selectLastValidPose, selectTrackingPhase } from "./tracking-slice.js";
1
+ import { t as geodesicAngleRad } from "../geodesic-angle-BTldfFgs.js";
2
+ import { selectTrackingPhase } from "./tracking-slice.js";
2
3
  import { selectAlignmentMatrix, selectGpsPositions, selectOdometryPositions, selectZeroReference } from "./app-selectors.js";
3
4
  import { calcGpsCoords, distanceInMeters } from "gps-plus-slam-js";
4
5
  import { mat4, quat, vec3 } from "gl-matrix";
@@ -13,19 +14,15 @@ const DEFAULT_TRACKING_QUALITY_OPTIONS = {
13
14
  coverageDirectionSpreadDeg: 90,
14
15
  convergenceRotationWarnDeg: 6,
15
16
  convergenceTranslationWarnM: 2,
16
- compassWarnDeg: 15,
17
- compassFailDeg: 35,
18
17
  warmupMinObservations: 10,
19
18
  warmupMinCoverage: .5,
20
19
  residualConfidenceTargetM: 3,
21
20
  gpsAccuracyFloorM: 1,
22
- firstAgreementMinStreak: 3,
23
21
  degradedHoldoff: 3,
24
22
  convergenceEmaAlpha: .3
25
23
  };
26
24
  const initialState = {
27
25
  recentAlignments: [],
28
- firstAgreementObservationIndex: null,
29
26
  report: null,
30
27
  degradedConsecutiveCount: 0,
31
28
  smoothedConvergence: null
@@ -47,11 +44,6 @@ function emaBlend(prev, raw, alpha) {
47
44
  if (prev === null) return raw;
48
45
  return prev + (Number.isFinite(alpha) ? alpha : 1) * (raw - prev);
49
46
  }
50
- function wrapToHalfCircle(deg) {
51
- let d = (deg + 180) % 360 - 180;
52
- if (d < -180) d += 360;
53
- return d;
54
- }
55
47
  function bearingDeg(north, east) {
56
48
  let deg = Math.atan2(east, north) * 180 / Math.PI;
57
49
  if (deg < 0) deg += 360;
@@ -88,6 +80,20 @@ function rampUp(value, low, high) {
88
80
  return (value - low) / (high - low);
89
81
  }
90
82
  /**
83
+ * True iff the matrix is well-formed: exactly 16 elements, all finite (no
84
+ * NaN/Infinity). The length check matters because `computeConvergence` scores a
85
+ * pair as corrupt (fail side) only when this returns false; without it a finite
86
+ * but wrong-length snapshot (`AlignmentSnapshot.matrix` is typed `number[]`,
87
+ * not a 16-tuple) would slip past here, then hit `matrixDelta`'s own length
88
+ * guard which returns ZERO deltas = "perfectly stable" — inflating the score
89
+ * instead of degrading it (the same trap the non-finite guard avoids).
90
+ */
91
+ function isFiniteMatrix(m) {
92
+ if (m.length !== 16) return false;
93
+ for (let i = 0; i < m.length; i++) if (!Number.isFinite(m[i])) return false;
94
+ return true;
95
+ }
96
+ /**
91
97
  * §4.1 matrix-history convergence over the buffered snapshots.
92
98
  *
93
99
  * Reuses the same kernel idea as `computeStabilityDelta` from the
@@ -118,7 +124,15 @@ function computeConvergence(snapshots, options = {}) {
118
124
  let sumTransM = 0;
119
125
  let pairCount = 0;
120
126
  for (let i = 1; i < snapshots.length; i++) {
121
- const { rotationDeltaDeg, translationDeltaM } = matrixDelta(snapshots[i - 1].matrix, snapshots[i].matrix);
127
+ const prevM = snapshots[i - 1].matrix;
128
+ const currM = snapshots[i].matrix;
129
+ if (!isFiniteMatrix(prevM) || !isFiniteMatrix(currM)) {
130
+ sumRotDeg += rotFail;
131
+ sumTransM += transFail;
132
+ pairCount += 1;
133
+ continue;
134
+ }
135
+ const { rotationDeltaDeg, translationDeltaM } = matrixDelta(prevM, currM);
122
136
  sumRotDeg += Math.abs(rotationDeltaDeg);
123
137
  sumTransM += Math.abs(translationDeltaM);
124
138
  pairCount += 1;
@@ -137,7 +151,8 @@ function computeConvergence(snapshots, options = {}) {
137
151
  * the column-major layout that gl-matrix and the framework use.
138
152
  *
139
153
  * Rotation: extract the orientation quaternion with `mat4.getRotation`
140
- * and compute the relative angle via `quat.getAngle` (degrees).
154
+ * and compute the relative angle via the shared `geodesicAngleRad` kernel
155
+ * (radians, converted to degrees here).
141
156
  * Translation: extract the origin column with `mat4.getTranslation` and
142
157
  * compute the Euclidean distance.
143
158
  *
@@ -161,15 +176,16 @@ function matrixDelta(prev, curr) {
161
176
  mat4.getRotation(currQuat, currMat);
162
177
  quat.normalize(prevQuat, prevQuat);
163
178
  quat.normalize(currQuat, currQuat);
164
- const angleRad = quat.getAngle(prevQuat, currQuat);
165
- const rotationDeltaDeg = Number.isNaN(angleRad) ? 0 : angleRad * 180 / Math.PI;
179
+ const angleRad = geodesicAngleRad(prevQuat, currQuat);
180
+ const rotationDeltaDeg = Number.isFinite(angleRad) ? angleRad * 180 / Math.PI : 0;
166
181
  const prevT = vec3.create();
167
182
  const currT = vec3.create();
168
183
  mat4.getTranslation(prevT, prevMat);
169
184
  mat4.getTranslation(currT, currMat);
185
+ const rawTranslation = vec3.distance(prevT, currT);
170
186
  return {
171
187
  rotationDeltaDeg,
172
- translationDeltaM: vec3.distance(prevT, currT)
188
+ translationDeltaM: Number.isFinite(rawTranslation) ? rawTranslation : 0
173
189
  };
174
190
  }
175
191
  /**
@@ -319,56 +335,6 @@ function computeCoverage(odometryPositions, options = {}) {
319
335
  };
320
336
  }
321
337
  /**
322
- * §4.3 — Compare the alignment matrix's mapping of AR-forward to ENU
323
- * against the device compass heading. Returns `score = null` whenever
324
- * the sensor's `absolute !== true` so the aggregate excludes the
325
- * sub-score (per Q2 in the plan).
326
- *
327
- * AR-forward in our `ARPose` convention is `(0, 0, -1)` (right-handed,
328
- * Y-up). We rotate that by the alignment's 3×3 rotation, take its
329
- * North/East components, and compare against the compass alpha.
330
- */
331
- function computeCompassAgreement(alignmentMatrix, sensorOrientation, arPose, options = {}) {
332
- const warn = options.warnDeg ?? DEFAULT_TRACKING_QUALITY_OPTIONS.compassWarnDeg;
333
- const fail = options.failDeg ?? DEFAULT_TRACKING_QUALITY_OPTIONS.compassFailDeg;
334
- if (!alignmentMatrix || !sensorOrientation || !arPose) return {
335
- score: null,
336
- headingDeltaDeg: null
337
- };
338
- if (sensorOrientation.absolute !== true) return {
339
- score: null,
340
- headingDeltaDeg: null
341
- };
342
- const q = arPose.orientation;
343
- const fx = 0;
344
- const fy = 0;
345
- const fz = -1;
346
- const x = q.x, y = q.y, z = q.z, w = q.w;
347
- const tx = 2 * (y * fz - z * fy);
348
- const ty = 2 * (z * fx - x * fz);
349
- const tz = 2 * (x * fy - y * fx);
350
- const arForwardLocal = [
351
- fx + w * tx + (y * tz - z * ty),
352
- fy + w * ty + (z * tx - x * tz),
353
- fz + w * tz + (x * ty - y * tx)
354
- ];
355
- const m = alignmentMatrix;
356
- const enuN = m[0] * arForwardLocal[0] + m[4] * arForwardLocal[1] + m[8] * arForwardLocal[2];
357
- m[1] * arForwardLocal[0] + m[5] * arForwardLocal[1] + m[9] * arForwardLocal[2];
358
- const enuE = m[2] * arForwardLocal[0] + m[6] * arForwardLocal[1] + m[10] * arForwardLocal[2];
359
- if (Math.hypot(enuN, enuE) < 1e-6) return {
360
- score: null,
361
- headingDeltaDeg: null
362
- };
363
- const alignmentHeadingDeg = bearingDeg(enuN, enuE);
364
- const compassHeadingDeg = sensorOrientation.alpha;
365
- const delta = Math.abs(wrapToHalfCircle(alignmentHeadingDeg - compassHeadingDeg));
366
- return {
367
- score: rampDown(delta, warn, fail),
368
- headingDeltaDeg: delta
369
- };
370
- }
371
- /**
372
338
  * §4.6 — Maximum lateral distance between raw GPS positions and the
373
339
  * positions you would predict by transforming the matching odometry
374
340
  * positions through the current alignment. Diagnostic only — never
@@ -418,9 +384,6 @@ const trackingQualitySlice = createSlice({
418
384
  reportUpdated(state, action) {
419
385
  state.report = action.payload;
420
386
  },
421
- firstAgreementReached(state, action) {
422
- state.firstAgreementObservationIndex = action.payload;
423
- },
424
387
  /** §4.8 — update the hysteresis counter for ok → degraded holdoff. */
425
388
  degradedCountUpdated(state, action) {
426
389
  state.degradedConsecutiveCount = action.payload;
@@ -439,7 +402,7 @@ const trackingQualitySlice = createSlice({
439
402
  }
440
403
  }
441
404
  });
442
- const { snapshotPushed, snapshotsTrimmed, reportUpdated, firstAgreementReached, degradedCountUpdated, resetTrackingQuality } = trackingQualitySlice.actions;
405
+ const { snapshotPushed, snapshotsTrimmed, reportUpdated, degradedCountUpdated, resetTrackingQuality } = trackingQualitySlice.actions;
443
406
  const { smoothedConvergenceUpdated } = trackingQualitySlice.actions;
444
407
  const trackingQualityReducer = trackingQualitySlice.reducer;
445
408
  function selectTrackingQuality(state) {
@@ -448,9 +411,6 @@ function selectTrackingQuality(state) {
448
411
  function selectRecentAlignments(state) {
449
412
  return state.trackingQuality?.recentAlignments ?? EMPTY_SNAPSHOTS;
450
413
  }
451
- function selectFirstAgreementObservationIndex(state) {
452
- return state.trackingQuality?.firstAgreementObservationIndex ?? null;
453
- }
454
414
  const EMPTY_SNAPSHOTS = Object.freeze([]);
455
415
  /**
456
416
  * Pure function — produces a {@link TrackingQualityReport} from a root
@@ -474,10 +434,7 @@ function computeTrackingQualityReport(rootState, options = {}) {
474
434
  const odometryPositions = selectOdometryPositions(rootState);
475
435
  const zeroRef = selectZeroReference(rootState);
476
436
  const trackingPhase = selectTrackingPhase(rootState);
477
- const sensorOrientation = selectLastSensorOrientation(rootState);
478
- const lastPose = selectLastValidPose(rootState);
479
437
  const snapshots = selectRecentAlignments(rootState);
480
- const firstAgreementIdx = selectFirstAgreementObservationIndex(rootState);
481
438
  const coverage = computeCoverage(odometryPositions, {
482
439
  walkedDistanceM: opts.coverageWalkedDistanceM,
483
440
  directionSpreadDeg: opts.coverageDirectionSpreadDeg
@@ -492,15 +449,10 @@ function computeTrackingQualityReport(rootState, options = {}) {
492
449
  rotationWarnDeg: opts.convergenceRotationWarnDeg,
493
450
  translationWarnM: opts.convergenceTranslationWarnM
494
451
  });
495
- const compass = computeCompassAgreement(alignmentMatrix, sensorOrientation, lastPose, {
496
- warnDeg: opts.compassWarnDeg,
497
- failDeg: opts.compassFailDeg
498
- });
499
452
  const gpsVsFusedMaxDivergenceM = computeGpsVsFusedDivergence(alignmentMatrix, gpsPositions, odometryPositions, zeroRef);
500
453
  const subScores = {
501
454
  convergence: clamp01(emaBlend(rootState.trackingQuality?.smoothedConvergence ?? null, convergence.score, opts.convergenceEmaAlpha)),
502
455
  residualConsensus: clamp01(residual.score),
503
- compassAgreement: compass.score === null ? null : clamp01(compass.score),
504
456
  gpsAccuracy: clamp01(gpsAccuracy.score),
505
457
  coverage: clamp01(coverage.score)
506
458
  };
@@ -510,7 +462,6 @@ function computeTrackingQualityReport(rootState, options = {}) {
510
462
  subScores.gpsAccuracy,
511
463
  subScores.coverage
512
464
  ];
513
- if (subScores.compassAgreement !== null) aggregateInputs.push(subScores.compassAgreement);
514
465
  const confidence = clamp01(Math.min(...aggregateInputs));
515
466
  const observationsSeen = gpsPositions.length;
516
467
  let state;
@@ -529,8 +480,6 @@ function computeTrackingQualityReport(rootState, options = {}) {
529
480
  medianRecentGpsAccuracyM: gpsAccuracy.medianM,
530
481
  walkedDistanceM: coverage.walkedDistanceM,
531
482
  directionSpreadDeg: coverage.directionSpreadDeg,
532
- headingDeltaDeg: compass.headingDeltaDeg,
533
- compassDriftDetected: firstAgreementIdx !== null && compass.headingDeltaDeg !== null && compass.headingDeltaDeg > opts.compassWarnDeg,
534
483
  observationsSeen,
535
484
  gpsVsFusedMaxDivergenceM
536
485
  }
@@ -540,15 +489,14 @@ function computeTrackingQualityReport(rootState, options = {}) {
540
489
  * Per-field tolerances for the {@link reportsEqual} dispatch gate.
541
490
  *
542
491
  * Why this exists: `tracking/poseReceived` fires on *every* XR frame
543
- * (30–60 fps). The §4.3 compass cross-check consumes the live AR pose
544
- * and sensor heading, so `subScores.compassAgreement` and
545
- * `diagnostics.headingDeltaDeg` (and, when compass is the minimum,
546
- * `confidence`) jitter by imperceptible floating-point amounts on a
547
- * held-still device. With a strict `!==` comparison every such frame
548
- * produced a fresh `reportUpdated` dispatch high Redux churn and a
549
- * HUD re-render at frame rate. The tolerances below quantise those
550
- * sub-perceptual changes so the gate only fires when the user-visible
551
- * quality actually moved.
492
+ * (30–60 fps). The per-frame recompute reuses GPS/odometry windows that
493
+ * are unchanged between pose frames, so the float sub-scores and
494
+ * diagnostics (and `confidence`) jitter by imperceptible floating-point
495
+ * amounts on a held-still device. With a strict `!==` comparison every
496
+ * such frame produced a fresh `reportUpdated` dispatch high Redux
497
+ * churn and a HUD re-render at frame rate. The tolerances below quantise
498
+ * those sub-perceptual changes so the gate only fires when the
499
+ * user-visible quality actually moved.
552
500
  *
553
501
  * The gate compares the freshly computed report against the *last
554
502
  * dispatched* report (the stored `prev`), not against the previous
@@ -567,10 +515,9 @@ const REPORT_METRE_EPSILON_M = .001;
567
515
  * (both `null` ⇒ equal; exactly one `null` ⇒ different) and otherwise
568
516
  * compares with an absolute epsilon. Non-finite values are compared with
569
517
  * {@link Object.is}, so a genuine finite↔NaN/Infinity transition still
570
- * fires a dispatch, but a *persistently* NaN diagnostic (the only
571
- * unguarded path is a NaN compass `alpha` leaking into
572
- * `diagnostics.headingDeltaDeg`) does NOT churn the gate every frame —
573
- * `Object.is(NaN, NaN)` is `true` whereas `NaN === NaN` is `false`.
518
+ * fires a dispatch, but a *persistently* NaN diagnostic does NOT churn
519
+ * the gate every frame — `Object.is(NaN, NaN)` is `true` whereas
520
+ * `NaN === NaN` is `false`.
574
521
  */
575
522
  function nearlyEqual(a, b, eps) {
576
523
  if (a === null || b === null) return a === b;
@@ -585,8 +532,8 @@ function nearlyEqual(a, b, eps) {
585
532
  *
586
533
  * Float fields use the per-field tolerances above
587
534
  * ({@link REPORT_SCORE_EPSILON} et al.) so imperceptible per-frame
588
- * compass jitter does not churn the store — see the constants' doc
589
- * comment for the rationale.
535
+ * jitter does not churn the store — see the constants' doc comment for
536
+ * the rationale.
590
537
  */
591
538
  function reportsEqual(a, b) {
592
539
  if (a === b) return true;
@@ -595,10 +542,10 @@ function reportsEqual(a, b) {
595
542
  if (!nearlyEqual(a.confidence, b.confidence, REPORT_SCORE_EPSILON)) return false;
596
543
  const sa = a.subScores;
597
544
  const sb = b.subScores;
598
- if (!nearlyEqual(sa.convergence, sb.convergence, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.residualConsensus, sb.residualConsensus, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.compassAgreement, sb.compassAgreement, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.gpsAccuracy, sb.gpsAccuracy, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.coverage, sb.coverage, REPORT_SCORE_EPSILON)) return false;
545
+ if (!nearlyEqual(sa.convergence, sb.convergence, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.residualConsensus, sb.residualConsensus, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.gpsAccuracy, sb.gpsAccuracy, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.coverage, sb.coverage, REPORT_SCORE_EPSILON)) return false;
599
546
  const da = a.diagnostics;
600
547
  const db = b.diagnostics;
601
- return da.compassDriftDetected === db.compassDriftDetected && da.observationsSeen === db.observationsSeen && nearlyEqual(da.recentSumRotationDeltaDeg, db.recentSumRotationDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.directionSpreadDeg, db.directionSpreadDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.headingDeltaDeg, db.headingDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.recentSumTranslationDeltaM, db.recentSumTranslationDeltaM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianResidualM, db.medianResidualM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianRecentGpsAccuracyM, db.medianRecentGpsAccuracyM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.walkedDistanceM, db.walkedDistanceM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.gpsVsFusedMaxDivergenceM, db.gpsVsFusedMaxDivergenceM, REPORT_METRE_EPSILON_M);
548
+ return da.observationsSeen === db.observationsSeen && nearlyEqual(da.recentSumRotationDeltaDeg, db.recentSumRotationDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.directionSpreadDeg, db.directionSpreadDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.recentSumTranslationDeltaM, db.recentSumTranslationDeltaM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianResidualM, db.medianResidualM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianRecentGpsAccuracyM, db.medianRecentGpsAccuracyM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.walkedDistanceM, db.walkedDistanceM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.gpsVsFusedMaxDivergenceM, db.gpsVsFusedMaxDivergenceM, REPORT_METRE_EPSILON_M);
602
549
  }
603
550
  function matricesNearlyEqual(a, b) {
604
551
  if (a.length !== b.length) return false;
@@ -650,16 +597,14 @@ function createTrackingQualityListenerMiddleware(options = {}) {
650
597
  ...DEFAULT_TRACKING_QUALITY_OPTIONS,
651
598
  ...options
652
599
  };
653
- let compassStreak = 0;
654
600
  const listenerMiddleware = createListenerMiddleware();
655
601
  listenerMiddleware.startListening({
656
602
  predicate: (action) => inputActionPredicate(action),
657
603
  effect: (action, api) => {
658
604
  const state = api.getState();
659
605
  if (action.type === TRACKING_QUALITY_INPUT_ACTIONS.startSession || action.type === TRACKING_QUALITY_INPUT_ACTIONS.resetTracking) {
660
- compassStreak = 0;
661
606
  const tq = state.trackingQuality;
662
- if (tq && (tq.recentAlignments.length > 0 || tq.firstAgreementObservationIndex !== null || tq.report !== null || tq.degradedConsecutiveCount > 0)) api.dispatch(resetTrackingQuality());
607
+ if (tq && (tq.recentAlignments.length > 0 || tq.report !== null || tq.degradedConsecutiveCount > 0)) api.dispatch(resetTrackingQuality());
663
608
  return;
664
609
  }
665
610
  if (action.type === TRACKING_QUALITY_INPUT_ACTIONS.gpsRecorded || action.type === TRACKING_QUALITY_INPUT_ACTIONS.setZeroPos) {
@@ -678,30 +623,6 @@ function createTrackingQualityListenerMiddleware(options = {}) {
678
623
  }
679
624
  }
680
625
  }
681
- const midState = api.getState();
682
- const midTq = midState.trackingQuality;
683
- if (midTq && midTq.firstAgreementObservationIndex === null) {
684
- const snapshots = selectRecentAlignments(midState);
685
- if (snapshots.length >= 2) {
686
- const conv = computeConvergence(snapshots, {
687
- rotationWarnDeg: opts.convergenceRotationWarnDeg,
688
- translationWarnM: opts.convergenceTranslationWarnM
689
- });
690
- const sensorOr = selectLastSensorOrientation(midState);
691
- const pose = selectLastValidPose(midState);
692
- const compass = computeCompassAgreement(selectAlignmentMatrix(midState), sensorOr, pose, {
693
- warnDeg: opts.compassWarnDeg,
694
- failDeg: opts.compassFailDeg
695
- });
696
- if (conv.score >= .7 && compass.headingDeltaDeg !== null && compass.headingDeltaDeg <= opts.compassWarnDeg) {
697
- compassStreak += 1;
698
- if (compassStreak >= opts.firstAgreementMinStreak) {
699
- const obsCount = selectGpsPositions(midState).length;
700
- api.dispatch(firstAgreementReached(obsCount));
701
- }
702
- } else compassStreak = 0;
703
- }
704
- }
705
626
  const next = api.getState();
706
627
  const report = computeTrackingQualityReport(next, opts);
707
628
  const prev = next.trackingQuality?.report ?? null;
@@ -720,4 +641,4 @@ function createTrackingQualityListenerMiddleware(options = {}) {
720
641
  return listenerMiddleware.middleware;
721
642
  }
722
643
  //#endregion
723
- export { DEFAULT_TRACKING_QUALITY_OPTIONS, computeCompassAgreement, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, firstAgreementReached, matrixDelta, reportUpdated, resetTrackingQuality, selectFirstAgreementObservationIndex, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
644
+ export { DEFAULT_TRACKING_QUALITY_OPTIONS, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, matrixDelta, reportUpdated, resetTrackingQuality, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
@@ -1,2 +1,2 @@
1
- import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-DJZ9qeGg.js";
1
+ import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-DMkHlbny.js";
2
2
  export { DeviceOrientation, PoseReceivedPayload, ResetTransformData, TrackingPhase, TrackingSliceState, clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectLastSensorOrientation, selectLastValidPose, selectLostFrameCount, selectTrackingPhase, trackingReducer };
@@ -7,7 +7,7 @@ import "./state/gps-ar-pose-sampler.js";
7
7
  import "./state/app-selectors.js";
8
8
  import { selectTrackingQuality } from "./state/tracking-quality.js";
9
9
  import "./state/persistence-middleware.js";
10
- import "./state/create-slam-app-store.js";
10
+ import "./create-slam-app-store-DXiAnJwR.js";
11
11
  import "./state/recording-replayer.js";
12
12
  import "./state/store-subscribers.js";
13
13
  import { add2dImage, calcRelativeCoordsInMeters, recordGpsEvent as recordGpsEvent$1, setZeroPos as setZeroPos$1 } from "gps-plus-slam-js";
@@ -1,2 +1,2 @@
1
- import { a as formatTimestamp, i as formatFrameFilename, n as SESSION_IMAGES_DIR, r as formatActionFilename, t as LEGACY_SESSION_IMAGES_DIR } from "../file-system-utils-Bs7lpeVP.js";
1
+ import { a as formatTimestamp, i as formatFrameFilename, n as SESSION_IMAGES_DIR, r as formatActionFilename, t as LEGACY_SESSION_IMAGES_DIR } from "../file-system-utils-DueXHxo9.js";
2
2
  export { LEGACY_SESSION_IMAGES_DIR, SESSION_IMAGES_DIR, formatActionFilename, formatFrameFilename, formatTimestamp };
@@ -1,9 +1,9 @@
1
- import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, n as SessionMetadata, o as getSessionHandle, r as checkStorageQuota, s as getSessionsRootHandle, u as resetOpfsStorage } from "../opfs-storage-DMtlt57S.js";
2
- import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-CTJHNdMp.js";
3
- import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-Q2ue6zlC.js";
4
- import { t as NullStorageBackend } from "../null-storage-backend-CuSdYFsM.js";
5
- import { t as OpfsStorageBackend } from "../opfs-storage-backend-CeQAb0SD.js";
6
- import { a as formatTimestamp, i as formatFrameFilename, r as formatActionFilename } from "../file-system-utils-Bs7lpeVP.js";
7
- import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-DDWXInTR.js";
8
- import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-tFohI2J4.js";
1
+ import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, n as SessionMetadata, o as getSessionHandle, r as checkStorageQuota, s as getSessionsRootHandle, u as resetOpfsStorage } from "../opfs-storage-CTkxvxN4.js";
2
+ import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-sEtmM4yW.js";
3
+ import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-CvruaGvD.js";
4
+ import { t as NullStorageBackend } from "../null-storage-backend-B1-jlpin.js";
5
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-D6Z03fML.js";
6
+ import { a as formatTimestamp, i as formatFrameFilename, r as formatActionFilename } from "../file-system-utils-DueXHxo9.js";
7
+ import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-CtiUnAlJ.js";
8
+ import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-ncVbaZAz.js";
9
9
  export { type CreateSessionResult, type Entry, type ExportSessionAsZipOptions, type GpsPathCoord, MAX_ACTION_FILE_SIZE, NullStorageBackend, OpfsStorageBackend, type RecordedAction, type SessionMetadata, type StorageBackend, type ZipActionEntry, type ZipContributorAddFile, type ZipExportContributor, type ZipExportResult, type ZipSubdirEntry, checkStorageQuota, createSession, downloadZip, embedCoverageInSessionJson, exportSessionAsZip, exportSessionHandleAsZip, formatActionFilename, formatFrameFilename, formatTimestamp, getAppRootHandle, getSessionHandle, getSessionsRootHandle, initOpfsStorage, listSessions, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadataFromBlob, loadSessionMetadata as loadSessionMetadataFromZip, readZipEntries, resetOpfsStorage, resetSessionHandles, syncToExternalZip, writeSessionMetadata };
@@ -1,2 +1,2 @@
1
- import { t as NullStorageBackend } from "../null-storage-backend-CuSdYFsM.js";
1
+ import { t as NullStorageBackend } from "../null-storage-backend-B1-jlpin.js";
2
2
  export { NullStorageBackend };
@@ -1,2 +1,2 @@
1
- import { t as OpfsStorageBackend } from "../opfs-storage-backend-CeQAb0SD.js";
1
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-D6Z03fML.js";
2
2
  export { OpfsStorageBackend };
@@ -1,2 +1,2 @@
1
- import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, f as setSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, m as writeFrame, n as SessionMetadata, o as getSessionHandle, p as writeAction, r as checkStorageQuota, s as getSessionsRootHandle, t as CreateSessionResult, u as resetOpfsStorage } from "../opfs-storage-DMtlt57S.js";
1
+ import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, f as setSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, m as writeFrame, n as SessionMetadata, o as getSessionHandle, p as writeAction, r as checkStorageQuota, s as getSessionsRootHandle, t as CreateSessionResult, u as resetOpfsStorage } from "../opfs-storage-CTkxvxN4.js";
2
2
  export { CreateSessionResult, SessionMetadata, checkStorageQuota, createSession, getAppRootHandle, getSessionHandle, getSessionsRootHandle, initOpfsStorage, listSessions, resetOpfsStorage, resetSessionHandles, setSessionHandles, writeAction, writeFrame, writeSessionMetadata };
@@ -89,6 +89,19 @@ function getAppRootHandle() {
89
89
  return gpsPlusSlamDir;
90
90
  }
91
91
  /**
92
+ * True when a session directory named `name` already exists under `dir`.
93
+ * Used by {@link createSession} to avoid silently reusing a directory when two
94
+ * recordings start within the same (second-resolution) timestamp.
95
+ */
96
+ async function sessionDirExists(dir, name) {
97
+ try {
98
+ await dir.getDirectoryHandle(name, { create: false });
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ /**
92
105
  * Create a new recording session.
93
106
  *
94
107
  * Creates the directory structure:
@@ -96,6 +109,11 @@ function getAppRootHandle() {
96
109
  * - /gps-plus-slam/sessions/recording-{timestamp}/actions/
97
110
  * - /gps-plus-slam/sessions/recording-{timestamp}/images/ (legacy: frames/)
98
111
  *
112
+ * The folder name is `recording-{timestamp}` at whole-second resolution; if a
113
+ * directory with that name already exists (two recordings started in the same
114
+ * UTC second), a numeric suffix (`-2`, `-3`, …) is appended so the new session
115
+ * never silently reuses and corrupts the earlier one's directory.
116
+ *
99
117
  * @param timestamp - Session start time (used for folder naming)
100
118
  * @param _contextTag - Opaque tag; not used by the flat layout but accepted
101
119
  * for interface compatibility with wrapping backends
@@ -104,7 +122,13 @@ function getAppRootHandle() {
104
122
  */
105
123
  async function createSession(timestamp, _contextTag) {
106
124
  if (!sessionsDir) throw new Error("OPFS storage not initialized. Call initOpfsStorage first.");
107
- const sessionName = `recording-${formatTimestamp(timestamp)}`;
125
+ const baseName = `recording-${formatTimestamp(timestamp)}`;
126
+ let sessionName = baseName;
127
+ let suffix = 1;
128
+ while (await sessionDirExists(sessionsDir, sessionName)) {
129
+ suffix += 1;
130
+ sessionName = `${baseName}-${suffix}`;
131
+ }
108
132
  currentSessionHandle = await sessionsDir.getDirectoryHandle(sessionName, { create: true });
109
133
  actionsHandle = await currentSessionHandle.getDirectoryHandle("actions", { create: true });
110
134
  framesHandle = await currentSessionHandle.getDirectoryHandle(SESSION_IMAGES_DIR, { create: true });
@@ -1,2 +1,2 @@
1
- import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-CTJHNdMp.js";
1
+ import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-sEtmM4yW.js";
2
2
  export { CreateSessionResult, StorageBackend };
@@ -1,2 +1,2 @@
1
- import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-tFohI2J4.js";
1
+ import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-ncVbaZAz.js";
2
2
  export { embedCoverageInSessionJson };
@@ -1,2 +1,2 @@
1
- import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-DDWXInTR.js";
1
+ import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-CtiUnAlJ.js";
2
2
  export { ExportSessionAsZipOptions, ZipContributorAddFile, ZipExportContributor, ZipExportResult, downloadZip, exportSessionAsZip, exportSessionHandleAsZip, syncToExternalZip };
@@ -1,2 +1,2 @@
1
- import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-Q2ue6zlC.js";
1
+ import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-CvruaGvD.js";
2
2
  export { Entry, GpsPathCoord, MAX_ACTION_FILE_SIZE, RecordedAction, ZipActionEntry, ZipSubdirEntry, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
@@ -1,4 +1,4 @@
1
- import { n as SessionMetadata } from "./opfs-storage-DMtlt57S.js";
1
+ import { n as SessionMetadata } from "./opfs-storage-CTkxvxN4.js";
2
2
 
3
3
  //#region ../src/storage/storage-backend.d.ts
4
4
  interface CreateSessionResult {
@@ -1,5 +1,5 @@
1
- import { t as MapData } from "./map-data-DdjPiSav.js";
2
- import { t as SubscribableStore } from "./subscribe-to-selector-Bp3sLx8L.js";
1
+ import { t as MapData } from "./map-data-PNfGKGWx.js";
2
+ import { t as SubscribableStore } from "./subscribe-to-selector-B9ylM5Oh.js";
3
3
  import { LatLong, Matrix4, Quaternion, Vector3 } from "gps-plus-slam-js";
4
4
 
5
5
  //#region ../src/state/store-subscribers.d.ts
@@ -1,4 +1,4 @@
1
- import { L as CombinedRootState } from "./create-slam-app-store-Ce2FMKCp.js";
1
+ import { N as CombinedRootState } from "./create-slam-app-store-D3nzh9Wb.js";
2
2
 
3
3
  //#region ../src/state/subscribe-to-selector.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { o as WebXRQuaternion, s as WebXRVec3 } from "../ar-types-BckI1E-O.js";
1
+ import { o as WebXRQuaternion, s as WebXRVec3 } from "../ar-types-Cc5yvBwn.js";
2
2
  import { Mock } from "vitest";
3
3
 
4
4
  //#region ../src/test-utils/browser-mocks.d.ts
@@ -1,4 +1,4 @@
1
- import { i as RecordedAction } from "../zip-reader-Q2ue6zlC.js";
1
+ import { i as RecordedAction } from "../zip-reader-CvruaGvD.js";
2
2
  import { LatLong } from "gps-plus-slam-js";
3
3
 
4
4
  //#region ../src/test-utils/zip-round-trip-helpers.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ARPose } from "./ar-types-BckI1E-O.js";
1
+ import { t as ARPose } from "./ar-types-Cc5yvBwn.js";
2
2
  import { OdometryTrackingRestartedPayload, Quaternion, Vector3 } from "gps-plus-slam-js";
3
3
  import * as _$_reduxjs_toolkit0 from "@reduxjs/toolkit";
4
4
 
@@ -1,2 +1,2 @@
1
- import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-BckI1E-O.js";
1
+ import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-Cc5yvBwn.js";
2
2
  export { ARPose, ArPoseTuples, DepthPoint, DepthSample, RgbTuple, WebXRQuaternion, WebXRVec3 };
@@ -1,2 +1,2 @@
1
- import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DqiBzPO9.js";
1
+ import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DrGVu8nS.js";
2
2
  export { GpsCoord, RawGpsSample, RefPointMarker };
@@ -1,3 +1,3 @@
1
- import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-BckI1E-O.js";
2
- import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DqiBzPO9.js";
1
+ import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-Cc5yvBwn.js";
2
+ import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DrGVu8nS.js";
3
3
  export { ARPose, ArPoseTuples, DepthPoint, DepthSample, GpsCoord, RawGpsSample, RefPointMarker, RgbTuple, WebXRQuaternion, WebXRVec3 };
@@ -1,2 +1,2 @@
1
- import { n as mapWithConcurrencyLimit, t as forEachWithConcurrencyLimit } from "../concurrency-DqAlKX4j.js";
1
+ import { n as mapWithConcurrencyLimit, t as forEachWithConcurrencyLimit } from "../concurrency-DPeGBH0X.js";
2
2
  export { forEachWithConcurrencyLimit, mapWithConcurrencyLimit };
@@ -1,2 +1,2 @@
1
- import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-Cnc_xWUw.js";
1
+ import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-CeKf5HIc.js";
2
2
  export { FailureTracker, FailureTrackerConfig, createFailureTracker };
@@ -1,2 +1,2 @@
1
- import { t as formatFileSize } from "../format-file-size-BFUWvir1.js";
1
+ import { t as formatFileSize } from "../format-file-size-DxN-EBLc.js";
2
2
  export { formatFileSize };
@@ -1,2 +1,2 @@
1
- import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C0FHe67t.js";
1
+ import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C5pp0PJU.js";
2
2
  export { FusedPathInput, computeFusedPath, fusedGpsFromOdom };
@@ -1,7 +1,8 @@
1
- import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CzQrCe6C.js";
2
- import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C0FHe67t.js";
3
- import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-Cnc_xWUw.js";
4
- import { n as mapWithConcurrencyLimit } from "../concurrency-DqAlKX4j.js";
5
- import { t as formatFileSize } from "../format-file-size-BFUWvir1.js";
6
- import { t as listFormatter } from "../list-formatter-ct8txwvR.js";
7
- export { type FailureTracker, type FailureTrackerConfig, type FusedPathInput, type LogEntry, LogLevel, type Logger, clearLogBuffer, computeFusedPath, createFailureTracker, createLogger, formatFileSize, fusedGpsFromOdom, getGlobalLogLevel, getLogBuffer, listFormatter, mapWithConcurrencyLimit, setGlobalLogLevel, subscribeToLogs };
1
+ import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CqTd5Kpk.js";
2
+ import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C5pp0PJU.js";
3
+ import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-CeKf5HIc.js";
4
+ import { n as mapWithConcurrencyLimit } from "../concurrency-DPeGBH0X.js";
5
+ import { t as geodesicAngleRad } from "../index-D74JTX4I.js";
6
+ import { t as formatFileSize } from "../format-file-size-DxN-EBLc.js";
7
+ import { t as listFormatter } from "../list-formatter-DLFDnS70.js";
8
+ export { FailureTracker, FailureTrackerConfig, FusedPathInput, LogEntry, LogLevel, Logger, clearLogBuffer, computeFusedPath, createFailureTracker, createLogger, formatFileSize, fusedGpsFromOdom, geodesicAngleRad, getGlobalLogLevel, getLogBuffer, listFormatter, mapWithConcurrencyLimit, setGlobalLogLevel, subscribeToLogs };
@@ -1,7 +1,8 @@
1
1
  import { LogLevel, clearLogBuffer, createLogger, getGlobalLogLevel, getLogBuffer, setGlobalLogLevel, subscribeToLogs } from "./logger.js";
2
2
  import { createFailureTracker } from "./failure-tracker.js";
3
+ import { t as geodesicAngleRad } from "../geodesic-angle-BTldfFgs.js";
3
4
  import { computeFusedPath, fusedGpsFromOdom } from "./fused-path.js";
4
5
  import { mapWithConcurrencyLimit } from "./concurrency.js";
5
6
  import { formatFileSize } from "./format-file-size.js";
6
7
  import { listFormatter } from "./list-formatter.js";
7
- export { LogLevel, clearLogBuffer, computeFusedPath, createFailureTracker, createLogger, formatFileSize, fusedGpsFromOdom, getGlobalLogLevel, getLogBuffer, listFormatter, mapWithConcurrencyLimit, setGlobalLogLevel, subscribeToLogs };
8
+ export { LogLevel, clearLogBuffer, computeFusedPath, createFailureTracker, createLogger, formatFileSize, fusedGpsFromOdom, geodesicAngleRad, getGlobalLogLevel, getLogBuffer, listFormatter, mapWithConcurrencyLimit, setGlobalLogLevel, subscribeToLogs };
@@ -1,2 +1,2 @@
1
- import { t as listFormatter } from "../list-formatter-ct8txwvR.js";
1
+ import { t as listFormatter } from "../list-formatter-DLFDnS70.js";
2
2
  export { listFormatter };
@@ -1,2 +1,2 @@
1
- import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CzQrCe6C.js";
1
+ import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CqTd5Kpk.js";
2
2
  export { LogEntry, LogLevel, Logger, clearLogBuffer, createLogger, getGlobalLogLevel, getLogBuffer, setGlobalLogLevel, subscribeToLogs };
@@ -1,2 +1,2 @@
1
- import { a as addAccuracyCircles, i as AccuracyCircleSample, n as ACCURACY_CIRCLE_STROKE_OPACITY, r as ACCURACY_CIRCLE_WEIGHT, t as ACCURACY_CIRCLE_FILL_OPACITY } from "../accuracy-circles-Pi2YeCrM.js";
1
+ import { a as addAccuracyCircles, i as AccuracyCircleSample, n as ACCURACY_CIRCLE_STROKE_OPACITY, r as ACCURACY_CIRCLE_WEIGHT, t as ACCURACY_CIRCLE_FILL_OPACITY } from "../accuracy-circles-DkWBkF5g.js";
2
2
  export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, AccuracyCircleSample, addAccuracyCircles };