gps-plus-slam-app-framework 1.0.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 (200) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/dist/alignment-lerper-D9BeDwZX.d.ts +29 -0
  4. package/dist/app-selectors-DyMzxCEC.d.ts +140 -0
  5. package/dist/ar/camera-blit-capture.d.ts +2 -0
  6. package/dist/ar/camera-blit-capture.js +253 -0
  7. package/dist/ar/capture-failure-tracker.d.ts +2 -0
  8. package/dist/ar/capture-failure-tracker.js +45 -0
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -0
  10. package/dist/ar/chromium-camera-access-workaround.js +32 -0
  11. package/dist/ar/depth-sampler.d.ts +3 -0
  12. package/dist/ar/depth-sampler.js +147 -0
  13. package/dist/ar/image-capture.d.ts +2 -0
  14. package/dist/ar/image-capture.js +152 -0
  15. package/dist/ar/index.d.ts +12 -0
  16. package/dist/ar/index.js +12 -0
  17. package/dist/ar/replay-scene.d.ts +2 -0
  18. package/dist/ar/replay-scene.js +325 -0
  19. package/dist/ar/scene-node-names.d.ts +2 -0
  20. package/dist/ar/scene-node-names.js +16 -0
  21. package/dist/ar/tracking-state.d.ts +2 -0
  22. package/dist/ar/tracking-state.js +164 -0
  23. package/dist/ar/webxr-session.d.ts +4 -0
  24. package/dist/ar/webxr-session.js +890 -0
  25. package/dist/ar/xr-camera-texture.d.ts +2 -0
  26. package/dist/ar/xr-camera-texture.js +39 -0
  27. package/dist/ar/xr-error-handler.d.ts +2 -0
  28. package/dist/ar/xr-error-handler.js +37 -0
  29. package/dist/ar-types-B-ORgk6Z.d.ts +82 -0
  30. package/dist/camera-blit-capture-DHuyrrxr.d.ts +108 -0
  31. package/dist/camera-follower-BMqPddxx.d.ts +29 -0
  32. package/dist/capture-failure-tracker-2Q6oI2uQ.d.ts +52 -0
  33. package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +61 -0
  34. package/dist/community-license-key-ChZLCJyO.d.ts +21 -0
  35. package/dist/concurrency-p6gB30fT.d.ts +24 -0
  36. package/dist/css3d-renderer-manager-rgLMVAvd.d.ts +23 -0
  37. package/dist/depth-sampler-BNCtnEoP.d.ts +108 -0
  38. package/dist/failure-tracker-1sQ_Cgyw.d.ts +49 -0
  39. package/dist/file-system-BsQgYCU3.d.ts +128 -0
  40. package/dist/file-system-utils-5cWO8Xyv.d.ts +24 -0
  41. package/dist/format-file-size-onGq0k1Q.d.ts +24 -0
  42. package/dist/fused-path-DtqCSqJh.d.ts +25 -0
  43. package/dist/geo-types-Xx-LVcnw.d.ts +29 -0
  44. package/dist/gps-Ddzr2KlX.d.ts +54 -0
  45. package/dist/gps-compass-cubes-C-6JEnpR.d.ts +22 -0
  46. package/dist/gps-error-handler-VPuNOuf8.d.ts +40 -0
  47. package/dist/gps-event-markers-DpEy2qRd.d.ts +67 -0
  48. package/dist/h3-ref-point-CFhayNSZ.d.ts +59 -0
  49. package/dist/image-capture-2pKjYUS6.d.ts +130 -0
  50. package/dist/index.d.ts +56 -0
  51. package/dist/index.js +61 -0
  52. package/dist/leaflet-map-overlay-CvfQXTye.d.ts +114 -0
  53. package/dist/lerp-utils-87NWjUOD.d.ts +20 -0
  54. package/dist/licensing/community-license-key.d.ts +2 -0
  55. package/dist/licensing/community-license-key.js +21 -0
  56. package/dist/licensing/index.d.ts +2 -0
  57. package/dist/licensing/index.js +2 -0
  58. package/dist/list-formatter-DKQQVLcl.d.ts +14 -0
  59. package/dist/logger-B81iwxx0.d.ts +74 -0
  60. package/dist/map-overlay-DuiZuCTx.d.ts +153 -0
  61. package/dist/null-storage-backend-CsWvQwN8.d.ts +11 -0
  62. package/dist/opfs-storage-ChOzF_Wp.d.ts +181 -0
  63. package/dist/opfs-storage-backend-CVcmfJHN.d.ts +11 -0
  64. package/dist/permission-checker-jO2ziCWC.d.ts +146 -0
  65. package/dist/persistence-middleware-DrptY02i.d.ts +31 -0
  66. package/dist/recorder-slice-fcwAXrsh.d.ts +35 -0
  67. package/dist/recording-coordinator-DzflP07_.d.ts +81 -0
  68. package/dist/recording-options-BYUFX8iU.d.ts +166 -0
  69. package/dist/recording-replayer-oOzPITrd.d.ts +32 -0
  70. package/dist/ref-point-importer-B_wn5ABk.d.ts +62 -0
  71. package/dist/ref-point-loader-Pt1smgHS.d.ts +117 -0
  72. package/dist/ref-points/h3-ref-point.d.ts +2 -0
  73. package/dist/ref-points/h3-ref-point.js +74 -0
  74. package/dist/ref-points/index.d.ts +2 -0
  75. package/dist/ref-points/index.js +2 -0
  76. package/dist/ref-points-slice-tC-Wd6XJ.d.ts +48 -0
  77. package/dist/reference-points-CFWy8_wj.d.ts +68 -0
  78. package/dist/replay-engine-DuX316ae.d.ts +103 -0
  79. package/dist/replay-scene-DtUm6bsr.d.ts +109 -0
  80. package/dist/routing-slice-BlHTM8eh.d.ts +23 -0
  81. package/dist/scene-node-names-DPsaE2z3.d.ts +14 -0
  82. package/dist/sensors/gps-error-handler.d.ts +2 -0
  83. package/dist/sensors/gps-error-handler.js +53 -0
  84. package/dist/sensors/gps.d.ts +2 -0
  85. package/dist/sensors/gps.js +93 -0
  86. package/dist/sensors/index.d.ts +4 -0
  87. package/dist/sensors/index.js +4 -0
  88. package/dist/sensors/permission-checker.d.ts +2 -0
  89. package/dist/sensors/permission-checker.js +397 -0
  90. package/dist/state/app-selectors.d.ts +2 -0
  91. package/dist/state/app-selectors.js +35 -0
  92. package/dist/state/index.d.ts +17 -0
  93. package/dist/state/index.js +13 -0
  94. package/dist/state/persistence-middleware.d.ts +2 -0
  95. package/dist/state/persistence-middleware.js +71 -0
  96. package/dist/state/recorder-slice.d.ts +2 -0
  97. package/dist/state/recorder-slice.js +47 -0
  98. package/dist/state/recording-coordinator.d.ts +2 -0
  99. package/dist/state/recording-coordinator.js +150 -0
  100. package/dist/state/recording-options.d.ts +2 -0
  101. package/dist/state/recording-options.js +192 -0
  102. package/dist/state/recording-replayer.d.ts +2 -0
  103. package/dist/state/recording-replayer.js +39 -0
  104. package/dist/state/ref-points-slice.d.ts +2 -0
  105. package/dist/state/ref-points-slice.js +44 -0
  106. package/dist/state/replay-engine.d.ts +2 -0
  107. package/dist/state/replay-engine.js +230 -0
  108. package/dist/state/routing-slice.d.ts +2 -0
  109. package/dist/state/routing-slice.js +13 -0
  110. package/dist/state/store-subscribers.d.ts +3 -0
  111. package/dist/state/store-subscribers.js +97 -0
  112. package/dist/state/store.d.ts +9 -0
  113. package/dist/state/store.js +71 -0
  114. package/dist/state/subscribe-to-selector.d.ts +2 -0
  115. package/dist/state/subscribe-to-selector.js +26 -0
  116. package/dist/storage/file-system-utils.d.ts +2 -0
  117. package/dist/storage/file-system-utils.js +31 -0
  118. package/dist/storage/file-system.d.ts +2 -0
  119. package/dist/storage/file-system.js +229 -0
  120. package/dist/storage/index.d.ts +11 -0
  121. package/dist/storage/index.js +10 -0
  122. package/dist/storage/null-storage-backend.d.ts +2 -0
  123. package/dist/storage/null-storage-backend.js +8 -0
  124. package/dist/storage/opfs-storage-backend.d.ts +2 -0
  125. package/dist/storage/opfs-storage-backend.js +15 -0
  126. package/dist/storage/opfs-storage.d.ts +2 -0
  127. package/dist/storage/opfs-storage.js +242 -0
  128. package/dist/storage/ref-point-importer.d.ts +2 -0
  129. package/dist/storage/ref-point-importer.js +163 -0
  130. package/dist/storage/ref-point-loader.d.ts +2 -0
  131. package/dist/storage/ref-point-loader.js +248 -0
  132. package/dist/storage/ref-point-recovery.d.ts +28 -0
  133. package/dist/storage/ref-point-recovery.js +159 -0
  134. package/dist/storage/storage-backend.d.ts +2 -0
  135. package/dist/storage/storage-backend.js +1 -0
  136. package/dist/storage/zip-export.d.ts +2 -0
  137. package/dist/storage/zip-export.js +214 -0
  138. package/dist/storage/zip-reader.d.ts +2 -0
  139. package/dist/storage/zip-reader.js +190 -0
  140. package/dist/storage-backend-BcEItnFr.d.ts +20 -0
  141. package/dist/store-C7PKOj8S.d.ts +96 -0
  142. package/dist/store-subscribers-CuoIzB5T.d.ts +77 -0
  143. package/dist/subscribe-to-selector-UTZ0KkAc.d.ts +26 -0
  144. package/dist/test-utils/browser-mocks.d.ts +294 -0
  145. package/dist/test-utils/browser-mocks.js +568 -0
  146. package/dist/test-utils/zip-round-trip-helpers.d.ts +60 -0
  147. package/dist/test-utils/zip-round-trip-helpers.js +207 -0
  148. package/dist/three-dispose-CCBHjE_a.d.ts +30 -0
  149. package/dist/tracking-state-B9QfuZCg.d.ts +101 -0
  150. package/dist/types/ar-types.d.ts +2 -0
  151. package/dist/types/ar-types.js +1 -0
  152. package/dist/types/geo-types.d.ts +2 -0
  153. package/dist/types/geo-types.js +1 -0
  154. package/dist/types/index.d.ts +3 -0
  155. package/dist/types/index.js +1 -0
  156. package/dist/utils/concurrency.d.ts +2 -0
  157. package/dist/utils/concurrency.js +40 -0
  158. package/dist/utils/failure-tracker.d.ts +2 -0
  159. package/dist/utils/failure-tracker.js +48 -0
  160. package/dist/utils/format-file-size.d.ts +2 -0
  161. package/dist/utils/format-file-size.js +41 -0
  162. package/dist/utils/fused-path.d.ts +2 -0
  163. package/dist/utils/fused-path.js +44 -0
  164. package/dist/utils/index.d.ts +7 -0
  165. package/dist/utils/index.js +7 -0
  166. package/dist/utils/list-formatter.d.ts +2 -0
  167. package/dist/utils/list-formatter.js +17 -0
  168. package/dist/utils/logger.d.ts +2 -0
  169. package/dist/utils/logger.js +226 -0
  170. package/dist/vis-colors-Qi2vlRGt.d.ts +63 -0
  171. package/dist/visualization/alignment-lerper.d.ts +2 -0
  172. package/dist/visualization/alignment-lerper.js +68 -0
  173. package/dist/visualization/camera-follower.d.ts +2 -0
  174. package/dist/visualization/camera-follower.js +51 -0
  175. package/dist/visualization/css3d-renderer-manager.d.ts +2 -0
  176. package/dist/visualization/css3d-renderer-manager.js +41 -0
  177. package/dist/visualization/gps-compass-cubes.d.ts +2 -0
  178. package/dist/visualization/gps-compass-cubes.js +129 -0
  179. package/dist/visualization/gps-event-markers.d.ts +2 -0
  180. package/dist/visualization/gps-event-markers.js +159 -0
  181. package/dist/visualization/index.d.ts +12 -0
  182. package/dist/visualization/index.js +12 -0
  183. package/dist/visualization/leaflet-map-overlay.d.ts +2 -0
  184. package/dist/visualization/leaflet-map-overlay.js +313 -0
  185. package/dist/visualization/lerp-utils.d.ts +2 -0
  186. package/dist/visualization/lerp-utils.js +22 -0
  187. package/dist/visualization/map-overlay.d.ts +2 -0
  188. package/dist/visualization/map-overlay.js +241 -0
  189. package/dist/visualization/reference-points.d.ts +2 -0
  190. package/dist/visualization/reference-points.js +160 -0
  191. package/dist/visualization/three-dispose.d.ts +2 -0
  192. package/dist/visualization/three-dispose.js +55 -0
  193. package/dist/visualization/vis-colors.d.ts +2 -0
  194. package/dist/visualization/vis-colors.js +74 -0
  195. package/dist/webxr-session-COlSrXdL.d.ts +316 -0
  196. package/dist/xr-camera-texture-BbukNE79.d.ts +53 -0
  197. package/dist/xr-error-handler-DTHfmvrl.d.ts +24 -0
  198. package/dist/zip-export-Rtpi75JJ.d.ts +87 -0
  199. package/dist/zip-reader-D7pkpX8K.d.ts +97 -0
  200. package/package.json +104 -0
@@ -0,0 +1,71 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { recordWriteFailure } from "./recorder-slice.js";
3
+ //#region ../src/state/persistence-middleware.ts
4
+ const log = createLogger("PersistenceMiddleware");
5
+ /** Maximum number of concurrent OPFS write operations. */
6
+ const MAX_CONCURRENT_WRITES = 3;
7
+ /**
8
+ * Simple write queue that limits concurrent OPFS operations.
9
+ * Prevents unbounded memory growth when storage is slow.
10
+ */
11
+ var WriteQueue = class {
12
+ pendingCount = 0;
13
+ queue = [];
14
+ enqueue(writeFn) {
15
+ this.queue.push(writeFn);
16
+ this.drain();
17
+ }
18
+ drain() {
19
+ while (this.pendingCount < MAX_CONCURRENT_WRITES && this.queue.length > 0) {
20
+ const fn = this.queue.shift();
21
+ this.pendingCount++;
22
+ fn().finally(() => {
23
+ this.pendingCount--;
24
+ this.drain();
25
+ });
26
+ }
27
+ }
28
+ };
29
+ /**
30
+ * Create a Redux middleware that persists qualifying actions to storage
31
+ * during active recording sessions.
32
+ *
33
+ * Persistence rules:
34
+ * - Only persists when `state.recorder.isRecording` is true (checked AFTER
35
+ * the action is reduced, so `startSession` itself is included).
36
+ * - Persists `gpsData/*` and `recorder/*` actions.
37
+ * - Excludes `recorder/recordWriteFailure` to prevent recursive persistence.
38
+ * - Excludes `routing/*`, `refPoints/*`, and any other non-recording actions.
39
+ * - Uses 1-based indexing for action files (000001.json, 000002.json, …).
40
+ * - Each middleware instance maintains its own action index (Bug 10 fix).
41
+ */
42
+ function createPersistenceMiddleware(options) {
43
+ const { storageBackend, onWriteFailure } = options;
44
+ let actionIndex = 0;
45
+ const writeQueue = new WriteQueue();
46
+ const middleware = (store) => (next) => (action) => {
47
+ const typedAction = action;
48
+ const actionType = typeof typedAction.type === "string" ? typedAction.type : null;
49
+ const wasRecording = store.getState().recorder?.isRecording ?? false;
50
+ const result = next(action);
51
+ if (!actionType) return result;
52
+ if (actionType === "recorder/startSession") actionIndex = 0;
53
+ if (!((store.getState().recorder?.isRecording ?? false) || wasRecording && actionType === "recorder/endSession")) return result;
54
+ if (!(actionType.startsWith("gpsData/") || actionType.startsWith("recorder/") && actionType !== "recorder/recordWriteFailure")) return result;
55
+ const index = ++actionIndex;
56
+ writeQueue.enqueue(async () => {
57
+ try {
58
+ await storageBackend.writeAction(typedAction, index);
59
+ } catch (err) {
60
+ const normalized = err instanceof Error ? err : new Error(String(err));
61
+ log.error("Failed to persist action:", normalized);
62
+ store.dispatch(recordWriteFailure(normalized.message));
63
+ if (onWriteFailure) onWriteFailure(normalized);
64
+ }
65
+ });
66
+ return result;
67
+ };
68
+ return middleware;
69
+ }
70
+ //#endregion
71
+ export { createPersistenceMiddleware };
@@ -0,0 +1,2 @@
1
+ import { a as recordWriteFailure, c as startSession, i as recordDepthSample, n as SessionMetadata, o as recorderReducer, r as endSession, s as setCurrentScenarioName, t as RecorderState } from "../recorder-slice-fcwAXrsh.js";
2
+ export { RecorderState, SessionMetadata, endSession, recordDepthSample, recordWriteFailure, recorderReducer, setCurrentScenarioName, startSession };
@@ -0,0 +1,47 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ //#region ../src/state/recorder-slice.ts
3
+ const recorderSlice = createSlice({
4
+ name: "recorder",
5
+ initialState: {
6
+ isRecording: false,
7
+ sessionMetadata: null,
8
+ actionCount: 0,
9
+ failedWriteCount: 0,
10
+ currentScenarioName: ""
11
+ },
12
+ reducers: {
13
+ startSession(state, action) {
14
+ state.isRecording = true;
15
+ state.sessionMetadata = action.payload;
16
+ state.actionCount = 0;
17
+ state.failedWriteCount = 0;
18
+ },
19
+ endSession(state) {
20
+ state.isRecording = false;
21
+ },
22
+ /**
23
+ * Record a depth sample from WebXR depth sensing.
24
+ * The sample includes camera pose and a grid of depth points.
25
+ * This action is persisted to enable replay with depth data.
26
+ */
27
+ recordDepthSample(_state, _action) {},
28
+ /**
29
+ * Record a write failure during persistence.
30
+ * User Feedback Issue #1 Part B: Track write failures for session summary.
31
+ */
32
+ recordWriteFailure(state, _action) {
33
+ state.failedWriteCount += 1;
34
+ },
35
+ /**
36
+ * Set the currently selected scenario name.
37
+ * Replaces the closure variable threading through folder-manager deps.
38
+ */
39
+ setCurrentScenarioName(state, action) {
40
+ state.currentScenarioName = action.payload;
41
+ }
42
+ }
43
+ });
44
+ const { startSession, endSession, recordDepthSample, recordWriteFailure, setCurrentScenarioName } = recorderSlice.actions;
45
+ const recorderReducer = recorderSlice.reducer;
46
+ //#endregion
47
+ export { endSession, recordDepthSample, recordWriteFailure, recorderReducer, setCurrentScenarioName, startSession };
@@ -0,0 +1,2 @@
1
+ import { a as eulerToQuaternion, c as getLastDeviceOrientation, i as createGpsPositionHandler, l as resetCoordinatorState, n as buildRawGpsPoint, o as extractOdomPosition, r as buildRecordGpsEventPayload, s as extractOdomRotation, t as RecordingCoordinatorConfig, u as updateDeviceOrientation } from "../recording-coordinator-DzflP07_.js";
2
+ export { RecordingCoordinatorConfig, buildRawGpsPoint, buildRecordGpsEventPayload, createGpsPositionHandler, eulerToQuaternion, extractOdomPosition, extractOdomRotation, getLastDeviceOrientation, resetCoordinatorState, updateDeviceOrientation };
@@ -0,0 +1,150 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { recordGpsEvent, setZeroPos } from "./store.js";
3
+ import { eulerToQuaternion, getOdometryPositions, getZeroReference } from "gps-plus-slam-js";
4
+ //#region ../src/state/recording-coordinator.ts
5
+ const log = createLogger("RecordingCoordinator");
6
+ /** Counter for generating unique GPS point IDs */
7
+ let gpsEventCounter = 0;
8
+ /**
9
+ * State for device orientation (captured separately from GPS)
10
+ */
11
+ let lastDeviceOrientation = null;
12
+ /**
13
+ * Update the cached device orientation.
14
+ * Called by orientation watch, used when GPS event arrives.
15
+ */
16
+ function updateDeviceOrientation(orientation) {
17
+ lastDeviceOrientation = orientation;
18
+ }
19
+ /**
20
+ * Get the current cached device orientation.
21
+ */
22
+ function getLastDeviceOrientation() {
23
+ return lastDeviceOrientation;
24
+ }
25
+ /**
26
+ * Clear cached state (for testing or session reset)
27
+ */
28
+ function resetCoordinatorState() {
29
+ lastDeviceOrientation = null;
30
+ gpsEventCounter = 0;
31
+ }
32
+ /**
33
+ * Extract raw odometry position tuple from ARPose.
34
+ *
35
+ * Returns the raw WebXR position without coordinate conversion.
36
+ * The reducer applies the WebXR→NUE transform when storing into state
37
+ * (raw-storage pattern, see docs/2026-04-09-raw-storage-convert-on-read.md).
38
+ *
39
+ * WebXR local-floor frame: X=East, Y=Up, Z=South (toward viewer / backward).
40
+ */
41
+ function extractOdomPosition(arPose) {
42
+ return [
43
+ arPose.position.x,
44
+ arPose.position.y,
45
+ arPose.position.z
46
+ ];
47
+ }
48
+ /**
49
+ * Extract odometry rotation tuple from ARPose.
50
+ */
51
+ function extractOdomRotation(arPose) {
52
+ return [
53
+ arPose.orientation.x,
54
+ arPose.orientation.y,
55
+ arPose.orientation.z,
56
+ arPose.orientation.w
57
+ ];
58
+ }
59
+ /**
60
+ * Build a RawGpsPoint from GPS position data.
61
+ * Returns only raw sensor fields — no derived fields (coordinates, weight,
62
+ * zeroRef, deviceRotation). Derived fields are computed by the library
63
+ * reducer when the action is dispatched (raw-storage pattern).
64
+ *
65
+ * @param gpsPosition - GPS position from Geolocation API
66
+ * @param deviceOrientation - Optional device orientation from sensors
67
+ * @returns RawGpsPoint ready for the action payload
68
+ */
69
+ function buildRawGpsPoint(gpsPosition, deviceOrientation) {
70
+ const compassAbsolute = deviceOrientation ? deviceOrientation.absolute : void 0;
71
+ return {
72
+ id: `gps-${++gpsEventCounter}`,
73
+ latitude: gpsPosition.lat,
74
+ longitude: gpsPosition.lon,
75
+ altitude: gpsPosition.altitude ?? void 0,
76
+ latLongAccuracy: gpsPosition.accuracy,
77
+ altitudeAccuracy: gpsPosition.altitudeAccuracy ?? void 0,
78
+ heading: gpsPosition.heading ?? void 0,
79
+ speed: gpsPosition.speed ?? void 0,
80
+ compassAbsolute,
81
+ timestamp: gpsPosition.timestamp
82
+ };
83
+ }
84
+ /**
85
+ * Build a RecordGpsEventPayload from GPS position and AR pose.
86
+ * This is a pure function for testability.
87
+ *
88
+ * Stores only raw sensor data in the payload. The library reducer
89
+ * computes derived fields (coordinates, weight, deviceRotation)
90
+ * when the action is processed (raw-storage pattern).
91
+ *
92
+ * @param gpsPosition - GPS position from Geolocation API
93
+ * @param arPose - AR pose from WebXR
94
+ * @param deviceOrientation - Optional device orientation from sensors
95
+ * @returns RecordGpsEventPayload ready for dispatch
96
+ */
97
+ function buildRecordGpsEventPayload(gpsPosition, arPose, deviceOrientation) {
98
+ const rawDeviceOrientation = deviceOrientation?.alpha != null && deviceOrientation?.beta != null && deviceOrientation?.gamma != null ? {
99
+ alpha: deviceOrientation.alpha,
100
+ beta: deviceOrientation.beta,
101
+ gamma: deviceOrientation.gamma,
102
+ absolute: deviceOrientation.absolute
103
+ } : void 0;
104
+ return {
105
+ odomPosition: extractOdomPosition(arPose),
106
+ odomRotation: extractOdomRotation(arPose),
107
+ rawGpsPoint: buildRawGpsPoint(gpsPosition, deviceOrientation),
108
+ rawDeviceOrientation
109
+ };
110
+ }
111
+ /**
112
+ * Create a GPS position handler that dispatches combined GPS+AR events.
113
+ *
114
+ * This is the CRITICAL function that implements the correct architecture:
115
+ * - Called when GPS arrives
116
+ * - Reads current AR pose
117
+ * - Dispatches single combined action
118
+ *
119
+ * @param config - Configuration with store and getArPose function
120
+ * @returns Callback function for GPS watch
121
+ */
122
+ function createGpsPositionHandler(config) {
123
+ const { store, getArPose } = config;
124
+ return (position) => {
125
+ if (!store.getState().recorder.isRecording) return;
126
+ const arPose = getArPose();
127
+ if (!arPose) {
128
+ log.warn("GPS arrived but no AR pose available");
129
+ return;
130
+ }
131
+ if (!store.getState().gpsData) {
132
+ const zeroRef = {
133
+ lat: position.lat,
134
+ lon: position.lon
135
+ };
136
+ store.dispatch(setZeroPos(zeroRef));
137
+ log.info(`Set zero reference: ${zeroRef.lat}, ${zeroRef.lon}`);
138
+ }
139
+ if (!getZeroReference(store.getState())) {
140
+ log.error("Failed to get zero reference");
141
+ return;
142
+ }
143
+ const payload = buildRecordGpsEventPayload(position, arPose, lastDeviceOrientation);
144
+ store.dispatch(recordGpsEvent(payload));
145
+ const eventCount = getOdometryPositions(store.getState()).length;
146
+ log.info(`Recorded GPS event #${eventCount}`);
147
+ };
148
+ }
149
+ //#endregion
150
+ export { buildRawGpsPoint, buildRecordGpsEventPayload, createGpsPositionHandler, eulerToQuaternion, extractOdomPosition, extractOdomRotation, getLastDeviceOrientation, resetCoordinatorState, updateDeviceOrientation };
@@ -0,0 +1,2 @@
1
+ import { _ as validateRecordingOptions, a as IMAGE_CONSTRAINTS, c as RecordingOptionsInput, d as loadRecordingOptions, f as resetRecordingOptions, g as validateImageOptions, h as validateDepthOptions, i as DepthCaptureOptions, l as STORAGE_KEY, m as validateArCrashIsolationOptions, n as DEFAULT_RECORDING_OPTIONS, o as ImageCaptureOptions, p as saveRecordingOptions, r as DEPTH_CONSTRAINTS, s as RecordingOptions, t as ArCrashIsolationOptions, u as cloneRecordingOptions } from "../recording-options-BYUFX8iU.js";
2
+ export { ArCrashIsolationOptions, DEFAULT_RECORDING_OPTIONS, DEPTH_CONSTRAINTS, DepthCaptureOptions, IMAGE_CONSTRAINTS, ImageCaptureOptions, RecordingOptions, RecordingOptionsInput, STORAGE_KEY, cloneRecordingOptions, loadRecordingOptions, resetRecordingOptions, saveRecordingOptions, validateArCrashIsolationOptions, validateDepthOptions, validateImageOptions, validateRecordingOptions };
@@ -0,0 +1,192 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ //#region ../src/state/recording-options.ts
3
+ /**
4
+ * Recording Options - User-configurable settings for data capture.
5
+ *
6
+ * Allows users to disable/configure high-frequency data streams
7
+ * (depth sampling, image capture) to improve performance on lower-end devices.
8
+ *
9
+ * Options persist in localStorage across sessions.
10
+ */
11
+ const log = createLogger("RecordingOptions");
12
+ /**
13
+ * localStorage key for persisted options.
14
+ *
15
+ * **Multi-tab caveat:** All tabs/instances sharing the same origin will
16
+ * read and write this key. In multi-tab or embedded scenarios, concurrent
17
+ * saves can silently overwrite each other. Use a custom `storageKey`
18
+ * parameter in `loadRecordingOptions` / `saveRecordingOptions` to isolate
19
+ * instances when needed.
20
+ */
21
+ const STORAGE_KEY = "gps-plus-slam-recorder-options";
22
+ /** Default recording options (all streams enabled) */
23
+ const DEFAULT_RECORDING_OPTIONS = {
24
+ depth: {
25
+ enabled: true,
26
+ intervalMs: 1e3,
27
+ gridSize: 3
28
+ },
29
+ images: {
30
+ enabled: true,
31
+ intervalMs: 2e3,
32
+ quality: .7,
33
+ resolutionDivisor: 1
34
+ },
35
+ arCrashIsolation: {
36
+ enableDomOverlay: true,
37
+ enableCameraAccess: true,
38
+ enableDepthSensingFeature: true,
39
+ enableCss3dRenderer: true,
40
+ enableCameraTextureAcquisition: true,
41
+ applyChromiumProjectionLayerWorkaround: true
42
+ }
43
+ };
44
+ /** Validation constraints for depth options */
45
+ const DEPTH_CONSTRAINTS = {
46
+ intervalMs: {
47
+ min: 500,
48
+ max: 5e3,
49
+ step: 100
50
+ },
51
+ gridSize: {
52
+ min: 2,
53
+ max: 10,
54
+ step: 1
55
+ }
56
+ };
57
+ /** Validation constraints for image options */
58
+ const IMAGE_CONSTRAINTS = {
59
+ intervalMs: {
60
+ min: 1e3,
61
+ max: 1e4,
62
+ step: 500
63
+ },
64
+ quality: {
65
+ min: .3,
66
+ max: 1,
67
+ step: .1
68
+ },
69
+ resolutionDivisor: {
70
+ min: 1,
71
+ max: 8,
72
+ step: 1
73
+ }
74
+ };
75
+ /**
76
+ * Validate and normalize AR crash isolation flags.
77
+ * Missing or invalid values fall back to defaults.
78
+ */
79
+ function validateArCrashIsolationOptions(options) {
80
+ const defaults = DEFAULT_RECORDING_OPTIONS.arCrashIsolation;
81
+ return {
82
+ enableDomOverlay: typeof options.enableDomOverlay === "boolean" ? options.enableDomOverlay : defaults.enableDomOverlay,
83
+ enableCameraAccess: typeof options.enableCameraAccess === "boolean" ? options.enableCameraAccess : defaults.enableCameraAccess,
84
+ enableDepthSensingFeature: typeof options.enableDepthSensingFeature === "boolean" ? options.enableDepthSensingFeature : defaults.enableDepthSensingFeature,
85
+ enableCss3dRenderer: typeof options.enableCss3dRenderer === "boolean" ? options.enableCss3dRenderer : defaults.enableCss3dRenderer,
86
+ enableCameraTextureAcquisition: typeof options.enableCameraTextureAcquisition === "boolean" ? options.enableCameraTextureAcquisition : defaults.enableCameraTextureAcquisition,
87
+ applyChromiumProjectionLayerWorkaround: typeof options.applyChromiumProjectionLayerWorkaround === "boolean" ? options.applyChromiumProjectionLayerWorkaround : defaults.applyChromiumProjectionLayerWorkaround
88
+ };
89
+ }
90
+ /**
91
+ * Clamp a value to the specified constraints.
92
+ */
93
+ function clamp(value, min, max) {
94
+ return Math.max(min, Math.min(max, value));
95
+ }
96
+ /**
97
+ * Validate and normalize depth options.
98
+ * Invalid values are clamped to valid ranges.
99
+ */
100
+ function validateDepthOptions(options) {
101
+ const defaults = DEFAULT_RECORDING_OPTIONS.depth;
102
+ return {
103
+ enabled: typeof options.enabled === "boolean" ? options.enabled : defaults.enabled,
104
+ intervalMs: clamp(typeof options.intervalMs === "number" ? options.intervalMs : defaults.intervalMs, DEPTH_CONSTRAINTS.intervalMs.min, DEPTH_CONSTRAINTS.intervalMs.max),
105
+ gridSize: clamp(typeof options.gridSize === "number" ? options.gridSize : defaults.gridSize, DEPTH_CONSTRAINTS.gridSize.min, DEPTH_CONSTRAINTS.gridSize.max)
106
+ };
107
+ }
108
+ /**
109
+ * Validate and normalize image options.
110
+ * Invalid values are clamped to valid ranges.
111
+ */
112
+ function validateImageOptions(options) {
113
+ const defaults = DEFAULT_RECORDING_OPTIONS.images;
114
+ return {
115
+ enabled: typeof options.enabled === "boolean" ? options.enabled : defaults.enabled,
116
+ intervalMs: clamp(typeof options.intervalMs === "number" ? options.intervalMs : defaults.intervalMs, IMAGE_CONSTRAINTS.intervalMs.min, IMAGE_CONSTRAINTS.intervalMs.max),
117
+ quality: clamp(typeof options.quality === "number" ? options.quality : defaults.quality, IMAGE_CONSTRAINTS.quality.min, IMAGE_CONSTRAINTS.quality.max),
118
+ resolutionDivisor: clamp(typeof options.resolutionDivisor === "number" ? options.resolutionDivisor : defaults.resolutionDivisor, IMAGE_CONSTRAINTS.resolutionDivisor.min, IMAGE_CONSTRAINTS.resolutionDivisor.max)
119
+ };
120
+ }
121
+ /**
122
+ * Validate and normalize a full RecordingOptions object.
123
+ * Merges with defaults and clamps invalid values.
124
+ */
125
+ function validateRecordingOptions(options) {
126
+ return {
127
+ depth: validateDepthOptions(options.depth ?? {}),
128
+ images: validateImageOptions(options.images ?? {}),
129
+ arCrashIsolation: validateArCrashIsolationOptions(options.arCrashIsolation ?? {})
130
+ };
131
+ }
132
+ /**
133
+ * Load recording options from localStorage.
134
+ * Returns defaults if no saved options exist or parsing fails.
135
+ * Validates and merges with defaults to handle schema evolution.
136
+ * @param storageKey - Optional custom localStorage key (defaults to STORAGE_KEY).
137
+ */
138
+ function loadRecordingOptions(storageKey = STORAGE_KEY) {
139
+ try {
140
+ const stored = localStorage.getItem(storageKey);
141
+ if (stored) {
142
+ const parsed = JSON.parse(stored);
143
+ log.debug("Loaded options from storage:", parsed);
144
+ return validateRecordingOptions(parsed);
145
+ }
146
+ } catch (err) {
147
+ log.warn("Failed to load recording options:", err);
148
+ }
149
+ log.debug("Using default recording options");
150
+ return cloneRecordingOptions(DEFAULT_RECORDING_OPTIONS);
151
+ }
152
+ /**
153
+ * Save recording options to localStorage.
154
+ * Options are validated before saving.
155
+ * @param storageKey - Optional custom localStorage key (defaults to STORAGE_KEY).
156
+ */
157
+ function saveRecordingOptions(options, storageKey = STORAGE_KEY) {
158
+ try {
159
+ const validated = validateRecordingOptions(options);
160
+ localStorage.setItem(storageKey, JSON.stringify(validated));
161
+ log.debug("Saved recording options:", validated);
162
+ } catch (err) {
163
+ log.warn("Failed to save recording options:", err);
164
+ }
165
+ }
166
+ /**
167
+ * Reset recording options to defaults.
168
+ * Clears localStorage and returns default options.
169
+ * @param storageKey - Optional custom localStorage key (defaults to STORAGE_KEY).
170
+ */
171
+ function resetRecordingOptions(storageKey = STORAGE_KEY) {
172
+ try {
173
+ localStorage.removeItem(storageKey);
174
+ log.debug("Reset recording options to defaults");
175
+ } catch (err) {
176
+ log.warn("Failed to clear recording options from storage:", err);
177
+ }
178
+ return cloneRecordingOptions(DEFAULT_RECORDING_OPTIONS);
179
+ }
180
+ /**
181
+ * Create a deep copy of recording options.
182
+ * Useful for creating mutable copies of the frozen defaults.
183
+ */
184
+ function cloneRecordingOptions(options) {
185
+ return {
186
+ depth: { ...options.depth },
187
+ images: { ...options.images },
188
+ arCrashIsolation: { ...options.arCrashIsolation }
189
+ };
190
+ }
191
+ //#endregion
192
+ export { DEFAULT_RECORDING_OPTIONS, DEPTH_CONSTRAINTS, IMAGE_CONSTRAINTS, STORAGE_KEY, cloneRecordingOptions, loadRecordingOptions, resetRecordingOptions, saveRecordingOptions, validateArCrashIsolationOptions, validateDepthOptions, validateImageOptions, validateRecordingOptions };
@@ -0,0 +1,2 @@
1
+ import { n as replayRecording, t as ReplayRecordingOptions } from "../recording-replayer-oOzPITrd.js";
2
+ export { ReplayRecordingOptions, replayRecording };
@@ -0,0 +1,39 @@
1
+ import { createRecorderStore } from "./store.js";
2
+ import { loadActionsFromZip } from "../storage/zip-reader.js";
3
+ import { NullStorageBackend } from "../storage/null-storage-backend.js";
4
+ //#region ../src/state/recording-replayer.ts
5
+ /**
6
+ * Recording Replayer
7
+ *
8
+ * Replays a recorded session from a zip file into a fresh store,
9
+ * producing the fully-computed CombinedRootState without any persistence
10
+ * side effects.
11
+ *
12
+ * This is the primary entry point for loading recordings for
13
+ * visualization, comparison, validation, or offline analysis.
14
+ *
15
+ * Uses NullStorageBackend to ensure no OPFS writes occur during replay.
16
+ *
17
+ * See also: Finding F5 in docs/2026-02-15-replay-integration-test-review.md
18
+ */
19
+ /**
20
+ * Replay a recording session from zip data, returning the final state.
21
+ *
22
+ * Loads all actions from the zip, creates a store with no persistence,
23
+ * optionally migrates old-format actions, dispatches in order, and returns
24
+ * the resulting state.
25
+ *
26
+ * @param zipData - The zip file content as a Uint8Array
27
+ * @param options - Optional replay configuration (e.g., action migration)
28
+ * @returns The fully-replayed combined state (library + recorder)
29
+ * @throws If the zip cannot be parsed or contains invalid data
30
+ */
31
+ async function replayRecording(zipData, options) {
32
+ const store = createRecorderStore({ storageBackend: new NullStorageBackend() });
33
+ let actions = (await loadActionsFromZip(zipData)).map((e) => e.action);
34
+ if (options?.migrateActions) actions = options.migrateActions(actions);
35
+ for (const action of actions) store.dispatch(action);
36
+ return store.getState();
37
+ }
38
+ //#endregion
39
+ export { replayRecording };
@@ -0,0 +1,2 @@
1
+ import { a as resetRefPointsState, i as refPointsReducer, n as clearSessionRefPointUsage, o as selectCachedKnownRefPoints, r as incrementRefPointUsage, s as setImportedRefPoints, t as RefPointsState } from "../ref-points-slice-tC-Wd6XJ.js";
2
+ export { RefPointsState, clearSessionRefPointUsage, incrementRefPointUsage, refPointsReducer, resetRefPointsState, selectCachedKnownRefPoints, setImportedRefPoints };
@@ -0,0 +1,44 @@
1
+ import { gpsToH3 } from "../ref-points/h3-ref-point.js";
2
+ import { createSelector, createSlice } from "@reduxjs/toolkit";
3
+ //#region ../src/state/ref-points-slice.ts
4
+ const initialState = {
5
+ importedRefPoints: [],
6
+ sessionRefPointUsage: {}
7
+ };
8
+ const refPointsSlice = createSlice({
9
+ name: "refPoints",
10
+ initialState,
11
+ reducers: {
12
+ setImportedRefPoints(state, action) {
13
+ state.importedRefPoints = action.payload;
14
+ },
15
+ incrementRefPointUsage(state, action) {
16
+ const id = action.payload;
17
+ state.sessionRefPointUsage[id] = (state.sessionRefPointUsage[id] ?? 0) + 1;
18
+ },
19
+ clearSessionRefPointUsage(state) {
20
+ state.sessionRefPointUsage = {};
21
+ },
22
+ resetRefPointsState() {
23
+ return initialState;
24
+ }
25
+ }
26
+ });
27
+ const { setImportedRefPoints, incrementRefPointUsage, clearSessionRefPointUsage, resetRefPointsState } = refPointsSlice.actions;
28
+ const refPointsReducer = refPointsSlice.reducer;
29
+ /**
30
+ * Memoized selector that derives KnownRefPoint[] (with H3 indices) from
31
+ * importedRefPoints. Replaces the closure-based `recomputeKnownRefPoints()`
32
+ * in ref-point-handlers.ts.
33
+ *
34
+ * Uses createSelector (reselect) for standard RTK memoization — recomputes
35
+ * only when the importedRefPoints array reference changes.
36
+ */
37
+ const selectCachedKnownRefPoints = createSelector((state) => state.importedRefPoints, (importedRefPoints) => importedRefPoints.map((rp) => ({
38
+ h3Index: gpsToH3(rp.lat, rp.lon),
39
+ displayName: rp.name || rp.id,
40
+ lat: rp.lat,
41
+ lon: rp.lon
42
+ })));
43
+ //#endregion
44
+ export { clearSessionRefPointUsage, incrementRefPointUsage, refPointsReducer, resetRefPointsState, selectCachedKnownRefPoints, setImportedRefPoints };
@@ -0,0 +1,2 @@
1
+ import { a as ReplayAction, c as computeInterActionDelay, i as ProgressCallback, l as extractActionTimestamp, n as DEFAULT_MAX_DELAY_MS, o as ReplayEngine, r as ErrorCallback, s as ReplayState, t as CompleteCallback } from "../replay-engine-DuX316ae.js";
2
+ export { CompleteCallback, DEFAULT_MAX_DELAY_MS, ErrorCallback, ProgressCallback, ReplayAction, ReplayEngine, ReplayState, computeInterActionDelay, extractActionTimestamp };