gps-plus-slam-app-framework 1.4.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 (212) hide show
  1. package/dist/absolute-orientation-CAEh840t.d.ts +54 -0
  2. package/dist/{app-selectors-CrOucL00.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 +1 -1
  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 +1 -1
  9. package/dist/ar/depth-sampler.d.ts +2 -2
  10. package/dist/ar/depth-unprojection.d.ts +1 -1
  11. package/dist/ar/enable-gps-ar.d.ts +1 -1
  12. package/dist/ar/enable-gps-ar.js +1 -1
  13. package/dist/ar/frame-loop.d.ts +1 -1
  14. package/dist/ar/image-capture.d.ts +2 -2
  15. package/dist/ar/image-capture.js +1 -154
  16. package/dist/ar/image-quality.d.ts +2 -0
  17. package/dist/ar/image-quality.js +168 -0
  18. package/dist/ar/index.d.ts +31 -30
  19. package/dist/ar/index.js +6 -5
  20. package/dist/ar/occupancy-grid.d.ts +1 -1
  21. package/dist/ar/planar-pnp.d.ts +1 -1
  22. package/dist/ar/qr-debug-view.d.ts +1 -1
  23. package/dist/ar/qr-derived-pose.d.ts +1 -1
  24. package/dist/ar/qr-detection-controller.d.ts +1 -1
  25. package/dist/ar/qr-frontend.d.ts +1 -1
  26. package/dist/ar/qr-pose.d.ts +1 -1
  27. package/dist/ar/qr-size-depth-context.d.ts +2 -2
  28. package/dist/ar/qr-size-measurer.d.ts +1 -1
  29. package/dist/ar/replay-scene.d.ts +1 -1
  30. package/dist/ar/replay-scene.js +1 -1
  31. package/dist/ar/scene-node-names.d.ts +1 -1
  32. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  33. package/dist/ar/webxr-session.d.ts +4 -4
  34. package/dist/ar/webxr-session.js +2 -2
  35. package/dist/ar/xr-camera-texture.d.ts +1 -1
  36. package/dist/ar/xr-error-handler.d.ts +1 -1
  37. package/dist/ar/xr-frame-loop.d.ts +1 -1
  38. package/dist/{ar-Dc72Ksiu.js → ar-MFpFlV7j.js} +3 -3
  39. package/dist/{ar-world-group-alignment-DDLkgNf_.d.ts → ar-world-group-alignment-69fgnB39.d.ts} +1 -1
  40. package/dist/capture-motion-gate-0Yl2NpQG.d.ts +24 -0
  41. package/dist/capture-motion-gate-4EscNZCw.js +94 -0
  42. package/dist/core/index.d.ts +2 -2
  43. package/dist/core/index.js +2 -2
  44. package/dist/create-slam-app-store-DkCHOpvb.js +149 -0
  45. package/dist/{create-slam-app-store-Ce2FMKCp.d.ts → create-slam-app-store-n_VF63D5.d.ts} +50 -40
  46. package/dist/{depth-grid-lookup-vMDrGTk_.d.ts → depth-grid-lookup-DQ-IYhO3.d.ts} +1 -1
  47. package/dist/{depth-sampler-DwfPYzS7.d.ts → depth-sampler-asc8exHt.d.ts} +1 -1
  48. package/dist/{depth-unprojection-CK3b9htH.d.ts → depth-unprojection-DjsbJW9L.d.ts} +1 -1
  49. package/dist/{enable-gps-ar-CjZ1nEdu.d.ts → enable-gps-ar-DkNhFv4Y.d.ts} +4 -4
  50. package/dist/{frame-conversions-D_Af8Dt3.d.ts → frame-conversions-DObhj8Wf.d.ts} +1 -1
  51. package/dist/{fused-path-C0FHe67t.d.ts → fused-path-v6eOmLl3.d.ts} +1 -1
  52. package/dist/geo/h3-proximity.d.ts +1 -1
  53. package/dist/geo/index.d.ts +1 -1
  54. package/dist/geodesic-angle-BTldfFgs.js +39 -0
  55. package/dist/{gps-anchor-C6I9NDHA.d.ts → gps-anchor-BeFO2RYa.d.ts} +1 -1
  56. package/dist/{gps-ar-pose-sampler-DaHg2aPZ.d.ts → gps-ar-pose-sampler-DHMZmYAc.d.ts} +1 -1
  57. package/dist/{gps-event-coordinator-C7hwHyLA.d.ts → gps-event-coordinator-CjWW636x.d.ts} +21 -11
  58. package/dist/image-capture-B2-b6tKK.js +374 -0
  59. package/dist/{image-capture-C2Feywet.d.ts → image-capture-CNZDrQXG.d.ts} +103 -3
  60. package/dist/image-quality-smCBoFj1.d.ts +158 -0
  61. package/dist/index-BAPokxMb.d.ts +22 -0
  62. package/dist/{index-m_LneUSi.d.ts → index-BODDUTvD.d.ts} +1 -1
  63. package/dist/{index-BvLo2poy.d.ts → index-BneSj4_5.d.ts} +3 -3
  64. package/dist/index-_Rhgoqkh.d.ts +2 -0
  65. package/dist/index.d.ts +80 -77
  66. package/dist/index.js +11 -8
  67. package/dist/{leaflet-map-overlay-bnb2U1NQ.d.ts → leaflet-map-overlay-CmIyiE8n.d.ts} +1 -1
  68. package/dist/{map-data-DdjPiSav.d.ts → map-data-B4oEehUh.d.ts} +1 -1
  69. package/dist/{map-overlay-draw-CPVdAsFq.d.ts → map-overlay-draw-ymz1s7fG.d.ts} +1 -1
  70. package/dist/{null-storage-backend-CuSdYFsM.d.ts → null-storage-backend-B9rtyDfG.d.ts} +2 -2
  71. package/dist/{occupancy-grid-Bvi2Gw1U.d.ts → occupancy-grid-DnOc-6PU.d.ts} +2 -2
  72. package/dist/{opfs-storage-backend-CeQAb0SD.d.ts → opfs-storage-backend-JJiUJo-3.d.ts} +2 -2
  73. package/dist/{persistence-middleware-5eearhSV.d.ts → persistence-middleware-DAPTTwB2.d.ts} +1 -1
  74. package/dist/{planar-pnp-cjthe7Be.d.ts → planar-pnp-my9bqULS.d.ts} +1 -1
  75. package/dist/{qr-debug-view-B8gTzjMm.d.ts → qr-debug-view-Brl73HGO.d.ts} +1 -1
  76. package/dist/{qr-derived-pose-DinKE600.d.ts → qr-derived-pose-CmxTP2m4.d.ts} +2 -2
  77. package/dist/{qr-detected-slice-Cpv-dg82.d.ts → qr-detected-slice-VQssZmQJ.d.ts} +4 -4
  78. package/dist/{qr-detection-controller-Cd2Ih4cC.d.ts → qr-detection-controller-CDurHv8E.d.ts} +3 -3
  79. package/dist/{qr-frontend-DufZp2xS.d.ts → qr-frontend--fFX25mV.d.ts} +1 -1
  80. package/dist/{qr-pose-aggregation-CFlmLm6w.d.ts → qr-pose-aggregation-B7tWJq7j.d.ts} +1 -1
  81. package/dist/{qr-pose-aggregation-DXR9STHs.js → qr-pose-aggregation-DBiaLW0O.js} +1 -10
  82. package/dist/{qr-size-depth-context-onu36E9s.d.ts → qr-size-depth-context-CeAG53fy.d.ts} +2 -2
  83. package/dist/{qr-size-measurer-D7739ZpB.d.ts → qr-size-measurer-DQdAsid1.d.ts} +3 -3
  84. package/dist/{recording-options-D93kksJU.d.ts → recording-options-BPCpMHLK.d.ts} +154 -9
  85. package/dist/{recording-replayer-CUEK82tM.d.ts → recording-replayer-DQKpAGyQ.d.ts} +2 -2
  86. package/dist/{recording-slice-B-uCgGoh.d.ts → recording-slice-DaxHZvNu.d.ts} +2 -2
  87. package/dist/{replay-engine-BSVd7Zzb.d.ts → replay-engine-CmKh_4wf.d.ts} +1 -1
  88. package/dist/{replay-scene-CPt56pps.d.ts → replay-scene-DBlDrV_x.d.ts} +3 -3
  89. package/dist/sensors/absolute-orientation.d.ts +2 -0
  90. package/dist/sensors/absolute-orientation.js +145 -0
  91. package/dist/sensors/gps-error-handler.d.ts +1 -1
  92. package/dist/sensors/gps.d.ts +1 -1
  93. package/dist/sensors/index.d.ts +5 -4
  94. package/dist/sensors/index.js +2 -1
  95. package/dist/sensors/permission-checker.d.ts +1 -1
  96. package/dist/state/app-selectors.d.ts +1 -1
  97. package/dist/state/combined-root-state.d.ts +1 -1
  98. package/dist/state/create-slam-app-store.d.ts +1 -1
  99. package/dist/state/create-slam-app-store.js +1 -74
  100. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  101. package/dist/state/gps-event-coordinator.d.ts +2 -2
  102. package/dist/state/gps-event-coordinator.js +27 -17
  103. package/dist/state/index.d.ts +20 -20
  104. package/dist/state/index.js +4 -4
  105. package/dist/state/persistence-middleware.d.ts +1 -1
  106. package/dist/state/persistence-middleware.js +30 -19
  107. package/dist/state/qr-detected-slice.d.ts +3 -3
  108. package/dist/state/qr-detected-slice.js +1 -1
  109. package/dist/state/recording-options.d.ts +2 -2
  110. package/dist/state/recording-options.js +155 -13
  111. package/dist/state/recording-replayer.d.ts +2 -2
  112. package/dist/state/recording-replayer.js +1 -1
  113. package/dist/state/recording-slice.d.ts +1 -1
  114. package/dist/state/replay-engine.d.ts +1 -1
  115. package/dist/state/store-subscribers.d.ts +2 -2
  116. package/dist/state/subscribe-to-selector.d.ts +1 -1
  117. package/dist/state/tracking-quality.d.ts +2 -2
  118. package/dist/state/tracking-quality.js +40 -128
  119. package/dist/state/tracking-slice.d.ts +1 -1
  120. package/dist/{state-IEw0ZsBu.js → state-E51QT_-j.js} +1 -1
  121. package/dist/storage/file-system-utils.d.ts +1 -1
  122. package/dist/storage/index.d.ts +8 -8
  123. package/dist/storage/null-storage-backend.d.ts +1 -1
  124. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  125. package/dist/storage/opfs-storage.d.ts +1 -1
  126. package/dist/storage/storage-backend.d.ts +1 -1
  127. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  128. package/dist/storage/zip-export.d.ts +1 -1
  129. package/dist/storage/zip-reader.d.ts +1 -1
  130. package/dist/{storage-backend-CTJHNdMp.d.ts → storage-backend-BdVLD0dU.d.ts} +1 -1
  131. package/dist/{store-subscribers-BZP81hjh.d.ts → store-subscribers-CA6AJH_8.d.ts} +2 -2
  132. package/dist/{subscribe-to-selector-Bp3sLx8L.d.ts → subscribe-to-selector-B6EUmQ3I.d.ts} +1 -1
  133. package/dist/test-utils/browser-mocks.d.ts +1 -1
  134. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  135. package/dist/{tracking-slice-DJZ9qeGg.d.ts → tracking-slice-BuAZxXZ5.d.ts} +1 -1
  136. package/dist/types/ar-types.d.ts +1 -1
  137. package/dist/types/geo-types.d.ts +1 -1
  138. package/dist/types/index.d.ts +2 -2
  139. package/dist/utils/concurrency.d.ts +1 -1
  140. package/dist/utils/failure-tracker.d.ts +1 -1
  141. package/dist/utils/format-file-size.d.ts +1 -1
  142. package/dist/utils/fused-path.d.ts +1 -1
  143. package/dist/utils/index.d.ts +8 -7
  144. package/dist/utils/index.js +2 -1
  145. package/dist/utils/list-formatter.d.ts +1 -1
  146. package/dist/utils/logger.d.ts +1 -1
  147. package/dist/visualization/accuracy-circles.d.ts +1 -1
  148. package/dist/visualization/alignment-lerper.d.ts +1 -1
  149. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  150. package/dist/visualization/ar-world-group-alignment.js +1 -1
  151. package/dist/visualization/camera-follower.d.ts +1 -1
  152. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  153. package/dist/visualization/frame-conversions.d.ts +1 -1
  154. package/dist/visualization/frustum-visibility.d.ts +1 -1
  155. package/dist/visualization/gps-anchor.d.ts +1 -1
  156. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  157. package/dist/visualization/gps-event-markers.d.ts +1 -1
  158. package/dist/visualization/gps-event-markers.js +1 -1
  159. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  160. package/dist/visualization/index.d.ts +18 -18
  161. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  162. package/dist/visualization/lerp-utils.d.ts +1 -1
  163. package/dist/visualization/map-data.d.ts +1 -1
  164. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  165. package/dist/visualization/map-overlay.d.ts +1 -1
  166. package/dist/visualization/three-dispose.d.ts +1 -1
  167. package/dist/visualization/vis-colors.d.ts +1 -1
  168. package/dist/{webxr-session-CJF9Nijp.js → webxr-session-DG-eNfny.js} +25 -4
  169. package/dist/{webxr-session-COsOjJwn.d.ts → webxr-session-Do5L6G3B.d.ts} +19 -9
  170. package/package.json +2 -2
  171. package/dist/index-eaI1Z1KR.d.ts +0 -2
  172. /package/dist/{accuracy-circles-Pi2YeCrM.d.ts → accuracy-circles-BtqOP_v1.d.ts} +0 -0
  173. /package/dist/{alignment-lerper-BUTsQ2yu.d.ts → alignment-lerper-Bil6vbVw.d.ts} +0 -0
  174. /package/dist/{ar-types-BckI1E-O.d.ts → ar-types-BFX2r1wk.d.ts} +0 -0
  175. /package/dist/{bresenham3d-ByjcmEgM.d.ts → bresenham3d-CU1Gv2W4.d.ts} +0 -0
  176. /package/dist/{camera-blit-capture-CqWe1G7s.d.ts → camera-blit-capture-NKT9H5-e.d.ts} +0 -0
  177. /package/dist/{camera-follower-BTb2Yp12.d.ts → camera-follower-CqJJ7zRj.d.ts} +0 -0
  178. /package/dist/{capability-checker-CXqoJYtb.d.ts → capability-checker-DUQLg6gq.d.ts} +0 -0
  179. /package/dist/{capture-failure-tracker-BL5SoG2a.d.ts → capture-failure-tracker-gsm8q7yi.d.ts} +0 -0
  180. /package/dist/{chromium-camera-access-workaround-BNK189P0.d.ts → chromium-camera-access-workaround-D4K2uwwQ.d.ts} +0 -0
  181. /package/dist/{concurrency-DqAlKX4j.d.ts → concurrency-By0oOTZt.d.ts} +0 -0
  182. /package/dist/{css3d-renderer-manager-m-ab19vp.d.ts → css3d-renderer-manager-B3kGNtcn.d.ts} +0 -0
  183. /package/dist/{failure-tracker-Cnc_xWUw.d.ts → failure-tracker-BO7M7jF_.d.ts} +0 -0
  184. /package/dist/{file-system-utils-Bs7lpeVP.d.ts → file-system-utils-DOajnMm-.d.ts} +0 -0
  185. /package/dist/{format-file-size-BFUWvir1.d.ts → format-file-size-Dc98ulzB.d.ts} +0 -0
  186. /package/dist/{frame-loop-BEO9_q3k.d.ts → frame-loop-CpVjQ1dH.d.ts} +0 -0
  187. /package/dist/{frustum-visibility-D_RGmH98.d.ts → frustum-visibility-CeoCZchO.d.ts} +0 -0
  188. /package/dist/{geo-types-DqiBzPO9.d.ts → geo-types-3Ip1NzVR.d.ts} +0 -0
  189. /package/dist/{gps-C5inWeNV.d.ts → gps-BBO3hKfT.d.ts} +0 -0
  190. /package/dist/{gps-compass-cubes-BExxrWJ-.d.ts → gps-compass-cubes-DSN87L8p.d.ts} +0 -0
  191. /package/dist/{gps-error-handler-C2bS69oJ.d.ts → gps-error-handler-BbPsfw-_.d.ts} +0 -0
  192. /package/dist/{gps-event-markers-Dd0G2bD0.d.ts → gps-event-markers-Cz9RMA2k.d.ts} +0 -0
  193. /package/dist/{h3-proximity-B57C6rYL.d.ts → h3-proximity-C-j0pw1j.d.ts} +0 -0
  194. /package/dist/{hit-test-reticle-Dqak9hiR.d.ts → hit-test-reticle-DV-2TpB9.d.ts} +0 -0
  195. /package/dist/{lerp-utils-kLxMJ2Xj.d.ts → lerp-utils-BLmHhkVe.d.ts} +0 -0
  196. /package/dist/{list-formatter-ct8txwvR.d.ts → list-formatter-KNMS0ay2.d.ts} +0 -0
  197. /package/dist/{logger-CzQrCe6C.d.ts → logger-CLnPrHiD.d.ts} +0 -0
  198. /package/dist/{map-overlay-Dci4yvm3.d.ts → map-overlay-Dh2B97MP.d.ts} +0 -0
  199. /package/dist/{opfs-storage-DMtlt57S.d.ts → opfs-storage-3VZh-qH6.d.ts} +0 -0
  200. /package/dist/{permission-checker-CtPtPQRq.d.ts → permission-checker-CDuI8BwC.d.ts} +0 -0
  201. /package/dist/{qr-pose-Bt2reSw7.d.ts → qr-pose-D2rfu5DA.d.ts} +0 -0
  202. /package/dist/{scene-node-names-CdNeQjOE.d.ts → scene-node-names-BiShOQDB.d.ts} +0 -0
  203. /package/dist/{session-disposers-D8wnO-uW.js → session-disposers-zv9OGXPB.js} +0 -0
  204. /package/dist/{three-dispose-BCZkSJp2.d.ts → three-dispose-Bt1k7WKh.d.ts} +0 -0
  205. /package/dist/{vis-colors-BZsSLI4i.d.ts → vis-colors-Obw2pLqT.d.ts} +0 -0
  206. /package/dist/{webxr-nue-basis-CW1wsbm6.d.ts → webxr-nue-basis-3nfjDJJp.d.ts} +0 -0
  207. /package/dist/{xr-camera-texture-BmrP7iNZ.d.ts → xr-camera-texture-Cva1q3Qj.d.ts} +0 -0
  208. /package/dist/{xr-error-handler-BEjRgZjt.d.ts → xr-error-handler-CtvHpjfm.d.ts} +0 -0
  209. /package/dist/{xr-frame-loop-K8ebdjLw.d.ts → xr-frame-loop-DIYtRcUa.d.ts} +0 -0
  210. /package/dist/{zip-coverage-embed-tFohI2J4.d.ts → zip-coverage-embed-DCAREVmw.d.ts} +0 -0
  211. /package/dist/{zip-export-DDWXInTR.d.ts → zip-export-ol0S_Ofd.d.ts} +0 -0
  212. /package/dist/{zip-reader-Q2ue6zlC.d.ts → zip-reader-D9iPQwdQ.d.ts} +0 -0
@@ -1,4 +1,6 @@
1
1
  import { createLogger } from "../utils/logger.js";
2
+ import { t as DEFAULT_MOTION_FILTER } from "../capture-motion-gate-4EscNZCw.js";
3
+ import { DEFAULT_QUALITY_FILTER } from "../ar/image-quality.js";
2
4
  //#region ../src/state/recording-options.ts
3
5
  /**
4
6
  * Recording Options - User-configurable settings for data capture.
@@ -31,7 +33,9 @@ const DEFAULT_RECORDING_OPTIONS = {
31
33
  enabled: true,
32
34
  intervalMs: 2e3,
33
35
  quality: .7,
34
- resolutionDivisor: 1
36
+ resolutionDivisor: 1,
37
+ motionFilter: { ...DEFAULT_MOTION_FILTER },
38
+ qualityFilter: { ...DEFAULT_QUALITY_FILTER }
35
39
  },
36
40
  arCrashIsolation: {
37
41
  enableDomOverlay: true,
@@ -41,7 +45,10 @@ const DEFAULT_RECORDING_OPTIONS = {
41
45
  enableCameraTextureAcquisition: true,
42
46
  applyChromiumProjectionLayerWorkaround: true
43
47
  },
44
- occupancy: { cellSizeM: .15 },
48
+ occupancy: {
49
+ cellSizeM: .15,
50
+ minConfidence: 3
51
+ },
45
52
  frameTileDisplay: { divisor: 2 },
46
53
  visualization: {
47
54
  frameTiles: true,
@@ -53,6 +60,11 @@ const DEFAULT_RECORDING_OPTIONS = {
53
60
  enabled: false,
54
61
  intervalMs: 125,
55
62
  captureSize: 1024
63
+ },
64
+ compassDebug: {
65
+ coldStartOverride: true,
66
+ rotationPrior: false,
67
+ webXRConsistency: false
56
68
  }
57
69
  };
58
70
  /** Validation constraints for depth options */
@@ -87,6 +99,65 @@ const IMAGE_CONSTRAINTS = {
87
99
  }
88
100
  };
89
101
  /**
102
+ * Validation constraints for the motion-filter (blurry-frame gate) thresholds.
103
+ *
104
+ * The velocity ranges (0.05–5) bracket the plausible scanning regime: below
105
+ * ~0.05 the gate would reject almost everything; above ~5 rad/s ≈ 286°/s (or
106
+ * 5 m/s) it would never reject, so the gate would be inert. `maxWaitMs` is
107
+ * clamped to 0.5–20 s — the never-calm fallback must always be able to fire.
108
+ * All three back a (currently advanced/hidden) settings slider, so a corrupt
109
+ * stored value can never disable capture. The default thresholds themselves are
110
+ * placeholders pending on-device field tuning (plan §7).
111
+ */
112
+ const MOTION_FILTER_CONSTRAINTS = {
113
+ maxAngularVelocity: {
114
+ min: .05,
115
+ max: 5,
116
+ step: .05
117
+ },
118
+ maxLinearVelocity: {
119
+ min: .05,
120
+ max: 5,
121
+ step: .05
122
+ },
123
+ maxWaitMs: {
124
+ min: 500,
125
+ max: 2e4,
126
+ step: 500
127
+ }
128
+ };
129
+ /**
130
+ * Validation constraints for the image-quality (blur/blackness) gate thresholds.
131
+ *
132
+ * `blurRelativeThreshold` (`k` in `sharpness < k·median`) is clamped to
133
+ * 0.05–0.95: it is a fraction of the recent sharpness median, so values must sit
134
+ * strictly inside (0, 1) — at ~0 the blur check never rejects, near 1 it rejects
135
+ * almost everything. `minMeanLuminance` (the absolute black cutoff on a 0–255
136
+ * luma scale) is clamped to 0–128: 0 disables the black check, and a cutoff
137
+ * above mid-grey would reject normally-lit frames. `maxWaitMs` mirrors the motion
138
+ * gate's range (0.5–20 s) so the never-good fallback can always fire. All back a
139
+ * (currently advanced/hidden) settings slider, so a corrupt stored value can
140
+ * never disable capture. The default thresholds are placeholders pending
141
+ * on-device field tuning (plan §5, §10).
142
+ */
143
+ const QUALITY_FILTER_CONSTRAINTS = {
144
+ blurRelativeThreshold: {
145
+ min: .05,
146
+ max: .95,
147
+ step: .05
148
+ },
149
+ minMeanLuminance: {
150
+ min: 0,
151
+ max: 128,
152
+ step: 1
153
+ },
154
+ maxWaitMs: {
155
+ min: 500,
156
+ max: 2e4,
157
+ step: 500
158
+ }
159
+ };
160
+ /**
90
161
  * Validation constraints for occupancy options.
91
162
  *
92
163
  * `cellSizeM` is clamped to 1–20 cm. The floor exists because cell count (and
@@ -94,12 +165,24 @@ const IMAGE_CONSTRAINTS = {
94
165
  * `points3D` row count) scales as 1/cellSize³ — sub-centimetre voxels are both
95
166
  * a memory/perf cliff on a phone and below the depth sensor's noise floor.
96
167
  * Step is 1 cm (the settings slider operates in cm).
168
+ *
169
+ * `minConfidence` is clamped to 1–10 (integer). 1 disables the filter (legacy
170
+ * behaviour: a single observation counts as occupied); the ceiling exists
171
+ * because real surfaces accumulate only a handful of observations per second
172
+ * of dwell, so a floor above ~10 would start hiding genuine geometry.
97
173
  */
98
- const OCCUPANCY_CONSTRAINTS = { cellSizeM: {
99
- min: .01,
100
- max: .2,
101
- step: .01
102
- } };
174
+ const OCCUPANCY_CONSTRAINTS = {
175
+ cellSizeM: {
176
+ min: .01,
177
+ max: .2,
178
+ step: .01
179
+ },
180
+ minConfidence: {
181
+ min: 1,
182
+ max: 10,
183
+ step: 1
184
+ }
185
+ };
103
186
  /**
104
187
  * Validation constraints for the frame-tile display-resolution divisor.
105
188
  *
@@ -150,6 +233,19 @@ function validateArCrashIsolationOptions(options) {
150
233
  };
151
234
  }
152
235
  /**
236
+ * Validate and normalize the compass alignment debug toggles. Boolean-or-default
237
+ * per field; a missing/corrupted/pre-feature value falls back to the OFF default
238
+ * so a bad persisted value can never silently turn an alignment override ON.
239
+ */
240
+ function validateCompassDebugOptions(options) {
241
+ const defaults = DEFAULT_RECORDING_OPTIONS.compassDebug;
242
+ return {
243
+ coldStartOverride: typeof options.coldStartOverride === "boolean" ? options.coldStartOverride : defaults.coldStartOverride,
244
+ rotationPrior: typeof options.rotationPrior === "boolean" ? options.rotationPrior : defaults.rotationPrior,
245
+ webXRConsistency: typeof options.webXRConsistency === "boolean" ? options.webXRConsistency : defaults.webXRConsistency
246
+ };
247
+ }
248
+ /**
153
249
  * Validate and normalize the live debug-overlay visibility toggles.
154
250
  * Each field is boolean-or-default (same policy as the AR-crash-isolation
155
251
  * flags): a missing, corrupted, or pre-feature persisted value falls back to
@@ -200,6 +296,41 @@ function validateDepthOptions(options) {
200
296
  };
201
297
  }
202
298
  /**
299
+ * Validate and normalize the motion-filter (blurry-frame gate) options.
300
+ * `enabled` is boolean-or-default; the three numeric thresholds are clamped to
301
+ * {@link MOTION_FILTER_CONSTRAINTS} with a `Number.isFinite` guard (a stored
302
+ * `NaN` is `typeof 'number'` and would survive `clamp`). A missing group
303
+ * default-fills entirely — a pre-feature persisted options object that lacks
304
+ * `motionFilter` therefore loads with the gate enabled rather than crashing.
305
+ */
306
+ function validateMotionFilterOptions(options) {
307
+ const defaults = DEFAULT_RECORDING_OPTIONS.images.motionFilter;
308
+ return {
309
+ enabled: typeof options.enabled === "boolean" ? options.enabled : defaults.enabled,
310
+ maxAngularVelocity: clamp(typeof options.maxAngularVelocity === "number" && Number.isFinite(options.maxAngularVelocity) ? options.maxAngularVelocity : defaults.maxAngularVelocity, MOTION_FILTER_CONSTRAINTS.maxAngularVelocity.min, MOTION_FILTER_CONSTRAINTS.maxAngularVelocity.max),
311
+ maxLinearVelocity: clamp(typeof options.maxLinearVelocity === "number" && Number.isFinite(options.maxLinearVelocity) ? options.maxLinearVelocity : defaults.maxLinearVelocity, MOTION_FILTER_CONSTRAINTS.maxLinearVelocity.min, MOTION_FILTER_CONSTRAINTS.maxLinearVelocity.max),
312
+ maxWaitMs: clamp(typeof options.maxWaitMs === "number" && Number.isFinite(options.maxWaitMs) ? options.maxWaitMs : defaults.maxWaitMs, MOTION_FILTER_CONSTRAINTS.maxWaitMs.min, MOTION_FILTER_CONSTRAINTS.maxWaitMs.max)
313
+ };
314
+ }
315
+ /**
316
+ * Validate and normalize the image-quality (blur/blackness gate) options. Same
317
+ * policy as {@link validateMotionFilterOptions}: `enabled` is boolean-or-default;
318
+ * the three numeric thresholds are clamped to {@link QUALITY_FILTER_CONSTRAINTS}
319
+ * with a `Number.isFinite` guard (a stored `NaN` is `typeof 'number'` and would
320
+ * survive `clamp`). A missing group default-fills entirely — a pre-feature
321
+ * persisted options object that lacks `qualityFilter` loads with the gate
322
+ * disabled (the safe default) rather than crashing.
323
+ */
324
+ function validateQualityFilterOptions(options) {
325
+ const defaults = DEFAULT_RECORDING_OPTIONS.images.qualityFilter;
326
+ return {
327
+ enabled: typeof options.enabled === "boolean" ? options.enabled : defaults.enabled,
328
+ blurRelativeThreshold: clamp(typeof options.blurRelativeThreshold === "number" && Number.isFinite(options.blurRelativeThreshold) ? options.blurRelativeThreshold : defaults.blurRelativeThreshold, QUALITY_FILTER_CONSTRAINTS.blurRelativeThreshold.min, QUALITY_FILTER_CONSTRAINTS.blurRelativeThreshold.max),
329
+ minMeanLuminance: clamp(typeof options.minMeanLuminance === "number" && Number.isFinite(options.minMeanLuminance) ? options.minMeanLuminance : defaults.minMeanLuminance, QUALITY_FILTER_CONSTRAINTS.minMeanLuminance.min, QUALITY_FILTER_CONSTRAINTS.minMeanLuminance.max),
330
+ maxWaitMs: clamp(typeof options.maxWaitMs === "number" && Number.isFinite(options.maxWaitMs) ? options.maxWaitMs : defaults.maxWaitMs, QUALITY_FILTER_CONSTRAINTS.maxWaitMs.min, QUALITY_FILTER_CONSTRAINTS.maxWaitMs.max)
331
+ };
332
+ }
333
+ /**
203
334
  * Validate and normalize image options.
204
335
  * Invalid values are clamped to valid ranges.
205
336
  */
@@ -209,7 +340,9 @@ function validateImageOptions(options) {
209
340
  enabled: typeof options.enabled === "boolean" ? options.enabled : defaults.enabled,
210
341
  intervalMs: clamp(typeof options.intervalMs === "number" ? options.intervalMs : defaults.intervalMs, IMAGE_CONSTRAINTS.intervalMs.min, IMAGE_CONSTRAINTS.intervalMs.max),
211
342
  quality: clamp(typeof options.quality === "number" ? options.quality : defaults.quality, IMAGE_CONSTRAINTS.quality.min, IMAGE_CONSTRAINTS.quality.max),
212
- resolutionDivisor: clamp(typeof options.resolutionDivisor === "number" ? options.resolutionDivisor : defaults.resolutionDivisor, IMAGE_CONSTRAINTS.resolutionDivisor.min, IMAGE_CONSTRAINTS.resolutionDivisor.max)
343
+ resolutionDivisor: clamp(typeof options.resolutionDivisor === "number" ? options.resolutionDivisor : defaults.resolutionDivisor, IMAGE_CONSTRAINTS.resolutionDivisor.min, IMAGE_CONSTRAINTS.resolutionDivisor.max),
344
+ motionFilter: validateMotionFilterOptions(options.motionFilter ?? {}),
345
+ qualityFilter: validateQualityFilterOptions(options.qualityFilter ?? {})
213
346
  };
214
347
  }
215
348
  /**
@@ -223,7 +356,10 @@ function validateImageOptions(options) {
223
356
  */
224
357
  function validateOccupancyOptions(options) {
225
358
  const defaults = DEFAULT_RECORDING_OPTIONS.occupancy;
226
- return { cellSizeM: clamp(typeof options.cellSizeM === "number" && Number.isFinite(options.cellSizeM) ? options.cellSizeM : defaults.cellSizeM, OCCUPANCY_CONSTRAINTS.cellSizeM.min, OCCUPANCY_CONSTRAINTS.cellSizeM.max) };
359
+ return {
360
+ cellSizeM: clamp(typeof options.cellSizeM === "number" && Number.isFinite(options.cellSizeM) ? options.cellSizeM : defaults.cellSizeM, OCCUPANCY_CONSTRAINTS.cellSizeM.min, OCCUPANCY_CONSTRAINTS.cellSizeM.max),
361
+ minConfidence: clamp(typeof options.minConfidence === "number" && Number.isFinite(options.minConfidence) ? Math.round(options.minConfidence) : defaults.minConfidence, OCCUPANCY_CONSTRAINTS.minConfidence.min, OCCUPANCY_CONSTRAINTS.minConfidence.max)
362
+ };
227
363
  }
228
364
  /**
229
365
  * Validate and normalize frame-tile display options. `divisor` is clamped to
@@ -248,7 +384,8 @@ function validateRecordingOptions(options) {
248
384
  occupancy: validateOccupancyOptions(options.occupancy ?? {}),
249
385
  frameTileDisplay: validateFrameTileDisplayOptions(options.frameTileDisplay ?? {}),
250
386
  visualization: validateVisualizationOptions(options.visualization ?? {}),
251
- qr: validateQrOptions(options.qr ?? {})
387
+ qr: validateQrOptions(options.qr ?? {}),
388
+ compassDebug: validateCompassDebugOptions(options.compassDebug ?? {})
252
389
  };
253
390
  }
254
391
  /**
@@ -306,13 +443,18 @@ function resetRecordingOptions(storageKey = STORAGE_KEY) {
306
443
  function cloneRecordingOptions(options) {
307
444
  return {
308
445
  depth: { ...options.depth },
309
- images: { ...options.images },
446
+ images: {
447
+ ...options.images,
448
+ motionFilter: { ...options.images.motionFilter },
449
+ qualityFilter: { ...options.images.qualityFilter }
450
+ },
310
451
  arCrashIsolation: { ...options.arCrashIsolation },
311
452
  occupancy: { ...options.occupancy },
312
453
  frameTileDisplay: { ...options.frameTileDisplay },
313
454
  visualization: { ...options.visualization },
314
- qr: { ...options.qr }
455
+ qr: { ...options.qr },
456
+ compassDebug: { ...options.compassDebug }
315
457
  };
316
458
  }
317
459
  //#endregion
318
- export { DEFAULT_RECORDING_OPTIONS, DEPTH_CONSTRAINTS, FRAME_TILE_DISPLAY_CONSTRAINTS, IMAGE_CONSTRAINTS, OCCUPANCY_CONSTRAINTS, QR_CONSTRAINTS, STORAGE_KEY, cloneRecordingOptions, loadRecordingOptions, resetRecordingOptions, saveRecordingOptions, validateArCrashIsolationOptions, validateDepthOptions, validateFrameTileDisplayOptions, validateImageOptions, validateOccupancyOptions, validateQrOptions, validateRecordingOptions, validateVisualizationOptions };
460
+ export { DEFAULT_RECORDING_OPTIONS, DEPTH_CONSTRAINTS, FRAME_TILE_DISPLAY_CONSTRAINTS, IMAGE_CONSTRAINTS, MOTION_FILTER_CONSTRAINTS, OCCUPANCY_CONSTRAINTS, QR_CONSTRAINTS, QUALITY_FILTER_CONSTRAINTS, STORAGE_KEY, cloneRecordingOptions, loadRecordingOptions, resetRecordingOptions, saveRecordingOptions, validateArCrashIsolationOptions, validateCompassDebugOptions, validateDepthOptions, validateFrameTileDisplayOptions, validateImageOptions, validateMotionFilterOptions, validateOccupancyOptions, validateQrOptions, validateQualityFilterOptions, validateRecordingOptions, validateVisualizationOptions };
@@ -1,3 +1,3 @@
1
- import { L as CombinedRootState } from "../create-slam-app-store-Ce2FMKCp.js";
2
- import { n as replayRecording, t as ReplayRecordingOptions } from "../recording-replayer-CUEK82tM.js";
1
+ import { N as CombinedRootState } from "../create-slam-app-store-n_VF63D5.js";
2
+ import { n as replayRecording, t as ReplayRecordingOptions } from "../recording-replayer-DQKpAGyQ.js";
3
3
  export { CombinedRootState, ReplayRecordingOptions, replayRecording };
@@ -1,6 +1,6 @@
1
1
  import { loadActionsFromZip } from "../storage/zip-reader.js";
2
2
  import { NullStorageBackend } from "../storage/null-storage-backend.js";
3
- import { createSlamAppStore } from "./create-slam-app-store.js";
3
+ import { t as createSlamAppStore } from "../create-slam-app-store-DkCHOpvb.js";
4
4
  //#region ../src/state/recording-replayer.ts
5
5
  /**
6
6
  * Recording Replayer
@@ -1,2 +1,2 @@
1
- import { a as recordWriteFailure, i as recordDepthSample, n as SessionMetadata, o as recordingReducer, r as endSession, s as startSession, t as RecordingState } from "../recording-slice-B-uCgGoh.js";
1
+ import { a as recordWriteFailure, i as recordDepthSample, n as SessionMetadata, o as recordingReducer, r as endSession, s as startSession, t as RecordingState } from "../recording-slice-DaxHZvNu.js";
2
2
  export { RecordingState, SessionMetadata, endSession, recordDepthSample, recordWriteFailure, recordingReducer, startSession };
@@ -1,2 +1,2 @@
1
- import { a as ReplayAction, c as computeInterActionDelay, i as ProgressCallback, l as extractActionTimestamp, n as DEFAULT_MAX_DELAY_MS, o as ReplayEngine, r as ErrorCallback, s as ReplayState, t as CompleteCallback } from "../replay-engine-BSVd7Zzb.js";
1
+ import { a as ReplayAction, c as computeInterActionDelay, i as ProgressCallback, l as extractActionTimestamp, n as DEFAULT_MAX_DELAY_MS, o as ReplayEngine, r as ErrorCallback, s as ReplayState, t as CompleteCallback } from "../replay-engine-CmKh_4wf.js";
2
2
  export { CompleteCallback, DEFAULT_MAX_DELAY_MS, ErrorCallback, ProgressCallback, ReplayAction, ReplayEngine, ReplayState, computeInterActionDelay, extractActionTimestamp };
@@ -1,3 +1,3 @@
1
- import { t as SubscribableStore } from "../subscribe-to-selector-Bp3sLx8L.js";
2
- import { n as wireStoreSubscribers, t as StoreSubscriberDeps } from "../store-subscribers-BZP81hjh.js";
1
+ import { t as SubscribableStore } from "../subscribe-to-selector-B6EUmQ3I.js";
2
+ import { n as wireStoreSubscribers, t as StoreSubscriberDeps } from "../store-subscribers-CA6AJH_8.js";
3
3
  export { StoreSubscriberDeps, SubscribableStore, wireStoreSubscribers };
@@ -1,2 +1,2 @@
1
- import { n as subscribeToSelector, t as SubscribableStore } from "../subscribe-to-selector-Bp3sLx8L.js";
1
+ import { n as subscribeToSelector, t as SubscribableStore } from "../subscribe-to-selector-B6EUmQ3I.js";
2
2
  export { SubscribableStore, subscribeToSelector };
@@ -1,2 +1,2 @@
1
- import { A as resetTrackingQuality, C as computeResidualConsensus, D as firstAgreementReached, E as degradedCountUpdated, F as snapshotsTrimmed, I as trackingQualityReducer, M as selectRecentAlignments, N as selectTrackingQuality, O as matrixDelta, P as snapshotPushed, S as computeGpsVsFusedDivergence, T as createTrackingQualityListenerMiddleware, _ as TrackingQualityState, b as computeCoverage, c as CompassAgreementResult, d as DEFAULT_TRACKING_QUALITY_OPTIONS, f as GpsAccuracyResult, g as TrackingQualitySliceState, h as TrackingQualityReport, j as selectFirstAgreementObservationIndex, k as reportUpdated, l as ConvergenceResult, m as TrackingQualityOptions, p as ResidualConsensusResult, s as AlignmentSnapshot, u as CoverageResult, v as computeCompassAgreement, w as computeTrackingQualityReport, x as computeGpsAccuracy, y as computeConvergence } from "../create-slam-app-store-Ce2FMKCp.js";
2
- export { AlignmentSnapshot, CompassAgreementResult, ConvergenceResult, CoverageResult, DEFAULT_TRACKING_QUALITY_OPTIONS, GpsAccuracyResult, ResidualConsensusResult, TrackingQualityOptions, TrackingQualityReport, TrackingQualitySliceState, TrackingQualityState, computeCompassAgreement, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, firstAgreementReached, matrixDelta, reportUpdated, resetTrackingQuality, selectFirstAgreementObservationIndex, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
1
+ import { A as snapshotPushed, C as createTrackingQualityListenerMiddleware, D as resetTrackingQuality, E as reportUpdated, M as trackingQualityReducer, O as selectRecentAlignments, S as computeTrackingQualityReport, T as matrixDelta, _ as computeConvergence, b as computeGpsVsFusedDivergence, c as ConvergenceResult, d as GpsAccuracyResult, f as ResidualConsensusResult, g as TrackingQualityState, h as TrackingQualitySliceState, j as snapshotsTrimmed, k as selectTrackingQuality, l as CoverageResult, m as TrackingQualityReport, p as TrackingQualityOptions, s as AlignmentSnapshot, u as DEFAULT_TRACKING_QUALITY_OPTIONS, v as computeCoverage, w as degradedCountUpdated, x as computeResidualConsensus, y as computeGpsAccuracy } from "../create-slam-app-store-n_VF63D5.js";
2
+ export { AlignmentSnapshot, ConvergenceResult, CoverageResult, DEFAULT_TRACKING_QUALITY_OPTIONS, GpsAccuracyResult, ResidualConsensusResult, TrackingQualityOptions, TrackingQualityReport, TrackingQualitySliceState, TrackingQualityState, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, matrixDelta, reportUpdated, resetTrackingQuality, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
@@ -1,4 +1,5 @@
1
- import { selectLastSensorOrientation, selectLastValidPose, selectTrackingPhase } from "./tracking-slice.js";
1
+ import { t as geodesicAngleRad } from "../geodesic-angle-BTldfFgs.js";
2
+ import { selectTrackingPhase } from "./tracking-slice.js";
2
3
  import { selectAlignmentMatrix, selectGpsPositions, selectOdometryPositions, selectZeroReference } from "./app-selectors.js";
3
4
  import { calcGpsCoords, distanceInMeters } from "gps-plus-slam-js";
4
5
  import { mat4, quat, vec3 } from "gl-matrix";
@@ -13,19 +14,15 @@ const DEFAULT_TRACKING_QUALITY_OPTIONS = {
13
14
  coverageDirectionSpreadDeg: 90,
14
15
  convergenceRotationWarnDeg: 6,
15
16
  convergenceTranslationWarnM: 2,
16
- compassWarnDeg: 15,
17
- compassFailDeg: 35,
18
17
  warmupMinObservations: 10,
19
18
  warmupMinCoverage: .5,
20
19
  residualConfidenceTargetM: 3,
21
20
  gpsAccuracyFloorM: 1,
22
- firstAgreementMinStreak: 3,
23
21
  degradedHoldoff: 3,
24
22
  convergenceEmaAlpha: .3
25
23
  };
26
24
  const initialState = {
27
25
  recentAlignments: [],
28
- firstAgreementObservationIndex: null,
29
26
  report: null,
30
27
  degradedConsecutiveCount: 0,
31
28
  smoothedConvergence: null
@@ -47,11 +44,6 @@ function emaBlend(prev, raw, alpha) {
47
44
  if (prev === null) return raw;
48
45
  return prev + (Number.isFinite(alpha) ? alpha : 1) * (raw - prev);
49
46
  }
50
- function wrapToHalfCircle(deg) {
51
- let d = (deg + 180) % 360 - 180;
52
- if (d < -180) d += 360;
53
- return d;
54
- }
55
47
  function bearingDeg(north, east) {
56
48
  let deg = Math.atan2(east, north) * 180 / Math.PI;
57
49
  if (deg < 0) deg += 360;
@@ -87,6 +79,11 @@ function rampUp(value, low, high) {
87
79
  if (value >= high) return 1;
88
80
  return (value - low) / (high - low);
89
81
  }
82
+ /** True iff every element of the matrix is a finite number (no NaN/Infinity). */
83
+ function isFiniteMatrix(m) {
84
+ for (let i = 0; i < m.length; i++) if (!Number.isFinite(m[i])) return false;
85
+ return true;
86
+ }
90
87
  /**
91
88
  * §4.1 matrix-history convergence over the buffered snapshots.
92
89
  *
@@ -118,7 +115,15 @@ function computeConvergence(snapshots, options = {}) {
118
115
  let sumTransM = 0;
119
116
  let pairCount = 0;
120
117
  for (let i = 1; i < snapshots.length; i++) {
121
- const { rotationDeltaDeg, translationDeltaM } = matrixDelta(snapshots[i - 1].matrix, snapshots[i].matrix);
118
+ const prevM = snapshots[i - 1].matrix;
119
+ const currM = snapshots[i].matrix;
120
+ if (!isFiniteMatrix(prevM) || !isFiniteMatrix(currM)) {
121
+ sumRotDeg += rotFail;
122
+ sumTransM += transFail;
123
+ pairCount += 1;
124
+ continue;
125
+ }
126
+ const { rotationDeltaDeg, translationDeltaM } = matrixDelta(prevM, currM);
122
127
  sumRotDeg += Math.abs(rotationDeltaDeg);
123
128
  sumTransM += Math.abs(translationDeltaM);
124
129
  pairCount += 1;
@@ -137,7 +142,8 @@ function computeConvergence(snapshots, options = {}) {
137
142
  * the column-major layout that gl-matrix and the framework use.
138
143
  *
139
144
  * Rotation: extract the orientation quaternion with `mat4.getRotation`
140
- * and compute the relative angle via `quat.getAngle` (degrees).
145
+ * and compute the relative angle via the shared `geodesicAngleRad` kernel
146
+ * (radians, converted to degrees here).
141
147
  * Translation: extract the origin column with `mat4.getTranslation` and
142
148
  * compute the Euclidean distance.
143
149
  *
@@ -161,15 +167,16 @@ function matrixDelta(prev, curr) {
161
167
  mat4.getRotation(currQuat, currMat);
162
168
  quat.normalize(prevQuat, prevQuat);
163
169
  quat.normalize(currQuat, currQuat);
164
- const angleRad = quat.getAngle(prevQuat, currQuat);
165
- const rotationDeltaDeg = Number.isNaN(angleRad) ? 0 : angleRad * 180 / Math.PI;
170
+ const angleRad = geodesicAngleRad(prevQuat, currQuat);
171
+ const rotationDeltaDeg = Number.isFinite(angleRad) ? angleRad * 180 / Math.PI : 0;
166
172
  const prevT = vec3.create();
167
173
  const currT = vec3.create();
168
174
  mat4.getTranslation(prevT, prevMat);
169
175
  mat4.getTranslation(currT, currMat);
176
+ const rawTranslation = vec3.distance(prevT, currT);
170
177
  return {
171
178
  rotationDeltaDeg,
172
- translationDeltaM: vec3.distance(prevT, currT)
179
+ translationDeltaM: Number.isFinite(rawTranslation) ? rawTranslation : 0
173
180
  };
174
181
  }
175
182
  /**
@@ -319,56 +326,6 @@ function computeCoverage(odometryPositions, options = {}) {
319
326
  };
320
327
  }
321
328
  /**
322
- * §4.3 — Compare the alignment matrix's mapping of AR-forward to ENU
323
- * against the device compass heading. Returns `score = null` whenever
324
- * the sensor's `absolute !== true` so the aggregate excludes the
325
- * sub-score (per Q2 in the plan).
326
- *
327
- * AR-forward in our `ARPose` convention is `(0, 0, -1)` (right-handed,
328
- * Y-up). We rotate that by the alignment's 3×3 rotation, take its
329
- * North/East components, and compare against the compass alpha.
330
- */
331
- function computeCompassAgreement(alignmentMatrix, sensorOrientation, arPose, options = {}) {
332
- const warn = options.warnDeg ?? DEFAULT_TRACKING_QUALITY_OPTIONS.compassWarnDeg;
333
- const fail = options.failDeg ?? DEFAULT_TRACKING_QUALITY_OPTIONS.compassFailDeg;
334
- if (!alignmentMatrix || !sensorOrientation || !arPose) return {
335
- score: null,
336
- headingDeltaDeg: null
337
- };
338
- if (sensorOrientation.absolute !== true) return {
339
- score: null,
340
- headingDeltaDeg: null
341
- };
342
- const q = arPose.orientation;
343
- const fx = 0;
344
- const fy = 0;
345
- const fz = -1;
346
- const x = q.x, y = q.y, z = q.z, w = q.w;
347
- const tx = 2 * (y * fz - z * fy);
348
- const ty = 2 * (z * fx - x * fz);
349
- const tz = 2 * (x * fy - y * fx);
350
- const arForwardLocal = [
351
- fx + w * tx + (y * tz - z * ty),
352
- fy + w * ty + (z * tx - x * tz),
353
- fz + w * tz + (x * ty - y * tx)
354
- ];
355
- const m = alignmentMatrix;
356
- const enuN = m[0] * arForwardLocal[0] + m[4] * arForwardLocal[1] + m[8] * arForwardLocal[2];
357
- m[1] * arForwardLocal[0] + m[5] * arForwardLocal[1] + m[9] * arForwardLocal[2];
358
- const enuE = m[2] * arForwardLocal[0] + m[6] * arForwardLocal[1] + m[10] * arForwardLocal[2];
359
- if (Math.hypot(enuN, enuE) < 1e-6) return {
360
- score: null,
361
- headingDeltaDeg: null
362
- };
363
- const alignmentHeadingDeg = bearingDeg(enuN, enuE);
364
- const compassHeadingDeg = sensorOrientation.alpha;
365
- const delta = Math.abs(wrapToHalfCircle(alignmentHeadingDeg - compassHeadingDeg));
366
- return {
367
- score: rampDown(delta, warn, fail),
368
- headingDeltaDeg: delta
369
- };
370
- }
371
- /**
372
329
  * §4.6 — Maximum lateral distance between raw GPS positions and the
373
330
  * positions you would predict by transforming the matching odometry
374
331
  * positions through the current alignment. Diagnostic only — never
@@ -418,9 +375,6 @@ const trackingQualitySlice = createSlice({
418
375
  reportUpdated(state, action) {
419
376
  state.report = action.payload;
420
377
  },
421
- firstAgreementReached(state, action) {
422
- state.firstAgreementObservationIndex = action.payload;
423
- },
424
378
  /** §4.8 — update the hysteresis counter for ok → degraded holdoff. */
425
379
  degradedCountUpdated(state, action) {
426
380
  state.degradedConsecutiveCount = action.payload;
@@ -439,7 +393,7 @@ const trackingQualitySlice = createSlice({
439
393
  }
440
394
  }
441
395
  });
442
- const { snapshotPushed, snapshotsTrimmed, reportUpdated, firstAgreementReached, degradedCountUpdated, resetTrackingQuality } = trackingQualitySlice.actions;
396
+ const { snapshotPushed, snapshotsTrimmed, reportUpdated, degradedCountUpdated, resetTrackingQuality } = trackingQualitySlice.actions;
443
397
  const { smoothedConvergenceUpdated } = trackingQualitySlice.actions;
444
398
  const trackingQualityReducer = trackingQualitySlice.reducer;
445
399
  function selectTrackingQuality(state) {
@@ -448,9 +402,6 @@ function selectTrackingQuality(state) {
448
402
  function selectRecentAlignments(state) {
449
403
  return state.trackingQuality?.recentAlignments ?? EMPTY_SNAPSHOTS;
450
404
  }
451
- function selectFirstAgreementObservationIndex(state) {
452
- return state.trackingQuality?.firstAgreementObservationIndex ?? null;
453
- }
454
405
  const EMPTY_SNAPSHOTS = Object.freeze([]);
455
406
  /**
456
407
  * Pure function — produces a {@link TrackingQualityReport} from a root
@@ -474,10 +425,7 @@ function computeTrackingQualityReport(rootState, options = {}) {
474
425
  const odometryPositions = selectOdometryPositions(rootState);
475
426
  const zeroRef = selectZeroReference(rootState);
476
427
  const trackingPhase = selectTrackingPhase(rootState);
477
- const sensorOrientation = selectLastSensorOrientation(rootState);
478
- const lastPose = selectLastValidPose(rootState);
479
428
  const snapshots = selectRecentAlignments(rootState);
480
- const firstAgreementIdx = selectFirstAgreementObservationIndex(rootState);
481
429
  const coverage = computeCoverage(odometryPositions, {
482
430
  walkedDistanceM: opts.coverageWalkedDistanceM,
483
431
  directionSpreadDeg: opts.coverageDirectionSpreadDeg
@@ -492,15 +440,10 @@ function computeTrackingQualityReport(rootState, options = {}) {
492
440
  rotationWarnDeg: opts.convergenceRotationWarnDeg,
493
441
  translationWarnM: opts.convergenceTranslationWarnM
494
442
  });
495
- const compass = computeCompassAgreement(alignmentMatrix, sensorOrientation, lastPose, {
496
- warnDeg: opts.compassWarnDeg,
497
- failDeg: opts.compassFailDeg
498
- });
499
443
  const gpsVsFusedMaxDivergenceM = computeGpsVsFusedDivergence(alignmentMatrix, gpsPositions, odometryPositions, zeroRef);
500
444
  const subScores = {
501
445
  convergence: clamp01(emaBlend(rootState.trackingQuality?.smoothedConvergence ?? null, convergence.score, opts.convergenceEmaAlpha)),
502
446
  residualConsensus: clamp01(residual.score),
503
- compassAgreement: compass.score === null ? null : clamp01(compass.score),
504
447
  gpsAccuracy: clamp01(gpsAccuracy.score),
505
448
  coverage: clamp01(coverage.score)
506
449
  };
@@ -510,7 +453,6 @@ function computeTrackingQualityReport(rootState, options = {}) {
510
453
  subScores.gpsAccuracy,
511
454
  subScores.coverage
512
455
  ];
513
- if (subScores.compassAgreement !== null) aggregateInputs.push(subScores.compassAgreement);
514
456
  const confidence = clamp01(Math.min(...aggregateInputs));
515
457
  const observationsSeen = gpsPositions.length;
516
458
  let state;
@@ -529,8 +471,6 @@ function computeTrackingQualityReport(rootState, options = {}) {
529
471
  medianRecentGpsAccuracyM: gpsAccuracy.medianM,
530
472
  walkedDistanceM: coverage.walkedDistanceM,
531
473
  directionSpreadDeg: coverage.directionSpreadDeg,
532
- headingDeltaDeg: compass.headingDeltaDeg,
533
- compassDriftDetected: firstAgreementIdx !== null && compass.headingDeltaDeg !== null && compass.headingDeltaDeg > opts.compassWarnDeg,
534
474
  observationsSeen,
535
475
  gpsVsFusedMaxDivergenceM
536
476
  }
@@ -540,15 +480,14 @@ function computeTrackingQualityReport(rootState, options = {}) {
540
480
  * Per-field tolerances for the {@link reportsEqual} dispatch gate.
541
481
  *
542
482
  * Why this exists: `tracking/poseReceived` fires on *every* XR frame
543
- * (30–60 fps). The §4.3 compass cross-check consumes the live AR pose
544
- * and sensor heading, so `subScores.compassAgreement` and
545
- * `diagnostics.headingDeltaDeg` (and, when compass is the minimum,
546
- * `confidence`) jitter by imperceptible floating-point amounts on a
547
- * held-still device. With a strict `!==` comparison every such frame
548
- * produced a fresh `reportUpdated` dispatch high Redux churn and a
549
- * HUD re-render at frame rate. The tolerances below quantise those
550
- * sub-perceptual changes so the gate only fires when the user-visible
551
- * quality actually moved.
483
+ * (30–60 fps). The per-frame recompute reuses GPS/odometry windows that
484
+ * are unchanged between pose frames, so the float sub-scores and
485
+ * diagnostics (and `confidence`) jitter by imperceptible floating-point
486
+ * amounts on a held-still device. With a strict `!==` comparison every
487
+ * such frame produced a fresh `reportUpdated` dispatch high Redux
488
+ * churn and a HUD re-render at frame rate. The tolerances below quantise
489
+ * those sub-perceptual changes so the gate only fires when the
490
+ * user-visible quality actually moved.
552
491
  *
553
492
  * The gate compares the freshly computed report against the *last
554
493
  * dispatched* report (the stored `prev`), not against the previous
@@ -567,10 +506,9 @@ const REPORT_METRE_EPSILON_M = .001;
567
506
  * (both `null` ⇒ equal; exactly one `null` ⇒ different) and otherwise
568
507
  * compares with an absolute epsilon. Non-finite values are compared with
569
508
  * {@link Object.is}, so a genuine finite↔NaN/Infinity transition still
570
- * fires a dispatch, but a *persistently* NaN diagnostic (the only
571
- * unguarded path is a NaN compass `alpha` leaking into
572
- * `diagnostics.headingDeltaDeg`) does NOT churn the gate every frame —
573
- * `Object.is(NaN, NaN)` is `true` whereas `NaN === NaN` is `false`.
509
+ * fires a dispatch, but a *persistently* NaN diagnostic does NOT churn
510
+ * the gate every frame — `Object.is(NaN, NaN)` is `true` whereas
511
+ * `NaN === NaN` is `false`.
574
512
  */
575
513
  function nearlyEqual(a, b, eps) {
576
514
  if (a === null || b === null) return a === b;
@@ -585,8 +523,8 @@ function nearlyEqual(a, b, eps) {
585
523
  *
586
524
  * Float fields use the per-field tolerances above
587
525
  * ({@link REPORT_SCORE_EPSILON} et al.) so imperceptible per-frame
588
- * compass jitter does not churn the store — see the constants' doc
589
- * comment for the rationale.
526
+ * jitter does not churn the store — see the constants' doc comment for
527
+ * the rationale.
590
528
  */
591
529
  function reportsEqual(a, b) {
592
530
  if (a === b) return true;
@@ -595,10 +533,10 @@ function reportsEqual(a, b) {
595
533
  if (!nearlyEqual(a.confidence, b.confidence, REPORT_SCORE_EPSILON)) return false;
596
534
  const sa = a.subScores;
597
535
  const sb = b.subScores;
598
- if (!nearlyEqual(sa.convergence, sb.convergence, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.residualConsensus, sb.residualConsensus, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.compassAgreement, sb.compassAgreement, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.gpsAccuracy, sb.gpsAccuracy, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.coverage, sb.coverage, REPORT_SCORE_EPSILON)) return false;
536
+ if (!nearlyEqual(sa.convergence, sb.convergence, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.residualConsensus, sb.residualConsensus, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.gpsAccuracy, sb.gpsAccuracy, REPORT_SCORE_EPSILON) || !nearlyEqual(sa.coverage, sb.coverage, REPORT_SCORE_EPSILON)) return false;
599
537
  const da = a.diagnostics;
600
538
  const db = b.diagnostics;
601
- return da.compassDriftDetected === db.compassDriftDetected && da.observationsSeen === db.observationsSeen && nearlyEqual(da.recentSumRotationDeltaDeg, db.recentSumRotationDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.directionSpreadDeg, db.directionSpreadDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.headingDeltaDeg, db.headingDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.recentSumTranslationDeltaM, db.recentSumTranslationDeltaM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianResidualM, db.medianResidualM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianRecentGpsAccuracyM, db.medianRecentGpsAccuracyM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.walkedDistanceM, db.walkedDistanceM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.gpsVsFusedMaxDivergenceM, db.gpsVsFusedMaxDivergenceM, REPORT_METRE_EPSILON_M);
539
+ return da.observationsSeen === db.observationsSeen && nearlyEqual(da.recentSumRotationDeltaDeg, db.recentSumRotationDeltaDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.directionSpreadDeg, db.directionSpreadDeg, REPORT_ANGLE_EPSILON_DEG) && nearlyEqual(da.recentSumTranslationDeltaM, db.recentSumTranslationDeltaM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianResidualM, db.medianResidualM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.medianRecentGpsAccuracyM, db.medianRecentGpsAccuracyM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.walkedDistanceM, db.walkedDistanceM, REPORT_METRE_EPSILON_M) && nearlyEqual(da.gpsVsFusedMaxDivergenceM, db.gpsVsFusedMaxDivergenceM, REPORT_METRE_EPSILON_M);
602
540
  }
603
541
  function matricesNearlyEqual(a, b) {
604
542
  if (a.length !== b.length) return false;
@@ -650,16 +588,14 @@ function createTrackingQualityListenerMiddleware(options = {}) {
650
588
  ...DEFAULT_TRACKING_QUALITY_OPTIONS,
651
589
  ...options
652
590
  };
653
- let compassStreak = 0;
654
591
  const listenerMiddleware = createListenerMiddleware();
655
592
  listenerMiddleware.startListening({
656
593
  predicate: (action) => inputActionPredicate(action),
657
594
  effect: (action, api) => {
658
595
  const state = api.getState();
659
596
  if (action.type === TRACKING_QUALITY_INPUT_ACTIONS.startSession || action.type === TRACKING_QUALITY_INPUT_ACTIONS.resetTracking) {
660
- compassStreak = 0;
661
597
  const tq = state.trackingQuality;
662
- if (tq && (tq.recentAlignments.length > 0 || tq.firstAgreementObservationIndex !== null || tq.report !== null || tq.degradedConsecutiveCount > 0)) api.dispatch(resetTrackingQuality());
598
+ if (tq && (tq.recentAlignments.length > 0 || tq.report !== null || tq.degradedConsecutiveCount > 0)) api.dispatch(resetTrackingQuality());
663
599
  return;
664
600
  }
665
601
  if (action.type === TRACKING_QUALITY_INPUT_ACTIONS.gpsRecorded || action.type === TRACKING_QUALITY_INPUT_ACTIONS.setZeroPos) {
@@ -678,30 +614,6 @@ function createTrackingQualityListenerMiddleware(options = {}) {
678
614
  }
679
615
  }
680
616
  }
681
- const midState = api.getState();
682
- const midTq = midState.trackingQuality;
683
- if (midTq && midTq.firstAgreementObservationIndex === null) {
684
- const snapshots = selectRecentAlignments(midState);
685
- if (snapshots.length >= 2) {
686
- const conv = computeConvergence(snapshots, {
687
- rotationWarnDeg: opts.convergenceRotationWarnDeg,
688
- translationWarnM: opts.convergenceTranslationWarnM
689
- });
690
- const sensorOr = selectLastSensorOrientation(midState);
691
- const pose = selectLastValidPose(midState);
692
- const compass = computeCompassAgreement(selectAlignmentMatrix(midState), sensorOr, pose, {
693
- warnDeg: opts.compassWarnDeg,
694
- failDeg: opts.compassFailDeg
695
- });
696
- if (conv.score >= .7 && compass.headingDeltaDeg !== null && compass.headingDeltaDeg <= opts.compassWarnDeg) {
697
- compassStreak += 1;
698
- if (compassStreak >= opts.firstAgreementMinStreak) {
699
- const obsCount = selectGpsPositions(midState).length;
700
- api.dispatch(firstAgreementReached(obsCount));
701
- }
702
- } else compassStreak = 0;
703
- }
704
- }
705
617
  const next = api.getState();
706
618
  const report = computeTrackingQualityReport(next, opts);
707
619
  const prev = next.trackingQuality?.report ?? null;
@@ -720,4 +632,4 @@ function createTrackingQualityListenerMiddleware(options = {}) {
720
632
  return listenerMiddleware.middleware;
721
633
  }
722
634
  //#endregion
723
- export { DEFAULT_TRACKING_QUALITY_OPTIONS, computeCompassAgreement, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, firstAgreementReached, matrixDelta, reportUpdated, resetTrackingQuality, selectFirstAgreementObservationIndex, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
635
+ export { DEFAULT_TRACKING_QUALITY_OPTIONS, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, matrixDelta, reportUpdated, resetTrackingQuality, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
@@ -1,2 +1,2 @@
1
- import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-DJZ9qeGg.js";
1
+ import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-BuAZxXZ5.js";
2
2
  export { DeviceOrientation, PoseReceivedPayload, ResetTransformData, TrackingPhase, TrackingSliceState, clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectLastSensorOrientation, selectLastValidPose, selectLostFrameCount, selectTrackingPhase, trackingReducer };