gps-plus-slam-app-framework 1.0.7 → 1.2.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 (189) hide show
  1. package/README.md +69 -15
  2. package/dist/{app-selectors-BeU0_ab8.d.ts → app-selectors-CcZ5UV-8.d.ts} +1 -1
  3. package/dist/ar/bresenham3d.d.ts +2 -0
  4. package/dist/ar/bresenham3d.js +81 -0
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/camera-blit-capture.js +30 -2
  7. package/dist/ar/capability-checker.d.ts +2 -0
  8. package/dist/ar/capability-checker.js +25 -0
  9. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  10. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
  11. package/dist/ar/chromium-camera-access-workaround.js +197 -8
  12. package/dist/ar/depth-sampler.d.ts +3 -3
  13. package/dist/ar/depth-sampler.js +63 -8
  14. package/dist/ar/depth-unprojection.d.ts +2 -0
  15. package/dist/ar/depth-unprojection.js +94 -0
  16. package/dist/ar/enable-gps-ar.d.ts +2 -0
  17. package/dist/ar/enable-gps-ar.js +137 -0
  18. package/dist/ar/frame-loop.d.ts +1 -1
  19. package/dist/ar/frame-loop.js +24 -1
  20. package/dist/ar/image-capture.d.ts +1 -1
  21. package/dist/ar/image-capture.js +2 -1
  22. package/dist/ar/index.d.ts +19 -12
  23. package/dist/ar/index.js +11 -4
  24. package/dist/ar/occupancy-grid.d.ts +2 -0
  25. package/dist/ar/occupancy-grid.js +164 -0
  26. package/dist/ar/replay-scene.d.ts +1 -1
  27. package/dist/ar/replay-scene.js +1 -1
  28. package/dist/ar/scene-node-names.d.ts +1 -1
  29. package/dist/ar/webxr-nue-basis.d.ts +2 -0
  30. package/dist/ar/webxr-nue-basis.js +54 -0
  31. package/dist/ar/webxr-session.d.ts +4 -4
  32. package/dist/ar/webxr-session.js +1 -984
  33. package/dist/ar/xr-camera-texture.d.ts +1 -1
  34. package/dist/ar/xr-error-handler.d.ts +1 -1
  35. package/dist/ar/xr-frame-loop.d.ts +2 -0
  36. package/dist/ar/xr-frame-loop.js +69 -0
  37. package/dist/{ar-types-Ck8LEq8k.d.ts → ar-types-Dan1MVTi.d.ts} +28 -5
  38. package/dist/ar-world-group-alignment-ColHB_5A.d.ts +43 -0
  39. package/dist/bresenham3d-CFfd7n6B.d.ts +59 -0
  40. package/dist/{camera-blit-capture-C1V9I1Z6.d.ts → camera-blit-capture-D5_mNhla.d.ts} +20 -0
  41. package/dist/capability-checker-CQXJHxXg.d.ts +43 -0
  42. package/dist/chromium-camera-access-workaround-Jqbn0apH.d.ts +137 -0
  43. package/dist/core/index.d.ts +1 -1
  44. package/dist/{create-slam-app-store-CNsZ3rcX.d.ts → create-slam-app-store-qNXQ-tq5.d.ts} +5 -5
  45. package/dist/depth-sampler-Bhanfwla.d.ts +169 -0
  46. package/dist/depth-unprojection-Y6i_mJuT.d.ts +52 -0
  47. package/dist/enable-gps-ar-CapmADVN.d.ts +95 -0
  48. package/dist/{file-system-DTApEv_Q.d.ts → file-system-C_eqiEA8.d.ts} +1 -1
  49. package/dist/frame-conversions-BfsafGWR.d.ts +54 -0
  50. package/dist/{frame-loop-QJIxLin0.d.ts → frame-loop-CMewS8cY.d.ts} +5 -0
  51. package/dist/{fused-path-CB4DwWOG.d.ts → fused-path-DJoVu98b.d.ts} +1 -1
  52. package/dist/geo/h3-proximity.d.ts +1 -1
  53. package/dist/geo/index.d.ts +1 -1
  54. package/dist/{gps-anchor-CnWY61XK.d.ts → gps-anchor-dYeCvvp5.d.ts} +12 -1
  55. package/dist/{gps-ar-pose-sampler-DsM1Kb2N.d.ts → gps-ar-pose-sampler-DVb96iOy.d.ts} +1 -1
  56. package/dist/{gps-event-coordinator-DBx9I1YX.d.ts → gps-event-coordinator-BDIQde2x.d.ts} +3 -3
  57. package/dist/hit-test-reticle-BEzEybxZ.d.ts +45 -0
  58. package/dist/{image-capture-DZLxHDN9.d.ts → image-capture-DZ4HFRgS.d.ts} +15 -2
  59. package/dist/index-KJq2amHF.d.ts +37 -0
  60. package/dist/index.d.ts +67 -58
  61. package/dist/index.js +18 -8
  62. package/dist/{leaflet-map-overlay-Cew7XAoO.d.ts → leaflet-map-overlay-6CJC-E1l.d.ts} +1 -1
  63. package/dist/{map-data-sih4g64v.d.ts → map-data-CkloBxIl.d.ts} +1 -1
  64. package/dist/{map-overlay-draw-B3R1c9Xp.d.ts → map-overlay-draw-krOKwDAq.d.ts} +1 -1
  65. package/dist/{null-storage-backend-BKOS7JpL.d.ts → null-storage-backend-CxEjZHRW.d.ts} +2 -2
  66. package/dist/occupancy-grid-BRNRaPyT.d.ts +72 -0
  67. package/dist/{opfs-storage-backend-Cj_7PWoq.d.ts → opfs-storage-backend-CjSAuqg_.d.ts} +2 -2
  68. package/dist/{persistence-middleware-BkHIqvaA.d.ts → persistence-middleware-DwyzgfWr.d.ts} +1 -1
  69. package/dist/{recording-options-Dq6IsKAv.d.ts → recording-options-DJBXuMxV.d.ts} +22 -6
  70. package/dist/{recording-replayer-CmCIIZ02.d.ts → recording-replayer-BL-x-7k0.d.ts} +2 -2
  71. package/dist/{recording-slice-d6OtCrza.d.ts → recording-slice-89O07ebs.d.ts} +10 -2
  72. package/dist/{replay-engine-BGBRQyXs.d.ts → replay-engine-BRqszuQ9.d.ts} +1 -1
  73. package/dist/{replay-scene-DpAGcAKY.d.ts → replay-scene-Bw58VL6p.d.ts} +3 -3
  74. package/dist/sensors/gps-error-handler.d.ts +1 -1
  75. package/dist/sensors/gps.d.ts +1 -1
  76. package/dist/sensors/index.d.ts +3 -3
  77. package/dist/sensors/index.js +1 -1
  78. package/dist/sensors/permission-checker.d.ts +1 -1
  79. package/dist/session-disposers-M-oashRH.js +57 -0
  80. package/dist/state/app-selectors.d.ts +1 -1
  81. package/dist/state/combined-root-state.d.ts +1 -1
  82. package/dist/state/create-slam-app-store.d.ts +1 -1
  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 -17
  86. package/dist/state/index.js +2 -2
  87. package/dist/state/persistence-middleware.d.ts +1 -1
  88. package/dist/state/persistence-middleware.js +9 -3
  89. package/dist/state/recording-options.d.ts +1 -1
  90. package/dist/state/recording-options.js +5 -3
  91. package/dist/state/recording-replayer.d.ts +2 -2
  92. package/dist/state/recording-slice.d.ts +1 -1
  93. package/dist/state/recording-slice.js +11 -2
  94. package/dist/state/replay-engine.d.ts +1 -1
  95. package/dist/state/store-subscribers.d.ts +2 -2
  96. package/dist/state/subscribe-to-selector.d.ts +1 -1
  97. package/dist/state/tracking-quality.d.ts +1 -1
  98. package/dist/state/tracking-quality.js +1 -1
  99. package/dist/state/tracking-slice.d.ts +1 -1
  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 +1 -1
  103. package/dist/storage/index.d.ts +8 -8
  104. package/dist/storage/null-storage-backend.d.ts +1 -1
  105. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  106. package/dist/storage/opfs-storage.d.ts +1 -1
  107. package/dist/storage/storage-backend.d.ts +1 -1
  108. package/dist/storage/zip-export.d.ts +1 -1
  109. package/dist/storage/zip-reader.d.ts +1 -1
  110. package/dist/{storage-backend-CT0Il_AB.d.ts → storage-backend-C9HLFtgz.d.ts} +1 -1
  111. package/dist/{store-subscribers-Dce_CoJF.d.ts → store-subscribers-rrkLkFTt.d.ts} +2 -2
  112. package/dist/{subscribe-to-selector-DIHm3BRZ.d.ts → subscribe-to-selector-CP3BKSbF.d.ts} +1 -1
  113. package/dist/test-utils/browser-mocks.d.ts +1 -1
  114. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  115. package/dist/{tracking-slice-utCNND8t.d.ts → tracking-slice-CdK8c-8P.d.ts} +1 -1
  116. package/dist/types/ar-types.d.ts +2 -2
  117. package/dist/types/geo-types.d.ts +1 -1
  118. package/dist/types/index.d.ts +3 -3
  119. package/dist/utils/concurrency.d.ts +1 -1
  120. package/dist/utils/failure-tracker.d.ts +1 -1
  121. package/dist/utils/format-file-size.d.ts +1 -1
  122. package/dist/utils/fused-path.d.ts +1 -1
  123. package/dist/utils/index.d.ts +6 -6
  124. package/dist/utils/list-formatter.d.ts +1 -1
  125. package/dist/utils/logger.d.ts +1 -1
  126. package/dist/visualization/accuracy-circles.d.ts +1 -1
  127. package/dist/visualization/alignment-lerper.d.ts +1 -1
  128. package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
  129. package/dist/visualization/ar-world-group-alignment.js +51 -0
  130. package/dist/visualization/camera-follower.d.ts +1 -1
  131. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  132. package/dist/visualization/frame-conversions.d.ts +2 -0
  133. package/dist/visualization/frame-conversions.js +88 -0
  134. package/dist/visualization/frustum-visibility.d.ts +1 -1
  135. package/dist/visualization/gps-anchor.d.ts +1 -1
  136. package/dist/visualization/gps-anchor.js +216 -1
  137. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  138. package/dist/visualization/gps-event-markers.d.ts +1 -1
  139. package/dist/visualization/gps-event-markers.js +1 -1
  140. package/dist/visualization/hit-test-reticle.d.ts +2 -0
  141. package/dist/visualization/hit-test-reticle.js +81 -0
  142. package/dist/visualization/index.d.ts +19 -17
  143. package/dist/visualization/index.js +5 -2
  144. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  145. package/dist/visualization/lerp-utils.d.ts +1 -1
  146. package/dist/visualization/map-data.d.ts +1 -1
  147. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  148. package/dist/visualization/map-overlay.d.ts +1 -1
  149. package/dist/visualization/three-dispose.d.ts +1 -1
  150. package/dist/visualization/vis-colors.d.ts +1 -1
  151. package/dist/webxr-nue-basis-CqKQmZzT.d.ts +23 -0
  152. package/dist/{webxr-session-DB5cIZnN.d.ts → webxr-session-DtAG9srK.d.ts} +45 -13
  153. package/dist/webxr-session-QhcSSub5.js +1041 -0
  154. package/dist/xr-frame-loop-BtYytZJg.d.ts +70 -0
  155. package/package.json +1 -1
  156. package/dist/chromium-camera-access-workaround-NvChXsi8.d.ts +0 -61
  157. package/dist/depth-sampler-xg8vna6c.d.ts +0 -108
  158. package/dist/gps-anchor-DjC_3MoT.js +0 -257
  159. package/dist/index-pOjUCKEP.d.ts +0 -24
  160. /package/dist/{accuracy-circles-CsxE_7x1.d.ts → accuracy-circles-BpwWAs8G.d.ts} +0 -0
  161. /package/dist/{alignment-lerper-L1RbWHQ4.d.ts → alignment-lerper-Dm9G7JJO.d.ts} +0 -0
  162. /package/dist/{camera-follower-1k8mnw5J.d.ts → camera-follower-C2mwEn7c.d.ts} +0 -0
  163. /package/dist/{capture-failure-tracker-JKb1ST8E.d.ts → capture-failure-tracker-CUeU3L_M.d.ts} +0 -0
  164. /package/dist/{concurrency-CMw8pSaG.d.ts → concurrency-CO1FQvs3.d.ts} +0 -0
  165. /package/dist/{css3d-renderer-manager-CkjCb2HW.d.ts → css3d-renderer-manager-ptu_t3B3.d.ts} +0 -0
  166. /package/dist/{failure-tracker-DnHXOIRa.d.ts → failure-tracker-Cn8Fo9D4.d.ts} +0 -0
  167. /package/dist/{file-system-utils-CtKiiw27.d.ts → file-system-utils-DyAmcuvX.d.ts} +0 -0
  168. /package/dist/{format-file-size-BTS5H62-.d.ts → format-file-size-CLCZz3lC.d.ts} +0 -0
  169. /package/dist/{frustum-visibility-BHnFrSKQ.d.ts → frustum-visibility-CmI96ESS.d.ts} +0 -0
  170. /package/dist/{geo-types-CTuF23gG.d.ts → geo-types-SaPnIEUM.d.ts} +0 -0
  171. /package/dist/{gps-DXoQb9Kf.d.ts → gps-VuezJWK3.d.ts} +0 -0
  172. /package/dist/{gps-compass-cubes-CsLtFv4B.d.ts → gps-compass-cubes-BM1uGiYV.d.ts} +0 -0
  173. /package/dist/{gps-error-handler-Dg3ziRVY.d.ts → gps-error-handler-CJl_2MVm.d.ts} +0 -0
  174. /package/dist/{gps-event-markers-BGr-iLvi.d.ts → gps-event-markers-Dc9FT-q1.d.ts} +0 -0
  175. /package/dist/{h3-proximity-BhfgNbdW.d.ts → h3-proximity-I1msGPgE.d.ts} +0 -0
  176. /package/dist/{index-gzWJMVpo.d.ts → index-CF05QV2m.d.ts} +0 -0
  177. /package/dist/{lerp-utils-B8MM8zCR.d.ts → lerp-utils-BpV-0Dtk.d.ts} +0 -0
  178. /package/dist/{list-formatter-CNqi-E2j.d.ts → list-formatter-RRcBe8Vg.d.ts} +0 -0
  179. /package/dist/{logger-C9vugiQb.d.ts → logger-BEHXsnnq.d.ts} +0 -0
  180. /package/dist/{map-overlay-DAmreyA3.d.ts → map-overlay-bcogY5fK.d.ts} +0 -0
  181. /package/dist/{opfs-storage-DOPkzgeT.d.ts → opfs-storage-DYm8WHq7.d.ts} +0 -0
  182. /package/dist/{permission-checker-DPJl_RU6.d.ts → permission-checker-GpAiSCwd.d.ts} +0 -0
  183. /package/dist/{scene-node-names-6LBjPmwz.d.ts → scene-node-names-Bu0S66H1.d.ts} +0 -0
  184. /package/dist/{three-dispose-C2drkp6f.d.ts → three-dispose-D950t71B.d.ts} +0 -0
  185. /package/dist/{vis-colors-CHQhjal3.d.ts → vis-colors-D6omC64K.d.ts} +0 -0
  186. /package/dist/{xr-camera-texture-BU5YvKAM.d.ts → xr-camera-texture-DBwCaKTd.d.ts} +0 -0
  187. /package/dist/{xr-error-handler-CDY5QxGb.d.ts → xr-error-handler-C0AQRmxs.d.ts} +0 -0
  188. /package/dist/{zip-export-QO4iLIi7.d.ts → zip-export-D_5HAvFz.d.ts} +0 -0
  189. /package/dist/{zip-reader-D1105idz.d.ts → zip-reader-DSEXt7K6.d.ts} +0 -0
@@ -1,2 +1,217 @@
1
- import { t as createGpsAnchor } from "../gps-anchor-DjC_3MoT.js";
1
+ import { registerFrameUpdate } from "../ar/frame-loop.js";
2
+ import { isObjectInCameraFrustum } from "./frustum-visibility.js";
3
+ import { calcRelativeCoordsInMeters } from "../core/index.js";
4
+ import { nueToArLocal } from "./frame-conversions.js";
5
+ import * as THREE from "three";
6
+ //#region ../src/visualization/gps-anchor.ts
7
+ /**
8
+ * `createGpsAnchor` — GPS-anchored placement of a single `THREE.Object3D`.
9
+ *
10
+ * See the colocated sidecar (`gps-anchor.ts.md`) and the port plan at
11
+ * `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-13-gps-anchor-port-plan.md`
12
+ * for the full design, state machine, and test matrix.
13
+ *
14
+ * This file implements sub-steps 2 (bootstrap phase), 3 (steady-state
15
+ * `'snap-every-tick'` + distance-scaled threshold gate), and 4
16
+ * (`'snap-when-offscreen'` mode gate, with a one-time initial-placement
17
+ * exemption for `skipBootstrap` anchors). Floor-Y correction is sub-step
18
+ * 6 and remains deferred.
19
+ */
20
+ /**
21
+ * Module-level registry of objects currently owned by a `GpsAnchor`.
22
+ * Used to detect nested anchors (parent + child both anchored) which
23
+ * we explicitly forbid; mirrors the C# invariant.
24
+ */
25
+ const anchoredObjects = /* @__PURE__ */ new WeakSet();
26
+ function isObjectInAnchoredChain(object) {
27
+ let cursor = object;
28
+ while (cursor) {
29
+ if (anchoredObjects.has(cursor)) return true;
30
+ cursor = cursor.parent;
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * Returns true iff `object` is `arWorldGroup` itself or a descendant of it.
36
+ * The anchor's `object3D` MUST satisfy this: only then does it ride the
37
+ * alignment applied to `arWorldGroup.matrix` (the node the camera also lives
38
+ * under) via scene-graph propagation. An anchor parented to the scene root
39
+ * instead never receives the alignment, so each steady-state re-registration
40
+ * snaps the full alignment delta and the object visibly slides as the user
41
+ * moves — the exact instability the anchor exists to prevent.
42
+ */
43
+ function isDescendantOf(object, arWorldGroup) {
44
+ let cursor = object;
45
+ while (cursor) {
46
+ if (cursor === arWorldGroup) return true;
47
+ cursor = cursor.parent;
48
+ }
49
+ return false;
50
+ }
51
+ function median(values) {
52
+ const sorted = [...values].sort((a, b) => a - b);
53
+ const mid = Math.floor(sorted.length / 2);
54
+ if (sorted.length % 2 === 0) return (sorted[mid - 1] + sorted[mid]) / 2;
55
+ return sorted[mid];
56
+ }
57
+ function medianPoint(samples) {
58
+ const lat = median(samples.map((s) => s.lat));
59
+ const lon = median(samples.map((s) => s.lon));
60
+ const alts = samples.map((s) => "altitude" in s ? s.altitude : void 0).filter((a) => typeof a === "number");
61
+ if (alts.length > 0) return {
62
+ lat,
63
+ lon,
64
+ altitude: median(alts)
65
+ };
66
+ return {
67
+ lat,
68
+ lon
69
+ };
70
+ }
71
+ function createGpsAnchor(options) {
72
+ if (isObjectInAnchoredChain(options.object3D)) throw new Error("createGpsAnchor: nested GpsAnchors are not supported — the supplied object3D is already inside an anchored parent chain.");
73
+ if (!isDescendantOf(options.object3D, options.arWorldGroup)) throw new Error("createGpsAnchor: object3D must be a descendant of arWorldGroup (the alignment-bearing node the camera lives under); parenting an anchor to the scene root defeats AR stability.");
74
+ anchoredObjects.add(options.object3D);
75
+ const sampleCount = options.secondsToAccumulateGpsPose ?? 7;
76
+ if (sampleCount < 1) {
77
+ anchoredObjects.delete(options.object3D);
78
+ throw new Error("createGpsAnchor: secondsToAccumulateGpsPose must be >= 1 — use skipBootstrap:true to bypass the accumulation phase.");
79
+ }
80
+ const settlingSeconds = options.settlingSeconds ?? 0;
81
+ const distanceThreshold = options.distanceThreshold ?? 2;
82
+ options.angleThresholdInDegrees;
83
+ const mode = options.mode ?? "snap-when-offscreen";
84
+ const scratchTarget = new THREE.Vector3();
85
+ const scratchCamWorld = new THREE.Vector3();
86
+ const scratchObjWorld = new THREE.Vector3();
87
+ let phase = options.skipBootstrap === true ? "anchored" : "bootstrap";
88
+ let isFullyAnchored = phase === "anchored";
89
+ let gpsPoint = options.gpsPoint;
90
+ let phaseEnteredAtElapsed = null;
91
+ let lastSampleAtElapsed = null;
92
+ const samples = [];
93
+ /**
94
+ * The one-time "initial placement" exemption from the `snap-when-offscreen`
95
+ * frustum gate. A `skipBootstrap` anchor is `anchored` from frame one but its
96
+ * `object3D` still sits at the AR origin (local 0,0,0 — "inside the user")
97
+ * until its first steady-state commit. That first commit MUST land even while
98
+ * the object is on-screen, otherwise the marker stays stuck at the origin
99
+ * until the user happens to look away. A fresh appearance is not a "jump", so
100
+ * it is exempt. Consumed (set false) by the first committed correction; never
101
+ * re-armed (a re-bootstrap moves an already-placed object, so its later
102
+ * corrections are normal gated snaps). Anchors that bootstrap are placed at
103
+ * their seed pose by the host, so they never need this exemption — hence it is
104
+ * armed only for `skipBootstrap`.
105
+ */
106
+ let firstCommitPending = options.skipBootstrap === true;
107
+ const enterBootstrap = () => {
108
+ phase = "bootstrap";
109
+ isFullyAnchored = false;
110
+ phaseEnteredAtElapsed = null;
111
+ lastSampleAtElapsed = null;
112
+ samples.length = 0;
113
+ };
114
+ const commitMedian = () => {
115
+ gpsPoint = medianPoint(samples);
116
+ phase = "anchored";
117
+ isFullyAnchored = true;
118
+ samples.length = 0;
119
+ options.onBootstrapComplete?.(gpsPoint);
120
+ };
121
+ /**
122
+ * Steady-state: compute the GPS-world NUE target from the stored
123
+ * `gpsPoint` and the current `zeroRef`, map it into `arWorldGroup`'s
124
+ * AR-local frame via the inverse alignment matrix, and commit it to
125
+ * `object3D.position` iff the (AR-local) position delta exceeds the
126
+ * distance-scaled threshold AND the mode gate allows it.
127
+ *
128
+ * Mode gate: in `'snap-when-offscreen'` a correction is suppressed while the
129
+ * object is inside the camera frustum — corrections only land when the user
130
+ * is not looking, so the anchor never visibly jumps. The whole AR frame
131
+ * (camera + every anchor) rides one lerped `arWorldGroup.matrix`
132
+ * (`enableArWorldGroupAlignment`), so an on-screen alignment change is
133
+ * absorbed smoothly for the entire view and never needs a per-anchor
134
+ * on-screen snap. The one exception is the first placement of a `skipBootstrap`
135
+ * anchor (see `firstCommitPending`), which must escape the origin even while
136
+ * on-screen. See
137
+ * `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-06-06-anchor-starter-cachehit-jump-investigation.md`.
138
+ *
139
+ * Frame note: `arWorldGroup.matrix` IS the alignment matrix, which maps
140
+ * **AR-odometry NUE → GPS-world NUE**. `object3D.position` is a *local*
141
+ * transform in that AR-odometry frame. To make the object's *world*
142
+ * position equal the GPS-world `nue`, the local target must be
143
+ * `alignment⁻¹ · nue` — writing raw `nue` would double-apply the
144
+ * alignment (world = `alignment · nue`). See
145
+ * `gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md`.
146
+ *
147
+ * Returns silently on missing inputs (no `zeroRef`, or no alignment
148
+ * matrix yet — an AR-local object cannot be placed without knowing the
149
+ * AR↔NUE transform).
150
+ */
151
+ const maybeCommitSteadyState = () => {
152
+ const zero = options.getGpsZeroRef();
153
+ if (zero === null || zero === void 0) return;
154
+ const currentAlignment = options.getAlignmentMatrix();
155
+ if (currentAlignment === null || currentAlignment === void 0) return;
156
+ const targetAlt = "altitude" in gpsPoint && typeof gpsPoint.altitude === "number" ? gpsPoint.altitude : 0;
157
+ const nue = calcRelativeCoordsInMeters(zero, gpsPoint, targetAlt, 0);
158
+ nueToArLocal(currentAlignment, [
159
+ nue[0],
160
+ nue[1],
161
+ nue[2]
162
+ ], scratchTarget);
163
+ options.camera.getWorldPosition(scratchCamWorld);
164
+ options.object3D.getWorldPosition(scratchObjWorld);
165
+ const scale = 1 + 10 * scratchCamWorld.distanceTo(scratchObjWorld) / 100;
166
+ if (options.object3D.position.distanceTo(scratchTarget) >= distanceThreshold * scale) {
167
+ if (!(mode === "snap-when-offscreen" && !firstCommitPending && isObjectInCameraFrustum(options.camera, options.object3D))) {
168
+ options.object3D.position.copy(scratchTarget);
169
+ firstCommitPending = false;
170
+ }
171
+ }
172
+ };
173
+ const tick = (_dt, elapsed) => {
174
+ if (phase === "anchored") {
175
+ maybeCommitSteadyState();
176
+ return;
177
+ }
178
+ if (phaseEnteredAtElapsed === null) {
179
+ phaseEnteredAtElapsed = elapsed;
180
+ lastSampleAtElapsed = elapsed - 1;
181
+ }
182
+ if (elapsed - phaseEnteredAtElapsed < settlingSeconds) return;
183
+ if (lastSampleAtElapsed !== null && elapsed - lastSampleAtElapsed < 1) return;
184
+ const sample = options.getCurrentGpsPoint();
185
+ if (sample === null || sample === void 0) return;
186
+ samples.push(sample);
187
+ lastSampleAtElapsed = elapsed;
188
+ if (samples.length >= sampleCount) commitMedian();
189
+ };
190
+ const unregister = registerFrameUpdate(tick);
191
+ return {
192
+ get phase() {
193
+ return phase;
194
+ },
195
+ get isFullyAnchored() {
196
+ return isFullyAnchored;
197
+ },
198
+ get gpsPoint() {
199
+ return gpsPoint;
200
+ },
201
+ markMovedExternally() {
202
+ enterBootstrap();
203
+ },
204
+ setGpsPoint(point) {
205
+ gpsPoint = point;
206
+ },
207
+ dispose() {
208
+ unregister();
209
+ anchoredObjects.delete(options.object3D);
210
+ },
211
+ __tickForTests(dt, elapsed) {
212
+ tick(dt, elapsed);
213
+ }
214
+ };
215
+ }
216
+ //#endregion
2
217
  export { createGpsAnchor };
@@ -1,2 +1,2 @@
1
- import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-CsLtFv4B.js";
1
+ import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-BM1uGiYV.js";
2
2
  export { COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, GpsCompassCubes, createGpsCompassCubes };
@@ -1,2 +1,2 @@
1
- import { n as GpsEventVisualizer, r as gpsEventVisualizer, t as GpsEventAccuracy } from "../gps-event-markers-BGr-iLvi.js";
1
+ import { n as GpsEventVisualizer, r as gpsEventVisualizer, t as GpsEventAccuracy } from "../gps-event-markers-Dc9FT-q1.js";
2
2
  export { GpsEventAccuracy, GpsEventVisualizer, gpsEventVisualizer };
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "../utils/logger.js";
2
2
  import { disposeMeshArray } from "./three-dispose.js";
3
- import { getArWorldGroup, getScene } from "../ar/webxr-session.js";
3
+ import { c as getArWorldGroup, m as getScene } from "../webxr-session-QhcSSub5.js";
4
4
  import { VIS_COLORS } from "./vis-colors.js";
5
5
  import * as THREE from "three";
6
6
  //#region ../src/visualization/gps-event-markers.ts
@@ -0,0 +1,2 @@
1
+ import { n as createReticleMesh, r as updateReticle, t as HitMatrix } from "../hit-test-reticle-BEzEybxZ.js";
2
+ export { HitMatrix, createReticleMesh, updateReticle };
@@ -0,0 +1,81 @@
1
+ import { WEBXR_TO_NUE } from "../ar/webxr-nue-basis.js";
2
+ import { Matrix4, Mesh, MeshBasicMaterial, RingGeometry } from "three";
3
+ //#region ../src/visualization/hit-test-reticle.ts
4
+ /**
5
+ * Hit-test reticle — the small, deterministic "reticle view-model" shared by
6
+ * the framework's example/starter apps.
7
+ *
8
+ * A faithful port of the reticle from the stock three.js `webxr_ar_hittest`
9
+ * example: a flat ring laid on whatever real-world surface the WebXR
10
+ * `hit-test` API reports under the screen centre. The framework delta is
11
+ * *where* the mesh is parented and *which frame the hit pose is in*: apps add
12
+ * the mesh under `getArWorldGroup()` (NUE local space) rather than the
13
+ * GPS-aligned scene root, so the reticle rides the same lerped `arWorldGroup`
14
+ * alignment as the camera — but the WebXR hit pose arrives in the WebXR
15
+ * reference space, so `updateReticle` applies the `WEBXR_TO_NUE` basis change
16
+ * to it (see that function for the full rationale).
17
+ *
18
+ * The per-frame XR plumbing (requesting the hit-test source, reading
19
+ * `frame.getHitTestResults(...)`) stays in each app's WebXR glue and is
20
+ * verified manually on-device. The two functions here are the unit-tested core:
21
+ * given the latest hit pose (a column-major 4x4 transform matrix) or `null`,
22
+ * drive the mesh's visibility + transform. They are unit tested because that is
23
+ * the logic a porting developer is most likely to get subtly wrong (e.g.
24
+ * forgetting the basis change so the reticle drifts off-centre, forgetting to
25
+ * hide the reticle when no surface is found, or letting Three.js overwrite the
26
+ * matrix).
27
+ */
28
+ const hitPoseMatrix = /* @__PURE__ */ new Matrix4();
29
+ /**
30
+ * Build the reticle mesh: a thin ring oriented flat (rotated to lie in the
31
+ * XZ plane) so it reads as a marker on the ground/wall.
32
+ *
33
+ * `matrixAutoUpdate` is disabled because the reticle's world transform is
34
+ * written wholesale from the hit-test pose every frame; letting Three.js
35
+ * recompose it from position/quaternion/scale would discard that pose.
36
+ *
37
+ * The mesh starts hidden (`visible = false`) — there is no surface yet.
38
+ */
39
+ function createReticleMesh() {
40
+ const reticle = new Mesh(new RingGeometry(.07, .09, 32).rotateX(-Math.PI / 2), new MeshBasicMaterial({ color: 5213439 }));
41
+ reticle.matrixAutoUpdate = false;
42
+ reticle.visible = false;
43
+ return reticle;
44
+ }
45
+ /**
46
+ * Apply the latest hit-test pose to the reticle.
47
+ *
48
+ * - When `matrix` is a 16-element transform, the reticle adopts it (after the
49
+ * WebXR→NUE basis change, see below) and becomes visible.
50
+ * - When `matrix` is `null` (no surface under the screen centre, or the
51
+ * hit-test source is not ready yet), the reticle is hidden.
52
+ *
53
+ * Frame handling — the part a porting developer must not get wrong. The
54
+ * hit-test pose is expressed in the **WebXR reference space** (`X=East, Y=Up,
55
+ * Z=South`), the same frame as the live camera pose. The reticle, however, is
56
+ * parented under `getArWorldGroup()`, whose local space is **NUE** (`X=North,
57
+ * Y=Up, Z=East`) — the camera reaches that frame through the static
58
+ * `basisChangeNode` that holds `WEBXR_TO_NUE`. The reticle has no such
59
+ * intermediate node, so we apply the same basis change here:
60
+ * `reticle.matrix = WEBXR_TO_NUE · hitPose`. The reticle's resulting world pose
61
+ * is then `arWorldGroup.matrix · WEBXR_TO_NUE · hitPose` — identical to the
62
+ * transform chain the camera rides, so it stays pinned under the screen centre.
63
+ *
64
+ * Without the basis change the WebXR coordinates were interpreted as NUE: the
65
+ * Up axis matched (both are Y) but East/North were swapped, so the reticle
66
+ * drifted sideways instead of tracking the screen centre.
67
+ *
68
+ * Works on any `Object3D` (not just the mesh from `createReticleMesh`) so it can
69
+ * be unit tested without a WebGL context.
70
+ */
71
+ function updateReticle(reticle, matrix) {
72
+ if (matrix === null) {
73
+ reticle.visible = false;
74
+ return;
75
+ }
76
+ hitPoseMatrix.fromArray(matrix);
77
+ reticle.matrix.multiplyMatrices(WEBXR_TO_NUE, hitPoseMatrix);
78
+ reticle.visible = true;
79
+ }
80
+ //#endregion
81
+ export { createReticleMesh, updateReticle };
@@ -1,17 +1,19 @@
1
- import { n as createCameraFollower, t as CameraFollower } from "../camera-follower-1k8mnw5J.js";
2
- import { n as createAlignmentLerper, t as AlignmentLerper } from "../alignment-lerper-L1RbWHQ4.js";
3
- import { n as createCss3dRendererManager, t as Css3dRendererManager } from "../css3d-renderer-manager-CkjCb2HW.js";
4
- import { n as MapDataInput, r as buildMapData, t as MapData } from "../map-data-sih4g64v.js";
5
- import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-CsLtFv4B.js";
6
- import { i as isSphereInCameraFrustum, n as isObjectInCameraFrustum, r as isPointInCameraFrustum, t as buildCameraFrustum } from "../frustum-visibility-BHnFrSKQ.js";
7
- import { t as nueToArLocal } from "../index-pOjUCKEP.js";
8
- import { a as GpsAnchorSamplePoint, i as GpsAnchorPhase, n as GpsAnchorMode, o as createGpsAnchor, r as GpsAnchorOptions, t as GpsAnchor } from "../gps-anchor-CnWY61XK.js";
9
- import { n as GpsEventVisualizer, r as gpsEventVisualizer } from "../gps-event-markers-BGr-iLvi.js";
10
- import { a as DEFAULT_Z_OFFSET, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions } from "../leaflet-map-overlay-Cew7XAoO.js";
11
- import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-B8MM8zCR.js";
12
- import { a as addAccuracyCircles, i as AccuracyCircleSample, n as ACCURACY_CIRCLE_STROKE_OPACITY, r as ACCURACY_CIRCLE_WEIGHT, t as ACCURACY_CIRCLE_FILL_OPACITY } from "../accuracy-circles-CsxE_7x1.js";
13
- import { a as MAP_PATH_POLYLINE_OPACITY, c as USER_POSITION_COLOR, i as FUSED_PATH_COLOR, l as drawMapData, n as DrawMapDataOptions, o as MAP_PATH_POLYLINE_WEIGHT, r as DrawnMapData, s as RAW_GPS_COLOR, t as ALIGNMENT_SNAPSHOT_COLOR } from "../map-overlay-draw-B3R1c9Xp.js";
14
- import { a as MapOverlayOptions, c as tileXYToLatLon, i as MapOverlay, n as DEFAULT_MAP_SIZE, o as TextureLoaderInterface, r as DEFAULT_ZOOM, s as latLonToTileXY, t as DEFAULT_HEIGHT_OFFSET } from "../map-overlay-DAmreyA3.js";
15
- import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-C2drkp6f.js";
16
- import { t as VIS_COLORS } from "../vis-colors-CHQhjal3.js";
17
- export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, AccuracyCircleSample, AlignmentLerper, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, CameraFollower, Css3dRendererManager, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, DisposeOptions, DrawMapDataOptions, DrawnMapData, FUSED_PATH_COLOR, GpsAnchor, GpsAnchorMode, GpsAnchorOptions, GpsAnchorPhase, GpsAnchorSamplePoint, GpsCompassCubes, GpsEventVisualizer, LeafletMapOverlay, LeafletMapOverlayOptions, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, MapData, MapDataInput, MapOverlay, MapOverlayOptions, RAW_GPS_COLOR, TextureLoaderInterface, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, disposeMeshArray, disposeObject3D, drawMapData, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon };
1
+ import { n as createCss3dRendererManager, t as Css3dRendererManager } from "../css3d-renderer-manager-ptu_t3B3.js";
2
+ import { n as createCameraFollower, t as CameraFollower } from "../camera-follower-C2mwEn7c.js";
3
+ import { n as createAlignmentLerper, t as AlignmentLerper } from "../alignment-lerper-Dm9G7JJO.js";
4
+ import { n as MapDataInput, r as buildMapData, t as MapData } from "../map-data-CkloBxIl.js";
5
+ import { n as ArWorldGroupAlignmentOptions, r as enableArWorldGroupAlignment, t as ArWorldGroupAlignmentHandle } from "../ar-world-group-alignment-ColHB_5A.js";
6
+ import { i as createGpsCompassCubes, n as COMPASS_CUBE_SIZE, r as GpsCompassCubes, t as COMPASS_CUBE_DISTANCE } from "../gps-compass-cubes-BM1uGiYV.js";
7
+ import { i as isSphereInCameraFrustum, n as isObjectInCameraFrustum, r as isPointInCameraFrustum, t as buildCameraFrustum } from "../frustum-visibility-CmI96ESS.js";
8
+ import { n as worldNueToGps, t as nueToArLocal } from "../frame-conversions-BfsafGWR.js";
9
+ import { n as createReticleMesh, r as updateReticle, t as HitMatrix } from "../hit-test-reticle-BEzEybxZ.js";
10
+ import { a as GpsAnchorSamplePoint, i as GpsAnchorPhase, n as GpsAnchorMode, o as createGpsAnchor, r as GpsAnchorOptions, t as GpsAnchor } from "../gps-anchor-dYeCvvp5.js";
11
+ import { n as GpsEventVisualizer, r as gpsEventVisualizer } from "../gps-event-markers-Dc9FT-q1.js";
12
+ import { a as DEFAULT_Z_OFFSET, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions } from "../leaflet-map-overlay-6CJC-E1l.js";
13
+ import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-BpV-0Dtk.js";
14
+ import { a as addAccuracyCircles, i as AccuracyCircleSample, n as ACCURACY_CIRCLE_STROKE_OPACITY, r as ACCURACY_CIRCLE_WEIGHT, t as ACCURACY_CIRCLE_FILL_OPACITY } from "../accuracy-circles-BpwWAs8G.js";
15
+ import { a as MAP_PATH_POLYLINE_OPACITY, c as USER_POSITION_COLOR, i as FUSED_PATH_COLOR, l as drawMapData, n as DrawMapDataOptions, o as MAP_PATH_POLYLINE_WEIGHT, r as DrawnMapData, s as RAW_GPS_COLOR, t as ALIGNMENT_SNAPSHOT_COLOR } from "../map-overlay-draw-krOKwDAq.js";
16
+ import { a as MapOverlayOptions, c as tileXYToLatLon, i as MapOverlay, n as DEFAULT_MAP_SIZE, o as TextureLoaderInterface, r as DEFAULT_ZOOM, s as latLonToTileXY, t as DEFAULT_HEIGHT_OFFSET } from "../map-overlay-bcogY5fK.js";
17
+ import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-D950t71B.js";
18
+ import { t as VIS_COLORS } from "../vis-colors-D6omC64K.js";
19
+ export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, type AccuracyCircleSample, type AlignmentLerper, type ArWorldGroupAlignmentHandle, type ArWorldGroupAlignmentOptions, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, type CameraFollower, type Css3dRendererManager, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, type DisposeOptions, type DrawMapDataOptions, type DrawnMapData, FUSED_PATH_COLOR, type GpsAnchor, type GpsAnchorMode, type GpsAnchorOptions, type GpsAnchorPhase, type GpsAnchorSamplePoint, type GpsCompassCubes, GpsEventVisualizer, type HitMatrix, LeafletMapOverlay, type LeafletMapOverlayOptions, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, type MapData, type MapDataInput, MapOverlay, type MapOverlayOptions, RAW_GPS_COLOR, type TextureLoaderInterface, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, createReticleMesh, disposeMeshArray, disposeObject3D, drawMapData, enableArWorldGroupAlignment, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon, updateReticle, worldNueToGps };
@@ -6,11 +6,14 @@ import { createAlignmentLerper } from "./alignment-lerper.js";
6
6
  import { VIS_COLORS } from "./vis-colors.js";
7
7
  import { COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, createGpsCompassCubes } from "./gps-compass-cubes.js";
8
8
  import { buildMapData } from "./map-data.js";
9
+ import { enableArWorldGroupAlignment } from "./ar-world-group-alignment.js";
9
10
  import { buildCameraFrustum, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum } from "./frustum-visibility.js";
10
- import { n as nueToArLocal, t as createGpsAnchor } from "../gps-anchor-DjC_3MoT.js";
11
+ import { nueToArLocal, worldNueToGps } from "./frame-conversions.js";
12
+ import { createReticleMesh, updateReticle } from "./hit-test-reticle.js";
13
+ import { createGpsAnchor } from "./gps-anchor.js";
11
14
  import { GpsEventVisualizer, gpsEventVisualizer } from "./gps-event-markers.js";
12
15
  import { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, addAccuracyCircles } from "./accuracy-circles.js";
13
16
  import { ALIGNMENT_SNAPSHOT_COLOR, FUSED_PATH_COLOR, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, RAW_GPS_COLOR, USER_POSITION_COLOR, drawMapData } from "./map-overlay-draw.js";
14
17
  import { DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_WORLD_SIZE, DEFAULT_Z_OFFSET, LeafletMapOverlay } from "./leaflet-map-overlay.js";
15
18
  import { DEFAULT_HEIGHT_OFFSET, DEFAULT_MAP_SIZE, DEFAULT_ZOOM, MapOverlay, latLonToTileXY, tileXYToLatLon } from "./map-overlay.js";
16
- export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, FUSED_PATH_COLOR, GpsEventVisualizer, LeafletMapOverlay, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, MapOverlay, RAW_GPS_COLOR, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, disposeMeshArray, disposeObject3D, drawMapData, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon };
19
+ export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, ALIGNMENT_SNAPSHOT_COLOR, COMPASS_CUBE_DISTANCE, COMPASS_CUBE_SIZE, DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_LERP_RATE, DEFAULT_MAP_SIZE, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, FUSED_PATH_COLOR, GpsEventVisualizer, LeafletMapOverlay, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, MapOverlay, RAW_GPS_COLOR, USER_POSITION_COLOR, VIS_COLORS, addAccuracyCircles, buildCameraFrustum, buildMapData, clampedAlpha, createAlignmentLerper, createCameraFollower, createCss3dRendererManager, createGpsAnchor, createGpsCompassCubes, createReticleMesh, disposeMeshArray, disposeObject3D, drawMapData, enableArWorldGroupAlignment, gpsEventVisualizer, isObjectInCameraFrustum, isPointInCameraFrustum, isSphereInCameraFrustum, latLonToTileXY, nueToArLocal, tileXYToLatLon, updateReticle, worldNueToGps };
@@ -1,2 +1,2 @@
1
- import { a as DEFAULT_Z_OFFSET, i as DEFAULT_ZOOM, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions, t as DEFAULT_HEIGHT_OFFSET } from "../leaflet-map-overlay-Cew7XAoO.js";
1
+ import { a as DEFAULT_Z_OFFSET, i as DEFAULT_ZOOM, n as DEFAULT_LEAFLET_MAP_SIZE_PX, o as LeafletMapOverlay, r as DEFAULT_WORLD_SIZE, s as LeafletMapOverlayOptions, t as DEFAULT_HEIGHT_OFFSET } from "../leaflet-map-overlay-6CJC-E1l.js";
2
2
  export { DEFAULT_HEIGHT_OFFSET, DEFAULT_LEAFLET_MAP_SIZE_PX, DEFAULT_WORLD_SIZE, DEFAULT_ZOOM, DEFAULT_Z_OFFSET, LeafletMapOverlay, LeafletMapOverlayOptions };
@@ -1,2 +1,2 @@
1
- import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-B8MM8zCR.js";
1
+ import { n as clampedAlpha, t as DEFAULT_LERP_RATE } from "../lerp-utils-BpV-0Dtk.js";
2
2
  export { DEFAULT_LERP_RATE, clampedAlpha };
@@ -1,2 +1,2 @@
1
- import { n as MapDataInput, r as buildMapData, t as MapData } from "../map-data-sih4g64v.js";
1
+ import { n as MapDataInput, r as buildMapData, t as MapData } from "../map-data-CkloBxIl.js";
2
2
  export { MapData, MapDataInput, buildMapData };
@@ -1,2 +1,2 @@
1
- import { a as MAP_PATH_POLYLINE_OPACITY, c as USER_POSITION_COLOR, i as FUSED_PATH_COLOR, l as drawMapData, n as DrawMapDataOptions, o as MAP_PATH_POLYLINE_WEIGHT, r as DrawnMapData, s as RAW_GPS_COLOR, t as ALIGNMENT_SNAPSHOT_COLOR } from "../map-overlay-draw-B3R1c9Xp.js";
1
+ import { a as MAP_PATH_POLYLINE_OPACITY, c as USER_POSITION_COLOR, i as FUSED_PATH_COLOR, l as drawMapData, n as DrawMapDataOptions, o as MAP_PATH_POLYLINE_WEIGHT, r as DrawnMapData, s as RAW_GPS_COLOR, t as ALIGNMENT_SNAPSHOT_COLOR } from "../map-overlay-draw-krOKwDAq.js";
2
2
  export { ALIGNMENT_SNAPSHOT_COLOR, DrawMapDataOptions, DrawnMapData, FUSED_PATH_COLOR, MAP_PATH_POLYLINE_OPACITY, MAP_PATH_POLYLINE_WEIGHT, RAW_GPS_COLOR, USER_POSITION_COLOR, drawMapData };
@@ -1,2 +1,2 @@
1
- import { a as MapOverlayOptions, c as tileXYToLatLon, i as MapOverlay, n as DEFAULT_MAP_SIZE, o as TextureLoaderInterface, r as DEFAULT_ZOOM, s as latLonToTileXY, t as DEFAULT_HEIGHT_OFFSET } from "../map-overlay-DAmreyA3.js";
1
+ import { a as MapOverlayOptions, c as tileXYToLatLon, i as MapOverlay, n as DEFAULT_MAP_SIZE, o as TextureLoaderInterface, r as DEFAULT_ZOOM, s as latLonToTileXY, t as DEFAULT_HEIGHT_OFFSET } from "../map-overlay-bcogY5fK.js";
2
2
  export { DEFAULT_HEIGHT_OFFSET, DEFAULT_MAP_SIZE, DEFAULT_ZOOM, MapOverlay, MapOverlayOptions, TextureLoaderInterface, latLonToTileXY, tileXYToLatLon };
@@ -1,2 +1,2 @@
1
- import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-C2drkp6f.js";
1
+ import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-D950t71B.js";
2
2
  export { DisposeOptions, disposeMeshArray, disposeObject3D };
@@ -1,2 +1,2 @@
1
- import { t as VIS_COLORS } from "../vis-colors-CHQhjal3.js";
1
+ import { t as VIS_COLORS } from "../vis-colors-D6omC64K.js";
2
2
  export { VIS_COLORS };
@@ -0,0 +1,23 @@
1
+ import { Matrix4 } from "three";
2
+
3
+ //#region ../src/ar/webxr-nue-basis.d.ts
4
+ /**
5
+ * Constant matrix converting WebXR local-floor coordinates to the internal
6
+ * NUE (North-Up-East) convention.
7
+ *
8
+ * WebXR: X=East, Y=Up, Z=South (right-handed, toward viewer)
9
+ * NUE: X=North, Y=Up, Z=East (right-handed)
10
+ *
11
+ * Mapping: NUE_X = -WebXR_Z, NUE_Y = WebXR_Y, NUE_Z = WebXR_X
12
+ *
13
+ * Row-major:
14
+ * [ 0 0 -1 0 ]
15
+ * [ 0 1 0 0 ]
16
+ * [ 1 0 0 0 ]
17
+ * [ 0 0 0 1 ]
18
+ *
19
+ * Stored column-major (Three.js / gl-matrix convention).
20
+ */
21
+ declare const WEBXR_TO_NUE: Matrix4;
22
+ //#endregion
23
+ export { WEBXR_TO_NUE as t };
@@ -1,9 +1,10 @@
1
- import { i as DepthSample, t as ARPose } from "./ar-types-Ck8LEq8k.js";
2
- import { i as ImageCaptureConfig, t as CapturedImage } from "./image-capture-DZLxHDN9.js";
3
- import { t as Css3dRendererManager } from "./css3d-renderer-manager-CkjCb2HW.js";
4
- import { a as TrackingSliceState, r as ResetTransformData } from "./tracking-slice-utCNND8t.js";
5
- import { t as ArCrashIsolationOptions } from "./recording-options-Dq6IsKAv.js";
6
- import { r as XRCameraLike } from "./xr-camera-texture-BU5YvKAM.js";
1
+ import { i as DepthSample, t as ARPose } from "./ar-types-Dan1MVTi.js";
2
+ import { i as DepthSamplerConfig } from "./depth-sampler-Bhanfwla.js";
3
+ import { t as ArCrashIsolationOptions } from "./recording-options-DJBXuMxV.js";
4
+ import { i as ImageCaptureConfig, t as CapturedImage } from "./image-capture-DZ4HFRgS.js";
5
+ import { a as TrackingSliceState, r as ResetTransformData } from "./tracking-slice-CdK8c-8P.js";
6
+ import { t as Css3dRendererManager } from "./css3d-renderer-manager-ptu_t3B3.js";
7
+ import { r as XRCameraLike } from "./xr-camera-texture-DBwCaKTd.js";
7
8
  import * as THREE from "three";
8
9
  import { OdometryTrackingRestartedPayload } from "gps-plus-slam-js";
9
10
 
@@ -102,15 +103,33 @@ declare function extractPoseFromViewer(pose: XRViewerPose | null): ARPose | null
102
103
  * @returns The latest AR pose, or null if no pose available yet
103
104
  */
104
105
  declare function getCurrentArPose(): ARPose | null;
106
+ /**
107
+ * Opt-in standard WebXR session features that are independent of the
108
+ * crash-isolation diagnostic flags. Kept separate from
109
+ * `ArCrashIsolationOptions` because requesting `hit-test` is a normal app
110
+ * capability, not a crash-isolation toggle.
111
+ */
112
+ interface SessionFeatureOptions {
113
+ /**
114
+ * Request the WebXR `hit-test` feature (as an *optional* feature) so app
115
+ * code can drive a reticle via `registerXrFrameUpdate`. Default `false` —
116
+ * existing recorder/anchor sessions are unaffected.
117
+ */
118
+ requestHitTest?: boolean;
119
+ }
105
120
  /**
106
121
  * Build XR session init options.
107
122
  * Extracted as a pure function for testability.
108
123
  *
109
124
  * @param rootElement - The DOM element for DOM overlay
125
+ * @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
126
+ * depth-sensing, camera-access)
127
+ * @param sessionFeatures - Opt-in standard WebXR features that are independent
128
+ * of crash isolation (currently `requestHitTest`)
110
129
  * @returns XRSessionInit options
111
130
  * @throws Error if rootElement is null
112
131
  */
113
- declare function buildSessionOptions(rootElement: Element | null, isolationOptions?: Partial<ArCrashIsolationOptions>): XRSessionInit;
132
+ declare function buildSessionOptions(rootElement: Element | null, isolationOptions?: Partial<ArCrashIsolationOptions>, sessionFeatures?: SessionFeatureOptions): XRSessionInit;
114
133
  /**
115
134
  * Check if WebXR immersive-ar is supported
116
135
  */
@@ -159,8 +178,11 @@ declare function createSceneHierarchy(): {
159
178
  /**
160
179
  * Initialize the AR session and Three.js renderer.
161
180
  * @param container - DOM element to host the AR canvas and CSS3D overlay.
181
+ * @param isolationOptions - Crash-isolation diagnostic flags.
182
+ * @param sessionFeatures - Opt-in standard WebXR features (e.g.
183
+ * `requestHitTest`) forwarded to the session negotiation.
162
184
  */
163
- declare function initAR(container: HTMLElement, isolationOptions?: Partial<ArCrashIsolationOptions>): Promise<void>;
185
+ declare function initAR(container: HTMLElement, isolationOptions?: Partial<ArCrashIsolationOptions>, sessionFeatures?: SessionFeatureOptions): Promise<void>;
164
186
  /**
165
187
  * Get the current Three.js scene (for adding objects like map)
166
188
  */
@@ -273,10 +295,14 @@ declare function setImageCaptureCallback(onCaptured: (image: CapturedImage) => v
273
295
  /**
274
296
  * Start capturing images during recording.
275
297
  * Must call setImageCaptureCallback first.
276
- * @param config - Optional capture configuration (intervalMs, quality)
277
- * @param resolutionDivisor - Resolution divisor: 1 = full, 2 = half, 4 = quarter (default: 1)
298
+ *
299
+ * @param config - Optional capture configuration. Accepts the whole user
300
+ * image-options section (`intervalMs`, `quality`, `resolutionDivisor`; any
301
+ * extra keys such as `enabled` are ignored). Passing the section as one
302
+ * object means a newly-added option flows through without editing this seam
303
+ * — see `2026-06-12-payload-rebuild-field-drop-audit.md` (F3).
278
304
  */
279
- declare function startImageCapture(config?: Partial<ImageCaptureConfig>, resolutionDivisor?: number): void;
305
+ declare function startImageCapture(config?: Partial<ImageCaptureConfig>): void;
280
306
  /**
281
307
  * Stop capturing images.
282
308
  */
@@ -329,8 +355,14 @@ declare function setDepthCaptureCallback(onCaptured: (sample: DepthSample) => vo
329
355
  /**
330
356
  * Start depth sampling during recording.
331
357
  * Must call setDepthCaptureCallback before initAR.
358
+ *
359
+ * @param config - optional sampler overrides (typically the user's
360
+ * `depth.intervalMs`/`depth.gridSize` recording options); applied via
361
+ * `DepthSampler.updateConfig` before sampling starts. Without this the
362
+ * sampler's own defaults apply — the settings knobs were dead before
363
+ * this parameter existed (occupancy-grid port plan, Iter 6).
332
364
  */
333
- declare function startDepthCapture(): void;
365
+ declare function startDepthCapture(config?: Partial<DepthSamplerConfig>): void;
334
366
  /**
335
367
  * Stop depth sampling.
336
368
  */
@@ -354,4 +386,4 @@ declare function setFrameCallback(callback: (() => void) | null): void;
354
386
  */
355
387
  declare function getLiveCss3dManager(): Css3dRendererManager | null;
356
388
  //#endregion
357
- export { setTrackingCallbacks as A, setArPose as C, setFrameCallback as D, setDepthCaptureCallback as E, startDepthCapture as F, startImageCapture as I, stopDepthCapture as L, setTrackingRecoveredCallback as M, setTrackingStore as N, setImageCaptureCallback as O, shouldLogCameraAccessDiagnostic as P, stopImageCapture as R, resetWebXRState as S, setCamera as T, initAR as _, endARSession as a, nuePositionToWebXR as b, getArPose as c, getCurrentArPose as d, getDepthSampleCount as f, getXrCameraFromPose as g, getScene as h, createSceneHierarchy as i, setTrackingLostCallback as j, setScene as k, getArWorldGroup as l, getLiveCss3dManager as m, applyAlignmentMatrix as n, extractPoseFromViewer as o, getImageCaptureFrameCount as p, buildSessionOptions as r, extractResetTransformData as s, TrackingSubscribableStore as t, getCamera as u, isWebXRSupported as v, setArWorldGroup as w, nueQuaternionToWebXR$1 as x, isXRCameraLike as y };
389
+ export { setScene as A, resetWebXRState as C, setDepthCaptureCallback as D, setCamera as E, shouldLogCameraAccessDiagnostic as F, startDepthCapture as I, startImageCapture as L, setTrackingLostCallback as M, setTrackingRecoveredCallback as N, setFrameCallback as O, setTrackingStore as P, stopDepthCapture as R, nueQuaternionToWebXR$1 as S, setArWorldGroup as T, getXrCameraFromPose as _, createSceneHierarchy as a, isXRCameraLike as b, extractResetTransformData as c, getCamera as d, getCurrentArPose as f, getScene as g, getLiveCss3dManager as h, buildSessionOptions as i, setTrackingCallbacks as j, setImageCaptureCallback as k, getArPose as l, getImageCaptureFrameCount as m, TrackingSubscribableStore as n, endARSession as o, getDepthSampleCount as p, applyAlignmentMatrix as r, extractPoseFromViewer as s, SessionFeatureOptions as t, getArWorldGroup as u, initAR as v, setArPose as w, nuePositionToWebXR as x, isWebXRSupported as y, stopImageCapture as z };