gps-plus-slam-app-framework 1.7.0 → 1.9.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 (238) 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-BbAiCRUT.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-Cfg0trC3.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 +2 -2
  54. package/dist/core/index.js +2 -2
  55. package/dist/{create-slam-app-store-n_VF63D5.d.ts → create-slam-app-store-D1QSMuKu.d.ts} +8 -9
  56. package/dist/{create-slam-app-store-DkCHOpvb.js → create-slam-app-store-DXiAnJwR.js} +9 -2
  57. package/dist/{depth-grid-lookup-DQ-IYhO3.d.ts → depth-grid-lookup-D5atpNZ3.d.ts} +1 -1
  58. package/dist/{depth-sampler-asc8exHt.d.ts → depth-sampler-CwObAwEA.d.ts} +33 -1
  59. package/dist/{depth-unprojection-DjsbJW9L.d.ts → depth-unprojection-DRdBU90S.d.ts} +1 -1
  60. package/dist/{enable-gps-ar-DkNhFv4Y.d.ts → enable-gps-ar-BBtRO2FJ.d.ts} +4 -4
  61. package/dist/{frame-conversions-DObhj8Wf.d.ts → frame-conversions-DDcNHkTb.d.ts} +1 -1
  62. package/dist/{fused-path-v6eOmLl3.d.ts → fused-path-C5pp0PJU.d.ts} +1 -1
  63. package/dist/geo/h3-proximity.d.ts +1 -1
  64. package/dist/geo/index.d.ts +1 -1
  65. package/dist/{gps-anchor-BeFO2RYa.d.ts → gps-anchor-CEjGqEVD.d.ts} +1 -1
  66. package/dist/{gps-ar-pose-sampler-DHMZmYAc.d.ts → gps-ar-pose-sampler-DEGNk2CI.d.ts} +1 -1
  67. package/dist/{gps-event-coordinator-CjWW636x.d.ts → gps-event-coordinator-BWpgBrHq.d.ts} +4 -4
  68. package/dist/{image-capture-CNZDrQXG.d.ts → image-capture-Bp6Pd4Md.d.ts} +3 -3
  69. package/dist/{image-capture-B2-b6tKK.js → image-capture-DVcuqMtX.js} +1 -1
  70. package/dist/{index-BODDUTvD.d.ts → index-B6iBnO_K.d.ts} +1 -1
  71. package/dist/index-DXPVzOpP.d.ts +2 -0
  72. package/dist/{index-BneSj4_5.d.ts → index-cCQWuYqy.d.ts} +3 -3
  73. package/dist/index.d.ts +82 -80
  74. package/dist/index.js +10 -8
  75. package/dist/{leaflet-map-overlay-CmIyiE8n.d.ts → leaflet-map-overlay-C8GS4cUy.d.ts} +64 -10
  76. package/dist/leaflet-map-overlay-CBWPvaX9.js +452 -0
  77. package/dist/lerp-utils-Qs8OhumS.d.ts +42 -0
  78. package/dist/map-data-1tF5gcYV.js +124 -0
  79. package/dist/map-data-CUnTzRIC.d.ts +78 -0
  80. package/dist/{map-overlay-draw-ymz1s7fG.d.ts → map-overlay-draw-6WJeq0Kg.d.ts} +1 -1
  81. package/dist/{null-storage-backend-B9rtyDfG.d.ts → null-storage-backend-B1-jlpin.d.ts} +2 -2
  82. package/dist/occlusion-mesh-kXCQ2FM1.d.ts +194 -0
  83. package/dist/occupancy-grid-BV7RhB7I.d.ts +194 -0
  84. package/dist/occupancy-mesher-C2ecWhNp.d.ts +100 -0
  85. package/dist/{opfs-storage-3VZh-qH6.d.ts → opfs-storage-CTkxvxN4.d.ts} +5 -0
  86. package/dist/{opfs-storage-backend-JJiUJo-3.d.ts → opfs-storage-backend-D6Z03fML.d.ts} +2 -2
  87. package/dist/{persistence-middleware-DAPTTwB2.d.ts → persistence-middleware-CG-t08X4.d.ts} +1 -1
  88. package/dist/{planar-pnp-my9bqULS.d.ts → planar-pnp-D7bc_rLX.d.ts} +1 -1
  89. package/dist/{qr-debug-view-Brl73HGO.d.ts → qr-debug-view-BJooqbiP.d.ts} +1 -1
  90. package/dist/{qr-derived-pose-CmxTP2m4.d.ts → qr-derived-pose-BwNWn8oV.d.ts} +2 -2
  91. package/dist/{qr-detected-slice-VQssZmQJ.d.ts → qr-detected-slice-TzVb8bij.d.ts} +4 -4
  92. package/dist/{qr-detection-controller-CDurHv8E.d.ts → qr-detection-controller-Bo4XfQTP.d.ts} +3 -3
  93. package/dist/{qr-frontend--fFX25mV.d.ts → qr-frontend-BPURlh3S.d.ts} +1 -1
  94. package/dist/{qr-pose-aggregation-B7tWJq7j.d.ts → qr-pose-aggregation-CypnmjA-.d.ts} +1 -1
  95. package/dist/{qr-size-depth-context-CeAG53fy.d.ts → qr-size-depth-context-apAOmbrZ.d.ts} +2 -2
  96. package/dist/{qr-size-measurer-DQdAsid1.d.ts → qr-size-measurer-DxiMqRUw.d.ts} +3 -3
  97. package/dist/{recording-options-BPCpMHLK.d.ts → recording-options-DYKWVI93.d.ts} +203 -24
  98. package/dist/{recording-replayer-DQKpAGyQ.d.ts → recording-replayer-BJ8Lf0QY.d.ts} +2 -2
  99. package/dist/{recording-slice-DaxHZvNu.d.ts → recording-slice-BW8SiLo1.d.ts} +2 -2
  100. package/dist/{replay-engine-CmKh_4wf.d.ts → replay-engine-DmCAc7WM.d.ts} +1 -1
  101. package/dist/{replay-scene-DBlDrV_x.d.ts → replay-scene-zJ8SFDjE.d.ts} +3 -3
  102. package/dist/sensors/absolute-orientation.d.ts +1 -1
  103. package/dist/sensors/absolute-orientation.js +23 -6
  104. package/dist/sensors/gps-error-handler.d.ts +1 -1
  105. package/dist/sensors/gps.d.ts +1 -1
  106. package/dist/sensors/index.d.ts +4 -4
  107. package/dist/sensors/permission-checker.d.ts +1 -1
  108. package/dist/state/app-selectors.d.ts +1 -1
  109. package/dist/state/combined-root-state.d.ts +1 -1
  110. package/dist/state/create-slam-app-store.d.ts +1 -1
  111. package/dist/state/create-slam-app-store.js +1 -1
  112. package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
  113. package/dist/state/gps-event-coordinator.d.ts +1 -1
  114. package/dist/state/index.d.ts +19 -19
  115. package/dist/state/index.js +2 -2
  116. package/dist/state/persistence-middleware.d.ts +1 -1
  117. package/dist/state/qr-detected-slice.d.ts +3 -3
  118. package/dist/state/recording-options.d.ts +2 -2
  119. package/dist/state/recording-options.js +150 -19
  120. package/dist/state/recording-replayer.d.ts +2 -2
  121. package/dist/state/recording-replayer.js +1 -1
  122. package/dist/state/recording-slice.d.ts +1 -1
  123. package/dist/state/replay-engine.d.ts +1 -1
  124. package/dist/state/store-subscribers.d.ts +2 -2
  125. package/dist/state/store-subscribers.js +26 -12
  126. package/dist/state/subscribe-to-selector.d.ts +1 -1
  127. package/dist/state/tracking-quality.d.ts +1 -1
  128. package/dist/state/tracking-quality.js +14 -6
  129. package/dist/state/tracking-slice.d.ts +1 -1
  130. package/dist/{state-E51QT_-j.js → state-BeKvn4B-.js} +1 -1
  131. package/dist/storage/file-system-utils.d.ts +1 -1
  132. package/dist/storage/index.d.ts +8 -8
  133. package/dist/storage/null-storage-backend.d.ts +1 -1
  134. package/dist/storage/opfs-storage-backend.d.ts +1 -1
  135. package/dist/storage/opfs-storage.d.ts +1 -1
  136. package/dist/storage/opfs-storage.js +30 -1
  137. package/dist/storage/storage-backend.d.ts +1 -1
  138. package/dist/storage/zip-coverage-embed.d.ts +1 -1
  139. package/dist/storage/zip-export.d.ts +1 -1
  140. package/dist/storage/zip-reader.d.ts +2 -2
  141. package/dist/storage/zip-reader.js +20 -7
  142. package/dist/{storage-backend-BdVLD0dU.d.ts → storage-backend-sEtmM4yW.d.ts} +1 -1
  143. package/dist/{store-subscribers-CA6AJH_8.d.ts → store-subscribers-DJUErW-o.d.ts} +6 -3
  144. package/dist/{subscribe-to-selector-B6EUmQ3I.d.ts → subscribe-to-selector-dQV8SpsO.d.ts} +1 -1
  145. package/dist/test-utils/browser-mocks.d.ts +1 -1
  146. package/dist/test-utils/browser-mocks.js +2 -0
  147. package/dist/test-utils/zip-round-trip-helpers.d.ts +1 -1
  148. package/dist/{tracking-slice-BuAZxXZ5.d.ts → tracking-slice-DMkHlbny.d.ts} +1 -1
  149. package/dist/types/ar-types.d.ts +1 -1
  150. package/dist/types/geo-types.d.ts +1 -1
  151. package/dist/types/index.d.ts +2 -2
  152. package/dist/utils/concurrency.d.ts +1 -1
  153. package/dist/utils/failure-tracker.d.ts +1 -1
  154. package/dist/utils/format-file-size.d.ts +1 -1
  155. package/dist/utils/fused-path.d.ts +1 -1
  156. package/dist/utils/index.d.ts +7 -7
  157. package/dist/utils/list-formatter.d.ts +1 -1
  158. package/dist/utils/logger.d.ts +1 -1
  159. package/dist/visualization/accuracy-circles.d.ts +1 -1
  160. package/dist/visualization/accuracy-circles.js +17 -8
  161. package/dist/visualization/alignment-lerper.d.ts +1 -1
  162. package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
  163. package/dist/visualization/ar-world-group-alignment.js +1 -1
  164. package/dist/visualization/camera-follower.d.ts +1 -1
  165. package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
  166. package/dist/visualization/frame-conversions.d.ts +1 -1
  167. package/dist/visualization/frustum-visibility.d.ts +1 -1
  168. package/dist/visualization/gps-anchor.d.ts +1 -1
  169. package/dist/visualization/gps-compass-cubes.d.ts +1 -1
  170. package/dist/visualization/gps-event-markers.d.ts +1 -1
  171. package/dist/visualization/gps-event-markers.js +1 -1
  172. package/dist/visualization/hit-test-reticle.d.ts +1 -1
  173. package/dist/visualization/index.d.ts +20 -19
  174. package/dist/visualization/index.js +4 -3
  175. package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
  176. package/dist/visualization/leaflet-map-overlay.js +1 -282
  177. package/dist/visualization/lerp-utils.d.ts +2 -2
  178. package/dist/visualization/lerp-utils.js +27 -3
  179. package/dist/visualization/map-data.d.ts +1 -1
  180. package/dist/visualization/map-data.js +1 -32
  181. package/dist/visualization/map-overlay-draw.d.ts +1 -1
  182. package/dist/visualization/map-overlay-draw.js +24 -2
  183. package/dist/visualization/map-overlay.d.ts +1 -1
  184. package/dist/visualization/occluder-mesh-driver.d.ts +130 -0
  185. package/dist/visualization/occluder-mesh-driver.js +219 -0
  186. package/dist/visualization/occlusion-mesh.d.ts +2 -0
  187. package/dist/visualization/occlusion-mesh.js +437 -0
  188. package/dist/visualization/three-dispose.d.ts +1 -1
  189. package/dist/visualization/vis-colors.d.ts +1 -1
  190. package/dist/{webxr-session-DG-eNfny.js → webxr-session-BCP2XK7k.js} +88 -13
  191. package/dist/{webxr-session-Do5L6G3B.d.ts → webxr-session-BWCpGd-2.d.ts} +73 -10
  192. package/dist/{zip-reader-D9iPQwdQ.d.ts → zip-reader-BzENzMH6.d.ts} +36 -10
  193. package/package.json +9 -5
  194. package/dist/index-_Rhgoqkh.d.ts +0 -2
  195. package/dist/lerp-utils-BLmHhkVe.d.ts +0 -20
  196. package/dist/map-data-B4oEehUh.d.ts +0 -49
  197. package/dist/occupancy-grid-DnOc-6PU.d.ts +0 -82
  198. /package/dist/{absolute-orientation-CAEh840t.d.ts → absolute-orientation-BAxc15J6.d.ts} +0 -0
  199. /package/dist/{alignment-lerper-Bil6vbVw.d.ts → alignment-lerper-gD70k7Fq.d.ts} +0 -0
  200. /package/dist/{ar-types-BFX2r1wk.d.ts → ar-types-Cc5yvBwn.d.ts} +0 -0
  201. /package/dist/{bresenham3d-CU1Gv2W4.d.ts → bresenham3d-DGc4nIb-.d.ts} +0 -0
  202. /package/dist/{camera-blit-capture-NKT9H5-e.d.ts → camera-blit-capture-BimSdyih.d.ts} +0 -0
  203. /package/dist/{camera-follower-CqJJ7zRj.d.ts → camera-follower-DfBm2J5i.d.ts} +0 -0
  204. /package/dist/{capability-checker-DUQLg6gq.d.ts → capability-checker-BTTpygvZ.d.ts} +0 -0
  205. /package/dist/{capture-failure-tracker-gsm8q7yi.d.ts → capture-failure-tracker-_BVfhpxv.d.ts} +0 -0
  206. /package/dist/{capture-motion-gate-0Yl2NpQG.d.ts → capture-motion-gate-BGoAMkmf.d.ts} +0 -0
  207. /package/dist/{chromium-camera-access-workaround-D4K2uwwQ.d.ts → chromium-camera-access-workaround-C6yJLXz9.d.ts} +0 -0
  208. /package/dist/{concurrency-By0oOTZt.d.ts → concurrency-DPeGBH0X.d.ts} +0 -0
  209. /package/dist/{css3d-renderer-manager-B3kGNtcn.d.ts → css3d-renderer-manager-CPpVtvgs.d.ts} +0 -0
  210. /package/dist/{failure-tracker-BO7M7jF_.d.ts → failure-tracker-CeKf5HIc.d.ts} +0 -0
  211. /package/dist/{file-system-utils-DOajnMm-.d.ts → file-system-utils-DueXHxo9.d.ts} +0 -0
  212. /package/dist/{format-file-size-Dc98ulzB.d.ts → format-file-size-DxN-EBLc.d.ts} +0 -0
  213. /package/dist/{frame-loop-CpVjQ1dH.d.ts → frame-loop-7GBMBu68.d.ts} +0 -0
  214. /package/dist/{frustum-visibility-CeoCZchO.d.ts → frustum-visibility-CokkgS_i.d.ts} +0 -0
  215. /package/dist/{geo-types-3Ip1NzVR.d.ts → geo-types-DrGVu8nS.d.ts} +0 -0
  216. /package/dist/{gps-BBO3hKfT.d.ts → gps-CDKDQokE.d.ts} +0 -0
  217. /package/dist/{gps-compass-cubes-DSN87L8p.d.ts → gps-compass-cubes-1mooS3BS.d.ts} +0 -0
  218. /package/dist/{gps-error-handler-BbPsfw-_.d.ts → gps-error-handler-C34GQuTu.d.ts} +0 -0
  219. /package/dist/{gps-event-markers-Cz9RMA2k.d.ts → gps-event-markers-Di2nBzx-.d.ts} +0 -0
  220. /package/dist/{h3-proximity-C-j0pw1j.d.ts → h3-proximity-D0GTHQmr.d.ts} +0 -0
  221. /package/dist/{hit-test-reticle-DV-2TpB9.d.ts → hit-test-reticle-CaQbrhbk.d.ts} +0 -0
  222. /package/dist/{image-quality-smCBoFj1.d.ts → image-quality-BMx2ko9-.d.ts} +0 -0
  223. /package/dist/{index-BAPokxMb.d.ts → index-D74JTX4I.d.ts} +0 -0
  224. /package/dist/{list-formatter-KNMS0ay2.d.ts → list-formatter-DLFDnS70.d.ts} +0 -0
  225. /package/dist/{logger-CLnPrHiD.d.ts → logger-CqTd5Kpk.d.ts} +0 -0
  226. /package/dist/{map-overlay-Dh2B97MP.d.ts → map-overlay-BCQgvIgU.d.ts} +0 -0
  227. /package/dist/{permission-checker-CDuI8BwC.d.ts → permission-checker-B-VtjnBA.d.ts} +0 -0
  228. /package/dist/{qr-pose-D2rfu5DA.d.ts → qr-pose-y3vaEd6x.d.ts} +0 -0
  229. /package/dist/{scene-node-names-BiShOQDB.d.ts → scene-node-names-CGb-ylBd.d.ts} +0 -0
  230. /package/dist/{session-disposers-zv9OGXPB.js → session-disposers-BPrMplIC.js} +0 -0
  231. /package/dist/{three-dispose-Bt1k7WKh.d.ts → three-dispose-BMOAmPhb.d.ts} +0 -0
  232. /package/dist/{vis-colors-Obw2pLqT.d.ts → vis-colors-C7HI0u9_.d.ts} +0 -0
  233. /package/dist/{webxr-nue-basis-3nfjDJJp.d.ts → webxr-nue-basis-CaCnYxuq.d.ts} +0 -0
  234. /package/dist/{xr-camera-texture-Cva1q3Qj.d.ts → xr-camera-texture-q0eIDSRg.d.ts} +0 -0
  235. /package/dist/{xr-error-handler-CtvHpjfm.d.ts → xr-error-handler-uVGDu9IX.d.ts} +0 -0
  236. /package/dist/{xr-frame-loop-DIYtRcUa.d.ts → xr-frame-loop-DX3JPpAU.d.ts} +0 -0
  237. /package/dist/{zip-coverage-embed-DCAREVmw.d.ts → zip-coverage-embed-ncVbaZAz.d.ts} +0 -0
  238. /package/dist/{zip-export-ol0S_Ofd.d.ts → zip-export-CtiUnAlJ.d.ts} +0 -0
@@ -1,10 +1,56 @@
1
1
  import { createDepthUnprojector } from "./depth-unprojection.js";
2
2
  import { bresenham3d } from "./bresenham3d.js";
3
+ import { i as cellKey, o as unpackCellCoord, r as cellCoordsInKeyRange, s as unpackCellKey, t as CELL_KEY_LIMIT } from "../cell-key-DIB4rOTV.js";
3
4
  //#region ../src/ar/occupancy-grid.ts
5
+ /**
6
+ * Highest `minConfidence` a consumer can ask for (mirrors
7
+ * `OCCUPANCY_CONSTRAINTS.minConfidence.max`). Once a cell's count exceeds this,
8
+ * further observations can no longer change ANY consumer's occupied set, so they
9
+ * do not bump {@link OccupancyGrid.getRevision} — letting a settled scene skip
10
+ * redundant re-meshing.
11
+ */
12
+ const MAX_RELEVANT_COUNT = 10;
4
13
  var OccupancyGrid = class {
5
14
  cellSizeM;
6
15
  carveStopCells;
7
16
  cells = /* @__PURE__ */ new Map();
17
+ /**
18
+ * Chunk index (Step 2 of the 2026-07-03 long-session fps plan): cell keys
19
+ * grouped by {@link CHUNK_EDGE_CELLS}³ chunk, maintained incrementally on
20
+ * add/carve/clear (O(1) per mutation). Lets {@link getOccupiedCellsWithin}
21
+ * visit only the chunks a query sphere touches — cost independent of total
22
+ * explored area. Empty chunk sets are dropped.
23
+ */
24
+ chunks = /* @__PURE__ */ new Map();
25
+ /**
26
+ * Per-chunk dirty revision, bumped whenever a mutation inside the chunk
27
+ * could change some consumer's occupied set (same semantics as the global
28
+ * {@link revision}). This is the bookkeeping a future dirty-chunk remesh
29
+ * needs (2026-07-01 worker plan, Phase-2 sketch) — landed with the index so
30
+ * that plan becomes a consumer-side change only. Entries survive a chunk
31
+ * emptying (a consumer must still see "changed"); reset by {@link clear}.
32
+ */
33
+ chunkRevisions = /* @__PURE__ */ new Map();
34
+ /**
35
+ * Monotonic counter bumped whenever the **occupied set** (at any
36
+ * `minConfidence ≤ MAX_RELEVANT_COUNT`) could have changed: a cell added, a
37
+ * cell removed by carving, a cell's count rising while still `≤
38
+ * MAX_RELEVANT_COUNT` (a possible threshold crossing), or `clear`. Re-observing
39
+ * an already-settled cell (count `> MAX_RELEVANT_COUNT`) does NOT bump it, so a
40
+ * consumer can cheaply skip a full re-derive (cube refresh / occluder re-mesh)
41
+ * when the revision is unchanged — the dominant idle-time saving over a long
42
+ * session (see `2026-06-30-occluder-tuning-followups.md`).
43
+ */
44
+ revision = 0;
45
+ /**
46
+ * Memo of the last {@link getOccupiedCells} walk (Step 1.2 of the
47
+ * 2026-07-03 long-session fps plan): with cubes + occluder both on, every
48
+ * throttled refresh triggers two identical full-grid walks with the same
49
+ * minConfidence floor — the second is answered from here. Valid only while
50
+ * `revision` is unchanged, so it is only used for floors the revision
51
+ * counter actually tracks (≤ {@link MAX_RELEVANT_COUNT}).
52
+ */
53
+ snapshotCache = null;
8
54
  constructor(options) {
9
55
  const cellSizeM = options?.cellSizeM ?? .15;
10
56
  const carveStopCells = options?.carveStopCells ?? 2;
@@ -18,6 +64,16 @@ var OccupancyGrid = class {
18
64
  return this.cells.size;
19
65
  }
20
66
  /**
67
+ * A monotonic version that changes only when the occupied set (at any
68
+ * `minConfidence ≤ 10`) could have changed. A consumer caches the value it
69
+ * last meshed/rendered and skips its full re-derive when it is unchanged — so
70
+ * a settled scene (already-observed cells being re-observed) costs nothing.
71
+ * See {@link revision}.
72
+ */
73
+ getRevision() {
74
+ return this.revision;
75
+ }
76
+ /**
21
77
  * Fold one depth sample into the grid: unproject each point, carve free
22
78
  * space from the camera cell to the point cell, then count the point's
23
79
  * cell as occupied. Points that cannot be unprojected (no
@@ -39,12 +95,21 @@ var OccupancyGrid = class {
39
95
  const unprojector = createDepthUnprojector(sample.cameraPos, sample.cameraRot, sample.projectionMatrix);
40
96
  if (!unprojector) return 0;
41
97
  const cameraCell = this.cellForPosition(sample.cameraPos);
98
+ const cameraInRange = cellInKeyRange(cameraCell);
99
+ const carvedEndpointKeys = /* @__PURE__ */ new Set();
42
100
  const endpoints = [];
43
101
  for (const point of sample.points) {
44
102
  const world = unprojector.unproject(point);
45
103
  if (!world) continue;
46
104
  const cell = this.cellForPosition(world);
47
- if (!cellsEqual(cameraCell, cell)) this.carve(cameraCell, cell);
105
+ if (!cellInKeyRange(cell)) continue;
106
+ if (cameraInRange && !cellsEqual(cameraCell, cell)) {
107
+ const key = cellKey(cell);
108
+ if (!carvedEndpointKeys.has(key)) {
109
+ carvedEndpointKeys.add(key);
110
+ this.carve(cameraCell, cell);
111
+ }
112
+ }
48
113
  endpoints.push({
49
114
  cell,
50
115
  world,
@@ -54,12 +119,152 @@ var OccupancyGrid = class {
54
119
  for (const endpoint of endpoints) this.increment(endpoint.cell, endpoint.world, endpoint.rgb);
55
120
  return endpoints.length;
56
121
  }
57
- /** Occupied cells observed at least `minObservations` times (default 1). */
122
+ /**
123
+ * Occupied cells observed at least `minObservations` times (default 1).
124
+ *
125
+ * The result is a **shared immutable snapshot**: a repeated call with the
126
+ * same floor on an unchanged grid returns the SAME array instance (the
127
+ * memo above), so callers must never mutate it. Floors above
128
+ * {@link MAX_RELEVANT_COUNT} bypass the memo — the revision counter does
129
+ * not track their threshold crossings, so a cached result could go stale.
130
+ */
58
131
  getOccupiedCells(minObservations = 1) {
132
+ const cache = this.snapshotCache;
133
+ if (cache && cache.revision === this.revision && cache.minObservations === minObservations) return cache.cells;
134
+ const result = [];
135
+ for (const [key, record] of this.cells) if (record.count >= minObservations) result.push(unpackCellKey(key));
136
+ if (minObservations <= MAX_RELEVANT_COUNT) this.snapshotCache = {
137
+ revision: this.revision,
138
+ minObservations,
139
+ cells: result
140
+ };
141
+ return result;
142
+ }
143
+ /**
144
+ * Flat `[x0,y0,z0, x1,y1,z1, …]` variant of {@link getOccupiedCells} for the
145
+ * mesh-worker pack path (Step 1.3 of the 2026-07-03 long-session fps plan):
146
+ * `packMeshRequest` ships the snapshot to the worker as a transferable
147
+ * Int32Array, so handing it over flat deletes the tuple-array intermediate
148
+ * it used to re-flatten. Same cells, same order as the tuple API.
149
+ *
150
+ * Unlike {@link getOccupiedCells} this returns a **fresh array every call**
151
+ * — the pack path TRANSFERS the buffer to the worker (detaching it), so a
152
+ * shared/cached array would be destroyed under the grid. When the tuple
153
+ * memo is warm (the cubes refresh just snapshotted the same floor) the
154
+ * fresh array is flattened from it in O(matching cells) without a re-walk.
155
+ */
156
+ getOccupiedCellsFlat(minObservations = 1) {
157
+ const cache = this.snapshotCache;
158
+ if (cache && cache.revision === this.revision && cache.minObservations === minObservations) {
159
+ const cells = cache.cells;
160
+ const flat = new Int32Array(cells.length * 3);
161
+ for (let i = 0; i < cells.length; i++) {
162
+ const c = cells[i];
163
+ flat[i * 3] = c[0];
164
+ flat[i * 3 + 1] = c[1];
165
+ flat[i * 3 + 2] = c[2];
166
+ }
167
+ return flat;
168
+ }
169
+ const flat = new Int32Array(this.cells.size * 3);
170
+ let used = 0;
171
+ for (const [key, record] of this.cells) if (record.count >= minObservations) {
172
+ flat[used] = unpackCellCoord(key, 0);
173
+ flat[used + 1] = unpackCellCoord(key, 1);
174
+ flat[used + 2] = unpackCellCoord(key, 2);
175
+ used += 3;
176
+ }
177
+ return used === flat.length ? flat : flat.slice(0, used);
178
+ }
179
+ /**
180
+ * Occupied cells (≥ `minObservations`) whose **centers** lie within
181
+ * `radiusM` of `centerPos` — the viewer-local window that keeps consumer
182
+ * refresh cost independent of total explored area (Step 2 of the
183
+ * 2026-07-03 long-session fps plan). Walks only the chunks whose
184
+ * cell-center AABB intersects the query sphere; chunks entirely inside it
185
+ * skip the per-cell distance test. Result set ≡ the brute-force radius
186
+ * filter of {@link getOccupiedCells} (property-tested; iteration order may
187
+ * differ). Fresh array per call (no memo — the camera moves every frame).
188
+ *
189
+ * @throws RangeError for a non-finite or non-positive `radiusM` (a
190
+ * windowed query without a window is an upstream bug). A non-finite
191
+ * `centerPos` (tracking glitch) returns `[]` instead — mirrors
192
+ * {@link raycast}'s non-finite policy.
193
+ */
194
+ getOccupiedCellsWithin(centerPos, radiusM, minObservations = 1) {
59
195
  const result = [];
60
- for (const record of this.cells.values()) if (record.count >= minObservations) result.push(record.cell);
196
+ this.forEachOccupiedCellWithin(centerPos, radiusM, minObservations, (x, y, z) => {
197
+ result.push([
198
+ x,
199
+ y,
200
+ z
201
+ ]);
202
+ });
61
203
  return result;
62
204
  }
205
+ /**
206
+ * Flat `[x0,y0,z0, …]` variant of {@link getOccupiedCellsWithin} for the
207
+ * mesh-worker pack path (same contract as {@link getOccupiedCellsFlat}:
208
+ * fresh array every call — packing transfers/detaches the buffer).
209
+ */
210
+ getOccupiedCellsWithinFlat(centerPos, radiusM, minObservations = 1) {
211
+ const coords = [];
212
+ this.forEachOccupiedCellWithin(centerPos, radiusM, minObservations, (x, y, z) => {
213
+ coords.push(x, y, z);
214
+ });
215
+ return Int32Array.from(coords);
216
+ }
217
+ /**
218
+ * Per-chunk dirty revision (see the field docs): 0 for a never-touched
219
+ * chunk. `chunk` is in chunk coordinates (`floor(cell / 16)` per axis).
220
+ */
221
+ getChunkRevision(chunk) {
222
+ return this.chunkRevisions.get(chunkKeyOf(chunk[0], chunk[1], chunk[2])) ?? 0;
223
+ }
224
+ /** Shared sphere-window walk behind the tuple and flat queries. */
225
+ forEachOccupiedCellWithin(centerPos, radiusM, minObservations, visit) {
226
+ if (!Number.isFinite(radiusM) || radiusM <= 0) throw new RangeError(`radiusM must be a positive finite number, got ${radiusM}`);
227
+ if (!isFiniteTriple(centerPos)) return;
228
+ const cs = this.cellSizeM;
229
+ const r2 = radiusM * radiusM;
230
+ const skipBeyond = (radiusM + cs * 1e-9) ** 2;
231
+ const wholeInsideR2 = (radiusM - cs * 1e-9) ** 2;
232
+ for (const [chunkKey, cellKeys] of this.chunks) {
233
+ const bx = unpackChunkCoord(chunkKey, 0) * CHUNK_EDGE_CELLS * cs;
234
+ const by = unpackChunkCoord(chunkKey, 1) * CHUNK_EDGE_CELLS * cs;
235
+ const bz = unpackChunkCoord(chunkKey, 2) * CHUNK_EDGE_CELLS * cs;
236
+ const extent = (CHUNK_EDGE_CELLS - 1) * cs;
237
+ let dMin2 = 0;
238
+ let dMax2 = 0;
239
+ for (let axis = 0; axis < 3; axis++) {
240
+ const lo = axis === 0 ? bx : axis === 1 ? by : bz;
241
+ const hi = lo + extent;
242
+ const v = centerPos[axis];
243
+ const below = lo - v;
244
+ const above = v - hi;
245
+ const dMin = Math.max(below, above, 0);
246
+ dMin2 += dMin * dMin;
247
+ const dMax = Math.max(Math.abs(v - lo), Math.abs(hi - v));
248
+ dMax2 += dMax * dMax;
249
+ }
250
+ if (dMin2 > skipBeyond) continue;
251
+ const wholeInside = dMax2 <= wholeInsideR2;
252
+ for (const key of cellKeys) {
253
+ const record = this.cells.get(key);
254
+ if (!record || record.count < minObservations) continue;
255
+ const x = unpackCellCoord(key, 0);
256
+ const y = unpackCellCoord(key, 1);
257
+ const z = unpackCellCoord(key, 2);
258
+ if (!wholeInside) {
259
+ const dx = x * cs - centerPos[0];
260
+ const dy = y * cs - centerPos[1];
261
+ const dz = z * cs - centerPos[2];
262
+ if (dx * dx + dy * dy + dz * dz > r2) continue;
263
+ }
264
+ visit(x, y, z);
265
+ }
266
+ }
267
+ }
63
268
  /** Quantize a raw-WebXR position to its grid cell (round per axis). */
64
269
  cellForPosition(pos) {
65
270
  return [
@@ -86,12 +291,13 @@ var OccupancyGrid = class {
86
291
  * `cellSizeM/2` of the cell center per axis.
87
292
  */
88
293
  getCellPoint(cell) {
294
+ if (!cellInKeyRange(cell)) return null;
89
295
  const record = this.cells.get(cellKey(cell));
90
296
  if (!record || record.count === 0) return null;
91
297
  return [
92
- record.posSum[0] / record.count,
93
- record.posSum[1] / record.count,
94
- record.posSum[2] / record.count
298
+ record.posSumX / record.count,
299
+ record.posSumY / record.count,
300
+ record.posSumZ / record.count
95
301
  ];
96
302
  }
97
303
  /**
@@ -101,13 +307,14 @@ var OccupancyGrid = class {
101
307
  * height-based coloring. Channels are rounded and clamped to 0–255.
102
308
  */
103
309
  getCellColor(cell) {
310
+ if (!cellInKeyRange(cell)) return null;
104
311
  const record = this.cells.get(cellKey(cell));
105
312
  if (!record || record.colorCount === 0) return null;
106
313
  const average = (sum) => Math.min(255, Math.max(0, Math.round(sum / record.colorCount)));
107
314
  return [
108
- average(record.colorSum[0]),
109
- average(record.colorSum[1]),
110
- average(record.colorSum[2])
315
+ average(record.colorSumR),
316
+ average(record.colorSumG),
317
+ average(record.colorSumB)
111
318
  ];
112
319
  }
113
320
  /**
@@ -117,8 +324,11 @@ var OccupancyGrid = class {
117
324
  */
118
325
  raycast(startPos, endPos, minObservations = 1) {
119
326
  if (!isFiniteTriple(startPos) || !isFiniteTriple(endPos)) return null;
327
+ const startCell = this.cellForPosition(startPos);
328
+ const endCell = this.cellForPosition(endPos);
329
+ if (!cellInKeyRange(startCell) || !cellInKeyRange(endCell)) return null;
120
330
  let hit = null;
121
- bresenham3d(this.cellForPosition(startPos), this.cellForPosition(endPos), (cell) => {
331
+ bresenham3d(startCell, endCell, (cell) => {
122
332
  const record = this.cells.get(cellKey(cell));
123
333
  if (record && record.count >= minObservations) {
124
334
  hit = cell;
@@ -130,7 +340,10 @@ var OccupancyGrid = class {
130
340
  }
131
341
  /** Remove all occupied cells (e.g. on store swap / new session). */
132
342
  clear() {
343
+ if (this.cells.size > 0) this.revision++;
133
344
  this.cells.clear();
345
+ this.chunks.clear();
346
+ this.chunkRevisions.clear();
134
347
  }
135
348
  /**
136
349
  * Delete occupied cells along the camera→point ray (the space was seen
@@ -141,45 +354,88 @@ var OccupancyGrid = class {
141
354
  */
142
355
  carve(cameraCell, pointCell) {
143
356
  bresenham3d(cameraCell, pointCell, (cell) => {
144
- if (!cellsEqual(cell, pointCell)) this.cells.delete(cellKey(cell));
357
+ if (!cellsEqual(cell, pointCell)) {
358
+ const key = cellKey(cell);
359
+ if (this.cells.delete(key)) {
360
+ this.revision++;
361
+ this.unindexCell(cell, key);
362
+ }
363
+ }
145
364
  return true;
146
365
  }, this.carveStopCells);
147
366
  }
367
+ /** Add a newly-occupied cell to its chunk's index set. */
368
+ indexCell(cell, key) {
369
+ const ck = chunkKeyOf(Math.floor(cell[0] / CHUNK_EDGE_CELLS), Math.floor(cell[1] / CHUNK_EDGE_CELLS), Math.floor(cell[2] / CHUNK_EDGE_CELLS));
370
+ let set = this.chunks.get(ck);
371
+ if (!set) {
372
+ set = /* @__PURE__ */ new Set();
373
+ this.chunks.set(ck, set);
374
+ }
375
+ set.add(key);
376
+ }
377
+ /** Remove a carved cell from its chunk (dropping an emptied chunk set). */
378
+ unindexCell(cell, key) {
379
+ const ck = chunkKeyOf(Math.floor(cell[0] / CHUNK_EDGE_CELLS), Math.floor(cell[1] / CHUNK_EDGE_CELLS), Math.floor(cell[2] / CHUNK_EDGE_CELLS));
380
+ const set = this.chunks.get(ck);
381
+ if (set) {
382
+ set.delete(key);
383
+ if (set.size === 0) this.chunks.delete(ck);
384
+ }
385
+ this.bumpChunkRevision(ck);
386
+ }
387
+ /** Mirror of the global revision bump, scoped to one chunk. */
388
+ bumpChunkRevision(chunkKey) {
389
+ this.chunkRevisions.set(chunkKey, (this.chunkRevisions.get(chunkKey) ?? 0) + 1);
390
+ }
148
391
  increment(cell, world, rgb) {
149
392
  const key = cellKey(cell);
150
393
  let record = this.cells.get(key);
151
394
  if (!record) {
152
395
  record = {
153
- cell,
154
396
  count: 0,
155
- posSum: [
156
- 0,
157
- 0,
158
- 0
159
- ],
397
+ posSumX: 0,
398
+ posSumY: 0,
399
+ posSumZ: 0,
160
400
  colorCount: 0,
161
- colorSum: [
162
- 0,
163
- 0,
164
- 0
165
- ]
401
+ colorSumR: 0,
402
+ colorSumG: 0,
403
+ colorSumB: 0
166
404
  };
167
405
  this.cells.set(key, record);
406
+ this.indexCell(cell, key);
168
407
  }
169
408
  record.count++;
170
- record.posSum[0] += world[0];
171
- record.posSum[1] += world[1];
172
- record.posSum[2] += world[2];
409
+ if (record.count <= MAX_RELEVANT_COUNT) {
410
+ this.revision++;
411
+ this.bumpChunkRevision(chunkKeyOf(Math.floor(cell[0] / CHUNK_EDGE_CELLS), Math.floor(cell[1] / CHUNK_EDGE_CELLS), Math.floor(cell[2] / CHUNK_EDGE_CELLS)));
412
+ }
413
+ record.posSumX += world[0];
414
+ record.posSumY += world[1];
415
+ record.posSumZ += world[2];
173
416
  if (rgb && isFiniteTriple(rgb)) {
174
417
  record.colorCount++;
175
- record.colorSum[0] += rgb[0];
176
- record.colorSum[1] += rgb[1];
177
- record.colorSum[2] += rgb[2];
418
+ record.colorSumR += rgb[0];
419
+ record.colorSumG += rgb[1];
420
+ record.colorSumB += rgb[2];
178
421
  }
179
422
  }
180
423
  };
181
- function cellKey(cell) {
182
- return `${cell[0]},${cell[1]},${cell[2]}`;
424
+ function cellInKeyRange(cell) {
425
+ return cellCoordsInKeyRange(cell, CELL_KEY_LIMIT);
426
+ }
427
+ const CHUNK_EDGE_CELLS = 16;
428
+ const CHUNK_KEY_OFFSET = 4096;
429
+ const CHUNK_KEY_FIELD = 8192;
430
+ const CHUNK_KEY_FIELD_SQ = CHUNK_KEY_FIELD * CHUNK_KEY_FIELD;
431
+ function chunkKeyOf(cx, cy, cz) {
432
+ return (cx + CHUNK_KEY_OFFSET) * CHUNK_KEY_FIELD_SQ + (cy + CHUNK_KEY_OFFSET) * CHUNK_KEY_FIELD + (cz + CHUNK_KEY_OFFSET);
433
+ }
434
+ /** Inverse of {@link chunkKeyOf} for one axis (0 = x, 1 = y, 2 = z). */
435
+ function unpackChunkCoord(key, axis) {
436
+ if (axis === 0) return Math.floor(key / CHUNK_KEY_FIELD_SQ) - CHUNK_KEY_OFFSET;
437
+ if (axis === 1) return Math.floor(key / CHUNK_KEY_FIELD) % CHUNK_KEY_FIELD - CHUNK_KEY_OFFSET;
438
+ return key % CHUNK_KEY_FIELD - CHUNK_KEY_OFFSET;
183
439
  }
184
440
  function cellsEqual(a, b) {
185
441
  return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
@@ -188,4 +444,4 @@ function isFiniteTriple(v) {
188
444
  return Number.isFinite(v[0]) && Number.isFinite(v[1]) && Number.isFinite(v[2]);
189
445
  }
190
446
  //#endregion
191
- export { OccupancyGrid };
447
+ export { OccupancyGrid, cellKey, unpackCellKey };
@@ -0,0 +1,2 @@
1
+ import { a as meshOccupiedCells, i as OccupancyMeshResult, n as MeshMode, r as MeshOccupiedCellsOptions, t as Aabb } from "../occupancy-mesher-C2ecWhNp.js";
2
+ export { Aabb, MeshMode, MeshOccupiedCellsOptions, OccupancyMeshResult, meshOccupiedCells };