gps-plus-slam-app-framework 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/LICENSE +4 -4
  2. package/README.md +310 -115
  3. package/dist/accuracy-circles-DEKr0Hoh.d.ts +43 -0
  4. package/dist/{app-selectors-CHt7DLuq.d.ts → app-selectors-CR9OWodf.d.ts} +8 -10
  5. package/dist/ar/camera-blit-capture.d.ts +1 -1
  6. package/dist/ar/capability-checker.d.ts +2 -0
  7. package/dist/ar/capability-checker.js +25 -0
  8. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -2
  10. package/dist/ar/chromium-camera-access-workaround.js +197 -8
  11. package/dist/ar/depth-sampler.d.ts +2 -2
  12. package/dist/ar/enable-gps-ar.d.ts +2 -0
  13. package/dist/ar/enable-gps-ar.js +137 -0
  14. package/dist/ar/frame-loop.d.ts +2 -0
  15. package/dist/ar/frame-loop.js +63 -0
  16. package/dist/ar/image-capture.d.ts +1 -1
  17. package/dist/ar/image-capture.js +1 -1
  18. package/dist/ar/index.d.ts +16 -12
  19. package/dist/ar/index.js +8 -4
  20. package/dist/ar/replay-scene.d.ts +1 -1
  21. package/dist/ar/scene-node-names.d.ts +1 -1
  22. package/dist/ar/webxr-session.d.ts +4 -4
  23. package/dist/ar/webxr-session.js +173 -93
  24. package/dist/ar/xr-camera-texture.d.ts +1 -1
  25. package/dist/ar/xr-error-handler.d.ts +1 -1
  26. package/dist/ar/xr-frame-loop.d.ts +2 -0
  27. package/dist/ar/xr-frame-loop.js +69 -0
  28. package/dist/ar-world-group-alignment-Dn4rQk_c.d.ts +43 -0
  29. package/dist/capability-checker-BTCmZRL4.d.ts +43 -0
  30. package/dist/chromium-camera-access-workaround-MifIwK9x.d.ts +137 -0
  31. package/dist/core/index.d.ts +2 -2
  32. package/dist/core/index.js +2 -2
  33. package/dist/create-slam-app-store-B76AGaI0.d.ts +371 -0
  34. package/dist/{depth-sampler-BNCtnEoP.d.ts → depth-sampler-CqrkRr2N.d.ts} +1 -1
  35. package/dist/enable-gps-ar-DNbO0zbg.d.ts +95 -0
  36. package/dist/{file-system-DD5TczSO.d.ts → file-system-TIsDfamK.d.ts} +32 -2
  37. package/dist/frame-conversions-D2EYjeNa.d.ts +54 -0
  38. package/dist/frame-loop-BTeRpDm4.d.ts +47 -0
  39. package/dist/frustum-visibility-DGnJqls0.d.ts +58 -0
  40. package/dist/{fused-path-DtqCSqJh.d.ts → fused-path-BdVHmDW9.d.ts} +1 -1
  41. package/dist/geo/h3-proximity.d.ts +1 -1
  42. package/dist/geo/index.d.ts +1 -1
  43. package/dist/{geo-types-Xx-LVcnw.d.ts → geo-types-clgzl8b5.d.ts} +16 -1
  44. package/dist/{gps-GKc6_Cly.d.ts → gps-B7AlMPz5.d.ts} +1 -1
  45. package/dist/gps-anchor-CISLPQIb.d.ts +59 -0
  46. package/dist/{gps-ar-pose-sampler-D_Zso40V.d.ts → gps-ar-pose-sampler-BypXZUqC.d.ts} +1 -1
  47. package/dist/{gps-event-coordinator-fwvmVfku.d.ts → gps-event-coordinator-BKVZbS1M.d.ts} +3 -3
  48. package/dist/gps-event-markers-BlHq3jYV.d.ts +103 -0
  49. package/dist/hit-test-reticle-BGXOxrUh.d.ts +45 -0
  50. package/dist/{image-capture-2pKjYUS6.d.ts → image-capture-BXZUmj7j.d.ts} +1 -1
  51. package/dist/index--ldLph4V.d.ts +2 -0
  52. package/dist/index-22v8MXJX.d.ts +23 -0
  53. package/dist/index-BB2KEWbN2.d.ts +37 -0
  54. package/dist/index.d.ts +64 -52
  55. package/dist/index.js +21 -8
  56. package/dist/{leaflet-map-overlay-CQZcnxbc.d.ts → leaflet-map-overlay-jNt-kcUr.d.ts} +22 -11
  57. package/dist/{logger-B81iwxx0.d.ts → logger-BZ44Dhd3.d.ts} +7 -1
  58. package/dist/map-data-BGIBM5rv.d.ts +49 -0
  59. package/dist/map-overlay-draw-DcGs9UV3.d.ts +42 -0
  60. package/dist/{null-storage-backend-C0TiwwzQ.d.ts → null-storage-backend-BPQJSaMw.d.ts} +2 -2
  61. package/dist/{opfs-storage-backend-Gt3rd76D.d.ts → opfs-storage-backend-u4QKvgvP.d.ts} +2 -2
  62. package/dist/{permission-checker-Cu3nbH1S.d.ts → permission-checker-CfV7INCa.d.ts} +41 -1
  63. package/dist/persistence-middleware-xB5sTllJ.d.ts +55 -0
  64. package/dist/{recording-options-BYUFX8iU.d.ts → recording-options-BSFpBSZH.d.ts} +10 -4
  65. package/dist/{recording-replayer-BFhFs7Mc.d.ts → recording-replayer-CTL2cUE0.d.ts} +2 -2
  66. package/dist/{recording-slice-BiktUIWF.d.ts → recording-slice-Cb888P6d.d.ts} +2 -2
  67. package/dist/{replay-engine-B88Va62x.d.ts → replay-engine-B-5GIZtc.d.ts} +1 -1
  68. package/dist/{replay-scene-DtUm6bsr.d.ts → replay-scene-DkNfldPh.d.ts} +3 -3
  69. package/dist/sensors/gps-error-handler.d.ts +1 -1
  70. package/dist/sensors/gps.d.ts +1 -1
  71. package/dist/sensors/gps.js +2 -2
  72. package/dist/sensors/index.d.ts +3 -3
  73. package/dist/sensors/index.js +1 -1
  74. package/dist/sensors/permission-checker.d.ts +2 -2
  75. package/dist/sensors/permission-checker.js +79 -1
  76. package/dist/session-disposers-M-oashRH.js +57 -0
  77. package/dist/state/app-selectors.d.ts +2 -2
  78. package/dist/state/app-selectors.js +33 -3
  79. package/dist/state/combined-root-state.d.ts +2 -0
  80. package/dist/state/combined-root-state.js +1 -0
  81. package/dist/state/create-slam-app-store.d.ts +1 -1
  82. package/dist/state/create-slam-app-store.js +27 -13
  83. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  84. package/dist/state/gps-event-coordinator.d.ts +1 -1
  85. package/dist/state/index.d.ts +17 -16
  86. package/dist/state/index.js +6 -4
  87. package/dist/state/persistence-middleware.d.ts +2 -2
  88. package/dist/state/persistence-middleware.js +43 -9
  89. package/dist/state/recording-options.d.ts +1 -1
  90. package/dist/state/recording-replayer.d.ts +2 -2
  91. package/dist/state/recording-slice.d.ts +1 -1
  92. package/dist/state/replay-engine.d.ts +1 -1
  93. package/dist/state/store-subscribers.d.ts +2 -2
  94. package/dist/state/store-subscribers.js +38 -12
  95. package/dist/state/subscribe-to-selector.d.ts +1 -1
  96. package/dist/state/tracking-quality.d.ts +2 -0
  97. package/dist/state/tracking-quality.js +723 -0
  98. package/dist/state/tracking-slice.d.ts +2 -0
  99. package/dist/state/tracking-slice.js +147 -0
  100. package/dist/state-BkIfpiyA.js +87 -0
  101. package/dist/storage/file-system-utils.d.ts +1 -1
  102. package/dist/storage/file-system.d.ts +2 -2
  103. package/dist/storage/file-system.js +52 -1
  104. package/dist/storage/index.d.ts +8 -8
  105. package/dist/storage/null-storage-backend.d.ts +1 -1
  106. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  107. package/dist/storage/opfs-storage.d.ts +1 -1
  108. package/dist/storage/storage-backend.d.ts +1 -1
  109. package/dist/storage/zip-export.d.ts +1 -1
  110. package/dist/storage/zip-reader.d.ts +1 -1
  111. package/dist/storage/zip-reader.js +8 -4
  112. package/dist/{storage-backend-yVnuWGTr.d.ts → storage-backend-yDSKafAQ.d.ts} +1 -1
  113. package/dist/{store-subscribers-DKKHld49.d.ts → store-subscribers-swlbtg_z.d.ts} +31 -6
  114. package/dist/{subscribe-to-selector-ghDKYtap.d.ts → subscribe-to-selector--KQpgLXG.d.ts} +1 -1
  115. package/dist/test-utils/browser-mocks.d.ts +1 -1
  116. package/dist/test-utils/zip-round-trip-helpers.d.ts +7 -1
  117. package/dist/test-utils/zip-round-trip-helpers.js +2 -1
  118. package/dist/tracking-slice-CDREeoZ_.d.ts +86 -0
  119. package/dist/types/ar-types.d.ts +1 -1
  120. package/dist/types/geo-types.d.ts +2 -2
  121. package/dist/types/index.d.ts +3 -3
  122. package/dist/utils/concurrency.d.ts +1 -1
  123. package/dist/utils/failure-tracker.d.ts +1 -1
  124. package/dist/utils/format-file-size.d.ts +1 -1
  125. package/dist/utils/fused-path.d.ts +1 -1
  126. package/dist/utils/index.d.ts +6 -6
  127. package/dist/utils/list-formatter.d.ts +1 -1
  128. package/dist/utils/logger.d.ts +1 -1
  129. package/dist/utils/logger.js +68 -8
  130. package/dist/visualization/accuracy-circles.d.ts +2 -0
  131. package/dist/visualization/accuracy-circles.js +57 -0
  132. package/dist/visualization/alignment-lerper.d.ts +1 -1
  133. package/dist/visualization/ar-world-group-alignment.d.ts +2 -0
  134. package/dist/visualization/ar-world-group-alignment.js +51 -0
  135. package/dist/visualization/camera-follower.d.ts +1 -1
  136. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  137. package/dist/visualization/frame-conversions.d.ts +2 -0
  138. package/dist/visualization/frame-conversions.js +88 -0
  139. package/dist/visualization/frustum-visibility.d.ts +2 -0
  140. package/dist/visualization/frustum-visibility.js +139 -0
  141. package/dist/visualization/gps-anchor.d.ts +2 -0
  142. package/dist/visualization/gps-anchor.js +217 -0
  143. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  144. package/dist/visualization/gps-event-markers.d.ts +2 -2
  145. package/dist/visualization/gps-event-markers.js +69 -2
  146. package/dist/visualization/hit-test-reticle.d.ts +2 -0
  147. package/dist/visualization/hit-test-reticle.js +81 -0
  148. package/dist/visualization/index.d.ts +19 -12
  149. package/dist/visualization/index.js +9 -2
  150. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  151. package/dist/visualization/leaflet-map-overlay.js +33 -79
  152. package/dist/visualization/lerp-utils.d.ts +1 -1
  153. package/dist/visualization/map-data.d.ts +2 -0
  154. package/dist/visualization/map-data.js +33 -0
  155. package/dist/visualization/map-overlay-draw.d.ts +2 -0
  156. package/dist/visualization/map-overlay-draw.js +107 -0
  157. package/dist/visualization/map-overlay.d.ts +1 -1
  158. package/dist/visualization/three-dispose.d.ts +1 -1
  159. package/dist/visualization/vis-colors.d.ts +1 -1
  160. package/dist/webxr-nue-basis-BbdqhqY6.js +54 -0
  161. package/dist/{webxr-session-COlSrXdL.d.ts → webxr-session-BnArFCew.d.ts} +76 -14
  162. package/dist/xr-frame-loop-BKckC7xC.d.ts +70 -0
  163. package/dist/{zip-reader-KgJEXbca.d.ts → zip-reader-B2lzN8F5.d.ts} +5 -0
  164. package/package.json +16 -16
  165. package/dist/ar/tracking-state.d.ts +0 -2
  166. package/dist/ar/tracking-state.js +0 -164
  167. package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +0 -61
  168. package/dist/combined-root-state-B3SvPNjU.d.ts +0 -6
  169. package/dist/create-slam-app-store-DBm7g2Zd.d.ts +0 -85
  170. package/dist/gps-anchored-mesh-manager-CovAFK4g.d.ts +0 -56
  171. package/dist/gps-event-markers-sVyO9utS.d.ts +0 -67
  172. package/dist/persistence-middleware-z-75iisb.d.ts +0 -31
  173. package/dist/tracking-state-B9QfuZCg.d.ts +0 -101
  174. package/dist/visualization/gps-anchored-mesh-manager.d.ts +0 -2
  175. package/dist/visualization/gps-anchored-mesh-manager.js +0 -128
  176. /package/dist/{alignment-lerper-D9BeDwZX.d.ts → alignment-lerper-CCZJf-Xb.d.ts} +0 -0
  177. /package/dist/{ar-types-B-ORgk6Z.d.ts → ar-types-isPsQptb.d.ts} +0 -0
  178. /package/dist/{camera-blit-capture-DHuyrrxr.d.ts → camera-blit-capture-75WWa9Xb.d.ts} +0 -0
  179. /package/dist/{camera-follower-BMqPddxx.d.ts → camera-follower-B-nS1sr6.d.ts} +0 -0
  180. /package/dist/{capture-failure-tracker-2Q6oI2uQ.d.ts → capture-failure-tracker-D4HYJNOj.d.ts} +0 -0
  181. /package/dist/{concurrency-p6gB30fT.d.ts → concurrency-Bsmv53qw.d.ts} +0 -0
  182. /package/dist/{css3d-renderer-manager-rgLMVAvd.d.ts → css3d-renderer-manager-EDED3nvg.d.ts} +0 -0
  183. /package/dist/{failure-tracker-1sQ_Cgyw.d.ts → failure-tracker-D7ELffm0.d.ts} +0 -0
  184. /package/dist/{file-system-utils-BH8FdyIu.d.ts → file-system-utils-BH6uIs2i.d.ts} +0 -0
  185. /package/dist/{format-file-size-onGq0k1Q.d.ts → format-file-size-B7gJb3Md.d.ts} +0 -0
  186. /package/dist/{gps-compass-cubes-jRIJMOuh.d.ts → gps-compass-cubes-Ys6Hf1nc.d.ts} +0 -0
  187. /package/dist/{gps-error-handler-CH1S528z.d.ts → gps-error-handler-BLDhUlw5.d.ts} +0 -0
  188. /package/dist/{h3-proximity-GwesPTFT.d.ts → h3-proximity-DJnghtir.d.ts} +0 -0
  189. /package/dist/{lerp-utils-CZgQlrmu.d.ts → lerp-utils-DNcSmKVX.d.ts} +0 -0
  190. /package/dist/{list-formatter-DKQQVLcl.d.ts → list-formatter-CsqcU4v5.d.ts} +0 -0
  191. /package/dist/{map-overlay-C9ifl_4-.d.ts → map-overlay-yrFsUtGv.d.ts} +0 -0
  192. /package/dist/{opfs-storage-Dlq1I_Gb.d.ts → opfs-storage-LsLY6VZV.d.ts} +0 -0
  193. /package/dist/{scene-node-names-DPsaE2z3.d.ts → scene-node-names-IHW7HR4S.d.ts} +0 -0
  194. /package/dist/{three-dispose-qt_r5wR1.d.ts → three-dispose-WTBAfaWU.d.ts} +0 -0
  195. /package/dist/{vis-colors-BKkdTIKb.d.ts → vis-colors-Djnyg_UH.d.ts} +0 -0
  196. /package/dist/{xr-camera-texture-BbukNE79.d.ts → xr-camera-texture-BU5YvKAM.d.ts} +0 -0
  197. /package/dist/{xr-error-handler-DTHfmvrl.d.ts → xr-error-handler-DcUFshNF.d.ts} +0 -0
  198. /package/dist/{zip-export-BjrQPv0X.d.ts → zip-export-C0_ErAqT.d.ts} +0 -0
@@ -0,0 +1,33 @@
1
+ import { computeFusedPath } from "../utils/fused-path.js";
2
+ //#region ../src/visualization/map-data.ts
3
+ /**
4
+ * Build a {@link MapData} snapshot from store-derived inputs.
5
+ *
6
+ * Pure and free of Leaflet/Three dependencies. The fused path is always
7
+ * derived via {@link computeFusedPath} (D2); when the matrix or zero reference
8
+ * is missing it is an empty array.
9
+ *
10
+ * @param input - Optional trajectory slices (see {@link MapDataInput}).
11
+ * @returns A fully-resolved {@link MapData} with all arrays defensively copied.
12
+ */
13
+ function buildMapData(input) {
14
+ const rawGpsPath = input.rawGpsPath ? [...input.rawGpsPath] : [];
15
+ const alignmentSnapshots = input.alignmentSnapshots ? [...input.alignmentSnapshots] : [];
16
+ const fusedPath = computeFusedPath({
17
+ odometryPositions: input.odometryPositions ?? [],
18
+ alignmentMatrix: input.alignmentMatrix ?? null,
19
+ zeroRef: input.zeroRef ?? null
20
+ });
21
+ const lastRaw = rawGpsPath[rawGpsPath.length - 1];
22
+ return {
23
+ userPosition: input.userPosition !== void 0 ? input.userPosition : lastRaw ? {
24
+ lat: lastRaw.lat,
25
+ lng: lastRaw.lng
26
+ } : null,
27
+ rawGpsPath,
28
+ fusedPath,
29
+ alignmentSnapshots
30
+ };
31
+ }
32
+ //#endregion
33
+ export { buildMapData };
@@ -0,0 +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-DcGs9UV3.js";
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 };
@@ -0,0 +1,107 @@
1
+ import { VIS_COLORS } from "./vis-colors.js";
2
+ import { addAccuracyCircles } from "./accuracy-circles.js";
3
+ import L from "leaflet";
4
+ //#region ../src/visualization/map-overlay-draw.ts
5
+ /**
6
+ * Shared map-overlay-drawing module.
7
+ *
8
+ * `drawMapData` is the SINGLE Leaflet drawing routine rendered by BOTH map
9
+ * consumers — the live/replay 3D overlay (`LeafletMapOverlay`) and the 2D
10
+ * session-summary map (`createSummaryMap`). Centralising the drawing here is
11
+ * Phase 3 of the map-system review and the fix for Findings 1 & 4 of the
12
+ * unified-trajectory-map user feedback: previously each renderer drew the
13
+ * trajectory by hand and the two visibly diverged.
14
+ *
15
+ * It consumes the resolved {@link MapData} produced by `buildMapData` and adds,
16
+ * in this order:
17
+ * 1. per-event accuracy circles for the raw GPS path (drawn first so the
18
+ * polyline stays on top), then the raw GPS polyline;
19
+ * 2. the fused (SLAM+GPS) polyline;
20
+ * 3. the alignment-snapshot polyline;
21
+ * 4. (optional) a user-position marker.
22
+ *
23
+ * SCOPE: this module draws only the genuinely-shared SLAM/GPS trajectory
24
+ * layers. Reference-point markers are a RECORDER concept and are drawn by the
25
+ * recorder-owned `ui/draw-ref-point-markers.ts` helper (called from both the
26
+ * summary map and the live overlay wiring), so the two maps stay identical
27
+ * while the framework remains ref-point-agnostic. See
28
+ * gps-plus-slam/GpsPlusSlamJs_Docs/docs/2026-05-31-unified-trajectory-map-phase3-plan.md
29
+ * § Step 5.
30
+ *
31
+ * The caller owns map creation, tile layer, `fitBounds`, resize handling and
32
+ * fullscreen — this module only draws data layers and reports the accumulated
33
+ * bounds so the caller can frame the view.
34
+ */
35
+ /** Raw GPS polyline + accuracy-circle color (yellow). */
36
+ const RAW_GPS_COLOR = VIS_COLORS.RAW_GPS.css;
37
+ /** Fused SLAM+GPS polyline color (cyan). */
38
+ const FUSED_PATH_COLOR = VIS_COLORS.FUSED_VIO.css;
39
+ /** Alignment-snapshot polyline color (red). */
40
+ const ALIGNMENT_SNAPSHOT_COLOR = VIS_COLORS.ALIGNMENT_SNAPSHOT.css;
41
+ /** User-position marker color (blue). */
42
+ const USER_POSITION_COLOR = VIS_COLORS.USER_POSITION.css;
43
+ /** Polyline weight (px) — matches the recorder's `PATH_POLYLINE_WEIGHT`. */
44
+ const MAP_PATH_POLYLINE_WEIGHT = 3;
45
+ /** Polyline opacity — matches the recorder's `PATH_POLYLINE_OPACITY`. */
46
+ const MAP_PATH_POLYLINE_OPACITY = .8;
47
+ /**
48
+ * Draw a {@link MapData} snapshot onto an existing Leaflet map.
49
+ *
50
+ * @param map - Target map (already created with a tile layer by the caller).
51
+ * @param data - Resolved trajectory data from `buildMapData`.
52
+ * @param options - Optional layer toggles (see {@link DrawMapDataOptions}).
53
+ * @returns The created layers and the bounds spanning every drawn coordinate.
54
+ */
55
+ function drawMapData(map, data, options = {}) {
56
+ const layers = [];
57
+ const bounds = L.latLngBounds([]);
58
+ if (data.rawGpsPath.length > 0) {
59
+ const rawLatLngs = data.rawGpsPath.map((p) => [p.lat, p.lng]);
60
+ const circles = addAccuracyCircles(map, data.rawGpsPath, RAW_GPS_COLOR);
61
+ layers.push(...circles);
62
+ const rawPolyline = L.polyline(rawLatLngs, {
63
+ color: RAW_GPS_COLOR,
64
+ weight: 3,
65
+ opacity: MAP_PATH_POLYLINE_OPACITY
66
+ }).addTo(map);
67
+ layers.push(rawPolyline);
68
+ for (const ll of rawLatLngs) bounds.extend(ll);
69
+ }
70
+ if (data.fusedPath.length > 0) {
71
+ const fusedLatLngs = data.fusedPath.map((p) => [p.lat, p.lng]);
72
+ const fusedPolyline = L.polyline(fusedLatLngs, {
73
+ color: FUSED_PATH_COLOR,
74
+ weight: 3,
75
+ opacity: MAP_PATH_POLYLINE_OPACITY
76
+ }).addTo(map);
77
+ layers.push(fusedPolyline);
78
+ for (const ll of fusedLatLngs) bounds.extend(ll);
79
+ }
80
+ if (data.alignmentSnapshots.length > 0) {
81
+ const snapshotLatLngs = data.alignmentSnapshots.map((p) => [p.lat, p.lng]);
82
+ const snapshotPolyline = L.polyline(snapshotLatLngs, {
83
+ color: ALIGNMENT_SNAPSHOT_COLOR,
84
+ weight: 3,
85
+ opacity: MAP_PATH_POLYLINE_OPACITY
86
+ }).addTo(map);
87
+ layers.push(snapshotPolyline);
88
+ for (const ll of snapshotLatLngs) bounds.extend(ll);
89
+ }
90
+ if (options.showUserPosition && data.userPosition) {
91
+ const icon = L.divIcon({
92
+ className: "map-overlay-user-position",
93
+ html: `<div style="background:${USER_POSITION_COLOR};width:14px;height:14px;border-radius:50%;border:2px solid white;"></div>`,
94
+ iconSize: [18, 18],
95
+ iconAnchor: [9, 9]
96
+ });
97
+ const marker = L.marker([data.userPosition.lat, data.userPosition.lng], { icon }).addTo(map);
98
+ layers.push(marker);
99
+ bounds.extend([data.userPosition.lat, data.userPosition.lng]);
100
+ }
101
+ return {
102
+ layers,
103
+ bounds
104
+ };
105
+ }
106
+ //#endregion
107
+ export { ALIGNMENT_SNAPSHOT_COLOR, 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-C9ifl_4-.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-yrFsUtGv.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-qt_r5wR1.js";
1
+ import { n as disposeMeshArray, r as disposeObject3D, t as DisposeOptions } from "../three-dispose-WTBAfaWU.js";
2
2
  export { DisposeOptions, disposeMeshArray, disposeObject3D };
@@ -1,2 +1,2 @@
1
- import { t as VIS_COLORS } from "../vis-colors-BKkdTIKb.js";
1
+ import { t as VIS_COLORS } from "../vis-colors-Djnyg_UH.js";
2
2
  export { VIS_COLORS };
@@ -0,0 +1,54 @@
1
+ import { Matrix4 } from "three";
2
+ //#region ../src/ar/webxr-nue-basis.ts
3
+ /**
4
+ * `webxr-nue-basis` — the single source of truth for the WebXR↔NUE basis
5
+ * change.
6
+ *
7
+ * The framework's scene graph keeps GPS-world content in the **NUE**
8
+ * convention (X=North, Y=Up, Z=East) while WebXR reports poses in its own
9
+ * convention (X=East, Y=Up, Z=South). `WEBXR_TO_NUE` converts a transform
10
+ * expressed in the WebXR reference space into the NUE frame.
11
+ *
12
+ * It lives in its own tiny module (depending only on three) so it can be shared
13
+ * by both `webxr-session.ts` (which parents it permanently as the static
14
+ * `basisChangeNode`) and `visualization/hit-test-reticle.ts` (which applies it
15
+ * to the live hit-test pose) without either pulling the other's heavy
16
+ * transitive dependencies into a unit-test import graph.
17
+ */
18
+ /**
19
+ * Constant matrix converting WebXR local-floor coordinates to the internal
20
+ * NUE (North-Up-East) convention.
21
+ *
22
+ * WebXR: X=East, Y=Up, Z=South (right-handed, toward viewer)
23
+ * NUE: X=North, Y=Up, Z=East (right-handed)
24
+ *
25
+ * Mapping: NUE_X = -WebXR_Z, NUE_Y = WebXR_Y, NUE_Z = WebXR_X
26
+ *
27
+ * Row-major:
28
+ * [ 0 0 -1 0 ]
29
+ * [ 0 1 0 0 ]
30
+ * [ 1 0 0 0 ]
31
+ * [ 0 0 0 1 ]
32
+ *
33
+ * Stored column-major (Three.js / gl-matrix convention).
34
+ */
35
+ const WEBXR_TO_NUE = /* @__PURE__ */ new Matrix4().fromArray([
36
+ 0,
37
+ 0,
38
+ 1,
39
+ 0,
40
+ 0,
41
+ 1,
42
+ 0,
43
+ 0,
44
+ -1,
45
+ 0,
46
+ 0,
47
+ 0,
48
+ 0,
49
+ 0,
50
+ 0,
51
+ 1
52
+ ]);
53
+ //#endregion
54
+ export { WEBXR_TO_NUE as t };
@@ -1,13 +1,30 @@
1
- import { i as DepthSample, t as ARPose } from "./ar-types-B-ORgk6Z.js";
2
- import { i as ImageCaptureConfig, t as CapturedImage } from "./image-capture-2pKjYUS6.js";
3
- import { t as Css3dRendererManager } from "./css3d-renderer-manager-rgLMVAvd.js";
4
- import { n as ResetTransformData } from "./tracking-state-B9QfuZCg.js";
5
- import { t as ArCrashIsolationOptions } from "./recording-options-BYUFX8iU.js";
6
- import { r as XRCameraLike } from "./xr-camera-texture-BbukNE79.js";
1
+ import { i as DepthSample, t as ARPose } from "./ar-types-isPsQptb.js";
2
+ import { t as ArCrashIsolationOptions } from "./recording-options-BSFpBSZH.js";
3
+ import { i as ImageCaptureConfig, t as CapturedImage } from "./image-capture-BXZUmj7j.js";
4
+ import { a as TrackingSliceState, r as ResetTransformData } from "./tracking-slice-CDREeoZ_.js";
5
+ import { t as Css3dRendererManager } from "./css3d-renderer-manager-EDED3nvg.js";
6
+ import { r as XRCameraLike } from "./xr-camera-texture-BU5YvKAM.js";
7
7
  import * as THREE from "three";
8
8
  import { OdometryTrackingRestartedPayload } from "gps-plus-slam-js";
9
9
 
10
10
  //#region ../src/ar/webxr-session.d.ts
11
+ /**
12
+ * Minimal subscribable-store contract the tracking pipeline needs:
13
+ * dispatch the slice actions, read the slice for the restart payload, and
14
+ * subscribe to phase transitions. Structurally compatible with the full
15
+ * `SlamAppStore` (and any test double) without coupling this module to the
16
+ * factory's exact generics.
17
+ */
18
+ interface TrackingSubscribableStore {
19
+ dispatch: (action: {
20
+ type: string;
21
+ payload?: unknown;
22
+ }) => unknown;
23
+ getState: () => {
24
+ tracking: TrackingSliceState;
25
+ };
26
+ subscribe: (listener: () => void) => () => void;
27
+ }
11
28
  declare function isXRCameraLike(value: unknown): value is XRCameraLike;
12
29
  /**
13
30
  * Extract a validated XRCameraLike from an XRViewerPose.
@@ -85,15 +102,33 @@ declare function extractPoseFromViewer(pose: XRViewerPose | null): ARPose | null
85
102
  * @returns The latest AR pose, or null if no pose available yet
86
103
  */
87
104
  declare function getCurrentArPose(): ARPose | null;
105
+ /**
106
+ * Opt-in standard WebXR session features that are independent of the
107
+ * crash-isolation diagnostic flags. Kept separate from
108
+ * `ArCrashIsolationOptions` because requesting `hit-test` is a normal app
109
+ * capability, not a crash-isolation toggle.
110
+ */
111
+ interface SessionFeatureOptions {
112
+ /**
113
+ * Request the WebXR `hit-test` feature (as an *optional* feature) so app
114
+ * code can drive a reticle via `registerXrFrameUpdate`. Default `false` —
115
+ * existing recorder/anchor sessions are unaffected.
116
+ */
117
+ requestHitTest?: boolean;
118
+ }
88
119
  /**
89
120
  * Build XR session init options.
90
121
  * Extracted as a pure function for testability.
91
122
  *
92
123
  * @param rootElement - The DOM element for DOM overlay
124
+ * @param isolationOptions - Crash-isolation diagnostic flags (DOM overlay,
125
+ * depth-sensing, camera-access)
126
+ * @param sessionFeatures - Opt-in standard WebXR features that are independent
127
+ * of crash isolation (currently `requestHitTest`)
93
128
  * @returns XRSessionInit options
94
129
  * @throws Error if rootElement is null
95
130
  */
96
- declare function buildSessionOptions(rootElement: Element | null, isolationOptions?: Partial<ArCrashIsolationOptions>): XRSessionInit;
131
+ declare function buildSessionOptions(rootElement: Element | null, isolationOptions?: Partial<ArCrashIsolationOptions>, sessionFeatures?: SessionFeatureOptions): XRSessionInit;
97
132
  /**
98
133
  * Check if WebXR immersive-ar is supported
99
134
  */
@@ -114,8 +149,18 @@ declare function isWebXRSupported(): Promise<boolean>;
114
149
  * basisChangeNode is a static scene-graph node that holds the WEBXR_TO_NUE
115
150
  * basis-change matrix permanently (matrixAutoUpdate=false). Moving it here
116
151
  * instead of composing it in applyAlignmentMatrix() keeps arWorldGroup's
117
- * local space as NUE: objects added directly to arWorldGroup can be placed
118
- * at [1,0,0]=North, [0,0,1]=East without any WebXR↔NUE conversion.
152
+ * local space in the **NUE axis convention** (X=North, Y=Up, Z=East), so no
153
+ * WebXR↔NUE swizzle is needed for children.
154
+ *
155
+ * CAUTION — two NUE frames: arWorldGroup's local space is the *AR-odometry*
156
+ * NUE frame, i.e. the **domain** of the alignment matrix, NOT the GPS-world
157
+ * NUE frame of the scene root. Only content authored in AR-odometry
158
+ * coordinates (e.g. the camera subtree) may be placed with raw local values.
159
+ * GPS-world content (a lat/lon → NUE point) is expressed in the scene-root
160
+ * frame and must be pre-multiplied by alignment⁻¹ before being used as a
161
+ * local position under arWorldGroup — see createGpsAnchor and the
162
+ * alignment-frame bug doc
163
+ * (GpsPlusSlamJs_Docs/docs/2026-05-31-gps-anchor-alignment-frame-bug.md).
119
164
  *
120
165
  * - Recording: arpose stays at identity; WebXRManager writes to camera.
121
166
  * - Replay: arpose receives recorded odomPosition/odomRotation;
@@ -132,8 +177,11 @@ declare function createSceneHierarchy(): {
132
177
  /**
133
178
  * Initialize the AR session and Three.js renderer.
134
179
  * @param container - DOM element to host the AR canvas and CSS3D overlay.
180
+ * @param isolationOptions - Crash-isolation diagnostic flags.
181
+ * @param sessionFeatures - Opt-in standard WebXR features (e.g.
182
+ * `requestHitTest`) forwarded to the session negotiation.
135
183
  */
136
- declare function initAR(container: HTMLElement, isolationOptions?: Partial<ArCrashIsolationOptions>): Promise<void>;
184
+ declare function initAR(container: HTMLElement, isolationOptions?: Partial<ArCrashIsolationOptions>, sessionFeatures?: SessionFeatureOptions): Promise<void>;
137
185
  /**
138
186
  * Get the current Three.js scene (for adding objects like map)
139
187
  */
@@ -225,9 +273,12 @@ declare function nueQuaternionToWebXR$1(nue: readonly number[]): readonly [numbe
225
273
  /**
226
274
  * End the current XR session and clean up all resources.
227
275
  *
228
- * Stops the animation loop, disposes the WebGL renderer, removes the
229
- * canvas from the DOM, and tears down the CSS3D overlay. This is the
230
- * production cleanup path call it when the AR experience is finished.
276
+ * Stops the animation loop, ends the XR session, then delegates the full
277
+ * teardown to {@link resetWebXRState} so every module-level reference is
278
+ * cleared (renderer/scene/camera, image-capture, depth, the tracking-phase
279
+ * subscription, the frame-update registry, diagnostics, blit resources).
280
+ * This is the production cleanup path — call it when the AR experience is
281
+ * finished.
231
282
  */
232
283
  declare function endARSession(): Promise<void>;
233
284
  /**
@@ -255,6 +306,17 @@ declare function stopImageCapture(): void;
255
306
  * Get the current image capture frame count.
256
307
  */
257
308
  declare function getImageCaptureFrameCount(): number;
309
+ /**
310
+ * Inject the Redux store used by the tracking-state slice pipeline.
311
+ *
312
+ * MUST be called before `initAR()` whenever the host also wires tracking
313
+ * callbacks via `setTrackingCallbacks`. Without a store the tracking
314
+ * pipeline silently no-ops.
315
+ *
316
+ * @param store — any store satisfying {@link TrackingSubscribableStore}.
317
+ * `null` clears the binding (useful for teardown in tests).
318
+ */
319
+ declare function setTrackingStore(store: TrackingSubscribableStore | null): void;
258
320
  /**
259
321
  * Set up tracking state callbacks.
260
322
  * Call this BEFORE initAR() to enable tracking restart detection.
@@ -313,4 +375,4 @@ declare function setFrameCallback(callback: (() => void) | null): void;
313
375
  */
314
376
  declare function getLiveCss3dManager(): Css3dRendererManager | null;
315
377
  //#endregion
316
- export { setTrackingLostCallback as A, setArWorldGroup as C, setImageCaptureCallback as D, setFrameCallback as E, stopDepthCapture as F, stopImageCapture as I, shouldLogCameraAccessDiagnostic as M, startDepthCapture as N, setScene as O, startImageCapture as P, setArPose as S, setDepthCaptureCallback as T, isWebXRSupported as _, extractPoseFromViewer as a, nueQuaternionToWebXR$1 as b, getArWorldGroup as c, getDepthSampleCount as d, getImageCaptureFrameCount as f, initAR as g, getXrCameraFromPose as h, endARSession as i, setTrackingRecoveredCallback as j, setTrackingCallbacks as k, getCamera as l, getScene as m, buildSessionOptions as n, extractResetTransformData as o, getLiveCss3dManager as p, createSceneHierarchy as r, getArPose as s, applyAlignmentMatrix as t, getCurrentArPose as u, isXRCameraLike as v, setCamera as w, resetWebXRState as x, nuePositionToWebXR as y };
378
+ 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 };
@@ -0,0 +1,70 @@
1
+ //#region ../src/ar/xr-frame-loop.d.ts
2
+ /**
3
+ * Per-frame **XR-access** callback registry — the safe seam that lets app
4
+ * code run standard WebXR work (hit-test, light estimation, depth, the WebXR
5
+ * Anchors API, …) without the framework wrapping each capability.
6
+ *
7
+ * The framework owns the single `renderer.setAnimationLoop(onXRFrame)` hook,
8
+ * and the live `XRFrame` never leaves `onXRFrame`. The plain `FrameUpdate`
9
+ * registry (`frame-loop.ts`) only passes `(dt, elapsed)`, which is enough for
10
+ * pose-free ticks but cannot drive hit-test. This registry additionally hands
11
+ * the callback the live `frame`, `referenceSpace`, and `session`.
12
+ *
13
+ * SAFETY CONTRACT (non-negotiable): `frame` / `referenceSpace` / `session` are
14
+ * valid **only synchronously inside the callback**. The `XRFrame` is
15
+ * use-after-frame-unsafe — storing it and reading it on a later tick throws or
16
+ * crashes. Passing them as arguments (never a stashable `getXrFrame()` getter)
17
+ * makes correct use the easy path and stashing the awkward one. Do NOT retain
18
+ * `ctx` or its fields beyond the callback's synchronous execution.
19
+ *
20
+ * See `2026-06-03-threejs-arbutton-minimal-ar-example-user-feedback.md`
21
+ * §6.2/§6.3 (option H-A2) for the design rationale.
22
+ */
23
+ /**
24
+ * Live, frame-scoped WebXR context. Valid only synchronously inside the
25
+ * `XrFrameUpdate` callback it is passed to.
26
+ */
27
+ interface XrFrameContext {
28
+ /** The live `XRFrame` for this animation-frame. Use-after-frame-unsafe. */
29
+ readonly frame: XRFrame;
30
+ /** The session's reference space (e.g. for `frame.getPose` / hit-test results). */
31
+ readonly referenceSpace: XRReferenceSpace;
32
+ /** The active `XRSession` (e.g. to call `requestHitTestSource` once). */
33
+ readonly session: XRSession;
34
+ /** Seconds since the previous frame (0 on the first frame after a reset). */
35
+ readonly dt: number;
36
+ /** Seconds since the session started. */
37
+ readonly elapsed: number;
38
+ }
39
+ /** A per-frame callback that needs live XR access. See the safety contract. */
40
+ type XrFrameUpdate = (ctx: XrFrameContext) => void;
41
+ /**
42
+ * Register a per-frame XR-access callback. Returns an unregister function.
43
+ *
44
+ * Registration is idempotent — registering the same `fn` twice is a no-op
45
+ * (it remains a single entry in the underlying `Set`).
46
+ */
47
+ declare function registerXrFrameUpdate(fn: XrFrameUpdate): () => void;
48
+ /**
49
+ * Invoke all registered XR-access callbacks. Called by the WebXR session's
50
+ * `onXRFrame` once per frame, only when a live `frame` / `referenceSpace` /
51
+ * `session` are all available.
52
+ *
53
+ * The set is snapshotted before iterating so that
54
+ * `registerXrFrameUpdate` / unregister calls made by a handler during the
55
+ * same frame are deferred to the next tick — mirroring `runFrameUpdates`.
56
+ *
57
+ * Each callback is invoked in its own `try/catch`: this registry is the public
58
+ * app seam, so a bug in one app-registered callback (which throws every frame)
59
+ * must not abort the remaining callbacks nor propagate up through `onXRFrame`
60
+ * and kill the scene render for the whole session. Failures are logged and the
61
+ * loop continues — mirroring `runFrameUpdates`.
62
+ */
63
+ declare function runXrFrameUpdates(ctx: XrFrameContext): void;
64
+ /**
65
+ * Clear all registrations. Called from `resetWebXRState()` so a fresh
66
+ * session starts with an empty registry.
67
+ */
68
+ declare function clearXrFrameUpdates(): void;
69
+ //#endregion
70
+ export { runXrFrameUpdates as a, registerXrFrameUpdate as i, XrFrameUpdate as n, clearXrFrameUpdates as r, XrFrameContext as t };
@@ -77,6 +77,11 @@ declare function loadSessionMetadataFromBlob(blob: Blob, maxFileSize?: number):
77
77
  interface GpsPathCoord {
78
78
  readonly lat: number;
79
79
  readonly lng: number;
80
+ /**
81
+ * Horizontal accuracy in meters (1σ), if the source GPS event included it.
82
+ * Used by 2D-map previews to draw a per-event accuracy circle.
83
+ */
84
+ readonly accuracy?: number;
80
85
  }
81
86
  /**
82
87
  * Extract GPS coordinates from a recording zip provided as a Blob.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gps-plus-slam-app-framework",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Reusable AR+GPS app framework layer — WebXR session management, Three.js visualization, GPS sensors, storage abstractions, replay engine, and store wiring for gps-plus-slam-js.",
5
5
  "type": "module",
6
6
  "files": [
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "@reduxjs/toolkit": "^2.11.2",
38
38
  "gl-matrix": "3.4.4",
39
- "gps-plus-slam-js": "^1.0.6"
39
+ "gps-plus-slam-js": "^1.0.7"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@sentry/browser": ">=10.0.0",
@@ -55,27 +55,27 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@eslint/js": "^10.0.1",
58
- "@sentry/browser": "^10.49.0",
58
+ "@sentry/browser": "^10.53.1",
59
59
  "@types/leaflet": "^1.9.21",
60
- "@types/node": "^25.6.0",
61
- "@types/three": "^0.184.0",
62
- "@vitest/coverage-v8": "^4.1.5",
63
- "@vitest/eslint-plugin": "^1.6.16",
60
+ "@types/node": "^25.7.0",
61
+ "@types/three": "^0.184.1",
62
+ "@vitest/coverage-v8": "^4.1.6",
63
+ "@vitest/eslint-plugin": "^1.6.17",
64
64
  "@zip.js/zip.js": "^2.8.26",
65
- "dpdm": "^4.0.1",
66
- "eslint": "^10.2.1",
65
+ "dpdm": "^4.2.0",
66
+ "eslint": "^10.3.0",
67
67
  "eslint-config-prettier": "^10.1.8",
68
- "fast-check": "^4.7.0",
69
- "globals": "^17.5.0",
68
+ "fast-check": "^4.8.0",
69
+ "globals": "^17.6.0",
70
70
  "h3-js": "^4.4.0",
71
- "jsdom": "^29.0.2",
71
+ "jsdom": "^29.1.1",
72
72
  "leaflet": "^1.9.4",
73
73
  "prettier": "^3.8.3",
74
74
  "three": "^0.184.0",
75
- "tsdown": "^0.21.9",
75
+ "tsdown": "^0.22.0",
76
76
  "typescript": "^6.0.3",
77
- "typescript-eslint": "^8.59.0",
78
- "vitest": "^4.1.5"
77
+ "typescript-eslint": "^8.59.3",
78
+ "vitest": "^4.1.6"
79
79
  },
80
80
  "keywords": [
81
81
  "ar",
@@ -105,7 +105,7 @@
105
105
  "verify:pack": "pnpm pack --dry-run",
106
106
  "verify:community-key-lifetime": "node scripts/verify-community-key-lifetime.mjs",
107
107
  "test:unit": "vitest run --coverage --config config/vitest.config.ts",
108
- "typecheck": "tsc -p tsconfig.json --noEmit",
108
+ "typecheck": "tsc -p tsconfig.app.json --noEmit",
109
109
  "typecheck:tests": "tsc -p tsconfig.vitest.json --noEmit",
110
110
  "lint": "eslint . --config config/eslint.config.mjs",
111
111
  "test:watch": "vitest --config config/vitest.config.ts",
@@ -1,2 +0,0 @@
1
- import { a as TrackingStateManager, i as TrackingStateCallbacks, n as ResetTransformData, r as TrackingState, t as DeviceOrientation } from "../tracking-state-B9QfuZCg.js";
2
- export { DeviceOrientation, ResetTransformData, TrackingState, TrackingStateCallbacks, TrackingStateManager };