lecodes-sdk 0.19.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 (367) hide show
  1. package/README.md +114 -0
  2. package/dist/global.d.ts +503 -0
  3. package/dist/host.d.ts +61 -0
  4. package/dist/inject.js +4730 -0
  5. package/dist/types/animate/animate.d.ts +20 -0
  6. package/dist/types/animate/bezier.d.ts +2 -0
  7. package/dist/types/animate/easings.d.ts +21 -0
  8. package/dist/types/canvas/Canvas.d.ts +168 -0
  9. package/dist/types/core/Aspect.d.ts +171 -0
  10. package/dist/types/core/InspectorUI.d.ts +88 -0
  11. package/dist/types/core/StateMachine.d.ts +81 -0
  12. package/dist/types/core/color.d.ts +13 -0
  13. package/dist/types/core/compWrite.d.ts +11 -0
  14. package/dist/types/core/events.d.ts +11 -0
  15. package/dist/types/core/fields.d.ts +58 -0
  16. package/dist/types/core/registry.d.ts +7 -0
  17. package/dist/types/core/signals.d.ts +29 -0
  18. package/dist/types/core/time.d.ts +31 -0
  19. package/dist/types/g2/Camera2D.d.ts +19 -0
  20. package/dist/types/g2/CharacterController2D.d.ts +134 -0
  21. package/dist/types/g2/Node2D.d.ts +91 -0
  22. package/dist/types/g2/OneWay2D.d.ts +24 -0
  23. package/dist/types/g2/Physics2D.d.ts +147 -0
  24. package/dist/types/g2/Scene2D.d.ts +72 -0
  25. package/dist/types/g2/Shape2D.d.ts +58 -0
  26. package/dist/types/g2/Sprite.d.ts +51 -0
  27. package/dist/types/g2/SpriteAnimation.d.ts +50 -0
  28. package/dist/types/g2/SpriteSheet.d.ts +69 -0
  29. package/dist/types/g2/Texture2D.d.ts +16 -0
  30. package/dist/types/g2/Tilemap.d.ts +20 -0
  31. package/dist/types/g2/Tileset.d.ts +32 -0
  32. package/dist/types/g2/Trigger2D.d.ts +20 -0
  33. package/dist/types/g2/autotile.d.ts +133 -0
  34. package/dist/types/g2/cells.d.ts +14 -0
  35. package/dist/types/g2/defineScene2d.d.ts +141 -0
  36. package/dist/types/g2/groups2d.d.ts +16 -0
  37. package/dist/types/g2/loop.d.ts +4 -0
  38. package/dist/types/g2/scenarios2d.d.ts +30 -0
  39. package/dist/types/g2/touch.d.ts +2 -0
  40. package/dist/types/gl/Camera.d.ts +79 -0
  41. package/dist/types/gl/CameraPlace.d.ts +26 -0
  42. package/dist/types/gl/CharacterController.d.ts +98 -0
  43. package/dist/types/gl/Gearbox.d.ts +86 -0
  44. package/dist/types/gl/Geometry.d.ts +39 -0
  45. package/dist/types/gl/IK.d.ts +53 -0
  46. package/dist/types/gl/InstancedMesh.d.ts +46 -0
  47. package/dist/types/gl/Light.d.ts +73 -0
  48. package/dist/types/gl/Lightmap.d.ts +85 -0
  49. package/dist/types/gl/Locomotion.d.ts +279 -0
  50. package/dist/types/gl/Material.d.ts +100 -0
  51. package/dist/types/gl/Mesh.d.ts +33 -0
  52. package/dist/types/gl/Model.d.ts +53 -0
  53. package/dist/types/gl/NavAgent.d.ts +99 -0
  54. package/dist/types/gl/NavMesh.d.ts +169 -0
  55. package/dist/types/gl/Node.d.ts +99 -0
  56. package/dist/types/gl/Noise.d.ts +20 -0
  57. package/dist/types/gl/Particles.d.ts +277 -0
  58. package/dist/types/gl/Physics.d.ts +91 -0
  59. package/dist/types/gl/Plane.d.ts +19 -0
  60. package/dist/types/gl/Ragdoll.d.ts +86 -0
  61. package/dist/types/gl/Ray.d.ts +8 -0
  62. package/dist/types/gl/Scene.d.ts +221 -0
  63. package/dist/types/gl/Shape.d.ts +122 -0
  64. package/dist/types/gl/Terrain.d.ts +280 -0
  65. package/dist/types/gl/Texture.d.ts +27 -0
  66. package/dist/types/gl/Trigger.d.ts +10 -0
  67. package/dist/types/gl/Vehicle.d.ts +191 -0
  68. package/dist/types/gl/Wheel.d.ts +95 -0
  69. package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
  70. package/dist/types/gl/animation/Animator.d.ts +219 -0
  71. package/dist/types/gl/animation/Layer.d.ts +31 -0
  72. package/dist/types/gl/animation/Loop.d.ts +17 -0
  73. package/dist/types/gl/animation/Playback.d.ts +36 -0
  74. package/dist/types/gl/animation/core.d.ts +367 -0
  75. package/dist/types/gl/controls.d.ts +19 -0
  76. package/dist/types/gl/physicsEvents.d.ts +1 -0
  77. package/dist/types/gl/scenarios.d.ts +98 -0
  78. package/dist/types/gl/state.d.ts +5 -0
  79. package/dist/types/gl/terrainMesh.d.ts +46 -0
  80. package/dist/types/gl/touch.d.ts +2 -0
  81. package/dist/types/inject.d.ts +123 -0
  82. package/dist/types/math/Mathf.d.ts +39 -0
  83. package/dist/types/math/mat4.d.ts +66 -0
  84. package/dist/types/math/quat.d.ts +53 -0
  85. package/dist/types/math/vec.d.ts +129 -0
  86. package/dist/types/net/codec.d.ts +25 -0
  87. package/dist/types/net/core.d.ts +106 -0
  88. package/dist/types/net/index.d.ts +80 -0
  89. package/dist/types/net/replication.d.ts +101 -0
  90. package/dist/types/plugins/camera.d.ts +25 -0
  91. package/dist/types/plugins/geolocation.d.ts +39 -0
  92. package/dist/types/plugins/oauth.d.ts +25 -0
  93. package/dist/types/plugins/permission.d.ts +1 -0
  94. package/dist/types/plugins/push.d.ts +46 -0
  95. package/dist/types/plugins/qr.d.ts +21 -0
  96. package/dist/types/plugins/service.d.ts +33 -0
  97. package/dist/types/runtime/app.d.ts +67 -0
  98. package/dist/types/runtime/appEvents.d.ts +7 -0
  99. package/dist/types/runtime/channel.d.ts +1 -0
  100. package/dist/types/runtime/clipboard.d.ts +7 -0
  101. package/dist/types/runtime/datetime.d.ts +80 -0
  102. package/dist/types/runtime/device.d.ts +126 -0
  103. package/dist/types/runtime/fetch.d.ts +31 -0
  104. package/dist/types/runtime/files.d.ts +33 -0
  105. package/dist/types/runtime/input.d.ts +121 -0
  106. package/dist/types/runtime/media.d.ts +46 -0
  107. package/dist/types/runtime/misc.d.ts +11 -0
  108. package/dist/types/runtime/net.d.ts +15 -0
  109. package/dist/types/runtime/rpc.d.ts +44 -0
  110. package/dist/types/runtime/service.d.ts +1 -0
  111. package/dist/types/runtime/share.d.ts +2 -0
  112. package/dist/types/runtime/storage.d.ts +5 -0
  113. package/dist/types/runtime/touch.d.ts +56 -0
  114. package/dist/types/scene/defineScene.d.ts +296 -0
  115. package/dist/types/scene/editorPlugins.d.ts +82 -0
  116. package/dist/types/scene/gizmos.d.ts +56 -0
  117. package/dist/types/scene/grammar.d.ts +56 -0
  118. package/dist/types/scene/level.d.ts +98 -0
  119. package/dist/types/scene/material.d.ts +76 -0
  120. package/dist/types/server/auth/appConfig.d.ts +16 -0
  121. package/dist/types/server/auth/global.d.ts +56 -0
  122. package/dist/types/server/auth/models.d.ts +164 -0
  123. package/dist/types/server/auth/types.d.ts +56 -0
  124. package/dist/types/server/channel.d.ts +28 -0
  125. package/dist/types/server/context.d.ts +27 -0
  126. package/dist/types/server/db/defineDb.d.ts +36 -0
  127. package/dist/types/server/db/fields.d.ts +146 -0
  128. package/dist/types/server/db/index.d.ts +6 -0
  129. package/dist/types/server/db/marci/query.d.ts +326 -0
  130. package/dist/types/server/db/types.d.ts +163 -0
  131. package/dist/types/server/errors.d.ts +8 -0
  132. package/dist/types/server/inject.d.ts +5 -0
  133. package/dist/types/ui/NativeView.d.ts +38 -0
  134. package/dist/types/ui/UI.d.ts +27 -0
  135. package/dist/types/ui/UIBottomSheet.d.ts +71 -0
  136. package/dist/types/ui/UIButton.d.ts +30 -0
  137. package/dist/types/ui/UIContainer.d.ts +33 -0
  138. package/dist/types/ui/UIImage.d.ts +35 -0
  139. package/dist/types/ui/UIInput.d.ts +110 -0
  140. package/dist/types/ui/UIModal.d.ts +70 -0
  141. package/dist/types/ui/UINode.d.ts +363 -0
  142. package/dist/types/ui/UIPager.d.ts +123 -0
  143. package/dist/types/ui/UIPopover.d.ts +39 -0
  144. package/dist/types/ui/UIScreen.d.ts +57 -0
  145. package/dist/types/ui/UIScrollable.d.ts +40 -0
  146. package/dist/types/ui/UISpacer.d.ts +9 -0
  147. package/dist/types/ui/UITabs.d.ts +96 -0
  148. package/dist/types/ui/UIText.d.ts +19 -0
  149. package/dist/types/ui/UIVideo.d.ts +28 -0
  150. package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
  151. package/dist/types/ui/UIWidget.d.ts +65 -0
  152. package/dist/types/ui/fonts.d.ts +9 -0
  153. package/dist/types/ui/presentable.d.ts +68 -0
  154. package/dist/types/ui/router.d.ts +40 -0
  155. package/dist/types/ui/theme.d.ts +26 -0
  156. package/dist/types.json +1 -0
  157. package/package.json +46 -0
  158. package/prompts/2d.md +395 -0
  159. package/prompts/3d-scene-files.md +113 -0
  160. package/prompts/3d-scene.md +119 -0
  161. package/prompts/3d.md +303 -0
  162. package/prompts/README.md +142 -0
  163. package/prompts/ar.md +109 -0
  164. package/prompts/canvas.md +27 -0
  165. package/prompts/compose.ts +46 -0
  166. package/prompts/concept.md +49 -0
  167. package/prompts/core-design.md +75 -0
  168. package/prompts/core.md +264 -0
  169. package/prompts/design.md +218 -0
  170. package/prompts/dist/2d-game.md +1530 -0
  171. package/prompts/dist/3d-app.md +1672 -0
  172. package/prompts/dist/ar-app.md +1548 -0
  173. package/prompts/dist/concept.md +49 -0
  174. package/prompts/dist/design.md +498 -0
  175. package/prompts/dist/ui-app.md +1134 -0
  176. package/prompts/index.md +37 -0
  177. package/prompts/intake-prompt.md +33 -0
  178. package/prompts/namer-prompt.md +12 -0
  179. package/prompts/router-prompt.md +50 -0
  180. package/prompts/select.ts +111 -0
  181. package/prompts/ui-design.md +203 -0
  182. package/prompts/ui.md +803 -0
  183. package/src/animate/animate.ts +238 -0
  184. package/src/animate/bezier.ts +138 -0
  185. package/src/animate/easings.ts +126 -0
  186. package/src/bridges.d.ts +1345 -0
  187. package/src/canvas/Canvas.ts +305 -0
  188. package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
  189. package/src/compile/__tests__/assetMacro.test.ts +74 -0
  190. package/src/compile/__tests__/assetName.test.ts +55 -0
  191. package/src/compile/__tests__/compile.test.ts +287 -0
  192. package/src/compile/__tests__/detectEntry.test.ts +132 -0
  193. package/src/compile/__tests__/fontMacro.test.ts +199 -0
  194. package/src/compile/aspectMacro.ts +86 -0
  195. package/src/compile/assetIconMacro.ts +384 -0
  196. package/src/compile/assetMacro.ts +146 -0
  197. package/src/compile/assetName.ts +50 -0
  198. package/src/compile/bundler.ts +287 -0
  199. package/src/compile/compileProject.ts +127 -0
  200. package/src/compile/detectEntry.ts +128 -0
  201. package/src/compile/fontMacro.ts +459 -0
  202. package/src/compile/fontRegistry.ts +78 -0
  203. package/src/compile/header.ts +67 -0
  204. package/src/compile/index.ts +100 -0
  205. package/src/compile/libraryImports.ts +52 -0
  206. package/src/compile/liteMaterial.ts +247 -0
  207. package/src/compile/sceneEditor.ts +88 -0
  208. package/src/compile/serverSplit.ts +233 -0
  209. package/src/compile/serverTypes.ts +227 -0
  210. package/src/compile/sfnt.ts +98 -0
  211. package/src/compile/shaderSchema.ts +202 -0
  212. package/src/compile/shaderTargets.ts +77 -0
  213. package/src/compile/sourcemap.ts +25 -0
  214. package/src/core/Aspect.ts +568 -0
  215. package/src/core/InspectorUI.ts +212 -0
  216. package/src/core/StateMachine.ts +308 -0
  217. package/src/core/__tests__/stateMachine.test.ts +132 -0
  218. package/src/core/color.ts +66 -0
  219. package/src/core/compWrite.ts +42 -0
  220. package/src/core/events.ts +38 -0
  221. package/src/core/fields.ts +120 -0
  222. package/src/core/registry.ts +23 -0
  223. package/src/core/signals.ts +277 -0
  224. package/src/core/time.ts +84 -0
  225. package/src/g2/Camera2D.ts +40 -0
  226. package/src/g2/CharacterController2D.ts +276 -0
  227. package/src/g2/Node2D.ts +267 -0
  228. package/src/g2/OneWay2D.ts +66 -0
  229. package/src/g2/Physics2D.ts +346 -0
  230. package/src/g2/Scene2D.ts +209 -0
  231. package/src/g2/Shape2D.ts +259 -0
  232. package/src/g2/Sprite.ts +89 -0
  233. package/src/g2/SpriteAnimation.ts +171 -0
  234. package/src/g2/SpriteSheet.ts +166 -0
  235. package/src/g2/Texture2D.ts +47 -0
  236. package/src/g2/Tilemap.ts +41 -0
  237. package/src/g2/Tileset.ts +71 -0
  238. package/src/g2/Trigger2D.ts +77 -0
  239. package/src/g2/autotile.ts +433 -0
  240. package/src/g2/cells.ts +91 -0
  241. package/src/g2/defineScene2d.ts +381 -0
  242. package/src/g2/groups2d.ts +106 -0
  243. package/src/g2/loop.ts +50 -0
  244. package/src/g2/scenarios2d.ts +69 -0
  245. package/src/g2/touch.ts +83 -0
  246. package/src/gl/Camera.ts +160 -0
  247. package/src/gl/CameraPlace.ts +52 -0
  248. package/src/gl/CharacterController.ts +238 -0
  249. package/src/gl/Gearbox.ts +212 -0
  250. package/src/gl/Geometry.ts +279 -0
  251. package/src/gl/IK.ts +193 -0
  252. package/src/gl/InstancedMesh.ts +132 -0
  253. package/src/gl/Light.ts +135 -0
  254. package/src/gl/Lightmap.ts +402 -0
  255. package/src/gl/Locomotion.ts +498 -0
  256. package/src/gl/Material.ts +279 -0
  257. package/src/gl/Mesh.ts +83 -0
  258. package/src/gl/Model.ts +124 -0
  259. package/src/gl/NavAgent.ts +337 -0
  260. package/src/gl/NavMesh.ts +397 -0
  261. package/src/gl/Node.ts +350 -0
  262. package/src/gl/Noise.ts +30 -0
  263. package/src/gl/Particles.ts +676 -0
  264. package/src/gl/Physics.ts +222 -0
  265. package/src/gl/Plane.ts +53 -0
  266. package/src/gl/Ragdoll.ts +270 -0
  267. package/src/gl/Ray.ts +16 -0
  268. package/src/gl/Scene.ts +505 -0
  269. package/src/gl/Shape.ts +409 -0
  270. package/src/gl/Terrain.ts +1092 -0
  271. package/src/gl/Texture.ts +63 -0
  272. package/src/gl/Trigger.ts +45 -0
  273. package/src/gl/Vehicle.ts +473 -0
  274. package/src/gl/Wheel.ts +240 -0
  275. package/src/gl/animation/AnimationClip.ts +204 -0
  276. package/src/gl/animation/Animator.ts +329 -0
  277. package/src/gl/animation/Layer.ts +39 -0
  278. package/src/gl/animation/Loop.ts +31 -0
  279. package/src/gl/animation/Playback.ts +52 -0
  280. package/src/gl/animation/core.ts +670 -0
  281. package/src/gl/controls.ts +95 -0
  282. package/src/gl/physicsEvents.ts +20 -0
  283. package/src/gl/scenarios.ts +291 -0
  284. package/src/gl/state.ts +6 -0
  285. package/src/gl/terrainMesh.ts +219 -0
  286. package/src/gl/touch.ts +68 -0
  287. package/src/host.d.ts +61 -0
  288. package/src/inject.ts +203 -0
  289. package/src/math/Mathf.ts +118 -0
  290. package/src/math/mat4.ts +278 -0
  291. package/src/math/quat.ts +232 -0
  292. package/src/math/vec.ts +255 -0
  293. package/src/net/codec.ts +119 -0
  294. package/src/net/core.ts +384 -0
  295. package/src/net/index.ts +181 -0
  296. package/src/net/replication.ts +622 -0
  297. package/src/plugins/camera.ts +81 -0
  298. package/src/plugins/geolocation.ts +123 -0
  299. package/src/plugins/oauth.ts +61 -0
  300. package/src/plugins/permission.ts +7 -0
  301. package/src/plugins/push.ts +132 -0
  302. package/src/plugins/qr.ts +73 -0
  303. package/src/plugins/service.ts +47 -0
  304. package/src/runtime/app.ts +101 -0
  305. package/src/runtime/appEvents.ts +54 -0
  306. package/src/runtime/channel.ts +61 -0
  307. package/src/runtime/clipboard.ts +20 -0
  308. package/src/runtime/datetime.ts +329 -0
  309. package/src/runtime/device.ts +293 -0
  310. package/src/runtime/fetch.ts +77 -0
  311. package/src/runtime/files.ts +108 -0
  312. package/src/runtime/input.ts +175 -0
  313. package/src/runtime/media.ts +111 -0
  314. package/src/runtime/misc.ts +16 -0
  315. package/src/runtime/net.ts +36 -0
  316. package/src/runtime/rpc.ts +218 -0
  317. package/src/runtime/service.ts +83 -0
  318. package/src/runtime/share.ts +9 -0
  319. package/src/runtime/storage.ts +13 -0
  320. package/src/runtime/touch.ts +76 -0
  321. package/src/scene/defineScene.ts +1396 -0
  322. package/src/scene/editorPlugins.ts +110 -0
  323. package/src/scene/gizmos.ts +148 -0
  324. package/src/scene/grammar.ts +120 -0
  325. package/src/scene/level.ts +296 -0
  326. package/src/scene/material.ts +188 -0
  327. package/src/server/auth/appConfig.ts +12 -0
  328. package/src/server/auth/global.ts +80 -0
  329. package/src/server/auth/host.ts +318 -0
  330. package/src/server/auth/models.ts +83 -0
  331. package/src/server/auth/types.ts +50 -0
  332. package/src/server/channel.ts +56 -0
  333. package/src/server/context.ts +36 -0
  334. package/src/server/db/defineDb.ts +237 -0
  335. package/src/server/db/fields.ts +132 -0
  336. package/src/server/db/httpTransport.ts +93 -0
  337. package/src/server/db/index.ts +7 -0
  338. package/src/server/db/marci/query.ts +412 -0
  339. package/src/server/db/types.ts +202 -0
  340. package/src/server/errors.ts +12 -0
  341. package/src/server/host.ts +74 -0
  342. package/src/server/inject.ts +13 -0
  343. package/src/server/runtime.ts +133 -0
  344. package/src/server/validate.ts +87 -0
  345. package/src/ui/NativeView.ts +142 -0
  346. package/src/ui/UI.ts +39 -0
  347. package/src/ui/UIBottomSheet.ts +139 -0
  348. package/src/ui/UIButton.ts +101 -0
  349. package/src/ui/UIContainer.ts +60 -0
  350. package/src/ui/UIImage.ts +83 -0
  351. package/src/ui/UIInput.ts +185 -0
  352. package/src/ui/UIModal.ts +139 -0
  353. package/src/ui/UINode.ts +830 -0
  354. package/src/ui/UIPager.ts +362 -0
  355. package/src/ui/UIPopover.ts +100 -0
  356. package/src/ui/UIScreen.ts +123 -0
  357. package/src/ui/UIScrollable.ts +87 -0
  358. package/src/ui/UISpacer.ts +14 -0
  359. package/src/ui/UITabs.ts +236 -0
  360. package/src/ui/UIText.ts +51 -0
  361. package/src/ui/UIVideo.ts +88 -0
  362. package/src/ui/UIVirtualizedList.ts +241 -0
  363. package/src/ui/UIWidget.ts +127 -0
  364. package/src/ui/fonts.ts +13 -0
  365. package/src/ui/presentable.ts +117 -0
  366. package/src/ui/router.ts +132 -0
  367. package/src/ui/theme.ts +84 -0
@@ -0,0 +1,568 @@
1
+ // The Aspect system: attachable capabilities (animation, collider, physics, and user behaviors)
2
+ // that hang off a node WITHOUT bloating the node class. Composition with the terseness of methods.
3
+ //
4
+ // const hero = new Sprite({ texture })
5
+ // .aspect(SpriteAnimation, { fps: 8, clips: { walk: [1, 2, 3] } }) // attach + configure
6
+ // .aspect(Shape2D, { box: [16, 8] }) // chains: returns the node
7
+ // hero.anim.play('walk') // access by name (the chain result is typed as having it)
8
+ // if (hero.has(Physics)) hero.physics.velocity = [0, 0] // has() is a type guard
9
+ //
10
+ // A user aspect declares its name + target node in the generic and reads `this.node`:
11
+ // class Health extends Aspect<'health', Sprite> { hp = 100; hurt(n: number) { this.hp -= n } }
12
+ // sprite.aspect(Health, { hp: 80 }).health.hurt(10)
13
+ //
14
+ // SYSTEMS are the same thing attached to a SCENE instead of a node — game logic that has no single
15
+ // node to live on (input mapping, FX pools, the HUD, the game mode): `scene.system(Fx)` → `scene.fx`.
16
+ // They share the lifecycle, the update phases, the ordering and the events of aspects (see System).
17
+ //
18
+ // The accessor name lives in the generic (Aspect<'name', Node>). The `chisel` bundler extracts it at
19
+ // compile time and injects the runtime `static aspect` on user aspects + the virtual type accessor;
20
+ // the SDK's own built-in aspects declare `static readonly aspect` directly (see SpriteAnimation etc.).
21
+
22
+ import type { Scene } from "../gl/Scene"
23
+ import { Emitter, type EventMap } from "./events"
24
+ import type { InspectorUI } from "./InspectorUI"
25
+ import type { Node } from "../gl/Node"
26
+ import { Time, _registerTimeScaleSink } from "./time"
27
+
28
+ // ---- type helpers -----------------------------------------------------------
29
+ /** The class object of an aspect — accepted even when its constructor is protected/abstract. */
30
+ export type AspectCtor<A extends Aspect<any, any, any>> = abstract new () => A
31
+ /** The accessor name carried in the aspect's generic. */
32
+ export type KeyOf<A> = A extends Aspect<infer K, any, any> ? K : never
33
+ /** The node kind an aspect targets (the scene kind for a System). */
34
+ export type TargetOf<A> = A extends Aspect<any, infer P, any> ? P : never
35
+ /** `{ name: Aspect }` — the field an aspect contributes to its node. */
36
+ export type FieldOf<A extends Aspect<any, any, any>> = { [P in KeyOf<A>]: A }
37
+
38
+ type UnionToIntersection<U> =
39
+ (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
40
+
41
+ /**
42
+ * A variable typed as a node KNOWN to carry the given aspect(s). Union, not a tuple — reads as
43
+ * English and mirrors the runtime guard `node.has(Health)`:
44
+ * let boss: With<Sprite, Health | Physics>
45
+ */
46
+ export type With<N, A extends Aspect<any, any, any>> =
47
+ N & UnionToIntersection<A extends any ? FieldOf<A> : never>
48
+
49
+ // ---- per-frame update dispatch (two phases) ---------------------------------
50
+ // Aspects tick every frame in one of two render-synced phases, chosen by WHICH METHOD they define:
51
+ // • `update(dt)` — LATE: after the physics step + transform sync + animators, right before the frame
52
+ // draws. Reads of node.worldPosition are the FINAL drawn position — cameras/followers land exactly,
53
+ // what you write to a plain node is what this frame shows. The default home for game logic.
54
+ // • `updateBefore(dt)` — EARLY: before the physics step, so writes of velocity / move commands /
55
+ // kinematic transforms are consumed by the SAME frame's step (zero input latency).
56
+ // An aspect rarely needs both; when a behaviour does (feed the sim, then pose something after the
57
+ // animators), it is two aspects on the same node — not one class in two phases.
58
+ //
59
+ // The FRAME SOURCE is pluggable so core never names a specific engine bridge (a 3D-only bundle must not
60
+ // pull in the 2D one): each engine layer installs a render-synced source via _installAspectFrames (the
61
+ // native early/late hooks), so dispatch is in lockstep with the draw. With none installed (a runtime
62
+ // without the hooks) it falls back to a single host setLoop, early-then-late — no true post-physics slot.
63
+ //
64
+ // ORDER within a phase: declared constraints first (`static after = [Controls]` / `static before`),
65
+ // then the numeric `updateOrder` (ascending, default 0), then attach order. Constraints are edges between
66
+ // classes: an aspect ranks after everything it names in `after` and after everything that names it in
67
+ // `before`; a class with no constraints has rank 0. `updateOrder` only breaks ties inside a rank — so a
68
+ // declared dependency always wins over a number.
69
+ //
70
+ // `dt` is GAME time (core/time.ts): scaled by Time.scale, 0 while Time.paused — and a paused frame skips
71
+ // every updater except those with `updateWhilePaused = true`. Lazy: the first update-bearing aspect installs
72
+ // the dispatch; a project with none starts nothing. No visibility culling yet.
73
+ // The dispatcher's view of an instance: the lifecycle members are `protected` on Aspect (the engine calls
74
+ // them, game code never does), so core reaches them through this structural type.
75
+ interface Updatable {
76
+ _phaseSeq: number
77
+ _phases: number
78
+ updateOrder: number
79
+ updateWhilePaused: boolean
80
+ update?(dt: number): void
81
+ updateBefore?(dt: number): void
82
+ updateFixed?(dt: number): void
83
+ onAttach?(): void
84
+ onDetach?(): void
85
+ onReconfigure?(): void
86
+ }
87
+ const internals = (a: Aspect<any, any, any>): Updatable => a as unknown as Updatable
88
+ type PhaseFn = (dt: number) => void
89
+ type Ctor = Function & { after?: Function[], before?: Function[], aspect?: string }
90
+
91
+ // A render-synced frame source: given the two phase runners, wire them to the engine and return true;
92
+ // return false if unavailable (→ setLoop fallback). Installed by the engine layers (Scene / Scene2D).
93
+ type FrameInstaller = (early: PhaseFn, late: PhaseFn) => boolean
94
+ // The FIXED-phase source: wire `fixed` to the engine's per-substep hook and return true; false when the
95
+ // host has no hook (older builds, 2D-only engines) -> the dispatcher steps its own 1/60 accumulator.
96
+ type FixedInstaller = (fixed: PhaseFn) => boolean
97
+ let frameInstaller: FrameInstaller | undefined
98
+ let fixedInstaller: FixedInstaller | undefined
99
+ /** @internal Install a render-synced frame source for aspect update(dt) (+ the fixed-step source). Called from an engine layer. */
100
+ export const _installAspectFrames = (fn: FrameInstaller, fixed?: FixedInstaller): void => { frameInstaller = fn; fixedInstaller = fixed }
101
+
102
+ /** @internal Engine-level phase hooks (the Net system, sdk/src/net): FIRST in the early and fixed
103
+ * phases, LAST in the late one, ahead of / behind every game aspect regardless of ordering
104
+ * constraints. Single slot each; raw dt. */
105
+ export const _phaseHooks: { early: PhaseFn | null; fixed: PhaseFn | null; late: PhaseFn | null } = { early: null, fixed: null, late: null }
106
+
107
+ let attachSeq = 0
108
+ const warned = new Set<string>()
109
+ const warnOnce = (msg: string): void => { if (!warned.has(msg)) { warned.add(msg); console.warn(msg) } }
110
+
111
+ /** One phase's updater list. Iterated LIVE (no per-frame copy): removals during a pass null the slot
112
+ * and compact afterwards; additions land past the pass's end and tick from the next frame. Sorting
113
+ * is lazy — re-ranked before the next pass after any add. */
114
+ class PhaseList {
115
+ readonly items: (Updatable | null)[] = []
116
+ private iterating = false
117
+ private dirty = false
118
+ private holes = false
119
+
120
+ add(a: Updatable, phase: "update" | "updateBefore" | "updateFixed"): void {
121
+ a._phaseSeq = ++attachSeq
122
+ this.items.push(a)
123
+ this.dirty = true
124
+ a._phases |= phase === "update" ? 2 : phase === "updateFixed" ? 4 : 1
125
+ }
126
+ remove(a: Updatable): boolean {
127
+ const i = this.items.indexOf(a)
128
+ if (i < 0) return false
129
+ if (this.iterating) { this.items[i] = null; this.holes = true }
130
+ else this.items.splice(i, 1)
131
+ return true
132
+ }
133
+ run(method: "update" | "updateBefore" | "updateFixed", dt: number): void {
134
+ if (this.dirty) this.sort()
135
+ const items = this.items
136
+ const n = items.length // appended during the pass → next frame (attach-order snapshot semantics)
137
+ const paused = Time.paused
138
+ this.iterating = true
139
+ for (let i = 0; i < n; i++) {
140
+ const a = items[i]
141
+ if (a === null || (paused && !a.updateWhilePaused)) continue
142
+ a[method]!(dt)
143
+ }
144
+ this.iterating = false
145
+ if (this.holes) {
146
+ this.holes = false
147
+ let w = 0
148
+ for (let r = 0; r < items.length; r++) { const a = items[r]; if (a !== null) items[w++] = a }
149
+ items.length = w
150
+ }
151
+ }
152
+ /** Stable sort by (constraint rank, updateOrder, attach sequence). */
153
+ private sort(): void {
154
+ this.dirty = false
155
+ const ranks = rankConstraints(this.items)
156
+ this.items.sort((x, y) => {
157
+ if (x === null || y === null) return x === null ? (y === null ? 0 : 1) : -1
158
+ const rx = ranks.get(x.constructor) ?? 0, ry = ranks.get(y.constructor) ?? 0
159
+ if (rx !== ry) return rx - ry
160
+ if (x.updateOrder !== y.updateOrder) return x.updateOrder - y.updateOrder
161
+ return x._phaseSeq - y._phaseSeq
162
+ })
163
+ }
164
+ }
165
+
166
+ // Constraint rank per class among the classes present in one phase list: rank = 0 with no incoming
167
+ // edge, else 1 + max(rank of predecessors). Edges: C after D (from C.after or D.before). A cycle is
168
+ // warned once and its closing edge ignored; a constraint naming a class not present in this phase is
169
+ // simply unconstrained here (early always precedes late anyway).
170
+ const rankConstraints = (items: (Updatable | null)[]): Map<Function, number> => {
171
+ const present = new Set<Function>()
172
+ for (const a of items) if (a) present.add(a.constructor)
173
+ const preds = new Map<Function, Set<Function>>()
174
+ const edge = (before: Function, after: Function): void => {
175
+ if (!present.has(before) || !present.has(after) || before === after) return
176
+ let s = preds.get(after)
177
+ if (!s) preds.set(after, (s = new Set()))
178
+ s.add(before)
179
+ }
180
+ for (const c of present) {
181
+ for (const d of (c as Ctor).after ?? []) edge(d, c)
182
+ for (const d of (c as Ctor).before ?? []) edge(c, d)
183
+ }
184
+ if (preds.size === 0) return new Map()
185
+ const rank = new Map<Function, number>()
186
+ const visiting = new Set<Function>()
187
+ const rankOf = (c: Function): number => {
188
+ const known = rank.get(c)
189
+ if (known !== undefined) return known
190
+ if (visiting.has(c)) {
191
+ warnOnce(`[aspects] ordering cycle through ${c.name || "?"} — its constraint is ignored`)
192
+ return 0
193
+ }
194
+ visiting.add(c)
195
+ let r = 0
196
+ for (const p of preds.get(c) ?? []) r = Math.max(r, rankOf(p) + 1)
197
+ visiting.delete(c)
198
+ rank.set(c, r)
199
+ return r
200
+ }
201
+ for (const c of present) rankOf(c)
202
+ return rank
203
+ }
204
+
205
+ const earlyUpdaters = new PhaseList()
206
+ const lateUpdaters = new PhaseList()
207
+ const fixedUpdaters = new PhaseList()
208
+ let dispatchInstalled = false
209
+ let fixedInstalled = false
210
+ let fixedFallbackOn = false
211
+ let fixedFallbackAcc = 0
212
+
213
+ // The FIXED phase: dt = Time.fixedDt (1/60) every call, once per physics substep, on every host. The
214
+ // engine drives it from inside its substep loop when it has the hook; otherwise (older wasm / Apple /
215
+ // Android builds, the setLoop path) the SDK steps its own accumulator right after the early phase -
216
+ // same step, same 4-substep cap, same drop-the-backlog rule as creator-gl's physics/world.cpp.
217
+ const fixedStep: PhaseFn = (dt) => { _phaseHooks.fixed?.(dt); fixedUpdaters.run("updateFixed", dt) }
218
+ const fixedFallback = (): void => {
219
+ const step = Time.fixedDt
220
+ fixedFallbackAcc += Time.dt // scaled: paused -> no steps, slow-motion -> fewer steps, never a shorter dt
221
+ let steps = 0
222
+ while (fixedFallbackAcc >= step && steps < 4) { fixedStep(step); fixedFallbackAcc -= step; steps++ }
223
+ if (steps === 4) fixedFallbackAcc = 0
224
+ }
225
+
226
+ const ensureDispatch = (): void => {
227
+ if (dispatchInstalled) return
228
+ dispatchInstalled = true
229
+ // Both phases scale their OWN raw dt (the engine hands the same value to both; a host or test that
230
+ // drives them apart still gets each phase's dt right). The frame counter / clocks advance once, early.
231
+ const early: PhaseFn = (dt) => {
232
+ Time._beginFrame(dt)
233
+ _phaseHooks.early?.(dt)
234
+ earlyUpdaters.run("updateBefore", Time.dt)
235
+ if (fixedFallbackOn) fixedFallback()
236
+ }
237
+ const late: PhaseFn = (dt) => { lateUpdaters.run("update", Time._phaseDt(dt)); _phaseHooks.late?.(dt) }
238
+ // render-synced source if an engine layer installed one; else one host loop (early then late).
239
+ if (!(frameInstaller && frameInstaller(early, late))) {
240
+ setLoop((dt) => { early(dt); late(dt) })
241
+ }
242
+ }
243
+ // Registered lazily - an app with no `updateFixed` never makes the engine call into JS per substep.
244
+ const ensureFixedDispatch = (): void => {
245
+ if (fixedInstalled) return
246
+ fixedInstalled = true
247
+ ensureDispatch()
248
+ if (!(fixedInstaller && fixedInstaller(fixedStep))) fixedFallbackOn = true
249
+ }
250
+
251
+ /** @internal Force the frame dispatch on (the Net system pumps from the phase hooks even in an app
252
+ * with no aspects) — and the fixed phase with it, since Net.tick is a fixed-step count. */
253
+ export const _ensureNetDispatch = (): void => { ensureDispatch(); ensureFixedDispatch() }
254
+
255
+ const registerUpdater = (inst: Aspect<any, any, any>): void => {
256
+ const a = internals(inst)
257
+ const hasBefore = typeof a.updateBefore === "function"
258
+ const hasUpdate = typeof a.update === "function"
259
+ const hasFixed = typeof a.updateFixed === "function"
260
+ if (!hasBefore && !hasUpdate && !hasFixed) return
261
+ if (hasBefore) earlyUpdaters.add(a, "updateBefore")
262
+ if (hasUpdate) lateUpdaters.add(a, "update")
263
+ if (hasFixed) fixedUpdaters.add(a, "updateFixed")
264
+ ensureDispatch()
265
+ if (hasFixed) ensureFixedDispatch()
266
+ }
267
+ const unregisterUpdater = (inst: Aspect<any, any, any>): void => {
268
+ const a = internals(inst)
269
+ if (a._phases === 0) return
270
+ earlyUpdaters.remove(a)
271
+ lateUpdaters.remove(a)
272
+ fixedUpdaters.remove(a)
273
+ a._phases = 0
274
+ }
275
+
276
+ // ---- per-class instance registry (`Aspect.all(Ctor)`) -----------------------
277
+ const instances = new Map<Function, Aspect<any, any, any>[]>()
278
+ const trackInstance = (a: Aspect<any, any, any>): void => {
279
+ let list = instances.get(a.constructor)
280
+ if (!list) instances.set(a.constructor, (list = []))
281
+ list.push(a)
282
+ }
283
+ const untrackInstance = (a: Aspect<any, any, any>): void => {
284
+ const list = instances.get(a.constructor)
285
+ if (!list) return
286
+ const i = list.indexOf(a)
287
+ if (i >= 0) list.splice(i, 1)
288
+ }
289
+
290
+ // ---- shared attach / detach (nodes AND scenes) ------------------------------
291
+ // `host` receives the named accessor and becomes `inst.node`; `store` is the class → instance map
292
+ // (a node's `_aspects`, a scene's `_systems`).
293
+ type Store = Map<Function, Aspect<any, any, any>>
294
+
295
+ const attachTo = <Self, A extends Aspect<any, any, any>>(
296
+ host: Self, store: Store, ctor: AspectCtor<A>, opts: Partial<A> | undefined, scene: unknown,
297
+ ): Self & FieldOf<A> => {
298
+ let inst = store.get(ctor) as A | undefined
299
+ const fresh = inst === undefined
300
+ if (!inst) {
301
+ inst = new (ctor as unknown as new () => A)()
302
+ ;(inst as { node: unknown }).node = host
303
+ if (scene !== undefined) (inst as { scene: unknown }).scene = scene
304
+ store.set(ctor, inst)
305
+ const name = (ctor as Ctor).aspect
306
+ if (name) (host as unknown as Record<string, unknown>)[name] = inst
307
+ }
308
+ if (opts) Object.assign(inst, opts)
309
+ if (fresh) {
310
+ trackInstance(inst)
311
+ internals(inst).onAttach?.()
312
+ registerUpdater(inst)
313
+ } else if (opts) internals(inst).onReconfigure?.()
314
+ return host as Self & FieldOf<A>
315
+ }
316
+
317
+ const detachFrom = (host: unknown, store: Store, ctor: Function): void => {
318
+ const inst = store.get(ctor)
319
+ if (!inst) return
320
+ internals(inst).onDetach?.()
321
+ unregisterUpdater(inst)
322
+ untrackInstance(inst)
323
+ inst._clearEvents()
324
+ store.delete(ctor)
325
+ const name = (ctor as Ctor).aspect
326
+ if (name) delete (host as Record<string, unknown>)[name]
327
+ }
328
+
329
+ /** Detach every aspect of a store, last-attached first (a Physics attached after its Shape releases
330
+ * its body before the Shape goes). */
331
+ const detachAllFrom = (host: unknown, store: Store): void => {
332
+ const ctors = [...store.keys()]
333
+ for (let i = ctors.length - 1; i >= 0; i--) detachFrom(host, store, ctors[i])
334
+ }
335
+
336
+ // ---- the host: mixed into every node kind (Node2D, Node) --------------------
337
+ /**
338
+ * Base for anything that can carry aspects. Extends Emitter, so node kinds get events too. Provides
339
+ * the four verbs; the named accessor (node.physics, node.health, …) is set as an own property at
340
+ * attach time, keyed by the aspect class's runtime `aspect` name.
341
+ */
342
+ export abstract class AspectHost<E extends EventMap = EventMap> extends Emitter<E> {
343
+ /** @internal class → instance, the authoritative store (named accessors mirror this). */
344
+ readonly _aspects = new Map<Function, Aspect<any, any, any>>()
345
+
346
+ /** Attach (and configure) an aspect, or reconfigure it if already present. Returns the node typed
347
+ * as now-having that aspect (so no guard/`?.` is needed afterwards). Rejects a wrong target node. */
348
+ aspect<Self extends TargetOf<A>, A extends Aspect<any, any, any>>(
349
+ this: Self,
350
+ ctor: AspectCtor<A>,
351
+ opts?: Partial<A>,
352
+ ): Self & FieldOf<A> {
353
+ return attachTo(this, (this as unknown as AspectHost)._aspects, ctor, opts, undefined)
354
+ }
355
+
356
+ /** Safe access — undefined if the aspect isn't attached. */
357
+ get<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): A | undefined {
358
+ return this._aspects.get(ctor) as A | undefined
359
+ }
360
+
361
+ /** Existence check AND type guard: inside `if (node.has(Physics))`, `node.physics` is present. */
362
+ has<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this is this & FieldOf<A> {
363
+ return this._aspects.has(ctor)
364
+ }
365
+
366
+ /** Detach an aspect (runs its onDetach). Named `removeAspect` so it never collides with the
367
+ * child-management `add`/`remove` semantics some node kinds expose. */
368
+ removeAspect<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this {
369
+ detachFrom(this, this._aspects, ctor)
370
+ return this
371
+ }
372
+
373
+ /** @internal Detach every aspect (node teardown): onDetach, updaters, events, registries. */
374
+ _detachAll(): void {
375
+ detachAllFrom(this, this._aspects)
376
+ this.clearListeners()
377
+ }
378
+ }
379
+
380
+ // ---- systems: aspects of a scene --------------------------------------------
381
+ /** @internal What a scene needs to carry systems. Scene / Scene2D implement it over these helpers. */
382
+ export type SystemHost = { readonly _systems: Map<Function, Aspect<any, any, any>> }
383
+
384
+ /** @internal `scene.system(Ctor, opts)`. */
385
+ export const _attachSystem = <Self extends SystemHost, A extends Aspect<any, any, any>>(
386
+ scene: Self, ctor: AspectCtor<A>, opts?: Partial<A>,
387
+ ): Self & FieldOf<A> => attachTo(scene, scene._systems, ctor, opts, scene)
388
+ /** @internal `scene.removeSystem(Ctor)`. */
389
+ export const _detachSystem = (scene: SystemHost, ctor: Function): void => detachFrom(scene, scene._systems, ctor)
390
+ /** @internal Scene teardown: every system, last-attached first. */
391
+ export const _detachAllSystems = (scene: SystemHost): void => detachAllFrom(scene, scene._systems)
392
+
393
+ /** @internal Engine layers call this once with their bridge's clock hook (feature-detected). */
394
+ export const _installTimeScale = (sink: (scale: number) => void): void => _registerTimeScaleSink(sink)
395
+
396
+ // ---- the aspect base --------------------------------------------------------
397
+ /**
398
+ * Base for everything you attach. `K` = accessor name (extracted by chisel for the runtime). `P` =
399
+ * the target node kind: `this.node` is typed to it, AND attaching to a wrong node is a compile error.
400
+ * `E` = the events this aspect emits (`{ explode: (b: Barrel) => void }`): `this.emit('explode', …)`
401
+ * inside, `node.barrel.on('explode', fn)` outside.
402
+ * Initialize in `onAttach` (the node is set by then) — not a constructor; aspects are created by the
403
+ * engine via `node.aspect()`, never `new`.
404
+ */
405
+ export abstract class Aspect<K extends string, P = Node, E extends EventMap = {}> {
406
+ /**
407
+ * Opt this aspect class into EDITOR-RUN mode (generators): while a scene is edited, the scene
408
+ * loader constructs the instance (refs resolved, `node`/`generated` set — never `onAttach`) and
409
+ * calls `rebuild()` — again on inspector prop edits and when a `ref()`-referenced node moves.
410
+ * `static editor = { rebuild: true }`.
411
+ */
412
+ static editor?: { rebuild?: boolean }
413
+
414
+ /**
415
+ * Custom inspector card for this aspect in the scene editor (immediate-mode — see
416
+ * core/InspectorUI.ts): re-runs on every edit/event and emits a widget list. Without it, the
417
+ * editor shows the inferred fields; `ui.auto()` emits those same fields, so a custom inspector
418
+ * usually starts with `ui.auto()` and appends status lines / buttons / dynamic dropdowns:
419
+ *
420
+ * static inspector(ui: InspectorUI, self: MyAspect) {
421
+ * ui.auto()
422
+ * if (ui.button('Randomize')) self.rebuild()
423
+ * }
424
+ */
425
+ static inspector?: (ui: InspectorUI, aspect: any) => void
426
+
427
+ /** Tick AFTER these classes within the same phase (`static after = [Controls]`). A constraint
428
+ * always beats the numeric `updateOrder`. Classes absent from the phase are ignored. */
429
+ static after?: Function[]
430
+ /** Tick BEFORE these classes within the same phase (`static before = [Camera]`). */
431
+ static before?: Function[]
432
+
433
+ /** Every live instance of an aspect/system class, in attach order — the registry a game used to
434
+ * hand-roll as `static all[]`. Live, read-only: copy it (`[...Aspect.all(Barrel)]`) before a loop
435
+ * that detaches. Empty in the scene editor (aspects are inert data there). */
436
+ static all<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): readonly A[] {
437
+ return (instances.get(ctor) ?? []) as unknown as readonly A[]
438
+ }
439
+
440
+ // NOTE: P has no `extends AspectHost` constraint on purpose — Emitter<E>'s generic
441
+ // addEventListener makes a specific-event node (AspectHost<NodeEvents>) not assignable to
442
+ // AspectHost<any>, which would reject every real node. P only types `this.node`; target
443
+ // enforcement happens in aspect() via `this: Self extends TargetOf<A>`. Defaults to Node
444
+ // (3D is the default engine) — a 2D aspect declares its own P, e.g. Aspect<'x', Sprite>.
445
+ /** The node this aspect is attached to (set at attach time). For a System: the scene. */
446
+ readonly node!: P
447
+ /**
448
+ * GENERATOR aspects (`static editor = { rebuild: true }`, attached through a scene file): a
449
+ * scene-added container child for the aspect's generated output — `rebuild()` clears and refills
450
+ * it. Provided by the scene loader before `onAttach`/`rebuild` run; nodes `add()`ed to it join
451
+ * the scene's draw set automatically (membership is separate from parenting). Undefined for
452
+ * aspects attached by hand outside scene files. See docs/3d/scene-files.md.
453
+ */
454
+ readonly generated!: Node & { clear(): void, readonly version: number }
455
+ /**
456
+ * GENERATOR aspects attached through a scene file: the `Scene` the host node was built into —
457
+ * what `handle.instantiate({ scene })` / `scene.add()` need inside `rebuild()`. Set by the scene
458
+ * loader (edit AND play mode); undefined for aspects attached by hand. 3D only (2D aspects own
459
+ * their `scene` field). For a System: always set — the scene it is attached to.
460
+ */
461
+ readonly scene!: P extends Node ? Scene : P extends SystemHost ? P : unknown
462
+ // Phantom fields — they let the type system recover `K` (the accessor name) and `E` from an
463
+ // instance type (`KeyOf<A>`), never read at runtime. Deliberately NOT marked as internal-only:
464
+ // build-types emits with `stripInternal` (which matches that tag in ANY leading comment, `//`
465
+ // included), and without these in the public .d.ts `infer K` collapses to `string`, which turned
466
+ // every `node.<accessor>` / `scene.<accessor>` into an untyped index signature in projects.
467
+ declare protected readonly __key__?: K
468
+ declare protected readonly __events__?: E
469
+ /** @internal attach sequence within the phase lists (tie-breaker). */
470
+ _phaseSeq = 0
471
+ /** @internal bit 1 = registered early, bit 2 = registered late. */
472
+ _phases = 0
473
+ private _listeners?: Map<string, Function[]>
474
+
475
+ // ---- lifecycle: the engine calls these, game code never does (hence `protected`; a subclass may
476
+ // still widen one to public when a method doubles as an explicit action). ----
477
+ /** The node (or scene) is set — initialize here, not in a constructor. */
478
+ protected onAttach?(): void
479
+ /** Release what onAttach acquired. */
480
+ protected onDetach?(): void
481
+ /** Called after `node.aspect(Ctor, opts)` re-assigns options on an ALREADY attached aspect (a
482
+ * pre-attached one like `model.anim`): rebuild whatever was derived from the options at attach. */
483
+ protected onReconfigure?(): void
484
+ /**
485
+ * GENERATOR aspects: (re)build the derived output under `this.generated` — must be idempotent
486
+ * (clear, then create). Call it from `onAttach()` for play mode; a class opting in with
487
+ * `static editor = { rebuild: true }` ALSO runs it while a scene is being edited: once at load,
488
+ * and again whenever an inspector prop changes or a `ref()`-referenced node moves.
489
+ */
490
+ rebuild?(): void
491
+ /**
492
+ * LATE phase — every frame while attached, after the physics step + transform sync + animators,
493
+ * right before the frame draws: reads of `node.worldPosition` are the final drawn position
494
+ * (cameras/followers have no 1-frame lag) and what you write to a plain node is what this frame
495
+ * shows. The default home for game logic. `dt` = GAME seconds since the last frame (`Time.scale`
496
+ * applied; 0 while paused — see `updateWhilePaused`).
497
+ */
498
+ protected update?(dt: number): void
499
+ /**
500
+ * EARLY phase — every frame BEFORE the physics step, so what you feed the simulation (velocity,
501
+ * `controller.move()`, forces, kinematic transforms) is consumed by this same frame's step: zero
502
+ * input latency. Use it only when you FEED the simulation. Reads here see last frame's settled
503
+ * state. A class rarely needs both phases — that is two aspects on one node.
504
+ */
505
+ protected updateBefore?(dt: number): void
506
+ /**
507
+ * FIXED phase - once per physics substep, BEFORE that substep's Jolt step, with `dt = Time.fixedDt`
508
+ * (1/60) exactly, on every machine: under `Time.scale` the NUMBER of steps per frame changes, never
509
+ * the dt (0-4 calls per frame). The home of simulation that must match another machine's - a
510
+ * multiplayer player sim, dt-dependent motion that may not drift with the frame rate. `controller.move()`
511
+ * / velocity writes here feed the same step; between steps the world is consistent, so `Physics.raycast`
512
+ * is fine. Anything that looks at the screen (cameras, HUD, FX) stays in `updateBefore` / `update`.
513
+ */
514
+ protected updateFixed?(dt: number): void
515
+
516
+ /** Tick order within a phase and rank — ascending; default 0, ties keep attach order. A declared
517
+ * `static after` / `static before` constraint always beats this number. Read once, at attach —
518
+ * set it as a class field. */
519
+ protected updateOrder = 0
520
+ /** Keep ticking while `Time.paused` (a HUD fade, the pause menu). `dt` is still 0 then — read
521
+ * `Time.unscaledDt` for wall-clock motion. Checked every frame; may be toggled at any time. */
522
+ protected updateWhilePaused = false
523
+
524
+ /** Opt-in: only run update(dt) while the node is on-screen. NOOP for now — visibility culling isn't
525
+ * wired yet, so every updater ticks regardless; declared so aspects can opt in ahead of it. */
526
+ protected updateWhenVisible = false
527
+
528
+ // ---- events: what this aspect tells the world (`E` in the generic) ----
529
+ /** Listen to one of this aspect's events. Chainable. Cleared on detach. */
530
+ on<C extends keyof E & string>(channel: C, callback: E[C]): this {
531
+ const map = (this._listeners ??= new Map())
532
+ const list = map.get(channel)
533
+ if (list) list.push(callback)
534
+ else map.set(channel, [callback])
535
+ return this
536
+ }
537
+ /** Remove a listener added with `on` (same function reference). */
538
+ off<C extends keyof E & string>(channel: C, callback: E[C]): this {
539
+ const list = this._listeners?.get(channel)
540
+ if (list) { const i = list.indexOf(callback); if (i >= 0) list.splice(i, 1) }
541
+ return this
542
+ }
543
+ /** Fire one of this aspect's events. Protected: only the aspect itself emits. */
544
+ protected emit<C extends keyof E & string>(channel: C, ...args: Parameters<E[C]>): void {
545
+ const list = this._listeners?.get(channel)
546
+ if (!list || list.length === 0) return
547
+ for (const fn of list.slice()) fn(...args)
548
+ }
549
+ /** @internal */
550
+ _clearEvents(): void { this._listeners = undefined }
551
+ }
552
+
553
+ /**
554
+ * An aspect of a SCENE: game logic with no single node to live on — input mapping, the FX pools, the
555
+ * HUD, the game mode. Same lifecycle (`onAttach`/`onDetach`), same phases (`updateFixed` / `updateBefore` /
556
+ * `update`), same ordering (`updateOrder`, `static after`) and events as a node aspect; `this.scene` is the
557
+ * scene it was attached to. `S` = the scene kind (`Scene` by default, `Scene2D` for 2D games).
558
+ *
559
+ * class Hud extends System<'hud'> {
560
+ * static after = [Player]
561
+ * update(dt: number) { … }
562
+ * }
563
+ * scene.system(Hud) // attach
564
+ * scene.hud // the typed accessor
565
+ */
566
+ export abstract class System<K extends string, S extends SystemHost = Scene, E extends EventMap = {}> extends Aspect<K, S, E> {
567
+ // `this.scene` (and `this.node`) are typed S by the base class's conditional — nothing to add.
568
+ }