gps-plus-slam-app-framework 1.0.6 → 1.1.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 (198) hide show
  1. package/LICENSE +4 -4
  2. package/README.md +310 -115
  3. package/dist/accuracy-circles-DEKr0Hoh.d.ts +43 -0
  4. package/dist/{app-selectors-CHt7DLuq.d.ts → app-selectors-CR9OWodf.d.ts} +8 -10
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/capability-checker.d.ts +2 -0
  7. package/dist/ar/capability-checker.js +25 -0
  8. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
  10. package/dist/ar/chromium-camera-access-workaround.js +197 -8
  11. package/dist/ar/depth-sampler.d.ts +2 -2
  12. package/dist/ar/enable-gps-ar.d.ts +2 -0
  13. package/dist/ar/enable-gps-ar.js +137 -0
  14. package/dist/ar/frame-loop.d.ts +2 -0
  15. package/dist/ar/frame-loop.js +63 -0
  16. package/dist/ar/image-capture.d.ts +1 -1
  17. package/dist/ar/image-capture.js +1 -1
  18. package/dist/ar/index.d.ts +16 -12
  19. package/dist/ar/index.js +8 -4
  20. package/dist/ar/replay-scene.d.ts +1 -1
  21. package/dist/ar/scene-node-names.d.ts +1 -1
  22. package/dist/ar/webxr-session.d.ts +4 -4
  23. package/dist/ar/webxr-session.js +173 -93
  24. package/dist/ar/xr-camera-texture.d.ts +1 -1
  25. package/dist/ar/xr-error-handler.d.ts +1 -1
  26. package/dist/ar/xr-frame-loop.d.ts +2 -0
  27. package/dist/ar/xr-frame-loop.js +69 -0
  28. package/dist/ar-world-group-alignment-Dn4rQk_c.d.ts +43 -0
  29. package/dist/capability-checker-BTCmZRL4.d.ts +43 -0
  30. package/dist/chromium-camera-access-workaround-MifIwK9x.d.ts +137 -0
  31. package/dist/core/index.d.ts +2 -2
  32. package/dist/core/index.js +2 -2
  33. package/dist/create-slam-app-store-B76AGaI0.d.ts +371 -0
  34. package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-CqrkRr2N.d.ts} +1 -1
  35. package/dist/enable-gps-ar-DNbO0zbg.d.ts +95 -0
  36. package/dist/{file-system-DD5TczSO.d.ts → file-system-TIsDfamK.d.ts} +32 -2
  37. package/dist/frame-conversions-D2EYjeNa.d.ts +54 -0
  38. package/dist/frame-loop-BTeRpDm4.d.ts +47 -0
  39. package/dist/frustum-visibility-DGnJqls0.d.ts +58 -0
  40. package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-BdVHmDW9.d.ts} +1 -1
  41. package/dist/geo/h3-proximity.d.ts +1 -1
  42. package/dist/geo/index.d.ts +1 -1
  43. package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-clgzl8b5.d.ts} +16 -1
  44. package/dist/{gps-GKc6_Cly.d.ts → gps-B7AlMPz5.d.ts} +1 -1
  45. package/dist/gps-anchor-CISLPQIb.d.ts +59 -0
  46. package/dist/{gps-ar-pose-sampler-D_Zso40V.d.ts → gps-ar-pose-sampler-BypXZUqC.d.ts} +1 -1
  47. package/dist/{gps-event-coordinator-fwvmVfku.d.ts → gps-event-coordinator-BKVZbS1M.d.ts} +3 -3
  48. package/dist/gps-event-markers-BlHq3jYV.d.ts +103 -0
  49. package/dist/hit-test-reticle-BGXOxrUh.d.ts +45 -0
  50. package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-BXZUmj7j.d.ts} +1 -1
  51. package/dist/index--ldLph4V.d.ts +2 -0
  52. package/dist/index-22v8MXJX.d.ts +23 -0
  53. package/dist/index-BB2KEWbN2.d.ts +37 -0
  54. package/dist/index.d.ts +64 -52
  55. package/dist/index.js +21 -8
  56. package/dist/{leaflet-map-overlay-CQZcnxbc.d.ts → leaflet-map-overlay-jNt-kcUr.d.ts} +22 -11
  57. package/dist/{logger-B81iwxx0.d.ts → logger-BZ44Dhd3.d.ts} +7 -1
  58. package/dist/map-data-BGIBM5rv.d.ts +49 -0
  59. package/dist/map-overlay-draw-DcGs9UV3.d.ts +42 -0
  60. package/dist/{null-storage-backend-C0TiwwzQ.d.ts → null-storage-backend-BPQJSaMw.d.ts} +2 -2
  61. package/dist/{opfs-storage-backend-Gt3rd76D.d.ts → opfs-storage-backend-u4QKvgvP.d.ts} +2 -2
  62. package/dist/{permission-checker-Cu3nbH1S.d.ts → permission-checker-CfV7INCa.d.ts} +41 -1
  63. package/dist/persistence-middleware-xB5sTllJ.d.ts +55 -0
  64. package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-BSFpBSZH.d.ts} +10 -4
  65. package/dist/{recording-replayer-BFhFs7Mc.d.ts → recording-replayer-CTL2cUE0.d.ts} +2 -2
  66. package/dist/{recording-slice-BiktUIWF.d.ts → recording-slice-Cb888P6d.d.ts} +2 -2
  67. package/dist/{replay-engine-B88Va62x.d.ts → replay-engine-B-5GIZtc.d.ts} +1 -1
  68. package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DkNfldPh.d.ts} +3 -3
  69. package/dist/sensors/gps-error-handler.d.ts +1 -1
  70. package/dist/sensors/gps.d.ts +1 -1
  71. package/dist/sensors/gps.js +2 -2
  72. package/dist/sensors/index.d.ts +3 -3
  73. package/dist/sensors/index.js +1 -1
  74. package/dist/sensors/permission-checker.d.ts +2 -2
  75. package/dist/sensors/permission-checker.js +79 -1
  76. package/dist/session-disposers-M-oashRH.js +57 -0
  77. package/dist/state/app-selectors.d.ts +2 -2
  78. package/dist/state/app-selectors.js +33 -3
  79. package/dist/state/combined-root-state.d.ts +2 -0
  80. package/dist/state/combined-root-state.js +1 -0
  81. package/dist/state/create-slam-app-store.d.ts +1 -1
  82. package/dist/state/create-slam-app-store.js +27 -13
  83. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  84. package/dist/state/gps-event-coordinator.d.ts +1 -1
  85. package/dist/state/index.d.ts +17 -16
  86. package/dist/state/index.js +6 -4
  87. package/dist/state/persistence-middleware.d.ts +2 -2
  88. package/dist/state/persistence-middleware.js +43 -9
  89. package/dist/state/recording-options.d.ts +1 -1
  90. package/dist/state/recording-replayer.d.ts +2 -2
  91. package/dist/state/recording-slice.d.ts +1 -1
  92. package/dist/state/replay-engine.d.ts +1 -1
  93. package/dist/state/store-subscribers.d.ts +2 -2
  94. package/dist/state/store-subscribers.js +38 -12
  95. package/dist/state/subscribe-to-selector.d.ts +1 -1
  96. package/dist/state/tracking-quality.d.ts +2 -0
  97. package/dist/state/tracking-quality.js +723 -0
  98. package/dist/state/tracking-slice.d.ts +2 -0
  99. package/dist/state/tracking-slice.js +147 -0
  100. package/dist/state-BkIfpiyA.js +87 -0
  101. package/dist/storage/file-system-utils.d.ts +1 -1
  102. package/dist/storage/file-system.d.ts +2 -2
  103. package/dist/storage/file-system.js +52 -1
  104. package/dist/storage/index.d.ts +8 -8
  105. package/dist/storage/null-storage-backend.d.ts +1 -1
  106. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  107. package/dist/storage/opfs-storage.d.ts +1 -1
  108. package/dist/storage/storage-backend.d.ts +1 -1
  109. package/dist/storage/zip-export.d.ts +1 -1
  110. package/dist/storage/zip-reader.d.ts +1 -1
  111. package/dist/storage/zip-reader.js +8 -4
  112. package/dist/{storage-backend-yVnuWGTr.d.ts → storage-backend-yDSKafAQ.d.ts} +1 -1
  113. package/dist/{store-subscribers-DKKHld49.d.ts → store-subscribers-swlbtg_z.d.ts} +31 -6
  114. package/dist/{subscribe-to-selector-ghDKYtap.d.ts → subscribe-to-selector--KQpgLXG.d.ts} +1 -1
  115. package/dist/test-utils/browser-mocks.d.ts +1 -1
  116. package/dist/test-utils/zip-round-trip-helpers.d.ts +7 -1
  117. package/dist/test-utils/zip-round-trip-helpers.js +2 -1
  118. package/dist/tracking-slice-CDREeoZ_.d.ts +86 -0
  119. package/dist/types/ar-types.d.ts +1 -1
  120. package/dist/types/geo-types.d.ts +2 -2
  121. package/dist/types/index.d.ts +3 -3
  122. package/dist/utils/concurrency.d.ts +1 -1
  123. package/dist/utils/failure-tracker.d.ts +1 -1
  124. package/dist/utils/format-file-size.d.ts +1 -1
  125. package/dist/utils/fused-path.d.ts +1 -1
  126. package/dist/utils/index.d.ts +6 -6
  127. package/dist/utils/list-formatter.d.ts +1 -1
  128. package/dist/utils/logger.d.ts +1 -1
  129. package/dist/utils/logger.js +68 -8
  130. package/dist/visualization/accuracy-circles.d.ts +2 -0
  131. package/dist/visualization/accuracy-circles.js +57 -0
  132. package/dist/visualization/alignment-lerper.d.ts +1 -1
  133. package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
  134. package/dist/visualization/ar-world-group-alignment.js +51 -0
  135. package/dist/visualization/camera-follower.d.ts +1 -1
  136. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  137. package/dist/visualization/frame-conversions.d.ts +2 -0
  138. package/dist/visualization/frame-conversions.js +88 -0
  139. package/dist/visualization/frustum-visibility.d.ts +2 -0
  140. package/dist/visualization/frustum-visibility.js +139 -0
  141. package/dist/visualization/gps-anchor.d.ts +2 -0
  142. package/dist/visualization/gps-anchor.js +217 -0
  143. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  144. package/dist/visualization/gps-event-markers.d.ts +2 -2
  145. package/dist/visualization/gps-event-markers.js +69 -2
  146. package/dist/visualization/hit-test-reticle.d.ts +2 -0
  147. package/dist/visualization/hit-test-reticle.js +81 -0
  148. package/dist/visualization/index.d.ts +19 -12
  149. package/dist/visualization/index.js +9 -2
  150. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  151. package/dist/visualization/leaflet-map-overlay.js +33 -79
  152. package/dist/visualization/lerp-utils.d.ts +1 -1
  153. package/dist/visualization/map-data.d.ts +2 -0
  154. package/dist/visualization/map-data.js +33 -0
  155. package/dist/visualization/map-overlay-draw.d.ts +2 -0
  156. package/dist/visualization/map-overlay-draw.js +107 -0
  157. package/dist/visualization/map-overlay.d.ts +1 -1
  158. package/dist/visualization/three-dispose.d.ts +1 -1
  159. package/dist/visualization/vis-colors.d.ts +1 -1
  160. package/dist/webxr-nue-basis-BbdqhqY6.js +54 -0
  161. package/dist/{webxr-session-COlSrXdL.d.ts → webxr-session-BnArFCew.d.ts} +76 -14
  162. package/dist/xr-frame-loop-BKckC7xC.d.ts +70 -0
  163. package/dist/{zip-reader-KgJEXbca.d.ts → zip-reader-B2lzN8F5.d.ts} +5 -0
  164. package/package.json +16 -16
  165. package/dist/ar/tracking-state.d.ts +0 -2
  166. package/dist/ar/tracking-state.js +0 -164
  167. package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +0 -61
  168. package/dist/combined-root-state-B3SvPNjU.d.ts +0 -6
  169. package/dist/create-slam-app-store-DBm7g2Zd.d.ts +0 -85
  170. package/dist/gps-anchored-mesh-manager-CovAFK4g.d.ts +0 -56
  171. package/dist/gps-event-markers-sVyO9utS.d.ts +0 -67
  172. package/dist/persistence-middleware-z-75iisb.d.ts +0 -31
  173. package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
  174. package/dist/visualization/gps-anchored-mesh-manager.d.ts +0 -2
  175. package/dist/visualization/gps-anchored-mesh-manager.js +0 -128
  176. /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-CCZJf-Xb.d.ts} +0 -0
  177. /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-isPsQptb.d.ts} +0 -0
  178. /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-75WWa9Xb.d.ts} +0 -0
  179. /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-B-nS1sr6.d.ts} +0 -0
  180. /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-D4HYJNOj.d.ts} +0 -0
  181. /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-Bsmv53qw.d.ts} +0 -0
  182. /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-EDED3nvg.d.ts} +0 -0
  183. /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-D7ELffm0.d.ts} +0 -0
  184. /package/dist/{file-system-utils-BH8FdyIu.d.ts → file-system-utils-BH6uIs2i.d.ts} +0 -0
  185. /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-B7gJb3Md.d.ts} +0 -0
  186. /package/dist/{gps-compass-cubes-jRIJMOuh.d.ts → gps-compass-cubes-Ys6Hf1nc.d.ts} +0 -0
  187. /package/dist/{gps-error-handler-CH1S528z.d.ts → gps-error-handler-BLDhUlw5.d.ts} +0 -0
  188. /package/dist/{h3-proximity-GwesPTFT.d.ts → h3-proximity-DJnghtir.d.ts} +0 -0
  189. /package/dist/{lerp-utils-CZgQlrmu.d.ts → lerp-utils-DNcSmKVX.d.ts} +0 -0
  190. /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CsqcU4v5.d.ts} +0 -0
  191. /package/dist/{map-overlay-C9ifl_4-.d.ts → map-overlay-yrFsUtGv.d.ts} +0 -0
  192. /package/dist/{opfs-storage-Dlq1I_Gb.d.ts → opfs-storage-LsLY6VZV.d.ts} +0 -0
  193. /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-IHW7HR4S.d.ts} +0 -0
  194. /package/dist/{three-dispose-qt_r5wR1.d.ts → three-dispose-WTBAfaWU.d.ts} +0 -0
  195. /package/dist/{vis-colors-BKkdTIKb.d.ts → vis-colors-Djnyg_UH.d.ts} +0 -0
  196. /package/dist/{xr-camera-texture-BbukNE79.d.ts → xr-camera-texture-BU5YvKAM.d.ts} +0 -0
  197. /package/dist/{xr-error-handler-DTHfmvrl.d.ts → xr-error-handler-DcUFshNF.d.ts} +0 -0
  198. /package/dist/{zip-export-BjrQPv0X.d.ts → zip-export-C0_ErAqT.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { i as DepthSample, t as ARPose } from "../ar-types-B-ORgk6Z.js";
2
- import { t as CapturedImage } from "../image-capture-2pKjYUS6.js";
3
- import { A as setTrackingLostCallback, C as setArWorldGroup, D as setImageCaptureCallback, E as setFrameCallback, F as stopDepthCapture, I as stopImageCapture, M as shouldLogCameraAccessDiagnostic, N as startDepthCapture, O as setScene, P as startImageCapture, S as setArPose, T as setDepthCaptureCallback, _ as isWebXRSupported, a as extractPoseFromViewer, b as nueQuaternionToWebXR, c as getArWorldGroup, d as getDepthSampleCount, f as getImageCaptureFrameCount, g as initAR, h as getXrCameraFromPose, i as endARSession, j as setTrackingRecoveredCallback, k as setTrackingCallbacks, l as getCamera, m as getScene, n as buildSessionOptions, o as extractResetTransformData, p as getLiveCss3dManager, r as createSceneHierarchy, s as getArPose, t as applyAlignmentMatrix, u as getCurrentArPose, v as isXRCameraLike, w as setCamera, x as resetWebXRState, y as nuePositionToWebXR } from "../webxr-session-COlSrXdL.js";
4
- export { ARPose, CapturedImage, DepthSample, applyAlignmentMatrix, buildSessionOptions, createSceneHierarchy, endARSession, extractPoseFromViewer, extractResetTransformData, getArPose, getArWorldGroup, getCamera, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getScene, getXrCameraFromPose, initAR, isWebXRSupported, isXRCameraLike, nuePositionToWebXR, nueQuaternionToWebXR, resetWebXRState, setArPose, setArWorldGroup, setCamera, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setScene, setTrackingCallbacks, setTrackingLostCallback, setTrackingRecoveredCallback, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
1
+ import { i as DepthSample, t as ARPose } from "../ar-types-isPsQptb.js";
2
+ import { t as CapturedImage } from "../image-capture-BXZUmj7j.js";
3
+ import { A as setScene, C as resetWebXRState, D as setDepthCaptureCallback, E as setCamera, F as shouldLogCameraAccessDiagnostic, I as startDepthCapture, L as startImageCapture, M as setTrackingLostCallback, N as setTrackingRecoveredCallback, O as setFrameCallback, P as setTrackingStore, R as stopDepthCapture, S as nueQuaternionToWebXR, T as setArWorldGroup, _ as getXrCameraFromPose, a as createSceneHierarchy, b as isXRCameraLike, c as extractResetTransformData, d as getCamera, f as getCurrentArPose, g as getScene, h as getLiveCss3dManager, i as buildSessionOptions, j as setTrackingCallbacks, k as setImageCaptureCallback, l as getArPose, m as getImageCaptureFrameCount, n as TrackingSubscribableStore, o as endARSession, p as getDepthSampleCount, r as applyAlignmentMatrix, s as extractPoseFromViewer, t as SessionFeatureOptions, u as getArWorldGroup, v as initAR, w as setArPose, x as nuePositionToWebXR, y as isWebXRSupported, z as stopImageCapture } from "../webxr-session-BnArFCew.js";
4
+ export { ARPose, CapturedImage, DepthSample, SessionFeatureOptions, TrackingSubscribableStore, applyAlignmentMatrix, buildSessionOptions, createSceneHierarchy, endARSession, extractPoseFromViewer, extractResetTransformData, getArPose, getArWorldGroup, getCamera, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getScene, getXrCameraFromPose, initAR, isWebXRSupported, isXRCameraLike, nuePositionToWebXR, nueQuaternionToWebXR, resetWebXRState, setArPose, setArWorldGroup, setCamera, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setScene, setTrackingCallbacks, setTrackingLostCallback, setTrackingRecoveredCallback, setTrackingStore, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
@@ -2,9 +2,13 @@ import { createLogger } from "../utils/logger.js";
2
2
  import { CameraBlitCapture, computeCaptureSize } from "./camera-blit-capture.js";
3
3
  import { getLastDeviceOrientation } from "../state/gps-event-coordinator.js";
4
4
  import { DepthSampler } from "./depth-sampler.js";
5
+ import { clearFrameUpdates, runFrameUpdates } from "./frame-loop.js";
6
+ import { clearXrFrameUpdates, runXrFrameUpdates } from "./xr-frame-loop.js";
7
+ import { t as WEBXR_TO_NUE } from "../webxr-nue-basis-BbdqhqY6.js";
5
8
  import { DEFAULT_CAPTURE_CONFIG, ImageCaptureManager } from "./image-capture.js";
6
- import { TrackingStateManager } from "./tracking-state.js";
9
+ import { clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectTrackingPhase } from "../state/tracking-slice.js";
7
10
  import { acquireCameraTexture } from "./xr-camera-texture.js";
11
+ import { n as runSessionDisposers } from "../session-disposers-M-oashRH.js";
8
12
  import { DEFAULT_RECORDING_OPTIONS, validateArCrashIsolationOptions } from "../state/recording-options.js";
9
13
  import { SCENE_NODE } from "./scene-node-names.js";
10
14
  import { createCss3dRendererManager } from "../visualization/css3d-renderer-manager.js";
@@ -84,6 +88,13 @@ let scene = null;
84
88
  let camera = null;
85
89
  let xrSession = null;
86
90
  /**
91
+ * Monotonic time of the previous XR frame, in milliseconds (XR `time`
92
+ * argument). Reset to 0 by `resetWebXRState()` so the first frame of a
93
+ * new session sees `dt = 0` rather than a stale delta from the prior
94
+ * session.
95
+ */
96
+ let lastFrameTime = 0;
97
+ /**
87
98
  * Reset WebXR module state - exported for testing only.
88
99
  * @internal
89
100
  */
@@ -100,12 +111,20 @@ function resetWebXRState() {
100
111
  arWorldGroup = null;
101
112
  arPoseNode = null;
102
113
  latestArPose = null;
114
+ lastFrameTime = 0;
115
+ clearFrameUpdates();
116
+ clearXrFrameUpdates();
117
+ runSessionDisposers();
103
118
  imageCaptureManager = null;
104
119
  onImageCaptured = null;
105
120
  getScreenRotation = null;
106
121
  onCaptureFailed = null;
107
122
  onSuspiciousImage = null;
108
- trackingStateManager = null;
123
+ if (trackingPhaseUnsubscribe) {
124
+ trackingPhaseUnsubscribe();
125
+ trackingPhaseUnsubscribe = null;
126
+ }
127
+ trackingStore = null;
109
128
  onTrackingRestarted = null;
110
129
  onTrackingLost = null;
111
130
  onTrackingRecovered = null;
@@ -163,9 +182,22 @@ let onSuspiciousImage = null;
163
182
  */
164
183
  let getScreenRotation = null;
165
184
  /**
166
- * Tracking state manager (created when AR session starts with tracking callbacks)
185
+ * Redux store injected by the host (`setTrackingStore`). When present
186
+ * together with `onTrackingRestarted`, `onXRFrame` dispatches
187
+ * `poseReceived`/`poseLost`, the XR reference-space reset listener
188
+ * dispatches `originReset`, and a `subscribeToSelector` translation
189
+ * surface translates phase transitions back into the existing
190
+ * `onTrackingLost` / `onTrackingRestarted` / `onTrackingRecovered`
191
+ * callbacks. When the store is absent the tracking pipeline simply
192
+ * no-ops.
167
193
  */
168
- let trackingStateManager = null;
194
+ let trackingStore = null;
195
+ /**
196
+ * Unsubscribe handle returned by the phase subscription set up in
197
+ * `initAR`. Cleared in `resetWebXRState` and on session-end so we never
198
+ * leave dangling listeners on a stale store.
199
+ */
200
+ let trackingPhaseUnsubscribe = null;
169
201
  /**
170
202
  * Callback for when tracking restarts (set via setTrackingCallbacks)
171
203
  */
@@ -323,10 +355,14 @@ function getCurrentArPose() {
323
355
  * Extracted as a pure function for testability.
324
356
  *
325
357
  * @param rootElement - The DOM element for DOM overlay
358
+ * @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
359
+ * depth-sensing, camera-access)
360
+ * @param sessionFeatures - Opt-in standard WebXR features that are independent
361
+ * of crash isolation (currently `requestHitTest`)
326
362
  * @returns XRSessionInit options
327
363
  * @throws Error if rootElement is null
328
364
  */
329
- function buildSessionOptions(rootElement, isolationOptions = {}) {
365
+ function buildSessionOptions(rootElement, isolationOptions = {}, sessionFeatures = {}) {
330
366
  if (!rootElement) throw new Error("App root element not found");
331
367
  const normalizedOptions = validateArCrashIsolationOptions(isolationOptions);
332
368
  const optionalFeatures = [];
@@ -343,6 +379,7 @@ function buildSessionOptions(rootElement, isolationOptions = {}) {
343
379
  } });
344
380
  }
345
381
  if (normalizedOptions.enableCameraAccess) optionalFeatures.push("camera-access");
382
+ if (sessionFeatures.requestHitTest) optionalFeatures.push("hit-test");
346
383
  if (optionalFeatures.length > 0) sessionOptions.optionalFeatures = optionalFeatures;
347
384
  return sessionOptions;
348
385
  }
@@ -373,8 +410,18 @@ async function isWebXRSupported() {
373
410
  * basisChangeNode is a static scene-graph node that holds the WEBXR_TO_NUE
374
411
  * basis-change matrix permanently (matrixAutoUpdate=false). Moving it here
375
412
  * instead of composing it in applyAlignmentMatrix() keeps arWorldGroup's
376
- * local space as NUE: objects added directly to arWorldGroup can be placed
377
- * at [1,0,0]=North, [0,0,1]=East without any WebXR↔NUE conversion.
413
+ * local space in the **NUE axis convention** (X=North, Y=Up, Z=East), so no
414
+ * WebXR↔NUE swizzle is needed for children.
415
+ *
416
+ * CAUTION — two NUE frames: arWorldGroup's local space is the *AR-odometry*
417
+ * NUE frame, i.e. the **domain** of the alignment matrix, NOT the GPS-world
418
+ * NUE frame of the scene root. Only content authored in AR-odometry
419
+ * coordinates (e.g. the camera subtree) may be placed with raw local values.
420
+ * GPS-world content (a lat/lon → NUE point) is expressed in the scene-root
421
+ * frame and must be pre-multiplied by alignment⁻¹ before being used as a
422
+ * local position under arWorldGroup — see createGpsAnchor and the
423
+ * alignment-frame bug doc
424
+ * (GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md).
378
425
  *
379
426
  * - Recording: arpose stays at identity; WebXRManager writes to camera.
380
427
  * - Replay: arpose receives recorded odomPosition/odomRotation;
@@ -412,9 +459,13 @@ function createSceneHierarchy() {
412
459
  /**
413
460
  * Initialize the AR session and Three.js renderer.
414
461
  * @param container - DOM element to host the AR canvas and CSS3D overlay.
462
+ * @param isolationOptions - Crash-isolation diagnostic flags.
463
+ * @param sessionFeatures - Opt-in standard WebXR features (e.g.
464
+ * `requestHitTest`) forwarded to the session negotiation.
415
465
  */
416
- async function initAR(container, isolationOptions = {}) {
466
+ async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
417
467
  if (!navigator.xr) throw new Error("WebXR not available");
468
+ if (renderer || xrSession) throw new Error("AR session already initialized — call endARSession() before initAR() again");
418
469
  currentArCrashIsolationOptions = validateArCrashIsolationOptions(isolationOptions);
419
470
  renderer = new THREE.WebGLRenderer({
420
471
  antialias: true,
@@ -430,44 +481,23 @@ async function initAR(container, isolationOptions = {}) {
430
481
  arWorldGroup = hierarchy.arWorldGroup;
431
482
  arPoseNode = hierarchy.arpose;
432
483
  camera = hierarchy.camera;
433
- const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions);
484
+ const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions, sessionFeatures);
434
485
  xrSession = await navigator.xr.requestSession("immersive-ar", sessionOptions);
435
486
  xrSession.addEventListener("end", () => {
436
487
  log.info("Session ended");
437
- if (trackingStateManager) trackingStateManager.reset();
488
+ if (trackingStore) trackingStore.dispatch(resetTracking());
438
489
  xrSession = null;
439
490
  latestArPose = null;
440
491
  });
441
492
  await renderer.xr.setSession(xrSession);
442
- if (onTrackingRestarted) {
443
- trackingStateManager = new TrackingStateManager({
444
- onTrackingLost: () => {
445
- log.warn("Tracking lost");
446
- latestArPose = null;
447
- onTrackingLost?.();
448
- },
449
- onTrackingRestarted: (payload) => {
450
- log.info("Tracking restarted (origin reset)");
451
- onTrackingRestarted?.(payload);
452
- },
453
- onTrackingRecovered: () => {
454
- log.info("Tracking recovered (same coordinate frame)");
455
- onTrackingRecovered?.();
456
- },
457
- getDeviceOrientation: () => {
458
- const orientation = getLastDeviceOrientation();
459
- return {
460
- alpha: orientation?.alpha ?? 0,
461
- beta: orientation?.beta ?? 0,
462
- gamma: orientation?.gamma ?? 0,
463
- absolute: orientation?.absolute ?? false
464
- };
465
- }
466
- });
493
+ if (onTrackingRestarted && trackingStore) {
494
+ const store = trackingStore;
495
+ store.dispatch(resetTracking());
496
+ trackingPhaseUnsubscribe = subscribeToTrackingPhase(store);
467
497
  const referenceSpace = renderer.xr.getReferenceSpace();
468
498
  if (referenceSpace) referenceSpace.addEventListener("reset", (event) => {
469
499
  const transformData = extractResetTransformData(event);
470
- trackingStateManager?.markOriginReset(transformData);
500
+ store.dispatch(originReset(transformData));
471
501
  log.warn("XR reference space reset detected", transformData ? "(transform available)" : "(no transform)");
472
502
  });
473
503
  }
@@ -480,12 +510,72 @@ async function initAR(container, isolationOptions = {}) {
480
510
  log.info("AR session started");
481
511
  }
482
512
  /**
483
- * Update tracking state manager with current pose state.
513
+ * Snapshot the current `DeviceOrientation` (with documented fallback
514
+ * defaults) for inclusion in `poseReceived` payloads.
515
+ */
516
+ function snapshotDeviceOrientation() {
517
+ const orientation = getLastDeviceOrientation();
518
+ return {
519
+ alpha: orientation?.alpha ?? 0,
520
+ beta: orientation?.beta ?? 0,
521
+ gamma: orientation?.gamma ?? 0,
522
+ absolute: orientation?.absolute ?? false
523
+ };
524
+ }
525
+ /**
526
+ * Wire the tracking-slice → host-callbacks translation. The subscriber
527
+ * runs synchronously inside each `dispatch`, so the host callbacks fire
528
+ * in the same order as a direct invocation would.
529
+ *
530
+ * Translation rules (locked in by tracking-slice tests):
531
+ * - `tracking → lost`: clear `latestArPose` (drops in-flight GPS events)
532
+ * and call `onTrackingLost?.()`.
533
+ * - `lost → tracking` with `lastRestartedPayload !== null` (Case 2):
534
+ * call `onTrackingRestarted?.(payload)` then dispatch
535
+ * `clearLastRestartedPayload` so a subsequent loss cycle starts clean.
536
+ * - `lost → tracking` with payload null (Case 1: seamless recovery):
537
+ * call `onTrackingRecovered?.()`.
538
+ * - `initializing → tracking`: no callback (initial acquisition is not
539
+ * a restart — same behaviour as the manager).
540
+ */
541
+ function subscribeToTrackingPhase(store) {
542
+ let prev = "initializing";
543
+ return store.subscribe(() => {
544
+ const next = selectTrackingPhase(store.getState());
545
+ if (next === prev) return;
546
+ const previous = prev;
547
+ prev = next;
548
+ if (previous === "tracking" && next === "lost") {
549
+ log.warn("Tracking lost");
550
+ latestArPose = null;
551
+ onTrackingLost?.();
552
+ return;
553
+ }
554
+ if (previous === "lost" && next === "tracking") {
555
+ const payload = selectLastRestartedPayload(store.getState());
556
+ if (payload !== null) {
557
+ log.info("Tracking restarted (origin reset)");
558
+ onTrackingRestarted?.(payload);
559
+ store.dispatch(clearLastRestartedPayload());
560
+ } else {
561
+ log.info("Tracking recovered (same coordinate frame)");
562
+ onTrackingRecovered?.();
563
+ }
564
+ }
565
+ });
566
+ }
567
+ /**
568
+ * Dispatch the per-frame `poseReceived` / `poseLost` action into the
569
+ * tracking slice. No-op when no store is bound or when tracking wiring
570
+ * was not requested.
484
571
  */
485
572
  function updateTrackingState(arPose) {
486
- if (!trackingStateManager) return;
487
- if (arPose) trackingStateManager.onPoseReceived(arPose);
488
- else trackingStateManager.onPoseLost();
573
+ if (!trackingStore || !onTrackingRestarted) return;
574
+ if (arPose) trackingStore.dispatch(poseReceived({
575
+ pose: arPose,
576
+ sensorOrientation: snapshotDeviceOrientation()
577
+ }));
578
+ else trackingStore.dispatch(poseLost());
489
579
  }
490
580
  /**
491
581
  * Called each XR frame
@@ -497,6 +587,17 @@ function onXRFrame(time, frame) {
497
587
  const pose = frame.getViewerPose(referenceSpace);
498
588
  const arPose = extractPoseFromViewer(pose);
499
589
  updateTrackingState(arPose);
590
+ const dt = lastFrameTime === 0 ? 0 : (time - lastFrameTime) / 1e3;
591
+ const elapsed = time / 1e3;
592
+ lastFrameTime = time;
593
+ runFrameUpdates(dt, elapsed);
594
+ if (xrSession) runXrFrameUpdates({
595
+ frame,
596
+ referenceSpace,
597
+ session: xrSession,
598
+ dt,
599
+ elapsed
600
+ });
500
601
  if (arPose) latestArPose = arPose;
501
602
  latestCameraTexture = null;
502
603
  if (currentArCrashIsolationOptions.enableCameraTextureAcquisition) {
@@ -607,41 +708,6 @@ function setArPose(a) {
607
708
  arPoseNode = a;
608
709
  }
609
710
  /**
610
- * Constant matrix converting WebXR local-floor coordinates to the internal
611
- * NUE (North-Up-East) convention.
612
- *
613
- * WebXR: X=East, Y=Up, Z=South (right-handed, toward viewer)
614
- * NUE: X=North, Y=Up, Z=East (right-handed)
615
- *
616
- * Mapping: NUE_X = -WebXR_Z, NUE_Y = WebXR_Y, NUE_Z = WebXR_X
617
- *
618
- * Row-major:
619
- * [ 0 0 -1 0 ]
620
- * [ 0 1 0 0 ]
621
- * [ 1 0 0 0 ]
622
- * [ 0 0 0 1 ]
623
- *
624
- * Stored column-major (Three.js / gl-matrix convention).
625
- */
626
- const WEBXR_TO_NUE = new THREE.Matrix4().fromArray([
627
- 0,
628
- 0,
629
- 1,
630
- 0,
631
- 0,
632
- 1,
633
- 0,
634
- 0,
635
- -1,
636
- 0,
637
- 0,
638
- 0,
639
- 0,
640
- 0,
641
- 0,
642
- 1
643
- ]);
644
- /**
645
711
  * Apply an alignment matrix to the AR world group.
646
712
  *
647
713
  * The alignment matrix maps odometry positions in NUE space
@@ -703,27 +769,20 @@ function nueQuaternionToWebXR(nue) {
703
769
  /**
704
770
  * End the current XR session and clean up all resources.
705
771
  *
706
- * Stops the animation loop, disposes the WebGL renderer, removes the
707
- * canvas from the DOM, and tears down the CSS3D overlay. This is the
708
- * production cleanup path call it when the AR experience is finished.
772
+ * Stops the animation loop, ends the XR session, then delegates the full
773
+ * teardown to {@link resetWebXRState} so every module-level reference is
774
+ * cleared (renderer/scene/camera, image-capture, depth, the tracking-phase
775
+ * subscription, the frame-update registry, diagnostics, blit resources).
776
+ * This is the production cleanup path — call it when the AR experience is
777
+ * finished.
709
778
  */
710
779
  async function endARSession() {
711
780
  if (renderer) renderer.setAnimationLoop(null);
712
- if (xrSession) {
713
- await xrSession.end();
714
- xrSession = null;
715
- }
716
- latestArPose = null;
717
- if (css3dManager) {
718
- css3dManager.dispose();
719
- css3dManager = null;
720
- }
721
- if (renderer) {
722
- if (renderer.domElement.parentElement) renderer.domElement.parentElement.removeChild(renderer.domElement);
723
- renderer.dispose();
724
- renderer = null;
781
+ try {
782
+ if (xrSession) await xrSession.end();
783
+ } finally {
784
+ resetWebXRState();
725
785
  }
726
- cleanupBlitResources();
727
786
  }
728
787
  /**
729
788
  * Set up image capture callbacks.
@@ -755,6 +814,10 @@ function startImageCapture(config, resolutionDivisor = 1) {
755
814
  log.warn("Cannot start image capture - callbacks not set");
756
815
  return;
757
816
  }
817
+ if (imageCaptureManager || blitCapture) {
818
+ log.warn("Image capture already running - stopping previous session");
819
+ stopImageCapture();
820
+ }
758
821
  const callbacks = {
759
822
  getCurrentPose: getCurrentArPose,
760
823
  getScreenRotation,
@@ -802,6 +865,23 @@ function getImageCaptureFrameCount() {
802
865
  return imageCaptureManager?.getFrameCount() ?? 0;
803
866
  }
804
867
  /**
868
+ * Inject the Redux store used by the tracking-state slice pipeline.
869
+ *
870
+ * MUST be called before `initAR()` whenever the host also wires tracking
871
+ * callbacks via `setTrackingCallbacks`. Without a store the tracking
872
+ * pipeline silently no-ops.
873
+ *
874
+ * @param store — any store satisfying {@link TrackingSubscribableStore}.
875
+ * `null` clears the binding (useful for teardown in tests).
876
+ */
877
+ function setTrackingStore(store) {
878
+ if (trackingPhaseUnsubscribe) {
879
+ trackingPhaseUnsubscribe();
880
+ trackingPhaseUnsubscribe = null;
881
+ }
882
+ trackingStore = store;
883
+ }
884
+ /**
805
885
  * Set up tracking state callbacks.
806
886
  * Call this BEFORE initAR() to enable tracking restart detection.
807
887
  *
@@ -887,4 +967,4 @@ function getLiveCss3dManager() {
887
967
  return css3dManager;
888
968
  }
889
969
  //#endregion
890
- export { applyAlignmentMatrix, buildSessionOptions, createSceneHierarchy, endARSession, extractPoseFromViewer, extractResetTransformData, getArPose, getArWorldGroup, getCamera, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getScene, getXrCameraFromPose, initAR, isWebXRSupported, isXRCameraLike, nuePositionToWebXR, nueQuaternionToWebXR, resetWebXRState, setArPose, setArWorldGroup, setCamera, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setScene, setTrackingCallbacks, setTrackingLostCallback, setTrackingRecoveredCallback, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
970
+ export { applyAlignmentMatrix, buildSessionOptions, createSceneHierarchy, endARSession, extractPoseFromViewer, extractResetTransformData, getArPose, getArWorldGroup, getCamera, getCurrentArPose, getDepthSampleCount, getImageCaptureFrameCount, getLiveCss3dManager, getScene, getXrCameraFromPose, initAR, isWebXRSupported, isXRCameraLike, nuePositionToWebXR, nueQuaternionToWebXR, resetWebXRState, setArPose, setArWorldGroup, setCamera, setDepthCaptureCallback, setFrameCallback, setImageCaptureCallback, setScene, setTrackingCallbacks, setTrackingLostCallback, setTrackingRecoveredCallback, setTrackingStore, shouldLogCameraAccessDiagnostic, startDepthCapture, startImageCapture, stopDepthCapture, stopImageCapture };
@@ -1,2 +1,2 @@
1
- import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-BbukNE79.js";
1
+ import { i as acquireCameraTexture, n as RendererLike, r as XRCameraLike, t as CameraTextureResult } from "../xr-camera-texture-BU5YvKAM.js";
2
2
  export { CameraTextureResult, RendererLike, XRCameraLike, acquireCameraTexture };
@@ -1,2 +1,2 @@
1
- import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-DTHfmvrl.js";
1
+ import { n as XR_ERROR_MESSAGE_UNKNOWN, r as getXrErrorMessage, t as XR_ERROR_MESSAGES } from "../xr-error-handler-DcUFshNF.js";
2
2
  export { XR_ERROR_MESSAGES, XR_ERROR_MESSAGE_UNKNOWN, getXrErrorMessage };
@@ -0,0 +1,2 @@
1
+ import { a as runXrFrameUpdates, i as registerXrFrameUpdate, n as XrFrameUpdate, r as clearXrFrameUpdates, t as XrFrameContext } from "../xr-frame-loop-BKckC7xC.js";
2
+ export { XrFrameContext, XrFrameUpdate, clearXrFrameUpdates, registerXrFrameUpdate, runXrFrameUpdates };
@@ -0,0 +1,69 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ //#region ../src/ar/xr-frame-loop.ts
3
+ /**
4
+ * Per-frame **XR-access** callback registry — the safe seam that lets app
5
+ * code run standard WebXR work (hit-test, light estimation, depth, the WebXR
6
+ * Anchors API, …) without the framework wrapping each capability.
7
+ *
8
+ * The framework owns the single `renderer.setAnimationLoop(onXRFrame)` hook,
9
+ * and the live `XRFrame` never leaves `onXRFrame`. The plain `FrameUpdate`
10
+ * registry (`frame-loop.ts`) only passes `(dt, elapsed)`, which is enough for
11
+ * pose-free ticks but cannot drive hit-test. This registry additionally hands
12
+ * the callback the live `frame`, `referenceSpace`, and `session`.
13
+ *
14
+ * SAFETY CONTRACT (non-negotiable): `frame` / `referenceSpace` / `session` are
15
+ * valid **only synchronously inside the callback**. The `XRFrame` is
16
+ * use-after-frame-unsafe — storing it and reading it on a later tick throws or
17
+ * crashes. Passing them as arguments (never a stashable `getXrFrame()` getter)
18
+ * makes correct use the easy path and stashing the awkward one. Do NOT retain
19
+ * `ctx` or its fields beyond the callback's synchronous execution.
20
+ *
21
+ * See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md`
22
+ * §6.2/§6.3 (option H-A2) for the design rationale.
23
+ */
24
+ const log = createLogger("XrFrameLoop");
25
+ const updates = /* @__PURE__ */ new Set();
26
+ /**
27
+ * Register a per-frame XR-access callback. Returns an unregister function.
28
+ *
29
+ * Registration is idempotent — registering the same `fn` twice is a no-op
30
+ * (it remains a single entry in the underlying `Set`).
31
+ */
32
+ function registerXrFrameUpdate(fn) {
33
+ updates.add(fn);
34
+ return () => {
35
+ updates.delete(fn);
36
+ };
37
+ }
38
+ /**
39
+ * Invoke all registered XR-access callbacks. Called by the WebXR session's
40
+ * `onXRFrame` once per frame, only when a live `frame` / `referenceSpace` /
41
+ * `session` are all available.
42
+ *
43
+ * The set is snapshotted before iterating so that
44
+ * `registerXrFrameUpdate` / unregister calls made by a handler during the
45
+ * same frame are deferred to the next tick — mirroring `runFrameUpdates`.
46
+ *
47
+ * Each callback is invoked in its own `try/catch`: this registry is the public
48
+ * app seam, so a bug in one app-registered callback (which throws every frame)
49
+ * must not abort the remaining callbacks nor propagate up through `onXRFrame`
50
+ * and kill the scene render for the whole session. Failures are logged and the
51
+ * loop continues — mirroring `runFrameUpdates`.
52
+ */
53
+ function runXrFrameUpdates(ctx) {
54
+ const snapshot = Array.from(updates);
55
+ for (const fn of snapshot) try {
56
+ fn(ctx);
57
+ } catch (error) {
58
+ log.error("A registered XrFrameUpdate threw; continuing the loop", error);
59
+ }
60
+ }
61
+ /**
62
+ * Clear all registrations. Called from `resetWebXRState()` so a fresh
63
+ * session starts with an empty registry.
64
+ */
65
+ function clearXrFrameUpdates() {
66
+ updates.clear();
67
+ }
68
+ //#endregion
69
+ export { clearXrFrameUpdates, registerXrFrameUpdate, runXrFrameUpdates };
@@ -0,0 +1,43 @@
1
+ import { t as SubscribableStore } from "./subscribe-to-selector--KQpgLXG.js";
2
+ import * as THREE from "three";
3
+
4
+ //#region ../src/visualization/ar-world-group-alignment.d.ts
5
+ interface ArWorldGroupAlignmentOptions {
6
+ /** The store whose alignment selector drives the lerper. */
7
+ readonly store: SubscribableStore;
8
+ /** The AR world group whose `.matrix` is lerped toward each alignment. */
9
+ readonly arWorldGroup: THREE.Object3D;
10
+ /**
11
+ * Lerp speed multiplier forwarded to {@link createAlignmentLerper}
12
+ * (default matches the lerper's own `DEFAULT_LERP_RATE`).
13
+ */
14
+ readonly lerpRate?: number;
15
+ }
16
+ interface ArWorldGroupAlignmentHandle {
17
+ /** Stop the subscription and the per-frame update. */
18
+ dispose(): void;
19
+ }
20
+ /**
21
+ * Begin applying the store's (lerped) alignment to `arWorldGroup`. Returns a
22
+ * handle whose `dispose()` removes both the store subscription and the
23
+ * per-frame update.
24
+ *
25
+ * Disposal is tied to the AR session lifecycle: the binding registers itself
26
+ * with the session-disposer registry that `resetWebXRState()` flushes on
27
+ * teardown, so a caller can `enableArWorldGroupAlignment(...)` once and never
28
+ * hold the handle — the binding cannot outlive the session (which is what two
29
+ * apps independently leaked: the store subscription survives a teardown because
30
+ * `clearFrameUpdates` only drops the per-frame tick). Because `initAR()` throws
31
+ * while a prior session is live, every restart passes through that flush. The
32
+ * handle is still returned for callers that want to stop alignment mid-session,
33
+ * and is idempotent + self-deregistering so a manual `dispose()` and the
34
+ * teardown flush never double-run. See
35
+ * gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-06-08-arworldgroup-alignment-session-scoped-disposal.md.
36
+ *
37
+ * Double-drive is still the caller's concern: the recorder owns its own lerper
38
+ * and must NOT also call this (it would double-lerp the same group). It does not
39
+ * call this helper, so flushing the registry on teardown never affects it.
40
+ */
41
+ declare function enableArWorldGroupAlignment(options: ArWorldGroupAlignmentOptions): ArWorldGroupAlignmentHandle;
42
+ //#endregion
43
+ export { ArWorldGroupAlignmentOptions as n, enableArWorldGroupAlignment as r, ArWorldGroupAlignmentHandle as t };
@@ -0,0 +1,43 @@
1
+ //#region ../src/ar/capability-checker.d.ts
2
+ /**
3
+ * Pure capability-gating helpers, promoted from the AnchorStarter app so the
4
+ * minimal AR example and AnchorStarter share the *decision* and *message*
5
+ * without sharing app-specific copy.
6
+ *
7
+ * The async feature probing (`isWebXRSupported()` / geolocation availability)
8
+ * stays in each app's `main.ts`; only the browser-free decision and the
9
+ * user-facing message live here so they can be unit-tested without a browser.
10
+ *
11
+ * See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md` §6.4.
12
+ */
13
+ /** The two capabilities every GPS+AR demo requires to run. */
14
+ interface CapabilitySupport {
15
+ /** Browser supports a WebXR `immersive-ar` session. */
16
+ readonly webxr: boolean;
17
+ /** Geolocation API is available. */
18
+ readonly geolocation: boolean;
19
+ }
20
+ /** Options that let an app flavour the otherwise-neutral gating message. */
21
+ interface CapabilityMessageOptions {
22
+ /**
23
+ * App-specific "what this demo lets you do" phrase, appended as
24
+ * `…outdoors, to try ${contextLabel}.`. Omit for a neutral message that
25
+ * ends at `…outdoors.`. Example: `'the persistent-anchor flow'`.
26
+ */
27
+ readonly contextLabel?: string;
28
+ }
29
+ /** True only when both AR and GPS are available — the demo can run. */
30
+ declare function isFullySupported(support: CapabilitySupport): boolean;
31
+ /**
32
+ * Build the capability-gated message shown when the demo cannot run. Names
33
+ * exactly which capabilities are missing so the user understands *why*, and
34
+ * always points them at the supported environment (an AR phone, outdoors).
35
+ * Returns `null` when everything is supported (no message needed).
36
+ *
37
+ * The trailing "to try …" clause is app-specific and supplied via
38
+ * `options.contextLabel`; without it the message ends neutrally at
39
+ * "…outdoors.".
40
+ */
41
+ declare function capabilityMessage(support: CapabilitySupport, options?: CapabilityMessageOptions): string | null;
42
+ //#endregion
43
+ export { isFullySupported as i, CapabilitySupport as n, capabilityMessage as r, CapabilityMessageOptions as t };