@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
package/doc/API.md ADDED
@@ -0,0 +1,617 @@
1
+ # API Reference
2
+
3
+ Integration and extension guide for `@cavegiant/cave-world-r3f`.
4
+
5
+ ## API stability
6
+
7
+ | Tier | Surface | Semver expectation |
8
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
9
+ | **Stable** | `CaveExplore`, `CaveOrbitViewer`, `CaveViewerBaseProps`, loaders (`createHttpCaveSpaceLoader`, `DefaultCaveSpaceLoader`), `CaveWorldProvider`, registries, `useCaveEvent` / `CaveEventBus`, `messages` / logger APIs, scene JSON types under `/types` | Breaking changes only in major releases |
10
+ | **Advanced** | Composition hooks (`useConnectorTransitions`, `usePrimarySpaceLoader`, …), `ConnectedSpacesRenderer`, `ExploreSimpleCharacter`, `RemoteCharacterController`, `WebSocketService`, `useCharacterSync`, store factories | May change in minor releases; prefer Stable APIs when possible |
11
+
12
+ Root package re-exports `CaveSpace` as **`CaveSpaceRenderer`** to avoid clashing with the `CaveSpace` config type. Prefer importing the component from `@cavegiant/cave-world-r3f/space` as `CaveSpace`.
13
+
14
+ - [Viewers](#viewers)
15
+ - [CaveExplore](#caveexplore)
16
+ - [CaveOrbitViewer](#caveorbitviewer)
17
+ - [Tracking the player's scene](#tracking-the-players-scene)
18
+ - [Events](#events)
19
+ - [Extending the scene](#extending-the-scene)
20
+ - [Replacing the UI](#replacing-the-ui)
21
+ - [The local character](#the-local-character)
22
+ - [Multiplayer](#multiplayer)
23
+ - [Load failures](#load-failures)
24
+ - [Styling and copy](#styling-and-copy)
25
+ - [Core hooks](#core-hooks)
26
+ - [Composing your own canvas](#composing-your-own-canvas)
27
+ - [Performance guidance](#performance-guidance)
28
+
29
+ ---
30
+
31
+ ## Viewers
32
+
33
+ Both viewers share a base set of props (`CaveViewerBaseProps`):
34
+
35
+ | Prop | Type | Default | Description |
36
+ | -------------------- | ------------------------------------ | ----------------------------------------------- | ------------------------------------------------- |
37
+ | `spaceId` | `string` | — | Scene id passed to the loader |
38
+ | `caveSpaceLoader` | `(id: string) => Promise<CaveSpace>` | `DefaultCaveSpaceLoader` (CaveGiant public API) | Scene JSON loader — pass your own in production |
39
+ | `onSpaceInitialized` | see per-viewer | — | Fires when the base layer is ready |
40
+ | `eventBus` | `CaveEventBus` | created internally | Reuse a bus to observe events from outside React |
41
+ | `objectRegistry` | `ObjectRegistry` | created internally | Custom object renderers |
42
+ | `componentRegistry` | `ComponentRegistry` | created internally | Custom behavior components |
43
+ | `messages` | `PartialCaveWorldMessages` | `zhCN` | Copy overrides, deep-merged |
44
+ | `injectStyles` | `boolean` | `true` | Inject the default stylesheet at runtime |
45
+ | `revealOnEnter` | `boolean` | `true` | Play the spread reveal on first splat load |
46
+ | `renderDetailWindow` | `() => ReactNode` | built-in | Replace the detail panel |
47
+ | `onLoadError` | `(error: SpaceLoadError) => void` | — | Terminal load failure (fires once) |
48
+ | `onLoadErrorExit` | `() => void` | — | Back-button handler; omitting it hides the button |
49
+ | `renderLoadError` | `LoadErrorRenderer` | built-in | Replace the load-error prompt |
50
+ | `children` | `ReactNode` | — | Extra R3F content inside the scene graph |
51
+
52
+ Importable type: `CaveViewerBaseProps`, `ActiveSpaceChangeHandler` from `@cavegiant/cave-world-r3f/explore`.
53
+
54
+ ### CaveExplore
55
+
56
+ Walkable first/third-person tour with physics, character controls, and cross-scene travel.
57
+
58
+ ```tsx
59
+ import { CaveExplore } from '@cavegiant/cave-world-r3f/explore'
60
+
61
+ ;<div style={{ width: '100vw', height: '100vh' }}>
62
+ <CaveExplore spaceId={id} />
63
+ </div>
64
+ ```
65
+
66
+ Uses `DefaultCaveSpaceLoader` (CaveGiant public API) when `caveSpaceLoader` is omitted. For a private backend:
67
+
68
+ ```tsx
69
+ import { CaveExplore, createHttpCaveSpaceLoader } from '@cavegiant/cave-world-r3f/explore'
70
+
71
+ const loader = createHttpCaveSpaceLoader({
72
+ buildUrl: (id) => `/api/spaces/${id}`
73
+ })
74
+
75
+ <CaveExplore spaceId={id} caveSpaceLoader={loader} />
76
+ ```
77
+
78
+ Additional props:
79
+
80
+ | Prop | Type | Default | Description |
81
+ | ---------------------- | ------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------ |
82
+ | `onActiveSpaceChange` | `(spaceId, space?) => void` | — | Fires for the initial scene and after every transition; see [below](#tracking-the-players-scene) |
83
+ | `multiplayer` | `false \| { url, avatarId?, avatars?, maxRemoteCharacters? }` | `false` | Character sync — **`url` is required** when enabled |
84
+ | `localCharacterModel` | `LocalCharacterModelProp` | viverse mannequin | Third-person avatar; see [below](#the-local-character) |
85
+ | `characterControls` | `CharacterControlsConfig` | viverse defaults | Movement speeds and input bindings |
86
+ | `connectorLabel` | `'auto' \| 'off'` | `'auto'` | Global default for wayfinding labels |
87
+ | `renderConnectorLabel` | `ConnectorLabelRenderer` | `ConnectorLabelBeacon` | Replace label visuals |
88
+ | `exploreHelper` | `ExploreHelperConfig \| false` | enabled | First-run controls hint |
89
+ | `renderExploreHelper` | `ExploreHelperRenderer` | built-in | Replace the hint UI |
90
+ | `onPortalNavigate` | `(portal, object) => boolean \| void` | — | Handle `portal` object activation |
91
+
92
+ #### Imperative handle
93
+
94
+ ```tsx
95
+ import { useRef } from 'react'
96
+ import { CaveExplore, type CaveExploreHandle } from '@cavegiant/cave-world-r3f/explore'
97
+
98
+ const ref = useRef<CaveExploreHandle>(null)
99
+
100
+ // Base-layer-local coordinates in the active scene; yaw 0 faces +Z.
101
+ ref.current?.teleport({ position: [12, 0, 4], yaw: Math.PI })
102
+ ref.current?.getActiveSpaceId()
103
+ ref.current?.getCharacter()
104
+
105
+ <CaveExplore ref={ref} spaceId={id} />
106
+ ```
107
+
108
+ #### Portal navigation
109
+
110
+ `portal` objects can point anywhere, so the SDK only acts on absolute `http(s)` URLs (opened in a new tab). Anything else — in-app routes, deep links — is yours to handle, because the SDK cannot know your router:
111
+
112
+ ```tsx
113
+ <CaveExplore
114
+ spaceId={id}
115
+ onPortalNavigate={(portal) => {
116
+ if (portal.internalScene?.id) {
117
+ navigate(`/scene/${portal.internalScene.id}`)
118
+ return true // handled
119
+ }
120
+ }}
121
+ />
122
+ ```
123
+
124
+ ### CaveOrbitViewer
125
+
126
+ Orbit-camera overview of one scene: no avatar, no locomotion, no cross-scene travel. Use it for overview pages and scene pickers.
127
+
128
+ ```tsx
129
+ import { Html } from '@react-three/drei'
130
+ import { CaveOrbitViewer } from '@cavegiant/cave-world-r3f/explore'
131
+
132
+ ;<CaveOrbitViewer
133
+ spaceId={id}
134
+ camera={{ position: [-0.28, 2.65, -2.12], target: [0, 0, 0] }}
135
+ orbit={{ maxPolarAngle: Math.PI / 2.3, minDistance: 2, maxDistance: 8 }}
136
+ onSpaceInitialized={({ camera, controls, space }) => {
137
+ // Per-scene framing the SDK can't know about
138
+ }}
139
+ >
140
+ {tags.map((tag) => (
141
+ <Html key={tag.id} position={tag.position} center>
142
+ <MyTag label={tag.label} />
143
+ </Html>
144
+ ))}
145
+ </CaveOrbitViewer>
146
+ ```
147
+
148
+ Additional props:
149
+
150
+ | Prop | Type | Default | Description |
151
+ | -------------------- | ------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------ |
152
+ | `transformSpace` | `(space) => CaveSpace` | — | Adjust the loaded scene before render (e.g. level an off-axis capture). Must return a new object |
153
+ | `camera` | `{ position, target?, fov? }` | `position: [0,10,10]` | Initial framing, applied when the base layer is ready |
154
+ | `orbit` | `OrbitControlsConfig` | damping off, `rotateSpeed: 0.8` | Orbit limits |
155
+ | `connectorLabels` | `boolean` | `false` | Show wayfinding labels |
156
+ | `onSpaceInitialized` | `(api: OrbitViewerReadyApi) => void` | — | Receives `{ camera, controls, space }` |
157
+
158
+ ---
159
+
160
+ ## Tracking the player's scene
161
+
162
+ Players walk between scenes, so the route id only reflects where they _entered_. Anything scene-scoped in your UI should follow `onActiveSpaceChange`, which fires once for the initial scene and again after every transition — and hands you that scene's loaded JSON, so names come from the data rather than a hardcoded map.
163
+
164
+ ```tsx
165
+ function ScenePage() {
166
+ const { id } = useParams()
167
+ const [currentSceneId, setCurrentSceneId] = useState(id)
168
+ const [sceneName, setSceneName] = useState('')
169
+
170
+ useEffect(() => {
171
+ setCurrentSceneId(id)
172
+ setSceneName('')
173
+ }, [id])
174
+
175
+ return (
176
+ <CaveExplore
177
+ spaceId={id}
178
+ onActiveSpaceChange={(sid, space) => {
179
+ setCurrentSceneId(sid)
180
+ setSceneName(space?.metadata?.name ?? '')
181
+ }}
182
+ />
183
+ )
184
+ }
185
+ ```
186
+
187
+ The `connector:transition` event covers the same ground but not the initial scene, so it is better suited to analytics than UI sync.
188
+
189
+ ---
190
+
191
+ ## Events
192
+
193
+ ```tsx
194
+ import { CaveEventBus } from '@cavegiant/cave-world-r3f/core'
195
+
196
+ // Outside React
197
+ const eventBus = new CaveEventBus()
198
+ const unsubscribe = eventBus.on('object:click', ({ object }) => console.log(object.name))
199
+
200
+ <CaveExplore spaceId={id} eventBus={eventBus} />
201
+ ```
202
+
203
+ ```tsx
204
+ import { useCaveEvent } from '@cavegiant/cave-world-r3f/core'
205
+
206
+ // Inside the provider tree — auto-unsubscribes, always sees latest state
207
+ function Listener() {
208
+ useCaveEvent('object:enter', ({ object }) => console.log(object.name))
209
+ return null
210
+ }
211
+ ```
212
+
213
+ | Event | Payload | Fires when |
214
+ | ------------------------- | --------------------------------------- | ------------------------------------------------- |
215
+ | `object:click` | `{ object, nativeEvent? }` | An object is clicked |
216
+ | `object:enter` | `{ object }` | Player enters an object's proximity sensor |
217
+ | `object:leave` | `{ object }` | Player leaves it |
218
+ | `object:interact` | `{ object, action }` | Player acts on an object (e.g. presses F) |
219
+ | `object:active` | `{ object }` | An object becomes the active interaction target |
220
+ | `detail:open` | `{ object }` | Detail window opened |
221
+ | `detail:close` | `{}` | Detail window closed |
222
+ | `connector:enter` | `{ connector }` | Player enters a connector volume |
223
+ | `connector:leave` | `{ connector }` | Player leaves it |
224
+ | `connector:transition` | `{ from, to, type }` | A cross-scene transition completed |
225
+ | `connector:portal:select` | `{ connector, target }` | A portal destination was chosen |
226
+ | `connector:label:click` | `{ connector }` | A wayfinding label was clicked |
227
+ | `space:loading` | `{ progress }` | Scene load progress, 0–1 |
228
+ | `space:initialized` | `{}` | Active scene's base layer is ready |
229
+ | `space:transfer:ready` | `{ spaceId }` | A connected scene finished preloading |
230
+ | `space:error` | `{ error, willRetry?, spaceId?, url? }` | A scene JSON or splat load failed (every attempt) |
231
+ | `boundary:contact` | `{ boundaryId, proximity }` | Player is blocked by a boundary fence |
232
+ | `boundary:release` | `{ boundaryId }` | Player moved away from the fence |
233
+ | `custom:*` | `unknown` | Your own events; no type registration needed |
234
+
235
+ Guarantees: handlers are snapshotted before dispatch, so subscribing or unsubscribing inside a handler cannot skip or double-fire a peer; and a throwing handler is logged and isolated rather than aborting the rest.
236
+
237
+ ---
238
+
239
+ ## Extending the scene
240
+
241
+ ### Custom object types
242
+
243
+ Each object in the scene JSON has a `type` that selects its renderer.
244
+
245
+ ```tsx
246
+ import { ObjectRegistry, type ObjectRendererProps } from '@cavegiant/cave-world-r3f/core'
247
+
248
+ function TreasureChest({ object, onClick }: ObjectRendererProps) {
249
+ return (
250
+ <mesh onClick={onClick} userData={{ customObject: object }}>
251
+ <boxGeometry args={[1, 0.6, 0.8]} />
252
+ <meshStandardMaterial color="gold" />
253
+ </mesh>
254
+ )
255
+ }
256
+
257
+ const objectRegistry = new ObjectRegistry()
258
+ objectRegistry.register('treasureChest', TreasureChest)
259
+
260
+ <CaveExplore spaceId={id} objectRegistry={objectRegistry} />
261
+ ```
262
+
263
+ Registering a built-in type name replaces that built-in: the SDK installs its own with `registerDefault`, which never overwrites an existing registration.
264
+
265
+ Built-in renderers: `model`, `splat`, `image`, `video`, `poi`, `portal`, `placeholder`.
266
+
267
+ Official schema types with **no** built-in renderer — they need product-specific UI, so register your own: `infoPoint`, `panoramaVideo`, `digitalHuman2D`, `text`, `light`, `audio`, `npc`. Objects of an unregistered type (or an unregistered behavior component) render nothing / are skipped. There is no throw. Call `setCaveWorldLogLevel('debug')` to log each skip once per type/name.
268
+
269
+ ### Custom behavior components
270
+
271
+ Components attach to any object through the JSON `components` field. They are self-contained: each owns its sensors, state, and events.
272
+
273
+ ```tsx
274
+ import { ComponentRegistry, type ObjectComponentProps } from '@cavegiant/cave-world-r3f/core'
275
+
276
+ function GlowEffect({ object }: ObjectComponentProps) {
277
+ return <pointLight color="cyan" intensity={2} distance={5} />
278
+ }
279
+
280
+ const componentRegistry = new ComponentRegistry()
281
+ componentRegistry.register('Glow', GlowEffect, { requiresInteraction: false })
282
+ ```
283
+
284
+ ```json
285
+ { "id": "obj1", "type": "model", "components": { "Glow": { "name": "Glow" } } }
286
+ ```
287
+
288
+ `requiresInteraction: true` makes the object's wrapper bind an `onClick` that emits `object:click`.
289
+
290
+ Built-in component: `Window` (proximity sensor + detail-window trigger).
291
+
292
+ ---
293
+
294
+ ## Replacing the UI
295
+
296
+ Every built-in overlay has an escape hatch, and they all communicate through events and the stores — so a replacement gets the same inputs the original had.
297
+
298
+ | Prop | Replaces |
299
+ | ---------------------- | ----------------------- |
300
+ | `renderDetailWindow` | Object detail panel |
301
+ | `renderConnectorLabel` | 3D wayfinding labels |
302
+ | `renderExploreHelper` | First-run controls hint |
303
+ | `renderLoadError` | Load-failure prompt |
304
+
305
+ ```tsx
306
+ import {
307
+ ConnectorLabelTagShell,
308
+ type ConnectorLabelRendererProps
309
+ } from '@cavegiant/cave-world-r3f/explore'
310
+
311
+ function MyLabel({
312
+ label,
313
+ opacity,
314
+ displayMode,
315
+ isPlayerInside,
316
+ transferStatus,
317
+ onClick
318
+ }: ConnectorLabelRendererProps) {
319
+ if (isPlayerInside || displayMode === 'hidden') return null
320
+ // Reuse the shell for camera-facing behavior and pointer handling.
321
+ return (
322
+ <ConnectorLabelTagShell opacity={opacity}>
323
+ <MySignpost text={label.text} pending={transferStatus === 'loading'} onClick={onClick} />
324
+ </ConnectorLabelTagShell>
325
+ )
326
+ }
327
+
328
+ ;<CaveExplore spaceId={id} connectorLabel="auto" renderConnectorLabel={MyLabel} />
329
+ ```
330
+
331
+ The SDK keeps owning visibility gating (distance fade, hide-while-inside, portal-only) so your renderer only supplies visuals.
332
+
333
+ A full host-style recipe — Mixamo avatar plus the Ordos portal's real connector
334
+ tag, Lottie loading carousel, and first-run guide — is in
335
+ [`examples/07-product-skin.tsx`](../examples/07-product-skin.tsx).
336
+
337
+ A custom detail window reads the same state the built-in one does:
338
+
339
+ ```tsx
340
+ import { useCaveEvent, useCaveEventBus, useCaveWorldStore } from '@cavegiant/cave-world-r3f/core'
341
+
342
+ function MyDetailWindow() {
343
+ const activeObject = useCaveWorldStore((s) => s.activeObject)
344
+ const detailVisible = useCaveWorldStore((s) => s.detailVisible)
345
+ const setDetailVisible = useCaveWorldStore((s) => s.setDetailVisible)
346
+ const eventBus = useCaveEventBus()
347
+
348
+ useCaveEvent('detail:open', () => setDetailVisible(true))
349
+ if (!detailVisible || !activeObject) return null
350
+
351
+ return (
352
+ <div>
353
+ <h2>{activeObject.name}</h2>
354
+ <button
355
+ onClick={() => {
356
+ setDetailVisible(false)
357
+ eventBus.emit('detail:close', {})
358
+ }}
359
+ >
360
+ Close
361
+ </button>
362
+ </div>
363
+ )
364
+ }
365
+ ```
366
+
367
+ ---
368
+
369
+ ## The local character
370
+
371
+ In third person the local player is rendered by viverse's `SimpleCharacter`. First person always hides the body.
372
+
373
+ | `localCharacterModel` | Result |
374
+ | --------------------------- | ------------------------------------- |
375
+ | omitted / `true` | viverse built-in mannequin |
376
+ | `false` | no visible mesh, even in third person |
377
+ | `LocalCharacterModelConfig` | your GLB/VRM from `url` |
378
+
379
+ ```tsx
380
+ import {
381
+ createLocalCharacterModelConfig,
382
+ MIXAMO_STYLE_BONE_MAP
383
+ } from '@cavegiant/cave-world-r3f/explore'
384
+
385
+ // Module scope: the reference must be stable (see below).
386
+ const AVATAR = createLocalCharacterModelConfig('./human.glb', {
387
+ boneMap: MIXAMO_STYLE_BONE_MAP,
388
+ scale: 0.8
389
+ })
390
+
391
+ <CaveExplore spaceId={id} localCharacterModel={AVATAR} />
392
+ ```
393
+
394
+ Config fields: `type?` (`'gltf' | 'vrm'`), `url`, `boneMap?`, `boneRotationOffset?`, `castShadow?`, `receiveShadow?`, `useDraco?`, `useViverseAvatar?` (default `false`), `scale?` (default `0.8`, scales both the mesh and the physics capsule).
395
+
396
+ `MIXAMO_STYLE_BONE_MAP` maps unprefixed Mixamo joint names (`Hips`, `LeftUpLeg`, `Spine01`, …) to VRM 1.0 names, so viverse's built-in idle/walk/run/jump animations work. Bones your model lacks are skipped during retargeting. A rig already using VRM 1.0 names needs only `url`.
397
+
398
+ > **Reference stability matters.** `ExploreSimpleCharacter` is `memo`-wrapped. A config object created inline in JSX is new on every render, which defeats the memo and can make viverse re-download the GLB and reset held-key movement state. Use a module constant, or `useMemo` keyed on the URL.
399
+
400
+ ### Movement and input
401
+
402
+ ```tsx
403
+ import { Vanilla } from '@react-three/viverse'
404
+ import type { CharacterControlsConfig } from '@cavegiant/cave-world-r3f/explore'
405
+
406
+ const MOVEMENT = { walk: { speed: 2.4 }, run: { speed: 6 }, jump: false }
407
+
408
+ export const CONTROLS: CharacterControlsConfig = {
409
+ movement: MOVEMENT,
410
+ mobileActionBindings: [
411
+ Vanilla.ScreenJoystickLocomotionActionBindings,
412
+ MySpeedSelectorBindings,
413
+ Vanilla.PointerCaptureRotateZoomActionBindings
414
+ ],
415
+ actionBindingOptions: { screenJoystickRunDistancePx: 9999 }
416
+ }
417
+ ```
418
+
419
+ For speed tiers, mutate `MOVEMENT.walk.speed` in place rather than passing a new object — replacing it remounts `SimpleCharacter` and drops keyboard/joystick state.
420
+
421
+ ---
422
+
423
+ ## Multiplayer
424
+
425
+ Multiplayer is **opt-in and explicit**. Bare `multiplayer` / `multiplayer={true}` without a `url` is ignored (warning). Omitting `avatars` does **not** load the CaveGiant CDN catalog — pass your own, or import `BUILTIN_AVATARS` for demos.
426
+
427
+ ```tsx
428
+ import {
429
+ BUILTIN_AVATARS,
430
+ DEFAULT_WEBSOCKET_URL,
431
+ CaveExplore
432
+ } from '@cavegiant/cave-world-r3f/explore'
433
+
434
+ ;<CaveExplore
435
+ spaceId={id}
436
+ multiplayer={{
437
+ url: 'wss://realtime.example.com/gameplay',
438
+ // or DEFAULT_WEBSOCKET_URL for the CaveGiant demo backend
439
+ avatarId: 'hero',
440
+ avatars: MY_AVATAR_CATALOG, // or BUILTIN_AVATARS for demos
441
+ maxRemoteCharacters: 20
442
+ }}
443
+ />
444
+ ```
445
+
446
+ The connection is per-viewer and torn down on unmount. Advanced exports for custom compositions: `WebSocketService`, `useCharacterSync`, `RemoteCharacterController`, `MessageActionEnum`, `DEFAULT_WEBSOCKET_URL`. Protocol details: [multiplayer-protocol.md](./multiplayer-protocol.md).
447
+
448
+ Wrap host UI around the viewer with a React error boundary if you need recovery from unexpected R3F/WebGL failures outside the splat load-error path.
449
+
450
+ ---
451
+
452
+ ## Load failures
453
+
454
+ 3DGS assets and scene JSON occasionally fail on weak networks. Because spark's `SplatMesh` has only `onLoad` — no `onError` — a failure would otherwise leave `initialized` rejected and the viewer stuck loading forever. The SDK defends against that:
455
+
456
+ - **Automatic retry** with backoff (3 attempts by default), re-downloading by remounting the mesh.
457
+ - **Stall watchdog** (~30s) for downloads that neither complete nor error.
458
+ - **`space:error`** on every failure, with `willRetry`, for telemetry.
459
+ - **Retry prompt** once retries are exhausted, portaled to `document.body` at a very high z-index so it escapes host stacking contexts. Host loaders are usually `fixed` siblings outside the viewer subtree and would otherwise cover it — leaving the user on a spinner that never resolves.
460
+
461
+ ```tsx
462
+ <CaveExplore
463
+ spaceId={id}
464
+ onLoadError={(error) => {
465
+ teardownMyLoader()
466
+ track(error)
467
+ }}
468
+ onLoadErrorExit={() => navigate('/')}
469
+ // renderLoadError={({ error, retry, exit }) => <MyPrompt … />}
470
+ />
471
+ ```
472
+
473
+ Failure state lives on the per-instance store as `spaceLoadError` (`{ stage: 'config' | 'splat', url?, spaceId?, error }`); `retrySpaceLoad()` clears it and bumps `retryToken` to re-trigger loading.
474
+
475
+ Loader errors are `CaveWorldError` with a stable `code`: `SPACE_FETCH_FAILED`, `SPACE_FETCH_TIMEOUT`, `INVALID_SPACE`, `SPLAT_LOAD_FAILED`, `SPLAT_LOAD_TIMEOUT`, `MISSING_PROVIDER`.
476
+
477
+ ---
478
+
479
+ ## Styling and copy
480
+
481
+ See the [README](../README.md#styling) for theming and localization. The short version: `.cw-*` classes themed through `--cw-*` variables, injected at runtime unless `injectStyles={false}`; all copy comes from `messages`, deep-merged over the default locale.
482
+
483
+ ---
484
+
485
+ ## Core hooks
486
+
487
+ All require a `<CaveWorldProvider>` ancestor, which the viewers mount.
488
+
489
+ | Hook | Returns | Notes |
490
+ | ------------------------------ | ------------------- | -------------------------------------------- |
491
+ | `useCaveWorld()` | `CaveWorldServices` | Everything: bus, registries, store, messages |
492
+ | `useCaveWorldStore(selector)` | `T` | Subscribe to world state |
493
+ | `useCaveWorldStoreApi()` | store handle | `getState` / `subscribe`, no re-render |
494
+ | `useCaveWorldMessages()` | `CaveWorldMessages` | Resolved copy |
495
+ | `useCaveEventBus()` | `CaveEventBus` | |
496
+ | `useCaveEvent(event, handler)` | `void` | Auto-cleanup; handler kept in a ref |
497
+ | `useObjectRegistry()` | `ObjectRegistry` | |
498
+ | `useComponentRegistry()` | `ComponentRegistry` | |
499
+
500
+ Exploration hooks (require `<CaveExploreProvider>`, mounted by `CaveExplore`):
501
+
502
+ | Hook | Returns |
503
+ | --------------------------- | -------------------------- |
504
+ | `useCaveExplore(selector)` | Exploration state slice |
505
+ | `useCaveExploreStore()` | Exploration store handle |
506
+ | `useActionPrompt(selector)` | Action-prompt state slice |
507
+ | `useActionPromptStore()` | Action-prompt store handle |
508
+
509
+ ### World state
510
+
511
+ ```ts
512
+ interface CaveWorldState {
513
+ spaceConfig: CaveSpace | undefined
514
+ activeObject: CustomObject | undefined
515
+ detailVisible: boolean
516
+ spaceLoadError: SpaceLoadError | undefined
517
+ retryToken: number
518
+
519
+ setSpaceConfig(config): void
520
+ setActiveObject(object): void
521
+ setDetailVisible(visible): void
522
+ setSpaceLoadError(error): void
523
+ retrySpaceLoad(): void
524
+ }
525
+ ```
526
+
527
+ ### Exploration state
528
+
529
+ ```ts
530
+ interface CaveExploreState {
531
+ viewMode: 'first-person' | 'third-person'
532
+ activeSpaceId: string | undefined
533
+ connectedSpaces: Record<string, CaveSpace> // primary + preloaded neighbors
534
+ spaceTransforms: Record<string, Matrix16> // world-space group matrices
535
+ activeConnector: ConnectorDef | undefined
536
+ readySpaceIds: Record<string, true>
537
+ // …setters
538
+ }
539
+ ```
540
+
541
+ > Inside `useFrame` loops and event handlers, read through `useCaveExploreStore().getState()` rather than a selector. `connectedSpaces` changes on every neighbor preload, and a subscription there re-renders the scene graph during loading.
542
+
543
+ ### Action prompts
544
+
545
+ ```ts
546
+ interface ActionDef {
547
+ id: string
548
+ label: string
549
+ shortcut?: string // e.g. 'F', '1'
550
+ disabled?: boolean // shown but not selectable
551
+ onAction: () => void
552
+ }
553
+ ```
554
+
555
+ `bubble` style anchors in 3D next to the object (single hint); `panel` is a screen-space list (multi-choice). Shortcuts are handled by one global listener, so components never register their own `keydown`.
556
+
557
+ ---
558
+
559
+ ## Composing your own canvas
560
+
561
+ If you already own an R3F canvas and physics world, use the `space` layer directly:
562
+
563
+ ```tsx
564
+ import { Canvas } from '@react-three/fiber'
565
+ import { BvhPhysicsWorld } from '@react-three/viverse'
566
+ import { CaveWorldProvider } from '@cavegiant/cave-world-r3f/core'
567
+ import {
568
+ CaveSpace,
569
+ configureCaveCanvasDomElement,
570
+ configureSparkWebGLRenderer,
571
+ getSparkCanvasDpr,
572
+ useVisibilityFrameloop
573
+ } from '@cavegiant/cave-world-r3f/space'
574
+ import '@cavegiant/cave-world-r3f/styles.css'
575
+
576
+ function MyApp({ spaceConfig }) {
577
+ const frameloop = useVisibilityFrameloop()
578
+
579
+ return (
580
+ <CaveWorldProvider>
581
+ <Canvas
582
+ dpr={getSparkCanvasDpr()}
583
+ frameloop={frameloop}
584
+ gl={{ antialias: false }}
585
+ onCreated={({ gl }) => {
586
+ configureSparkWebGLRenderer(gl)
587
+ configureCaveCanvasDomElement(gl.domElement)
588
+ }}
589
+ >
590
+ <BvhPhysicsWorld>
591
+ <CaveSpace space={spaceConfig} revealOnEnter onInitialized={() => {}} />
592
+ </BvhPhysicsWorld>
593
+ </Canvas>
594
+ </CaveWorldProvider>
595
+ )
596
+ }
597
+ ```
598
+
599
+ `<CaveWorldProvider>` is mandatory. `useVisibilityFrameloop` stops rendering while the page is hidden (GPU drops to zero). `getSparkCanvasDpr` caps backing-store resolution so splat blending cost stays bounded.
600
+
601
+ ---
602
+
603
+ ## Performance guidance
604
+
605
+ **Scene authoring**
606
+
607
+ - Give the container a definite width and height; layout thrash forces canvas resizes.
608
+ - Pre-author `baseLayer.colliders`. Deriving a collider from splat geometry at runtime costs roughly a second.
609
+ - Provide `splatLevels` for heavy captures so mobile gets a variant it can actually render.
610
+ - Prefer `.glb` over `.gltf` for models.
611
+ - Only declare a `Collider` component on objects that need physics.
612
+
613
+ **Integration**
614
+
615
+ - Keep `localCharacterModel`, `characterControls.movement`, and `boneMap` references stable.
616
+ - Read cross-scene state through `getState()` in handlers and frame loops.
617
+ - Multiplayer renders at most 50 remote characters by default.