gps-plus-slam-app-framework 1.10.0 → 1.11.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 (203) hide show
  1. package/README.md +14 -14
  2. package/dist/{absolute-orientation-C_IxDR_j.d.ts → absolute-orientation-Dj1One3y.d.ts} +1 -1
  3. package/dist/{accuracy-circles-BE7tbvYV.d.ts → accuracy-circles-BHz7yVIL.d.ts} +1 -1
  4. package/dist/{app-selectors-CV7ggGoo.d.ts → app-selectors-DG0ZqaeI.d.ts} +1 -1
  5. package/dist/ar/ar-crash-isolation.d.ts +2 -0
  6. package/dist/ar/ar-crash-isolation.js +40 -0
  7. package/dist/ar/capability-checker.d.ts +1 -1
  8. package/dist/ar/capture-motion-gate.d.ts +2 -0
  9. package/dist/{capture-motion-gate-D1eyGC_o.js → ar/capture-motion-gate.js} +25 -2
  10. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  11. package/dist/ar/chromium-camera-access-workaround.js +1 -1
  12. package/dist/ar/depth-grid-lookup.d.ts +1 -1
  13. package/dist/ar/depth-occluder.d.ts +1 -1
  14. package/dist/ar/depth-occluder.js +1 -1
  15. package/dist/ar/depth-sampler.d.ts +2 -2
  16. package/dist/ar/depth-unprojection.d.ts +1 -1
  17. package/dist/ar/enable-gps-ar.d.ts +1 -1
  18. package/dist/ar/enable-gps-ar.js +2 -2
  19. package/dist/ar/frame-loop.d.ts +1 -1
  20. package/dist/ar/image-capture.d.ts +1 -1
  21. package/dist/ar/image-capture.js +1 -1
  22. package/dist/ar/image-quality.d.ts +2 -2
  23. package/dist/ar/image-quality.js +136 -4
  24. package/dist/ar/index.d.ts +28 -26
  25. package/dist/ar/index.js +15 -14
  26. package/dist/ar/occlusion-mesh-worker.d.ts +1 -1
  27. package/dist/ar/occupancy-grid.d.ts +1 -1
  28. package/dist/ar/occupancy-grid.js +1 -1
  29. package/dist/ar/occupancy-mesher.d.ts +1 -1
  30. package/dist/ar/occupancy-mesher.js +1 -1
  31. package/dist/ar/qr/index.d.ts +11 -0
  32. package/dist/ar/qr/index.js +11 -0
  33. package/dist/ar/{planar-pnp.d.ts → qr/planar-pnp.d.ts} +1 -1
  34. package/dist/ar/{planar-pnp.js → qr/planar-pnp.js} +1 -1
  35. package/dist/ar/qr/qr-debug-view.d.ts +2 -0
  36. package/dist/ar/{qr-debug-view.js → qr/qr-debug-view.js} +2 -2
  37. package/dist/ar/{qr-derived-pose.d.ts → qr/qr-derived-pose.d.ts} +1 -1
  38. package/dist/ar/{qr-derived-pose.js → qr/qr-derived-pose.js} +2 -2
  39. package/dist/ar/{qr-detection-controller.d.ts → qr/qr-detection-controller.d.ts} +1 -1
  40. package/dist/ar/qr/qr-detection-controller.js +2 -0
  41. package/dist/ar/{qr-frontend.d.ts → qr/qr-frontend.d.ts} +1 -1
  42. package/dist/ar/{qr-frontend.js → qr/qr-frontend.js} +1 -1
  43. package/dist/ar/{qr-pose.d.ts → qr/qr-pose.d.ts} +1 -1
  44. package/dist/ar/{qr-pose.js → qr/qr-pose.js} +1 -1
  45. package/dist/ar/qr/qr-size-depth-context.d.ts +3 -0
  46. package/dist/ar/{qr-size-depth-context.js → qr/qr-size-depth-context.js} +4 -4
  47. package/dist/ar/{qr-size-measurer.d.ts → qr/qr-size-measurer.d.ts} +1 -1
  48. package/dist/ar/qr/qr-size-measurer.js +2 -0
  49. package/dist/ar/replay-scene.d.ts +1 -1
  50. package/dist/ar/replay-scene.js +15 -13
  51. package/dist/ar/webxr-session.d.ts +4 -4
  52. package/dist/ar/webxr-session.js +2 -2
  53. package/dist/ar/xr-camera-texture.d.ts +1 -1
  54. package/dist/ar/xr-frame-loop.d.ts +1 -1
  55. package/dist/ar/xr-frame-loop.js +1 -1
  56. package/dist/ar-crash-isolation-DlXCGzMf.d.ts +52 -0
  57. package/dist/{ar-world-group-alignment-B6XiA1cT.d.ts → ar-world-group-alignment-BdS_h8Nf.d.ts} +2 -2
  58. package/dist/{capability-checker-DEo-ktWI.d.ts → capability-checker-BZ3_x1YL.d.ts} +1 -1
  59. package/dist/capture-motion-gate-CPwLekNf.d.ts +120 -0
  60. package/dist/{chromium-camera-access-workaround-DCd8SP2N.d.ts → chromium-camera-access-workaround-BHR4clTE.d.ts} +1 -1
  61. package/dist/{create-slam-app-store-CH31erpP.d.ts → create-slam-app-store-DFm5K9Ob.d.ts} +13 -4
  62. package/dist/{create-slam-app-store-DLv5ZwqU.js → create-slam-app-store-oK9bnqyI.js} +8 -6
  63. package/dist/{depth-grid-lookup-DY6zYTLx.d.ts → depth-grid-lookup-CCuzlqUo.d.ts} +1 -1
  64. package/dist/{depth-sampler-txG8evgQ.d.ts → depth-sampler-CS2jAFw4.d.ts} +2 -2
  65. package/dist/{depth-unprojection-JKkeqJrV.d.ts → depth-unprojection-ET3Gwedf.d.ts} +1 -1
  66. package/dist/{enable-gps-ar-BbNWyqp5.d.ts → enable-gps-ar-D_xEHzh5.d.ts} +10 -5
  67. package/dist/{file-system-utils-CfLTj5Ra.d.ts → file-system-utils-BHI0rXr9.d.ts} +1 -1
  68. package/dist/geo/h3-proximity.d.ts +1 -1
  69. package/dist/geo/h3-proximity.js +1 -1
  70. package/dist/geo/index.d.ts +1 -1
  71. package/dist/{geodesic-angle-HXBWiXiI.js → geodesic-angle-CZ_Ow90I.js} +2 -2
  72. package/dist/{gps-ar-pose-sampler-BOOV0jJk.d.ts → gps-ar-pose-sampler-BuuUuK-D.d.ts} +1 -1
  73. package/dist/{gps-event-coordinator-03rVh_us.d.ts → gps-event-coordinator-Cruoe5I-.d.ts} +4 -4
  74. package/dist/{gps-event-markers-BsC2Ynlh.d.ts → gps-event-markers-y2b2aVl6.d.ts} +35 -1
  75. package/dist/{h3-proximity-pFluGfwL.d.ts → h3-proximity-BaAulrnM.d.ts} +1 -1
  76. package/dist/{image-capture-Cr_rljeV.d.ts → image-capture-BevgKpoO.d.ts} +6 -6
  77. package/dist/{image-capture-BUqqsOi3.js → image-capture-C4DwxC10.js} +3 -3
  78. package/dist/{image-quality-BjBLoTec.d.ts → image-quality--HLuyJ2r.d.ts} +44 -2
  79. package/dist/{index-C2Bo0buN.d.ts → index-BWVwd9Zw.d.ts} +9 -71
  80. package/dist/{index-CuHaGY0F.d.ts → index-ClmnNJ6-.d.ts} +1 -1
  81. package/dist/index-Dxb6ATNK.d.ts +64 -0
  82. package/dist/index.d.ts +55 -55
  83. package/dist/index.js +23 -23
  84. package/dist/{leaflet-map-overlay-BRkUhZMD.js → leaflet-map-overlay-CTcTVmL1.js} +1 -1
  85. package/dist/{leaflet-map-overlay-QhF199BW.d.ts → leaflet-map-overlay-CksetpTt.d.ts} +1 -1
  86. package/dist/{map-data-D8sSxpOr.d.ts → map-data-D8cCqpOM.d.ts} +2 -2
  87. package/dist/{map-data-D3oE8AQP.js → map-data-DkhsYDGM.js} +1 -1
  88. package/dist/{map-overlay-draw-1E61FLKs.d.ts → map-overlay-draw-Cy-klqoA.d.ts} +1 -1
  89. package/dist/{median-Bla_W50f.js → median-DDBrZGP0.js} +2 -2
  90. package/dist/{occlusion-mesh-CZ4i_SW2.d.ts → occlusion-mesh-BWe2PrVi.d.ts} +22 -3
  91. package/dist/{occupancy-grid-f0I07UxI.d.ts → occupancy-grid-CYBdmMOo.d.ts} +2 -2
  92. package/dist/{persistence-middleware-CI4Cetnl.d.ts → persistence-middleware-Cun4aF5M.d.ts} +12 -2
  93. package/dist/{planar-pnp-CPLhg5MX.d.ts → planar-pnp-ByxwDEyM.d.ts} +2 -2
  94. package/dist/{ar-D-03Kso-.js → qr-CTp5xI83.js} +11 -25
  95. package/dist/{qr-debug-view-BmbWo06j.d.ts → qr-debug-view-CGlITU09.d.ts} +2 -2
  96. package/dist/{qr-derived-pose-DEYT85ul.d.ts → qr-derived-pose-DZPxZd9C.d.ts} +3 -3
  97. package/dist/{qr-detected-slice-CPEkGbxh.d.ts → qr-detected-slice-mfyxD2-M.d.ts} +9 -8
  98. package/dist/{qr-detection-controller-IcMRpTjV.d.ts → qr-detection-controller-C5NII-vs.d.ts} +4 -4
  99. package/dist/{qr-detection-controller-bRTJXC6G.js → qr-detection-controller-DXc1yHRf.js} +3 -3
  100. package/dist/{qr-frontend-61SKPSyU.d.ts → qr-frontend-QzKZSqB-.d.ts} +2 -2
  101. package/dist/{qr-pose-CCTF1PP0.d.ts → qr-pose-21fRDcFO.d.ts} +1 -1
  102. package/dist/{qr-pose-aggregation-H-O_Jjz9.js → qr-pose-aggregation-Cp0Mtg_j.js} +4 -4
  103. package/dist/{qr-pose-aggregation-BL6gfl0s.d.ts → qr-pose-aggregation-mABSN0AW.d.ts} +2 -2
  104. package/dist/{qr-size-depth-context-DJsYqbqY.d.ts → qr-size-depth-context-CMDv6T29.d.ts} +3 -3
  105. package/dist/{qr-size-measurer-Bl_i-umk.d.ts → qr-size-measurer-rVFf4AdM.d.ts} +5 -5
  106. package/dist/{qr-size-measurer-BVOIg-He.js → qr-size-measurer-zZB9S1va.js} +4 -4
  107. package/dist/{recording-replayer-DyIN13nG.d.ts → recording-replayer-qT2EM1Qs.d.ts} +1 -1
  108. package/dist/{recording-slice-DziOl7P5.d.ts → recording-slice-DXw5-UvO.d.ts} +10 -5
  109. package/dist/{replay-engine-DAymjsd1.d.ts → replay-engine-B1H_kLhK.d.ts} +1 -1
  110. package/dist/{replay-scene-CZdWgcpc.d.ts → replay-scene-BJeO36YT.d.ts} +12 -5
  111. package/dist/sensors/absolute-orientation.d.ts +1 -1
  112. package/dist/sensors/absolute-orientation.js +1 -1
  113. package/dist/sensors/gps.d.ts +1 -1
  114. package/dist/sensors/index.d.ts +3 -3
  115. package/dist/sensors/permission-checker.d.ts +1 -1
  116. package/dist/{session-disposers-BZS-hi8u.js → session-disposers-B7hUN90w.js} +1 -1
  117. package/dist/state/app-selectors.d.ts +1 -1
  118. package/dist/state/combined-root-state.d.ts +1 -1
  119. package/dist/state/create-slam-app-store.d.ts +1 -1
  120. package/dist/state/create-slam-app-store.js +1 -1
  121. package/dist/state/gps-ar-pose-sampler.d.ts +2 -2
  122. package/dist/state/gps-event-coordinator.d.ts +2 -2
  123. package/dist/state/gps-event-coordinator.js +1 -1
  124. package/dist/state/index.d.ts +17 -18
  125. package/dist/state/index.js +4 -5
  126. package/dist/state/persistence-middleware.d.ts +2 -2
  127. package/dist/state/persistence-middleware.js +24 -15
  128. package/dist/state/qr-detected-slice.d.ts +3 -3
  129. package/dist/state/qr-detected-slice.js +8 -8
  130. package/dist/state/recording-replayer.d.ts +2 -2
  131. package/dist/state/recording-replayer.js +1 -1
  132. package/dist/state/recording-slice.d.ts +1 -1
  133. package/dist/state/replay-engine.d.ts +1 -1
  134. package/dist/state/store-subscribers.d.ts +2 -2
  135. package/dist/state/store-subscribers.js +1 -1
  136. package/dist/state/subscribe-to-selector.d.ts +1 -1
  137. package/dist/state/tracking-quality.d.ts +1 -1
  138. package/dist/state/tracking-quality.js +2 -2
  139. package/dist/state/tracking-slice.d.ts +1 -1
  140. package/dist/{state-U45ioVyB.js → state-CDF5LvsO.js} +1 -2
  141. package/dist/storage/file-system-utils.d.ts +1 -1
  142. package/dist/storage/file-system-utils.js +1 -1
  143. package/dist/storage/index.d.ts +2 -2
  144. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  145. package/dist/storage/zip-coverage-embed.js +1 -1
  146. package/dist/{store-subscribers-DtwakLrQ.d.ts → store-subscribers-CWHToX6N.d.ts} +2 -2
  147. package/dist/{subscribe-to-selector-Cgo9JTfS.d.ts → subscribe-to-selector-j-eOYybQ.d.ts} +1 -1
  148. package/dist/test-utils/browser-mocks.d.ts +1 -1
  149. package/dist/{tracking-slice-Bt2OEQhA.d.ts → tracking-slice-BAbcZK-Y.d.ts} +3 -2
  150. package/dist/types/ar-types.d.ts +1 -1
  151. package/dist/types/index.d.ts +1 -1
  152. package/dist/utils/index.js +1 -1
  153. package/dist/visualization/accuracy-circles.d.ts +1 -1
  154. package/dist/visualization/accuracy-circles.js +1 -1
  155. package/dist/visualization/alignment-lerper.d.ts +1 -1
  156. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  157. package/dist/visualization/ar-world-group-alignment.js +2 -2
  158. package/dist/visualization/camera-follower.d.ts +1 -1
  159. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  160. package/dist/visualization/gps-anchor.js +2 -2
  161. package/dist/visualization/gps-event-markers.d.ts +2 -2
  162. package/dist/visualization/gps-event-markers.js +27 -6
  163. package/dist/visualization/index.d.ts +13 -14
  164. package/dist/visualization/index.js +4 -5
  165. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  166. package/dist/visualization/leaflet-map-overlay.js +1 -1
  167. package/dist/visualization/map-data.d.ts +1 -1
  168. package/dist/visualization/map-data.js +1 -1
  169. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  170. package/dist/visualization/occluder-mesh-driver.d.ts +1 -1
  171. package/dist/visualization/occluder-mesh-driver.js +1 -1
  172. package/dist/visualization/occlusion-mesh.d.ts +2 -2
  173. package/dist/visualization/occlusion-mesh.js +28 -3
  174. package/dist/visualization/three-dispose.d.ts +1 -1
  175. package/dist/visualization/vis-colors.d.ts +1 -1
  176. package/dist/{webxr-session-CGFZ429B.d.ts → webxr-session-DefwObIu.d.ts} +144 -194
  177. package/dist/{webxr-session-DWKUsCN9.js → webxr-session-OyR3YbTq.js} +151 -281
  178. package/dist/{xr-frame-loop-25gM4B0K.d.ts → xr-frame-loop-BiZvgqP7.d.ts} +1 -1
  179. package/dist/{zip-coverage-embed-BrodRLq0.d.ts → zip-coverage-embed-CyoX7XL7.d.ts} +1 -1
  180. package/package.json +4 -2
  181. package/dist/ar/qr-debug-view.d.ts +0 -2
  182. package/dist/ar/qr-detection-controller.js +0 -2
  183. package/dist/ar/qr-size-depth-context.d.ts +0 -3
  184. package/dist/ar/qr-size-measurer.js +0 -2
  185. package/dist/capture-motion-gate-BmjiWUJv.d.ts +0 -24
  186. package/dist/map-overlay-0yJOcEVO.d.ts +0 -153
  187. package/dist/recording-options-Cai9D14V.d.ts +0 -700
  188. package/dist/state/recording-options.d.ts +0 -2
  189. package/dist/state/recording-options.js +0 -620
  190. package/dist/visualization/map-overlay.d.ts +0 -2
  191. package/dist/visualization/map-overlay.js +0 -241
  192. /package/dist/{alignment-lerper-B4COh_0p.d.ts → alignment-lerper-DAHicRON.d.ts} +0 -0
  193. /package/dist/{ar-types-CBafnxhE.d.ts → ar-types-D6PYS4bc.d.ts} +0 -0
  194. /package/dist/{camera-follower-B6zA3dy9.d.ts → camera-follower-CO_ibF2o.d.ts} +0 -0
  195. /package/dist/{css3d-renderer-manager-DbrSY7Mh.d.ts → css3d-renderer-manager-CJjHtCMT.d.ts} +0 -0
  196. /package/dist/{device-orientation-cache-BjEaEAmh.js → device-orientation-cache-j-kLP8he.js} +0 -0
  197. /package/dist/{frame-loop-7HigCj7m.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
  198. /package/dist/{gps-CX5b9g3s.d.ts → gps-CnhYrJCa.d.ts} +0 -0
  199. /package/dist/{occupancy-mesher-BqnRd10x.d.ts → occupancy-mesher-B4Bv4BTk.d.ts} +0 -0
  200. /package/dist/{permission-checker-D1C5zvo-.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
  201. /package/dist/{three-dispose-C9mdpSRX.d.ts → three-dispose-C3mnKQR5.d.ts} +0 -0
  202. /package/dist/{vis-colors-DjStl_tD.d.ts → vis-colors-CoSpbZEO.d.ts} +0 -0
  203. /package/dist/{xr-camera-texture-C6Z4d7RR.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_AR_CRASH_ISOLATION, validateArCrashIsolationOptions } from "./ar/ar-crash-isolation.js";
1
2
  import { createLogger } from "./utils/logger.js";
2
3
  import { CameraBlitCapture, computeAspectFitSize, computeCaptureSize } from "./ar/camera-blit-capture.js";
3
4
  import { applyChromiumProjectionLayerWorkaround } from "./ar/chromium-camera-access-workaround.js";
@@ -5,12 +6,11 @@ import { DepthSampler, wrapXRDepthInfo } from "./ar/depth-sampler.js";
5
6
  import { WEBXR_TO_NUE } from "./ar/webxr-nue-basis.js";
6
7
  import { clearFrameUpdates, runFrameUpdates } from "./ar/frame-loop.js";
7
8
  import { clearXrFrameUpdates, runXrFrameUpdates } from "./ar/xr-frame-loop.js";
8
- import { n as ImageCaptureManager, t as DEFAULT_CAPTURE_CONFIG } from "./image-capture-BUqqsOi3.js";
9
+ import { n as ImageCaptureManager, t as DEFAULT_CAPTURE_CONFIG } from "./image-capture-C4DwxC10.js";
9
10
  import { clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectTrackingPhase } from "./state/tracking-slice.js";
10
11
  import { acquireCameraTexture } from "./ar/xr-camera-texture.js";
11
- import { n as runSessionDisposers } from "./session-disposers-BZS-hi8u.js";
12
- import { t as getLastDeviceOrientation } from "./device-orientation-cache-BjEaEAmh.js";
13
- import { DEFAULT_RECORDING_OPTIONS, validateArCrashIsolationOptions } from "./state/recording-options.js";
12
+ import { n as runSessionDisposers } from "./session-disposers-B7hUN90w.js";
13
+ import { t as getLastDeviceOrientation } from "./device-orientation-cache-j-kLP8he.js";
14
14
  import { SCENE_NODE } from "./ar/scene-node-names.js";
15
15
  import { createCss3dRendererManager } from "./visualization/css3d-renderer-manager.js";
16
16
  import * as THREE from "three";
@@ -132,6 +132,7 @@ function createRgbLookup(pixels, width, height) {
132
132
  * The camera's WORLD transform is the GPS-aligned world pose.
133
133
  */
134
134
  const log = createLogger("WebXR");
135
+ /** Runtime guard for `XRView.camera` candidates (finite, positive dimensions). */
135
136
  function isXRCameraLike(value) {
136
137
  if (typeof value !== "object" || value === null) return false;
137
138
  const camera = value;
@@ -181,6 +182,67 @@ function getXrCameraFromPose(pose) {
181
182
  function shouldLogCameraAccessDiagnostic(pose, alreadyLogged, captureActive) {
182
183
  return pose !== null && !alreadyLogged && captureActive;
183
184
  }
185
+ /**
186
+ * Extract the reset transform from an XRReferenceSpaceEvent-like object.
187
+ *
188
+ * Distinguishes three cases per OdometryTrackingRestartedPayload semantics:
189
+ * - Transform property missing (older browsers) → returns `undefined`
190
+ * - Transform property present but null (runtime can't determine delta) → returns `null`
191
+ * - Transform property present with data → returns `ResetTransformData`
192
+ *
193
+ * This is a pure function extracted for testability.
194
+ *
195
+ * @param event - The event object, cast to a record with an optional transform property
196
+ * @returns ResetTransformData, null, or undefined
197
+ */
198
+ function extractResetTransformData(event) {
199
+ if (!("transform" in event)) return;
200
+ const transform = event.transform;
201
+ if (!transform) return null;
202
+ const pos = transform.position;
203
+ const ori = transform.orientation;
204
+ return {
205
+ position: [
206
+ pos.x,
207
+ pos.y,
208
+ pos.z
209
+ ],
210
+ orientation: [
211
+ ori.x,
212
+ ori.y,
213
+ ori.z,
214
+ ori.w
215
+ ]
216
+ };
217
+ }
218
+ /**
219
+ * Extract pose data from an XRViewerPose.
220
+ * Returns null if pose or views are unavailable.
221
+ *
222
+ * This is a pure function extracted for testability.
223
+ *
224
+ * @param pose - The XRViewerPose from frame.getViewerPose()
225
+ * @returns ARPose with position and orientation, or null
226
+ */
227
+ function extractPoseFromViewer(pose) {
228
+ if (!pose) return null;
229
+ const view = pose.views[0];
230
+ if (!view) return null;
231
+ const { position, orientation } = view.transform;
232
+ return {
233
+ position: {
234
+ x: position.x,
235
+ y: position.y,
236
+ z: position.z
237
+ },
238
+ orientation: {
239
+ x: orientation.x,
240
+ y: orientation.y,
241
+ z: orientation.z,
242
+ w: orientation.w
243
+ }
244
+ };
245
+ }
184
246
  let renderer = null;
185
247
  let scene = null;
186
248
  let camera = null;
@@ -207,7 +269,6 @@ function resetWebXRState() {
207
269
  camera = null;
208
270
  xrSession = null;
209
271
  arWorldGroup = null;
210
- arPoseNode = null;
211
272
  latestArPose = null;
212
273
  lastFrameTime = 0;
213
274
  clearFrameUpdates();
@@ -218,6 +279,7 @@ function resetWebXRState() {
218
279
  getScreenRotation = null;
219
280
  onCaptureFailed = null;
220
281
  onSuspiciousImage = null;
282
+ imageQualityAnalyzer = null;
221
283
  if (trackingPhaseUnsubscribe) {
222
284
  trackingPhaseUnsubscribe();
223
285
  trackingPhaseUnsubscribe = null;
@@ -251,7 +313,7 @@ function resetWebXRState() {
251
313
  getCameraTextureNullCount = 0;
252
314
  latestCameraWidth = 0;
253
315
  latestCameraHeight = 0;
254
- currentArCrashIsolationOptions = { ...DEFAULT_RECORDING_OPTIONS.arCrashIsolation };
316
+ currentArCrashIsolationOptions = { ...DEFAULT_AR_CRASH_ISOLATION };
255
317
  cleanupBlitResources();
256
318
  }
257
319
  /**
@@ -261,12 +323,6 @@ function resetWebXRState() {
261
323
  */
262
324
  let arWorldGroup = null;
263
325
  /**
264
- * The arpose Object3D — intermediate node between arWorldGroup and camera.
265
- * During recording it stays at identity; during replay it receives recorded
266
- * odomPosition/odomRotation from the store subscriber.
267
- */
268
- let arPoseNode = null;
269
- /**
270
326
  * Stores the latest raw AR pose from WebXR (updated every frame).
271
327
  * This is read by the GPS callback to create paired GPS+AR events.
272
328
  */
@@ -276,39 +332,39 @@ let latestArPose = null;
276
332
  */
277
333
  let imageCaptureManager = null;
278
334
  /**
279
- * Callback for when an image is captured (set via setImageCaptureCallback)
335
+ * Callback for when an image is captured (initAR `callbacks.imageCapture`)
280
336
  */
281
337
  let onImageCaptured = null;
282
338
  /**
283
- * Callback for when image capture fails (set via setImageCaptureCallback)
339
+ * Callback for when image capture fails (initAR `callbacks.imageCapture`)
284
340
  */
285
341
  let onCaptureFailed = null;
286
342
  /**
287
343
  * Callback for when a captured image appears suspicious/black
288
- * (set via setImageCaptureCallback)
344
+ * (initAR `callbacks.imageCapture`)
289
345
  */
290
346
  let onSuspiciousImage = null;
291
347
  /**
292
- * Screen rotation getter (set via setImageCaptureCallback)
348
+ * Screen rotation getter (initAR `callbacks.imageCapture`)
293
349
  */
294
350
  let getScreenRotation = null;
295
351
  /**
296
- * Off-thread image-quality analyzer (set via {@link setImageQualityAnalyzer}).
352
+ * Off-thread image-quality analyzer (initAR `callbacks.imageCapture.qualityAnalyzer`).
297
353
  * Device-specific (a Web Worker in the recorder), so it is INJECTED rather than
298
354
  * built here. Forwarded to `ImageCaptureManager` as its `analyzeFrame` callback;
299
355
  * the manager only invokes it when `config.qualityFilter.enabled`. `null` ⇒ the
300
- * legacy save-immediately path.
356
+ * legacy save-immediately path. Per-session: cleared by resetWebXRState() like
357
+ * every other init-time callback (the host re-passes it each session).
301
358
  */
302
359
  let imageQualityAnalyzer = null;
303
360
  /**
304
- * Redux store injected by the host (`setTrackingStore`). When present
305
- * together with `onTrackingRestarted`, `onXRFrame` dispatches
306
- * `poseReceived`/`poseLost`, the XR reference-space reset listener
307
- * dispatches `originReset`, and a `subscribeToSelector` translation
308
- * surface translates phase transitions back into the existing
309
- * `onTrackingLost` / `onTrackingRestarted` / `onTrackingRecovered`
310
- * callbacks. When the store is absent the tracking pipeline simply
311
- * no-ops.
361
+ * Redux store injected by the host (initAR `callbacks.tracking.store`;
362
+ * swapped mid-session via {@link rebindTrackingStore}). When present,
363
+ * `onXRFrame` dispatches `poseReceived`/`poseLost`, the XR reference-space
364
+ * reset listener dispatches `originReset`, and a subscription translates
365
+ * phase transitions back into the host's `onTrackingLost` /
366
+ * `onTrackingRestarted` / `onTrackingRecovered` callbacks. When the store
367
+ * is absent the tracking pipeline simply no-ops.
312
368
  */
313
369
  let trackingStore = null;
314
370
  /**
@@ -318,22 +374,22 @@ let trackingStore = null;
318
374
  */
319
375
  let trackingPhaseUnsubscribe = null;
320
376
  /**
321
- * Callback for when tracking restarts (set via setTrackingCallbacks)
377
+ * Callback for when tracking restarts (initAR `callbacks.tracking.onRestarted`)
322
378
  */
323
379
  let onTrackingRestarted = null;
324
380
  /**
325
- * Callback for when tracking is lost (set via setTrackingLostCallback)
381
+ * Callback for when tracking is lost (initAR `callbacks.tracking.onLost`)
326
382
  * Field Test Readiness Issue #3: Provide user feedback when tracking is lost
327
383
  */
328
384
  let onTrackingLost = null;
329
385
  /**
330
386
  * Callback for when tracking recovers seamlessly without origin reset (Case 1).
331
- * Set via setTrackingRecoveredCallback.
387
+ * initAR `callbacks.tracking.onRecovered`.
332
388
  */
333
389
  let onTrackingRecovered = null;
334
390
  /**
335
391
  * Host callback fired exactly once whenever the XRSession ends — on BOTH the
336
- * app-initiated and the system-initiated path (set via setSessionEndCallback).
392
+ * app-initiated and the system-initiated path (initAR `callbacks.onSessionEnd`).
337
393
  * Cleared by resetWebXRState() like every other module-level callback.
338
394
  */
339
395
  let onSessionEnd = null;
@@ -350,7 +406,7 @@ let endRequestedByApp = false;
350
406
  */
351
407
  let depthSampler = null;
352
408
  /**
353
- * Callback for when a depth sample is captured (set via setDepthCaptureCallback)
409
+ * Callback for when a depth sample is captured (initAR `callbacks.depth`)
354
410
  */
355
411
  let onDepthCaptured = null;
356
412
  /**
@@ -410,7 +466,7 @@ let cameraFrameBlit = null;
410
466
  * so 1024 is the safe default. Raise per-consumer via
411
467
  * `startCameraFrameCapture({ captureSize })`.
412
468
  *
413
- * @see GpsPlusSlamJs_Docs/docs/2026-06-17-qr-size-accuracy-and-thin-demo-plan.md (WS-C)
469
+ * @see GpsPlusSlamJs_Docs/docs/2026-06-17-1020-qr-size-accuracy-and-thin-demo-plan.md (WS-C)
414
470
  */
415
471
  const DEFAULT_CAMERA_FRAME_CAPTURE_SIZE = 1024;
416
472
  /**
@@ -434,7 +490,7 @@ let cameraFrameCaptureSize = DEFAULT_CAMERA_FRAME_CAPTURE_SIZE;
434
490
  * SCOPE note in `camera-frame-source.ts`.
435
491
  */
436
492
  let cameraFrameSource = null;
437
- /** Callback for each throttled camera RGBA frame (set via setCameraFrameCallback). */
493
+ /** Callback for each throttled camera RGBA frame (initAR `callbacks.cameraFrame`). */
438
494
  let onCameraFrame = null;
439
495
  /**
440
496
  * Latest WebXR camera texture, updated each frame when camera-access is enabled.
@@ -459,7 +515,7 @@ let cameraAccessLoggedOnce = false;
459
515
  */
460
516
  let getCameraTextureNullCount = 0;
461
517
  const GET_CAMERA_TEXTURE_LOG_THRESHOLD = 5;
462
- let currentArCrashIsolationOptions = { ...DEFAULT_RECORDING_OPTIONS.arCrashIsolation };
518
+ let currentArCrashIsolationOptions = { ...DEFAULT_AR_CRASH_ISOLATION };
463
519
  /**
464
520
  * Dispose blit capture resources and clear the cached camera texture.
465
521
  * Shared by resetWebXRState() and stopImageCapture() to avoid duplication.
@@ -499,67 +555,6 @@ function acquireCameraFrameRgba() {
499
555
  return cameraFrameBlit.captureToRgba(renderer, latestCameraTexture);
500
556
  }
501
557
  /**
502
- * Extract the reset transform from an XRReferenceSpaceEvent-like object.
503
- *
504
- * Distinguishes three cases per OdometryTrackingRestartedPayload semantics:
505
- * - Transform property missing (older browsers) → returns `undefined`
506
- * - Transform property present but null (runtime can't determine delta) → returns `null`
507
- * - Transform property present with data → returns `ResetTransformData`
508
- *
509
- * This is a pure function extracted for testability.
510
- *
511
- * @param event - The event object, cast to a record with an optional transform property
512
- * @returns ResetTransformData, null, or undefined
513
- */
514
- function extractResetTransformData(event) {
515
- if (!("transform" in event)) return;
516
- const transform = event.transform;
517
- if (!transform) return null;
518
- const pos = transform.position;
519
- const ori = transform.orientation;
520
- return {
521
- position: [
522
- pos.x,
523
- pos.y,
524
- pos.z
525
- ],
526
- orientation: [
527
- ori.x,
528
- ori.y,
529
- ori.z,
530
- ori.w
531
- ]
532
- };
533
- }
534
- /**
535
- * Extract pose data from an XRViewerPose.
536
- * Returns null if pose or views are unavailable.
537
- *
538
- * This is a pure function extracted for testability.
539
- *
540
- * @param pose - The XRViewerPose from frame.getViewerPose()
541
- * @returns ARPose with position and orientation, or null
542
- */
543
- function extractPoseFromViewer(pose) {
544
- if (!pose) return null;
545
- const view = pose.views[0];
546
- if (!view) return null;
547
- const { position, orientation } = view.transform;
548
- return {
549
- position: {
550
- x: position.x,
551
- y: position.y,
552
- z: position.z
553
- },
554
- orientation: {
555
- x: orientation.x,
556
- y: orientation.y,
557
- z: orientation.z,
558
- w: orientation.w
559
- }
560
- };
561
- }
562
- /**
563
558
  * Get the current raw AR pose from the latest XR frame.
564
559
  * This is updated every frame and should be called when GPS arrives
565
560
  * to get the AR pose at that moment.
@@ -576,6 +571,12 @@ function getCurrentArPose() {
576
571
  * Build XR session init options.
577
572
  * Extracted as a pure function for testability.
578
573
  *
574
+ * Exported for unit testing only (surface-reduction step 3, B-5 precedent):
575
+ * production code reaches this exclusively through {@link initAR}; the export
576
+ * exists so `webxr-session.test.ts` can pin the full session-negotiation
577
+ * matrix (isolation flags × session features) without mocking a renderer and
578
+ * `navigator.xr` for every combination. Not re-exported by the ar/ barrel.
579
+ *
579
580
  * @param rootElement - The DOM element for DOM overlay
580
581
  * @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
581
582
  * depth-sensing, camera-access)
@@ -629,6 +630,10 @@ async function isWebXRSupported() {
629
630
  * Note: these apply to WebGL content only. The CSS3D minimap is composited by
630
631
  * the browser from the camera fov alone — near/far do not clip it (F1 in the
631
632
  * same feedback doc).
633
+ *
634
+ * Module-private since surface-reduction step 3 (no consumer outside this
635
+ * file): the values are pinned observably on the constructed camera by the
636
+ * `createSceneHierarchy` frustum test in `webxr-session.test.ts`.
632
637
  */
633
638
  const AR_CAMERA_FOV = 70;
634
639
  const AR_CAMERA_NEAR = .01;
@@ -681,7 +686,7 @@ function createSceneHierarchy() {
681
686
  const newArPose = new THREE.Object3D();
682
687
  newArPose.name = "ar-pose";
683
688
  newBasisChangeNode.add(newArPose);
684
- const newCamera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, AR_CAMERA_NEAR, 200);
689
+ const newCamera = new THREE.PerspectiveCamera(AR_CAMERA_FOV, window.innerWidth / window.innerHeight, AR_CAMERA_NEAR, AR_CAMERA_FAR);
685
690
  newArPose.add(newCamera);
686
691
  const ambientLight = new THREE.AmbientLight(16777215, .5);
687
692
  newScene.add(ambientLight);
@@ -701,10 +706,27 @@ function createSceneHierarchy() {
701
706
  * @param isolationOptions - Crash-isolation diagnostic flags.
702
707
  * @param sessionFeatures - Opt-in standard WebXR features (e.g.
703
708
  * `requestHitTest`) forwarded to the session negotiation.
709
+ * @param callbacks - Host callbacks for this session (see
710
+ * {@link ArSessionCallbacks}); unpacked once into module slots that
711
+ * `resetWebXRState()` clears at session end.
704
712
  */
705
- async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
713
+ async function initAR(container, isolationOptions = {}, sessionFeatures = {}, callbacks = {}) {
706
714
  if (!navigator.xr) throw new Error("WebXR not available");
707
715
  if (renderer || xrSession) throw new Error("AR session already initialized — call endARSession() before initAR() again");
716
+ onImageCaptured = callbacks.imageCapture?.onCaptured ?? null;
717
+ getScreenRotation = callbacks.imageCapture?.getScreenRotation ?? null;
718
+ onCaptureFailed = callbacks.imageCapture?.onFailed ?? null;
719
+ onSuspiciousImage = callbacks.imageCapture?.onSuspicious ?? null;
720
+ imageQualityAnalyzer = callbacks.imageCapture?.qualityAnalyzer ?? null;
721
+ trackingStore = callbacks.tracking?.store ?? null;
722
+ onTrackingRestarted = callbacks.tracking?.onRestarted ?? null;
723
+ onTrackingLost = callbacks.tracking?.onLost ?? null;
724
+ onTrackingRecovered = callbacks.tracking?.onRecovered ?? null;
725
+ onDepthCaptured = callbacks.depth?.onCaptured ?? null;
726
+ onDepthUnavailable = callbacks.depth?.onUnavailable ?? null;
727
+ onCameraFrame = callbacks.cameraFrame?.onFrame ?? null;
728
+ onFrameCallback = callbacks.onFrame ?? null;
729
+ onSessionEnd = callbacks.onSessionEnd ?? null;
708
730
  currentArCrashIsolationOptions = validateArCrashIsolationOptions(isolationOptions);
709
731
  if (currentArCrashIsolationOptions.applyChromiumProjectionLayerWorkaround) applyChromiumProjectionLayerWorkaround();
710
732
  renderer = new THREE.WebGLRenderer({
@@ -719,7 +741,6 @@ async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
719
741
  const hierarchy = createSceneHierarchy();
720
742
  scene = hierarchy.scene;
721
743
  arWorldGroup = hierarchy.arWorldGroup;
722
- arPoseNode = hierarchy.arpose;
723
744
  camera = hierarchy.camera;
724
745
  const sessionOptions = buildSessionOptions(container, currentArCrashIsolationOptions, sessionFeatures);
725
746
  xrSession = await navigator.xr.requestSession("immersive-ar", sessionOptions);
@@ -727,8 +748,7 @@ async function initAR(container, isolationOptions = {}, sessionFeatures = {}) {
727
748
  handleSessionEnded();
728
749
  });
729
750
  await renderer.xr.setSession(xrSession);
730
- if (onTrackingRestarted !== null !== (trackingStore !== null)) log.warn(trackingStore ? "Tracking pipeline half-wired: setTrackingStore() was called but setTrackingCallbacks() was not — no tracking callbacks will fire. Call both before initAR()." : "Tracking pipeline half-wired: setTrackingCallbacks() was called but setTrackingStore() was not — the tracking slice will not update. Call both before initAR().");
731
- if (onTrackingRestarted && trackingStore) {
751
+ if (trackingStore) {
732
752
  const store = trackingStore;
733
753
  store.dispatch(resetTracking());
734
754
  trackingPhaseUnsubscribe = subscribeToTrackingPhase(store);
@@ -812,11 +832,11 @@ function subscribeToTrackingPhase(store) {
812
832
  }
813
833
  /**
814
834
  * Dispatch the per-frame `poseReceived` / `poseLost` action into the
815
- * tracking slice. No-op when no store is bound or when tracking wiring
816
- * was not requested.
835
+ * tracking slice. No-op when no store is bound (tracking wiring was not
836
+ * requested via initAR `callbacks.tracking`).
817
837
  */
818
838
  function updateTrackingState(arPose) {
819
- if (!trackingStore || !onTrackingRestarted) return;
839
+ if (!trackingStore) return;
820
840
  if (arPose) trackingStore.dispatch(poseReceived({
821
841
  pose: arPose,
822
842
  sensorOrientation: snapshotDeviceOrientation()
@@ -920,48 +940,6 @@ function getCamera() {
920
940
  return camera;
921
941
  }
922
942
  /**
923
- * Set the scene externally (for replay mode).
924
- * Allows non-WebXR code paths to register a scene so that modules
925
- * calling getScene() receive it.
926
- * @see docs/2026-02-19-replay-mode.md Risk R1
927
- */
928
- function setScene(s) {
929
- scene = s;
930
- }
931
- /**
932
- * Set the AR world group externally (for replay mode).
933
- * Allows non-WebXR code paths to register an arWorldGroup so that
934
- * applyAlignmentMatrix() and visualizers work correctly.
935
- * @see docs/2026-02-19-replay-mode.md Risk R1
936
- */
937
- function setArWorldGroup(g) {
938
- arWorldGroup = g;
939
- }
940
- /**
941
- * Set the camera externally (for replay mode).
942
- * Allows non-WebXR code paths to register a camera so that modules
943
- * calling getCamera() receive it.
944
- * @see docs/2026-02-19-replay-mode.md Risk R1
945
- */
946
- function setCamera(c) {
947
- camera = c;
948
- }
949
- /**
950
- * Get the arpose Object3D (intermediate node between arWorldGroup and camera).
951
- * Returns null before scene initialization.
952
- */
953
- function getArPose() {
954
- return arPoseNode;
955
- }
956
- /**
957
- * Set the arpose Object3D externally (for replay mode).
958
- * Allows non-WebXR code paths to register an arpose so that
959
- * store subscribers can update it with recorded odom data.
960
- */
961
- function setArPose(a) {
962
- arPoseNode = a;
963
- }
964
- /**
965
943
  * Apply an alignment matrix to the AR world group.
966
944
  *
967
945
  * The alignment matrix maps odometry positions in NUE space
@@ -1021,20 +999,6 @@ function nueQuaternionToWebXR$1(nue) {
1021
999
  return nueQuaternionToWebXR(nue);
1022
1000
  }
1023
1001
  /**
1024
- * Register a host callback fired exactly once whenever the XRSession ends —
1025
- * app-initiated ({@link endARSession}) AND system-initiated (e.g. the Android
1026
- * back gesture, which ends an immersive session directly and uncancelably).
1027
- * `info.requestedByApp` discriminates the two. Fired AFTER the full teardown,
1028
- * so the host can immediately start a fresh session from inside the callback.
1029
- *
1030
- * Cleared by resetWebXRState() (i.e. after every session end) like the other
1031
- * module-level callbacks — re-register before/with each new session.
1032
- * Pass `null` to unregister. F3, 2026-07-04 user feedback.
1033
- */
1034
- function setSessionEndCallback(cb) {
1035
- onSessionEnd = cb;
1036
- }
1037
- /**
1038
1002
  * The ONE teardown for a session that has ended, shared by both trigger
1039
1003
  * paths via the XRSession 'end' listener. Before F3 the system-initiated
1040
1004
  * path (back gesture) only nulled `xrSession`/`latestArPose`, leaving the
@@ -1077,41 +1041,14 @@ async function endARSession() {
1077
1041
  }
1078
1042
  }
1079
1043
  /**
1080
- * Set up image capture callbacks.
1081
- * Call this before starting image capture to wire up the callback handlers.
1082
- *
1083
- * @param onCaptured - Called when an image is successfully captured
1084
- * @param screenRotationGetter - Returns current device screen rotation (0, 90, 180, 270)
1085
- * @param onFailed - Optional callback for when image capture fails (e.g., low memory)
1086
- * @param onSuspicious - Optional callback for when a captured image appears black/empty
1087
- */
1088
- function setImageCaptureCallback(onCaptured, screenRotationGetter, onFailed, onSuspicious) {
1089
- onImageCaptured = onCaptured;
1090
- getScreenRotation = screenRotationGetter;
1091
- onCaptureFailed = onFailed ?? null;
1092
- onSuspiciousImage = onSuspicious ?? null;
1093
- }
1094
- /**
1095
- * Inject (or clear) the off-thread image-quality analyzer used by the blur/
1096
- * blackness drop+retry gate. Pass the recorder's Web Worker-backed analyzer to
1097
- * enable the gate, or `null` to clear it (legacy save-immediately path). Must be
1098
- * set BEFORE {@link startImageCapture} for the active recording — the manager
1099
- * reads it once when constructed. The analyzer only runs when the capture
1100
- * config's `qualityFilter.enabled` is true, so injecting it is harmless when the
1101
- * gate is off. Ownership/disposal of the worker is the caller's responsibility.
1102
- */
1103
- function setImageQualityAnalyzer(analyzer) {
1104
- imageQualityAnalyzer = analyzer;
1105
- }
1106
- /**
1107
1044
  * Start capturing images during recording.
1108
- * Must call setImageCaptureCallback first.
1045
+ * Requires the `imageCapture` callbacks group to have been passed to initAR.
1109
1046
  *
1110
1047
  * @param config - Optional capture configuration. Accepts the whole user
1111
1048
  * image-options section (`intervalMs`, `quality`, `resolutionDivisor`; any
1112
1049
  * extra keys such as `enabled` are ignored). Passing the section as one
1113
1050
  * object means a newly-added option flows through without editing this seam
1114
- * — see `2026-06-12-payload-rebuild-field-drop-audit.md` (F3).
1051
+ * — see `2026-06-12-1130-payload-rebuild-field-drop-audit.md` (F3).
1115
1052
  */
1116
1053
  function startImageCapture(config) {
1117
1054
  if (!renderer) {
@@ -1188,16 +1125,27 @@ function getImageCaptureFrameCount() {
1188
1125
  return imageCaptureManager?.getFrameCount() ?? 0;
1189
1126
  }
1190
1127
  /**
1191
- * Inject the Redux store used by the tracking-state slice pipeline.
1128
+ * Re-point the tracking pipeline at a NEW store mid-session.
1129
+ *
1130
+ * This is the ONE mutation that deliberately survived the fold of the
1131
+ * pre-init setters into initAR's `callbacks` (surface-reduction step 1):
1132
+ * it is a genuine RUNTIME need, not init-time wiring — the recorder swaps
1133
+ * its Redux store for every recording started inside a single AR session,
1134
+ * and without re-pointing, every per-frame `poseReceived` dispatch would
1135
+ * keep flowing into the orphaned previous store, pinning the new store's
1136
+ * `tracking.phase` at 'initializing' (Finding #1, 2026-05-23 user
1137
+ * feedback). Everything else about a session's callbacks stays fixed from
1138
+ * `initAR` until `resetWebXRState()`.
1192
1139
  *
1193
- * MUST be called before `initAR()` whenever the host also wires tracking
1194
- * callbacks via `setTrackingCallbacks`. Without a store the tracking
1195
- * pipeline silently no-ops.
1140
+ * Matches the old `setTrackingStore` semantics exactly: an active phase
1141
+ * subscription to the previous store is torn down before swapping (the
1142
+ * phase subscription itself is only (re)established inside `initAR`).
1143
+ * No-op pipeline when tracking was not wired at init (the host callbacks
1144
+ * stay whatever `initAR` set).
1196
1145
  *
1197
1146
  * @param store — any store satisfying {@link TrackingSubscribableStore}.
1198
- * `null` clears the binding (useful for teardown in tests).
1199
1147
  */
1200
- function setTrackingStore(store) {
1148
+ function rebindTrackingStore(store) {
1201
1149
  if (trackingPhaseUnsubscribe) {
1202
1150
  trackingPhaseUnsubscribe();
1203
1151
  trackingPhaseUnsubscribe = null;
@@ -1205,47 +1153,9 @@ function setTrackingStore(store) {
1205
1153
  trackingStore = store;
1206
1154
  }
1207
1155
  /**
1208
- * Set up tracking state callbacks.
1209
- * Call this BEFORE initAR() to enable tracking restart detection.
1210
- *
1211
- * @param onRestarted - Called when tracking restarts after being lost
1212
- */
1213
- function setTrackingCallbacks(onRestarted) {
1214
- onTrackingRestarted = onRestarted;
1215
- }
1216
- /**
1217
- * Set a callback for when AR tracking is lost.
1218
- * Call this BEFORE initAR() to enable tracking loss notifications.
1219
- * Field Test Readiness Issue #3: Provide user feedback when tracking is lost.
1220
- *
1221
- * @param callback - Called when tracking is lost (pose becomes null)
1222
- */
1223
- function setTrackingLostCallback(callback) {
1224
- onTrackingLost = callback;
1225
- }
1226
- /**
1227
- * Set a callback for when AR tracking recovers seamlessly (Case 1: same coordinate frame).
1228
- * Call this BEFORE initAR() to enable seamless recovery notifications.
1229
- *
1230
- * @param callback - Called when tracking recovers without origin reset
1231
- */
1232
- function setTrackingRecoveredCallback(callback) {
1233
- onTrackingRecovered = callback;
1234
- }
1235
- /**
1236
- * Set up depth capture callback.
1237
- * Call this BEFORE initAR() to enable depth sampling.
1238
- *
1239
- * @param onCaptured - Called when a depth sample is captured
1240
- * @param onUnavailable - Called once if depth is unavailable after threshold
1241
- */
1242
- function setDepthCaptureCallback(onCaptured, onUnavailable) {
1243
- onDepthCaptured = onCaptured;
1244
- onDepthUnavailable = onUnavailable ?? null;
1245
- }
1246
- /**
1247
1156
  * Start depth sampling during recording.
1248
- * Must call setDepthCaptureCallback before initAR.
1157
+ * Requires the `depth` callbacks group to have been passed to initAR
1158
+ * (the sampler is created there).
1249
1159
  *
1250
1160
  * @param config - optional sampler overrides (typically the user's
1251
1161
  * `depth.intervalMs`/`depth.gridSize` recording options); applied via
@@ -1279,24 +1189,12 @@ function getDepthSampleCount() {
1279
1189
  return depthSampler?.getSampleCount() ?? 0;
1280
1190
  }
1281
1191
  /**
1282
- * Set the per-frame camera RGBA callback (B2) the generic CV frame feed (QR
1283
- * detection today, object detection / OpenCV later). Call this BEFORE initAR to
1284
- * enable in-session camera frame capture; `startCameraFrameCapture` then begins
1285
- * delivering frames.
1286
- *
1287
- * The callback receives a **top-left-origin** RGBA image (no JPEG round-trip),
1288
- * captured at the throttled detection cadence — feed it straight to a
1289
- * `BarcodeDetector` / OpenCV front-end. Mirrors `setDepthCaptureCallback`.
1290
- *
1291
- * @param onFrame - Called with each throttled camera frame, or `null` to clear.
1292
- */
1293
- function setCameraFrameCallback(onFrame) {
1294
- onCameraFrame = onFrame;
1295
- }
1296
- /**
1297
- * Start camera frame capture during an AR session. Must call
1298
- * setCameraFrameCallback before initAR (the source is created there). No-op if
1299
- * the source was not initialized (callback not set before initAR).
1192
+ * Start camera frame capture during an AR session. Requires the
1193
+ * `cameraFrame` callbacks group to have been passed to initAR (the source is
1194
+ * created there); the callback receives **top-left-origin** RGBA images (no
1195
+ * JPEG round-trip) at the throttled detection cadence — feed them straight to
1196
+ * a `BarcodeDetector` / OpenCV front-end (B2). No-op if the source was not
1197
+ * initialized (group not passed to initAR).
1300
1198
  *
1301
1199
  * The source is the single cadence owner: drive your detection scheduler from
1302
1200
  * the delivered frames with its own `minIntervalMs: 0` (Option A).
@@ -1330,33 +1228,5 @@ function stopCameraFrameCapture() {
1330
1228
  function getCameraFrameCount() {
1331
1229
  return cameraFrameSource?.getFrameCount() ?? 0;
1332
1230
  }
1333
- /**
1334
- * The current longer-edge resolution (px) of the camera-frame blit — the
1335
- * {@link DEFAULT_CAMERA_FRAME_CAPTURE_SIZE} default unless overridden via
1336
- * `startCameraFrameCapture({ captureSize })`. Exposed for diagnostics and to
1337
- * let tests assert the on-device-tuned default without reaching into module
1338
- * state.
1339
- */
1340
- function getCameraFrameCaptureSize() {
1341
- return cameraFrameCaptureSize;
1342
- }
1343
- /**
1344
- * Set a per-frame callback for custom updates.
1345
- * The callback is invoked every XR frame after pose updates but before render.
1346
- * Useful for updating elements that need to follow the camera smoothly
1347
- * (e.g., map overlay position).
1348
- *
1349
- * @param callback - Function to call each frame, or null to clear
1350
- */
1351
- function setFrameCallback(callback) {
1352
- onFrameCallback = callback;
1353
- }
1354
- /**
1355
- * Get the CSS3D renderer manager for live AR mode.
1356
- * Returns null if no AR session is active or CSS3D was not created.
1357
- */
1358
- function getLiveCss3dManager() {
1359
- return css3dManager;
1360
- }
1361
1231
  //#endregion
1362
- export { setArWorldGroup as A, setTrackingLostCallback as B, initAR as C, nueQuaternionToWebXR$1 as D, nuePositionToWebXR as E, setImageCaptureCallback as F, startDepthCapture as G, setTrackingStore as H, setImageQualityAnalyzer as I, stopDepthCapture as J, startImageCapture as K, setScene as L, setCameraFrameCallback as M, setDepthCaptureCallback as N, resetWebXRState as O, setFrameCallback as P, setSessionEndCallback as R, getXrCameraFromPose as S, isXRCameraLike as T, shouldLogCameraAccessDiagnostic as U, setTrackingRecoveredCallback as V, startCameraFrameCapture as W, CameraFrameSource as X, stopImageCapture as Y, getDepthInfoFromFrame as _, applyAlignmentMatrix as a, getLiveCss3dManager as b, endARSession as c, getArPose as d, getArWorldGroup as f, getCurrentArPose as g, getCameraFrameCount as h, DEFAULT_CAMERA_FRAME_CAPTURE_SIZE as i, setCamera as j, setArPose as k, extractPoseFromViewer as l, getCameraFrameCaptureSize as m, AR_CAMERA_FOV as n, buildSessionOptions as o, getCamera as p, stopCameraFrameCapture as q, AR_CAMERA_NEAR as r, createSceneHierarchy as s, AR_CAMERA_FAR as t, extractResetTransformData as u, getDepthSampleCount as v, isWebXRSupported as w, getScene as x, getImageCaptureFrameCount as y, setTrackingCallbacks as z };
1232
+ export { stopImageCapture as A, resetWebXRState as C, startImageCapture as D, startDepthCapture as E, stopCameraFrameCapture as O, rebindTrackingStore as S, startCameraFrameCapture as T, initAR as _, endARSession as a, nuePositionToWebXR as b, getArWorldGroup as c, getCurrentArPose as d, getDepthInfoFromFrame as f, getXrCameraFromPose as g, getScene as h, createSceneHierarchy as i, CameraFrameSource as j, stopDepthCapture as k, getCamera as l, getImageCaptureFrameCount as m, applyAlignmentMatrix as n, extractPoseFromViewer as o, getDepthSampleCount as p, buildSessionOptions as r, extractResetTransformData as s, DEFAULT_CAMERA_FRAME_CAPTURE_SIZE as t, getCameraFrameCount as u, isWebXRSupported as v, shouldLogCameraAccessDiagnostic as w, nueQuaternionToWebXR$1 as x, isXRCameraLike as y };
@@ -17,7 +17,7 @@
17
17
  * makes correct use the easy path and stashing the awkward one. Do NOT retain
18
18
  * `ctx` or its fields beyond the callback's synchronous execution.
19
19
  *
20
- * See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md`
20
+ * See `2026-06-03-0553-threejs-arbutton-minimal-ar-example-user-feedback.md`
21
21
  * §6.2/§6.3 (option H-A2) for the design rationale.
22
22
  */
23
23
  /**