gps-plus-slam-app-framework 1.2.0 → 1.4.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 (230) hide show
  1. package/dist/{app-selectors-CcZ5UV-8.d.ts → app-selectors-CrOucL00.d.ts} +1 -1
  2. package/dist/ar/bresenham3d.d.ts +1 -1
  3. package/dist/ar/camera-blit-capture.d.ts +2 -2
  4. package/dist/ar/camera-blit-capture.js +87 -9
  5. package/dist/ar/capability-checker.d.ts +1 -1
  6. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  7. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  8. package/dist/ar/depth-grid-lookup.d.ts +2 -0
  9. package/dist/ar/depth-grid-lookup.js +43 -0
  10. package/dist/ar/depth-sampler.d.ts +2 -2
  11. package/dist/ar/depth-sampler.js +2 -4
  12. package/dist/ar/depth-unprojection.d.ts +1 -1
  13. package/dist/ar/enable-gps-ar.d.ts +1 -1
  14. package/dist/ar/enable-gps-ar.js +26 -1
  15. package/dist/ar/frame-loop.d.ts +1 -1
  16. package/dist/ar/image-capture.d.ts +2 -2
  17. package/dist/ar/image-capture.js +7 -5
  18. package/dist/ar/index.d.ts +30 -19
  19. package/dist/ar/index.js +15 -4
  20. package/dist/ar/occupancy-grid.d.ts +1 -1
  21. package/dist/ar/occupancy-grid.js +29 -2
  22. package/dist/ar/planar-pnp.d.ts +2 -0
  23. package/dist/ar/planar-pnp.js +425 -0
  24. package/dist/ar/qr-debug-view.d.ts +2 -0
  25. package/dist/ar/qr-debug-view.js +89 -0
  26. package/dist/ar/qr-derived-pose.d.ts +2 -0
  27. package/dist/ar/qr-derived-pose.js +114 -0
  28. package/dist/ar/qr-detection-controller.d.ts +2 -0
  29. package/dist/ar/qr-detection-controller.js +2 -0
  30. package/dist/ar/qr-frontend.d.ts +2 -0
  31. package/dist/ar/qr-frontend.js +60 -0
  32. package/dist/ar/qr-pose.d.ts +2 -0
  33. package/dist/ar/qr-pose.js +267 -0
  34. package/dist/ar/qr-size-depth-context.d.ts +3 -0
  35. package/dist/ar/qr-size-depth-context.js +42 -0
  36. package/dist/ar/qr-size-measurer.d.ts +2 -0
  37. package/dist/ar/qr-size-measurer.js +2 -0
  38. package/dist/ar/replay-scene.d.ts +1 -1
  39. package/dist/ar/replay-scene.js +1 -1
  40. package/dist/ar/scene-node-names.d.ts +1 -1
  41. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  42. package/dist/ar/webxr-session.d.ts +4 -4
  43. package/dist/ar/webxr-session.js +2 -2
  44. package/dist/ar/xr-camera-texture.d.ts +1 -1
  45. package/dist/ar/xr-error-handler.d.ts +1 -1
  46. package/dist/ar/xr-frame-loop.d.ts +1 -1
  47. package/dist/ar-Dc72Ksiu.js +382 -0
  48. package/dist/{ar-world-group-alignment-ColHB_5A.d.ts → ar-world-group-alignment-DDLkgNf_.d.ts} +1 -1
  49. package/dist/{camera-blit-capture-D5_mNhla.d.ts → camera-blit-capture-CqWe1G7s.d.ts} +66 -4
  50. package/dist/concurrency-DqAlKX4j.d.ts +50 -0
  51. package/dist/core/index.d.ts +1 -1
  52. package/dist/{create-slam-app-store-qNXQ-tq5.d.ts → create-slam-app-store-Ce2FMKCp.d.ts} +5 -5
  53. package/dist/depth-grid-lookup-vMDrGTk_.d.ts +21 -0
  54. package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-DwfPYzS7.d.ts} +1 -1
  55. package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-CK3b9htH.d.ts} +1 -1
  56. package/dist/{enable-gps-ar-CapmADVN.d.ts → enable-gps-ar-CjZ1nEdu.d.ts} +4 -4
  57. package/dist/file-system-utils-Bs7lpeVP.d.ts +40 -0
  58. package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-D_Af8Dt3.d.ts} +1 -1
  59. package/dist/{fused-path-DJoVu98b.d.ts → fused-path-C0FHe67t.d.ts} +1 -1
  60. package/dist/geo/h3-proximity.d.ts +2 -2
  61. package/dist/geo/h3-proximity.js +61 -2
  62. package/dist/geo/index.d.ts +2 -2
  63. package/dist/geo/index.js +2 -2
  64. package/dist/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-C6I9NDHA.d.ts} +1 -1
  65. package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DaHg2aPZ.d.ts} +1 -1
  66. package/dist/{gps-event-coordinator-BDIQde2x.d.ts → gps-event-coordinator-C7hwHyLA.d.ts} +3 -3
  67. package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Dd0G2bD0.d.ts} +18 -0
  68. package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-B57C6rYL.d.ts} +39 -1
  69. package/dist/{image-capture-DZ4HFRgS.d.ts → image-capture-C2Feywet.d.ts} +30 -4
  70. package/dist/index-BvLo2poy.d.ts +399 -0
  71. package/dist/{index-KJq2amHF.d.ts → index-m_LneUSi.d.ts} +1 -1
  72. package/dist/index.d.ts +77 -67
  73. package/dist/index.js +18 -9
  74. package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-bnb2U1NQ.d.ts} +1 -1
  75. package/dist/{map-data-CkloBxIl.d.ts → map-data-DdjPiSav.d.ts} +1 -1
  76. package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-CPVdAsFq.d.ts} +1 -1
  77. package/dist/{null-storage-backend-CxEjZHRW.d.ts → null-storage-backend-CuSdYFsM.d.ts} +2 -2
  78. package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-Bvi2Gw1U.d.ts} +12 -2
  79. package/dist/{opfs-storage-DYm8WHq7.d.ts → opfs-storage-DMtlt57S.d.ts} +25 -6
  80. package/dist/{opfs-storage-backend-CjSAuqg_.d.ts → opfs-storage-backend-CeQAb0SD.d.ts} +3 -3
  81. package/dist/{persistence-middleware-DwyzgfWr.d.ts → persistence-middleware-5eearhSV.d.ts} +1 -1
  82. package/dist/planar-pnp-cjthe7Be.d.ts +67 -0
  83. package/dist/qr-debug-view-B8gTzjMm.d.ts +29 -0
  84. package/dist/qr-derived-pose-DinKE600.d.ts +122 -0
  85. package/dist/qr-detected-slice-Cpv-dg82.d.ts +166 -0
  86. package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
  87. package/dist/qr-detection-controller-Cd2Ih4cC.d.ts +48 -0
  88. package/dist/qr-frontend-DufZp2xS.d.ts +58 -0
  89. package/dist/qr-pose-Bt2reSw7.d.ts +138 -0
  90. package/dist/qr-pose-aggregation-CFlmLm6w.d.ts +90 -0
  91. package/dist/qr-pose-aggregation-DXR9STHs.js +200 -0
  92. package/dist/qr-size-depth-context-onu36E9s.d.ts +13 -0
  93. package/dist/qr-size-measurer-D7739ZpB.d.ts +165 -0
  94. package/dist/qr-size-measurer-DLb33geH.js +596 -0
  95. package/dist/recording-options-D93kksJU.d.ts +369 -0
  96. package/dist/{recording-replayer-BL-x-7k0.d.ts → recording-replayer-CUEK82tM.d.ts} +2 -2
  97. package/dist/recording-slice-B-uCgGoh.d.ts +55 -0
  98. package/dist/{replay-engine-BRqszuQ9.d.ts → replay-engine-BSVd7Zzb.d.ts} +7 -4
  99. package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-CPt56pps.d.ts} +3 -3
  100. package/dist/sensors/gps-error-handler.d.ts +1 -1
  101. package/dist/sensors/gps.d.ts +1 -1
  102. package/dist/sensors/index.d.ts +3 -3
  103. package/dist/sensors/permission-checker.d.ts +1 -1
  104. package/dist/sensors/permission-checker.js +1 -1
  105. package/dist/state/app-selectors.d.ts +1 -1
  106. package/dist/state/app-selectors.js +3 -1
  107. package/dist/state/combined-root-state.d.ts +1 -1
  108. package/dist/state/create-slam-app-store.d.ts +1 -1
  109. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  110. package/dist/state/gps-event-coordinator.d.ts +1 -1
  111. package/dist/state/index.d.ts +20 -17
  112. package/dist/state/index.js +3 -2
  113. package/dist/state/persistence-middleware.d.ts +1 -1
  114. package/dist/state/qr-detected-slice.d.ts +4 -0
  115. package/dist/state/qr-detected-slice.js +311 -0
  116. package/dist/state/recording-options.d.ts +2 -2
  117. package/dist/state/recording-options.js +128 -4
  118. package/dist/state/recording-replayer.d.ts +2 -2
  119. package/dist/state/recording-slice.d.ts +1 -1
  120. package/dist/state/recording-slice.js +5 -1
  121. package/dist/state/replay-engine.d.ts +1 -1
  122. package/dist/state/replay-engine.js +7 -3
  123. package/dist/state/store-subscribers.d.ts +2 -2
  124. package/dist/state/subscribe-to-selector.d.ts +1 -1
  125. package/dist/state/tracking-quality.d.ts +1 -1
  126. package/dist/state/tracking-slice.d.ts +1 -1
  127. package/dist/{state-BkIfpiyA.js → state-IEw0ZsBu.js} +1 -0
  128. package/dist/storage/file-system-utils.d.ts +2 -2
  129. package/dist/storage/file-system-utils.js +17 -1
  130. package/dist/storage/index.d.ts +9 -9
  131. package/dist/storage/index.js +4 -4
  132. package/dist/storage/null-storage-backend.d.ts +1 -1
  133. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  134. package/dist/storage/opfs-storage-backend.js +3 -3
  135. package/dist/storage/opfs-storage.d.ts +1 -1
  136. package/dist/storage/opfs-storage.js +9 -8
  137. package/dist/storage/storage-backend.d.ts +1 -1
  138. package/dist/storage/zip-coverage-embed.d.ts +2 -0
  139. package/dist/storage/zip-coverage-embed.js +88 -0
  140. package/dist/storage/zip-export.d.ts +2 -2
  141. package/dist/storage/zip-export.js +38 -60
  142. package/dist/storage/zip-reader.d.ts +1 -1
  143. package/dist/storage/zip-reader.js +1 -1
  144. package/dist/{storage-backend-C9HLFtgz.d.ts → storage-backend-CTJHNdMp.d.ts} +1 -1
  145. package/dist/{store-subscribers-rrkLkFTt.d.ts → store-subscribers-BZP81hjh.d.ts} +2 -2
  146. package/dist/{subscribe-to-selector-CP3BKSbF.d.ts → subscribe-to-selector-Bp3sLx8L.d.ts} +1 -1
  147. package/dist/test-utils/browser-mocks.d.ts +7 -1
  148. package/dist/test-utils/browser-mocks.js +6 -0
  149. package/dist/test-utils/zip-round-trip-helpers.d.ts +9 -1
  150. package/dist/test-utils/zip-round-trip-helpers.js +8 -3
  151. package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-DJZ9qeGg.d.ts} +1 -1
  152. package/dist/types/ar-types.d.ts +1 -1
  153. package/dist/types/geo-types.d.ts +1 -1
  154. package/dist/types/index.d.ts +2 -2
  155. package/dist/utils/concurrency.d.ts +2 -2
  156. package/dist/utils/concurrency.js +66 -2
  157. package/dist/utils/failure-tracker.d.ts +1 -1
  158. package/dist/utils/format-file-size.d.ts +1 -1
  159. package/dist/utils/fused-path.d.ts +1 -1
  160. package/dist/utils/index.d.ts +6 -6
  161. package/dist/utils/list-formatter.d.ts +1 -1
  162. package/dist/utils/logger.d.ts +1 -1
  163. package/dist/visualization/accuracy-circles.d.ts +1 -1
  164. package/dist/visualization/alignment-lerper.d.ts +1 -1
  165. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  166. package/dist/visualization/ar-world-group-alignment.js +1 -1
  167. package/dist/visualization/camera-follower.d.ts +1 -1
  168. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  169. package/dist/visualization/frame-conversions.d.ts +1 -1
  170. package/dist/visualization/frustum-visibility.d.ts +1 -1
  171. package/dist/visualization/gps-anchor.d.ts +1 -1
  172. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  173. package/dist/visualization/gps-event-markers.d.ts +1 -1
  174. package/dist/visualization/gps-event-markers.js +40 -5
  175. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  176. package/dist/visualization/hit-test-reticle.js +1 -0
  177. package/dist/visualization/index.d.ts +18 -18
  178. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  179. package/dist/visualization/leaflet-map-overlay.js +15 -3
  180. package/dist/visualization/lerp-utils.d.ts +1 -1
  181. package/dist/visualization/map-data.d.ts +1 -1
  182. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  183. package/dist/visualization/map-overlay.d.ts +1 -1
  184. package/dist/visualization/three-dispose.d.ts +1 -1
  185. package/dist/visualization/vis-colors.d.ts +1 -1
  186. package/dist/{webxr-session-QhcSSub5.js → webxr-session-CJF9Nijp.js} +234 -9
  187. package/dist/{webxr-session-DtAG9srK.d.ts → webxr-session-COsOjJwn.d.ts} +76 -8
  188. package/dist/zip-coverage-embed-tFohI2J4.d.ts +41 -0
  189. package/dist/{zip-export-D_5HAvFz.d.ts → zip-export-DDWXInTR.d.ts} +28 -24
  190. package/package.json +2 -2
  191. package/dist/concurrency-CO1FQvs3.d.ts +0 -24
  192. package/dist/file-system-C_eqiEA8.d.ts +0 -114
  193. package/dist/file-system-utils-DyAmcuvX.d.ts +0 -24
  194. package/dist/recording-options-DJBXuMxV.d.ts +0 -182
  195. package/dist/recording-slice-89O07ebs.d.ts +0 -36
  196. package/dist/storage/file-system.d.ts +0 -2
  197. package/dist/storage/file-system.js +0 -289
  198. /package/dist/{accuracy-circles-BpwWAs8G.d.ts → accuracy-circles-Pi2YeCrM.d.ts} +0 -0
  199. /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-BUTsQ2yu.d.ts} +0 -0
  200. /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BckI1E-O.d.ts} +0 -0
  201. /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-ByjcmEgM.d.ts} +0 -0
  202. /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-BTb2Yp12.d.ts} +0 -0
  203. /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-CXqoJYtb.d.ts} +0 -0
  204. /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-BL5SoG2a.d.ts} +0 -0
  205. /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-BNK189P0.d.ts} +0 -0
  206. /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-m-ab19vp.d.ts} +0 -0
  207. /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-Cnc_xWUw.d.ts} +0 -0
  208. /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-BFUWvir1.d.ts} +0 -0
  209. /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-BEO9_q3k.d.ts} +0 -0
  210. /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-D_RGmH98.d.ts} +0 -0
  211. /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-DqiBzPO9.d.ts} +0 -0
  212. /package/dist/{gps-VuezJWK3.d.ts → gps-C5inWeNV.d.ts} +0 -0
  213. /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-BExxrWJ-.d.ts} +0 -0
  214. /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-C2bS69oJ.d.ts} +0 -0
  215. /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-Dqak9hiR.d.ts} +0 -0
  216. /package/dist/{index-CF05QV2m.d.ts → index-eaI1Z1KR.d.ts} +0 -0
  217. /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-kLxMJ2Xj.d.ts} +0 -0
  218. /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-ct8txwvR.d.ts} +0 -0
  219. /package/dist/{logger-BEHXsnnq.d.ts → logger-CzQrCe6C.d.ts} +0 -0
  220. /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dci4yvm3.d.ts} +0 -0
  221. /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CtPtPQRq.d.ts} +0 -0
  222. /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-CdNeQjOE.d.ts} +0 -0
  223. /package/dist/{session-disposers-M-oashRH.js → session-disposers-D8wnO-uW.js} +0 -0
  224. /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-BCZkSJp2.d.ts} +0 -0
  225. /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-BZsSLI4i.d.ts} +0 -0
  226. /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-CW1wsbm6.d.ts} +0 -0
  227. /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-BmrP7iNZ.d.ts} +0 -0
  228. /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-BEjRgZjt.d.ts} +0 -0
  229. /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-K8ebdjLw.d.ts} +0 -0
  230. /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-Q2ue6zlC.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { gridDisk, latLngToCell } from "h3-js";
1
+ import { cellToParent, getResolution, gridDisk, isValidCell, latLngToCell } from "h3-js";
2
2
  //#region ../src/geo/h3-proximity.ts
3
3
  /**
4
4
  * H3 Geo-Anchor Proximity Matching
@@ -22,6 +22,65 @@ function gpsToH3(lat, lng) {
22
22
  return latLngToCell(lat, lng, 11);
23
23
  }
24
24
  /**
25
+ * Reduce a recorded GPS path to the deduplicated set of res-11 H3 cells it
26
+ * crossed — the per-tour "coverage index" stored in `SessionMetadata.h3Cells`.
27
+ *
28
+ * Cells are returned in first-seen (chronological) order and deduplicated, so a
29
+ * tour that dwells in one ~25 m cell contributes a single cell. This keeps the
30
+ * stored index small (see the metadata-size risk in the map-browser feedback
31
+ * doc, §7). Non-finite coordinates are skipped defensively — bad sensor data
32
+ * must not poison the index or throw inside `latLngToCell`.
33
+ */
34
+ function gpsPathToCoverageCells(path) {
35
+ const seen = /* @__PURE__ */ new Set();
36
+ const cells = [];
37
+ for (const p of path) {
38
+ if (!Number.isFinite(p.lat) || !Number.isFinite(p.lng)) continue;
39
+ const cell = gpsToH3(p.lat, p.lng);
40
+ if (!seen.has(cell)) {
41
+ seen.add(cell);
42
+ cells.push(cell);
43
+ }
44
+ }
45
+ return cells;
46
+ }
47
+ /**
48
+ * Coarsen res-11 coverage cells to a target resolution for map zoom-clustering.
49
+ *
50
+ * Uses h3-js `cellToParent` — the ONLY correct way to coarsen an H3 cell. NEVER
51
+ * truncate the hex-string id: resolution is encoded in the high bits with
52
+ * trailing `f` padding, so slicing yields INVALID cells, not parents (verified;
53
+ * see the D1 gotcha in
54
+ * docs/2026-06-14-map-centric-recording-browser-and-h3-index-user-feedback.md).
55
+ *
56
+ * `targetRes` is clamped to `[0, H3_RESOLUTION]` because `cellToParent` throws
57
+ * when asked for a FINER resolution than the cell, and res 11 is the finest
58
+ * data stored — so the highest map zooms render the stored cells unclustered
59
+ * (`cellToParent(cell, 11) === cell`). A non-finite `targetRes` (e.g. a bad
60
+ * zoom→res mapping) degrades to unclustered output rather than throwing.
61
+ * Invalid input cells (corrupt/legacy metadata) are skipped defensively, as are
62
+ * valid cells coarser than `res` (which have no parent at the finer target res
63
+ * and would otherwise throw "incompatible resolutions").
64
+ *
65
+ * Output is deduplicated in first-seen order: sibling cells under one parent
66
+ * collapse to a single tile.
67
+ */
68
+ function clusterCellsByZoom(cells, targetRes) {
69
+ const res = Number.isFinite(targetRes) ? Math.max(0, Math.min(11, Math.floor(targetRes))) : 11;
70
+ const seen = /* @__PURE__ */ new Set();
71
+ const parents = [];
72
+ for (const cell of cells) {
73
+ if (!isValidCell(cell)) continue;
74
+ if (getResolution(cell) < res) continue;
75
+ const parent = cellToParent(cell, res);
76
+ if (!seen.has(parent)) {
77
+ seen.add(parent);
78
+ parents.push(parent);
79
+ }
80
+ }
81
+ return parents;
82
+ }
83
+ /**
25
84
  * Approximate distance in metres using equirectangular projection.
26
85
  * Accurate to < 0.1% for distances under 1 km at mid-latitudes,
27
86
  * which is sufficient for ranking ref point candidates within a
@@ -75,4 +134,4 @@ function isH3Index(id) {
75
134
  return H3_INDEX_PATTERN.test(id);
76
135
  }
77
136
  //#endregion
78
- export { H3_RESOLUTION, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
137
+ export { H3_RESOLUTION, approxDistanceMetres, clusterCellsByZoom, findNearbyGeoAnchor, gpsPathToCoverageCells, gpsToH3, h3CellsMatch, isH3Index };
@@ -1,2 +1,2 @@
1
- import { a as gpsToH3, i as findNearbyGeoAnchor, n as KnownGeoAnchor, o as h3CellsMatch, r as approxDistanceMetres, s as isH3Index, t as H3_RESOLUTION } from "../h3-proximity-I1msGPgE.js";
2
- export { H3_RESOLUTION, type KnownGeoAnchor, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
1
+ import { a as findNearbyGeoAnchor, c as h3CellsMatch, i as clusterCellsByZoom, l as isH3Index, n as KnownGeoAnchor, o as gpsPathToCoverageCells, r as approxDistanceMetres, s as gpsToH3, t as H3_RESOLUTION } from "../h3-proximity-B57C6rYL.js";
2
+ export { H3_RESOLUTION, type KnownGeoAnchor, approxDistanceMetres, clusterCellsByZoom, findNearbyGeoAnchor, gpsPathToCoverageCells, gpsToH3, h3CellsMatch, isH3Index };
package/dist/geo/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { H3_RESOLUTION, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index } from "./h3-proximity.js";
2
- export { H3_RESOLUTION, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
1
+ import { H3_RESOLUTION, approxDistanceMetres, clusterCellsByZoom, findNearbyGeoAnchor, gpsPathToCoverageCells, gpsToH3, h3CellsMatch, isH3Index } from "./h3-proximity.js";
2
+ export { H3_RESOLUTION, approxDistanceMetres, clusterCellsByZoom, findNearbyGeoAnchor, gpsPathToCoverageCells, gpsToH3, h3CellsMatch, isH3Index };
@@ -1,4 +1,4 @@
1
- import { i as LatLongAlt, r as LatLong } from "./index-CF05QV2m.js";
1
+ import { i as LatLongAlt, r as LatLong } from "./index-eaI1Z1KR.js";
2
2
  import * as THREE from "three";
3
3
 
4
4
  //#region ../src/visualization/gps-anchor.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ARPose } from "./ar-types-Dan1MVTi.js";
1
+ import { t as ARPose } from "./ar-types-BckI1E-O.js";
2
2
  import { Quaternion, Vector3 } from "gps-plus-slam-js";
3
3
 
4
4
  //#region ../src/state/gps-ar-pose-sampler.d.ts
@@ -1,6 +1,6 @@
1
- import { t as ARPose } from "./ar-types-Dan1MVTi.js";
2
- import { n as RawDeviceOrientation, t as GpsPosition } from "./gps-VuezJWK3.js";
3
- import { i as SlamAppStore } from "./create-slam-app-store-qNXQ-tq5.js";
1
+ import { t as ARPose } from "./ar-types-BckI1E-O.js";
2
+ import { n as RawDeviceOrientation, t as GpsPosition } from "./gps-C5inWeNV.js";
3
+ import { i as SlamAppStore } from "./create-slam-app-store-Ce2FMKCp.js";
4
4
  import { Quaternion, RawGpsPoint, RecordGpsEventPayload, Vector3, eulerToQuaternion } from "gps-plus-slam-js";
5
5
  import { ReducersMapObject } from "@reduxjs/toolkit";
6
6
 
@@ -24,6 +24,14 @@ declare class GpsEventVisualizer {
24
24
  private zeroRef;
25
25
  private eventCounter;
26
26
  private snapshotCounter;
27
+ /**
28
+ * Whether the debug markers are drawn. Toggled by {@link setVisible} for the
29
+ * recorder's `visualization.gpsAlignmentMarkers` opt-out (Finding B). Applied
30
+ * to every marker at creation so events spawned later by the live
31
+ * store-subscriber inherit the current state instead of popping into view.
32
+ * Default `true` — the markers render exactly as before until opted out.
33
+ */
34
+ private markersVisible;
27
35
  /**
28
36
  * Set the GPS zero reference (origin for coordinate conversion).
29
37
  * Must be called before adding GPS events.
@@ -64,6 +72,16 @@ declare class GpsEventVisualizer {
64
72
  * Create a sphere marker mesh with the given color and name.
65
73
  */
66
74
  private createMarkerMesh;
75
+ /**
76
+ * Show or hide ALL debug markers — raw GPS (yellow), fused (cyan), and
77
+ * alignment-snapshot (red) — and remember the state so markers added later
78
+ * inherit it. Used by the recorder's `visualization.gpsAlignmentMarkers`
79
+ * opt-out, read once at Enter-AR (live only; replay keeps markers visible).
80
+ *
81
+ * This only changes rendering: capture, GPS-event recording, counts, and the
82
+ * snapshot positions read back at session end are all unaffected.
83
+ */
84
+ setVisible(visible: boolean): void;
67
85
  /**
68
86
  * Clear all markers from the scene and reset state.
69
87
  */
@@ -29,6 +29,44 @@ interface KnownGeoAnchor {
29
29
  * Compute the H3 resolution-11 index for a GPS position.
30
30
  */
31
31
  declare function gpsToH3(lat: number, lng: number): string;
32
+ /** Minimal GPS coordinate shape (Leaflet convention: `lng`, not `lon`). */
33
+ interface LatLngLike {
34
+ readonly lat: number;
35
+ readonly lng: number;
36
+ }
37
+ /**
38
+ * Reduce a recorded GPS path to the deduplicated set of res-11 H3 cells it
39
+ * crossed — the per-tour "coverage index" stored in `SessionMetadata.h3Cells`.
40
+ *
41
+ * Cells are returned in first-seen (chronological) order and deduplicated, so a
42
+ * tour that dwells in one ~25 m cell contributes a single cell. This keeps the
43
+ * stored index small (see the metadata-size risk in the map-browser feedback
44
+ * doc, §7). Non-finite coordinates are skipped defensively — bad sensor data
45
+ * must not poison the index or throw inside `latLngToCell`.
46
+ */
47
+ declare function gpsPathToCoverageCells(path: readonly LatLngLike[]): string[];
48
+ /**
49
+ * Coarsen res-11 coverage cells to a target resolution for map zoom-clustering.
50
+ *
51
+ * Uses h3-js `cellToParent` — the ONLY correct way to coarsen an H3 cell. NEVER
52
+ * truncate the hex-string id: resolution is encoded in the high bits with
53
+ * trailing `f` padding, so slicing yields INVALID cells, not parents (verified;
54
+ * see the D1 gotcha in
55
+ * docs/2026-06-14-map-centric-recording-browser-and-h3-index-user-feedback.md).
56
+ *
57
+ * `targetRes` is clamped to `[0, H3_RESOLUTION]` because `cellToParent` throws
58
+ * when asked for a FINER resolution than the cell, and res 11 is the finest
59
+ * data stored — so the highest map zooms render the stored cells unclustered
60
+ * (`cellToParent(cell, 11) === cell`). A non-finite `targetRes` (e.g. a bad
61
+ * zoom→res mapping) degrades to unclustered output rather than throwing.
62
+ * Invalid input cells (corrupt/legacy metadata) are skipped defensively, as are
63
+ * valid cells coarser than `res` (which have no parent at the finer target res
64
+ * and would otherwise throw "incompatible resolutions").
65
+ *
66
+ * Output is deduplicated in first-seen order: sibling cells under one parent
67
+ * collapse to a single tile.
68
+ */
69
+ declare function clusterCellsByZoom(cells: readonly string[], targetRes: number): string[];
32
70
  /**
33
71
  * Approximate distance in metres using equirectangular projection.
34
72
  * Accurate to < 0.1% for distances under 1 km at mid-latitudes,
@@ -60,4 +98,4 @@ declare function h3CellsMatch(h3a: string, h3b: string): boolean;
60
98
  */
61
99
  declare function isH3Index(id: string): boolean;
62
100
  //#endregion
63
- export { gpsToH3 as a, findNearbyGeoAnchor as i, KnownGeoAnchor as n, h3CellsMatch as o, approxDistanceMetres as r, isH3Index as s, H3_RESOLUTION as t };
101
+ export { findNearbyGeoAnchor as a, h3CellsMatch as c, clusterCellsByZoom as i, isH3Index as l, KnownGeoAnchor as n, gpsPathToCoverageCells as o, approxDistanceMetres as r, gpsToH3 as s, H3_RESOLUTION as t };
@@ -1,4 +1,4 @@
1
- import { o as WebXRQuaternion, s as WebXRVec3, t as ARPose } from "./ar-types-Dan1MVTi.js";
1
+ import { o as WebXRQuaternion, s as WebXRVec3, t as ARPose } from "./ar-types-BckI1E-O.js";
2
2
 
3
3
  //#region ../src/ar/image-capture.d.ts
4
4
  /**
@@ -54,6 +54,30 @@ interface CapturedImage {
54
54
  readonly rotation: WebXRQuaternion;
55
55
  /** Device screen orientation (0, 90, 180, 270) */
56
56
  readonly screenRotation: number;
57
+ /**
58
+ * Width of the encoded JPEG in pixels — the blit/canvas dimensions the blob
59
+ * was produced from, so it equals the decoded image's width. Persisted as
60
+ * `ArImageCapture.width` so consumers (the 3D frame-tile visualizer) can
61
+ * render each frame at its true aspect ratio. Always present from the
62
+ * capture pipeline; optional so the field can be threaded without forcing
63
+ * every test/caller to supply it.
64
+ */
65
+ readonly width?: number;
66
+ /** Height of the encoded JPEG in pixels. See {@link CapturedImage.width}. */
67
+ readonly height?: number;
68
+ }
69
+ /**
70
+ * A captured frame blob together with the pixel dimensions it was encoded at.
71
+ * Returned by the optional `captureFrame` (blit) callback so the pose-invariant
72
+ * image size can be persisted as first-class metadata (frame-tile aspect-ratio
73
+ * fix, D1 of 2026-06-13-frame-tile-rendering-bugs-user-feedback.md). The
74
+ * dimensions are the blit render-target size — identical to the JPEG's own
75
+ * width/height — so no decode is needed to learn the aspect ratio.
76
+ */
77
+ interface CapturedFrame {
78
+ readonly blob: Blob;
79
+ readonly width: number;
80
+ readonly height: number;
57
81
  }
58
82
  /**
59
83
  * Callbacks for image capture integration
@@ -80,10 +104,12 @@ interface ImageCaptureCallbacks {
80
104
  * be read directly via canvas.toBlob() (which returns black pixels).
81
105
  *
82
106
  * @param quality - JPEG quality 0.0-1.0
83
- * @returns Promise resolving to a JPEG Blob, or null if capture fails
107
+ * @returns Promise resolving to the JPEG blob plus the pixel dimensions it
108
+ * was encoded at, or null if capture fails. The dimensions flow into the
109
+ * persisted `ArImageCapture.width`/`height` for aspect-correct rendering.
84
110
  * @see docs/2026-02-06-bug-camera-frames-black.md
85
111
  */
86
- captureFrame?: (quality: number) => Promise<Blob | null>;
112
+ captureFrame?: (quality: number) => Promise<CapturedFrame | null>;
87
113
  }
88
114
  /**
89
115
  * Manages periodic image capture from a canvas.
@@ -140,4 +166,4 @@ declare class ImageCaptureManager {
140
166
  private handleCapturedBlob;
141
167
  }
142
168
  //#endregion
143
- export { ImageCaptureManager as a, ImageCaptureConfig as i, DEFAULT_CAPTURE_CONFIG as n, MIN_VALID_IMAGE_BYTES as o, ImageCaptureCallbacks as r, CapturedImage as t };
169
+ export { ImageCaptureConfig as a, ImageCaptureCallbacks as i, CapturedImage as n, ImageCaptureManager as o, DEFAULT_CAPTURE_CONFIG as r, MIN_VALID_IMAGE_BYTES as s, CapturedFrame as t };
@@ -0,0 +1,399 @@
1
+ import { t as GridCell } from "./bresenham3d-ByjcmEgM.js";
2
+ import { a as QrPoseSolution, i as Pose, r as Point2, t as CameraIntrinsics } from "./qr-pose-Bt2reSw7.js";
3
+ import { a as QrFrontEnd, o as RgbaImage } from "./qr-frontend-DufZp2xS.js";
4
+ import { Quaternion, RecordGpsEventPayload, Vector3 } from "gps-plus-slam-js";
5
+
6
+ //#region ../src/ar/qr-gps-vote.d.ts
7
+ /** Mean meters per degree of latitude (WGS-84 average). */
8
+ declare const METERS_PER_DEG_LAT = 111320;
9
+ /** Absolute geo pose of the printed QR, from the level file. */
10
+ interface QrGeoPose {
11
+ lat: number;
12
+ lon: number;
13
+ /** Altitude of the QR center, meters. */
14
+ alt: number;
15
+ /**
16
+ * Compass bearing (degrees clockwise from true North) that the QR's local +X
17
+ * axis points toward. The QR is assumed vertical (wall-mounted): local +Y =
18
+ * world up, local +X = horizontal along the wall at this bearing.
19
+ */
20
+ headingDeg: number;
21
+ }
22
+ interface QrGpsVoteInput {
23
+ /** Solved QR pose in raw-WebXR/odom space (`solveQrPose().qrPoseWorld`). */
24
+ qrPoseWorld: Pose;
25
+ /** Printed physical side length, meters. */
26
+ sizeM: number;
27
+ /** Absolute geo pose from the level file. */
28
+ qrGeo: QrGeoPose;
29
+ /**
30
+ * Synthetic GPS accuracy in meters. Small → very high weight (the core
31
+ * library computes `weight = 1/accuracy^gpsAccuracyExponent`). Pick & validate
32
+ * against the fusion rather than hardcoding blindly (plan §6).
33
+ */
34
+ syntheticAccuracyM: number;
35
+ /**
36
+ * Device/odom rotation to stamp on each payload. Defaults to the QR's world
37
+ * rotation; the fusion uses this only for the device-rotation derivation, not
38
+ * for the positional correspondence.
39
+ */
40
+ odomRotation?: Quaternion;
41
+ /** 4 corner observations (default) vs. a single center observation. */
42
+ multiCorrespondence?: boolean;
43
+ /**
44
+ * WIDE-BASELINE north-stiffness knob (Note 2 of the follow-up plan). When > 0,
45
+ * synthesize the correspondences on a regular polygon of THIS radius (meters)
46
+ * in the QR plane instead of using the physical corners. Because the full
47
+ * 6-DoF pose + geo + heading are known, these virtual points are consistent in
48
+ * BOTH odom and geo space by construction; a wider baseline gives the single
49
+ * pose far more lever-arm, so corner-pixel noise maps to a much smaller heading
50
+ * error. The physical-corner mode is the `baselineM = 0`/undefined special case.
51
+ *
52
+ * ⚠️ This adds NO new information — every point derives from the one solved
53
+ * pose — so it is a pure leverage/reweighting device. Increasing `count`
54
+ * makes a BAD detection harder for the outlier-rejection step to reject (all
55
+ * pairs are wrong together); it is only safe because the pre-injection gates (reprojection,
56
+ * occupancy plausibility, N-consecutive-lock) ensure only good detections vote.
57
+ * Treat it as a bounded tuning knob, not a free dial (plan §1 "dominates but
58
+ * does not entirely erase").
59
+ */
60
+ baselineM?: number;
61
+ /**
62
+ * Number of synthetic correspondences in wide-baseline mode (≥ 3 for a
63
+ * non-collinear, well-posed rotation constraint). Default 4. Ignored unless
64
+ * `baselineM > 0`. This is the dominance (vote-count) half of the knob.
65
+ */
66
+ count?: number;
67
+ /** Epoch ms stamped on every synthetic point. Defaults to `Date.now()`. */
68
+ timestamp?: number;
69
+ /** Id prefix for the synthetic points. Default `'qr'`. */
70
+ idPrefix?: string;
71
+ }
72
+ /** East/North/Up offset (meters) of a QR-plane point from the QR center. */
73
+ interface Enu {
74
+ east: number;
75
+ north: number;
76
+ up: number;
77
+ }
78
+ /**
79
+ * Map a QR-local plane offset (meters; +x right, +y up on the printed face) to
80
+ * an East/North/Up offset, given the QR's compass heading. Vertical-QR
81
+ * convention: +y is world up; +x is horizontal at `headingDeg` (clockwise from
82
+ * North), so east = x·sin(h), north = x·cos(h).
83
+ */
84
+ declare function localPlaneToEnu(localX: number, localY: number, headingDeg: number): Enu;
85
+ /** Apply an ENU meter offset to a geo pose (equirectangular; exact enough for sub-meter QR corners). */
86
+ declare function offsetGeo(center: QrGeoPose, enu: Enu): {
87
+ latitude: number;
88
+ longitude: number;
89
+ altitude: number;
90
+ };
91
+ /**
92
+ * Build the synthetic `recordGpsEvent` payload(s) for one QR detection. Returns
93
+ * 4 corner correspondences by default, or 1 center correspondence when
94
+ * `multiCorrespondence` is false. Each payload pairs the corner's odom position
95
+ * (from the solved pose) with its absolute geo position (from the level file),
96
+ * stamped with the tiny synthetic accuracy.
97
+ *
98
+ * @throws RangeError on a non-positive `sizeM` or `syntheticAccuracyM`.
99
+ */
100
+ declare function buildQrGpsVotes(input: QrGpsVoteInput): RecordGpsEventPayload[];
101
+ //#endregion
102
+ //#region ../src/ar/qr-level.d.ts
103
+ /**
104
+ * A validated QR level file.
105
+ *
106
+ * Both `physicalSizeM` and `geo` are OPTIONAL (Note 3 of the follow-up plan:
107
+ * flat optionals + a capability model, not a discriminated union). Their
108
+ * PRESENCE gates which capabilities activate, and the use-cases are combinable:
109
+ * - `geo` present → the high-weight GPS vote (`buildQrGpsVotes`) runs.
110
+ * - `physicalSizeM` present → size is authored; otherwise it must be MEASURED
111
+ * first (Note 4 depth path) before size-dependent features (PnP solve, vote)
112
+ * unlock. See the size lifecycle in `state/qr-detected-slice.ts`.
113
+ * - neither → a debug/observe or trigger-only level (still keyed by payload).
114
+ */
115
+ interface QrLevel {
116
+ /** Schema version for forward-compat. */
117
+ version: number;
118
+ qr: {
119
+ /** Printed physical side length, meters. Optional — may be measured instead. */physicalSizeM?: number; /** Absolute geo pose of the QR center + heading. Optional — geo-less levels skip the vote. */
120
+ geo?: QrGeoPose;
121
+ };
122
+ /** AR content to instantiate (format deferred — plan §12). Opaque here. */
123
+ content?: unknown;
124
+ }
125
+ /** Thrown when a fetched level file fails validation. */
126
+ declare class QrLevelValidationError extends Error {
127
+ constructor(message: string);
128
+ }
129
+ /**
130
+ * Validate an already-parsed value as a {@link QrLevel}. Throws
131
+ * {@link QrLevelValidationError} with a descriptive message on any violation.
132
+ */
133
+ declare function parseQrLevel(data: unknown): QrLevel;
134
+ /** Minimal `fetch` slice used by {@link fetchQrLevel}. */
135
+ type FetchLike = (url: string, init?: {
136
+ signal?: AbortSignal;
137
+ }) => Promise<{
138
+ ok: boolean;
139
+ status: number;
140
+ json(): Promise<unknown>;
141
+ }>;
142
+ interface FetchQrLevelOptions {
143
+ /** Injected fetch (defaults to global `fetch`). */
144
+ fetchImpl?: FetchLike;
145
+ /** Optional abort signal for cancellation. */
146
+ signal?: AbortSignal;
147
+ }
148
+ /**
149
+ * Fetch and validate a level file from `url`. Rejects with
150
+ * {@link QrLevelValidationError} on a non-OK response, non-JSON body, or a
151
+ * schema violation.
152
+ */
153
+ declare function fetchQrLevel(url: string, options?: FetchQrLevelOptions): Promise<QrLevel>;
154
+ //#endregion
155
+ //#region ../src/ar/qr-tracking-controller.d.ts
156
+ type QrTrackingStatus = 'idle' | 'scanning' | 'loading-level' | 'tracking' | 'error';
157
+ /** Inputs to the injected pose solve (so the controller doesn't import OpenCV). */
158
+ interface QrSolvePoseInput {
159
+ imagePoints: readonly Point2[];
160
+ sizeM: number;
161
+ intrinsics: CameraIntrinsics;
162
+ cameraPose: Pose;
163
+ }
164
+ /**
165
+ * Emitted on every lock, INDEPENDENTLY of the GPS vote (Note 3). The app maps
166
+ * this onto `recordQrDetection` of the `qrDetected` slice. Kept structural (no
167
+ * import of the state slice) so the `ar` layer never depends on `state` — that
168
+ * would close a cycle (`state/qr-detected-slice` already imports `ar/qr-pose`).
169
+ */
170
+ interface QrDetectionEvent {
171
+ /** Decoded payload (text/URL) — the marker key. */
172
+ text: string;
173
+ qrPoseWorld: Pose;
174
+ qrPoseInCamera: Pose;
175
+ reprojectionErrorPx: number;
176
+ /** Epoch ms (or the injected clock) when the lock fired. */
177
+ timestamp: number;
178
+ }
179
+ interface QrTrackingControllerConfig {
180
+ /** Detect + decode (BarcodeDetector / OpenCV front-end). */
181
+ frontEnd: QrFrontEnd;
182
+ /** Solve the QR world pose from corners (production: wraps `solveQrPose`). */
183
+ solvePose: (input: QrSolvePoseInput) => QrPoseSolution | null;
184
+ /** Fetch + validate a level file from the decoded URL (cached by the controller). */
185
+ fetchLevel: (url: string) => Promise<QrLevel>;
186
+ /** Dispatch the synthetic GPS votes (production: `recordGpsEvent` per payload). */
187
+ dispatchVotes: (votes: RecordGpsEventPayload[]) => void;
188
+ /**
189
+ * Emitted on every lock, independent of the vote (Note 3). Apps wire this to
190
+ * `dispatch(recordQrDetection(event))`. The vote is conditional on `geo`;
191
+ * this emission is not.
192
+ */
193
+ onDetection?: (event: QrDetectionEvent) => void;
194
+ /**
195
+ * Resolve the physical size (m) when the level omits `physicalSizeM` — e.g. a
196
+ * depth-measured running median (Note 4). Returns `null` while size is still
197
+ * unknown, which BLOCKS the pose solve (and therefore the vote + detection
198
+ * emission) until a size is authored or measured-and-locked (the Note 3 size
199
+ * lifecycle gate). Ignored when the level already carries `physicalSizeM`.
200
+ */
201
+ resolveSizeM?: (text: string, level: QrLevel) => number | null;
202
+ /**
203
+ * Resolve the STABLE (sliding-window filtered) world pose for the vote — e.g.
204
+ * `selectStableQrPose(store.getState(), text)`. Returns `null` until the pose
205
+ * has converged, which GATES the high-weight vote (the detection emission is
206
+ * unconditional; only the vote waits for stability). When omitted (back-compat)
207
+ * the raw single-frame solve pose drives the vote.
208
+ *
209
+ * Ordering: the `onDetection` emission above feeds this frame's RAW pose into
210
+ * the slice synchronously, so the window this reads already includes it. See
211
+ * the sliding-window stabilization design doc.
212
+ */
213
+ resolveStablePose?: (text: string) => Pose | null;
214
+ /** Current camera pose in raw-WebXR/odom space, or `null` if unavailable. */
215
+ getCameraPose: () => Pose | null;
216
+ /** Intrinsics for the exact frame buffer, or `null` if unavailable. */
217
+ getIntrinsics: (image: RgbaImage) => CameraIntrinsics | null;
218
+ /** Synthetic GPS accuracy (m) → vote weight. */
219
+ syntheticAccuracyM: number;
220
+ /**
221
+ * Optional wide-baseline north-stiffness knob (Note 2), forwarded to
222
+ * `buildQrGpsVotes`. `voteBaselineM > 0` synthesizes the correspondences on a
223
+ * polygon of that radius (stiffer rotation fit); `voteCount` (≥3) sets how
224
+ * many. Leave unset for the default physical-corner mode. Treat as a bounded
225
+ * tuning knob — a larger count makes a bad detection harder to outlier-reject.
226
+ */
227
+ voteBaselineM?: number;
228
+ voteCount?: number;
229
+ /** Optional plausibility gate (e.g. occupancy self-check); `false` rejects. */
230
+ isPlausible?: (solution: QrPoseSolution, cameraPose: Pose) => boolean;
231
+ /** Status-change notifications for the UI. */
232
+ onStatus?: (status: QrTrackingStatus) => void;
233
+ /** Called each time a locked detection dispatches votes. */
234
+ onLocked?: (solution: QrPoseSolution, level: QrLevel) => void;
235
+ /** Surfaced failures (level fetch, detect throw). */
236
+ onError?: (err: unknown) => void;
237
+ /** Scheduler tuning (see `detection-scheduler.ts`). */
238
+ minIntervalMs?: number;
239
+ requiredLockCount?: number;
240
+ now?: () => number;
241
+ }
242
+ interface QrTrackingController {
243
+ /** Offer the latest camera frame; throttled/coalesced internally. */
244
+ offerFrame(image: RgbaImage): void;
245
+ /** Current status. */
246
+ readonly status: QrTrackingStatus;
247
+ /** Stop tracking and reset to `idle` (clears the level cache). */
248
+ reset(): void;
249
+ }
250
+ declare function createQrTrackingController(config: QrTrackingControllerConfig): QrTrackingController;
251
+ //#endregion
252
+ //#region ../src/ar/camera-frame-source.d.ts
253
+ /** Tuning for the camera frame source. */
254
+ interface CameraFrameSourceConfig {
255
+ /**
256
+ * Minimum interval between captures in milliseconds. Default 125 ms (≈ 8 Hz),
257
+ * the plan §9 5–10 Hz detection target. The capture (and therefore the blit)
258
+ * fires at most once per `intervalMs`.
259
+ */
260
+ intervalMs: number;
261
+ }
262
+ /** Injected I/O for the camera frame source. */
263
+ interface CameraFrameSourceCallbacks {
264
+ /**
265
+ * Capture the current XR frame as top-left-origin RGBA, or `null` when no
266
+ * frame is available (no camera texture yet, GL failure). This is the GPU
267
+ * blit + readback; the source only invokes it at the throttled cadence so
268
+ * the cost is bounded. A `null` return does NOT consume the interval slot —
269
+ * the next frame retries immediately (a missing texture is transient).
270
+ */
271
+ capture: () => RgbaImage | null;
272
+ /** Receive a throttled, successfully-captured frame. */
273
+ onCapture: (image: RgbaImage) => void;
274
+ }
275
+ /**
276
+ * Throttled RGBA capturer. Construct with the injected `capture`/`onCapture`
277
+ * pair, `start()`, then call `onFrame(timestamp)` once per XR frame.
278
+ */
279
+ declare class CameraFrameSource {
280
+ private readonly callbacks;
281
+ private readonly config;
282
+ private running;
283
+ private captureCount;
284
+ private lastCaptureTime;
285
+ constructor(callbacks: CameraFrameSourceCallbacks, config?: Partial<CameraFrameSourceConfig>);
286
+ /** Begin throttling. Resets the cadence so the first tick captures. */
287
+ start(): void;
288
+ /** Stop capturing. `onFrame` becomes a no-op until `start()` is called again. */
289
+ stop(): void;
290
+ isRunning(): boolean;
291
+ /** Number of frames successfully captured + delivered since `start()`. */
292
+ getFrameCount(): number;
293
+ getConfig(): CameraFrameSourceConfig;
294
+ /**
295
+ * Apply partial config (e.g. the app's detection cadence). Invalid values
296
+ * are ignored defensively — `intervalMs` requires a finite positive number.
297
+ */
298
+ updateConfig(config: Partial<CameraFrameSourceConfig>): void;
299
+ /**
300
+ * Per-XR-frame tick. Captures + delivers a frame at most once per
301
+ * `intervalMs`; otherwise a cheap no-op so it is safe to call every frame.
302
+ *
303
+ * @param timestamp - monotonic frame time in ms (the XR `time` argument).
304
+ */
305
+ onFrame(timestamp: number): void;
306
+ /**
307
+ * Run the injected capture, guarded so a blit failure (e.g. GL context loss)
308
+ * can never throw out of the XR frame loop — it degrades to "no frame this
309
+ * tick", exactly like a missing texture.
310
+ */
311
+ private captureSafely;
312
+ }
313
+ //#endregion
314
+ //#region ../src/ar/detection-scheduler.d.ts
315
+ interface DetectionSchedulerConfig<TResult, TImage = RgbaImage> {
316
+ /** The full detect→solve step; resolves to a result or `null` (no hit / rejected). */
317
+ detect: (image: TImage) => Promise<TResult | null>;
318
+ /** Minimum ms between detection STARTS (throttle). 100 ms ≈ 10 Hz. */
319
+ minIntervalMs: number;
320
+ /** Consecutive successes required before a lock is reported. Default 3. */
321
+ requiredLockCount?: number;
322
+ /** Injectable clock (ms). Defaults to `performance.now()`/`Date.now()`. */
323
+ now?: () => number;
324
+ /** Called on each success once locked (consecutiveLocks ≥ requiredLockCount). */
325
+ onLocked?: (result: TResult) => void;
326
+ /** Called when a detection completes with no usable result. */
327
+ onMiss?: () => void;
328
+ /** Called when `detect` rejects (the counter is reset). */
329
+ onError?: (err: unknown) => void;
330
+ }
331
+ interface DetectionScheduler<TImage = RgbaImage> {
332
+ /** Offer the latest camera frame; may or may not start a detection. */
333
+ offerFrame(image: TImage): void;
334
+ /** True while a detection is awaiting `detect`. */
335
+ readonly inFlight: boolean;
336
+ /** Current consecutive-success count (capped at requiredLockCount). */
337
+ readonly consecutiveLocks: number;
338
+ /** True once `consecutiveLocks` has reached `requiredLockCount`. */
339
+ readonly locked: boolean;
340
+ }
341
+ declare function createDetectionScheduler<TResult, TImage = RgbaImage>(config: DetectionSchedulerConfig<TResult, TImage>): DetectionScheduler<TImage>;
342
+ /** {@link DetectionSchedulerConfig} specialized to the QR pose solution. */
343
+ type QrDetectionSchedulerConfig = DetectionSchedulerConfig<QrPoseSolution>;
344
+ /** {@link DetectionScheduler} specialized to the QR (RGBA) frame. */
345
+ type QrDetectionScheduler = DetectionScheduler<RgbaImage>;
346
+ /** The QR path: a detection scheduler whose result is a {@link QrPoseSolution}. */
347
+ declare function createQrDetectionScheduler(config: QrDetectionSchedulerConfig): QrDetectionScheduler;
348
+ //#endregion
349
+ //#region ../src/ar/qr-occupancy-check.d.ts
350
+ /**
351
+ * The slice of `OccupancyGrid` this check needs. Declared structurally so the
352
+ * real grid satisfies it and tests can pass a tiny synthetic stand-in.
353
+ */
354
+ interface OccupancySurface {
355
+ readonly cellSizeM: number;
356
+ getOccupiedCells(minObservations?: number): readonly GridCell[];
357
+ getCellCenter(cell: GridCell): Vector3;
358
+ }
359
+ type QrPlausibilityVerdict = 'plausible' | 'floating' | 'behind-surface' | 'no-grid';
360
+ interface QrPlausibility {
361
+ verdict: QrPlausibilityVerdict;
362
+ /** `false` only for an implausible pose (`floating` / `behind-surface`). */
363
+ ok: boolean;
364
+ /** Distance from the QR center to the nearest occupied voxel; `Infinity` when the grid is empty. */
365
+ nearestSurfaceDistanceM: number;
366
+ }
367
+ interface QrPlausibilityOptions {
368
+ /**
369
+ * How far (in `cellSizeM` units) the QR may sit from the nearest occupied
370
+ * voxel before it counts as "floating". Default 1.5.
371
+ */
372
+ surfaceToleranceCells?: number;
373
+ /** Minimum observation count for a voxel to count as occupied. Default 1. */
374
+ minObservations?: number;
375
+ /**
376
+ * Perpendicular distance (in `cellSizeM` units) within which an occupied
377
+ * voxel counts as lying ON the camera→QR ray. Default 1.
378
+ */
379
+ rayCorridorCells?: number;
380
+ /**
381
+ * Margin (in `cellSizeM` units) subtracted from the camera→QR distance so the
382
+ * QR's own wall voxel is not mistaken for an occluder. Default 2.
383
+ */
384
+ occlusionMarginCells?: number;
385
+ }
386
+ /**
387
+ * Classify a solved QR center against the occupancy grid.
388
+ *
389
+ * - `no-grid` (ok): the grid has no occupied voxels — nothing to check against.
390
+ * - `floating` (not ok): nearest occupied voxel is farther than the tolerance.
391
+ * - `behind-surface` (not ok): an occupied voxel lies on the camera→QR ray
392
+ * clearly in front of the QR (the QR is inside / beyond geometry).
393
+ * - `plausible` (ok): on a surface with empty space toward the camera.
394
+ *
395
+ * All positions are raw-WebXR/odom space (the same frame as `qrPoseWorld`).
396
+ */
397
+ declare function checkQrPlausibility(qrCenterWorld: Vector3, cameraPosWorld: Vector3, grid: OccupancySurface, options?: QrPlausibilityOptions): QrPlausibility;
398
+ //#endregion
399
+ export { QrGpsVoteInput as A, QrLevel as C, Enu as D, parseQrLevel as E, localPlaneToEnu as M, offsetGeo as N, METERS_PER_DEG_LAT as O, FetchQrLevelOptions as S, fetchQrLevel as T, QrTrackingController as _, checkQrPlausibility as a, createQrTrackingController as b, QrDetectionScheduler as c, createQrDetectionScheduler as d, CameraFrameSource as f, QrSolvePoseInput as g, QrDetectionEvent as h, QrPlausibilityVerdict as i, buildQrGpsVotes as j, QrGeoPose as k, QrDetectionSchedulerConfig as l, CameraFrameSourceConfig as m, QrPlausibility as n, DetectionScheduler as o, CameraFrameSourceCallbacks as p, QrPlausibilityOptions as r, DetectionSchedulerConfig as s, OccupancySurface as t, createDetectionScheduler as u, QrTrackingControllerConfig as v, QrLevelValidationError as w, FetchLike as x, QrTrackingStatus as y };
@@ -1,4 +1,4 @@
1
- import { L as CombinedRootState, h as TrackingQualityReport } from "./create-slam-app-store-qNXQ-tq5.js";
1
+ import { L as CombinedRootState, h as TrackingQualityReport } from "./create-slam-app-store-Ce2FMKCp.js";
2
2
  import { Add2dImagePayload, GpsPoint as GpsPoint$1, LatLong as LatLong$1, RawDeviceOrientation, RawGpsPoint as RawGpsPoint$1, RecordGpsEventPayload as RecordGpsEventPayload$1, add2dImage, calcRelativeCoordsInMeters, recordGpsEvent as recordGpsEvent$1, setZeroPos as setZeroPos$1 } from "gps-plus-slam-js";
3
3
 
4
4
  //#region ../src/state/onboarding-guidance.d.ts