@vgai/engine 0.5.5 → 0.5.7

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 (446) hide show
  1. package/dist/adapter/adapter-surface.d.ts +30 -0
  2. package/dist/adapter/adapter-surface.d.ts.map +1 -0
  3. package/dist/adapter/adapter-surface.js +18 -0
  4. package/dist/adapter/authoring.d.ts +597 -0
  5. package/dist/adapter/authoring.d.ts.map +1 -0
  6. package/dist/adapter/authoring.js +22 -0
  7. package/dist/adapter/colyseus-networking-adapter.d.ts +77 -0
  8. package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -0
  9. package/dist/adapter/colyseus-networking-adapter.js +76 -0
  10. package/dist/adapter/first-party-systems.d.ts +23 -0
  11. package/dist/adapter/first-party-systems.d.ts.map +1 -0
  12. package/dist/adapter/first-party-systems.js +196 -0
  13. package/dist/adapter/host-context.d.ts +194 -0
  14. package/dist/adapter/host-context.d.ts.map +1 -0
  15. package/dist/adapter/host-context.js +80 -0
  16. package/dist/adapter/index.d.ts +33 -0
  17. package/dist/adapter/index.d.ts.map +1 -0
  18. package/dist/adapter/index.js +25 -0
  19. package/dist/adapter/ingest/game-contract.d.ts +53 -0
  20. package/dist/adapter/ingest/game-contract.d.ts.map +1 -0
  21. package/dist/adapter/ingest/game-contract.js +30 -0
  22. package/dist/adapter/ingest/scene-capture.d.ts +121 -0
  23. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -0
  24. package/dist/adapter/ingest/scene-capture.js +270 -0
  25. package/dist/adapter/ingest/structural-ids.d.ts +64 -0
  26. package/dist/adapter/ingest/structural-ids.d.ts.map +1 -0
  27. package/dist/adapter/ingest/structural-ids.js +110 -0
  28. package/dist/adapter/ingest/upstream-pin.d.ts +44 -0
  29. package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -0
  30. package/dist/adapter/ingest/upstream-pin.js +47 -0
  31. package/dist/adapter/loop-gate-report.d.ts +34 -0
  32. package/dist/adapter/loop-gate-report.d.ts.map +1 -0
  33. package/dist/adapter/loop-gate-report.js +37 -0
  34. package/dist/adapter/rapier-physics-adapter.d.ts +69 -0
  35. package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -0
  36. package/dist/adapter/rapier-physics-adapter.js +135 -0
  37. package/dist/adapter/root-adapter.d.ts +195 -0
  38. package/dist/adapter/root-adapter.d.ts.map +1 -0
  39. package/dist/adapter/root-adapter.js +25 -0
  40. package/dist/adapter/setup-three-root-adapter.d.ts +87 -0
  41. package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -0
  42. package/dist/adapter/setup-three-root-adapter.js +791 -0
  43. package/dist/adapter/system-adapter.d.ts +453 -0
  44. package/dist/adapter/system-adapter.d.ts.map +1 -0
  45. package/dist/adapter/system-adapter.js +13 -0
  46. package/dist/adapter/transform.d.ts +18 -0
  47. package/dist/adapter/transform.d.ts.map +1 -0
  48. package/dist/adapter/transform.js +1 -0
  49. package/dist/ai/navigation.d.ts +78 -0
  50. package/dist/ai/navigation.d.ts.map +1 -0
  51. package/dist/ai/navigation.js +152 -0
  52. package/dist/animation/anim-graph-types.d.ts +25 -0
  53. package/dist/animation/anim-graph-types.d.ts.map +1 -0
  54. package/dist/animation/anim-graph-types.js +1 -0
  55. package/dist/animation/animation-clock.d.ts +213 -0
  56. package/dist/animation/animation-clock.d.ts.map +1 -0
  57. package/dist/animation/animation-clock.js +301 -0
  58. package/dist/animation/blend-node.d.ts +22 -0
  59. package/dist/animation/blend-node.d.ts.map +1 -0
  60. package/dist/animation/blend-node.js +97 -0
  61. package/dist/animation/camera-ownership.d.ts +249 -0
  62. package/dist/animation/camera-ownership.d.ts.map +1 -0
  63. package/dist/animation/camera-ownership.js +169 -0
  64. package/dist/animation/cinematic-cues.d.ts +200 -0
  65. package/dist/animation/cinematic-cues.d.ts.map +1 -0
  66. package/dist/animation/cinematic-cues.js +213 -0
  67. package/dist/animation/clip-map.d.ts +12 -0
  68. package/dist/animation/clip-map.d.ts.map +1 -0
  69. package/dist/animation/clip-map.js +31 -0
  70. package/dist/animation/cubic-spline-interpolant.d.ts +54 -0
  71. package/dist/animation/cubic-spline-interpolant.d.ts.map +1 -0
  72. package/dist/animation/cubic-spline-interpolant.js +111 -0
  73. package/dist/animation/theatre-clock-binding.d.ts +89 -0
  74. package/dist/animation/theatre-clock-binding.d.ts.map +1 -0
  75. package/dist/animation/theatre-clock-binding.js +29 -0
  76. package/dist/animation/theatre-director.d.ts +277 -0
  77. package/dist/animation/theatre-director.d.ts.map +1 -0
  78. package/dist/animation/theatre-director.js +248 -0
  79. package/dist/animation/theatre-object-binding.d.ts +305 -0
  80. package/dist/animation/theatre-object-binding.d.ts.map +1 -0
  81. package/dist/animation/theatre-object-binding.js +351 -0
  82. package/dist/animation/xstate-animation-binding.d.ts +119 -0
  83. package/dist/animation/xstate-animation-binding.d.ts.map +1 -0
  84. package/dist/animation/xstate-animation-binding.js +349 -0
  85. package/dist/animation/xstate-animation-meta.d.ts +266 -0
  86. package/dist/animation/xstate-animation-meta.d.ts.map +1 -0
  87. package/dist/animation/xstate-animation-meta.js +252 -0
  88. package/dist/asset-formats/camera.d.ts +15 -0
  89. package/dist/asset-formats/camera.d.ts.map +1 -0
  90. package/dist/asset-formats/camera.js +15 -0
  91. package/dist/asset-formats/collider.d.ts +21 -0
  92. package/dist/asset-formats/collider.d.ts.map +1 -0
  93. package/dist/asset-formats/collider.js +53 -0
  94. package/dist/asset-formats/index.d.ts +46 -0
  95. package/dist/asset-formats/index.d.ts.map +1 -0
  96. package/dist/asset-formats/index.js +36 -0
  97. package/dist/asset-formats/instances.d.ts +17 -0
  98. package/dist/asset-formats/instances.d.ts.map +1 -0
  99. package/dist/asset-formats/instances.js +31 -0
  100. package/dist/asset-formats/light.d.ts +21 -0
  101. package/dist/asset-formats/light.d.ts.map +1 -0
  102. package/dist/asset-formats/light.js +35 -0
  103. package/dist/asset-formats/material.d.ts +129 -0
  104. package/dist/asset-formats/material.d.ts.map +1 -0
  105. package/dist/asset-formats/material.js +88 -0
  106. package/dist/asset-formats/mesh.d.ts +22 -0
  107. package/dist/asset-formats/mesh.d.ts.map +1 -0
  108. package/dist/asset-formats/mesh.js +64 -0
  109. package/dist/asset-formats/parse.d.ts +20 -0
  110. package/dist/asset-formats/parse.d.ts.map +1 -0
  111. package/dist/asset-formats/parse.js +34 -0
  112. package/dist/asset-formats/particles.d.ts +1390 -0
  113. package/dist/asset-formats/particles.d.ts.map +1 -0
  114. package/dist/asset-formats/particles.js +319 -0
  115. package/dist/asset-formats/render-env.d.ts +387 -0
  116. package/dist/asset-formats/render-env.d.ts.map +1 -0
  117. package/dist/asset-formats/render-env.js +300 -0
  118. package/dist/asset-formats/tuples.d.ts +12 -0
  119. package/dist/asset-formats/tuples.d.ts.map +1 -0
  120. package/dist/asset-formats/tuples.js +14 -0
  121. package/dist/asset-loaders.d.ts +97 -0
  122. package/dist/asset-loaders.d.ts.map +1 -0
  123. package/dist/asset-loaders.js +209 -0
  124. package/dist/asset-parse-error.d.ts +24 -0
  125. package/dist/asset-parse-error.d.ts.map +1 -0
  126. package/dist/asset-parse-error.js +29 -0
  127. package/dist/asset-registry.d.ts +38 -0
  128. package/dist/asset-registry.d.ts.map +1 -0
  129. package/dist/asset-registry.js +66 -0
  130. package/dist/assets.d.ts +26 -0
  131. package/dist/assets.d.ts.map +1 -0
  132. package/dist/assets.js +55 -0
  133. package/dist/audio/wav-encode.d.ts +38 -0
  134. package/dist/audio/wav-encode.d.ts.map +1 -0
  135. package/dist/audio/wav-encode.js +97 -0
  136. package/dist/canvas-react/engine-bridge.d.ts +45 -0
  137. package/dist/canvas-react/engine-bridge.d.ts.map +1 -0
  138. package/dist/canvas-react/engine-bridge.js +45 -0
  139. package/dist/canvas-react/index.d.ts +42 -0
  140. package/dist/canvas-react/index.d.ts.map +1 -0
  141. package/dist/canvas-react/index.js +41 -0
  142. package/dist/canvas-react/pixi-primitive.d.ts +85 -0
  143. package/dist/canvas-react/pixi-primitive.d.ts.map +1 -0
  144. package/dist/canvas-react/pixi-primitive.js +181 -0
  145. package/dist/canvas-react/pixi-react-adapter.d.ts +71 -0
  146. package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -0
  147. package/dist/canvas-react/pixi-react-adapter.js +229 -0
  148. package/dist/canvas-react/pixi-react-root-factory.d.ts +56 -0
  149. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -0
  150. package/dist/canvas-react/pixi-react-root-factory.js +73 -0
  151. package/dist/canvas-react/world-context.d.ts +88 -0
  152. package/dist/canvas-react/world-context.d.ts.map +1 -0
  153. package/dist/canvas-react/world-context.js +212 -0
  154. package/dist/config.d.ts +9 -0
  155. package/dist/config.d.ts.map +1 -0
  156. package/dist/config.js +8 -0
  157. package/dist/core/frame-pacing.d.ts +90 -0
  158. package/dist/core/frame-pacing.d.ts.map +1 -0
  159. package/dist/core/frame-pacing.js +89 -0
  160. package/dist/core/game-loop.d.ts +62 -0
  161. package/dist/core/game-loop.d.ts.map +1 -0
  162. package/dist/core/game-loop.js +189 -0
  163. package/dist/core/seeded-random.d.ts +69 -0
  164. package/dist/core/seeded-random.d.ts.map +1 -0
  165. package/dist/core/seeded-random.js +121 -0
  166. package/dist/core/sim-clock.d.ts +199 -0
  167. package/dist/core/sim-clock.d.ts.map +1 -0
  168. package/dist/core/sim-clock.js +291 -0
  169. package/dist/core/system-runner.d.ts +96 -0
  170. package/dist/core/system-runner.d.ts.map +1 -0
  171. package/dist/core/system-runner.js +253 -0
  172. package/dist/core/types.d.ts +111 -0
  173. package/dist/core/types.d.ts.map +1 -0
  174. package/dist/core/types.js +25 -0
  175. package/dist/data/data-asset.d.ts +101 -0
  176. package/dist/data/data-asset.d.ts.map +1 -0
  177. package/dist/data/data-asset.js +122 -0
  178. package/dist/data/data-check-core.d.ts +109 -0
  179. package/dist/data/data-check-core.d.ts.map +1 -0
  180. package/dist/data/data-check-core.js +177 -0
  181. package/dist/data/data-ref.d.ts +79 -0
  182. package/dist/data/data-ref.d.ts.map +1 -0
  183. package/dist/data/data-ref.js +130 -0
  184. package/dist/data/vite-plugin-data.d.ts +79 -0
  185. package/dist/data/vite-plugin-data.d.ts.map +1 -0
  186. package/dist/data/vite-plugin-data.js +217 -0
  187. package/dist/defaults.d.ts +233 -0
  188. package/dist/defaults.d.ts.map +1 -0
  189. package/dist/defaults.js +137 -0
  190. package/dist/dev/chrome-trace.d.ts +45 -0
  191. package/dist/dev/chrome-trace.d.ts.map +1 -0
  192. package/dist/dev/chrome-trace.js +114 -0
  193. package/dist/dev/console-bridge.d.ts +22 -0
  194. package/dist/dev/console-bridge.d.ts.map +1 -0
  195. package/dist/dev/console-bridge.js +71 -0
  196. package/dist/dev/debug-draw.d.ts +24 -0
  197. package/dist/dev/debug-draw.d.ts.map +1 -0
  198. package/dist/dev/debug-draw.js +73 -0
  199. package/dist/dev/instruments.d.ts +133 -0
  200. package/dist/dev/instruments.d.ts.map +1 -0
  201. package/dist/dev/instruments.js +231 -0
  202. package/dist/dev/logger.d.ts +30 -0
  203. package/dist/dev/logger.d.ts.map +1 -0
  204. package/dist/dev/logger.js +90 -0
  205. package/dist/dev/performance-profiler.d.ts +111 -0
  206. package/dist/dev/performance-profiler.d.ts.map +1 -0
  207. package/dist/dev/performance-profiler.js +215 -0
  208. package/dist/dev/render-debug-adapter.d.ts +57 -0
  209. package/dist/dev/render-debug-adapter.d.ts.map +1 -0
  210. package/dist/dev/render-debug-adapter.js +141 -0
  211. package/dist/dev/render-memory.d.ts +53 -0
  212. package/dist/dev/render-memory.d.ts.map +1 -0
  213. package/dist/dev/render-memory.js +190 -0
  214. package/dist/dev/webgl-frame-capture.d.ts +134 -0
  215. package/dist/dev/webgl-frame-capture.d.ts.map +1 -0
  216. package/dist/dev/webgl-frame-capture.js +270 -0
  217. package/dist/dev/webgl-gpu-timer.d.ts +8 -0
  218. package/dist/dev/webgl-gpu-timer.d.ts.map +1 -0
  219. package/dist/dev/webgl-gpu-timer.js +52 -0
  220. package/dist/ecs/scene-index.d.ts +179 -0
  221. package/dist/ecs/scene-index.d.ts.map +1 -0
  222. package/dist/ecs/scene-index.js +331 -0
  223. package/dist/ecs/scene-query.d.ts +6 -0
  224. package/dist/ecs/scene-query.d.ts.map +1 -0
  225. package/dist/ecs/scene-query.js +40 -0
  226. package/dist/ecs/user-data.d.ts +193 -0
  227. package/dist/ecs/user-data.d.ts.map +1 -0
  228. package/dist/ecs/user-data.js +145 -0
  229. package/dist/index.d.ts +35 -0
  230. package/dist/index.d.ts.map +1 -0
  231. package/dist/index.js +29 -0
  232. package/dist/input/input-manager.d.ts +944 -0
  233. package/dist/input/input-manager.d.ts.map +1 -0
  234. package/dist/input/input-manager.js +2209 -0
  235. package/dist/input/input-types.d.ts +174 -0
  236. package/dist/input/input-types.d.ts.map +1 -0
  237. package/dist/input/input-types.js +1 -0
  238. package/dist/input/prompt-labels.d.ts +21 -0
  239. package/dist/input/prompt-labels.d.ts.map +1 -0
  240. package/dist/input/prompt-labels.js +121 -0
  241. package/dist/input/rebind-controller.d.ts +59 -0
  242. package/dist/input/rebind-controller.d.ts.map +1 -0
  243. package/dist/input/rebind-controller.js +85 -0
  244. package/dist/input/schema.d.ts +16 -0
  245. package/dist/input/schema.d.ts.map +1 -0
  246. package/dist/input/schema.js +228 -0
  247. package/dist/loader.d.ts +76 -0
  248. package/dist/loader.d.ts.map +1 -0
  249. package/dist/loader.js +114 -0
  250. package/dist/manifest/editor-port.d.ts +53 -0
  251. package/dist/manifest/editor-port.d.ts.map +1 -0
  252. package/dist/manifest/editor-port.js +67 -0
  253. package/dist/manifest/filename.d.ts +14 -0
  254. package/dist/manifest/filename.d.ts.map +1 -0
  255. package/dist/manifest/filename.js +43 -0
  256. package/dist/manifest/index.d.ts +6 -0
  257. package/dist/manifest/index.d.ts.map +1 -0
  258. package/dist/manifest/index.js +9 -0
  259. package/dist/manifest/load-file.d.ts +7 -0
  260. package/dist/manifest/load-file.d.ts.map +1 -0
  261. package/dist/manifest/load-file.js +23 -0
  262. package/dist/manifest/load.d.ts +161 -0
  263. package/dist/manifest/load.d.ts.map +1 -0
  264. package/dist/manifest/load.js +269 -0
  265. package/dist/manifest/locate.d.ts +24 -0
  266. package/dist/manifest/locate.d.ts.map +1 -0
  267. package/dist/manifest/locate.js +48 -0
  268. package/dist/manifest/schema.d.ts +293 -0
  269. package/dist/manifest/schema.d.ts.map +1 -0
  270. package/dist/manifest/schema.js +633 -0
  271. package/dist/physics/collider-dimensions.d.ts +63 -0
  272. package/dist/physics/collider-dimensions.d.ts.map +1 -0
  273. package/dist/physics/collider-dimensions.js +98 -0
  274. package/dist/physics/collision-system.d.ts +41 -0
  275. package/dist/physics/collision-system.d.ts.map +1 -0
  276. package/dist/physics/collision-system.js +64 -0
  277. package/dist/physics/physics-registry.d.ts +45 -0
  278. package/dist/physics/physics-registry.d.ts.map +1 -0
  279. package/dist/physics/physics-registry.js +65 -0
  280. package/dist/physics/transform-writer.d.ts +16 -0
  281. package/dist/physics/transform-writer.d.ts.map +1 -0
  282. package/dist/physics/transform-writer.js +38 -0
  283. package/dist/pixi/authoring.d.ts +93 -0
  284. package/dist/pixi/authoring.d.ts.map +1 -0
  285. package/dist/pixi/authoring.js +207 -0
  286. package/dist/pixi/index.d.ts +16 -0
  287. package/dist/pixi/index.d.ts.map +1 -0
  288. package/dist/pixi/index.js +15 -0
  289. package/dist/pixi/ingest-iframe.d.ts +82 -0
  290. package/dist/pixi/ingest-iframe.d.ts.map +1 -0
  291. package/dist/pixi/ingest-iframe.js +166 -0
  292. package/dist/pixi/ingest.d.ts +97 -0
  293. package/dist/pixi/ingest.d.ts.map +1 -0
  294. package/dist/pixi/ingest.js +40 -0
  295. package/dist/pixi/physics-registry.d.ts +24 -0
  296. package/dist/pixi/physics-registry.d.ts.map +1 -0
  297. package/dist/pixi/physics-registry.js +38 -0
  298. package/dist/pixi/scene-capture.d.ts +43 -0
  299. package/dist/pixi/scene-capture.d.ts.map +1 -0
  300. package/dist/pixi/scene-capture.js +92 -0
  301. package/dist/pixi/system-adapters.d.ts +16 -0
  302. package/dist/pixi/system-adapters.d.ts.map +1 -0
  303. package/dist/pixi/system-adapters.js +37 -0
  304. package/dist/react/unmanaged-root-detector.d.ts +18 -0
  305. package/dist/react/unmanaged-root-detector.d.ts.map +1 -0
  306. package/dist/react/unmanaged-root-detector.js +70 -0
  307. package/dist/react/use-data.d.ts +60 -0
  308. package/dist/react/use-data.d.ts.map +1 -0
  309. package/dist/react/use-data.js +101 -0
  310. package/dist/react/use-selection.d.ts +54 -0
  311. package/dist/react/use-selection.d.ts.map +1 -0
  312. package/dist/react/use-selection.js +71 -0
  313. package/dist/react/world-state.d.ts +154 -0
  314. package/dist/react/world-state.d.ts.map +1 -0
  315. package/dist/react/world-state.js +220 -0
  316. package/dist/render/auto-batcher.d.ts +34 -0
  317. package/dist/render/auto-batcher.d.ts.map +1 -0
  318. package/dist/render/auto-batcher.js +140 -0
  319. package/dist/render/instance-mesh.d.ts +5 -0
  320. package/dist/render/instance-mesh.d.ts.map +1 -0
  321. package/dist/render/instance-mesh.js +19 -0
  322. package/dist/render/light-camera-factory.d.ts +24 -0
  323. package/dist/render/light-camera-factory.d.ts.map +1 -0
  324. package/dist/render/light-camera-factory.js +71 -0
  325. package/dist/render/lod.d.ts +13 -0
  326. package/dist/render/lod.d.ts.map +1 -0
  327. package/dist/render/lod.js +16 -0
  328. package/dist/render/material-factory.d.ts +15 -0
  329. package/dist/render/material-factory.d.ts.map +1 -0
  330. package/dist/render/material-factory.js +186 -0
  331. package/dist/render/mesh-shadow.d.ts +11 -0
  332. package/dist/render/mesh-shadow.d.ts.map +1 -0
  333. package/dist/render/mesh-shadow.js +19 -0
  334. package/dist/render/particles-factory.d.ts +99 -0
  335. package/dist/render/particles-factory.d.ts.map +1 -0
  336. package/dist/render/particles-factory.js +361 -0
  337. package/dist/render/render-batch-system.d.ts +33 -0
  338. package/dist/render/render-batch-system.d.ts.map +1 -0
  339. package/dist/render/render-batch-system.js +223 -0
  340. package/dist/render/render-features.d.ts +47 -0
  341. package/dist/render/render-features.d.ts.map +1 -0
  342. package/dist/render/render-features.js +105 -0
  343. package/dist/render/render-settings.d.ts +35 -0
  344. package/dist/render/render-settings.d.ts.map +1 -0
  345. package/dist/render/render-settings.js +51 -0
  346. package/dist/render/spark-renderer-lifecycle.d.ts +18 -0
  347. package/dist/render/spark-renderer-lifecycle.d.ts.map +1 -0
  348. package/dist/render/spark-renderer-lifecycle.js +57 -0
  349. package/dist/render/viewport-shading.d.ts +17 -0
  350. package/dist/render/viewport-shading.d.ts.map +1 -0
  351. package/dist/render/viewport-shading.js +87 -0
  352. package/dist/runtime/create-runtime.d.ts +259 -0
  353. package/dist/runtime/create-runtime.d.ts.map +1 -0
  354. package/dist/runtime/create-runtime.js +666 -0
  355. package/dist/runtime/debug-bridge.d.ts +288 -0
  356. package/dist/runtime/debug-bridge.d.ts.map +1 -0
  357. package/dist/runtime/debug-bridge.js +277 -0
  358. package/dist/runtime/debug-registry.d.ts +287 -0
  359. package/dist/runtime/debug-registry.d.ts.map +1 -0
  360. package/dist/runtime/debug-registry.js +528 -0
  361. package/dist/runtime/dev-layers.d.ts +34 -0
  362. package/dist/runtime/dev-layers.d.ts.map +1 -0
  363. package/dist/runtime/dev-layers.js +40 -0
  364. package/dist/runtime/frame-selector-cache.d.ts +41 -0
  365. package/dist/runtime/frame-selector-cache.d.ts.map +1 -0
  366. package/dist/runtime/frame-selector-cache.js +65 -0
  367. package/dist/runtime/game.d.ts +576 -0
  368. package/dist/runtime/game.d.ts.map +1 -0
  369. package/dist/runtime/game.js +1003 -0
  370. package/dist/runtime/gameplay-rng-trap.d.ts +47 -0
  371. package/dist/runtime/gameplay-rng-trap.d.ts.map +1 -0
  372. package/dist/runtime/gameplay-rng-trap.js +95 -0
  373. package/dist/runtime/input-router.d.ts +78 -0
  374. package/dist/runtime/input-router.d.ts.map +1 -0
  375. package/dist/runtime/input-router.js +171 -0
  376. package/dist/runtime/mount-game.d.ts +126 -0
  377. package/dist/runtime/mount-game.d.ts.map +1 -0
  378. package/dist/runtime/mount-game.js +159 -0
  379. package/dist/runtime/mount-manifest.d.ts +174 -0
  380. package/dist/runtime/mount-manifest.d.ts.map +1 -0
  381. package/dist/runtime/mount-manifest.js +304 -0
  382. package/dist/runtime/presentation.d.ts +85 -0
  383. package/dist/runtime/presentation.d.ts.map +1 -0
  384. package/dist/runtime/presentation.js +92 -0
  385. package/dist/runtime/render-audio-control.d.ts +126 -0
  386. package/dist/runtime/render-audio-control.d.ts.map +1 -0
  387. package/dist/runtime/render-audio-control.js +116 -0
  388. package/dist/runtime/render-control.d.ts +347 -0
  389. package/dist/runtime/render-control.d.ts.map +1 -0
  390. package/dist/runtime/render-control.js +317 -0
  391. package/dist/runtime/render-seed.d.ts +56 -0
  392. package/dist/runtime/render-seed.d.ts.map +1 -0
  393. package/dist/runtime/render-seed.js +73 -0
  394. package/dist/runtime/state-bridge.d.ts +60 -0
  395. package/dist/runtime/state-bridge.d.ts.map +1 -0
  396. package/dist/runtime/state-bridge.js +62 -0
  397. package/dist/runtime/types.d.ts +285 -0
  398. package/dist/runtime/types.d.ts.map +1 -0
  399. package/dist/runtime/types.js +1 -0
  400. package/dist/runtime/unexported-game-trap.d.ts +36 -0
  401. package/dist/runtime/unexported-game-trap.d.ts.map +1 -0
  402. package/dist/runtime/unexported-game-trap.js +80 -0
  403. package/dist/setup/setup-audio.d.ts +20 -0
  404. package/dist/setup/setup-audio.d.ts.map +1 -0
  405. package/dist/setup/setup-audio.js +41 -0
  406. package/dist/setup/setup-particles.d.ts +17 -0
  407. package/dist/setup/setup-particles.d.ts.map +1 -0
  408. package/dist/setup/setup-particles.js +16 -0
  409. package/dist/setup/setup-physics.d.ts +25 -0
  410. package/dist/setup/setup-physics.d.ts.map +1 -0
  411. package/dist/setup/setup-physics.js +51 -0
  412. package/dist/setup/setup-renderer.d.ts +82 -0
  413. package/dist/setup/setup-renderer.d.ts.map +1 -0
  414. package/dist/setup/setup-renderer.js +381 -0
  415. package/dist/world3d-react/engine-bridge.d.ts +47 -0
  416. package/dist/world3d-react/engine-bridge.d.ts.map +1 -0
  417. package/dist/world3d-react/engine-bridge.js +48 -0
  418. package/dist/world3d-react/index.d.ts +47 -0
  419. package/dist/world3d-react/index.d.ts.map +1 -0
  420. package/dist/world3d-react/index.js +46 -0
  421. package/dist/world3d-react/r3f-adapter.d.ts +51 -0
  422. package/dist/world3d-react/r3f-adapter.d.ts.map +1 -0
  423. package/dist/world3d-react/r3f-adapter.js +368 -0
  424. package/dist/world3d-react/r3f-root-factory.d.ts +62 -0
  425. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -0
  426. package/dist/world3d-react/r3f-root-factory.js +79 -0
  427. package/dist/world3d-react/rapier-physics-bridge.d.ts +76 -0
  428. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -0
  429. package/dist/world3d-react/rapier-physics-bridge.js +120 -0
  430. package/dist/world3d-react/renderer-config.d.ts +58 -0
  431. package/dist/world3d-react/renderer-config.d.ts.map +1 -0
  432. package/dist/world3d-react/renderer-config.js +96 -0
  433. package/dist/world3d-react/world-context.d.ts +123 -0
  434. package/dist/world3d-react/world-context.d.ts.map +1 -0
  435. package/dist/world3d-react/world-context.js +204 -0
  436. package/dist-config/config.js +303 -0
  437. package/dist-config/config.js.map +7 -0
  438. package/package.json +14 -3
  439. package/src/adapter/rapier-physics-adapter.ts +77 -34
  440. package/src/config.ts +8 -0
  441. package/src/core/frame-pacing.ts +26 -0
  442. package/src/core/game-loop.ts +4 -3
  443. package/src/dev/instruments.ts +302 -0
  444. package/src/setup/setup-renderer.ts +2 -0
  445. package/src/world3d-react/index.ts +9 -0
  446. package/src/world3d-react/rapier-physics-bridge.tsx +137 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * `SimClock` — P3, the sim-time scheduler (punchlist item P3, rung 4).
3
+ *
4
+ * `await` **is** the scheduler; JS already won that argument. The only thing
5
+ * missing from the engine was *clock binding*: a `delay(seconds)` that resolves
6
+ * on the fixed loop's own accumulator instead of on wall time, so a timer
7
+ * pauses when the game pauses, steps when the game steps, and reproduces
8
+ * exactly in an offline export. That, plus a timed-dispose helper for debris,
9
+ * is this whole module.
10
+ *
11
+ * ## This is NOT `AnimationClock`, and it never will be
12
+ *
13
+ * `animation/animation-clock.ts` is a seekable **cinematic** clock: it plays,
14
+ * pauses, loops, runs in reverse and is `seek()`ed to arbitrary times so
15
+ * Theatre/GSAP timelines and cue evaluators can be scrubbed. `SimClock` is the
16
+ * **monotonic gameplay clock**: it only ever moves forward, one fixed substep
17
+ * at a time, and there is no seek. Both live in one engine because they answer
18
+ * different questions — "where is the cinematic playhead" versus "how much
19
+ * gameplay time has actually elapsed".
20
+ *
21
+ * The reason they can never merge is a fact about Promises: **a Promise cannot
22
+ * un-resolve.** Scrub a seekable clock backwards past a `delay` that already
23
+ * fired and there is no correct behavior — you cannot un-await it. So `delay`
24
+ * binds to the clock that cannot rewind, and cinematic scrubbing stays with the
25
+ * clock that can.
26
+ *
27
+ * ## Two forms, because sync and async are genuinely different here
28
+ *
29
+ * `Game.runTicks` is **synchronous** (`runtime/game.ts`): it runs N substeps in
30
+ * one straight-line loop. A `delay` continuation is a microtask, so under
31
+ * `runTicks` — and under an offline export driven the same way — it does *not*
32
+ * interleave between ticks; it runs when the caller's stack unwinds, after the
33
+ * whole run. Under the ordinary rAF loop each frame yields, so the difference
34
+ * is invisible.
35
+ *
36
+ * Therefore:
37
+ *
38
+ * - `await clock.delay(n)` is the **ergonomic** form. Use it for gameplay.
39
+ * - `clock.after(n, fn)` is the **exact** form: `fn` is invoked inline, during
40
+ * the flush of the exact due tick, under every driver — rAF, `runTicks`,
41
+ * `step()`, offline export.
42
+ * - `clock.disposeAfter(obj, n)` is built on `after`, so debris removal is
43
+ * frame-exact in an offline export rather than "some time after the run".
44
+ *
45
+ * ## Firing rule (deterministic — pinned by `test/sim-clock.test.ts`)
46
+ *
47
+ * - The runtime calls `flush(simT)` immediately after it bumps `tick`/`simT`,
48
+ * inside the same `advanced` guard — so a paused or frozen world fires no
49
+ * timers at all, and `Game.play.step()` fires exactly the timers that one
50
+ * substep makes due.
51
+ * - A timer is due when `simT >= scheduledAt + seconds`.
52
+ * - Due timers run in due-time ascending order, insertion-sequence ascending
53
+ * on ties.
54
+ * - A timer scheduled *during* a flush is never due in that same flush, even
55
+ * at `seconds === 0`. That is what makes a self-rescheduling timer unable to
56
+ * hang the frame.
57
+ *
58
+ * ## Cancellation is `AbortSignal`
59
+ *
60
+ * The same rung-1 web primitive the rest of this program uses. An
61
+ * already-aborted signal makes `delay` reject immediately and `after` a no-op;
62
+ * rejection is a `DOMException` with `name === 'AbortError'`, matching `fetch`.
63
+ * Disposing the clock rejects every pending `delay` the same way — game code
64
+ * awaiting a delay is expected to tolerate rejection exactly as aborted `fetch`
65
+ * callers do.
66
+ *
67
+ * ## Ownership: the clock is GAME-scoped, and only the Game disposes it
68
+ *
69
+ * One clock per `Game`, shared by every world on it (`getSimClock(game)`); a
70
+ * mount with no Game shell builds a private, mount-local one instead. Whoever
71
+ * CREATED a clock disposes it: `GameInternal.dispose()` disposes the
72
+ * game-scoped one — `runtime/create-runtime.ts` calls that after every root has
73
+ * torn down — and a root adapter disposes only its own mount-local fallback.
74
+ * A per-root teardown must NEVER dispose the game-scoped clock: disposing one
75
+ * world's `mounted` is a supported way to end a sub-session while the Game
76
+ * keeps running, and doing so would freeze `now()`, reject every sibling
77
+ * world's pending `delay` and turn its later `after()` calls into silent
78
+ * no-ops. (`seededRandom` and `debugRegistry` are game-scoped the same way, and
79
+ * per-root teardown has never destroyed either — that asymmetry is what
80
+ * exposed the bug.)
81
+ *
82
+ * ## Cancelling your own timers is the GAME's job — warm restart will not
83
+ *
84
+ * A timer outlives the code that scheduled it unless something cancels it, and
85
+ * `hotReload` (warm restart) deliberately does not reach the game-scoped clock:
86
+ * it is per-ROOT, so cancelling here would kill a sibling root's live timers —
87
+ * the same ownership violation as above. There is deliberately no `reset()`.
88
+ *
89
+ * So a stale `after` closure surviving a warm restart is the same hazard class
90
+ * as a stale event listener, and it has the same owner and the same remedy: the
91
+ * game's own cleanup. `hotReload` runs the game's `dispose()` first for exactly
92
+ * this reason, and both `after` and `delay` take an `AbortSignal` so one
93
+ * controller cancels everything a `setup()` scheduled:
94
+ *
95
+ * ```ts
96
+ * export async function setup(ctx: GameContext): Promise<GameCleanup> {
97
+ * const ac = new AbortController();
98
+ * ctx.clock.after(3, () => spawnWave(), { signal: ac.signal });
99
+ * return { dispose: () => ac.abort() }; // cancels timers AND listeners
100
+ * }
101
+ * ```
102
+ *
103
+ * This is the engine-wide "Examples must clean up" contract (CLAUDE.md), not a
104
+ * special rule for timers.
105
+ *
106
+ * ## Deliberately absent
107
+ *
108
+ * - **No `every`/`repeat`/interval.** Nothing needs one, and `after`
109
+ * re-arming itself is three lines the game owns.
110
+ * - **No fiber kernel, no coroutine emulation, no `task.spawn`.** Calling an
111
+ * `async function` *is* `task.spawn` — the language already has it.
112
+ * - **No wall clock.** This module reads no system timer and creates no
113
+ * host timeout; every time value comes from the runtime's own accumulator.
114
+ */
115
+ const NOOP_HANDLE = { cancel() { } };
116
+ /** `fetch`-shaped abort rejection: a `DOMException` named `AbortError`. */
117
+ function abortError(message) {
118
+ return new DOMException(message, 'AbortError');
119
+ }
120
+ function assertSeconds(method, seconds) {
121
+ if (!Number.isFinite(seconds) || seconds < 0) {
122
+ throw new RangeError(`SimClock.${method}: seconds must be a finite, non-negative number, got ${seconds}`);
123
+ }
124
+ }
125
+ /**
126
+ * Build the one sim clock a `Game` owns. See the module header for the
127
+ * contract; `runtime/game.ts` is the only production caller.
128
+ */
129
+ export function createSimClock(options) {
130
+ const disposeObject = options.dispose;
131
+ let simT = 0;
132
+ let ticks = 0;
133
+ let seq = 0;
134
+ let disposed = false;
135
+ const pending = new Set();
136
+ /** Rejectors for in-flight `delay`s, so `dispose()` can settle them all. */
137
+ const pendingDelays = new Set();
138
+ function cancelTimer(timer) {
139
+ if (timer.cancelled)
140
+ return;
141
+ timer.cancelled = true;
142
+ pending.delete(timer);
143
+ timer.detachAbort?.();
144
+ timer.detachAbort = null;
145
+ }
146
+ function after(seconds, fn, opts) {
147
+ assertSeconds('after', seconds);
148
+ const signal = opts?.signal;
149
+ // Already-aborted, or a clock that is gone: a no-op, never a throw.
150
+ if (disposed || signal?.aborted)
151
+ return NOOP_HANDLE;
152
+ const timer = {
153
+ dueAt: simT + seconds,
154
+ seq: seq++,
155
+ fn,
156
+ cancelled: false,
157
+ detachAbort: null,
158
+ };
159
+ pending.add(timer);
160
+ if (signal) {
161
+ const onAbort = () => cancelTimer(timer);
162
+ signal.addEventListener('abort', onAbort, { once: true });
163
+ timer.detachAbort = () => signal.removeEventListener('abort', onAbort);
164
+ }
165
+ return {
166
+ cancel() {
167
+ cancelTimer(timer);
168
+ },
169
+ };
170
+ }
171
+ return {
172
+ now: () => simT,
173
+ tickNow: () => ticks,
174
+ after,
175
+ delay(seconds, opts) {
176
+ assertSeconds('delay', seconds);
177
+ const signal = opts?.signal;
178
+ if (signal?.aborted) {
179
+ return Promise.reject(abortError('SimClock.delay: aborted before it was scheduled'));
180
+ }
181
+ if (disposed) {
182
+ return Promise.reject(abortError('SimClock.delay: the sim clock is disposed'));
183
+ }
184
+ return new Promise((resolve, reject) => {
185
+ let handle = null;
186
+ let detachAbort = null;
187
+ let settled = false;
188
+ const finish = () => {
189
+ settled = true;
190
+ pendingDelays.delete(rejectDelay);
191
+ detachAbort?.();
192
+ detachAbort = null;
193
+ };
194
+ function rejectDelay(message) {
195
+ if (settled)
196
+ return;
197
+ finish();
198
+ handle?.cancel();
199
+ reject(abortError(message));
200
+ }
201
+ handle = after(seconds, () => {
202
+ if (settled)
203
+ return;
204
+ finish();
205
+ resolve();
206
+ });
207
+ pendingDelays.add(rejectDelay);
208
+ if (signal) {
209
+ const onAbort = () => rejectDelay('SimClock.delay: aborted');
210
+ signal.addEventListener('abort', onAbort, { once: true });
211
+ detachAbort = () => signal.removeEventListener('abort', onAbort);
212
+ }
213
+ });
214
+ },
215
+ disposeAfter(obj, seconds) {
216
+ assertSeconds('disposeAfter', seconds);
217
+ return after(seconds, () => disposeObject(obj));
218
+ },
219
+ flush(nextSimT) {
220
+ if (disposed)
221
+ return;
222
+ simT = nextSimT;
223
+ ticks++;
224
+ if (pending.size === 0)
225
+ return;
226
+ // Snapshot BEFORE running anything: a timer scheduled by one of these
227
+ // callbacks lands in `pending` but not in this batch, so it can never be
228
+ // due in the same flush (module header, firing rule 4).
229
+ const batch = [];
230
+ for (const timer of pending) {
231
+ if (!timer.cancelled && timer.dueAt <= simT)
232
+ batch.push(timer);
233
+ }
234
+ if (batch.length === 0)
235
+ return;
236
+ batch.sort((a, b) => a.dueAt - b.dueAt || a.seq - b.seq);
237
+ for (const timer of batch) {
238
+ // A previously-fired callback may have cancelled this one.
239
+ if (timer.cancelled)
240
+ continue;
241
+ cancelTimer(timer);
242
+ try {
243
+ timer.fn();
244
+ }
245
+ catch (err) {
246
+ // biome-ignore lint/suspicious/noConsole: loud degrade — one bad timer must not abort the frame's remaining timers (same isolation idiom as `runFrameImpl`'s per-world try/catch)
247
+ console.error('[sim-clock] timer callback threw:', err);
248
+ }
249
+ }
250
+ },
251
+ dispose() {
252
+ if (disposed)
253
+ return;
254
+ disposed = true;
255
+ for (const timer of pending) {
256
+ timer.detachAbort?.();
257
+ timer.detachAbort = null;
258
+ timer.cancelled = true;
259
+ }
260
+ pending.clear();
261
+ const rejectors = [...pendingDelays];
262
+ pendingDelays.clear();
263
+ for (const rejectDelay of rejectors) {
264
+ rejectDelay('SimClock: disposed while a delay was pending');
265
+ }
266
+ },
267
+ };
268
+ }
269
+ // ---------------------------------------------------------------------------
270
+ // Game-scoped registry — mirrors `core/seeded-random.ts`'s
271
+ // `registerSeededRandom`/`getSeededRandom` WeakMap pattern exactly, keyed on a
272
+ // bare `object` (not `Game`) so `core/` never imports `runtime/`. `createGame`
273
+ // is the one real registrant, passing the `GameInternal` shell as the key.
274
+ // ---------------------------------------------------------------------------
275
+ const clockByOwner = new WeakMap();
276
+ /** Called once by `createGame`, right after the clock and the Game shell exist. */
277
+ export function registerSimClock(owner, clock) {
278
+ clockByOwner.set(owner, clock);
279
+ }
280
+ /** The game-scoped clock backing every world's `ctx.clock` — `null` for an
281
+ * owner built without one (a hand-built `Game`-shaped stand-in that never went
282
+ * through `createGame`).
283
+ *
284
+ * Returns the INTERNAL view because this registry is engine-only — but a
285
+ * caller that RESOLVES a clock here is not its owner and must not call
286
+ * `dispose()` on it (module header, ownership). Game code never reaches it at
287
+ * all: `GameContext.clock` is typed as the public {@link SimClock}, which has
288
+ * neither `flush` nor `dispose`, the same way `Game` hides `GameInternal`. */
289
+ export function getSimClock(owner) {
290
+ return clockByOwner.get(owner) ?? null;
291
+ }
@@ -0,0 +1,96 @@
1
+ import { type SystemDef, type SystemFn, type SystemOptions, type SystemPhaseName, type SystemRunObserver } from './types';
2
+ /**
3
+ * Ordered system execution by named phase.
4
+ *
5
+ * Systems are registered with a phase name. When `run(dt)` is called, all
6
+ * phases execute in `PHASE_ORDER` via `runPhase(phase, dt)`. Within a phase,
7
+ * `runPhase` executes two ordered buckets (T7.1 slice 2):
8
+ *
9
+ * 1. **engine** — everything `add()`/`register()`ed at or before the last
10
+ * `markEngineBoundary()` call, or EVERYTHING if no boundary has ever
11
+ * been marked.
12
+ * 2. **game** — everything `add()`/`register()`ed AFTER the last
13
+ * `markEngineBoundary()` call.
14
+ *
15
+ * Within each bucket, systems run in registration order.
16
+ */
17
+ export declare function createSystemRunner(observer?: SystemRunObserver, scope?: string): {
18
+ /**
19
+ * Register a bare system function in a specific phase.
20
+ * Systems within the same phase run in the order they were added.
21
+ */
22
+ add(phase: SystemPhaseName, fn: SystemFn, options?: SystemOptions): void;
23
+ /**
24
+ * Remove a bare system function from a phase. Also drops it from the
25
+ * engine-membership set (if present) — the fix for the length-based
26
+ * bookkeeping bug: removing this has no effect on how any OTHER entry
27
+ * (before or after it) is classified, since classification is now
28
+ * per-function membership, not position.
29
+ */
30
+ remove(phase: SystemPhaseName, fn: SystemFn): void;
31
+ /**
32
+ * Register a lifecycle system. Adds its update to the correct phase.
33
+ * Call runInit() after all systems are registered to invoke init hooks.
34
+ */
35
+ register(system: SystemDef): void;
36
+ /**
37
+ * Unregister a lifecycle system. Removes its update and calls dispose.
38
+ */
39
+ unregister(system: SystemDef): void;
40
+ /**
41
+ * Call init() on all registered lifecycle systems, in phase order.
42
+ * Call once after scene load, before the first game loop tick.
43
+ */
44
+ runInit(): void;
45
+ /**
46
+ * Call dispose() on all registered lifecycle systems and remove them.
47
+ */
48
+ runDispose(): void;
49
+ /**
50
+ * Run ONE phase's two ordered buckets — engine, game
51
+ * (see the module doc comment). Each system call is isolated: a
52
+ * throwing system is loudly logged (never swallowed) but does not stop
53
+ * its siblings in the same bucket, a later bucket in this phase, or a
54
+ * later phase, from running this frame.
55
+ *
56
+ * Public — the Game root's frame executor (`runtime/game.ts`) calls this
57
+ * directly per (phase, world); `run(dt)` below is just a loop over it,
58
+ * preserved for any direct caller.
59
+ */
60
+ runPhase(phase: SystemPhaseName, dt: number): void;
61
+ /**
62
+ * Run all phases in order, via `runPhase`.
63
+ */
64
+ run(dt: number): void;
65
+ /**
66
+ * Mark everything registered so far (via `add()`/`register()`, in every
67
+ * phase) as "engine" — infrastructure that must survive a warm restart.
68
+ * Call once, right after mount finishes wiring engine-level systems and
69
+ * BEFORE any game (`setup()`/scene load) registers its own. `hotReload`
70
+ * calls `removeAllNonEngine()` on every restart, which only ever removes
71
+ * what was added after this mark.
72
+ */
73
+ markEngineBoundary(): void;
74
+ /**
75
+ * Bulk-remove every system registered after the last `markEngineBoundary()`
76
+ * call — both bare `add()`ed functions and lifecycle `register()`ed systems
77
+ * (the latter get `dispose()`d, mirroring `unregister()`). A no-op if no
78
+ * boundary has been marked. Idempotent: calling it again with nothing new
79
+ * registered since is a safe no-op.
80
+ *
81
+ * This is what makes a warm restart (`hotReload`) safe to call N times
82
+ * without accumulating duplicate systems/listeners — each restart's
83
+ * outgoing game systems are fully removed before the new one registers its
84
+ * own, while the engine systems (input/physics/render/...) registered
85
+ * before the boundary are never touched.
86
+ */
87
+ removeAllNonEngine(): void;
88
+ /**
89
+ * Total system-function count, optionally scoped to one phase.
90
+ * Test/introspection helper — used to assert a warm restart doesn't
91
+ * accumulate systems (see `removeAllNonEngine`).
92
+ */
93
+ count(phase?: SystemPhaseName): number;
94
+ };
95
+ export type SystemRunner = ReturnType<typeof createSystemRunner>;
96
+ //# sourceMappingURL=system-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-runner.d.ts","sourceRoot":"","sources":["../../src/core/system-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,KAAK,SAAU;IAyC5E;;;OAGG;eACQ,eAAe,MAAM,QAAQ,YAAY,aAAa;IASjE;;;;;;OAMG;kBACW,eAAe,MAAM,QAAQ;IAQ3C;;;OAGG;qBACc,SAAS;IAU1B;;OAEG;uBACgB,SAAS;IAa5B;;;OAGG;;IAUH;;OAEG;;IAaH;;;;;;;;;;OAUG;oBACa,eAAe,MAAM,MAAM;IA4B3C;;OAEG;YACK,MAAM;IAMd;;;;;;;OAOG;;IAUH;;;;;;;;;;;;OAYG;;IAsBH;;;;OAIG;kBACW,eAAe,GAAG,MAAM;EAWzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,253 @@
1
+ import { PHASE_ORDER, } from './types';
2
+ /**
3
+ * Ordered system execution by named phase.
4
+ *
5
+ * Systems are registered with a phase name. When `run(dt)` is called, all
6
+ * phases execute in `PHASE_ORDER` via `runPhase(phase, dt)`. Within a phase,
7
+ * `runPhase` executes two ordered buckets (T7.1 slice 2):
8
+ *
9
+ * 1. **engine** — everything `add()`/`register()`ed at or before the last
10
+ * `markEngineBoundary()` call, or EVERYTHING if no boundary has ever
11
+ * been marked.
12
+ * 2. **game** — everything `add()`/`register()`ed AFTER the last
13
+ * `markEngineBoundary()` call.
14
+ *
15
+ * Within each bucket, systems run in registration order.
16
+ */
17
+ export function createSystemRunner(observer, scope = 'world') {
18
+ const systems = new Map();
19
+ const registered = [];
20
+ const labels = new Map();
21
+ let anonymousId = 0;
22
+ // Initialize all phases with empty arrays.
23
+ for (const phase of PHASE_ORDER) {
24
+ systems.set(phase, []);
25
+ }
26
+ // Membership bookkeeping for `markEngineBoundary()` (T7.2 review fix —
27
+ // replaces the old length/index-snapshot bookkeeping, which broke if a
28
+ // pre-boundary "engine" entry was ever removed: shrinking the list would
29
+ // shift a later "game" entry underneath the recorded length, misclassifying
30
+ // it as engine). `engineFns`/`engineRegistered` instead record WHICH
31
+ // specific function/system was present at the last `markEngineBoundary()`
32
+ // call — membership survives arbitrary removal of any other entry,
33
+ // regardless of position. `boundaryMarked` is false until the first mark
34
+ // (mirrors the old `boundary === null`: everything is "engine" pre-mark).
35
+ let boundaryMarked = false;
36
+ const engineFns = new Map();
37
+ for (const phase of PHASE_ORDER)
38
+ engineFns.set(phase, new Set());
39
+ const engineRegistered = new Set();
40
+ /** Run one system function, isolating a throw so it never wedges the frame. */
41
+ function runOne(fn, phase, dt) {
42
+ const label = labels.get(fn) ?? fn.name ?? `anonymous-${++anonymousId}`;
43
+ labels.set(fn, label);
44
+ observer?.beginSystem(scope, phase, label);
45
+ try {
46
+ fn(dt);
47
+ }
48
+ catch (err) {
49
+ const label = fn.name ? `"${fn.name}"` : '(anonymous)';
50
+ console.error(`[system-runner] system ${label} in phase "${phase}" threw:`, err);
51
+ }
52
+ finally {
53
+ observer?.endSystem(scope, phase, label);
54
+ }
55
+ }
56
+ return {
57
+ /**
58
+ * Register a bare system function in a specific phase.
59
+ * Systems within the same phase run in the order they were added.
60
+ */
61
+ add(phase, fn, options) {
62
+ const list = systems.get(phase);
63
+ if (!list) {
64
+ throw new Error(`Unknown phase: ${phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
65
+ }
66
+ list.push(fn);
67
+ labels.set(fn, options?.name ?? fn.name ?? `anonymous-${++anonymousId}`);
68
+ },
69
+ /**
70
+ * Remove a bare system function from a phase. Also drops it from the
71
+ * engine-membership set (if present) — the fix for the length-based
72
+ * bookkeeping bug: removing this has no effect on how any OTHER entry
73
+ * (before or after it) is classified, since classification is now
74
+ * per-function membership, not position.
75
+ */
76
+ remove(phase, fn) {
77
+ const list = systems.get(phase);
78
+ if (!list)
79
+ return;
80
+ const idx = list.indexOf(fn);
81
+ if (idx !== -1)
82
+ list.splice(idx, 1);
83
+ engineFns.get(phase)?.delete(fn);
84
+ },
85
+ /**
86
+ * Register a lifecycle system. Adds its update to the correct phase.
87
+ * Call runInit() after all systems are registered to invoke init hooks.
88
+ */
89
+ register(system) {
90
+ const list = systems.get(system.phase);
91
+ if (!list) {
92
+ throw new Error(`Unknown phase: ${system.phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
93
+ }
94
+ list.push(system.update);
95
+ labels.set(system.update, system.name ?? system.update.name ?? `anonymous-${++anonymousId}`);
96
+ registered.push(system);
97
+ },
98
+ /**
99
+ * Unregister a lifecycle system. Removes its update and calls dispose.
100
+ */
101
+ unregister(system) {
102
+ const list = systems.get(system.phase);
103
+ if (list) {
104
+ const idx = list.indexOf(system.update);
105
+ if (idx !== -1)
106
+ list.splice(idx, 1);
107
+ }
108
+ engineFns.get(system.phase)?.delete(system.update);
109
+ const regIdx = registered.indexOf(system);
110
+ if (regIdx !== -1)
111
+ registered.splice(regIdx, 1);
112
+ engineRegistered.delete(system);
113
+ system.dispose?.();
114
+ },
115
+ /**
116
+ * Call init() on all registered lifecycle systems, in phase order.
117
+ * Call once after scene load, before the first game loop tick.
118
+ */
119
+ runInit() {
120
+ const sorted = [...registered].sort((a, b) => PHASE_ORDER.indexOf(a.phase) - PHASE_ORDER.indexOf(b.phase));
121
+ for (const sys of sorted) {
122
+ sys.init?.();
123
+ }
124
+ },
125
+ /**
126
+ * Call dispose() on all registered lifecycle systems and remove them.
127
+ */
128
+ runDispose() {
129
+ for (const sys of registered) {
130
+ const list = systems.get(sys.phase);
131
+ if (list) {
132
+ const idx = list.indexOf(sys.update);
133
+ if (idx !== -1)
134
+ list.splice(idx, 1);
135
+ }
136
+ sys.dispose?.();
137
+ }
138
+ registered.length = 0;
139
+ },
140
+ /**
141
+ * Run ONE phase's two ordered buckets — engine, game
142
+ * (see the module doc comment). Each system call is isolated: a
143
+ * throwing system is loudly logged (never swallowed) but does not stop
144
+ * its siblings in the same bucket, a later bucket in this phase, or a
145
+ * later phase, from running this frame.
146
+ *
147
+ * Public — the Game root's frame executor (`runtime/game.ts`) calls this
148
+ * directly per (phase, world); `run(dt)` below is just a loop over it,
149
+ * preserved for any direct caller.
150
+ */
151
+ runPhase(phase, dt) {
152
+ const list = systems.get(phase);
153
+ if (!list) {
154
+ throw new Error(`Unknown phase: ${phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
155
+ }
156
+ // Partition by MEMBERSHIP, not position: everything in this phase's
157
+ // `engineFns` set (or, if no boundary has ever been marked, everything)
158
+ // runs immediately, in list order, as it's
159
+ // encountered; everything else is queued into `gameFns` (also in list
160
+ // order) and run after them. Because classification is
161
+ // per-function rather than "index < some remembered length", removing
162
+ // ANY entry via `remove()` — including a pre-boundary "engine" one —
163
+ // cannot shift a later game system into the engine bucket.
164
+ const engineSet = engineFns.get(phase);
165
+ const gameFns = [];
166
+ for (const fn of list) {
167
+ if (boundaryMarked && !engineSet.has(fn)) {
168
+ gameFns.push(fn);
169
+ }
170
+ else {
171
+ runOne(fn, phase, dt);
172
+ }
173
+ }
174
+ for (const fn of gameFns) {
175
+ runOne(fn, phase, dt);
176
+ }
177
+ },
178
+ /**
179
+ * Run all phases in order, via `runPhase`.
180
+ */
181
+ run(dt) {
182
+ for (const phase of PHASE_ORDER) {
183
+ this.runPhase(phase, dt);
184
+ }
185
+ },
186
+ /**
187
+ * Mark everything registered so far (via `add()`/`register()`, in every
188
+ * phase) as "engine" — infrastructure that must survive a warm restart.
189
+ * Call once, right after mount finishes wiring engine-level systems and
190
+ * BEFORE any game (`setup()`/scene load) registers its own. `hotReload`
191
+ * calls `removeAllNonEngine()` on every restart, which only ever removes
192
+ * what was added after this mark.
193
+ */
194
+ markEngineBoundary() {
195
+ for (const phase of PHASE_ORDER) {
196
+ const set = engineFns.get(phase);
197
+ for (const fn of systems.get(phase))
198
+ set.add(fn);
199
+ }
200
+ for (const sys of registered)
201
+ engineRegistered.add(sys);
202
+ boundaryMarked = true;
203
+ },
204
+ /**
205
+ * Bulk-remove every system registered after the last `markEngineBoundary()`
206
+ * call — both bare `add()`ed functions and lifecycle `register()`ed systems
207
+ * (the latter get `dispose()`d, mirroring `unregister()`). A no-op if no
208
+ * boundary has been marked. Idempotent: calling it again with nothing new
209
+ * registered since is a safe no-op.
210
+ *
211
+ * This is what makes a warm restart (`hotReload`) safe to call N times
212
+ * without accumulating duplicate systems/listeners — each restart's
213
+ * outgoing game systems are fully removed before the new one registers its
214
+ * own, while the engine systems (input/physics/render/...) registered
215
+ * before the boundary are never touched.
216
+ */
217
+ removeAllNonEngine() {
218
+ if (!boundaryMarked)
219
+ return;
220
+ // Membership-based, same reasoning as runPhase: partition by whether
221
+ // each entry is in the engine set, not by a remembered length/index.
222
+ const keptRegistered = registered.filter((sys) => engineRegistered.has(sys));
223
+ const removedRegistered = registered.filter((sys) => !engineRegistered.has(sys));
224
+ registered.length = 0;
225
+ registered.push(...keptRegistered);
226
+ for (const sys of removedRegistered) {
227
+ sys.dispose?.();
228
+ }
229
+ for (const phase of PHASE_ORDER) {
230
+ const list = systems.get(phase);
231
+ const engineSet = engineFns.get(phase);
232
+ const kept = list.filter((fn) => engineSet.has(fn));
233
+ list.length = 0;
234
+ list.push(...kept);
235
+ }
236
+ },
237
+ /**
238
+ * Total system-function count, optionally scoped to one phase.
239
+ * Test/introspection helper — used to assert a warm restart doesn't
240
+ * accumulate systems (see `removeAllNonEngine`).
241
+ */
242
+ count(phase) {
243
+ if (phase) {
244
+ return systems.get(phase)?.length ?? 0;
245
+ }
246
+ let total = 0;
247
+ for (const list of systems.values()) {
248
+ total += list.length;
249
+ }
250
+ return total;
251
+ },
252
+ };
253
+ }