gps-plus-slam-app-framework 1.3.0 → 1.7.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 (247) hide show
  1. package/dist/absolute-orientation-CAEh840t.d.ts +54 -0
  2. package/dist/{app-selectors-B8VQoMDY.d.ts → app-selectors-ikTUQZpe.d.ts} +1 -1
  3. package/dist/ar/bresenham3d.d.ts +1 -1
  4. package/dist/ar/camera-blit-capture.d.ts +2 -2
  5. package/dist/ar/camera-blit-capture.js +74 -9
  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 +2 -0
  10. package/dist/ar/depth-grid-lookup.js +43 -0
  11. package/dist/ar/depth-sampler.d.ts +2 -2
  12. package/dist/ar/depth-sampler.js +2 -4
  13. package/dist/ar/depth-unprojection.d.ts +1 -1
  14. package/dist/ar/enable-gps-ar.d.ts +1 -1
  15. package/dist/ar/enable-gps-ar.js +26 -1
  16. package/dist/ar/frame-loop.d.ts +1 -1
  17. package/dist/ar/image-capture.d.ts +2 -2
  18. package/dist/ar/image-capture.js +1 -154
  19. package/dist/ar/image-quality.d.ts +2 -0
  20. package/dist/ar/image-quality.js +168 -0
  21. package/dist/ar/index.d.ts +31 -19
  22. package/dist/ar/index.js +17 -5
  23. package/dist/ar/occupancy-grid.d.ts +1 -1
  24. package/dist/ar/occupancy-grid.js +29 -2
  25. package/dist/ar/planar-pnp.d.ts +2 -0
  26. package/dist/ar/planar-pnp.js +425 -0
  27. package/dist/ar/qr-debug-view.d.ts +2 -0
  28. package/dist/ar/qr-debug-view.js +89 -0
  29. package/dist/ar/qr-derived-pose.d.ts +2 -0
  30. package/dist/ar/qr-derived-pose.js +114 -0
  31. package/dist/ar/qr-detection-controller.d.ts +2 -0
  32. package/dist/ar/qr-detection-controller.js +2 -0
  33. package/dist/ar/qr-frontend.d.ts +2 -0
  34. package/dist/ar/qr-frontend.js +60 -0
  35. package/dist/ar/qr-pose.d.ts +2 -0
  36. package/dist/ar/qr-pose.js +267 -0
  37. package/dist/ar/qr-size-depth-context.d.ts +3 -0
  38. package/dist/ar/qr-size-depth-context.js +42 -0
  39. package/dist/ar/qr-size-measurer.d.ts +2 -0
  40. package/dist/ar/qr-size-measurer.js +2 -0
  41. package/dist/ar/replay-scene.d.ts +1 -1
  42. package/dist/ar/replay-scene.js +1 -1
  43. package/dist/ar/scene-node-names.d.ts +1 -1
  44. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  45. package/dist/ar/webxr-session.d.ts +4 -4
  46. package/dist/ar/webxr-session.js +2 -2
  47. package/dist/ar/xr-camera-texture.d.ts +1 -1
  48. package/dist/ar/xr-error-handler.d.ts +1 -1
  49. package/dist/ar/xr-frame-loop.d.ts +1 -1
  50. package/dist/ar-MFpFlV7j.js +382 -0
  51. package/dist/{ar-world-group-alignment-C9lwwIyI.d.ts → ar-world-group-alignment-69fgnB39.d.ts} +1 -1
  52. package/dist/{camera-blit-capture-CnxUDbKx.d.ts → camera-blit-capture-NKT9H5-e.d.ts} +57 -4
  53. package/dist/capture-motion-gate-0Yl2NpQG.d.ts +24 -0
  54. package/dist/capture-motion-gate-4EscNZCw.js +94 -0
  55. package/dist/concurrency-By0oOTZt.d.ts +50 -0
  56. package/dist/core/index.d.ts +2 -2
  57. package/dist/core/index.js +2 -2
  58. package/dist/create-slam-app-store-DkCHOpvb.js +149 -0
  59. package/dist/{create-slam-app-store-BljusOua.d.ts → create-slam-app-store-n_VF63D5.d.ts} +50 -40
  60. package/dist/depth-grid-lookup-DQ-IYhO3.d.ts +21 -0
  61. package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-asc8exHt.d.ts} +1 -1
  62. package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-DjsbJW9L.d.ts} +1 -1
  63. package/dist/{enable-gps-ar-BfUJOgpW.d.ts → enable-gps-ar-DkNhFv4Y.d.ts} +4 -4
  64. package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-DObhj8Wf.d.ts} +1 -1
  65. package/dist/{fused-path-DJoVu98b.d.ts → fused-path-v6eOmLl3.d.ts} +1 -1
  66. package/dist/geo/h3-proximity.d.ts +2 -2
  67. package/dist/geo/h3-proximity.js +61 -2
  68. package/dist/geo/index.d.ts +2 -2
  69. package/dist/geo/index.js +2 -2
  70. package/dist/geodesic-angle-BTldfFgs.js +39 -0
  71. package/dist/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-BeFO2RYa.d.ts} +1 -1
  72. package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DHMZmYAc.d.ts} +1 -1
  73. package/dist/{gps-event-coordinator-CWIDOk4U.d.ts → gps-event-coordinator-CjWW636x.d.ts} +21 -11
  74. package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Cz9RMA2k.d.ts} +18 -0
  75. package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-C-j0pw1j.d.ts} +39 -1
  76. package/dist/image-capture-B2-b6tKK.js +374 -0
  77. package/dist/{image-capture-qAZ5Fh5X.d.ts → image-capture-CNZDrQXG.d.ts} +103 -3
  78. package/dist/image-quality-smCBoFj1.d.ts +158 -0
  79. package/dist/index-BAPokxMb.d.ts +22 -0
  80. package/dist/{index-BYYUdf9v.d.ts → index-BODDUTvD.d.ts} +1 -1
  81. package/dist/index-BneSj4_5.d.ts +399 -0
  82. package/dist/index-_Rhgoqkh.d.ts +2 -0
  83. package/dist/index.d.ts +80 -67
  84. package/dist/index.js +24 -12
  85. package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-CmIyiE8n.d.ts} +1 -1
  86. package/dist/{map-data-CkloBxIl.d.ts → map-data-B4oEehUh.d.ts} +1 -1
  87. package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-ymz1s7fG.d.ts} +1 -1
  88. package/dist/{null-storage-backend-uKQE8tl7.d.ts → null-storage-backend-B9rtyDfG.d.ts} +2 -2
  89. package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-DnOc-6PU.d.ts} +12 -2
  90. package/dist/{opfs-storage-D5zAn_lx.d.ts → opfs-storage-3VZh-qH6.d.ts} +23 -4
  91. package/dist/{opfs-storage-backend-6aYMUZlJ.d.ts → opfs-storage-backend-JJiUJo-3.d.ts} +3 -3
  92. package/dist/{persistence-middleware-B9ei4Nhx.d.ts → persistence-middleware-DAPTTwB2.d.ts} +1 -1
  93. package/dist/planar-pnp-my9bqULS.d.ts +67 -0
  94. package/dist/qr-debug-view-Brl73HGO.d.ts +29 -0
  95. package/dist/qr-derived-pose-CmxTP2m4.d.ts +122 -0
  96. package/dist/qr-detected-slice-VQssZmQJ.d.ts +166 -0
  97. package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
  98. package/dist/qr-detection-controller-CDurHv8E.d.ts +48 -0
  99. package/dist/qr-frontend--fFX25mV.d.ts +58 -0
  100. package/dist/qr-pose-D2rfu5DA.d.ts +138 -0
  101. package/dist/qr-pose-aggregation-B7tWJq7j.d.ts +90 -0
  102. package/dist/qr-pose-aggregation-DBiaLW0O.js +191 -0
  103. package/dist/qr-size-depth-context-CeAG53fy.d.ts +13 -0
  104. package/dist/qr-size-measurer-DLb33geH.js +596 -0
  105. package/dist/qr-size-measurer-DQdAsid1.d.ts +165 -0
  106. package/dist/recording-options-BPCpMHLK.d.ts +514 -0
  107. package/dist/{recording-replayer-r0DZ9ALp.d.ts → recording-replayer-DQKpAGyQ.d.ts} +2 -2
  108. package/dist/recording-slice-DaxHZvNu.d.ts +55 -0
  109. package/dist/{replay-engine-XpDa0dy1.d.ts → replay-engine-CmKh_4wf.d.ts} +7 -4
  110. package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-DBlDrV_x.d.ts} +3 -3
  111. package/dist/sensors/absolute-orientation.d.ts +2 -0
  112. package/dist/sensors/absolute-orientation.js +145 -0
  113. package/dist/sensors/gps-error-handler.d.ts +1 -1
  114. package/dist/sensors/gps.d.ts +1 -1
  115. package/dist/sensors/index.d.ts +5 -4
  116. package/dist/sensors/index.js +2 -1
  117. package/dist/sensors/permission-checker.d.ts +1 -1
  118. package/dist/sensors/permission-checker.js +1 -1
  119. package/dist/state/app-selectors.d.ts +1 -1
  120. package/dist/state/combined-root-state.d.ts +1 -1
  121. package/dist/state/create-slam-app-store.d.ts +1 -1
  122. package/dist/state/create-slam-app-store.js +1 -74
  123. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  124. package/dist/state/gps-event-coordinator.d.ts +2 -2
  125. package/dist/state/gps-event-coordinator.js +27 -17
  126. package/dist/state/index.d.ts +20 -17
  127. package/dist/state/index.js +5 -4
  128. package/dist/state/persistence-middleware.d.ts +1 -1
  129. package/dist/state/persistence-middleware.js +30 -19
  130. package/dist/state/qr-detected-slice.d.ts +4 -0
  131. package/dist/state/qr-detected-slice.js +311 -0
  132. package/dist/state/recording-options.d.ts +2 -2
  133. package/dist/state/recording-options.js +273 -7
  134. package/dist/state/recording-replayer.d.ts +2 -2
  135. package/dist/state/recording-replayer.js +1 -1
  136. package/dist/state/recording-slice.d.ts +1 -1
  137. package/dist/state/recording-slice.js +5 -1
  138. package/dist/state/replay-engine.d.ts +1 -1
  139. package/dist/state/replay-engine.js +7 -3
  140. package/dist/state/store-subscribers.d.ts +2 -2
  141. package/dist/state/subscribe-to-selector.d.ts +1 -1
  142. package/dist/state/tracking-quality.d.ts +2 -2
  143. package/dist/state/tracking-quality.js +40 -128
  144. package/dist/state/tracking-slice.d.ts +1 -1
  145. package/dist/{state-BkIfpiyA.js → state-E51QT_-j.js} +2 -1
  146. package/dist/storage/file-system-utils.d.ts +1 -1
  147. package/dist/storage/index.d.ts +9 -9
  148. package/dist/storage/index.js +4 -4
  149. package/dist/storage/null-storage-backend.d.ts +1 -1
  150. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  151. package/dist/storage/opfs-storage-backend.js +3 -3
  152. package/dist/storage/opfs-storage.d.ts +1 -1
  153. package/dist/storage/opfs-storage.js +5 -4
  154. package/dist/storage/storage-backend.d.ts +1 -1
  155. package/dist/storage/zip-coverage-embed.d.ts +2 -0
  156. package/dist/storage/zip-coverage-embed.js +88 -0
  157. package/dist/storage/zip-export.d.ts +2 -2
  158. package/dist/storage/zip-export.js +37 -59
  159. package/dist/storage/zip-reader.d.ts +1 -1
  160. package/dist/{storage-backend-Do6YDpNo.d.ts → storage-backend-BdVLD0dU.d.ts} +1 -1
  161. package/dist/{store-subscribers-0YWyOLvV.d.ts → store-subscribers-CA6AJH_8.d.ts} +2 -2
  162. package/dist/{subscribe-to-selector-CZ6k1NFP.d.ts → subscribe-to-selector-B6EUmQ3I.d.ts} +1 -1
  163. package/dist/test-utils/browser-mocks.d.ts +7 -1
  164. package/dist/test-utils/browser-mocks.js +6 -0
  165. package/dist/test-utils/zip-round-trip-helpers.d.ts +9 -1
  166. package/dist/test-utils/zip-round-trip-helpers.js +5 -1
  167. package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-BuAZxXZ5.d.ts} +1 -1
  168. package/dist/types/ar-types.d.ts +1 -1
  169. package/dist/types/geo-types.d.ts +1 -1
  170. package/dist/types/index.d.ts +2 -2
  171. package/dist/utils/concurrency.d.ts +2 -2
  172. package/dist/utils/concurrency.js +66 -2
  173. package/dist/utils/failure-tracker.d.ts +1 -1
  174. package/dist/utils/format-file-size.d.ts +1 -1
  175. package/dist/utils/fused-path.d.ts +1 -1
  176. package/dist/utils/index.d.ts +8 -7
  177. package/dist/utils/index.js +2 -1
  178. package/dist/utils/list-formatter.d.ts +1 -1
  179. package/dist/utils/logger.d.ts +1 -1
  180. package/dist/visualization/accuracy-circles.d.ts +1 -1
  181. package/dist/visualization/alignment-lerper.d.ts +1 -1
  182. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  183. package/dist/visualization/ar-world-group-alignment.js +1 -1
  184. package/dist/visualization/camera-follower.d.ts +1 -1
  185. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  186. package/dist/visualization/frame-conversions.d.ts +1 -1
  187. package/dist/visualization/frustum-visibility.d.ts +1 -1
  188. package/dist/visualization/gps-anchor.d.ts +1 -1
  189. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  190. package/dist/visualization/gps-event-markers.d.ts +1 -1
  191. package/dist/visualization/gps-event-markers.js +40 -5
  192. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  193. package/dist/visualization/hit-test-reticle.js +1 -0
  194. package/dist/visualization/index.d.ts +18 -18
  195. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  196. package/dist/visualization/leaflet-map-overlay.js +15 -3
  197. package/dist/visualization/lerp-utils.d.ts +1 -1
  198. package/dist/visualization/map-data.d.ts +1 -1
  199. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  200. package/dist/visualization/map-overlay.d.ts +1 -1
  201. package/dist/visualization/three-dispose.d.ts +1 -1
  202. package/dist/visualization/vis-colors.d.ts +1 -1
  203. package/dist/{webxr-session-TIaD883f.js → webxr-session-DG-eNfny.js} +244 -11
  204. package/dist/{webxr-session-GjZ2BIpS.d.ts → webxr-session-Do5L6G3B.d.ts} +86 -8
  205. package/dist/zip-coverage-embed-DCAREVmw.d.ts +41 -0
  206. package/dist/{zip-export-G14hkSPG.d.ts → zip-export-ol0S_Ofd.d.ts} +27 -23
  207. package/package.json +2 -2
  208. package/dist/concurrency-CO1FQvs3.d.ts +0 -24
  209. package/dist/file-system-CwXTNHmB.d.ts +0 -114
  210. package/dist/index-CF05QV2m.d.ts +0 -2
  211. package/dist/recording-options-DJBXuMxV.d.ts +0 -182
  212. package/dist/recording-slice-89O07ebs.d.ts +0 -36
  213. package/dist/storage/file-system.d.ts +0 -2
  214. package/dist/storage/file-system.js +0 -289
  215. /package/dist/{accuracy-circles-BpwWAs8G.d.ts → accuracy-circles-BtqOP_v1.d.ts} +0 -0
  216. /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-Bil6vbVw.d.ts} +0 -0
  217. /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BFX2r1wk.d.ts} +0 -0
  218. /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-CU1Gv2W4.d.ts} +0 -0
  219. /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-CqJJ7zRj.d.ts} +0 -0
  220. /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-DUQLg6gq.d.ts} +0 -0
  221. /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-gsm8q7yi.d.ts} +0 -0
  222. /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-D4K2uwwQ.d.ts} +0 -0
  223. /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-B3kGNtcn.d.ts} +0 -0
  224. /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-BO7M7jF_.d.ts} +0 -0
  225. /package/dist/{file-system-utils-CXIoL-DM.d.ts → file-system-utils-DOajnMm-.d.ts} +0 -0
  226. /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-Dc98ulzB.d.ts} +0 -0
  227. /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-CpVjQ1dH.d.ts} +0 -0
  228. /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-CeoCZchO.d.ts} +0 -0
  229. /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-3Ip1NzVR.d.ts} +0 -0
  230. /package/dist/{gps-VuezJWK3.d.ts → gps-BBO3hKfT.d.ts} +0 -0
  231. /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-DSN87L8p.d.ts} +0 -0
  232. /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-BbPsfw-_.d.ts} +0 -0
  233. /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-DV-2TpB9.d.ts} +0 -0
  234. /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-BLmHhkVe.d.ts} +0 -0
  235. /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-KNMS0ay2.d.ts} +0 -0
  236. /package/dist/{logger-BEHXsnnq.d.ts → logger-CLnPrHiD.d.ts} +0 -0
  237. /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dh2B97MP.d.ts} +0 -0
  238. /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CDuI8BwC.d.ts} +0 -0
  239. /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-BiShOQDB.d.ts} +0 -0
  240. /package/dist/{session-disposers-M-oashRH.js → session-disposers-zv9OGXPB.js} +0 -0
  241. /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-Bt1k7WKh.d.ts} +0 -0
  242. /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-Obw2pLqT.d.ts} +0 -0
  243. /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-3nfjDJJp.d.ts} +0 -0
  244. /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-Cva1q3Qj.d.ts} +0 -0
  245. /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-CtvHpjfm.d.ts} +0 -0
  246. /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-DIYtRcUa.d.ts} +0 -0
  247. /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-D9iPQwdQ.d.ts} +0 -0
@@ -0,0 +1,382 @@
1
+ import "./ar/camera-blit-capture.js";
2
+ import "./ar/chromium-camera-access-workaround.js";
3
+ import "./ar/capture-failure-tracker.js";
4
+ import "./ar/depth-sampler.js";
5
+ import "./ar/depth-unprojection.js";
6
+ import "./qr-size-measurer-DLb33geH.js";
7
+ import "./ar/qr-size-depth-context.js";
8
+ import { buildObjectPoints, transformPoint } from "./ar/qr-pose.js";
9
+ import "./ar/qr-derived-pose.js";
10
+ import "./ar/webxr-nue-basis.js";
11
+ import "./ar/qr-debug-view.js";
12
+ import { n as createDetectionScheduler } from "./qr-detection-controller-B-tLVd-S.js";
13
+ import "./qr-pose-aggregation-DBiaLW0O.js";
14
+ import "./webxr-session-DG-eNfny.js";
15
+ import "./ar/occupancy-grid.js";
16
+ import "./ar/frame-loop.js";
17
+ import "./ar/xr-frame-loop.js";
18
+ import "./image-capture-B2-b6tKK.js";
19
+ import "./ar/xr-camera-texture.js";
20
+ import "./ar/enable-gps-ar.js";
21
+ import "./ar/replay-scene.js";
22
+ //#region ../src/ar/qr-level.ts
23
+ /** Thrown when a fetched level file fails validation. */
24
+ var QrLevelValidationError = class extends Error {
25
+ constructor(message) {
26
+ super(`qr-level: ${message}`);
27
+ this.name = "QrLevelValidationError";
28
+ }
29
+ };
30
+ function isFiniteNumber(v) {
31
+ return typeof v === "number" && Number.isFinite(v);
32
+ }
33
+ function isRecord(v) {
34
+ return typeof v === "object" && v !== null;
35
+ }
36
+ /**
37
+ * Validate the optional `qr.physicalSizeM`. When present it MUST be a positive
38
+ * number (a `0`/negative authored size is a bug, not a "measure it instead"
39
+ * signal). Returns `undefined` when omitted.
40
+ */
41
+ function parsePhysicalSize(value) {
42
+ if (value === void 0) return void 0;
43
+ if (!isFiniteNumber(value) || value <= 0) throw new QrLevelValidationError("\"qr.physicalSizeM\" must be a positive number when present");
44
+ return value;
45
+ }
46
+ /**
47
+ * Validate the optional `qr.geo`. When present every field is validated (a
48
+ * partial geo is a bug — it would silently place the vote wrong). Returns
49
+ * `undefined` when omitted; heading is normalized into `[0, 360)`.
50
+ */
51
+ function parseGeo(value) {
52
+ if (value === void 0) return void 0;
53
+ if (!isRecord(value)) throw new QrLevelValidationError("\"qr.geo\" must be an object when present");
54
+ const { lat, lon, alt, headingDeg } = value;
55
+ if (!isFiniteNumber(lat) || lat < -90 || lat > 90) throw new QrLevelValidationError("\"qr.geo.lat\" must be a number in [-90, 90]");
56
+ if (!isFiniteNumber(lon) || lon < -180 || lon > 180) throw new QrLevelValidationError("\"qr.geo.lon\" must be a number in [-180, 180]");
57
+ if (!isFiniteNumber(alt)) throw new QrLevelValidationError("\"qr.geo.alt\" must be a finite number");
58
+ if (!isFiniteNumber(headingDeg)) throw new QrLevelValidationError("\"qr.geo.headingDeg\" must be a finite number");
59
+ return {
60
+ lat,
61
+ lon,
62
+ alt,
63
+ headingDeg: (headingDeg % 360 + 360) % 360
64
+ };
65
+ }
66
+ /**
67
+ * Validate an already-parsed value as a {@link QrLevel}. Throws
68
+ * {@link QrLevelValidationError} with a descriptive message on any violation.
69
+ */
70
+ function parseQrLevel(data) {
71
+ if (!isRecord(data)) throw new QrLevelValidationError("level file must be a JSON object");
72
+ if (!isFiniteNumber(data.version)) throw new QrLevelValidationError("missing/invalid \"version\"");
73
+ if (!isRecord(data.qr)) throw new QrLevelValidationError("missing/invalid \"qr\"");
74
+ const physicalSizeM = parsePhysicalSize(data.qr.physicalSizeM);
75
+ const geo = parseGeo(data.qr.geo);
76
+ return {
77
+ version: data.version,
78
+ qr: {
79
+ ...physicalSizeM !== void 0 ? { physicalSizeM } : {},
80
+ ...geo !== void 0 ? { geo } : {}
81
+ },
82
+ content: "content" in data ? data.content : void 0
83
+ };
84
+ }
85
+ /**
86
+ * Fetch and validate a level file from `url`. Rejects with
87
+ * {@link QrLevelValidationError} on a non-OK response, non-JSON body, or a
88
+ * schema violation.
89
+ */
90
+ async function fetchQrLevel(url, options = {}) {
91
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
92
+ if (!fetchImpl) throw new QrLevelValidationError("no fetch implementation available");
93
+ let response;
94
+ try {
95
+ response = await fetchImpl(url, { signal: options.signal });
96
+ } catch (err) {
97
+ throw new QrLevelValidationError(`fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`);
98
+ }
99
+ if (!response.ok) throw new QrLevelValidationError(`fetch ${url} returned status ${response.status}`);
100
+ let body;
101
+ try {
102
+ body = await response.json();
103
+ } catch {
104
+ throw new QrLevelValidationError(`response for ${url} was not valid JSON`);
105
+ }
106
+ return parseQrLevel(body);
107
+ }
108
+ //#endregion
109
+ //#region ../src/ar/qr-gps-vote.ts
110
+ /** Mean meters per degree of latitude (WGS-84 average). */
111
+ const METERS_PER_DEG_LAT = 111320;
112
+ /**
113
+ * `count` points evenly spaced on a circle of radius `baselineM` in the QR
114
+ * plane (z = 0), starting at local +x. The symmetric ring's centroid is the QR
115
+ * center, so translation stays anchored there while the wide radius stiffens
116
+ * the rotation fit. Non-collinear for `count ≥ 3`.
117
+ */
118
+ function widePlanePoints(baselineM, count) {
119
+ const points = [];
120
+ for (let i = 0; i < count; i++) {
121
+ const theta = 2 * Math.PI * i / count;
122
+ points.push([
123
+ baselineM * Math.cos(theta),
124
+ baselineM * Math.sin(theta),
125
+ 0
126
+ ]);
127
+ }
128
+ return points;
129
+ }
130
+ /**
131
+ * Map a QR-local plane offset (meters; +x right, +y up on the printed face) to
132
+ * an East/North/Up offset, given the QR's compass heading. Vertical-QR
133
+ * convention: +y is world up; +x is horizontal at `headingDeg` (clockwise from
134
+ * North), so east = x·sin(h), north = x·cos(h).
135
+ */
136
+ function localPlaneToEnu(localX, localY, headingDeg) {
137
+ const h = headingDeg * Math.PI / 180;
138
+ return {
139
+ east: localX * Math.sin(h),
140
+ north: localX * Math.cos(h),
141
+ up: localY
142
+ };
143
+ }
144
+ /** Apply an ENU meter offset to a geo pose (equirectangular; exact enough for sub-meter QR corners). */
145
+ function offsetGeo(center, enu) {
146
+ const dLat = enu.north / METERS_PER_DEG_LAT;
147
+ const metersPerDegLon = METERS_PER_DEG_LAT * Math.cos(center.lat * Math.PI / 180);
148
+ const dLon = Math.abs(metersPerDegLon) < 1e-9 ? 0 : enu.east / metersPerDegLon;
149
+ return {
150
+ latitude: center.lat + dLat,
151
+ longitude: center.lon + dLon,
152
+ altitude: center.alt + enu.up
153
+ };
154
+ }
155
+ /**
156
+ * Build the synthetic `recordGpsEvent` payload(s) for one QR detection. Returns
157
+ * 4 corner correspondences by default, or 1 center correspondence when
158
+ * `multiCorrespondence` is false. Each payload pairs the corner's odom position
159
+ * (from the solved pose) with its absolute geo position (from the level file),
160
+ * stamped with the tiny synthetic accuracy.
161
+ *
162
+ * @throws RangeError on a non-positive `sizeM` or `syntheticAccuracyM`.
163
+ */
164
+ function buildQrGpsVotes(input) {
165
+ const { qrPoseWorld, sizeM, qrGeo, syntheticAccuracyM, odomRotation = qrPoseWorld.rotation, multiCorrespondence = true, baselineM, count = 4, timestamp = Date.now(), idPrefix = "qr" } = input;
166
+ if (!(syntheticAccuracyM > 0) || !Number.isFinite(syntheticAccuracyM)) throw new RangeError(`qr-gps-vote: syntheticAccuracyM must be positive, got ${syntheticAccuracyM}`);
167
+ const useWideBaseline = baselineM !== void 0 && Number.isFinite(baselineM) && baselineM > 0;
168
+ if (useWideBaseline && count < 3) throw new RangeError(`qr-gps-vote: wide-baseline count must be >= 3 (collinear otherwise), got ${count}`);
169
+ return (useWideBaseline ? widePlanePoints(baselineM, count) : multiCorrespondence ? buildObjectPoints(sizeM) : [[
170
+ 0,
171
+ 0,
172
+ 0
173
+ ]]).map((local, i) => {
174
+ const odomPosition = transformPoint(local, qrPoseWorld);
175
+ const geo = offsetGeo(qrGeo, localPlaneToEnu(local[0], local[1], qrGeo.headingDeg));
176
+ return {
177
+ odomPosition,
178
+ odomRotation,
179
+ rawGpsPoint: {
180
+ id: `${idPrefix}-${timestamp}-${i}`,
181
+ latitude: geo.latitude,
182
+ longitude: geo.longitude,
183
+ altitude: geo.altitude,
184
+ latLongAccuracy: syntheticAccuracyM,
185
+ timestamp
186
+ }
187
+ };
188
+ });
189
+ }
190
+ //#endregion
191
+ //#region ../src/ar/qr-tracking-controller.ts
192
+ function createQrTrackingController(config) {
193
+ const { frontEnd, solvePose, fetchLevel, dispatchVotes, onDetection, resolveSizeM, resolveStablePose, getCameraPose, getIntrinsics, syntheticAccuracyM, voteBaselineM, voteCount, isPlausible, onStatus, onLocked, onError, minIntervalMs = 150, requiredLockCount = 3, now } = config;
194
+ const timestampNow = now ?? (() => Date.now());
195
+ let status = "idle";
196
+ const levelCache = /* @__PURE__ */ new Map();
197
+ let active = null;
198
+ function setStatus(next) {
199
+ if (status === next) return;
200
+ status = next;
201
+ onStatus?.(next);
202
+ }
203
+ async function ensureLevel(url) {
204
+ const cached = levelCache.get(url);
205
+ if (cached) return cached;
206
+ setStatus("loading-level");
207
+ const level = await fetchLevel(url);
208
+ levelCache.set(url, level);
209
+ return level;
210
+ }
211
+ async function detect(image) {
212
+ if (status === "idle" || status === "error") setStatus("scanning");
213
+ const detection = await frontEnd.detect(image);
214
+ if (!detection) {
215
+ active = null;
216
+ return null;
217
+ }
218
+ const level = await ensureLevel(detection.text);
219
+ const sizeM = level.qr.physicalSizeM ?? resolveSizeM?.(detection.text, level) ?? null;
220
+ if (sizeM === null || !(sizeM > 0) || !Number.isFinite(sizeM)) {
221
+ active = null;
222
+ if (status === "loading-level") setStatus("scanning");
223
+ return null;
224
+ }
225
+ const cameraPose = getCameraPose();
226
+ const intrinsics = getIntrinsics(image);
227
+ if (!cameraPose || !intrinsics) {
228
+ active = null;
229
+ return null;
230
+ }
231
+ const solution = solvePose({
232
+ imagePoints: detection.corners,
233
+ sizeM,
234
+ intrinsics,
235
+ cameraPose
236
+ });
237
+ if (!solution) {
238
+ active = null;
239
+ return null;
240
+ }
241
+ if (isPlausible && !isPlausible(solution, cameraPose)) {
242
+ active = null;
243
+ return null;
244
+ }
245
+ active = {
246
+ level,
247
+ text: detection.text,
248
+ sizeM
249
+ };
250
+ return solution;
251
+ }
252
+ const scheduler = createDetectionScheduler({
253
+ detect,
254
+ minIntervalMs,
255
+ requiredLockCount,
256
+ now,
257
+ onLocked: (solution) => {
258
+ const current = active;
259
+ if (!current) return;
260
+ const { level, text, sizeM } = current;
261
+ onDetection?.({
262
+ text,
263
+ qrPoseWorld: solution.qrPoseWorld,
264
+ qrPoseInCamera: solution.qrPoseInCamera,
265
+ reprojectionErrorPx: solution.reprojectionErrorPx,
266
+ timestamp: timestampNow()
267
+ });
268
+ if (level.qr.geo) {
269
+ const votePose = resolveStablePose ? resolveStablePose(text) : solution.qrPoseWorld;
270
+ if (votePose) dispatchVotes(buildQrGpsVotes({
271
+ qrPoseWorld: votePose,
272
+ sizeM,
273
+ qrGeo: level.qr.geo,
274
+ syntheticAccuracyM,
275
+ ...voteBaselineM !== void 0 ? { baselineM: voteBaselineM } : {},
276
+ ...voteCount !== void 0 ? { count: voteCount } : {}
277
+ }));
278
+ }
279
+ setStatus("tracking");
280
+ onLocked?.(solution, level);
281
+ },
282
+ onMiss: () => {
283
+ if (status === "tracking") setStatus("scanning");
284
+ },
285
+ onError: (err) => {
286
+ active = null;
287
+ setStatus("error");
288
+ onError?.(err);
289
+ }
290
+ });
291
+ return {
292
+ offerFrame(image) {
293
+ scheduler.offerFrame(image);
294
+ },
295
+ get status() {
296
+ return status;
297
+ },
298
+ reset() {
299
+ levelCache.clear();
300
+ active = null;
301
+ setStatus("idle");
302
+ }
303
+ };
304
+ }
305
+ //#endregion
306
+ //#region ../src/ar/qr-occupancy-check.ts
307
+ const DEFAULTS = {
308
+ surfaceToleranceCells: 1.5,
309
+ minObservations: 1,
310
+ rayCorridorCells: 1,
311
+ occlusionMarginCells: 2
312
+ };
313
+ function distance(a, b) {
314
+ return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
315
+ }
316
+ /**
317
+ * Classify a solved QR center against the occupancy grid.
318
+ *
319
+ * - `no-grid` (ok): the grid has no occupied voxels — nothing to check against.
320
+ * - `floating` (not ok): nearest occupied voxel is farther than the tolerance.
321
+ * - `behind-surface` (not ok): an occupied voxel lies on the camera→QR ray
322
+ * clearly in front of the QR (the QR is inside / beyond geometry).
323
+ * - `plausible` (ok): on a surface with empty space toward the camera.
324
+ *
325
+ * All positions are raw-WebXR/odom space (the same frame as `qrPoseWorld`).
326
+ */
327
+ function checkQrPlausibility(qrCenterWorld, cameraPosWorld, grid, options = {}) {
328
+ const surfaceToleranceCells = options.surfaceToleranceCells ?? DEFAULTS.surfaceToleranceCells;
329
+ const minObservations = options.minObservations ?? DEFAULTS.minObservations;
330
+ const rayCorridorCells = options.rayCorridorCells ?? DEFAULTS.rayCorridorCells;
331
+ const occlusionMarginCells = options.occlusionMarginCells ?? DEFAULTS.occlusionMarginCells;
332
+ const cells = grid.getOccupiedCells(minObservations);
333
+ if (cells.length === 0) return {
334
+ verdict: "no-grid",
335
+ ok: true,
336
+ nearestSurfaceDistanceM: Infinity
337
+ };
338
+ const cellSizeM = grid.cellSizeM;
339
+ const centers = cells.map((c) => grid.getCellCenter(c));
340
+ let nearest = Infinity;
341
+ for (const center of centers) {
342
+ const d = distance(center, qrCenterWorld);
343
+ if (d < nearest) nearest = d;
344
+ }
345
+ if (nearest > surfaceToleranceCells * cellSizeM) return {
346
+ verdict: "floating",
347
+ ok: false,
348
+ nearestSurfaceDistanceM: nearest
349
+ };
350
+ const dir = [
351
+ qrCenterWorld[0] - cameraPosWorld[0],
352
+ qrCenterWorld[1] - cameraPosWorld[1],
353
+ qrCenterWorld[2] - cameraPosWorld[2]
354
+ ];
355
+ const rayLength = Math.hypot(dir[0], dir[1], dir[2]);
356
+ if (rayLength > cellSizeM) {
357
+ const nx = dir[0] / rayLength;
358
+ const ny = dir[1] / rayLength;
359
+ const nz = dir[2] / rayLength;
360
+ const corridor = rayCorridorCells * cellSizeM;
361
+ const tMax = rayLength - occlusionMarginCells * cellSizeM;
362
+ for (const center of centers) {
363
+ const ox = center[0] - cameraPosWorld[0];
364
+ const oy = center[1] - cameraPosWorld[1];
365
+ const oz = center[2] - cameraPosWorld[2];
366
+ const t = ox * nx + oy * ny + oz * nz;
367
+ if (t <= cellSizeM || t >= tMax) continue;
368
+ if (Math.hypot(ox - t * nx, oy - t * ny, oz - t * nz) < corridor) return {
369
+ verdict: "behind-surface",
370
+ ok: false,
371
+ nearestSurfaceDistanceM: nearest
372
+ };
373
+ }
374
+ }
375
+ return {
376
+ verdict: "plausible",
377
+ ok: true,
378
+ nearestSurfaceDistanceM: nearest
379
+ };
380
+ }
381
+ //#endregion
382
+ export { localPlaneToEnu as a, fetchQrLevel as c, buildQrGpsVotes as i, parseQrLevel as l, createQrTrackingController as n, offsetGeo as o, METERS_PER_DEG_LAT as r, QrLevelValidationError as s, checkQrPlausibility as t };
@@ -1,4 +1,4 @@
1
- import { t as SubscribableStore } from "./subscribe-to-selector-CZ6k1NFP.js";
1
+ import { t as SubscribableStore } from "./subscribe-to-selector-B6EUmQ3I.js";
2
2
  import * as THREE from "three";
3
3
 
4
4
  //#region ../src/visualization/ar-world-group-alignment.d.ts
@@ -31,6 +31,28 @@ declare function computeCaptureSize(cameraWidth: number, cameraHeight: number, d
31
31
  width: number;
32
32
  height: number;
33
33
  };
34
+ /**
35
+ * Compute blit dimensions that PRESERVE the camera aspect ratio with the
36
+ * **longer edge fixed at `maxEdge`**. Unlike {@link computeCaptureSize} (which
37
+ * divides the native resolution by a user divisor), this fits the frame into a
38
+ * fixed pixel budget, so a 4:3 camera becomes e.g. 512×384 — NOT a stretched
39
+ * 512×512. Used by the QR-detection blit (B2) so the detector sees an
40
+ * undistorted code while the readback cost stays bounded by `maxEdge²`.
41
+ *
42
+ * @param cameraWidth - Native camera width in pixels (from XRCamera)
43
+ * @param cameraHeight - Native camera height in pixels (from XRCamera)
44
+ * @param maxEdge - Target length (px) of the longer output edge.
45
+ * @returns Integer dimensions, longer edge == `maxEdge`, aspect preserved
46
+ * (within rounding), each clamped to ≥ 1. When `maxEdge` itself is invalid
47
+ * (< 1 / NaN / Infinity) the longer edge falls back to
48
+ * `DEFAULT_BLIT_CONFIG.width` (still aspect-preserving). When the camera
49
+ * dimensions are invalid (≤ 0 / NaN / Infinity) the aspect is unknown, so it
50
+ * returns a square at the (possibly defaulted) edge.
51
+ */
52
+ declare function computeAspectFitSize(cameraWidth: number, cameraHeight: number, maxEdge: number): {
53
+ width: number;
54
+ height: number;
55
+ };
34
56
  /**
35
57
  * Captures readable pixel data from WebXR opaque camera textures
36
58
  * using the intermediate render target "blit" technique.
@@ -83,6 +105,29 @@ declare class CameraBlitCapture {
83
105
  width: number;
84
106
  height: number;
85
107
  } | null;
108
+ /**
109
+ * Capture the camera texture as **top-left-origin** RGBA — the orientation
110
+ * QR detection (and `BarcodeDetector`) expects — returning a FRESH copy that
111
+ * is safe to retain beyond the next capture.
112
+ *
113
+ * This is the efficient replacement for the demo's old JPEG round-trip: blit
114
+ * + readback (shared with {@link captureToBlob} / {@link captureToPixels}),
115
+ * then the same vertical flip the JPEG encoder applies, but with no encode →
116
+ * decode. Unlike {@link captureToPixels} (which returns the reusable internal
117
+ * buffer in WebGL bottom-row-first order), this returns an owned,
118
+ * correctly-oriented `Uint8ClampedArray`.
119
+ *
120
+ * IMPORTANT: Must be called within the XR animation frame callback, while the
121
+ * camera texture is valid.
122
+ *
123
+ * @returns `{ data, width, height }` (top-left RGBA), or null on
124
+ * failure/dispose.
125
+ */
126
+ captureToRgba(renderer: THREE.WebGLRenderer, cameraTexture: THREE.Texture): {
127
+ data: Uint8ClampedArray;
128
+ width: number;
129
+ height: number;
130
+ } | null;
86
131
  /**
87
132
  * Check if a pixel buffer is entirely black (all zeros).
88
133
  * Uses sampling for performance (checks BLACK_CHECK_SAMPLE_COUNT evenly-spaced pixels).
@@ -100,13 +145,21 @@ declare class CameraBlitCapture {
100
145
  */
101
146
  private pixelsToJpegBlob;
102
147
  private getFlippedImageData;
148
+ /**
149
+ * Produce a FRESH, top-left-origin RGBA copy of the internal readback buffer.
150
+ * WebGL `readPixels` returns bottom-row-first; this copies (so the internal
151
+ * buffer stays y-flipped for a subsequent capture) and swaps rows. Shared by
152
+ * the JPEG encode path ({@link getFlippedImageData}) and {@link captureToRgba}.
153
+ */
154
+ private flippedPixelCopy;
103
155
  private pixelsToJpegViaOffscreenCanvas;
104
156
  private pixelsToJpegViaCanvas;
105
157
  /**
106
- * Flip ImageData vertically in-place.
107
- * WebGL readPixels returns bottom-row-first; canvas expects top-row-first.
158
+ * Flip an RGBA buffer vertically in-place (row swap).
159
+ * WebGL readPixels returns bottom-row-first; canvas / detectors expect
160
+ * top-row-first.
108
161
  */
109
- private flipImageDataVertically;
162
+ private flipRowsVertically;
110
163
  /**
111
164
  * Resize the render target and pixel buffer to match new dimensions.
112
165
  * Call this when the camera resolution is first known or changes.
@@ -134,4 +187,4 @@ declare class CameraBlitCapture {
134
187
  dispose(): void;
135
188
  }
136
189
  //#endregion
137
- export { computeCaptureSize as i, CameraBlitCaptureConfig as n, DEFAULT_BLIT_CONFIG as r, CameraBlitCapture as t };
190
+ export { computeCaptureSize as a, computeAspectFitSize as i, CameraBlitCaptureConfig as n, DEFAULT_BLIT_CONFIG as r, CameraBlitCapture as t };
@@ -0,0 +1,24 @@
1
+ //#region ../src/ar/capture-motion-gate.d.ts
2
+ /**
3
+ * User-/consumer-facing motion-filter configuration. Shared by both config
4
+ * shapes that carry it — `ImageCaptureConfig` (what `ImageCaptureManager`
5
+ * consumes) and `ImageCaptureOptions` (the persisted recorder options) — so the
6
+ * one definition cannot drift between them. The window size and glitch ceilings
7
+ * are deliberately NOT here: they are internal tuning constants, not exposed.
8
+ */
9
+ interface MotionFilterConfig {
10
+ /** Master switch for the motion gate. Default `true`. */
11
+ enabled: boolean;
12
+ /** Angular-velocity threshold (rad/s); at/below this a frame is "calm". */
13
+ maxAngularVelocity: number;
14
+ /** Linear-velocity threshold (m/s); at/below this a frame is "calm". */
15
+ maxLinearVelocity: number;
16
+ /**
17
+ * Never-calm safety fallback: once a due capture has waited this many ms it
18
+ * fires regardless of motion, so an interval is never silently lost. A
19
+ * sensible value is ~2× the capture interval.
20
+ */
21
+ maxWaitMs: number;
22
+ }
23
+ //#endregion
24
+ export { MotionFilterConfig as t };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Angular velocity (rad/s) above which a sample is treated as a tracking glitch
3
+ * (e.g. relocalization teleport), not real motion. A genuine hand/device turn
4
+ * tops out well below this; a teleport is far above it. Module-internal tuning
5
+ * constant (not exported — the gate's user surface is only the four
6
+ * `MotionFilterConfig` fields); overridable per-instance via the `MotionWindow`
7
+ * constructor for tests.
8
+ */
9
+ const ANGULAR_GLITCH_CEILING_RAD_S = 50;
10
+ /**
11
+ * Linear velocity (m/s) above which a sample is treated as a tracking glitch.
12
+ * ~20 m/s (72 km/h) is unreachable by handheld scanning but trivially exceeded
13
+ * by a relocalization origin jump. Module-internal tuning constant (see
14
+ * {@link ANGULAR_GLITCH_CEILING_RAD_S}).
15
+ */
16
+ const LINEAR_GLITCH_CEILING_M_S = 20;
17
+ /**
18
+ * Default motion-filter configuration. Enabled by default (plan §1). Thresholds
19
+ * are PLACEHOLDERS pending on-device field tuning (plan §7) — record measured
20
+ * values in implementation-progress.md once known. `maxWaitMs` of 4000 ms is
21
+ * 2× the default 2000 ms image interval.
22
+ */
23
+ const DEFAULT_MOTION_FILTER = {
24
+ enabled: true,
25
+ maxAngularVelocity: .6,
26
+ maxLinearVelocity: .5,
27
+ maxWaitMs: 4e3
28
+ };
29
+ /**
30
+ * Decide whether a due capture should fire now or defer to a later frame.
31
+ *
32
+ * A frame is "calm" when BOTH windowed maxima are at/below their thresholds —
33
+ * an empty window reports `Infinity`, so it is never calm (no capture before a
34
+ * valid sample exists). If not calm, the capture is deferred UNTIL `msSinceDue`
35
+ * reaches `maxWaitMs`, at which point it fires regardless to guarantee an
36
+ * interval is never silently lost (worst case: one blurry frame, not a gap).
37
+ */
38
+ function decideCapture(input) {
39
+ if (input.windowMaxAngular <= input.maxAngularVelocity && input.windowMaxLinear <= input.maxLinearVelocity) return "capture";
40
+ if (input.msSinceDue >= input.maxWaitMs) return "capture";
41
+ return "defer";
42
+ }
43
+ /**
44
+ * Fixed-size ring of the most recent VALID (non-glitch) per-frame velocities.
45
+ *
46
+ * `maxAngular()`/`maxLinear()` return the max over the retained samples, or
47
+ * `Infinity` when empty so "no data yet" reads as not-calm in
48
+ * {@link decideCapture}. A sample exceeding either glitch ceiling is rejected
49
+ * outright (`push` returns `false`) and never stored, so a relocalization spike
50
+ * neither pollutes the window (no spurious defer) nor is treated as motion.
51
+ */
52
+ var MotionWindow = class {
53
+ angularGlitchCeiling;
54
+ linearGlitchCeiling;
55
+ size;
56
+ angular = [];
57
+ linear = [];
58
+ constructor(size = 3, angularGlitchCeiling = ANGULAR_GLITCH_CEILING_RAD_S, linearGlitchCeiling = LINEAR_GLITCH_CEILING_M_S) {
59
+ this.angularGlitchCeiling = angularGlitchCeiling;
60
+ this.linearGlitchCeiling = linearGlitchCeiling;
61
+ this.size = Number.isFinite(size) && size >= 1 ? Math.floor(size) : 1;
62
+ }
63
+ /**
64
+ * Record a per-frame velocity pair. Returns `false` (and stores nothing) when
65
+ * the sample exceeds either glitch ceiling, `true` otherwise.
66
+ */
67
+ push(angularVel, linearVel) {
68
+ if (!Number.isFinite(angularVel) || !Number.isFinite(linearVel) || angularVel > this.angularGlitchCeiling || linearVel > this.linearGlitchCeiling) return false;
69
+ this.angular.push(angularVel);
70
+ this.linear.push(linearVel);
71
+ if (this.angular.length > this.size) this.angular.shift();
72
+ if (this.linear.length > this.size) this.linear.shift();
73
+ return true;
74
+ }
75
+ /** Whether the window holds at least one valid (non-glitch) sample yet. */
76
+ hasSamples() {
77
+ return this.angular.length > 0;
78
+ }
79
+ /** Max angular velocity over the retained window, or `Infinity` if empty. */
80
+ maxAngular() {
81
+ return this.angular.length === 0 ? Number.POSITIVE_INFINITY : Math.max(...this.angular);
82
+ }
83
+ /** Max linear velocity over the retained window, or `Infinity` if empty. */
84
+ maxLinear() {
85
+ return this.linear.length === 0 ? Number.POSITIVE_INFINITY : Math.max(...this.linear);
86
+ }
87
+ /** Clear all samples (e.g. on tracking loss / capture restart). */
88
+ reset() {
89
+ this.angular.length = 0;
90
+ this.linear.length = 0;
91
+ }
92
+ };
93
+ //#endregion
94
+ export { MotionWindow as n, decideCapture as r, DEFAULT_MOTION_FILTER as t };
@@ -0,0 +1,50 @@
1
+ //#region ../src/utils/concurrency.d.ts
2
+ /**
3
+ * Concurrency Utilities
4
+ *
5
+ * Provides helpers for limiting the number of concurrent async operations.
6
+ * Used to prevent excessive memory consumption when scanning many files
7
+ * in parallel (e.g., reading zip metadata during scenario discovery).
8
+ */
9
+ /**
10
+ * Map over an array with a concurrency limit on the async mapper function.
11
+ *
12
+ * Behaves like `Promise.all(items.map(fn))` but limits how many mapper
13
+ * invocations run simultaneously. Results are returned in the same order
14
+ * as the input items.
15
+ *
16
+ * @param items - Array of items to process
17
+ * @param limit - Maximum number of concurrent mapper invocations
18
+ * @param mapper - Async function to apply to each item
19
+ * @returns Array of results in the same order as input items
20
+ * @throws Re-throws the first error from any mapper invocation (fail-fast)
21
+ */
22
+ declare function mapWithConcurrencyLimit<T, R>(items: T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
23
+ /**
24
+ * Run an async worker over each item with a concurrency cap, invoking the worker
25
+ * per item as the pool pulls it (rather than collecting results into an array).
26
+ *
27
+ * Unlike {@link mapWithConcurrencyLimit}, this returns no values — the worker is
28
+ * expected to produce its side effect (e.g. emit a streaming callback) itself.
29
+ * This is the shape progressive consumers need: each item is handled and can be
30
+ * reported as soon as it settles, instead of waiting for the whole batch.
31
+ *
32
+ * Completion order is **not** guaranteed (it depends on which worker finishes
33
+ * first). Each item's worker is invoked exactly once unless the run is aborted.
34
+ *
35
+ * When an `AbortSignal` is supplied, workers stop pulling **new** items as soon
36
+ * as the signal is aborted (checked before each pull). Items already in flight
37
+ * run to completion — the File System Access reads underneath cannot be torn
38
+ * mid-read — but no further items are started, so a destroyed consumer stops
39
+ * accumulating work. If the signal is already aborted, no work starts at all.
40
+ *
41
+ * @param items - Items to process
42
+ * @param limit - Maximum number of concurrent worker invocations (`>= 1`)
43
+ * @param worker - Async side-effecting function applied to each item
44
+ * @param signal - Optional abort signal that halts pulling new items
45
+ * @throws RangeError if `limit < 1`
46
+ * @throws Re-throws the first error from any worker invocation (fail-fast)
47
+ */
48
+ declare function forEachWithConcurrencyLimit<T>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<void>, signal?: AbortSignal): Promise<void>;
49
+ //#endregion
50
+ export { mapWithConcurrencyLimit as n, forEachWithConcurrencyLimit as t };
@@ -1,2 +1,2 @@
1
- import { a as LibraryRootState, c as RootState, d as calcRelativeCoordsInMeters, f as createGpsSlamStore, g as webxrToNUE, h as validateLicenseKey, i as LatLongAlt, l as Vector3, m as odometryTrackingRestarted, n as GpsPoint, o as Matrix4, p as isIdentityMatrix4, r as LatLong, s as Quaternion, t as ArImageCapture, u as calcGpsCoords } from "../index-CF05QV2m.js";
2
- export { ArImageCapture, GpsPoint, LatLong, LatLongAlt, LibraryRootState, Matrix4, Quaternion, RootState, Vector3, calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, odometryTrackingRestarted, validateLicenseKey, webxrToNUE };
1
+ import { _ as odometryTrackingRestarted, a as LibraryRootState, c as RootState, d as bearingDeltaDeg, f as calcGpsCoords, g as magneticHeadingFromEnuQuat, h as isIdentityMatrix4, i as LatLongAlt, l as Vector3, m as createGpsSlamStore, n as GpsPoint, o as Matrix4, p as calcRelativeCoordsInMeters, r as LatLong, s as Quaternion, t as ArImageCapture, u as arNorthBearingDeg, v as validateLicenseKey, y as webxrToNUE } from "../index-_Rhgoqkh.js";
2
+ export { ArImageCapture, GpsPoint, LatLong, LatLongAlt, LibraryRootState, Matrix4, Quaternion, RootState, Vector3, arNorthBearingDeg, bearingDeltaDeg, calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, magneticHeadingFromEnuQuat, odometryTrackingRestarted, validateLicenseKey, webxrToNUE };
@@ -1,2 +1,2 @@
1
- import { calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, odometryTrackingRestarted, validateLicenseKey, webxrToNUE } from "gps-plus-slam-js";
2
- export { calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, odometryTrackingRestarted, validateLicenseKey, webxrToNUE };
1
+ import { arNorthBearingDeg, bearingDeltaDeg, calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, magneticHeadingFromEnuQuat, odometryTrackingRestarted, validateLicenseKey, webxrToNUE } from "gps-plus-slam-js";
2
+ export { arNorthBearingDeg, bearingDeltaDeg, calcGpsCoords, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, magneticHeadingFromEnuQuat, odometryTrackingRestarted, validateLicenseKey, webxrToNUE };