gps-plus-slam-app-framework 1.7.0 → 1.8.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 (233) hide show
  1. package/dist/{accuracy-circles-BtqOP_v1.d.ts → accuracy-circles-DkWBkF5g.d.ts} +16 -6
  2. package/dist/{app-selectors-ikTUQZpe.d.ts → app-selectors-JqydaUMN.d.ts} +1 -1
  3. package/dist/ar/bresenham3d.d.ts +1 -1
  4. package/dist/ar/camera-blit-capture.d.ts +1 -1
  5. package/dist/ar/capability-checker.d.ts +1 -1
  6. package/dist/ar/capture-failure-tracker.d.ts +1 -1
  7. package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
  8. package/dist/ar/depth-grid-lookup.d.ts +1 -1
  9. package/dist/ar/depth-occluder.d.ts +127 -0
  10. package/dist/ar/depth-occluder.js +299 -0
  11. package/dist/ar/depth-sampler.d.ts +2 -2
  12. package/dist/ar/depth-sampler.js +24 -4
  13. package/dist/ar/depth-unprojection.d.ts +1 -1
  14. package/dist/ar/depth-unprojection.js +52 -21
  15. package/dist/ar/enable-gps-ar.d.ts +1 -1
  16. package/dist/ar/enable-gps-ar.js +1 -1
  17. package/dist/ar/frame-loop.d.ts +1 -1
  18. package/dist/ar/frame-loop.js +12 -2
  19. package/dist/ar/image-capture.d.ts +1 -1
  20. package/dist/ar/image-capture.js +1 -1
  21. package/dist/ar/image-quality.d.ts +1 -1
  22. package/dist/ar/image-quality.js +2 -1
  23. package/dist/ar/index.d.ts +32 -31
  24. package/dist/ar/index.js +5 -4
  25. package/dist/ar/occlusion-mesh-worker.d.ts +58 -0
  26. package/dist/ar/occlusion-mesh-worker.js +129 -0
  27. package/dist/ar/occupancy-grid.d.ts +2 -2
  28. package/dist/ar/occupancy-grid.js +287 -31
  29. package/dist/ar/occupancy-mesher.d.ts +2 -0
  30. package/dist/ar/occupancy-mesher.js +638 -0
  31. package/dist/ar/planar-pnp.d.ts +1 -1
  32. package/dist/ar/qr-debug-view.d.ts +1 -1
  33. package/dist/ar/qr-derived-pose.d.ts +1 -1
  34. package/dist/ar/qr-detection-controller.d.ts +1 -1
  35. package/dist/ar/qr-frontend.d.ts +1 -1
  36. package/dist/ar/qr-pose.d.ts +1 -1
  37. package/dist/ar/qr-size-depth-context.d.ts +2 -2
  38. package/dist/ar/qr-size-measurer.d.ts +1 -1
  39. package/dist/ar/replay-scene.d.ts +1 -1
  40. package/dist/ar/replay-scene.js +1 -1
  41. package/dist/ar/scene-node-names.d.ts +1 -1
  42. package/dist/ar/webxr-nue-basis.d.ts +1 -1
  43. package/dist/ar/webxr-session.d.ts +4 -4
  44. package/dist/ar/webxr-session.js +2 -2
  45. package/dist/ar/xr-camera-texture.d.ts +1 -1
  46. package/dist/ar/xr-error-handler.d.ts +1 -1
  47. package/dist/ar/xr-frame-loop.d.ts +1 -1
  48. package/dist/ar/xr-frame-loop.js +12 -2
  49. package/dist/{ar-MFpFlV7j.js → ar-BqCudtqE.js} +3 -2
  50. package/dist/{ar-world-group-alignment-69fgnB39.d.ts → ar-world-group-alignment-BlvGcIAk.d.ts} +1 -1
  51. package/dist/{capture-motion-gate-4EscNZCw.js → capture-motion-gate-_NdTSwKi.js} +17 -5
  52. package/dist/cell-key-DIB4rOTV.js +51 -0
  53. package/dist/core/index.d.ts +1 -1
  54. package/dist/{create-slam-app-store-n_VF63D5.d.ts → create-slam-app-store-D3nzh9Wb.d.ts} +4 -4
  55. package/dist/{create-slam-app-store-DkCHOpvb.js → create-slam-app-store-DXiAnJwR.js} +9 -2
  56. package/dist/{depth-grid-lookup-DQ-IYhO3.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
  57. package/dist/{depth-sampler-asc8exHt.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
  58. package/dist/{depth-unprojection-DjsbJW9L.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
  59. package/dist/{enable-gps-ar-DkNhFv4Y.d.ts → enable-gps-ar-DvGzBOrp.d.ts} +4 -4
  60. package/dist/{frame-conversions-DObhj8Wf.d.ts → frame-conversions-C4bnNOEY.d.ts} +1 -1
  61. package/dist/{fused-path-v6eOmLl3.d.ts → fused-path-C5pp0PJU.d.ts} +1 -1
  62. package/dist/geo/h3-proximity.d.ts +1 -1
  63. package/dist/geo/index.d.ts +1 -1
  64. package/dist/{gps-anchor-BeFO2RYa.d.ts → gps-anchor-BjECcYnD.d.ts} +1 -1
  65. package/dist/{gps-ar-pose-sampler-DHMZmYAc.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
  66. package/dist/{gps-event-coordinator-CjWW636x.d.ts → gps-event-coordinator-CHtIkhb7.d.ts} +4 -4
  67. package/dist/{image-capture-CNZDrQXG.d.ts → image-capture-Bp6Pd4Md.d.ts} +3 -3
  68. package/dist/{image-capture-B2-b6tKK.js → image-capture-DVcuqMtX.js} +1 -1
  69. package/dist/{index-BODDUTvD.d.ts → index-DlsOo2IB.d.ts} +1 -1
  70. package/dist/{index-BneSj4_5.d.ts → index-cCQWuYqy.d.ts} +3 -3
  71. package/dist/index.d.ts +82 -80
  72. package/dist/index.js +10 -8
  73. package/dist/{leaflet-map-overlay-CmIyiE8n.d.ts → leaflet-map-overlay-BOZFW5kT.d.ts} +64 -10
  74. package/dist/leaflet-map-overlay-CBWPvaX9.js +452 -0
  75. package/dist/lerp-utils-Qs8OhumS.d.ts +42 -0
  76. package/dist/map-data-D7rpMC6f.js +120 -0
  77. package/dist/{map-data-B4oEehUh.d.ts → map-data-PNfGKGWx.d.ts} +22 -2
  78. package/dist/{map-overlay-draw-ymz1s7fG.d.ts → map-overlay-draw-DiroaJed.d.ts} +1 -1
  79. package/dist/{null-storage-backend-B9rtyDfG.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
  80. package/dist/occlusion-mesh-b28lRJ6S.d.ts +194 -0
  81. package/dist/occupancy-grid-BV7RhB7I.d.ts +194 -0
  82. package/dist/occupancy-mesher-C2ecWhNp.d.ts +100 -0
  83. package/dist/{opfs-storage-3VZh-qH6.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
  84. package/dist/{opfs-storage-backend-JJiUJo-3.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
  85. package/dist/{persistence-middleware-DAPTTwB2.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
  86. package/dist/{planar-pnp-my9bqULS.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
  87. package/dist/{qr-debug-view-Brl73HGO.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
  88. package/dist/{qr-derived-pose-CmxTP2m4.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
  89. package/dist/{qr-detected-slice-VQssZmQJ.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
  90. package/dist/{qr-detection-controller-CDurHv8E.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
  91. package/dist/{qr-frontend--fFX25mV.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
  92. package/dist/{qr-pose-aggregation-B7tWJq7j.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
  93. package/dist/{qr-size-depth-context-CeAG53fy.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
  94. package/dist/{qr-size-measurer-DQdAsid1.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
  95. package/dist/{recording-options-BPCpMHLK.d.ts → recording-options-vASCFzc5.d.ts} +177 -24
  96. package/dist/{recording-replayer-DQKpAGyQ.d.ts → recording-replayer-D0V9WkmY.d.ts} +2 -2
  97. package/dist/{recording-slice-DaxHZvNu.d.ts → recording-slice-DTUtSVgd.d.ts} +2 -2
  98. package/dist/{replay-engine-CmKh_4wf.d.ts → replay-engine-xdrUgWJi.d.ts} +1 -1
  99. package/dist/{replay-scene-DBlDrV_x.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
  100. package/dist/sensors/absolute-orientation.d.ts +1 -1
  101. package/dist/sensors/absolute-orientation.js +23 -6
  102. package/dist/sensors/gps-error-handler.d.ts +1 -1
  103. package/dist/sensors/gps.d.ts +1 -1
  104. package/dist/sensors/index.d.ts +4 -4
  105. package/dist/sensors/permission-checker.d.ts +1 -1
  106. package/dist/state/app-selectors.d.ts +1 -1
  107. package/dist/state/combined-root-state.d.ts +1 -1
  108. package/dist/state/create-slam-app-store.d.ts +1 -1
  109. package/dist/state/create-slam-app-store.js +1 -1
  110. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  111. package/dist/state/gps-event-coordinator.d.ts +1 -1
  112. package/dist/state/index.d.ts +19 -19
  113. package/dist/state/index.js +2 -2
  114. package/dist/state/persistence-middleware.d.ts +1 -1
  115. package/dist/state/qr-detected-slice.d.ts +3 -3
  116. package/dist/state/recording-options.d.ts +2 -2
  117. package/dist/state/recording-options.js +134 -15
  118. package/dist/state/recording-replayer.d.ts +2 -2
  119. package/dist/state/recording-replayer.js +1 -1
  120. package/dist/state/recording-slice.d.ts +1 -1
  121. package/dist/state/replay-engine.d.ts +1 -1
  122. package/dist/state/store-subscribers.d.ts +2 -2
  123. package/dist/state/store-subscribers.js +2 -1
  124. package/dist/state/subscribe-to-selector.d.ts +1 -1
  125. package/dist/state/tracking-quality.d.ts +1 -1
  126. package/dist/state/tracking-quality.js +10 -1
  127. package/dist/state/tracking-slice.d.ts +1 -1
  128. package/dist/{state-E51QT_-j.js → state-BeKvn4B-.js} +1 -1
  129. package/dist/storage/file-system-utils.d.ts +1 -1
  130. package/dist/storage/index.d.ts +8 -8
  131. package/dist/storage/null-storage-backend.d.ts +1 -1
  132. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  133. package/dist/storage/opfs-storage.d.ts +1 -1
  134. package/dist/storage/opfs-storage.js +25 -1
  135. package/dist/storage/storage-backend.d.ts +1 -1
  136. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  137. package/dist/storage/zip-export.d.ts +1 -1
  138. package/dist/storage/zip-reader.d.ts +1 -1
  139. package/dist/{storage-backend-BdVLD0dU.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
  140. package/dist/{store-subscribers-CA6AJH_8.d.ts → store-subscribers-DScA7tdT.d.ts} +2 -2
  141. package/dist/{subscribe-to-selector-B6EUmQ3I.d.ts → subscribe-to-selector-B9ylM5Oh.d.ts} +1 -1
  142. package/dist/test-utils/browser-mocks.d.ts +1 -1
  143. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  144. package/dist/{tracking-slice-BuAZxXZ5.d.ts → tracking-slice-DMkHlbny.d.ts} +1 -1
  145. package/dist/types/ar-types.d.ts +1 -1
  146. package/dist/types/geo-types.d.ts +1 -1
  147. package/dist/types/index.d.ts +2 -2
  148. package/dist/utils/concurrency.d.ts +1 -1
  149. package/dist/utils/failure-tracker.d.ts +1 -1
  150. package/dist/utils/format-file-size.d.ts +1 -1
  151. package/dist/utils/fused-path.d.ts +1 -1
  152. package/dist/utils/index.d.ts +7 -7
  153. package/dist/utils/list-formatter.d.ts +1 -1
  154. package/dist/utils/logger.d.ts +1 -1
  155. package/dist/visualization/accuracy-circles.d.ts +1 -1
  156. package/dist/visualization/accuracy-circles.js +17 -8
  157. package/dist/visualization/alignment-lerper.d.ts +1 -1
  158. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  159. package/dist/visualization/ar-world-group-alignment.js +1 -1
  160. package/dist/visualization/camera-follower.d.ts +1 -1
  161. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  162. package/dist/visualization/frame-conversions.d.ts +1 -1
  163. package/dist/visualization/frustum-visibility.d.ts +1 -1
  164. package/dist/visualization/gps-anchor.d.ts +1 -1
  165. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  166. package/dist/visualization/gps-event-markers.d.ts +1 -1
  167. package/dist/visualization/gps-event-markers.js +1 -1
  168. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  169. package/dist/visualization/index.d.ts +20 -19
  170. package/dist/visualization/index.js +4 -3
  171. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  172. package/dist/visualization/leaflet-map-overlay.js +1 -282
  173. package/dist/visualization/lerp-utils.d.ts +2 -2
  174. package/dist/visualization/lerp-utils.js +27 -3
  175. package/dist/visualization/map-data.d.ts +1 -1
  176. package/dist/visualization/map-data.js +1 -32
  177. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  178. package/dist/visualization/map-overlay-draw.js +24 -2
  179. package/dist/visualization/map-overlay.d.ts +1 -1
  180. package/dist/visualization/occluder-mesh-driver.d.ts +130 -0
  181. package/dist/visualization/occluder-mesh-driver.js +219 -0
  182. package/dist/visualization/occlusion-mesh.d.ts +2 -0
  183. package/dist/visualization/occlusion-mesh.js +437 -0
  184. package/dist/visualization/three-dispose.d.ts +1 -1
  185. package/dist/visualization/vis-colors.d.ts +1 -1
  186. package/dist/{webxr-session-DG-eNfny.js → webxr-session-BCP2XK7k.js} +88 -13
  187. package/dist/{webxr-session-Do5L6G3B.d.ts → webxr-session-cRG2SYAQ.d.ts} +73 -10
  188. package/package.json +4 -4
  189. package/dist/lerp-utils-BLmHhkVe.d.ts +0 -20
  190. package/dist/occupancy-grid-DnOc-6PU.d.ts +0 -82
  191. /package/dist/{absolute-orientation-CAEh840t.d.ts → absolute-orientation-BAxc15J6.d.ts} +0 -0
  192. /package/dist/{alignment-lerper-Bil6vbVw.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
  193. /package/dist/{ar-types-BFX2r1wk.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
  194. /package/dist/{bresenham3d-CU1Gv2W4.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
  195. /package/dist/{camera-blit-capture-NKT9H5-e.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
  196. /package/dist/{camera-follower-CqJJ7zRj.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
  197. /package/dist/{capability-checker-DUQLg6gq.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
  198. /package/dist/{capture-failure-tracker-gsm8q7yi.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
  199. /package/dist/{capture-motion-gate-0Yl2NpQG.d.ts → capture-motion-gate-BGoAMkmf.d.ts} +0 -0
  200. /package/dist/{chromium-camera-access-workaround-D4K2uwwQ.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
  201. /package/dist/{concurrency-By0oOTZt.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
  202. /package/dist/{css3d-renderer-manager-B3kGNtcn.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
  203. /package/dist/{failure-tracker-BO7M7jF_.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
  204. /package/dist/{file-system-utils-DOajnMm-.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
  205. /package/dist/{format-file-size-Dc98ulzB.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
  206. /package/dist/{frame-loop-CpVjQ1dH.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
  207. /package/dist/{frustum-visibility-CeoCZchO.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
  208. /package/dist/{geo-types-3Ip1NzVR.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
  209. /package/dist/{gps-BBO3hKfT.d.ts → gps-CDKDQokE.d.ts} +0 -0
  210. /package/dist/{gps-compass-cubes-DSN87L8p.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
  211. /package/dist/{gps-error-handler-BbPsfw-_.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
  212. /package/dist/{gps-event-markers-Cz9RMA2k.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
  213. /package/dist/{h3-proximity-C-j0pw1j.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
  214. /package/dist/{hit-test-reticle-DV-2TpB9.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
  215. /package/dist/{image-quality-smCBoFj1.d.ts → image-quality-BMx2ko9-.d.ts} +0 -0
  216. /package/dist/{index-_Rhgoqkh.d.ts → index-D4vrgV4b.d.ts} +0 -0
  217. /package/dist/{index-BAPokxMb.d.ts → index-D74JTX4I.d.ts} +0 -0
  218. /package/dist/{list-formatter-KNMS0ay2.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
  219. /package/dist/{logger-CLnPrHiD.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
  220. /package/dist/{map-overlay-Dh2B97MP.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
  221. /package/dist/{permission-checker-CDuI8BwC.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
  222. /package/dist/{qr-pose-D2rfu5DA.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
  223. /package/dist/{scene-node-names-BiShOQDB.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
  224. /package/dist/{session-disposers-zv9OGXPB.js → session-disposers-BPrMplIC.js} +0 -0
  225. /package/dist/{three-dispose-Bt1k7WKh.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
  226. /package/dist/{vis-colors-Obw2pLqT.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
  227. /package/dist/{webxr-nue-basis-3nfjDJJp.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
  228. /package/dist/{xr-camera-texture-Cva1q3Qj.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
  229. /package/dist/{xr-error-handler-CtvHpjfm.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
  230. /package/dist/{xr-frame-loop-DIYtRcUa.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
  231. /package/dist/{zip-coverage-embed-DCAREVmw.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
  232. /package/dist/{zip-export-ol0S_Ofd.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
  233. /package/dist/{zip-reader-D9iPQwdQ.d.ts → zip-reader-CvruaGvD.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "../utils/logger.js";
2
- import { t as DEFAULT_MOTION_FILTER } from "../capture-motion-gate-4EscNZCw.js";
2
+ import { t as DEFAULT_MOTION_FILTER } from "../capture-motion-gate-_NdTSwKi.js";
3
3
  import { DEFAULT_QUALITY_FILTER } from "../ar/image-quality.js";
4
4
  //#region ../src/state/recording-options.ts
5
5
  /**
@@ -12,6 +12,55 @@ import { DEFAULT_QUALITY_FILTER } from "../ar/image-quality.js";
12
12
  */
13
13
  const log = createLogger("RecordingOptions");
14
14
  /**
15
+ * Configuration for the derived AR-space occupancy grid (the voxelization of
16
+ * the depth samples, port plan 2026-06-11). These settings do NOT change what
17
+ * is recorded — they govern the grid derived from the recorded depth points,
18
+ * so they also apply when replaying an existing recording, letting the same
19
+ * session be re-quantized at a different resolution.
20
+ */
21
+ /**
22
+ * Mesher strategy for the **persistent occluder** mesh, exposed as a recorder
23
+ * setting (2026-06-30 occluder-tuning, F2/F2b) so the two surface-hugging
24
+ * approaches can be A/B-tested on-device against the blocky baseline:
25
+ * - `'greedy'` — blocky greedy cubes (the existing default; fewest triangles).
26
+ * - `'corner-fit'` — cubes with corners pulled to the measured centroids
27
+ * (surface-hugging **and** watertight).
28
+ * - `'smooth'` — surface nets: smoothest, hugs the measured surface, but an open
29
+ * sheet over thin features.
30
+ *
31
+ * Maps directly onto `meshOccupiedCells` / `OcclusionMesh` `MeshMode` (a subset).
32
+ * `'per-face'` is intentionally not offered (same shape as `'greedy'`, more
33
+ * triangles — no on-device value).
34
+ */
35
+ const OCCLUDER_MESH_MODES = [
36
+ "greedy",
37
+ "corner-fit",
38
+ "smooth"
39
+ ];
40
+ /**
41
+ * Debug-visualization style for the **persistent occluder** mesh (2026-07-02
42
+ * debug-viz-styles plan) — which visible debug skin(s) `OcclusionMesh` renders
43
+ * on top of the invisible depth-only occluder:
44
+ * - `'off'` — no debug rendering (the shipped default; occlusion is invisible).
45
+ * - `'matcap'` — the original shiny semi-transparent cyan skin.
46
+ * - `'depth-shaded'` — matcap + camera-distance fade + white fresnel rim, so
47
+ * overlapping near/far surfaces read as separate shells.
48
+ * - `'wireframe'` — the raw triangulation as GL lines (mesh-structure
49
+ * inspection: triangle density, mesher seams, degenerate spots).
50
+ * - `'depth-shaded-wireframe'` — both of the above composed.
51
+ *
52
+ * Values array module-private (validation only; the settings `<select>`
53
+ * options are hardcoded in the recorder's `index.html`), type exported —
54
+ * mirroring `OCCLUDER_MESH_MODES` / {@link OccluderMeshMode}.
55
+ */
56
+ const OCCLUDER_DEBUG_STYLES = [
57
+ "off",
58
+ "matcap",
59
+ "depth-shaded",
60
+ "wireframe",
61
+ "depth-shaded-wireframe"
62
+ ];
63
+ /**
15
64
  * localStorage key for persisted options.
16
65
  *
17
66
  * **Multi-tab caveat:** All tabs/instances sharing the same origin will
@@ -25,8 +74,8 @@ const STORAGE_KEY = "gps-plus-slam-recorder-options";
25
74
  const DEFAULT_RECORDING_OPTIONS = {
26
75
  depth: {
27
76
  enabled: true,
28
- intervalMs: 1e3,
29
- gridSize: 16,
77
+ intervalMs: 500,
78
+ gridSize: 32,
30
79
  rgb: true
31
80
  },
32
81
  images: {
@@ -47,14 +96,24 @@ const DEFAULT_RECORDING_OPTIONS = {
47
96
  },
48
97
  occupancy: {
49
98
  cellSizeM: .15,
50
- minConfidence: 3
99
+ minConfidence: 3,
100
+ persistentOcclusion: true,
101
+ liveOcclusion: false,
102
+ occluderDebugStyle: "off",
103
+ occluderMeshMode: "smooth",
104
+ occluderRadiusM: 25
105
+ },
106
+ frameTileDisplay: {
107
+ divisor: 2,
108
+ maxTiles: 100
51
109
  },
52
- frameTileDisplay: { divisor: 2 },
53
110
  visualization: {
54
111
  frameTiles: true,
55
112
  occupancyCubes: true,
56
113
  gpsAlignmentMarkers: true,
57
- compassCubes: true
114
+ compassCubes: true,
115
+ headingUpMap: true,
116
+ statsOverlay: false
58
117
  },
59
118
  qr: {
60
119
  enabled: false,
@@ -76,7 +135,7 @@ const DEPTH_CONSTRAINTS = {
76
135
  },
77
136
  gridSize: {
78
137
  min: 2,
79
- max: 32,
138
+ max: 64,
80
139
  step: 1
81
140
  }
82
141
  };
@@ -181,6 +240,11 @@ const OCCUPANCY_CONSTRAINTS = {
181
240
  min: 1,
182
241
  max: 10,
183
242
  step: 1
243
+ },
244
+ occluderRadiusM: {
245
+ min: 0,
246
+ max: 200,
247
+ step: 5
184
248
  }
185
249
  };
186
250
  /**
@@ -191,11 +255,18 @@ const OCCUPANCY_CONSTRAINTS = {
191
255
  * same. The intended stops are 1, 2, 4, 8 (full / half / quarter / eighth); the
192
256
  * divisor is rounded to an integer so the resize target dimensions stay clean.
193
257
  */
194
- const FRAME_TILE_DISPLAY_CONSTRAINTS = { divisor: {
195
- min: 1,
196
- max: 8,
197
- step: 1
198
- } };
258
+ const FRAME_TILE_DISPLAY_CONSTRAINTS = {
259
+ divisor: {
260
+ min: 1,
261
+ max: 8,
262
+ step: 1
263
+ },
264
+ maxTiles: {
265
+ min: 0,
266
+ max: 2e3,
267
+ step: 10
268
+ }
269
+ };
199
270
  /**
200
271
  * Validation constraints for QR-capture options.
201
272
  *
@@ -257,7 +328,9 @@ function validateVisualizationOptions(options) {
257
328
  frameTiles: typeof options.frameTiles === "boolean" ? options.frameTiles : defaults.frameTiles,
258
329
  occupancyCubes: typeof options.occupancyCubes === "boolean" ? options.occupancyCubes : defaults.occupancyCubes,
259
330
  gpsAlignmentMarkers: typeof options.gpsAlignmentMarkers === "boolean" ? options.gpsAlignmentMarkers : defaults.gpsAlignmentMarkers,
260
- compassCubes: typeof options.compassCubes === "boolean" ? options.compassCubes : defaults.compassCubes
331
+ compassCubes: typeof options.compassCubes === "boolean" ? options.compassCubes : defaults.compassCubes,
332
+ headingUpMap: typeof options.headingUpMap === "boolean" ? options.headingUpMap : defaults.headingUpMap,
333
+ statsOverlay: typeof options.statsOverlay === "boolean" ? options.statsOverlay : defaults.statsOverlay
261
334
  };
262
335
  }
263
336
  /**
@@ -353,12 +426,55 @@ function validateImageOptions(options) {
353
426
  * `RangeError` on a non-finite cell size, and `clamp(NaN, …)` would otherwise
354
427
  * pass `NaN` straight through (it is `typeof 'number'`). Falling back to the
355
428
  * default keeps a corrupted stored value from crashing grid construction.
429
+ *
430
+ * **Backward-compat migration:** the occlusion options were a single
431
+ * `occlusionMeshEnabled` boolean before 2026-06-29; they are now the two
432
+ * composable booleans `persistentOcclusion` + `liveOcclusion`. A persisted
433
+ * object that predates the split carries only the legacy field, so when the new
434
+ * `persistentOcclusion` is absent we read `occlusionMeshEnabled` and map
435
+ * `true → persistentOcclusion: true` (the old mesh occluder is the persistent
436
+ * one); the legacy shape never enabled a live occluder, so `liveOcclusion`
437
+ * stays at its default. A present new field always wins over the legacy one.
438
+ * See `2026-06-29-occupancy-mesh-followups.md`.
439
+ */
440
+ /**
441
+ * Resolve `persistentOcclusion` with legacy migration. A **present** new field
442
+ * always wins over the legacy `occlusionMeshEnabled` — even when its value is
443
+ * invalid: a present-but-corrupt value falls back to the default, never to the
444
+ * legacy flag, so corrupted saved options can't silently flip the occluder
445
+ * against the "new field wins" contract. Only an **absent** new field migrates
446
+ * the legacy boolean (`true → persistent on`); else the default.
356
447
  */
448
+ function resolvePersistentOcclusion(options, legacyOcclusionMeshEnabled, defaultValue) {
449
+ if ("persistentOcclusion" in options) return typeof options.persistentOcclusion === "boolean" ? options.persistentOcclusion : defaultValue;
450
+ return typeof legacyOcclusionMeshEnabled === "boolean" ? legacyOcclusionMeshEnabled : defaultValue;
451
+ }
452
+ /**
453
+ * Resolve `occluderDebugStyle` with legacy migration, following the same
454
+ * contract as {@link resolvePersistentOcclusion}: a **present** new field
455
+ * always wins over the legacy `occluderDebugViz` boolean — even when its value
456
+ * is unknown/corrupt it falls back to the default (`'off'`), never to the
457
+ * legacy flag, so corrupted saved options can't silently turn a debug render
458
+ * on. Only an **absent** new field migrates the legacy boolean
459
+ * (`true → 'matcap'`, the skin the boolean used to enable); else the default.
460
+ */
461
+ function resolveOccluderDebugStyle(options, legacyOccluderDebugViz, defaultValue) {
462
+ if ("occluderDebugStyle" in options) return OCCLUDER_DEBUG_STYLES.includes(options.occluderDebugStyle) ? options.occluderDebugStyle : defaultValue;
463
+ if (typeof legacyOccluderDebugViz === "boolean") return legacyOccluderDebugViz ? "matcap" : "off";
464
+ return defaultValue;
465
+ }
357
466
  function validateOccupancyOptions(options) {
358
467
  const defaults = DEFAULT_RECORDING_OPTIONS.occupancy;
468
+ const legacyOcclusionMeshEnabled = options.occlusionMeshEnabled;
469
+ const legacyOccluderDebugViz = options.occluderDebugViz;
359
470
  return {
360
471
  cellSizeM: clamp(typeof options.cellSizeM === "number" && Number.isFinite(options.cellSizeM) ? options.cellSizeM : defaults.cellSizeM, OCCUPANCY_CONSTRAINTS.cellSizeM.min, OCCUPANCY_CONSTRAINTS.cellSizeM.max),
361
- minConfidence: clamp(typeof options.minConfidence === "number" && Number.isFinite(options.minConfidence) ? Math.round(options.minConfidence) : defaults.minConfidence, OCCUPANCY_CONSTRAINTS.minConfidence.min, OCCUPANCY_CONSTRAINTS.minConfidence.max)
472
+ minConfidence: clamp(typeof options.minConfidence === "number" && Number.isFinite(options.minConfidence) ? Math.round(options.minConfidence) : defaults.minConfidence, OCCUPANCY_CONSTRAINTS.minConfidence.min, OCCUPANCY_CONSTRAINTS.minConfidence.max),
473
+ persistentOcclusion: resolvePersistentOcclusion(options, legacyOcclusionMeshEnabled, defaults.persistentOcclusion),
474
+ liveOcclusion: typeof options.liveOcclusion === "boolean" ? options.liveOcclusion : defaults.liveOcclusion,
475
+ occluderDebugStyle: resolveOccluderDebugStyle(options, legacyOccluderDebugViz, defaults.occluderDebugStyle),
476
+ occluderMeshMode: OCCLUDER_MESH_MODES.includes(options.occluderMeshMode) ? options.occluderMeshMode : defaults.occluderMeshMode,
477
+ occluderRadiusM: clamp(typeof options.occluderRadiusM === "number" && Number.isFinite(options.occluderRadiusM) ? Math.round(options.occluderRadiusM) : defaults.occluderRadiusM, OCCUPANCY_CONSTRAINTS.occluderRadiusM.min, OCCUPANCY_CONSTRAINTS.occluderRadiusM.max)
362
478
  };
363
479
  }
364
480
  /**
@@ -370,7 +486,10 @@ function validateOccupancyOptions(options) {
370
486
  */
371
487
  function validateFrameTileDisplayOptions(options) {
372
488
  const defaults = DEFAULT_RECORDING_OPTIONS.frameTileDisplay;
373
- return { divisor: clamp(typeof options.divisor === "number" && Number.isFinite(options.divisor) ? Math.round(options.divisor) : defaults.divisor, FRAME_TILE_DISPLAY_CONSTRAINTS.divisor.min, FRAME_TILE_DISPLAY_CONSTRAINTS.divisor.max) };
489
+ return {
490
+ divisor: clamp(typeof options.divisor === "number" && Number.isFinite(options.divisor) ? Math.round(options.divisor) : defaults.divisor, FRAME_TILE_DISPLAY_CONSTRAINTS.divisor.min, FRAME_TILE_DISPLAY_CONSTRAINTS.divisor.max),
491
+ maxTiles: clamp(typeof options.maxTiles === "number" && Number.isFinite(options.maxTiles) ? Math.round(options.maxTiles) : defaults.maxTiles, FRAME_TILE_DISPLAY_CONSTRAINTS.maxTiles.min, FRAME_TILE_DISPLAY_CONSTRAINTS.maxTiles.max)
492
+ };
374
493
  }
375
494
  /**
376
495
  * Validate and normalize a full RecordingOptions object.
@@ -1,3 +1,3 @@
1
- import { N as CombinedRootState } from "../create-slam-app-store-n_VF63D5.js";
2
- import { n as replayRecording, t as ReplayRecordingOptions } from "../recording-replayer-DQKpAGyQ.js";
1
+ import { N as CombinedRootState } from "../create-slam-app-store-D3nzh9Wb.js";
2
+ import { n as replayRecording, t as ReplayRecordingOptions } from "../recording-replayer-D0V9WkmY.js";
3
3
  export { CombinedRootState, ReplayRecordingOptions, replayRecording };
@@ -1,6 +1,6 @@
1
1
  import { loadActionsFromZip } from "../storage/zip-reader.js";
2
2
  import { NullStorageBackend } from "../storage/null-storage-backend.js";
3
- import { t as createSlamAppStore } from "../create-slam-app-store-DkCHOpvb.js";
3
+ import { t as createSlamAppStore } from "../create-slam-app-store-DXiAnJwR.js";
4
4
  //#region ../src/state/recording-replayer.ts
5
5
  /**
6
6
  * Recording Replayer
@@ -1,2 +1,2 @@
1
- import { a as recordWriteFailure, i as recordDepthSample, n as SessionMetadata, o as recordingReducer, r as endSession, s as startSession, t as RecordingState } from "../recording-slice-DaxHZvNu.js";
1
+ import { a as recordWriteFailure, i as recordDepthSample, n as SessionMetadata, o as recordingReducer, r as endSession, s as startSession, t as RecordingState } from "../recording-slice-DTUtSVgd.js";
2
2
  export { RecordingState, SessionMetadata, endSession, recordDepthSample, recordWriteFailure, recordingReducer, startSession };
@@ -1,2 +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-CmKh_4wf.js";
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-xdrUgWJi.js";
2
2
  export { CompleteCallback, DEFAULT_MAX_DELAY_MS, ErrorCallback, ProgressCallback, ReplayAction, ReplayEngine, ReplayState, computeInterActionDelay, extractActionTimestamp };
@@ -1,3 +1,3 @@
1
- import { t as SubscribableStore } from "../subscribe-to-selector-B6EUmQ3I.js";
2
- import { n as wireStoreSubscribers, t as StoreSubscriberDeps } from "../store-subscribers-CA6AJH_8.js";
1
+ import { t as SubscribableStore } from "../subscribe-to-selector-B9ylM5Oh.js";
2
+ import { n as wireStoreSubscribers, t as StoreSubscriberDeps } from "../store-subscribers-DScA7tdT.js";
3
3
  export { StoreSubscriberDeps, SubscribableStore, wireStoreSubscribers };
@@ -1,5 +1,5 @@
1
1
  import { selectAlignmentMatrix, selectGpsPositions, selectOdometryPositions, selectOdometryRotations, selectZeroReference } from "./app-selectors.js";
2
- import { buildMapData } from "../visualization/map-data.js";
2
+ import { t as buildMapData } from "../map-data-D7rpMC6f.js";
3
3
  import { subscribeToSelector } from "./subscribe-to-selector.js";
4
4
  import { calcGpsCoords } from "gps-plus-slam-js";
5
5
  import { mat4, vec3 } from "gl-matrix";
@@ -46,6 +46,7 @@ function wireStoreSubscribers(store, deps) {
46
46
  deps.mapOverlay.render(buildMapData({
47
47
  rawGpsPath,
48
48
  odometryPositions: selectOdometryPositions(state),
49
+ odometryRotations: selectOdometryRotations(state),
49
50
  alignmentMatrix: selectAlignmentMatrix(state),
50
51
  zeroRef: selectZeroReference(state),
51
52
  alignmentSnapshots: alignmentSnapshotGps
@@ -1,2 +1,2 @@
1
- import { n as subscribeToSelector, t as SubscribableStore } from "../subscribe-to-selector-B6EUmQ3I.js";
1
+ import { n as subscribeToSelector, t as SubscribableStore } from "../subscribe-to-selector-B9ylM5Oh.js";
2
2
  export { SubscribableStore, subscribeToSelector };
@@ -1,2 +1,2 @@
1
- import { A as snapshotPushed, C as createTrackingQualityListenerMiddleware, D as resetTrackingQuality, E as reportUpdated, M as trackingQualityReducer, O as selectRecentAlignments, S as computeTrackingQualityReport, T as matrixDelta, _ as computeConvergence, b as computeGpsVsFusedDivergence, c as ConvergenceResult, d as GpsAccuracyResult, f as ResidualConsensusResult, g as TrackingQualityState, h as TrackingQualitySliceState, j as snapshotsTrimmed, k as selectTrackingQuality, l as CoverageResult, m as TrackingQualityReport, p as TrackingQualityOptions, s as AlignmentSnapshot, u as DEFAULT_TRACKING_QUALITY_OPTIONS, v as computeCoverage, w as degradedCountUpdated, x as computeResidualConsensus, y as computeGpsAccuracy } from "../create-slam-app-store-n_VF63D5.js";
1
+ import { A as snapshotPushed, C as createTrackingQualityListenerMiddleware, D as resetTrackingQuality, E as reportUpdated, M as trackingQualityReducer, O as selectRecentAlignments, S as computeTrackingQualityReport, T as matrixDelta, _ as computeConvergence, b as computeGpsVsFusedDivergence, c as ConvergenceResult, d as GpsAccuracyResult, f as ResidualConsensusResult, g as TrackingQualityState, h as TrackingQualitySliceState, j as snapshotsTrimmed, k as selectTrackingQuality, l as CoverageResult, m as TrackingQualityReport, p as TrackingQualityOptions, s as AlignmentSnapshot, u as DEFAULT_TRACKING_QUALITY_OPTIONS, v as computeCoverage, w as degradedCountUpdated, x as computeResidualConsensus, y as computeGpsAccuracy } from "../create-slam-app-store-D3nzh9Wb.js";
2
2
  export { AlignmentSnapshot, ConvergenceResult, CoverageResult, DEFAULT_TRACKING_QUALITY_OPTIONS, GpsAccuracyResult, ResidualConsensusResult, TrackingQualityOptions, TrackingQualityReport, TrackingQualitySliceState, TrackingQualityState, computeConvergence, computeCoverage, computeGpsAccuracy, computeGpsVsFusedDivergence, computeResidualConsensus, computeTrackingQualityReport, createTrackingQualityListenerMiddleware, degradedCountUpdated, matrixDelta, reportUpdated, resetTrackingQuality, selectRecentAlignments, selectTrackingQuality, snapshotPushed, snapshotsTrimmed, trackingQualityReducer };
@@ -79,8 +79,17 @@ function rampUp(value, low, high) {
79
79
  if (value >= high) return 1;
80
80
  return (value - low) / (high - low);
81
81
  }
82
- /** True iff every element of the matrix is a finite number (no NaN/Infinity). */
82
+ /**
83
+ * True iff the matrix is well-formed: exactly 16 elements, all finite (no
84
+ * NaN/Infinity). The length check matters because `computeConvergence` scores a
85
+ * pair as corrupt (fail side) only when this returns false; without it a finite
86
+ * but wrong-length snapshot (`AlignmentSnapshot.matrix` is typed `number[]`,
87
+ * not a 16-tuple) would slip past here, then hit `matrixDelta`'s own length
88
+ * guard which returns ZERO deltas = "perfectly stable" — inflating the score
89
+ * instead of degrading it (the same trap the non-finite guard avoids).
90
+ */
83
91
  function isFiniteMatrix(m) {
92
+ if (m.length !== 16) return false;
84
93
  for (let i = 0; i < m.length; i++) if (!Number.isFinite(m[i])) return false;
85
94
  return true;
86
95
  }
@@ -1,2 +1,2 @@
1
- import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-BuAZxXZ5.js";
1
+ import { a as TrackingSliceState, c as poseLost, d as selectLastRestartedPayload, f as selectLastSensorOrientation, g as trackingReducer, h as selectTrackingPhase, i as TrackingPhase, l as poseReceived, m as selectLostFrameCount, n as PoseReceivedPayload, o as clearLastRestartedPayload, p as selectLastValidPose, r as ResetTransformData, s as originReset, t as DeviceOrientation, u as resetTracking } from "../tracking-slice-DMkHlbny.js";
2
2
  export { DeviceOrientation, PoseReceivedPayload, ResetTransformData, TrackingPhase, TrackingSliceState, clearLastRestartedPayload, originReset, poseLost, poseReceived, resetTracking, selectLastRestartedPayload, selectLastSensorOrientation, selectLastValidPose, selectLostFrameCount, selectTrackingPhase, trackingReducer };
@@ -7,7 +7,7 @@ import "./state/gps-ar-pose-sampler.js";
7
7
  import "./state/app-selectors.js";
8
8
  import { selectTrackingQuality } from "./state/tracking-quality.js";
9
9
  import "./state/persistence-middleware.js";
10
- import "./create-slam-app-store-DkCHOpvb.js";
10
+ import "./create-slam-app-store-DXiAnJwR.js";
11
11
  import "./state/recording-replayer.js";
12
12
  import "./state/store-subscribers.js";
13
13
  import { add2dImage, calcRelativeCoordsInMeters, recordGpsEvent as recordGpsEvent$1, setZeroPos as setZeroPos$1 } from "gps-plus-slam-js";
@@ -1,2 +1,2 @@
1
- import { a as formatTimestamp, i as formatFrameFilename, n as SESSION_IMAGES_DIR, r as formatActionFilename, t as LEGACY_SESSION_IMAGES_DIR } from "../file-system-utils-DOajnMm-.js";
1
+ import { a as formatTimestamp, i as formatFrameFilename, n as SESSION_IMAGES_DIR, r as formatActionFilename, t as LEGACY_SESSION_IMAGES_DIR } from "../file-system-utils-DueXHxo9.js";
2
2
  export { LEGACY_SESSION_IMAGES_DIR, SESSION_IMAGES_DIR, formatActionFilename, formatFrameFilename, formatTimestamp };
@@ -1,9 +1,9 @@
1
- import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, n as SessionMetadata, o as getSessionHandle, r as checkStorageQuota, s as getSessionsRootHandle, u as resetOpfsStorage } from "../opfs-storage-3VZh-qH6.js";
2
- import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-BdVLD0dU.js";
3
- import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-D9iPQwdQ.js";
4
- import { t as NullStorageBackend } from "../null-storage-backend-B9rtyDfG.js";
5
- import { t as OpfsStorageBackend } from "../opfs-storage-backend-JJiUJo-3.js";
6
- import { a as formatTimestamp, i as formatFrameFilename, r as formatActionFilename } from "../file-system-utils-DOajnMm-.js";
7
- import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-ol0S_Ofd.js";
8
- import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-DCAREVmw.js";
1
+ import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, n as SessionMetadata, o as getSessionHandle, r as checkStorageQuota, s as getSessionsRootHandle, u as resetOpfsStorage } from "../opfs-storage-CTkxvxN4.js";
2
+ import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-sEtmM4yW.js";
3
+ import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-CvruaGvD.js";
4
+ import { t as NullStorageBackend } from "../null-storage-backend-B1-jlpin.js";
5
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-D6Z03fML.js";
6
+ import { a as formatTimestamp, i as formatFrameFilename, r as formatActionFilename } from "../file-system-utils-DueXHxo9.js";
7
+ import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-CtiUnAlJ.js";
8
+ import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-ncVbaZAz.js";
9
9
  export { type CreateSessionResult, type Entry, type ExportSessionAsZipOptions, type GpsPathCoord, MAX_ACTION_FILE_SIZE, NullStorageBackend, OpfsStorageBackend, type RecordedAction, type SessionMetadata, type StorageBackend, type ZipActionEntry, type ZipContributorAddFile, type ZipExportContributor, type ZipExportResult, type ZipSubdirEntry, checkStorageQuota, createSession, downloadZip, embedCoverageInSessionJson, exportSessionAsZip, exportSessionHandleAsZip, formatActionFilename, formatFrameFilename, formatTimestamp, getAppRootHandle, getSessionHandle, getSessionsRootHandle, initOpfsStorage, listSessions, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadataFromBlob, loadSessionMetadata as loadSessionMetadataFromZip, readZipEntries, resetOpfsStorage, resetSessionHandles, syncToExternalZip, writeSessionMetadata };
@@ -1,2 +1,2 @@
1
- import { t as NullStorageBackend } from "../null-storage-backend-B9rtyDfG.js";
1
+ import { t as NullStorageBackend } from "../null-storage-backend-B1-jlpin.js";
2
2
  export { NullStorageBackend };
@@ -1,2 +1,2 @@
1
- import { t as OpfsStorageBackend } from "../opfs-storage-backend-JJiUJo-3.js";
1
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-D6Z03fML.js";
2
2
  export { OpfsStorageBackend };
@@ -1,2 +1,2 @@
1
- import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, f as setSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, m as writeFrame, n as SessionMetadata, o as getSessionHandle, p as writeAction, r as checkStorageQuota, s as getSessionsRootHandle, t as CreateSessionResult, u as resetOpfsStorage } from "../opfs-storage-3VZh-qH6.js";
1
+ import { a as getAppRootHandle, c as initOpfsStorage, d as resetSessionHandles, f as setSessionHandles, h as writeSessionMetadata, i as createSession, l as listSessions, m as writeFrame, n as SessionMetadata, o as getSessionHandle, p as writeAction, r as checkStorageQuota, s as getSessionsRootHandle, t as CreateSessionResult, u as resetOpfsStorage } from "../opfs-storage-CTkxvxN4.js";
2
2
  export { CreateSessionResult, SessionMetadata, checkStorageQuota, createSession, getAppRootHandle, getSessionHandle, getSessionsRootHandle, initOpfsStorage, listSessions, resetOpfsStorage, resetSessionHandles, setSessionHandles, writeAction, writeFrame, writeSessionMetadata };
@@ -89,6 +89,19 @@ function getAppRootHandle() {
89
89
  return gpsPlusSlamDir;
90
90
  }
91
91
  /**
92
+ * True when a session directory named `name` already exists under `dir`.
93
+ * Used by {@link createSession} to avoid silently reusing a directory when two
94
+ * recordings start within the same (second-resolution) timestamp.
95
+ */
96
+ async function sessionDirExists(dir, name) {
97
+ try {
98
+ await dir.getDirectoryHandle(name, { create: false });
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ /**
92
105
  * Create a new recording session.
93
106
  *
94
107
  * Creates the directory structure:
@@ -96,6 +109,11 @@ function getAppRootHandle() {
96
109
  * - /gps-plus-slam/sessions/recording-{timestamp}/actions/
97
110
  * - /gps-plus-slam/sessions/recording-{timestamp}/images/ (legacy: frames/)
98
111
  *
112
+ * The folder name is `recording-{timestamp}` at whole-second resolution; if a
113
+ * directory with that name already exists (two recordings started in the same
114
+ * UTC second), a numeric suffix (`-2`, `-3`, …) is appended so the new session
115
+ * never silently reuses and corrupts the earlier one's directory.
116
+ *
99
117
  * @param timestamp - Session start time (used for folder naming)
100
118
  * @param _contextTag - Opaque tag; not used by the flat layout but accepted
101
119
  * for interface compatibility with wrapping backends
@@ -104,7 +122,13 @@ function getAppRootHandle() {
104
122
  */
105
123
  async function createSession(timestamp, _contextTag) {
106
124
  if (!sessionsDir) throw new Error("OPFS storage not initialized. Call initOpfsStorage first.");
107
- const sessionName = `recording-${formatTimestamp(timestamp)}`;
125
+ const baseName = `recording-${formatTimestamp(timestamp)}`;
126
+ let sessionName = baseName;
127
+ let suffix = 1;
128
+ while (await sessionDirExists(sessionsDir, sessionName)) {
129
+ suffix += 1;
130
+ sessionName = `${baseName}-${suffix}`;
131
+ }
108
132
  currentSessionHandle = await sessionsDir.getDirectoryHandle(sessionName, { create: true });
109
133
  actionsHandle = await currentSessionHandle.getDirectoryHandle("actions", { create: true });
110
134
  framesHandle = await currentSessionHandle.getDirectoryHandle(SESSION_IMAGES_DIR, { create: true });
@@ -1,2 +1,2 @@
1
- import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-BdVLD0dU.js";
1
+ import { n as StorageBackend, t as CreateSessionResult } from "../storage-backend-sEtmM4yW.js";
2
2
  export { CreateSessionResult, StorageBackend };
@@ -1,2 +1,2 @@
1
- import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-DCAREVmw.js";
1
+ import { t as embedCoverageInSessionJson } from "../zip-coverage-embed-ncVbaZAz.js";
2
2
  export { embedCoverageInSessionJson };
@@ -1,2 +1,2 @@
1
- import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-ol0S_Ofd.js";
1
+ import { a as downloadZip, c as syncToExternalZip, i as ZipExportResult, n as ZipContributorAddFile, o as exportSessionAsZip, r as ZipExportContributor, s as exportSessionHandleAsZip, t as ExportSessionAsZipOptions } from "../zip-export-CtiUnAlJ.js";
2
2
  export { ExportSessionAsZipOptions, ZipContributorAddFile, ZipExportContributor, ZipExportResult, downloadZip, exportSessionAsZip, exportSessionHandleAsZip, syncToExternalZip };
@@ -1,2 +1,2 @@
1
- import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-D9iPQwdQ.js";
1
+ import { a as ZipActionEntry, c as loadEntriesFromSubdir, d as loadSessionMetadataFromBlob, f as readZipEntries, i as RecordedAction, l as loadGpsPathFromBlob, n as GpsPathCoord, o as ZipSubdirEntry, r as MAX_ACTION_FILE_SIZE, s as loadActionsFromZip, t as Entry, u as loadSessionMetadata } from "../zip-reader-CvruaGvD.js";
2
2
  export { Entry, GpsPathCoord, MAX_ACTION_FILE_SIZE, RecordedAction, ZipActionEntry, ZipSubdirEntry, loadActionsFromZip, loadEntriesFromSubdir, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries };
@@ -1,4 +1,4 @@
1
- import { n as SessionMetadata } from "./opfs-storage-3VZh-qH6.js";
1
+ import { n as SessionMetadata } from "./opfs-storage-CTkxvxN4.js";
2
2
 
3
3
  //#region ../src/storage/storage-backend.d.ts
4
4
  interface CreateSessionResult {
@@ -1,5 +1,5 @@
1
- import { t as MapData } from "./map-data-B4oEehUh.js";
2
- import { t as SubscribableStore } from "./subscribe-to-selector-B6EUmQ3I.js";
1
+ import { t as MapData } from "./map-data-PNfGKGWx.js";
2
+ import { t as SubscribableStore } from "./subscribe-to-selector-B9ylM5Oh.js";
3
3
  import { LatLong, Matrix4, Quaternion, Vector3 } from "gps-plus-slam-js";
4
4
 
5
5
  //#region ../src/state/store-subscribers.d.ts
@@ -1,4 +1,4 @@
1
- import { N as CombinedRootState } from "./create-slam-app-store-n_VF63D5.js";
1
+ import { N as CombinedRootState } from "./create-slam-app-store-D3nzh9Wb.js";
2
2
 
3
3
  //#region ../src/state/subscribe-to-selector.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { o as WebXRQuaternion, s as WebXRVec3 } from "../ar-types-BFX2r1wk.js";
1
+ import { o as WebXRQuaternion, s as WebXRVec3 } from "../ar-types-Cc5yvBwn.js";
2
2
  import { Mock } from "vitest";
3
3
 
4
4
  //#region ../src/test-utils/browser-mocks.d.ts
@@ -1,4 +1,4 @@
1
- import { i as RecordedAction } from "../zip-reader-D9iPQwdQ.js";
1
+ import { i as RecordedAction } from "../zip-reader-CvruaGvD.js";
2
2
  import { LatLong } from "gps-plus-slam-js";
3
3
 
4
4
  //#region ../src/test-utils/zip-round-trip-helpers.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ARPose } from "./ar-types-BFX2r1wk.js";
1
+ import { t as ARPose } from "./ar-types-Cc5yvBwn.js";
2
2
  import { OdometryTrackingRestartedPayload, Quaternion, Vector3 } from "gps-plus-slam-js";
3
3
  import * as _$_reduxjs_toolkit0 from "@reduxjs/toolkit";
4
4
 
@@ -1,2 +1,2 @@
1
- import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-BFX2r1wk.js";
1
+ import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-Cc5yvBwn.js";
2
2
  export { ARPose, ArPoseTuples, DepthPoint, DepthSample, RgbTuple, WebXRQuaternion, WebXRVec3 };
@@ -1,2 +1,2 @@
1
- import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-3Ip1NzVR.js";
1
+ import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DrGVu8nS.js";
2
2
  export { GpsCoord, RawGpsSample, RefPointMarker };
@@ -1,3 +1,3 @@
1
- import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-BFX2r1wk.js";
2
- import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-3Ip1NzVR.js";
1
+ import { a as RgbTuple, i as DepthSample, n as ArPoseTuples, o as WebXRQuaternion, r as DepthPoint, s as WebXRVec3, t as ARPose } from "../ar-types-Cc5yvBwn.js";
2
+ import { n as RawGpsSample, r as RefPointMarker, t as GpsCoord } from "../geo-types-DrGVu8nS.js";
3
3
  export { ARPose, ArPoseTuples, DepthPoint, DepthSample, GpsCoord, RawGpsSample, RefPointMarker, RgbTuple, WebXRQuaternion, WebXRVec3 };
@@ -1,2 +1,2 @@
1
- import { n as mapWithConcurrencyLimit, t as forEachWithConcurrencyLimit } from "../concurrency-By0oOTZt.js";
1
+ import { n as mapWithConcurrencyLimit, t as forEachWithConcurrencyLimit } from "../concurrency-DPeGBH0X.js";
2
2
  export { forEachWithConcurrencyLimit, mapWithConcurrencyLimit };
@@ -1,2 +1,2 @@
1
- import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-BO7M7jF_.js";
1
+ import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-CeKf5HIc.js";
2
2
  export { FailureTracker, FailureTrackerConfig, createFailureTracker };
@@ -1,2 +1,2 @@
1
- import { t as formatFileSize } from "../format-file-size-Dc98ulzB.js";
1
+ import { t as formatFileSize } from "../format-file-size-DxN-EBLc.js";
2
2
  export { formatFileSize };
@@ -1,2 +1,2 @@
1
- import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-v6eOmLl3.js";
1
+ import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C5pp0PJU.js";
2
2
  export { FusedPathInput, computeFusedPath, fusedGpsFromOdom };
@@ -1,8 +1,8 @@
1
- import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CLnPrHiD.js";
2
- import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-v6eOmLl3.js";
3
- import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-BO7M7jF_.js";
4
- import { n as mapWithConcurrencyLimit } from "../concurrency-By0oOTZt.js";
5
- import { t as geodesicAngleRad } from "../index-BAPokxMb.js";
6
- import { t as formatFileSize } from "../format-file-size-Dc98ulzB.js";
7
- import { t as listFormatter } from "../list-formatter-KNMS0ay2.js";
1
+ import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CqTd5Kpk.js";
2
+ import { n as computeFusedPath, r as fusedGpsFromOdom, t as FusedPathInput } from "../fused-path-C5pp0PJU.js";
3
+ import { n as FailureTrackerConfig, r as createFailureTracker, t as FailureTracker } from "../failure-tracker-CeKf5HIc.js";
4
+ import { n as mapWithConcurrencyLimit } from "../concurrency-DPeGBH0X.js";
5
+ import { t as geodesicAngleRad } from "../index-D74JTX4I.js";
6
+ import { t as formatFileSize } from "../format-file-size-DxN-EBLc.js";
7
+ import { t as listFormatter } from "../list-formatter-DLFDnS70.js";
8
8
  export { FailureTracker, FailureTrackerConfig, FusedPathInput, LogEntry, LogLevel, Logger, clearLogBuffer, computeFusedPath, createFailureTracker, createLogger, formatFileSize, fusedGpsFromOdom, geodesicAngleRad, getGlobalLogLevel, getLogBuffer, listFormatter, mapWithConcurrencyLimit, setGlobalLogLevel, subscribeToLogs };
@@ -1,2 +1,2 @@
1
- import { t as listFormatter } from "../list-formatter-KNMS0ay2.js";
1
+ import { t as listFormatter } from "../list-formatter-DLFDnS70.js";
2
2
  export { listFormatter };
@@ -1,2 +1,2 @@
1
- import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CLnPrHiD.js";
1
+ import { a as createLogger, c as setGlobalLogLevel, i as clearLogBuffer, l as subscribeToLogs, n as LogLevel, o as getGlobalLogLevel, r as Logger, s as getLogBuffer, t as LogEntry } from "../logger-CqTd5Kpk.js";
2
2
  export { LogEntry, LogLevel, Logger, clearLogBuffer, createLogger, getGlobalLogLevel, getLogBuffer, setGlobalLogLevel, subscribeToLogs };
@@ -1,2 +1,2 @@
1
- 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-BtqOP_v1.js";
1
+ 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-DkWBkF5g.js";
2
2
  export { ACCURACY_CIRCLE_FILL_OPACITY, ACCURACY_CIRCLE_STROKE_OPACITY, ACCURACY_CIRCLE_WEIGHT, AccuracyCircleSample, addAccuracyCircles };
@@ -14,16 +14,26 @@ import L from "leaflet";
14
14
  /**
15
15
  * Style for per-event GPS accuracy circles. Radius comes from the GPS event's
16
16
  * horizontal accuracy in meters; larger circles mean lower-quality fixes.
17
- * Filled and stroked with a highly transparent variant of the path color so
18
- * overlapping circles remain legible without obscuring the basemap or the
19
- * polyline drawn on top.
17
+ *
18
+ * Circles are drawn **stroke-only** (no fill): Leaflet composites overlapping
19
+ * semi-transparent SVG fills, so N stacked filled circles reach
20
+ * `1 − (1 − fillOpacity)^N` opacity and paint the basemap solid on dense
21
+ * recordings (~90 % at ~18 overlaps with the old 0.12 fill). A fill-free
22
+ * outline never accumulates an opaque interior, so the basemap shows through at
23
+ * ANY overlap density while each accuracy radius stays readable.
24
+ */
25
+ /**
26
+ * @deprecated Stroke-only since 2026-06-28 (Finding 1) — accuracy circles no
27
+ * longer render a fill, so this opacity is unused. Retained as an exported
28
+ * constant only to preserve the published API surface; do not pass it to
29
+ * `L.circle`. See `2026-06-28-map-rings-transparency-and-view-direction-user-feedback.md`.
20
30
  */
21
31
  const ACCURACY_CIRCLE_FILL_OPACITY = .12;
22
32
  const ACCURACY_CIRCLE_STROKE_OPACITY = .5;
23
33
  const ACCURACY_CIRCLE_WEIGHT = 1;
24
34
  /**
25
- * Draw one transparent circle per sample whose `accuracy` is a finite
26
- * positive number. Samples without a usable accuracy value are skipped so
35
+ * Draw one stroke-only (un-filled) circle per sample whose `accuracy` is a
36
+ * finite positive number. Samples without a usable accuracy value are skipped so
27
37
  * pre-accuracy recordings still render their polyline.
28
38
  *
29
39
  * Circles are added to `map` immediately. The caller is responsible for
@@ -33,7 +43,7 @@ const ACCURACY_CIRCLE_WEIGHT = 1;
33
43
  * @param map - The Leaflet map to add circles to.
34
44
  * @param samples - GPS samples to consider; non-positive / non-finite
35
45
  * `accuracy` values are silently skipped.
36
- * @param color - CSS color string used for both stroke and fill.
46
+ * @param color - CSS color string used for the stroke (no fill is drawn).
37
47
  * @returns The created `L.Circle` instances, in the order they were added.
38
48
  * Callers that track layers for cleanup can append these to their list.
39
49
  */
@@ -46,8 +56,7 @@ function addAccuracyCircles(map, samples, color) {
46
56
  color,
47
57
  weight: 1,
48
58
  opacity: ACCURACY_CIRCLE_STROKE_OPACITY,
49
- fillColor: color,
50
- fillOpacity: ACCURACY_CIRCLE_FILL_OPACITY
59
+ fill: false
51
60
  }).addTo(map);
52
61
  circles.push(circle);
53
62
  }
@@ -1,2 +1,2 @@
1
- import { n as createAlignmentLerper, t as AlignmentLerper } from "../alignment-lerper-Bil6vbVw.js";
1
+ import { n as createAlignmentLerper, t as AlignmentLerper } from "../alignment-lerper-gD70k7Fq.js";
2
2
  export { AlignmentLerper, createAlignmentLerper };