gps-plus-slam-app-framework 1.0.0 → 1.0.7

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 (216) hide show
  1. package/LICENSE +4 -4
  2. package/README.md +249 -108
  3. package/dist/accuracy-circles-CsxE_7x1.d.ts +43 -0
  4. package/dist/{app-selectors-DyMzxCEC.d.ts → app-selectors-BeU0_ab8.d.ts} +14 -15
  5. package/dist/ar/camera-blit-capture.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-sampler.d.ts +2 -2
  9. package/dist/ar/depth-sampler.js +1 -1
  10. package/dist/ar/frame-loop.d.ts +2 -0
  11. package/dist/ar/frame-loop.js +40 -0
  12. package/dist/ar/image-capture.d.ts +1 -1
  13. package/dist/ar/image-capture.js +1 -1
  14. package/dist/ar/index.d.ts +12 -12
  15. package/dist/ar/index.js +3 -3
  16. package/dist/ar/replay-scene.d.ts +1 -1
  17. package/dist/ar/scene-node-names.d.ts +1 -1
  18. package/dist/ar/webxr-session.d.ts +4 -4
  19. package/dist/ar/webxr-session.js +151 -56
  20. package/dist/ar/xr-camera-texture.d.ts +1 -1
  21. package/dist/ar/xr-error-handler.d.ts +1 -1
  22. package/dist/core/index.d.ts +2 -0
  23. package/dist/core/index.js +2 -0
  24. package/dist/create-slam-app-store-CNsZ3rcX.d.ts +371 -0
  25. package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-xg8vna6c.d.ts} +1 -1
  26. package/dist/file-system-DTApEv_Q.d.ts +114 -0
  27. package/dist/frame-loop-QJIxLin0.d.ts +42 -0
  28. package/dist/frustum-visibility-BHnFrSKQ.d.ts +58 -0
  29. package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-CB4DwWOG.d.ts} +1 -1
  30. package/dist/geo/h3-proximity.d.ts +2 -0
  31. package/dist/{ref-points/h3-ref-point.js → geo/h3-proximity.js} +16 -12
  32. package/dist/geo/index.d.ts +2 -0
  33. package/dist/geo/index.js +2 -0
  34. package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-CTuF23gG.d.ts} +16 -1
  35. package/dist/{gps-Ddzr2KlX.d.ts → gps-DXoQb9Kf.d.ts} +1 -1
  36. package/dist/gps-anchor-CnWY61XK.d.ts +48 -0
  37. package/dist/gps-anchor-DjC_3MoT.js +257 -0
  38. package/dist/gps-ar-pose-sampler-DsM1Kb2N.d.ts +43 -0
  39. package/dist/{recording-coordinator-DzflP07_.d.ts → gps-event-coordinator-DBx9I1YX.d.ts} +7 -6
  40. package/dist/gps-event-markers-BGr-iLvi.d.ts +103 -0
  41. package/dist/{h3-ref-point-CFhayNSZ.d.ts → h3-proximity-BhfgNbdW.d.ts} +16 -12
  42. package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-DZLxHDN9.d.ts} +1 -1
  43. package/dist/index-gzWJMVpo.d.ts +2 -0
  44. package/dist/index-pOjUCKEP.d.ts +24 -0
  45. package/dist/index.d.ts +59 -56
  46. package/dist/index.js +22 -21
  47. package/dist/{leaflet-map-overlay-CvfQXTye.d.ts → leaflet-map-overlay-Cew7XAoO.d.ts} +32 -17
  48. package/dist/licensing/index.d.ts +1 -1
  49. package/dist/licensing/index.js +1 -1
  50. package/dist/{logger-B81iwxx0.d.ts → logger-C9vugiQb.d.ts} +7 -1
  51. package/dist/map-data-sih4g64v.d.ts +49 -0
  52. package/dist/map-overlay-draw-B3R1c9Xp.d.ts +42 -0
  53. package/dist/{null-storage-backend-CsWvQwN8.d.ts → null-storage-backend-BKOS7JpL.d.ts} +4 -2
  54. package/dist/{opfs-storage-ChOzF_Wp.d.ts → opfs-storage-DOPkzgeT.d.ts} +54 -51
  55. package/dist/opfs-storage-backend-Cj_7PWoq.d.ts +13 -0
  56. package/dist/{permission-checker-jO2ziCWC.d.ts → permission-checker-DPJl_RU6.d.ts} +41 -1
  57. package/dist/persistence-middleware-BkHIqvaA.d.ts +55 -0
  58. package/dist/{recording-replayer-oOzPITrd.d.ts → recording-replayer-CmCIIZ02.d.ts} +2 -2
  59. package/dist/recording-slice-d6OtCrza.d.ts +28 -0
  60. package/dist/{replay-engine-DuX316ae.d.ts → replay-engine-BGBRQyXs.d.ts} +4 -1
  61. package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DpAGcAKY.d.ts} +3 -3
  62. package/dist/sensors/gps-error-handler.d.ts +1 -1
  63. package/dist/sensors/gps.d.ts +1 -1
  64. package/dist/sensors/gps.js +2 -2
  65. package/dist/sensors/index.d.ts +3 -3
  66. package/dist/sensors/index.js +1 -1
  67. package/dist/sensors/permission-checker.d.ts +2 -2
  68. package/dist/sensors/permission-checker.js +79 -1
  69. package/dist/state/app-selectors.d.ts +2 -2
  70. package/dist/state/app-selectors.js +33 -3
  71. package/dist/state/combined-root-state.d.ts +2 -0
  72. package/dist/state/combined-root-state.js +1 -0
  73. package/dist/state/create-slam-app-store.d.ts +2 -0
  74. package/dist/state/create-slam-app-store.js +75 -0
  75. package/dist/state/gps-ar-pose-sampler.d.ts +4 -0
  76. package/dist/state/gps-ar-pose-sampler.js +19 -0
  77. package/dist/state/{recording-coordinator.d.ts → gps-event-coordinator.d.ts} +1 -1
  78. package/dist/state/{recording-coordinator.js → gps-event-coordinator.js} +3 -4
  79. package/dist/state/index.d.ts +17 -17
  80. package/dist/state/index.js +10 -8
  81. package/dist/state/persistence-middleware.d.ts +2 -2
  82. package/dist/state/persistence-middleware.js +39 -11
  83. package/dist/state/recording-options.d.ts +1 -1
  84. package/dist/state/recording-replayer.d.ts +3 -2
  85. package/dist/state/recording-replayer.js +2 -2
  86. package/dist/state/recording-slice.d.ts +2 -0
  87. package/dist/state/{recorder-slice.js → recording-slice.js} +7 -15
  88. package/dist/state/replay-engine.d.ts +1 -1
  89. package/dist/state/replay-engine.js +3 -3
  90. package/dist/state/store-subscribers.d.ts +2 -2
  91. package/dist/state/store-subscribers.js +38 -20
  92. package/dist/state/subscribe-to-selector.d.ts +1 -1
  93. package/dist/state/tracking-quality.d.ts +2 -0
  94. package/dist/state/tracking-quality.js +723 -0
  95. package/dist/state/tracking-slice.d.ts +2 -0
  96. package/dist/state/tracking-slice.js +147 -0
  97. package/dist/storage/file-system-utils.d.ts +1 -1
  98. package/dist/storage/file-system.d.ts +2 -2
  99. package/dist/storage/file-system.js +154 -94
  100. package/dist/storage/index.d.ts +9 -11
  101. package/dist/storage/index.js +5 -7
  102. package/dist/storage/null-storage-backend.d.ts +1 -1
  103. package/dist/storage/null-storage-backend.js +7 -0
  104. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  105. package/dist/storage/opfs-storage-backend.js +7 -1
  106. package/dist/storage/opfs-storage.d.ts +2 -2
  107. package/dist/storage/opfs-storage.js +69 -85
  108. package/dist/storage/storage-backend.d.ts +2 -2
  109. package/dist/storage/zip-export.d.ts +2 -2
  110. package/dist/storage/zip-export.js +55 -62
  111. package/dist/storage/zip-reader.d.ts +2 -2
  112. package/dist/storage/zip-reader.js +32 -5
  113. package/dist/storage-backend-CT0Il_AB.d.ts +28 -0
  114. package/dist/{store-subscribers-CuoIzB5T.d.ts → store-subscribers-Dce_CoJF.d.ts} +31 -7
  115. package/dist/{subscribe-to-selector-UTZ0KkAc.d.ts → subscribe-to-selector-DIHm3BRZ.d.ts} +1 -1
  116. package/dist/test-utils/browser-mocks.d.ts +1 -1
  117. package/dist/test-utils/zip-round-trip-helpers.d.ts +7 -1
  118. package/dist/test-utils/zip-round-trip-helpers.js +6 -5
  119. package/dist/tracking-slice-utCNND8t.d.ts +86 -0
  120. package/dist/types/ar-types.d.ts +1 -1
  121. package/dist/types/geo-types.d.ts +2 -2
  122. package/dist/types/index.d.ts +3 -3
  123. package/dist/utils/concurrency.d.ts +1 -1
  124. package/dist/utils/failure-tracker.d.ts +1 -1
  125. package/dist/utils/format-file-size.d.ts +1 -1
  126. package/dist/utils/fused-path.d.ts +1 -1
  127. package/dist/utils/index.d.ts +6 -6
  128. package/dist/utils/list-formatter.d.ts +1 -1
  129. package/dist/utils/logger.d.ts +1 -1
  130. package/dist/utils/logger.js +68 -8
  131. package/dist/visualization/accuracy-circles.d.ts +2 -0
  132. package/dist/visualization/accuracy-circles.js +57 -0
  133. package/dist/visualization/alignment-lerper.d.ts +1 -1
  134. package/dist/visualization/camera-follower.d.ts +1 -1
  135. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  136. package/dist/visualization/frustum-visibility.d.ts +2 -0
  137. package/dist/visualization/frustum-visibility.js +139 -0
  138. package/dist/visualization/gps-anchor.d.ts +2 -0
  139. package/dist/visualization/gps-anchor.js +2 -0
  140. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  141. package/dist/visualization/gps-event-markers.d.ts +2 -2
  142. package/dist/visualization/gps-event-markers.js +69 -2
  143. package/dist/visualization/index.d.ts +17 -12
  144. package/dist/visualization/index.js +6 -2
  145. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  146. package/dist/visualization/leaflet-map-overlay.js +56 -98
  147. package/dist/visualization/lerp-utils.d.ts +1 -1
  148. package/dist/visualization/map-data.d.ts +2 -0
  149. package/dist/visualization/map-data.js +33 -0
  150. package/dist/visualization/map-overlay-draw.d.ts +2 -0
  151. package/dist/visualization/map-overlay-draw.js +107 -0
  152. package/dist/visualization/map-overlay.d.ts +1 -1
  153. package/dist/visualization/three-dispose.d.ts +1 -1
  154. package/dist/visualization/vis-colors.d.ts +1 -1
  155. package/dist/{webxr-session-COlSrXdL.d.ts → webxr-session-DB5cIZnN.d.ts} +53 -12
  156. package/dist/{zip-export-Rtpi75JJ.d.ts → zip-export-QO4iLIi7.d.ts} +58 -7
  157. package/dist/{zip-reader-D7pkpX8K.d.ts → zip-reader-D1105idz.d.ts} +38 -1
  158. package/package.json +29 -18
  159. package/dist/ar/tracking-state.d.ts +0 -2
  160. package/dist/ar/tracking-state.js +0 -164
  161. package/dist/community-license-key-ChZLCJyO.d.ts +0 -21
  162. package/dist/file-system-BsQgYCU3.d.ts +0 -128
  163. package/dist/gps-event-markers-DpEy2qRd.d.ts +0 -67
  164. package/dist/licensing/community-license-key.d.ts +0 -2
  165. package/dist/licensing/community-license-key.js +0 -21
  166. package/dist/opfs-storage-backend-CVcmfJHN.d.ts +0 -11
  167. package/dist/persistence-middleware-DrptY02i.d.ts +0 -31
  168. package/dist/recorder-slice-fcwAXrsh.d.ts +0 -35
  169. package/dist/ref-point-importer-B_wn5ABk.d.ts +0 -62
  170. package/dist/ref-point-loader-Pt1smgHS.d.ts +0 -117
  171. package/dist/ref-points/h3-ref-point.d.ts +0 -2
  172. package/dist/ref-points/index.d.ts +0 -2
  173. package/dist/ref-points/index.js +0 -2
  174. package/dist/ref-points-slice-tC-Wd6XJ.d.ts +0 -48
  175. package/dist/reference-points-CFWy8_wj.d.ts +0 -68
  176. package/dist/routing-slice-BlHTM8eh.d.ts +0 -23
  177. package/dist/state/recorder-slice.d.ts +0 -2
  178. package/dist/state/ref-points-slice.d.ts +0 -2
  179. package/dist/state/ref-points-slice.js +0 -44
  180. package/dist/state/routing-slice.d.ts +0 -2
  181. package/dist/state/routing-slice.js +0 -13
  182. package/dist/state/store.d.ts +0 -9
  183. package/dist/state/store.js +0 -71
  184. package/dist/storage/ref-point-importer.d.ts +0 -2
  185. package/dist/storage/ref-point-importer.js +0 -163
  186. package/dist/storage/ref-point-loader.d.ts +0 -2
  187. package/dist/storage/ref-point-loader.js +0 -248
  188. package/dist/storage/ref-point-recovery.d.ts +0 -28
  189. package/dist/storage/ref-point-recovery.js +0 -159
  190. package/dist/storage-backend-BcEItnFr.d.ts +0 -20
  191. package/dist/store-C7PKOj8S.d.ts +0 -96
  192. package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
  193. package/dist/visualization/reference-points.d.ts +0 -2
  194. package/dist/visualization/reference-points.js +0 -160
  195. /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-L1RbWHQ4.d.ts} +0 -0
  196. /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-Ck8LEq8k.d.ts} +0 -0
  197. /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-C1V9I1Z6.d.ts} +0 -0
  198. /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-1k8mnw5J.d.ts} +0 -0
  199. /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-JKb1ST8E.d.ts} +0 -0
  200. /package/dist/{chromium-camera-access-workaround-CU5zSKNr.d.ts → chromium-camera-access-workaround-NvChXsi8.d.ts} +0 -0
  201. /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-CMw8pSaG.d.ts} +0 -0
  202. /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-CkjCb2HW.d.ts} +0 -0
  203. /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-DnHXOIRa.d.ts} +0 -0
  204. /package/dist/{file-system-utils-5cWO8Xyv.d.ts → file-system-utils-CtKiiw27.d.ts} +0 -0
  205. /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-BTS5H62-.d.ts} +0 -0
  206. /package/dist/{gps-compass-cubes-C-6JEnpR.d.ts → gps-compass-cubes-CsLtFv4B.d.ts} +0 -0
  207. /package/dist/{gps-error-handler-VPuNOuf8.d.ts → gps-error-handler-Dg3ziRVY.d.ts} +0 -0
  208. /package/dist/{lerp-utils-87NWjUOD.d.ts → lerp-utils-B8MM8zCR.d.ts} +0 -0
  209. /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CNqi-E2j.d.ts} +0 -0
  210. /package/dist/{map-overlay-DuiZuCTx.d.ts → map-overlay-DAmreyA3.d.ts} +0 -0
  211. /package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-Dq6IsKAv.d.ts} +0 -0
  212. /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-6LBjPmwz.d.ts} +0 -0
  213. /package/dist/{three-dispose-CCBHjE_a.d.ts → three-dispose-C2drkp6f.d.ts} +0 -0
  214. /package/dist/{vis-colors-Qi2vlRGt.d.ts → vis-colors-CHQhjal3.d.ts} +0 -0
  215. /package/dist/{xr-camera-texture-BbukNE79.d.ts → xr-camera-texture-BU5YvKAM.d.ts} +0 -0
  216. /package/dist/{xr-error-handler-DTHfmvrl.d.ts → xr-error-handler-CDY5QxGb.d.ts} +0 -0
@@ -0,0 +1,371 @@
1
+ import { t as ARPose } from "./ar-types-Ck8LEq8k.js";
2
+ import { a as TrackingSliceState, t as DeviceOrientation } from "./tracking-slice-utCNND8t.js";
3
+ import { t as RecordingState } from "./recording-slice-d6OtCrza.js";
4
+ import { n as SessionMetadata } from "./opfs-storage-DOPkzgeT.js";
5
+ import { n as StorageBackend } from "./storage-backend-CT0Il_AB.js";
6
+ import { GpsPoint, LatLong, Matrix4, RootState, Vector3 } from "gps-plus-slam-js";
7
+ import * as _$_reduxjs_toolkit0 from "@reduxjs/toolkit";
8
+ import { Middleware, Reducer, ReducersMapObject, configureStore } from "@reduxjs/toolkit";
9
+
10
+ //#region ../src/state/combined-root-state.d.ts
11
+ type CombinedRootState = SlamAppCombinedState;
12
+ //#endregion
13
+ //#region ../src/state/tracking-quality.d.ts
14
+ type TrackingQualityState = 'warming-up' | 'ar-lost' | 'degraded' | 'ok';
15
+ interface TrackingQualityReport {
16
+ state: TrackingQualityState;
17
+ /** 0..1, monotonically combined from {@link subScores}. */
18
+ confidence: number;
19
+ subScores: {
20
+ convergence: number;
21
+ residualConsensus: number; /** `null` when the device cannot supply an absolute compass heading. */
22
+ compassAgreement: number | null;
23
+ gpsAccuracy: number;
24
+ coverage: number;
25
+ };
26
+ diagnostics: {
27
+ /**
28
+ * §4.1 (Finding 6): **sum of |Δrotation|** across the last
29
+ * `matrixHistorySize - 1` consecutive snapshot pairs in the ring
30
+ * buffer, in degrees. Replaced the per-pair `recentMaxRotationDeltaDeg`
31
+ * on 2026-05-23 — see
32
+ * `GpsPlusSlamJs_Docs/docs/2026-05-23-tracking-quality-hud-user-feedback.md`
33
+ * (Finding 6). Surfaced in the recorder HUD as `ΣΔrot:`.
34
+ */
35
+ recentSumRotationDeltaDeg: number; /** §4.1 (Finding 6): sum of |Δtranslation| across the same window, in metres. Surfaced as `ΣΔpos:`. */
36
+ recentSumTranslationDeltaM: number;
37
+ medianResidualM: number;
38
+ medianRecentGpsAccuracyM: number;
39
+ walkedDistanceM: number;
40
+ directionSpreadDeg: number; /** `null` when §4.3 is unavailable (no absolute compass). */
41
+ headingDeltaDeg: number | null;
42
+ compassDriftDetected: boolean;
43
+ observationsSeen: number; /** §4.6 — diagnostic only, never feeds the aggregate. */
44
+ gpsVsFusedMaxDivergenceM: number;
45
+ };
46
+ }
47
+ interface TrackingQualityOptions {
48
+ /** §4.1 ring-buffer length. Seed default; corpus-tuned in §6.1. */
49
+ matrixHistorySize?: number;
50
+ /** §4.2 GPS-window length. Seed default; corpus-tuned in §6.1. */
51
+ residualWindowSize?: number;
52
+ /** Confidence threshold below which `state` reports `'degraded'`. */
53
+ degradedThreshold?: number;
54
+ /** §4.4 window length for GPS-accuracy budget. */
55
+ gpsAccuracyWindowSize?: number;
56
+ /** §4.5 walked-distance threshold for coverage = 1.0. */
57
+ coverageWalkedDistanceM?: number;
58
+ /** §4.5 direction-spread threshold for coverage = 1.0. */
59
+ coverageDirectionSpreadDeg?: number;
60
+ /** §4.1 ΣΔrotation (deg) at/below which convergence scores 1.0. Fail ramp ends at 4×. */
61
+ convergenceRotationWarnDeg?: number;
62
+ /** §4.1 ΣΔtranslation (m) at/below which convergence scores 1.0. Fail ramp ends at 4×. */
63
+ convergenceTranslationWarnM?: number;
64
+ /** §4.3 EMA threshold (deg) below which compass scores 1.0. */
65
+ compassWarnDeg?: number;
66
+ /** §4.3 EMA threshold (deg) above which compass scores 0.0. */
67
+ compassFailDeg?: number;
68
+ /** Minimum GPS observations before leaving `warming-up`. */
69
+ warmupMinObservations?: number;
70
+ /** Minimum coverage score before leaving `warming-up`. */
71
+ warmupMinCoverage?: number;
72
+ /** §4.2 residual saturation target (m). */
73
+ residualConfidenceTargetM?: number;
74
+ /** §4.4 GPS-accuracy floor used to normalise residuals (m). */
75
+ gpsAccuracyFloorM?: number;
76
+ /** §4.3 consecutive compass-agreeing observations for first agreement. */
77
+ firstAgreementMinStreak?: number;
78
+ /** §4.8 consecutive sub-threshold observations before ok → degraded. */
79
+ degradedHoldoff?: number;
80
+ /**
81
+ * §4.8b (Finding 4) — EMA blend factor applied to `subScores.convergence`.
82
+ * `1.0` disables smoothing (raw value). `0 < α < 1` blends as
83
+ * `smoothed = prevSmoothed + α * (raw - prevSmoothed)`. First observation
84
+ * (no prior smoothed value) is seeded to the raw value.
85
+ */
86
+ convergenceEmaAlpha?: number;
87
+ }
88
+ declare const DEFAULT_TRACKING_QUALITY_OPTIONS: Required<TrackingQualityOptions>;
89
+ /**
90
+ * Single buffered alignment matrix snapshot. `matrix` is stored as a
91
+ * plain `number[]` (length 16, row-major) so it stays serializable for
92
+ * RTK's `SerializableStateInvariantMiddleware` and for action logs.
93
+ */
94
+ interface AlignmentSnapshot {
95
+ observationIndex: number;
96
+ matrix: number[];
97
+ }
98
+ interface TrackingQualitySliceState {
99
+ recentAlignments: AlignmentSnapshot[];
100
+ firstAgreementObservationIndex: number | null;
101
+ report: TrackingQualityReport | null;
102
+ /** §4.8 — consecutive observations with raw confidence < degradedThreshold. */
103
+ degradedConsecutiveCount: number;
104
+ /**
105
+ * §4.8b (Finding 4) — last EMA-smoothed convergence sub-score. `null`
106
+ * means "no prior observation" and the next aggregator run will seed
107
+ * the value with the raw convergence score. Reset on
108
+ * `resetTrackingQuality`.
109
+ */
110
+ smoothedConvergence: number | null;
111
+ }
112
+ interface ConvergenceResult {
113
+ score: number;
114
+ /** §4.1 (Finding 6): sum of |Δrotation| across the last N-1 pairs. */
115
+ recentSumRotationDeltaDeg: number;
116
+ /** §4.1 (Finding 6): sum of |Δtranslation| across the last N-1 pairs. */
117
+ recentSumTranslationDeltaM: number;
118
+ /** Pair count used (≥ 2 snapshots ⇒ ≥ 1 pair, else 0). */
119
+ pairCount: number;
120
+ }
121
+ /**
122
+ * §4.1 matrix-history convergence over the buffered snapshots.
123
+ *
124
+ * Reuses the same kernel idea as `computeStabilityDelta` from the
125
+ * Investigation helpers (rotation + translation delta between two 4×4
126
+ * matrices). With 0 or 1 snapshot we return score 0 — convergence is
127
+ * undefined until at least one consecutive pair exists.
128
+ *
129
+ * Finding 6 (2026-05-23 field test): the per-pair `max` aggregation was
130
+ * replaced with a **sum** across the window. `max` is a burst detector
131
+ * — dominated by one bad pair, blind to slow drift; `sum` answers
132
+ * "how much alignment motion accumulated over the window?" and exposes
133
+ * slow creeping drift in user-readable units (°, m). The HUD now shows
134
+ * the raw sums (`ΣΔrot:`, `ΣΔpos:`) alongside the EMA-smoothed `Conv:`
135
+ * sub-score (Finding 4) so the user can debug an unstable reading.
136
+ */
137
+ declare function computeConvergence(snapshots: readonly AlignmentSnapshot[], options?: {
138
+ rotationWarnDeg?: number;
139
+ translationWarnM?: number;
140
+ }): ConvergenceResult;
141
+ /**
142
+ * Rotation and translation delta between two 4×4 alignment matrices in
143
+ * the column-major layout that gl-matrix and the framework use.
144
+ *
145
+ * Rotation: extract the orientation quaternion with `mat4.getRotation`
146
+ * and compute the relative angle via `quat.getAngle` (degrees).
147
+ * Translation: extract the origin column with `mat4.getTranslation` and
148
+ * compute the Euclidean distance.
149
+ *
150
+ * This is the **single shared kernel** for the AppFramework reporter and
151
+ * the Investigation harness (see §11 (a) of the tracking-quality plan).
152
+ * `GpsPlusSlamJs_Investigation/src/investigation-helpers.ts` re-exports
153
+ * `computeStabilityDelta` as a thin wrapper around this function so the
154
+ * §6.1 corpus sweep and the runtime convergence score share one numeric
155
+ * definition.
156
+ */
157
+ declare function matrixDelta(prev: readonly number[], curr: readonly number[]): {
158
+ rotationDeltaDeg: number;
159
+ translationDeltaM: number;
160
+ };
161
+ interface ResidualConsensusResult {
162
+ score: number;
163
+ medianResidualM: number;
164
+ count: number;
165
+ }
166
+ /**
167
+ * §4.2 — Median residual between the last `K` GPS observations and the
168
+ * positions you would have predicted by transforming the matching
169
+ * odometry positions through the current alignment matrix.
170
+ *
171
+ * Returns `score = 0`, `medianResidualM = 0` when we have fewer than
172
+ * 2 paired observations or when the alignment matrix / zero ref is
173
+ * not yet set.
174
+ */
175
+ declare function computeResidualConsensus(alignmentMatrix: Matrix4 | null, gpsPositions: readonly GpsPoint[], odometryPositions: readonly Vector3[], zeroRef: LatLong | null, options?: Pick<Required<TrackingQualityOptions>, 'residualWindowSize' | 'residualConfidenceTargetM' | 'gpsAccuracyFloorM'>): ResidualConsensusResult;
176
+ interface GpsAccuracyResult {
177
+ score: number;
178
+ medianM: number;
179
+ countAccurate: number;
180
+ countTotal: number;
181
+ }
182
+ /**
183
+ * §4.4 — Median `latLongAccuracy` of the last `K` GPS points, with
184
+ * an "accurate-fix count" diagnostic. Cheap, always available.
185
+ *
186
+ * `score` ramps from 1.0 at median ≤ 3 m to 0.0 at median ≥ 25 m
187
+ * (linear), matching the plan's "≤ 5 m is good" heuristic with a
188
+ * generous fail tail.
189
+ */
190
+ declare function computeGpsAccuracy(gpsPositions: readonly GpsPoint[], options?: {
191
+ windowSize?: number;
192
+ goodMedianM?: number;
193
+ badMedianM?: number;
194
+ }): GpsAccuracyResult;
195
+ interface CoverageResult {
196
+ score: number;
197
+ walkedDistanceM: number;
198
+ directionSpreadDeg: number;
199
+ }
200
+ /**
201
+ * §4.5 — Coverage = how far the user has walked AND how diverse the
202
+ * walking direction has been. Both required: a long but unidirectional
203
+ * walk leaves the rotation about the walking axis under-determined.
204
+ */
205
+ declare function computeCoverage(odometryPositions: readonly Vector3[], options?: {
206
+ walkedDistanceM?: number;
207
+ directionSpreadDeg?: number;
208
+ }): CoverageResult;
209
+ interface CompassAgreementResult {
210
+ /** `null` when `absolute !== true` on the sensor orientation. */
211
+ score: number | null;
212
+ headingDeltaDeg: number | null;
213
+ }
214
+ /**
215
+ * §4.3 — Compare the alignment matrix's mapping of AR-forward to ENU
216
+ * against the device compass heading. Returns `score = null` whenever
217
+ * the sensor's `absolute !== true` so the aggregate excludes the
218
+ * sub-score (per Q2 in the plan).
219
+ *
220
+ * AR-forward in our `ARPose` convention is `(0, 0, -1)` (right-handed,
221
+ * Y-up). We rotate that by the alignment's 3×3 rotation, take its
222
+ * North/East components, and compare against the compass alpha.
223
+ */
224
+ declare function computeCompassAgreement(alignmentMatrix: Matrix4 | null, sensorOrientation: DeviceOrientation | null, arPose: ARPose | null, options?: {
225
+ warnDeg?: number;
226
+ failDeg?: number;
227
+ }): CompassAgreementResult;
228
+ /**
229
+ * §4.6 — Maximum lateral distance between raw GPS positions and the
230
+ * positions you would predict by transforming the matching odometry
231
+ * positions through the current alignment. Diagnostic only — never
232
+ * feeds the aggregate confidence per the plan.
233
+ */
234
+ declare function computeGpsVsFusedDivergence(alignmentMatrix: Matrix4 | null, gpsPositions: readonly GpsPoint[], odometryPositions: readonly Vector3[], zeroRef: LatLong | null, windowSize?: number): number;
235
+ declare const snapshotPushed: _$_reduxjs_toolkit0.ActionCreatorWithPayload<AlignmentSnapshot, "trackingQuality/snapshotPushed">, snapshotsTrimmed: _$_reduxjs_toolkit0.ActionCreatorWithPayload<{
236
+ size: number;
237
+ }, "trackingQuality/snapshotsTrimmed">, reportUpdated: _$_reduxjs_toolkit0.ActionCreatorWithPayload<TrackingQualityReport | null, "trackingQuality/reportUpdated">, firstAgreementReached: _$_reduxjs_toolkit0.ActionCreatorWithPayload<number, "trackingQuality/firstAgreementReached">, degradedCountUpdated: _$_reduxjs_toolkit0.ActionCreatorWithPayload<number, "trackingQuality/degradedCountUpdated">, resetTrackingQuality: _$_reduxjs_toolkit0.ActionCreatorWithoutPayload<"trackingQuality/resetTrackingQuality">;
238
+ declare const trackingQualityReducer: _$_reduxjs_toolkit0.Reducer<TrackingQualitySliceState>;
239
+ interface RootWithTrackingQuality {
240
+ trackingQuality?: TrackingQualitySliceState;
241
+ }
242
+ declare function selectTrackingQuality(state: RootWithTrackingQuality): TrackingQualityReport | null;
243
+ declare function selectRecentAlignments(state: RootWithTrackingQuality): readonly AlignmentSnapshot[];
244
+ declare function selectFirstAgreementObservationIndex(state: RootWithTrackingQuality): number | null;
245
+ /**
246
+ * Pure function — produces a {@link TrackingQualityReport} from a root
247
+ * state and options. The Investigation harness imports this directly to
248
+ * sweep `(N, K)` without going through the Redux listener middleware.
249
+ *
250
+ * The report's `state` enum follows §5 of the plan:
251
+ * - `ar-lost` whenever `tracking.phase !== 'tracking'`
252
+ * - `warming-up` until `coverage ≥ warmupMinCoverage` AND
253
+ * `observationsSeen ≥ warmupMinObservations`
254
+ * - then `ok` / `degraded` from `min(subScores)` vs.
255
+ * `degradedThreshold`.
256
+ */
257
+ declare function computeTrackingQualityReport(rootState: CombinedRootState, options?: TrackingQualityOptions): TrackingQualityReport;
258
+ /**
259
+ * Build the listener middleware that drives the `trackingQuality`
260
+ * slice. Register the returned middleware in `createSlamAppStore`.
261
+ *
262
+ * Behaviour:
263
+ * - On `gpsData/recordGpsEvent` and `gpsData/setZeroPos`: if the
264
+ * alignment matrix changed, dispatch `snapshotPushed` +
265
+ * `snapshotsTrimmed`. Then recompute the report and dispatch
266
+ * `reportUpdated` if it changed.
267
+ * - On `recording/startSession` and `tracking/resetTracking`:
268
+ * dispatch `resetTrackingQuality` and recompute the report.
269
+ * - On `tracking/poseReceived` and `tracking/poseLost`: only
270
+ * recompute the report (no buffer change).
271
+ * - All dispatches are idempotency-gated so quiet motion does not
272
+ * churn the store.
273
+ */
274
+ declare function createTrackingQualityListenerMiddleware(options?: TrackingQualityOptions): Middleware;
275
+ //#endregion
276
+ //#region ../src/state/create-slam-app-store.d.ts
277
+ /**
278
+ * Base shape produced by `createSlamAppStore` with no `extraReducers`.
279
+ *
280
+ * Library state (`gpsData` / `gpsElements` / `arElements`) plus the
281
+ * framework recording slice (`recording`).
282
+ */
283
+ interface SlamAppRootState extends RootState {
284
+ recording: RecordingState;
285
+ tracking: TrackingSliceState;
286
+ trackingQuality: TrackingQualitySliceState;
287
+ }
288
+ /** A bare-minimum middleware signature compatible with RTK's middleware list. */
289
+ type SlamAppMiddleware = Middleware<any, any, any>;
290
+ /**
291
+ * Options for {@link createSlamAppStore}.
292
+ */
293
+ interface SlamAppStoreOptions<ExtraReducers extends ReducersMapObject = Record<string, never>> {
294
+ /**
295
+ * Persistence backend used to bridge Redux actions to durable storage.
296
+ * Tests / replay paths should pass `NullStorageBackend`.
297
+ */
298
+ storageBackend: StorageBackend;
299
+ /**
300
+ * Caller-supplied reducers added alongside the framework's built-ins.
301
+ * Use this seam to plug recorder slices (routing, refPoints, scenario)
302
+ * or any app-specific state without forking the factory.
303
+ */
304
+ extraReducers?: ExtraReducers;
305
+ /**
306
+ * Caller-supplied middlewares appended after RTK defaults and the
307
+ * persistence middleware.
308
+ */
309
+ extraMiddleware?: ReadonlyArray<SlamAppMiddleware>;
310
+ /**
311
+ * Additional slice prefixes to persist beyond the framework built-ins
312
+ * (`gpsData`, `recording`). Pass caller-owned slice names derived from
313
+ * the slice itself — e.g. `slicePrefixOf(addRefPointEntry.type)` or
314
+ * `refPointsSlice.name` — never a hand-typed literal, so a rename can
315
+ * never silently drop the slice's actions from recordings.
316
+ */
317
+ persistedExtraPrefixes?: readonly string[];
318
+ /**
319
+ * Invoked when the persistence middleware fails to durably write an action.
320
+ */
321
+ onWriteFailure?: (error: Error) => void;
322
+ /**
323
+ * Disables RTK's expensive dev-only middleware (Serializable / Immutable
324
+ * checks). Default `true`; set `false` for high-throughput replay scenarios.
325
+ */
326
+ enableDevChecks?: boolean;
327
+ /**
328
+ * License key for the core library. Defaults to the bundled community key.
329
+ * Apps with a paid license override here. Validation always runs and throws
330
+ * on invalid / expired / empty keys.
331
+ *
332
+ * @see EULA.md §3 — License Key
333
+ */
334
+ licenseKey?: string;
335
+ /**
336
+ * Optional overrides for the tracking-quality reporter
337
+ * (matrix-history size, residual window, thresholds, etc.).
338
+ *
339
+ * @see docs/2026-05-16-tracking-quality-metrics-plan.md
340
+ */
341
+ trackingQualityOptions?: Partial<TrackingQualityOptions>;
342
+ }
343
+ /**
344
+ * Combined root state: the framework's base state plus any caller-supplied
345
+ * extras. Generic so consumers get exact typing for the slices they add.
346
+ */
347
+ type SlamAppCombinedState<ExtraReducers extends ReducersMapObject = Record<never, never>> = SlamAppRootState & { [K in keyof ExtraReducers]: ExtraReducers[K] extends Reducer<infer S> ? S : never };
348
+ /**
349
+ * The store object returned by {@link createSlamAppStore}.
350
+ *
351
+ * Wraps RTK's store and adds storage-delegation helpers so consumers can
352
+ * issue frame / metadata writes without holding a separate handle to the
353
+ * `StorageBackend`.
354
+ */
355
+ interface SlamAppStore<ExtraReducers extends ReducersMapObject = Record<string, never>> {
356
+ getState: () => SlamAppCombinedState<ExtraReducers>;
357
+ dispatch: ReturnType<typeof configureStore>['dispatch'];
358
+ subscribe: (listener: () => void) => () => void;
359
+ /** Persist a captured camera frame via the configured backend. */
360
+ writeFrame: (blob: Blob, index: number) => Promise<void>;
361
+ /** Persist session metadata (`session.json`) via the configured backend. */
362
+ writeSessionMetadata: (metadata: SessionMetadata) => Promise<void>;
363
+ }
364
+ /**
365
+ * Build a Redux store wired with library + recording slices, persistence
366
+ * middleware, and any caller-supplied extras. See module docstring for the
367
+ * design rationale.
368
+ */
369
+ declare function createSlamAppStore<ExtraReducers extends ReducersMapObject = Record<string, never>>(options: SlamAppStoreOptions<ExtraReducers>): SlamAppStore<ExtraReducers>;
370
+ //#endregion
371
+ export { resetTrackingQuality as A, computeResidualConsensus as C, firstAgreementReached as D, degradedCountUpdated as E, snapshotsTrimmed as F, trackingQualityReducer as I, CombinedRootState as L, selectRecentAlignments as M, selectTrackingQuality as N, matrixDelta as O, snapshotPushed as P, computeGpsVsFusedDivergence as S, createTrackingQualityListenerMiddleware as T, TrackingQualityState as _, SlamAppStoreOptions as a, computeCoverage as b, CompassAgreementResult as c, DEFAULT_TRACKING_QUALITY_OPTIONS as d, GpsAccuracyResult as f, TrackingQualitySliceState as g, TrackingQualityReport as h, SlamAppStore as i, selectFirstAgreementObservationIndex as j, reportUpdated as k, ConvergenceResult as l, TrackingQualityOptions as m, SlamAppMiddleware as n, createSlamAppStore as o, ResidualConsensusResult as p, SlamAppRootState as r, AlignmentSnapshot as s, SlamAppCombinedState as t, CoverageResult as u, computeCompassAgreement as v, computeTrackingQualityReport as w, computeGpsAccuracy as x, computeConvergence as y };
@@ -1,4 +1,4 @@
1
- import { i as DepthSample, t as ARPose } from "./ar-types-B-ORgk6Z.js";
1
+ import { i as DepthSample, t as ARPose } from "./ar-types-Ck8LEq8k.js";
2
2
 
3
3
  //#region ../src/ar/depth-sampler.d.ts
4
4
  /**
@@ -0,0 +1,114 @@
1
+ import { n as SessionMetadata } from "./opfs-storage-DOPkzgeT.js";
2
+
3
+ //#region ../src/storage/file-system.d.ts
4
+ /**
5
+ * Reset module state - exported for testing only
6
+ * @internal
7
+ */
8
+ declare function resetStorageState(): void;
9
+ /**
10
+ * Reset session-level state for a new recording.
11
+ *
12
+ * Clears scenario/session names and OPFS session handles, but preserves
13
+ * `storageInitialized` and the OPFS root so `initStorage()` doesn't need
14
+ * to be called again.
15
+ */
16
+ declare function resetForNewSession(): void;
17
+ /**
18
+ * Result of write access verification.
19
+ */
20
+ interface WriteAccessResult {
21
+ /** Whether write access was successfully verified */
22
+ success: boolean;
23
+ /** Error message if write verification failed */
24
+ error?: string;
25
+ }
26
+ /**
27
+ * Verify that we have actual write access to a directory.
28
+ */
29
+ declare function verifyWriteAccess(dirHandle: FileSystemDirectoryHandle): Promise<WriteAccessResult>;
30
+ /**
31
+ * Initialize storage using OPFS.
32
+ *
33
+ * @returns List of existing scenario folder names
34
+ * @throws Error if OPFS is not supported
35
+ */
36
+ declare function initStorage(): Promise<string[]>;
37
+ /**
38
+ * Create a new session folder.
39
+ *
40
+ * When scenarioName is provided, creates under the scenario directory
41
+ * (legacy scenario layout for the recorder). When omitted, creates under
42
+ * the flat sessions/ directory.
43
+ *
44
+ * @param scenarioName - Optional name of the scenario (omit for flat layout)
45
+ */
46
+ declare function startSession(scenarioName?: string): Promise<{
47
+ scenarioPath: string;
48
+ sessionPath: string;
49
+ }>;
50
+ /**
51
+ * List all session names.
52
+ * When scenarioName is provided, lists sessions under that scenario.
53
+ * Otherwise lists flat sessions.
54
+ */
55
+ declare function listSessions(scenarioName?: string): Promise<string[]>;
56
+ /**
57
+ * Write a Redux action to OPFS as a JSON file.
58
+ */
59
+ declare function writeAction(action: unknown, index: number): Promise<void>;
60
+ /**
61
+ * Write a captured frame image to OPFS.
62
+ */
63
+ declare function writeFrame(blob: Blob, index: number): Promise<void>;
64
+ /**
65
+ * Write session metadata (session.json) to OPFS.
66
+ */
67
+ declare function writeSessionMetadata(metadata: SessionMetadata): Promise<void>;
68
+ /**
69
+ * Get the current scenario handle (for loading prior sessions).
70
+ * Legacy: uses scenario directory layout.
71
+ */
72
+ declare function getCurrentScenarioHandle(): FileSystemDirectoryHandle | null;
73
+ /**
74
+ * Set the current scenario handle (when user selects a scenario).
75
+ * Legacy: uses scenario directory layout.
76
+ */
77
+ declare function setCurrentScenario(scenarioName: string): Promise<FileSystemDirectoryHandle | null>;
78
+ /**
79
+ * Ensure a scenario directory exists in OPFS, creating it if necessary.
80
+ * Legacy: used during OPFS recovery.
81
+ */
82
+ declare function ensureScenarioDirectory(scenarioName: string): Promise<FileSystemDirectoryHandle | null>;
83
+ /**
84
+ * Result of clearing the reference-point cache across all scenarios.
85
+ */
86
+ interface ClearRefPointsCacheResult {
87
+ /** Number of scenarios whose `refPoints/` directory was deleted. */
88
+ readonly scenariosCleared: number;
89
+ /** Total number of scenarios scanned (including ones with no cache). */
90
+ readonly scenariosScanned: number;
91
+ /** Names of scenarios that failed to clear, with reason. */
92
+ readonly errors: {
93
+ scenarioName: string;
94
+ reason: string;
95
+ }[];
96
+ }
97
+ /**
98
+ * Clear the cached `refPoints/` directory for every scenario in OPFS.
99
+ *
100
+ * Used by the "Clear Reference Point Cache" maintenance action so that the
101
+ * next scenario load re-imports ref points from the read folder's `*.zip`
102
+ * recordings (see ref-point-recovery in the recorder app).
103
+ *
104
+ * Failures for individual scenarios are collected in `errors` rather than
105
+ * aborting the whole operation — partial progress is still useful.
106
+ *
107
+ * @throws Error if OPFS storage is unavailable (e.g. `initStorage()` not yet
108
+ * called, or browser does not support OPFS). Surfacing this loudly avoids
109
+ * reporting a silent "0 scenarios cleared" success that would mask the
110
+ * real failure to the user.
111
+ */
112
+ declare function clearRefPointsCacheForAllScenarios(): Promise<ClearRefPointsCacheResult>;
113
+ //#endregion
114
+ export { getCurrentScenarioHandle as a, resetForNewSession as c, startSession as d, verifyWriteAccess as f, writeSessionMetadata as h, ensureScenarioDirectory as i, resetStorageState as l, writeFrame as m, WriteAccessResult as n, initStorage as o, writeAction as p, clearRefPointsCacheForAllScenarios as r, listSessions as s, ClearRefPointsCacheResult as t, setCurrentScenario as u };
@@ -0,0 +1,42 @@
1
+ //#region ../src/ar/frame-loop.d.ts
2
+ /**
3
+ * Per-frame callback registry. The WebXR session owns the single
4
+ * `renderer.setAnimationLoop(...)` hook for the page; components that need
5
+ * a per-frame tick register here and the session's `onXRFrame` invokes
6
+ * `runFrameUpdates(dt, elapsed)` once per frame.
7
+ *
8
+ * Plain functions, not a class: there is exactly one frame loop per session
9
+ * and singletons-of-singletons add no value. See
10
+ * `2026-05-13-ecs-migration-plan.md` for the design rationale and the
11
+ * rules new `FrameUpdate` bodies must follow (pure function of `dt` plus
12
+ * selectors; no direct Redux dispatch from inside a tick).
13
+ */
14
+ type FrameUpdate = (dt: number, elapsed: number) => void;
15
+ /**
16
+ * Register a per-frame callback. Returns an unregister function.
17
+ *
18
+ * Registration is idempotent — calling with the same `fn` twice is a no-op
19
+ * (it remains a single entry in the underlying `Set`).
20
+ */
21
+ declare function registerFrameUpdate(fn: FrameUpdate): () => void;
22
+ /**
23
+ * Invoke all registered callbacks. Called by the WebXR session's
24
+ * `onXRFrame` once per frame with the XR-derived `dt` (seconds since the
25
+ * previous frame; 0 on the first frame after a reset) and `elapsed`
26
+ * (seconds since the session started).
27
+ *
28
+ * The set is snapshotted before iterating so that
29
+ * `registerFrameUpdate` / unregister calls made by a handler during the
30
+ * same frame are deferred to the next tick. Iterating the live `Set`
31
+ * would otherwise skip a not-yet-visited entry that an earlier handler
32
+ * unregistered — a hard-to-debug source of non-determinism.
33
+ */
34
+ declare function runFrameUpdates(dt: number, elapsed: number): void;
35
+ /**
36
+ * Clear all registrations. Called from `resetWebXRState()` so a fresh
37
+ * session starts with an empty registry (any callbacks from the previous
38
+ * session are dropped along with their owning components).
39
+ */
40
+ declare function clearFrameUpdates(): void;
41
+ //#endregion
42
+ export { runFrameUpdates as i, clearFrameUpdates as n, registerFrameUpdate as r, FrameUpdate as t };
@@ -0,0 +1,58 @@
1
+ import * as THREE from "three";
2
+
3
+ //#region ../src/visualization/frustum-visibility.d.ts
4
+ /**
5
+ * Build (or refresh) a Three.js `Frustum` from `camera`'s current world
6
+ * transform and projection matrix. If `out` is provided, the frustum is
7
+ * written into it and returned; otherwise the module-level scratch frustum
8
+ * is used. Callers MUST treat the returned frustum as immutable until they
9
+ * next call this function or use one of the predicates without an injected
10
+ * frustum (both reuse the same scratch).
11
+ */
12
+ declare function buildCameraFrustum(camera: THREE.Camera, out?: THREE.Frustum): THREE.Frustum;
13
+ /**
14
+ * Strictly check whether a single world-space point lies inside all six
15
+ * frustum planes. Useful for HUD/follow logic where the caller already has
16
+ * the target world position and wants the cheapest possible check.
17
+ */
18
+ declare function isPointInCameraFrustum(camera: THREE.Camera, point: THREE.Vector3, frustum?: THREE.Frustum): boolean;
19
+ /**
20
+ * Check whether a world-space bounding sphere intersects the camera frustum
21
+ * (returns true even for partial overlap). Useful for callers that maintain
22
+ * a precomputed bounding sphere (LOD systems, instanced meshes, future
23
+ * `ArPowerSaver`).
24
+ */
25
+ declare function isSphereInCameraFrustum(camera: THREE.Camera, sphere: THREE.Sphere, frustum?: THREE.Frustum): boolean;
26
+ /**
27
+ * Check whether an `Object3D` is currently (at least partially) inside the
28
+ * camera frustum. The world bounding volume is resolved in tiers, cheapest
29
+ * first, so each common Three.js object type is gated by where it actually
30
+ * draws:
31
+ *
32
+ * 1. **Sprites** (`isSprite`) are screen-space billboards with no meaningful
33
+ * world geometry, so their world-space origin is tested as a point.
34
+ * 2. **Objects carrying an object-level `boundingSphere`** (`InstancedMesh`,
35
+ * `SkinnedMesh`, or any caller that pre-populates one) use that sphere,
36
+ * which reflects the instance spread / posed extent — unlike the base
37
+ * geometry sphere. It is computed lazily via `computeBoundingSphere()`
38
+ * when missing.
39
+ * 3. **Single renderables carrying a `geometry`** (`Mesh`, `Points`, `Line`,
40
+ * …) use the geometry's local bounding sphere × `matrixWorld`.
41
+ * 4. **Containers with children** (`Group`, `LOD`, …) use the world-space
42
+ * union of their descendants' bounding boxes (`Box3.setFromObject`),
43
+ * converted to a sphere.
44
+ * 5. **Truly empty / geometry-less objects** have no bounding volume and are
45
+ * treated as visible (`true`) — the conservative default for visibility
46
+ * gating.
47
+ *
48
+ * This deliberately avoids `Frustum.intersectsObject`, which unconditionally
49
+ * dereferences `object.geometry.boundingSphere` and therefore throws a
50
+ * `TypeError` for geometry-less objects such as `Group`s.
51
+ *
52
+ * Callers should ensure `object.updateMatrixWorld()` (with descendants, for
53
+ * containers: `updateMatrixWorld(true)`) has already run this frame (it
54
+ * normally has, via `renderer.render`).
55
+ */
56
+ declare function isObjectInCameraFrustum(camera: THREE.Camera, object: THREE.Object3D, frustum?: THREE.Frustum): boolean;
57
+ //#endregion
58
+ export { isSphereInCameraFrustum as i, isObjectInCameraFrustum as n, isPointInCameraFrustum as r, buildCameraFrustum as t };
@@ -1,4 +1,4 @@
1
- import { t as GpsCoord } from "./geo-types-Xx-LVcnw.js";
1
+ import { t as GpsCoord } from "./geo-types-CTuF23gG.js";
2
2
  import { LatLong, Matrix4, Vector3, fusedGpsFromOdom } from "gps-plus-slam-js";
3
3
 
4
4
  //#region ../src/utils/fused-path.d.ts
@@ -0,0 +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-BhfgNbdW.js";
2
+ export { H3_RESOLUTION, KnownGeoAnchor, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
@@ -1,12 +1,16 @@
1
1
  import { gridDisk, latLngToCell } from "h3-js";
2
- //#region ../src/ref-points/h3-ref-point.ts
2
+ //#region ../src/geo/h3-proximity.ts
3
3
  /**
4
- * H3 Reference Point Matching
4
+ * H3 Geo-Anchor Proximity Matching
5
5
  *
6
- * Utility for matching GPS positions to known reference points using
7
- * H3 hexagonal grid indices. Uses gridDisk(cell, 1) to create a ~65m
6
+ * Generic utility for matching GPS positions to known geo-anchored points
7
+ * using H3 hexagonal grid indices. Uses gridDisk(cell, 1) to create a ~65m
8
8
  * safe zone that absorbs GPS jitter of 3-10m.
9
9
  *
10
+ * Renamed from `ref-points/h3-ref-point.ts` in Iter 4 to drop recorder-only
11
+ * naming. The math is unchanged; only the public type/function names were
12
+ * generalised so the framework can serve any geo-anchored consumer.
13
+ *
10
14
  * @see docs/2026-03-08-ref-point-naming-investigation.md §6
11
15
  */
12
16
  /** H3 resolution 11: ~25m edge, ~65m gridDisk safe zone */
@@ -32,20 +36,20 @@ function approxDistanceMetres(lat1, lon1, lat2, lon2) {
32
36
  return R * Math.sqrt(dLat * dLat + x * x);
33
37
  }
34
38
  /**
35
- * Find the known reference point closest to the given GPS position,
39
+ * Find the known geo-anchor closest to the given GPS position,
36
40
  * provided it falls within the gridDisk safe zone.
37
41
  *
38
42
  * The safe zone is the center cell plus its 6 neighbors (~65m radius),
39
- * which absorbs GPS jitter of 3-10m while keeping ref points >130m apart
43
+ * which absorbs GPS jitter of 3-10m while keeping anchors >130m apart
40
44
  * distinguishable.
41
45
  *
42
- * When multiple ref points have overlapping safe zones (65–130 m apart),
46
+ * When multiple anchors have overlapping safe zones (65–130 m apart),
43
47
  * the closest one by equirectangular distance is returned.
44
48
  */
45
- function findNearbyRefPoint(lat, lng, knownRefPoints) {
46
- if (knownRefPoints.length === 0) return;
49
+ function findNearbyGeoAnchor(lat, lng, knownAnchors) {
50
+ if (knownAnchors.length === 0) return;
47
51
  const safeZone = gridDisk(latLngToCell(lat, lng, 11), 1);
48
- const candidates = knownRefPoints.filter((rp) => safeZone.includes(rp.h3Index));
52
+ const candidates = knownAnchors.filter((rp) => safeZone.includes(rp.h3Index));
49
53
  if (candidates.length === 0) return void 0;
50
54
  if (candidates.length === 1) return candidates[0];
51
55
  return candidates.reduce((closest, rp) => {
@@ -57,7 +61,7 @@ function findNearbyRefPoint(lat, lng, knownRefPoints) {
57
61
  * (i.e., one falls within the other's gridDisk safe zone).
58
62
  * This is the canonical cross-session matching check.
59
63
  */
60
- function h3RefsMatch(h3a, h3b) {
64
+ function h3CellsMatch(h3a, h3b) {
61
65
  if (h3a === h3b) return true;
62
66
  return gridDisk(h3a, 1).includes(h3b);
63
67
  }
@@ -71,4 +75,4 @@ function isH3Index(id) {
71
75
  return H3_INDEX_PATTERN.test(id);
72
76
  }
73
77
  //#endregion
74
- export { H3_RESOLUTION, approxDistanceMetres, findNearbyRefPoint, gpsToH3, h3RefsMatch, isH3Index };
78
+ export { H3_RESOLUTION, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };
@@ -0,0 +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-BhfgNbdW.js";
2
+ export { H3_RESOLUTION, type KnownGeoAnchor, approxDistanceMetres, findNearbyGeoAnchor, gpsToH3, h3CellsMatch, isH3Index };