@cavegiant/cave-world-r3f 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 (339) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +189 -0
  4. package/dist/core/connector-label/readiness.d.ts +11 -0
  5. package/dist/core/connector-label/readiness.d.ts.map +1 -0
  6. package/dist/core/connector-label/readiness.js +31 -0
  7. package/dist/core/connector-label/readiness.js.map +1 -0
  8. package/dist/core/connector-label/types.d.ts +64 -0
  9. package/dist/core/connector-label/types.d.ts.map +1 -0
  10. package/dist/core/connector-label/types.js +47 -0
  11. package/dist/core/connector-label/types.js.map +1 -0
  12. package/dist/core/context/cave-world-context.d.ts +96 -0
  13. package/dist/core/context/cave-world-context.d.ts.map +1 -0
  14. package/dist/core/context/cave-world-context.js +100 -0
  15. package/dist/core/context/cave-world-context.js.map +1 -0
  16. package/dist/core/errors.d.ts +34 -0
  17. package/dist/core/errors.d.ts.map +1 -0
  18. package/dist/core/errors.js +17 -0
  19. package/dist/core/errors.js.map +1 -0
  20. package/dist/core/events/event-bus.d.ts +122 -0
  21. package/dist/core/events/event-bus.d.ts.map +1 -0
  22. package/dist/core/events/event-bus.js +62 -0
  23. package/dist/core/events/event-bus.js.map +1 -0
  24. package/dist/core/hooks/use-cave-event.d.ts +13 -0
  25. package/dist/core/hooks/use-cave-event.d.ts.map +1 -0
  26. package/dist/core/hooks/use-cave-event.js +12 -0
  27. package/dist/core/hooks/use-cave-event.js.map +1 -0
  28. package/dist/core/i18n/messages.d.ts +82 -0
  29. package/dist/core/i18n/messages.d.ts.map +1 -0
  30. package/dist/core/i18n/messages.js +131 -0
  31. package/dist/core/i18n/messages.js.map +1 -0
  32. package/dist/core/index.d.ts +11 -0
  33. package/dist/core/index.d.ts.map +1 -0
  34. package/dist/core/index.js +47 -0
  35. package/dist/core/index.js.map +1 -0
  36. package/dist/core/logging/logger.d.ts +29 -0
  37. package/dist/core/logging/logger.d.ts.map +1 -0
  38. package/dist/core/logging/logger.js +47 -0
  39. package/dist/core/logging/logger.js.map +1 -0
  40. package/dist/core/registry/component-registry.d.ts +41 -0
  41. package/dist/core/registry/component-registry.d.ts.map +1 -0
  42. package/dist/core/registry/component-registry.js +34 -0
  43. package/dist/core/registry/component-registry.js.map +1 -0
  44. package/dist/core/registry/object-registry.d.ts +38 -0
  45. package/dist/core/registry/object-registry.d.ts.map +1 -0
  46. package/dist/core/registry/object-registry.js +32 -0
  47. package/dist/core/registry/object-registry.js.map +1 -0
  48. package/dist/explore/cave-explore.d.ts +85 -0
  49. package/dist/explore/cave-explore.d.ts.map +1 -0
  50. package/dist/explore/cave-explore.js +204 -0
  51. package/dist/explore/cave-explore.js.map +1 -0
  52. package/dist/explore/cave-orbit-viewer.d.ts +76 -0
  53. package/dist/explore/cave-orbit-viewer.d.ts.map +1 -0
  54. package/dist/explore/cave-orbit-viewer.js +123 -0
  55. package/dist/explore/cave-orbit-viewer.js.map +1 -0
  56. package/dist/explore/characters/character-configs.d.ts +38 -0
  57. package/dist/explore/characters/character-configs.d.ts.map +1 -0
  58. package/dist/explore/characters/character-configs.js +45 -0
  59. package/dist/explore/characters/character-configs.js.map +1 -0
  60. package/dist/explore/characters/character-controls.d.ts +44 -0
  61. package/dist/explore/characters/character-controls.d.ts.map +1 -0
  62. package/dist/explore/characters/character-pose.d.ts +19 -0
  63. package/dist/explore/characters/character-pose.d.ts.map +1 -0
  64. package/dist/explore/characters/character-pose.js +13 -0
  65. package/dist/explore/characters/character-pose.js.map +1 -0
  66. package/dist/explore/characters/local-character-model.d.ts +53 -0
  67. package/dist/explore/characters/local-character-model.d.ts.map +1 -0
  68. package/dist/explore/characters/local-character-model.js +36 -0
  69. package/dist/explore/characters/local-character-model.js.map +1 -0
  70. package/dist/explore/characters/mixamo-style-bone-map.d.ts +9 -0
  71. package/dist/explore/characters/mixamo-style-bone-map.d.ts.map +1 -0
  72. package/dist/explore/characters/mixamo-style-bone-map.js +28 -0
  73. package/dist/explore/characters/mixamo-style-bone-map.js.map +1 -0
  74. package/dist/explore/characters/remote-character-controller.d.ts +38 -0
  75. package/dist/explore/characters/remote-character-controller.d.ts.map +1 -0
  76. package/dist/explore/characters/remote-character-controller.js +135 -0
  77. package/dist/explore/characters/remote-character-controller.js.map +1 -0
  78. package/dist/explore/characters/remote-character.d.ts +19 -0
  79. package/dist/explore/characters/remote-character.d.ts.map +1 -0
  80. package/dist/explore/characters/remote-character.js +63 -0
  81. package/dist/explore/characters/remote-character.js.map +1 -0
  82. package/dist/explore/connected-spaces-renderer.d.ts +16 -0
  83. package/dist/explore/connected-spaces-renderer.d.ts.map +1 -0
  84. package/dist/explore/connected-spaces-renderer.js +103 -0
  85. package/dist/explore/connected-spaces-renderer.js.map +1 -0
  86. package/dist/explore/hooks/use-action-shortcuts.d.ts +16 -0
  87. package/dist/explore/hooks/use-action-shortcuts.d.ts.map +1 -0
  88. package/dist/explore/hooks/use-action-shortcuts.js +23 -0
  89. package/dist/explore/hooks/use-action-shortcuts.js.map +1 -0
  90. package/dist/explore/hooks/use-active-space-notify.d.ts +11 -0
  91. package/dist/explore/hooks/use-active-space-notify.d.ts.map +1 -0
  92. package/dist/explore/hooks/use-active-space-notify.js +17 -0
  93. package/dist/explore/hooks/use-active-space-notify.js.map +1 -0
  94. package/dist/explore/hooks/use-character-sync.d.ts +46 -0
  95. package/dist/explore/hooks/use-character-sync.d.ts.map +1 -0
  96. package/dist/explore/hooks/use-character-sync.js +72 -0
  97. package/dist/explore/hooks/use-character-sync.js.map +1 -0
  98. package/dist/explore/hooks/use-connector-preloader.d.ts +20 -0
  99. package/dist/explore/hooks/use-connector-preloader.d.ts.map +1 -0
  100. package/dist/explore/hooks/use-connector-preloader.js +78 -0
  101. package/dist/explore/hooks/use-connector-preloader.js.map +1 -0
  102. package/dist/explore/hooks/use-connector-transitions.d.ts +31 -0
  103. package/dist/explore/hooks/use-connector-transitions.d.ts.map +1 -0
  104. package/dist/explore/hooks/use-connector-transitions.js +63 -0
  105. package/dist/explore/hooks/use-connector-transitions.js.map +1 -0
  106. package/dist/explore/hooks/use-explore-actions.d.ts +17 -0
  107. package/dist/explore/hooks/use-explore-actions.d.ts.map +1 -0
  108. package/dist/explore/hooks/use-explore-actions.js +134 -0
  109. package/dist/explore/hooks/use-explore-actions.js.map +1 -0
  110. package/dist/explore/hooks/use-load-error-notify.d.ts +11 -0
  111. package/dist/explore/hooks/use-load-error-notify.d.ts.map +1 -0
  112. package/dist/explore/hooks/use-load-error-notify.js +18 -0
  113. package/dist/explore/hooks/use-load-error-notify.js.map +1 -0
  114. package/dist/explore/hooks/use-primary-space-loader.d.ts +24 -0
  115. package/dist/explore/hooks/use-primary-space-loader.d.ts.map +1 -0
  116. package/dist/explore/hooks/use-primary-space-loader.js +29 -0
  117. package/dist/explore/hooks/use-primary-space-loader.js.map +1 -0
  118. package/dist/explore/index.d.ts +37 -0
  119. package/dist/explore/index.d.ts.map +1 -0
  120. package/dist/explore/index.js +107 -0
  121. package/dist/explore/index.js.map +1 -0
  122. package/dist/explore/loaders/cave-loader.d.ts +58 -0
  123. package/dist/explore/loaders/cave-loader.d.ts.map +1 -0
  124. package/dist/explore/loaders/cave-loader.js +79 -0
  125. package/dist/explore/loaders/cave-loader.js.map +1 -0
  126. package/dist/explore/math/space-transforms.d.ts +49 -0
  127. package/dist/explore/math/space-transforms.d.ts.map +1 -0
  128. package/dist/explore/math/space-transforms.js +54 -0
  129. package/dist/explore/math/space-transforms.js.map +1 -0
  130. package/dist/explore/overlays/action-bubble.d.ts +8 -0
  131. package/dist/explore/overlays/action-bubble.d.ts.map +1 -0
  132. package/dist/explore/overlays/action-bubble.js +18 -0
  133. package/dist/explore/overlays/action-bubble.js.map +1 -0
  134. package/dist/explore/overlays/action-panel.d.ts +12 -0
  135. package/dist/explore/overlays/action-panel.d.ts.map +1 -0
  136. package/dist/explore/overlays/action-panel.js +69 -0
  137. package/dist/explore/overlays/action-panel.js.map +1 -0
  138. package/dist/explore/overlays/connector-label-beacon.d.ts +11 -0
  139. package/dist/explore/overlays/connector-label-beacon.d.ts.map +1 -0
  140. package/dist/explore/overlays/connector-label-beacon.js +38 -0
  141. package/dist/explore/overlays/connector-label-beacon.js.map +1 -0
  142. package/dist/explore/overlays/connector-label-tag-shell.d.ts +16 -0
  143. package/dist/explore/overlays/connector-label-tag-shell.d.ts.map +1 -0
  144. package/dist/explore/overlays/connector-label-tag-shell.js +18 -0
  145. package/dist/explore/overlays/connector-label-tag-shell.js.map +1 -0
  146. package/dist/explore/overlays/detail-window.d.ts +9 -0
  147. package/dist/explore/overlays/detail-window.d.ts.map +1 -0
  148. package/dist/explore/overlays/detail-window.js +92 -0
  149. package/dist/explore/overlays/detail-window.js.map +1 -0
  150. package/dist/explore/overlays/explore-helper.d.ts +44 -0
  151. package/dist/explore/overlays/explore-helper.d.ts.map +1 -0
  152. package/dist/explore/overlays/explore-helper.js +57 -0
  153. package/dist/explore/overlays/explore-helper.js.map +1 -0
  154. package/dist/explore/overlays/icons.d.ts +16 -0
  155. package/dist/explore/overlays/icons.d.ts.map +1 -0
  156. package/dist/explore/overlays/icons.js +34 -0
  157. package/dist/explore/overlays/icons.js.map +1 -0
  158. package/dist/explore/overlays/space-load-error-overlay.d.ts +27 -0
  159. package/dist/explore/overlays/space-load-error-overlay.d.ts.map +1 -0
  160. package/dist/explore/overlays/space-load-error-overlay.js +24 -0
  161. package/dist/explore/overlays/space-load-error-overlay.js.map +1 -0
  162. package/dist/explore/services/websocket.service.d.ts +180 -0
  163. package/dist/explore/services/websocket.service.d.ts.map +1 -0
  164. package/dist/explore/services/websocket.service.js +187 -0
  165. package/dist/explore/services/websocket.service.js.map +1 -0
  166. package/dist/explore/simple-character.d.ts +46 -0
  167. package/dist/explore/simple-character.d.ts.map +1 -0
  168. package/dist/explore/simple-character.js +119 -0
  169. package/dist/explore/simple-character.js.map +1 -0
  170. package/dist/explore/stores/action-prompt.store.d.ts +27 -0
  171. package/dist/explore/stores/action-prompt.store.d.ts.map +1 -0
  172. package/dist/explore/stores/action-prompt.store.js +14 -0
  173. package/dist/explore/stores/action-prompt.store.js.map +1 -0
  174. package/dist/explore/stores/cave-explore.store.d.ts +77 -0
  175. package/dist/explore/stores/cave-explore.store.d.ts.map +1 -0
  176. package/dist/explore/stores/cave-explore.store.js +66 -0
  177. package/dist/explore/stores/cave-explore.store.js.map +1 -0
  178. package/dist/explore/stores/explore-context.d.ts +35 -0
  179. package/dist/explore/stores/explore-context.d.ts.map +1 -0
  180. package/dist/explore/stores/explore-context.js +43 -0
  181. package/dist/explore/stores/explore-context.js.map +1 -0
  182. package/dist/explore/styles/cave-world-css.d.ts +10 -0
  183. package/dist/explore/styles/cave-world-css.d.ts.map +1 -0
  184. package/dist/explore/styles/cave-world-css.js +935 -0
  185. package/dist/explore/styles/cave-world-css.js.map +1 -0
  186. package/dist/explore/styles/inject.d.ts +32 -0
  187. package/dist/explore/styles/inject.d.ts.map +1 -0
  188. package/dist/explore/styles/inject.js +40 -0
  189. package/dist/explore/styles/inject.js.map +1 -0
  190. package/dist/explore/utils/schedule-idle.d.ts +12 -0
  191. package/dist/explore/utils/schedule-idle.d.ts.map +1 -0
  192. package/dist/explore/utils/schedule-idle.js +16 -0
  193. package/dist/explore/utils/schedule-idle.js.map +1 -0
  194. package/dist/explore/viewer-shared.d.ts +56 -0
  195. package/dist/explore/viewer-shared.d.ts.map +1 -0
  196. package/dist/index.d.ts +16 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/index.js +78 -0
  199. package/dist/index.js.map +1 -0
  200. package/dist/space/cave-base-layer.d.ts +42 -0
  201. package/dist/space/cave-base-layer.d.ts.map +1 -0
  202. package/dist/space/cave-base-layer.js +221 -0
  203. package/dist/space/cave-base-layer.js.map +1 -0
  204. package/dist/space/cave-custom-layer.d.ts +12 -0
  205. package/dist/space/cave-custom-layer.d.ts.map +1 -0
  206. package/dist/space/cave-custom-layer.js +17 -0
  207. package/dist/space/cave-custom-layer.js.map +1 -0
  208. package/dist/space/cave-env.d.ts +6 -0
  209. package/dist/space/cave-env.d.ts.map +1 -0
  210. package/dist/space/cave-env.js +23 -0
  211. package/dist/space/cave-env.js.map +1 -0
  212. package/dist/space/cave-space.d.ts +46 -0
  213. package/dist/space/cave-space.d.ts.map +1 -0
  214. package/dist/space/cave-space.js +37 -0
  215. package/dist/space/cave-space.js.map +1 -0
  216. package/dist/space/connector-label-host.d.ts +21 -0
  217. package/dist/space/connector-label-host.d.ts.map +1 -0
  218. package/dist/space/connector-label-host.js +65 -0
  219. package/dist/space/connector-label-host.js.map +1 -0
  220. package/dist/space/index.d.ts +18 -0
  221. package/dist/space/index.d.ts.map +1 -0
  222. package/dist/space/index.js +47 -0
  223. package/dist/space/index.js.map +1 -0
  224. package/dist/space/objects/builtin-registrar.d.ts +17 -0
  225. package/dist/space/objects/builtin-registrar.d.ts.map +1 -0
  226. package/dist/space/objects/builtin-registrar.js +19 -0
  227. package/dist/space/objects/builtin-registrar.js.map +1 -0
  228. package/dist/space/objects/custom-object.d.ts +9 -0
  229. package/dist/space/objects/custom-object.d.ts.map +1 -0
  230. package/dist/space/objects/custom-object.js +56 -0
  231. package/dist/space/objects/custom-object.js.map +1 -0
  232. package/dist/space/objects/image-object.d.ts +7 -0
  233. package/dist/space/objects/image-object.d.ts.map +1 -0
  234. package/dist/space/objects/image-object.js +14 -0
  235. package/dist/space/objects/image-object.js.map +1 -0
  236. package/dist/space/objects/model-object/fbx-object.d.ts +8 -0
  237. package/dist/space/objects/model-object/fbx-object.d.ts.map +1 -0
  238. package/dist/space/objects/model-object/fbx-object.js +26 -0
  239. package/dist/space/objects/model-object/fbx-object.js.map +1 -0
  240. package/dist/space/objects/model-object/gltf-object.d.ts +9 -0
  241. package/dist/space/objects/model-object/gltf-object.d.ts.map +1 -0
  242. package/dist/space/objects/model-object/gltf-object.js +66 -0
  243. package/dist/space/objects/model-object/gltf-object.js.map +1 -0
  244. package/dist/space/objects/model-object/index.d.ts +9 -0
  245. package/dist/space/objects/model-object/index.d.ts.map +1 -0
  246. package/dist/space/objects/model-object/index.js +12 -0
  247. package/dist/space/objects/model-object/index.js.map +1 -0
  248. package/dist/space/objects/model-object/splat-object.d.ts +9 -0
  249. package/dist/space/objects/model-object/splat-object.d.ts.map +1 -0
  250. package/dist/space/objects/model-object/splat-object.js +19 -0
  251. package/dist/space/objects/model-object/splat-object.js.map +1 -0
  252. package/dist/space/objects/object-components/windows/index.d.ts +5 -0
  253. package/dist/space/objects/object-components/windows/index.d.ts.map +1 -0
  254. package/dist/space/objects/object-components/windows/index.js +20 -0
  255. package/dist/space/objects/object-components/windows/index.js.map +1 -0
  256. package/dist/space/objects/placeholder-object.d.ts +7 -0
  257. package/dist/space/objects/placeholder-object.d.ts.map +1 -0
  258. package/dist/space/objects/placeholder-object.js +12 -0
  259. package/dist/space/objects/placeholder-object.js.map +1 -0
  260. package/dist/space/objects/poi-object.d.ts +7 -0
  261. package/dist/space/objects/poi-object.d.ts.map +1 -0
  262. package/dist/space/objects/poi-object.js +38 -0
  263. package/dist/space/objects/poi-object.js.map +1 -0
  264. package/dist/space/objects/portal-object.d.ts +7 -0
  265. package/dist/space/objects/portal-object.d.ts.map +1 -0
  266. package/dist/space/objects/portal-object.js +84 -0
  267. package/dist/space/objects/portal-object.js.map +1 -0
  268. package/dist/space/objects/video-object.d.ts +7 -0
  269. package/dist/space/objects/video-object.d.ts.map +1 -0
  270. package/dist/space/objects/video-object.js +32 -0
  271. package/dist/space/objects/video-object.js.map +1 -0
  272. package/dist/space/polygon-boundary.d.ts +10 -0
  273. package/dist/space/polygon-boundary.d.ts.map +1 -0
  274. package/dist/space/polygon-boundary.js +205 -0
  275. package/dist/space/polygon-boundary.js.map +1 -0
  276. package/dist/space/splats/spark/robust-splat-mesh.d.ts +59 -0
  277. package/dist/space/splats/spark/robust-splat-mesh.d.ts.map +1 -0
  278. package/dist/space/splats/spark/robust-splat-mesh.js +105 -0
  279. package/dist/space/splats/spark/robust-splat-mesh.js.map +1 -0
  280. package/dist/space/splats/spark/splat-mesh.d.ts +5 -0
  281. package/dist/space/splats/spark/splat-mesh.d.ts.map +1 -0
  282. package/dist/space/splats/spark/splat-mesh.js +7 -0
  283. package/dist/space/splats/spark/splat-mesh.js.map +1 -0
  284. package/dist/space/splats/spark/splat-spread-reveal.d.ts +14 -0
  285. package/dist/space/splats/spark/splat-spread-reveal.d.ts.map +1 -0
  286. package/dist/space/splats/spark/splat-spread-reveal.js +45 -0
  287. package/dist/space/splats/spark/splat-spread-reveal.js.map +1 -0
  288. package/dist/space/utils/cave-world-player.d.ts +3 -0
  289. package/dist/space/utils/cave-world-player.d.ts.map +1 -0
  290. package/dist/space/utils/cave-world-player.js +5 -0
  291. package/dist/space/utils/cave-world-player.js.map +1 -0
  292. package/dist/space/utils/configure-canvas.d.ts +20 -0
  293. package/dist/space/utils/configure-canvas.d.ts.map +1 -0
  294. package/dist/space/utils/configure-canvas.js +12 -0
  295. package/dist/space/utils/configure-canvas.js.map +1 -0
  296. package/dist/space/utils/polygon-boundary-geometry.d.ts +8 -0
  297. package/dist/space/utils/polygon-boundary-geometry.d.ts.map +1 -0
  298. package/dist/space/utils/polygon-boundary-geometry.js +20 -0
  299. package/dist/space/utils/polygon-boundary-geometry.js.map +1 -0
  300. package/dist/space/utils/polygon-boundary-segments.d.ts +50 -0
  301. package/dist/space/utils/polygon-boundary-segments.d.ts.map +1 -0
  302. package/dist/space/utils/polygon-boundary-segments.js +64 -0
  303. package/dist/space/utils/polygon-boundary-segments.js.map +1 -0
  304. package/dist/space/utils/rotation-compat.d.ts +4 -0
  305. package/dist/space/utils/rotation-compat.d.ts.map +1 -0
  306. package/dist/space/utils/rotation-compat.js +9 -0
  307. package/dist/space/utils/rotation-compat.js.map +1 -0
  308. package/dist/space/utils/select-splat-level.d.ts +52 -0
  309. package/dist/space/utils/select-splat-level.d.ts.map +1 -0
  310. package/dist/space/utils/select-splat-level.js +43 -0
  311. package/dist/space/utils/select-splat-level.js.map +1 -0
  312. package/dist/space/utils/spark-performance.d.ts +10 -0
  313. package/dist/space/utils/spark-performance.d.ts.map +1 -0
  314. package/dist/space/utils/spark-performance.js +10 -0
  315. package/dist/space/utils/spark-performance.js.map +1 -0
  316. package/dist/space/utils/splat-collider-helper.d.ts +5 -0
  317. package/dist/space/utils/splat-collider-helper.d.ts.map +1 -0
  318. package/dist/space/utils/splat-collider-helper.js +162 -0
  319. package/dist/space/utils/splat-collider-helper.js.map +1 -0
  320. package/dist/space/utils/touch-primary-device.d.ts +16 -0
  321. package/dist/space/utils/touch-primary-device.d.ts.map +1 -0
  322. package/dist/space/utils/touch-primary-device.js +52 -0
  323. package/dist/space/utils/touch-primary-device.js.map +1 -0
  324. package/dist/space/utils/use-visibility-frameloop.d.ts +20 -0
  325. package/dist/space/utils/use-visibility-frameloop.d.ts.map +1 -0
  326. package/dist/space/utils/use-visibility-frameloop.js +18 -0
  327. package/dist/space/utils/use-visibility-frameloop.js.map +1 -0
  328. package/dist/styles.css +930 -0
  329. package/dist/types/index.d.ts +2 -0
  330. package/dist/types/index.d.ts.map +1 -0
  331. package/dist/types/index.js +2 -0
  332. package/dist/types/index.js.map +1 -0
  333. package/dist/types/space-types.d.ts +404 -0
  334. package/dist/types/space-types.d.ts.map +1 -0
  335. package/doc/API.md +617 -0
  336. package/doc/ARCHITECTURE.md +264 -0
  337. package/doc/multiplayer-protocol.md +757 -0
  338. package/doc/scene-json-structure.md +567 -0
  339. package/package.json +131 -0
@@ -0,0 +1,264 @@
1
+ # Architecture
2
+
3
+ Internal design of `@cavegiant/cave-world-r3f`, for maintainers. For the rules you must not break while changing it, see [AGENTS.md](../AGENTS.md).
4
+
5
+ ## 1. Layers
6
+
7
+ ```
8
+ types ──→ core ──→ space ──→ explore
9
+ (types) (runtime) (scene) (viewers)
10
+ ```
11
+
12
+ Dependencies run strictly left to right, enforced by `no-restricted-imports` in `eslint.config.js`. Anything shared by `space` and `explore` lives in `space`.
13
+
14
+ ```
15
+ @cave-world/
16
+ ├── types/ # Pure TypeScript, zero runtime
17
+ │ └── space-types.ts # Complete CaveSpace JSON schema
18
+
19
+ ├── core/ # Runtime foundation
20
+ │ ├── events/event-bus.ts # CaveEventBus — typed pub/sub
21
+ │ ├── registry/ # ObjectRegistry, ComponentRegistry
22
+ │ ├── context/ # CaveWorldProvider + per-instance world store
23
+ │ ├── hooks/use-cave-event.ts # useCaveEvent()
24
+ │ ├── connector-label/ # Label resolution + transfer readiness (pure)
25
+ │ ├── i18n/messages.ts # All user-visible copy
26
+ │ ├── logging/logger.ts # Leveled logger (no bare console anywhere)
27
+ │ └── errors.ts # CaveWorldError + stable codes
28
+
29
+ ├── space/ # Scene rendering (R3F)
30
+ │ ├── cave-space.tsx # Composition root: env + base + custom layers
31
+ │ ├── cave-env.tsx # Lighting
32
+ │ ├── cave-base-layer.tsx # Splat + colliders + boundaries + connectors
33
+ │ ├── cave-custom-layer.tsx # Iterates custom objects
34
+ │ ├── connector-label-host.tsx # Label visibility driver
35
+ │ ├── polygon-boundary.tsx # Fence colliders + touch-only visuals
36
+ │ ├── objects/ # Renderers + builtin-registrar + behaviors
37
+ │ ├── splats/spark/ # SplatMesh, RobustSplatMesh, reveal
38
+ │ └── utils/ # Colliders, LOD selection, device + canvas helpers
39
+
40
+ └── explore/ # Viewers and interaction
41
+ ├── cave-explore.tsx # Walkable tour — composition only
42
+ ├── cave-orbit-viewer.tsx # Orbit overview viewer
43
+ ├── viewer-shared.ts # CaveViewerBaseProps
44
+ ├── connected-spaces-renderer.tsx # Multi-space scene graph
45
+ ├── simple-character.tsx # Local player + viverse camera bridge
46
+ ├── math/space-transforms.ts # Seamless / portal / spawn math (pure, tested)
47
+ ├── hooks/ # Loading, preload, transitions, actions, shortcuts
48
+ ├── overlays/ # Built-in UI, each replaceable
49
+ ├── characters/ # Avatar config, pose, multiplayer characters
50
+ ├── services/ # WebSocket protocol
51
+ ├── loaders/ # Scene JSON loaders
52
+ ├── stores/ # Per-instance exploration + action-prompt stores
53
+ └── styles/ # Stylesheet source + runtime injector
54
+ ```
55
+
56
+ ## 2. Design patterns
57
+
58
+ | Pattern | Implementation | Why |
59
+ | ------------------------- | ----------------------------------------------- | ---------------------------------------------------- |
60
+ | Registry | `ObjectRegistry`, `ComponentRegistry` | Add object/behavior types without touching core code |
61
+ | Event bus | `CaveEventBus` | Decouples overlays from the scene graph entirely |
62
+ | Context DI | `CaveWorldProvider`, `CaveExploreProvider` | Per-instance services, no module singletons |
63
+ | Self-contained components | Each behavior owns its sensors and events | Unity-style component model |
64
+ | JSON-driven scene graph | `CaveSpace` → layers → objects | Content is data, not code |
65
+ | Per-object Suspense | One boundary per object | One slow asset can't blank the scene |
66
+ | Render props | `renderDetailWindow`, `renderConnectorLabel`, … | UI replaceable without forking |
67
+
68
+ ### Everything is per-instance
69
+
70
+ `createCaveWorldStore`, `createCaveExploreStore`, and `createActionPromptStore` are factories; the providers own one each. Module-level stores would make two viewers on one page share active-space and prompt state. The same rule applies to caches — the boundary fence keeps its local-player lookup in a ref rather than a module variable.
71
+
72
+ ## 3. Data flow
73
+
74
+ ### 3.1 Scene loading
75
+
76
+ ```
77
+ CaveExplore(spaceId)
78
+
79
+
80
+ usePrimarySpaceLoader → caveSpaceLoader(spaceId) ──fetch──→ CaveSpace JSON
81
+
82
+ ├──→ CaveWorldStore.setSpaceConfig(config)
83
+ └──→ CaveExploreStore.setConnectedSpace(id, config) + setActiveSpaceId(id)
84
+
85
+
86
+ ConnectedSpacesRenderer renders every loaded space
87
+ └── CaveSpace (per space, memoised, own Suspense boundary)
88
+ ├── CaveEnv ← baseLayer.lighting
89
+ ├── CaveBaseLayer ← splat + colliders + boundaries + connectors
90
+ └── CaveCustomLayer ← customLayers[] → CustomObject × N
91
+ ├── ObjectRegistry.get(type)
92
+ └── ComponentRegistry.get(name) × N
93
+ ```
94
+
95
+ The primary scene is registered in `connectedSpaces` like any neighbor. Treating it uniformly is what makes seamless back-traversal (A → B → A) work through the same code path as forward travel.
96
+
97
+ Once the primary reports ready (`space:initialized`):
98
+
99
+ 1. If `baseLayer.spawnPoint` exists, the character is placed there once (guarded so transitions don't re-run it).
100
+ 2. `useConnectorPreloader` fetches every connected scene. Deliberately deferred until the primary's splat and colliders are done, so neighbors don't compete for bandwidth on the same origin, and each neighbor's mount is scheduled in idle time so the burst doesn't stutter input.
101
+
102
+ ### 3.2 Active vs. preloaded spaces
103
+
104
+ Each `CaveSpace` receives `active`; only the current scene gets `true`.
105
+
106
+ - `CaveBaseLayer` mounts connector sensors and labels only when active — otherwise a hidden neighbor's connector boxes would fire as the player walks through the same world coordinates in the scene they're actually in, and labels would spoil destinations early.
107
+ - `CaveCustomLayer` renders nothing when inactive, so hidden scenes never pay network/decode/build cost for their objects.
108
+ - The splat **stays rendered** (hidden by the parent `<group visible={false}>`) to keep the mesh resident in GPU memory. That residency is what makes a transition instant.
109
+ - Splat fallback colliders are generated as soon as the splat loads, not when physics turns on, so first entry into a preloaded scene doesn't pay ~1s of generation as visible stutter.
110
+
111
+ ### 3.3 Character orientation
112
+
113
+ Writing the character's facing **must** go through `behavior.rotationYaw`, never the Group's quaternion.
114
+
115
+ viverse derives both input direction (`updateSimpleCharacterVelocity`) and model yaw (`updateSimpleCharacterRotation`) from `state.camera`, and the camera behavior overwrites `camera.rotation` from its internal `rotationYaw` every frame. The Group's own quaternion is never read in that pipeline. Since `<SimpleCharacter>` discards the ref returned by its internal `useCharacterCameraBehavior`, `ExploreSimpleCharacter` runs its own behavior, exposes it as `characterRef.current.viverseCameraBehavior`, and passes `cameraBehavior={false}` so viverse's copy short-circuits.
116
+
117
+ There is also a 180° convention gap: scene data treats `yaw = 0` as facing **+Z** (object convention), while `rotationYaw` is a camera Y-rotation and Three.js cameras look down **−Z**. `applyCharacterPose` adds `Math.PI` to reconcile them.
118
+
119
+ ### 3.4 Action prompts
120
+
121
+ All in-scene interaction hints flow through one store:
122
+
123
+ ```
124
+ events (object:enter/leave, connector:enter/leave)
125
+
126
+
127
+ useExploreActions — decides the action list from context
128
+
129
+
130
+ ActionPromptStore { actions, anchor, style }
131
+
132
+ ├─ anchor set → ActionBubble (3D Html, single hint)
133
+ └─ anchor null → ActionPanel (screen-space list)
134
+
135
+
136
+ useActionShortcuts — one global keydown listener, reads store.getState()
137
+ ```
138
+
139
+ `useActionShortcuts` does **not** subscribe to `actions`. Subscribing would re-render the component hosting the character on every prompt show/hide, which trips a bug in viverse's `useSimpleCharacterActionBindings` (dep-less effect plus an `instanceof` guard that never passes): it recreates the keyboard bindings with an empty `pressedKeys` set, so the player stops mid-walk whenever a connector prompt appears. The `memo` on `ExploreSimpleCharacter` and the memoised `physics` object close the remaining paths.
140
+
141
+ ### 3.5 Cross-scene travel
142
+
143
+ ```
144
+ JSON: baseLayer.connectors[] = [{ id, transform, connectorInfo: { to: [...] } }]
145
+
146
+
147
+ CaveBaseLayer mounts a BvhPhysicsSensor per connector (active scene only)
148
+
149
+ ├─ plus ConnectorLabelHost for portal connectors with labels enabled
150
+ │ └─ distance fade, hides on entry, greys out while target preloads
151
+
152
+
153
+ player touches the box → emit('connector:enter')
154
+
155
+
156
+ useConnectorTransitions
157
+ ├─ seamless target → switch activeSpaceId immediately
158
+ └─ portal target → setActiveConnector → useExploreActions shows the picker
159
+ └─ selection → emit('connector:portal:select')
160
+ └─ teleport + switch activeSpaceId
161
+ ```
162
+
163
+ **Seamless alignment.** `ConnectorTarget.transform` is a column-major 4×4 mapping **source-local → target-local** (before `baseLayer.transform`):
164
+
165
+ ```
166
+ p_target_local = M_raw × p_source_local
167
+ G_target = G_source × T_source × inverse(M_raw) × inverse(T_target)
168
+ ```
169
+
170
+ Implemented in `explore/math/space-transforms.ts` and covered by tests, including an A → B → A round-trip that must restore A's original placement.
171
+
172
+ **Ghost-enter suppression.** After a seamless A → B transition the character occupies the same world position, which by construction is inside B's matching back-link connector. Since sensors mount only for the active scene, B's sensor is added while the player is already inside it and fires `onIntersectedChanged(true)` on the next tick — which would ping-pong them straight back to A. `useConnectorTransitions` writes a 200 ms timestamp to `connectorEnterSuppressUntil`; both it and `useExploreActions` check the window but neither consumes it, so one handler can't leave the other seeing clean state (which would pop a spurious panel for connectors with mixed seamless + portal targets). Portal transitions don't need this: the arrival point is 0.5 m outside the target box.
173
+
174
+ ### 3.6 Load-failure hardening
175
+
176
+ spark's `SplatMesh` has `onLoad` but no `onError`, and a failed download leaves `mesh.initialized` rejected with nothing catching it. Since readiness gates on `onLoad`, that alone would hang the viewer forever.
177
+
178
+ ```
179
+ RobustSplatMesh
180
+ ├─ attaches to mesh.initialized → catches spark's unhandled rejection
181
+ ├─ watchdog timeout (~30s) → catches silently stalled downloads
182
+ ├─ retries with backoff (×3) → remounts the mesh via a fresh args ref
183
+ ├─ emits space:error each time → telemetry, with willRetry
184
+ └─ exhausted → onError → CaveWorldState.spaceLoadError
185
+
186
+
187
+ SpaceLoadErrorOverlay (portaled to document.body)
188
+ ```
189
+
190
+ The overlay is portaled at a very high z-index specifically to escape host stacking contexts: host loading UI is typically a `fixed` sibling outside the viewer subtree and would otherwise cover the prompt, leaving the user on a spinner that never resolves.
191
+
192
+ Only the **active** scene surfaces a terminal error. A hidden neighbor failing in the background must not pop a full-screen prompt over a working scene; it still retries, and a manual retry re-downloads it too.
193
+
194
+ ### 3.7 Entry reveal
195
+
196
+ The primary scene's splat plays a one-shot reveal, driven inside `RobustSplatMesh` so it adds no state to `CaveBaseLayer`:
197
+
198
+ ```
199
+ onLoad → applySpreadReveal() — attach a spark dyno objectModifier
200
+ onFrame → advance shader time → mesh.updateVersion()
201
+ ~6s → clearSpreadReveal() + detach onFrame
202
+ ```
203
+
204
+ Visually: the whole scene appears immediately as a point cloud (original colors, uniform tiny scale), then a radial wave from the center snaps each splat to full scale. It runs in parallel with `space:initialized` and never delays readiness. Only `isPrimary` scenes honour `revealOnEnter`, so preloaded neighbors and transitions never replay it.
205
+
206
+ ## 4. State ownership
207
+
208
+ | Store | Location | Holds |
209
+ | ------------------- | ----------------- | ---------------------------------------------------------------------------- |
210
+ | `CaveWorldState` | `core/context/` | Scene config, active object, detail visibility, load error, retry token |
211
+ | `CaveWorldServices` | `core/context/` | Event bus, registries, store, messages, label renderer |
212
+ | `CaveExploreState` | `explore/stores/` | View mode, active space, connected spaces, transforms, connectors, room info |
213
+ | `ActionPromptState` | `explore/stores/` | Current actions, anchor, style |
214
+
215
+ `CaveWorldState` is shared by `space` and `explore` through context. The exploration stores are `explore`-only. On a scene change, `CaveWorldState.spaceConfig` is updated to the new active scene so existing overlays keep working.
216
+
217
+ ## 5. Tech stack
218
+
219
+ | Concern | Choice |
220
+ | ----------- | ------------------------------------------------------------- |
221
+ | Rendering | React Three Fiber + Three.js |
222
+ | Physics | `@react-three/viverse` (BVH collision) |
223
+ | Splats | `@sparkjsdev/spark` 0.1.x |
224
+ | State | Zustand + `subscribeWithSelector` |
225
+ | 3D helpers | `@react-three/drei` |
226
+ | Multiplayer | In-house WebSocket protocol |
227
+ | UI | Hand-written CSS, `.cw-*` namespace, `--cw-*` theme variables |
228
+
229
+ ### Spark version
230
+
231
+ **Pinned to 0.1.x deliberately.** Spark 2.x regenerates its splat accumulator every frame the view changes — i.e. only while walking. viverse integrates the character as `position += velocity * delta` with no smoothing, so that frame-time variance surfaces as the character stepping fore/aft. Idle and static environments are unaffected, which makes it read like a character bug. Disabling 2.x LOD and foveation did not help. 0.1.x also auto-injects its renderer, so no `SparkRenderer` component is needed, and `SplatMesh` takes plain `{ url, onLoad }` options.
232
+
233
+ ## 6. Performance measures
234
+
235
+ - `antialias: false` on the canvas ([Spark guidance](https://sparkjs.dev/docs/performance/)).
236
+ - `getSparkCanvasDpr()` caps `devicePixelRatio`; splat blending cost scales with pixel count.
237
+ - `useVisibilityFrameloop()` switches R3F `frameloop` to `never` while the page is hidden — GPU work drops to zero, which is the main heat driver on mobile. R3F's `setFrameloop` restarts the clock, so the first visible frame has no delta spike.
238
+ - Device-aware splat LOD via `selectSplatLevel`, evaluated once per scene mount so a resize can't trigger a re-download.
239
+ - One Suspense boundary per suspending subtree — see [6.1](#61-suspense-topology).
240
+ - Both viewers call `preloadSpaceAssets()` as soon as the scene JSON lands, so collider OBJs download alongside the splat instead of after the base layer mounts.
241
+ - `ConnectorLabelHost` computes visibility every frame but only publishes to React across a threshold.
242
+ - Neighbor preloads are serialized and idle-scheduled.
243
+ - `startTransition` wraps collider generation and scene switches.
244
+ - Remote characters are capped (50 by default).
245
+
246
+ ### 6.1 Suspense topology
247
+
248
+ When a component suspends, React throws away everything already rendered inside the boundary it unwinds to and rebuilds that subtree once the promise resolves. For a splat scene that is not merely wasted work: spark's `SplatMesh` starts its download in the constructor and keeps no URL cache, so a rebuilt mesh downloads the asset again — while the discarded fetch keeps running, because spark offers no way to abort it. Entering a scene could issue the same multi-megabyte request several times.
249
+
250
+ Every leaf that suspends therefore owns a boundary:
251
+
252
+ | Suspending leaf | Suspends via | Boundary lives in |
253
+ | --------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------- |
254
+ | A space's whole subtree | anything below it | `SpaceSubtree` (`ConnectedSpacesRenderer`), `CaveOrbitViewer` |
255
+ | Model collider | `useLoader(OBJLoader, …)` | `CaveBaseLayer`, one per collider |
256
+ | Scene object (GLB / FBX / texture / video / `Text`) | drei loader hooks | `CustomObject` |
257
+ | Local character | viverse `suspend()` — once for the model, once per animation clip | `CaveExplore` |
258
+ | Remote player | `useGLTF` × 7 | `RemoteCharacterController`, one per player |
259
+ | Host `children` | anything | `CaveExplore`, `CaveOrbitViewer` |
260
+
261
+ Two properties of that layout are load-bearing:
262
+
263
+ - **Collider boundaries sit outside `BvhPhysicsBody`.** viverse snapshots the body's children into the BVH world in a mount effect and documents that its content must not suspend; a mesh that appears later never becomes a collider.
264
+ - **Sibling assets load in parallel.** A shared boundary serializes them: the first suspension aborts the render before its siblings start their own request, so each retry only gets one step further.