rn-gamekit 0.1.0 → 0.2.1

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 (601) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +97 -16
  3. package/lib/module/assets/defineAssets.js +28 -3
  4. package/lib/module/assets/defineAssets.js.map +1 -1
  5. package/lib/module/assets.js +11 -0
  6. package/lib/module/assets.js.map +1 -0
  7. package/lib/module/audio/createGameAudio.js +784 -0
  8. package/lib/module/audio/createGameAudio.js.map +1 -0
  9. package/lib/module/audio/errors.js +12 -0
  10. package/lib/module/audio/errors.js.map +1 -0
  11. package/lib/module/audio/index.js +5 -0
  12. package/lib/module/audio/index.js.map +1 -0
  13. package/lib/module/audio/resolver.js +37 -0
  14. package/lib/module/audio/resolver.js.map +1 -0
  15. package/lib/module/audio/types.js +4 -0
  16. package/lib/module/audio/types.js.map +1 -0
  17. package/lib/module/audio.js +9 -0
  18. package/lib/module/audio.js.map +1 -0
  19. package/lib/module/camera2d/bounds.js +59 -0
  20. package/lib/module/camera2d/bounds.js.map +1 -0
  21. package/lib/module/camera2d/extents.js +25 -0
  22. package/lib/module/camera2d/extents.js.map +1 -0
  23. package/lib/module/camera2d/follow.js +138 -0
  24. package/lib/module/camera2d/follow.js.map +1 -0
  25. package/lib/module/camera2d/index.js +20 -0
  26. package/lib/module/camera2d/index.js.map +1 -0
  27. package/lib/module/camera2d/interpolation.js +98 -0
  28. package/lib/module/camera2d/interpolation.js.map +1 -0
  29. package/lib/module/camera2d/shake.js +85 -0
  30. package/lib/module/camera2d/shake.js.map +1 -0
  31. package/lib/module/camera2d/transform.js +151 -0
  32. package/lib/module/camera2d/transform.js.map +1 -0
  33. package/lib/module/camera2d/types.js +4 -0
  34. package/lib/module/camera2d/types.js.map +1 -0
  35. package/lib/module/camera2d/validation.js +98 -0
  36. package/lib/module/camera2d/validation.js.map +1 -0
  37. package/lib/module/camera2d/visibility.js +138 -0
  38. package/lib/module/camera2d/visibility.js.map +1 -0
  39. package/lib/module/camera2d.js +9 -0
  40. package/lib/module/camera2d.js.map +1 -0
  41. package/lib/module/collision2d/colliders.js +266 -0
  42. package/lib/module/collision2d/colliders.js.map +1 -0
  43. package/lib/module/collision2d/debug.js +105 -0
  44. package/lib/module/collision2d/debug.js.map +1 -0
  45. package/lib/module/collision2d/filters.js +40 -0
  46. package/lib/module/collision2d/filters.js.map +1 -0
  47. package/lib/module/collision2d/index.js +12 -0
  48. package/lib/module/collision2d/index.js.map +1 -0
  49. package/lib/module/collision2d/intersections.js +67 -0
  50. package/lib/module/collision2d/intersections.js.map +1 -0
  51. package/lib/module/collision2d/manifolds.js +218 -0
  52. package/lib/module/collision2d/manifolds.js.map +1 -0
  53. package/lib/module/collision2d/segments.js +187 -0
  54. package/lib/module/collision2d/segments.js.map +1 -0
  55. package/lib/module/collision2d/spatialHash.js +167 -0
  56. package/lib/module/collision2d/spatialHash.js.map +1 -0
  57. package/lib/module/collision2d/sweeps.js +255 -0
  58. package/lib/module/collision2d/sweeps.js.map +1 -0
  59. package/lib/module/collision2d.js +9 -0
  60. package/lib/module/collision2d.js.map +1 -0
  61. package/lib/module/core/input/buttonPad.js +214 -0
  62. package/lib/module/core/input/buttonPad.js.map +1 -0
  63. package/lib/module/core/input/createInputBuffer.js +66 -14
  64. package/lib/module/core/input/createInputBuffer.js.map +1 -1
  65. package/lib/module/core/session/createGameSession.js +454 -50
  66. package/lib/module/core/session/createGameSession.js.map +1 -1
  67. package/lib/module/core/session/deepFreeze.js +92 -2
  68. package/lib/module/core/session/deepFreeze.js.map +1 -1
  69. package/lib/module/core/session/types.js +12 -0
  70. package/lib/module/core/session/types.js.map +1 -1
  71. package/lib/module/definition/defineGame.js +23 -0
  72. package/lib/module/definition/defineGame.js.map +1 -1
  73. package/lib/module/events/defineGameEvents.js +46 -0
  74. package/lib/module/events/defineGameEvents.js.map +1 -0
  75. package/lib/module/events/errors.js +16 -0
  76. package/lib/module/events/errors.js.map +1 -0
  77. package/lib/module/events/index.js +7 -0
  78. package/lib/module/events/index.js.map +1 -0
  79. package/lib/module/events/payload.js +219 -0
  80. package/lib/module/events/payload.js.map +1 -0
  81. package/lib/module/events/seed.js +33 -0
  82. package/lib/module/events/seed.js.map +1 -0
  83. package/lib/module/events/types.js +4 -0
  84. package/lib/module/events/types.js.map +1 -0
  85. package/lib/module/events.js +13 -0
  86. package/lib/module/events.js.map +1 -0
  87. package/lib/module/geometry/helpers.js +140 -0
  88. package/lib/module/geometry/helpers.js.map +1 -0
  89. package/lib/module/geometry/index.js +5 -0
  90. package/lib/module/geometry/index.js.map +1 -0
  91. package/lib/module/geometry/validation.js +104 -0
  92. package/lib/module/geometry/validation.js.map +1 -0
  93. package/lib/module/geometry.js +9 -0
  94. package/lib/module/geometry.js.map +1 -0
  95. package/lib/module/haptics/createGameHaptics.js +214 -0
  96. package/lib/module/haptics/createGameHaptics.js.map +1 -0
  97. package/lib/module/haptics/errors.js +12 -0
  98. package/lib/module/haptics/errors.js.map +1 -0
  99. package/lib/module/haptics/index.js +5 -0
  100. package/lib/module/haptics/index.js.map +1 -0
  101. package/lib/module/haptics/resolver.js +37 -0
  102. package/lib/module/haptics/resolver.js.map +1 -0
  103. package/lib/module/haptics/types.js +4 -0
  104. package/lib/module/haptics/types.js.map +1 -0
  105. package/lib/module/haptics.js +9 -0
  106. package/lib/module/haptics.js.map +1 -0
  107. package/lib/module/index.js +8 -1
  108. package/lib/module/index.js.map +1 -1
  109. package/lib/module/particles/createParticleSystem.js +481 -0
  110. package/lib/module/particles/createParticleSystem.js.map +1 -0
  111. package/lib/module/particles/defineParticleEffect.js +178 -0
  112. package/lib/module/particles/defineParticleEffect.js.map +1 -0
  113. package/lib/module/particles/errors.js +9 -0
  114. package/lib/module/particles/errors.js.map +1 -0
  115. package/lib/module/particles/index.js +8 -0
  116. package/lib/module/particles/index.js.map +1 -0
  117. package/lib/module/particles/sampling.js +121 -0
  118. package/lib/module/particles/sampling.js.map +1 -0
  119. package/lib/module/particles/types.js +4 -0
  120. package/lib/module/particles/types.js.map +1 -0
  121. package/lib/module/particles.js +4 -0
  122. package/lib/module/particles.js.map +1 -0
  123. package/lib/module/react/GameButtonPad.js +344 -0
  124. package/lib/module/react/GameButtonPad.js.map +1 -0
  125. package/lib/module/react/GamePointerInput.js +74 -16
  126. package/lib/module/react/GamePointerInput.js.map +1 -1
  127. package/lib/module/react/GameView.js +139 -60
  128. package/lib/module/react/GameView.js.map +1 -1
  129. package/lib/module/react/assets/createGameAssetStore.js +145 -95
  130. package/lib/module/react/assets/createGameAssetStore.js.map +1 -1
  131. package/lib/module/react/assets/useGameAssets.js +60 -35
  132. package/lib/module/react/assets/useGameAssets.js.map +1 -1
  133. package/lib/module/react/bindAppLifecycle.js +46 -17
  134. package/lib/module/react/bindAppLifecycle.js.map +1 -1
  135. package/lib/module/react/bindGameSession.js +13 -2
  136. package/lib/module/react/bindGameSession.js.map +1 -1
  137. package/lib/module/react/camera2d/GameLayer2D.js +50 -0
  138. package/lib/module/react/camera2d/GameLayer2D.js.map +1 -0
  139. package/lib/module/react/camera2d/defineGameCamera2D.js +30 -0
  140. package/lib/module/react/camera2d/defineGameCamera2D.js.map +1 -0
  141. package/lib/module/react/camera2d/usePresentedCameraBinding.js +177 -0
  142. package/lib/module/react/camera2d/usePresentedCameraBinding.js.map +1 -0
  143. package/lib/module/react/cleanupList.js +38 -0
  144. package/lib/module/react/cleanupList.js.map +1 -0
  145. package/lib/module/react/lifecycleSource.js +49 -0
  146. package/lib/module/react/lifecycleSource.js.map +1 -0
  147. package/lib/module/react/particles/ParticleView.js +308 -0
  148. package/lib/module/react/particles/ParticleView.js.map +1 -0
  149. package/lib/module/react/particles/culling.js +115 -0
  150. package/lib/module/react/particles/culling.js.map +1 -0
  151. package/lib/module/react/particles/index.js +7 -0
  152. package/lib/module/react/particles/index.js.map +1 -0
  153. package/lib/module/react/particles/spriteXForm.js +45 -0
  154. package/lib/module/react/particles/spriteXForm.js.map +1 -0
  155. package/lib/module/react/particles/useParticlePresentation.js +204 -0
  156. package/lib/module/react/particles/useParticlePresentation.js.map +1 -0
  157. package/lib/module/react/pointerBinding.js +55 -17
  158. package/lib/module/react/pointerBinding.js.map +1 -1
  159. package/lib/module/react/pointerCamera.js +28 -0
  160. package/lib/module/react/pointerCamera.js.map +1 -0
  161. package/lib/module/react/pointerCoalescer.js +16 -9
  162. package/lib/module/react/pointerCoalescer.js.map +1 -1
  163. package/lib/module/react/sprites/GameSprite.js +5 -17
  164. package/lib/module/react/sprites/GameSprite.js.map +1 -1
  165. package/lib/module/react/sprites/GameWorld2D.js +112 -9
  166. package/lib/module/react/sprites/GameWorld2D.js.map +1 -1
  167. package/lib/module/react/sprites/Sprite.js +48 -63
  168. package/lib/module/react/sprites/Sprite.js.map +1 -1
  169. package/lib/module/react/sprites/SpriteBatch.js +53 -28
  170. package/lib/module/react/sprites/SpriteBatch.js.map +1 -1
  171. package/lib/module/react/sprites/batchVisibility.js +58 -0
  172. package/lib/module/react/sprites/batchVisibility.js.map +1 -0
  173. package/lib/module/react/sprites/spriteSelection.js +103 -0
  174. package/lib/module/react/sprites/spriteSelection.js.map +1 -0
  175. package/lib/module/react/sprites/spriteTransform.js +46 -25
  176. package/lib/module/react/sprites/spriteTransform.js.map +1 -1
  177. package/lib/module/react/tilemap/TileMapLayer2D.js +217 -0
  178. package/lib/module/react/tilemap/TileMapLayer2D.js.map +1 -0
  179. package/lib/module/react/tilemap/tilePresentation.js +374 -0
  180. package/lib/module/react/tilemap/tilePresentation.js.map +1 -0
  181. package/lib/module/react/useGameSession.js +70 -0
  182. package/lib/module/react/useGameSession.js.map +1 -0
  183. package/lib/module/react/useGameSessionStatus.js +46 -0
  184. package/lib/module/react/useGameSessionStatus.js.map +1 -0
  185. package/lib/module/react/viewportBinding.js +5 -1
  186. package/lib/module/react/viewportBinding.js.map +1 -1
  187. package/lib/module/react.js +12 -1
  188. package/lib/module/react.js.map +1 -1
  189. package/lib/module/scene/defineScene.js +17 -0
  190. package/lib/module/scene/defineScene.js.map +1 -1
  191. package/lib/module/sprites/sampleSpriteClip.js +66 -6
  192. package/lib/module/sprites/sampleSpriteClip.js.map +1 -1
  193. package/lib/module/sprites/spriteAnimationState.js +10 -20
  194. package/lib/module/sprites/spriteAnimationState.js.map +1 -1
  195. package/lib/module/sprites.js +11 -0
  196. package/lib/module/sprites.js.map +1 -0
  197. package/lib/module/storage/adapters/asyncStorage.js +57 -0
  198. package/lib/module/storage/adapters/asyncStorage.js.map +1 -0
  199. package/lib/module/storage/adapters/memory.js +50 -0
  200. package/lib/module/storage/adapters/memory.js.map +1 -0
  201. package/lib/module/storage/errors.js +34 -0
  202. package/lib/module/storage/errors.js.map +1 -0
  203. package/lib/module/storage/index.js +11 -0
  204. package/lib/module/storage/index.js.map +1 -0
  205. package/lib/module/storage/schema.js +151 -0
  206. package/lib/module/storage/schema.js.map +1 -0
  207. package/lib/module/storage/serialization.js +249 -0
  208. package/lib/module/storage/serialization.js.map +1 -0
  209. package/lib/module/storage/store.js +387 -0
  210. package/lib/module/storage/store.js.map +1 -0
  211. package/lib/module/storage/types.js +16 -0
  212. package/lib/module/storage/types.js.map +1 -0
  213. package/lib/module/storage/validation.js +111 -0
  214. package/lib/module/storage/validation.js.map +1 -0
  215. package/lib/module/storage.js +4 -0
  216. package/lib/module/storage.js.map +1 -0
  217. package/lib/module/testing.js +4 -0
  218. package/lib/module/testing.js.map +1 -1
  219. package/lib/module/tilemap/chunkStats.js +27 -0
  220. package/lib/module/tilemap/chunkStats.js.map +1 -0
  221. package/lib/module/tilemap/definitions.js +308 -0
  222. package/lib/module/tilemap/definitions.js.map +1 -0
  223. package/lib/module/tilemap/errors.js +13 -0
  224. package/lib/module/tilemap/errors.js.map +1 -0
  225. package/lib/module/tilemap/index.js +10 -0
  226. package/lib/module/tilemap/index.js.map +1 -0
  227. package/lib/module/tilemap/movement.js +379 -0
  228. package/lib/module/tilemap/movement.js.map +1 -0
  229. package/lib/module/tilemap/queries.js +114 -0
  230. package/lib/module/tilemap/queries.js.map +1 -0
  231. package/lib/module/tilemap/tiledAdapter.js +141 -0
  232. package/lib/module/tilemap/tiledAdapter.js.map +1 -0
  233. package/lib/module/tilemap/types.js +4 -0
  234. package/lib/module/tilemap/types.js.map +1 -0
  235. package/lib/module/tilemap.js +4 -0
  236. package/lib/module/tilemap.js.map +1 -0
  237. package/lib/typescript/src/assets/defineAssets.d.ts +1 -1
  238. package/lib/typescript/src/assets/defineAssets.d.ts.map +1 -1
  239. package/lib/typescript/src/assets.d.ts +11 -0
  240. package/lib/typescript/src/assets.d.ts.map +1 -0
  241. package/lib/typescript/src/audio/createGameAudio.d.ts +5 -0
  242. package/lib/typescript/src/audio/createGameAudio.d.ts.map +1 -0
  243. package/lib/typescript/src/audio/errors.d.ts +6 -0
  244. package/lib/typescript/src/audio/errors.d.ts.map +1 -0
  245. package/lib/typescript/src/audio/index.d.ts +4 -0
  246. package/lib/typescript/src/audio/index.d.ts.map +1 -0
  247. package/lib/typescript/src/audio/resolver.d.ts +44 -0
  248. package/lib/typescript/src/audio/resolver.d.ts.map +1 -0
  249. package/lib/typescript/src/audio/types.d.ts +42 -0
  250. package/lib/typescript/src/audio/types.d.ts.map +1 -0
  251. package/lib/typescript/src/audio.d.ts +8 -0
  252. package/lib/typescript/src/audio.d.ts.map +1 -0
  253. package/lib/typescript/src/camera2d/bounds.d.ts +25 -0
  254. package/lib/typescript/src/camera2d/bounds.d.ts.map +1 -0
  255. package/lib/typescript/src/camera2d/extents.d.ts +17 -0
  256. package/lib/typescript/src/camera2d/extents.d.ts.map +1 -0
  257. package/lib/typescript/src/camera2d/follow.d.ts +24 -0
  258. package/lib/typescript/src/camera2d/follow.d.ts.map +1 -0
  259. package/lib/typescript/src/camera2d/index.d.ts +19 -0
  260. package/lib/typescript/src/camera2d/index.d.ts.map +1 -0
  261. package/lib/typescript/src/camera2d/interpolation.d.ts +17 -0
  262. package/lib/typescript/src/camera2d/interpolation.d.ts.map +1 -0
  263. package/lib/typescript/src/camera2d/shake.d.ts +20 -0
  264. package/lib/typescript/src/camera2d/shake.d.ts.map +1 -0
  265. package/lib/typescript/src/camera2d/transform.d.ts +44 -0
  266. package/lib/typescript/src/camera2d/transform.d.ts.map +1 -0
  267. package/lib/typescript/src/camera2d/types.d.ts +74 -0
  268. package/lib/typescript/src/camera2d/types.d.ts.map +1 -0
  269. package/lib/typescript/src/camera2d/validation.d.ts +30 -0
  270. package/lib/typescript/src/camera2d/validation.d.ts.map +1 -0
  271. package/lib/typescript/src/camera2d/visibility.d.ts +47 -0
  272. package/lib/typescript/src/camera2d/visibility.d.ts.map +1 -0
  273. package/lib/typescript/src/camera2d.d.ts +7 -0
  274. package/lib/typescript/src/camera2d.d.ts.map +1 -0
  275. package/lib/typescript/src/collision2d/colliders.d.ts +120 -0
  276. package/lib/typescript/src/collision2d/colliders.d.ts.map +1 -0
  277. package/lib/typescript/src/collision2d/debug.d.ts +67 -0
  278. package/lib/typescript/src/collision2d/debug.d.ts.map +1 -0
  279. package/lib/typescript/src/collision2d/filters.d.ts +28 -0
  280. package/lib/typescript/src/collision2d/filters.d.ts.map +1 -0
  281. package/lib/typescript/src/collision2d/index.d.ts +17 -0
  282. package/lib/typescript/src/collision2d/index.d.ts.map +1 -0
  283. package/lib/typescript/src/collision2d/intersections.d.ts +24 -0
  284. package/lib/typescript/src/collision2d/intersections.d.ts.map +1 -0
  285. package/lib/typescript/src/collision2d/manifolds.d.ts +39 -0
  286. package/lib/typescript/src/collision2d/manifolds.d.ts.map +1 -0
  287. package/lib/typescript/src/collision2d/segments.d.ts +24 -0
  288. package/lib/typescript/src/collision2d/segments.d.ts.map +1 -0
  289. package/lib/typescript/src/collision2d/spatialHash.d.ts +50 -0
  290. package/lib/typescript/src/collision2d/spatialHash.d.ts.map +1 -0
  291. package/lib/typescript/src/collision2d/sweeps.d.ts +60 -0
  292. package/lib/typescript/src/collision2d/sweeps.d.ts.map +1 -0
  293. package/lib/typescript/src/collision2d.d.ts +7 -0
  294. package/lib/typescript/src/collision2d.d.ts.map +1 -0
  295. package/lib/typescript/src/core/input/buttonPad.d.ts +59 -0
  296. package/lib/typescript/src/core/input/buttonPad.d.ts.map +1 -0
  297. package/lib/typescript/src/core/input/createInputBuffer.d.ts +1 -1
  298. package/lib/typescript/src/core/input/createInputBuffer.d.ts.map +1 -1
  299. package/lib/typescript/src/core/input/types.d.ts +7 -0
  300. package/lib/typescript/src/core/input/types.d.ts.map +1 -1
  301. package/lib/typescript/src/core/session/createGameSession.d.ts +4 -2
  302. package/lib/typescript/src/core/session/createGameSession.d.ts.map +1 -1
  303. package/lib/typescript/src/core/session/deepFreeze.d.ts +21 -1
  304. package/lib/typescript/src/core/session/deepFreeze.d.ts.map +1 -1
  305. package/lib/typescript/src/core/session/diagnostics.d.ts +2 -0
  306. package/lib/typescript/src/core/session/diagnostics.d.ts.map +1 -1
  307. package/lib/typescript/src/core/session/types.d.ts +69 -4
  308. package/lib/typescript/src/core/session/types.d.ts.map +1 -1
  309. package/lib/typescript/src/definition/defineGame.d.ts +12 -1
  310. package/lib/typescript/src/definition/defineGame.d.ts.map +1 -1
  311. package/lib/typescript/src/definition/types.d.ts +10 -1
  312. package/lib/typescript/src/definition/types.d.ts.map +1 -1
  313. package/lib/typescript/src/events/defineGameEvents.d.ts +26 -0
  314. package/lib/typescript/src/events/defineGameEvents.d.ts.map +1 -0
  315. package/lib/typescript/src/events/errors.d.ts +12 -0
  316. package/lib/typescript/src/events/errors.d.ts.map +1 -0
  317. package/lib/typescript/src/events/index.d.ts +6 -0
  318. package/lib/typescript/src/events/index.d.ts.map +1 -0
  319. package/lib/typescript/src/events/payload.d.ts +21 -0
  320. package/lib/typescript/src/events/payload.d.ts.map +1 -0
  321. package/lib/typescript/src/events/seed.d.ts +3 -0
  322. package/lib/typescript/src/events/seed.d.ts.map +1 -0
  323. package/lib/typescript/src/events/types.d.ts +42 -0
  324. package/lib/typescript/src/events/types.d.ts.map +1 -0
  325. package/lib/typescript/src/events.d.ts +12 -0
  326. package/lib/typescript/src/events.d.ts.map +1 -0
  327. package/lib/typescript/src/geometry/helpers.d.ts +35 -0
  328. package/lib/typescript/src/geometry/helpers.d.ts.map +1 -0
  329. package/lib/typescript/src/geometry/index.d.ts +4 -0
  330. package/lib/typescript/src/geometry/index.d.ts.map +1 -0
  331. package/lib/typescript/src/geometry/types.d.ts +49 -0
  332. package/lib/typescript/src/geometry/types.d.ts.map +1 -1
  333. package/lib/typescript/src/geometry/validation.d.ts +44 -0
  334. package/lib/typescript/src/geometry/validation.d.ts.map +1 -0
  335. package/lib/typescript/src/geometry.d.ts +7 -0
  336. package/lib/typescript/src/geometry.d.ts.map +1 -0
  337. package/lib/typescript/src/haptics/createGameHaptics.d.ts +3 -0
  338. package/lib/typescript/src/haptics/createGameHaptics.d.ts.map +1 -0
  339. package/lib/typescript/src/haptics/errors.d.ts +6 -0
  340. package/lib/typescript/src/haptics/errors.d.ts.map +1 -0
  341. package/lib/typescript/src/haptics/index.d.ts +4 -0
  342. package/lib/typescript/src/haptics/index.d.ts.map +1 -0
  343. package/lib/typescript/src/haptics/resolver.d.ts +22 -0
  344. package/lib/typescript/src/haptics/resolver.d.ts.map +1 -0
  345. package/lib/typescript/src/haptics/types.d.ts +37 -0
  346. package/lib/typescript/src/haptics/types.d.ts.map +1 -0
  347. package/lib/typescript/src/haptics.d.ts +8 -0
  348. package/lib/typescript/src/haptics.d.ts.map +1 -0
  349. package/lib/typescript/src/index.d.ts +10 -2
  350. package/lib/typescript/src/index.d.ts.map +1 -1
  351. package/lib/typescript/src/particles/createParticleSystem.d.ts +5 -0
  352. package/lib/typescript/src/particles/createParticleSystem.d.ts.map +1 -0
  353. package/lib/typescript/src/particles/defineParticleEffect.d.ts +3 -0
  354. package/lib/typescript/src/particles/defineParticleEffect.d.ts.map +1 -0
  355. package/lib/typescript/src/particles/errors.d.ts +5 -0
  356. package/lib/typescript/src/particles/errors.d.ts.map +1 -0
  357. package/lib/typescript/src/particles/index.d.ts +6 -0
  358. package/lib/typescript/src/particles/index.d.ts.map +1 -0
  359. package/lib/typescript/src/particles/sampling.d.ts +54 -0
  360. package/lib/typescript/src/particles/sampling.d.ts.map +1 -0
  361. package/lib/typescript/src/particles/types.d.ts +292 -0
  362. package/lib/typescript/src/particles/types.d.ts.map +1 -0
  363. package/lib/typescript/src/particles.d.ts +2 -0
  364. package/lib/typescript/src/particles.d.ts.map +1 -0
  365. package/lib/typescript/src/react/GameButtonPad.d.ts +84 -0
  366. package/lib/typescript/src/react/GameButtonPad.d.ts.map +1 -0
  367. package/lib/typescript/src/react/GamePointerInput.d.ts.map +1 -1
  368. package/lib/typescript/src/react/GameView.d.ts +27 -1
  369. package/lib/typescript/src/react/GameView.d.ts.map +1 -1
  370. package/lib/typescript/src/react/assets/createGameAssetStore.d.ts.map +1 -1
  371. package/lib/typescript/src/react/assets/useGameAssets.d.ts.map +1 -1
  372. package/lib/typescript/src/react/bindAppLifecycle.d.ts +7 -1
  373. package/lib/typescript/src/react/bindAppLifecycle.d.ts.map +1 -1
  374. package/lib/typescript/src/react/bindGameSession.d.ts.map +1 -1
  375. package/lib/typescript/src/react/camera2d/GameLayer2D.d.ts +24 -0
  376. package/lib/typescript/src/react/camera2d/GameLayer2D.d.ts.map +1 -0
  377. package/lib/typescript/src/react/camera2d/defineGameCamera2D.d.ts +44 -0
  378. package/lib/typescript/src/react/camera2d/defineGameCamera2D.d.ts.map +1 -0
  379. package/lib/typescript/src/react/camera2d/usePresentedCameraBinding.d.ts +22 -0
  380. package/lib/typescript/src/react/camera2d/usePresentedCameraBinding.d.ts.map +1 -0
  381. package/lib/typescript/src/react/cleanupList.d.ts +21 -0
  382. package/lib/typescript/src/react/cleanupList.d.ts.map +1 -0
  383. package/lib/typescript/src/react/instrumentation.d.ts +33 -3
  384. package/lib/typescript/src/react/instrumentation.d.ts.map +1 -1
  385. package/lib/typescript/src/react/lifecycleSource.d.ts +21 -0
  386. package/lib/typescript/src/react/lifecycleSource.d.ts.map +1 -0
  387. package/lib/typescript/src/react/particles/ParticleView.d.ts +65 -0
  388. package/lib/typescript/src/react/particles/ParticleView.d.ts.map +1 -0
  389. package/lib/typescript/src/react/particles/culling.d.ts +70 -0
  390. package/lib/typescript/src/react/particles/culling.d.ts.map +1 -0
  391. package/lib/typescript/src/react/particles/index.d.ts +7 -0
  392. package/lib/typescript/src/react/particles/index.d.ts.map +1 -0
  393. package/lib/typescript/src/react/particles/spriteXForm.d.ts +28 -0
  394. package/lib/typescript/src/react/particles/spriteXForm.d.ts.map +1 -0
  395. package/lib/typescript/src/react/particles/useParticlePresentation.d.ts +48 -0
  396. package/lib/typescript/src/react/particles/useParticlePresentation.d.ts.map +1 -0
  397. package/lib/typescript/src/react/pointerBinding.d.ts +38 -8
  398. package/lib/typescript/src/react/pointerBinding.d.ts.map +1 -1
  399. package/lib/typescript/src/react/pointerCamera.d.ts +20 -0
  400. package/lib/typescript/src/react/pointerCamera.d.ts.map +1 -0
  401. package/lib/typescript/src/react/pointerCoalescer.d.ts +17 -1
  402. package/lib/typescript/src/react/pointerCoalescer.d.ts.map +1 -1
  403. package/lib/typescript/src/react/sprites/GameSprite.d.ts +3 -10
  404. package/lib/typescript/src/react/sprites/GameSprite.d.ts.map +1 -1
  405. package/lib/typescript/src/react/sprites/GameWorld2D.d.ts +51 -6
  406. package/lib/typescript/src/react/sprites/GameWorld2D.d.ts.map +1 -1
  407. package/lib/typescript/src/react/sprites/Sprite.d.ts.map +1 -1
  408. package/lib/typescript/src/react/sprites/SpriteBatch.d.ts +22 -1
  409. package/lib/typescript/src/react/sprites/SpriteBatch.d.ts.map +1 -1
  410. package/lib/typescript/src/react/sprites/batchVisibility.d.ts +28 -0
  411. package/lib/typescript/src/react/sprites/batchVisibility.d.ts.map +1 -0
  412. package/lib/typescript/src/react/sprites/spriteSelection.d.ts +58 -0
  413. package/lib/typescript/src/react/sprites/spriteSelection.d.ts.map +1 -0
  414. package/lib/typescript/src/react/sprites/spriteTransform.d.ts +27 -12
  415. package/lib/typescript/src/react/sprites/spriteTransform.d.ts.map +1 -1
  416. package/lib/typescript/src/react/tilemap/TileMapLayer2D.d.ts +69 -0
  417. package/lib/typescript/src/react/tilemap/TileMapLayer2D.d.ts.map +1 -0
  418. package/lib/typescript/src/react/tilemap/tilePresentation.d.ts +217 -0
  419. package/lib/typescript/src/react/tilemap/tilePresentation.d.ts.map +1 -0
  420. package/lib/typescript/src/react/useGameSession.d.ts +48 -0
  421. package/lib/typescript/src/react/useGameSession.d.ts.map +1 -0
  422. package/lib/typescript/src/react/useGameSessionStatus.d.ts +23 -0
  423. package/lib/typescript/src/react/useGameSessionStatus.d.ts.map +1 -0
  424. package/lib/typescript/src/react/viewportBinding.d.ts +5 -1
  425. package/lib/typescript/src/react/viewportBinding.d.ts.map +1 -1
  426. package/lib/typescript/src/react.d.ts +18 -1
  427. package/lib/typescript/src/react.d.ts.map +1 -1
  428. package/lib/typescript/src/scene/defineScene.d.ts +22 -2
  429. package/lib/typescript/src/scene/defineScene.d.ts.map +1 -1
  430. package/lib/typescript/src/scene/types.d.ts +24 -4
  431. package/lib/typescript/src/scene/types.d.ts.map +1 -1
  432. package/lib/typescript/src/sprites/sampleSpriteClip.d.ts +26 -2
  433. package/lib/typescript/src/sprites/sampleSpriteClip.d.ts.map +1 -1
  434. package/lib/typescript/src/sprites/spriteAnimationState.d.ts +6 -7
  435. package/lib/typescript/src/sprites/spriteAnimationState.d.ts.map +1 -1
  436. package/lib/typescript/src/sprites.d.ts +10 -0
  437. package/lib/typescript/src/sprites.d.ts.map +1 -0
  438. package/lib/typescript/src/storage/adapters/asyncStorage.d.ts +18 -0
  439. package/lib/typescript/src/storage/adapters/asyncStorage.d.ts.map +1 -0
  440. package/lib/typescript/src/storage/adapters/memory.d.ts +16 -0
  441. package/lib/typescript/src/storage/adapters/memory.d.ts.map +1 -0
  442. package/lib/typescript/src/storage/errors.d.ts +37 -0
  443. package/lib/typescript/src/storage/errors.d.ts.map +1 -0
  444. package/lib/typescript/src/storage/index.d.ts +11 -0
  445. package/lib/typescript/src/storage/index.d.ts.map +1 -0
  446. package/lib/typescript/src/storage/schema.d.ts +22 -0
  447. package/lib/typescript/src/storage/schema.d.ts.map +1 -0
  448. package/lib/typescript/src/storage/serialization.d.ts +23 -0
  449. package/lib/typescript/src/storage/serialization.d.ts.map +1 -0
  450. package/lib/typescript/src/storage/store.d.ts +3 -0
  451. package/lib/typescript/src/storage/store.d.ts.map +1 -0
  452. package/lib/typescript/src/storage/types.d.ts +69 -0
  453. package/lib/typescript/src/storage/types.d.ts.map +1 -0
  454. package/lib/typescript/src/storage/validation.d.ts +24 -0
  455. package/lib/typescript/src/storage/validation.d.ts.map +1 -0
  456. package/lib/typescript/src/storage.d.ts +2 -0
  457. package/lib/typescript/src/storage.d.ts.map +1 -0
  458. package/lib/typescript/src/testing.d.ts +1 -0
  459. package/lib/typescript/src/testing.d.ts.map +1 -1
  460. package/lib/typescript/src/tilemap/chunkStats.d.ts +14 -0
  461. package/lib/typescript/src/tilemap/chunkStats.d.ts.map +1 -0
  462. package/lib/typescript/src/tilemap/definitions.d.ts +38 -0
  463. package/lib/typescript/src/tilemap/definitions.d.ts.map +1 -0
  464. package/lib/typescript/src/tilemap/errors.d.ts +7 -0
  465. package/lib/typescript/src/tilemap/errors.d.ts.map +1 -0
  466. package/lib/typescript/src/tilemap/index.d.ts +8 -0
  467. package/lib/typescript/src/tilemap/index.d.ts.map +1 -0
  468. package/lib/typescript/src/tilemap/movement.d.ts +52 -0
  469. package/lib/typescript/src/tilemap/movement.d.ts.map +1 -0
  470. package/lib/typescript/src/tilemap/queries.d.ts +21 -0
  471. package/lib/typescript/src/tilemap/queries.d.ts.map +1 -0
  472. package/lib/typescript/src/tilemap/tiledAdapter.d.ts +32 -0
  473. package/lib/typescript/src/tilemap/tiledAdapter.d.ts.map +1 -0
  474. package/lib/typescript/src/tilemap/types.d.ts +127 -0
  475. package/lib/typescript/src/tilemap/types.d.ts.map +1 -0
  476. package/lib/typescript/src/tilemap.d.ts +2 -0
  477. package/lib/typescript/src/tilemap.d.ts.map +1 -0
  478. package/package.json +100 -10
  479. package/src/assets/defineAssets.ts +31 -5
  480. package/src/assets.ts +27 -0
  481. package/src/audio/createGameAudio.ts +749 -0
  482. package/src/audio/errors.ts +12 -0
  483. package/src/audio/index.ts +9 -0
  484. package/src/audio/resolver.ts +74 -0
  485. package/src/audio/types.ts +47 -0
  486. package/src/audio.ts +13 -0
  487. package/src/camera2d/bounds.ts +69 -0
  488. package/src/camera2d/extents.ts +25 -0
  489. package/src/camera2d/follow.ts +165 -0
  490. package/src/camera2d/index.ts +29 -0
  491. package/src/camera2d/interpolation.ts +113 -0
  492. package/src/camera2d/shake.ts +90 -0
  493. package/src/camera2d/transform.ts +148 -0
  494. package/src/camera2d/types.ts +73 -0
  495. package/src/camera2d/validation.ts +126 -0
  496. package/src/camera2d/visibility.ts +181 -0
  497. package/src/camera2d.ts +6 -0
  498. package/src/collision2d/colliders.ts +292 -0
  499. package/src/collision2d/debug.ts +133 -0
  500. package/src/collision2d/filters.ts +48 -0
  501. package/src/collision2d/index.ts +74 -0
  502. package/src/collision2d/intersections.ts +82 -0
  503. package/src/collision2d/manifolds.ts +191 -0
  504. package/src/collision2d/segments.ts +176 -0
  505. package/src/collision2d/spatialHash.ts +211 -0
  506. package/src/collision2d/sweeps.ts +269 -0
  507. package/src/collision2d.ts +6 -0
  508. package/src/core/input/buttonPad.ts +235 -0
  509. package/src/core/input/createInputBuffer.ts +95 -14
  510. package/src/core/input/types.ts +7 -0
  511. package/src/core/session/createGameSession.ts +480 -53
  512. package/src/core/session/deepFreeze.ts +104 -3
  513. package/src/core/session/diagnostics.ts +2 -0
  514. package/src/core/session/types.ts +77 -4
  515. package/src/definition/defineGame.ts +61 -3
  516. package/src/definition/types.ts +10 -0
  517. package/src/events/defineGameEvents.ts +46 -0
  518. package/src/events/errors.ts +14 -0
  519. package/src/events/index.ts +14 -0
  520. package/src/events/payload.ts +234 -0
  521. package/src/events/seed.ts +33 -0
  522. package/src/events/types.ts +53 -0
  523. package/src/events.ts +20 -0
  524. package/src/geometry/helpers.ts +136 -0
  525. package/src/geometry/index.ts +14 -0
  526. package/src/geometry/types.ts +54 -0
  527. package/src/geometry/validation.ts +131 -0
  528. package/src/geometry.ts +6 -0
  529. package/src/haptics/createGameHaptics.ts +214 -0
  530. package/src/haptics/errors.ts +12 -0
  531. package/src/haptics/index.ts +3 -0
  532. package/src/haptics/resolver.ts +58 -0
  533. package/src/haptics/types.ts +48 -0
  534. package/src/haptics.ts +7 -0
  535. package/src/index.ts +37 -1
  536. package/src/particles/createParticleSystem.ts +505 -0
  537. package/src/particles/defineParticleEffect.ts +164 -0
  538. package/src/particles/errors.ts +6 -0
  539. package/src/particles/index.ts +5 -0
  540. package/src/particles/sampling.ts +141 -0
  541. package/src/particles/types.ts +327 -0
  542. package/src/particles.ts +1 -0
  543. package/src/react/GameButtonPad.tsx +436 -0
  544. package/src/react/GamePointerInput.tsx +73 -8
  545. package/src/react/GameView.tsx +160 -61
  546. package/src/react/assets/createGameAssetStore.ts +140 -107
  547. package/src/react/assets/useGameAssets.ts +63 -31
  548. package/src/react/bindAppLifecycle.ts +55 -19
  549. package/src/react/bindGameSession.ts +12 -2
  550. package/src/react/camera2d/GameLayer2D.tsx +43 -0
  551. package/src/react/camera2d/defineGameCamera2D.ts +59 -0
  552. package/src/react/camera2d/usePresentedCameraBinding.ts +185 -0
  553. package/src/react/cleanupList.ts +48 -0
  554. package/src/react/instrumentation.ts +31 -3
  555. package/src/react/lifecycleSource.ts +58 -0
  556. package/src/react/particles/ParticleView.tsx +358 -0
  557. package/src/react/particles/culling.ts +145 -0
  558. package/src/react/particles/index.ts +6 -0
  559. package/src/react/particles/spriteXForm.ts +72 -0
  560. package/src/react/particles/useParticlePresentation.ts +261 -0
  561. package/src/react/pointerBinding.ts +74 -16
  562. package/src/react/pointerCamera.ts +33 -0
  563. package/src/react/pointerCoalescer.ts +36 -10
  564. package/src/react/sprites/GameSprite.tsx +4 -33
  565. package/src/react/sprites/GameWorld2D.tsx +113 -7
  566. package/src/react/sprites/Sprite.tsx +51 -72
  567. package/src/react/sprites/SpriteBatch.tsx +74 -22
  568. package/src/react/sprites/batchVisibility.ts +76 -0
  569. package/src/react/sprites/spriteSelection.ts +133 -0
  570. package/src/react/sprites/spriteTransform.ts +46 -26
  571. package/src/react/tilemap/TileMapLayer2D.tsx +274 -0
  572. package/src/react/tilemap/tilePresentation.ts +491 -0
  573. package/src/react/useGameSession.ts +90 -0
  574. package/src/react/useGameSessionStatus.ts +53 -0
  575. package/src/react/viewportBinding.ts +5 -1
  576. package/src/react.ts +18 -1
  577. package/src/scene/defineScene.ts +47 -5
  578. package/src/scene/types.ts +37 -3
  579. package/src/sprites/sampleSpriteClip.ts +82 -14
  580. package/src/sprites/spriteAnimationState.ts +29 -25
  581. package/src/sprites.ts +22 -0
  582. package/src/storage/adapters/asyncStorage.ts +77 -0
  583. package/src/storage/adapters/memory.ts +58 -0
  584. package/src/storage/errors.ts +88 -0
  585. package/src/storage/index.ts +18 -0
  586. package/src/storage/schema.ts +147 -0
  587. package/src/storage/serialization.ts +248 -0
  588. package/src/storage/store.ts +380 -0
  589. package/src/storage/types.ts +76 -0
  590. package/src/storage/validation.ts +102 -0
  591. package/src/storage.ts +1 -0
  592. package/src/testing.ts +4 -0
  593. package/src/tilemap/chunkStats.ts +24 -0
  594. package/src/tilemap/definitions.ts +327 -0
  595. package/src/tilemap/errors.ts +11 -0
  596. package/src/tilemap/index.ts +10 -0
  597. package/src/tilemap/movement.ts +368 -0
  598. package/src/tilemap/queries.ts +136 -0
  599. package/src/tilemap/tiledAdapter.ts +165 -0
  600. package/src/tilemap/types.ts +119 -0
  601. package/src/tilemap.ts +1 -0
@@ -0,0 +1,505 @@
1
+ import { ParticleError } from './errors';
2
+ import { defineParticleEffect } from './defineParticleEffect';
3
+ import { createRng, sampleInitialSlot, sampleSlotAtAge } from './sampling';
4
+ import type {
5
+ ParticleDiagnostics,
6
+ ParticleEmissionRecord,
7
+ ParticleUiRegistry,
8
+ ParticleDriverHandle,
9
+ ParticleEffectDefinition,
10
+ ParticleEffectDefinitionInput,
11
+ ParticlePresentationBinding,
12
+ ParticleSlotSnapshot,
13
+ ParticleSystem,
14
+ ParticleSystemOptions,
15
+ } from './types';
16
+ import type { Point2D } from '../geometry/types';
17
+
18
+ /** Adapter from the mutable pool slot to the pure sampler's input. */
19
+ function ageView(slot: MutableSlot): {
20
+ readonly lifetime: number;
21
+ readonly origin: Point2D;
22
+ readonly velocity: Point2D;
23
+ readonly rotation: number;
24
+ readonly rotationSpeed: number;
25
+ readonly scaleStart: number;
26
+ readonly scaleEnd: number;
27
+ } {
28
+ return {
29
+ lifetime: slot.lifetime,
30
+ origin: { x: slot.originX, y: slot.originY },
31
+ velocity: { x: slot.vx, y: slot.vy },
32
+ rotation: slot.rotation,
33
+ rotationSpeed: slot.rotationSpeed,
34
+ scaleStart: slot.scaleStart,
35
+ scaleEnd: slot.scaleEnd,
36
+ };
37
+ }
38
+
39
+ function assertCommand(cmd: unknown): asserts cmd is { position: Point2D; seed: number } {
40
+ if (cmd === null || typeof cmd !== 'object' || Array.isArray(cmd)) {
41
+ throw new ParticleError('emit command must be an object');
42
+ }
43
+ const c = cmd as Record<string, unknown>;
44
+ const pos = c.position;
45
+ if (pos === null || typeof pos !== 'object' || Array.isArray(pos)) {
46
+ throw new ParticleError('emit command position must be {x,y}');
47
+ }
48
+ const pp = pos as Record<string, unknown>;
49
+ if (typeof pp.x !== 'number' || !Number.isFinite(pp.x)) {
50
+ throw new ParticleError('emit command position.x must be a finite number');
51
+ }
52
+ if (typeof pp.y !== 'number' || !Number.isFinite(pp.y)) {
53
+ throw new ParticleError('emit command position.y must be a finite number');
54
+ }
55
+ if (typeof c.seed !== 'number' || !Number.isFinite(c.seed)) {
56
+ throw new ParticleError('emit command seed must be a finite number');
57
+ }
58
+ }
59
+
60
+ interface MutableSlot {
61
+ active: boolean;
62
+ age: number;
63
+ bornAt: number;
64
+ lifetime: number;
65
+ originX: number;
66
+ originY: number;
67
+ vx: number;
68
+ vy: number;
69
+ rotation: number;
70
+ rotationSpeed: number;
71
+ scaleStart: number;
72
+ scaleEnd: number;
73
+ color: string;
74
+ spawnSequence: number;
75
+ }
76
+
77
+ // T20A-R3: accept raw author definitions or already-normalized definitions
78
+ // (normalized definitions are assignable to the author-input union); every
79
+ // value is validated, cloned, and frozen at this boundary.
80
+ export function createParticleSystem<
81
+ TEffects extends Record<string, ParticleEffectDefinitionInput>,
82
+ >(
83
+ options: ParticleSystemOptions & { effects: TEffects },
84
+ ): ParticleSystem<TEffects> {
85
+ if (options === null || typeof options !== 'object') {
86
+ throw new ParticleError('createParticleSystem requires options');
87
+ }
88
+ const rawEffects = options.effects;
89
+ if (rawEffects === null || typeof rawEffects !== 'object') {
90
+ throw new ParticleError('createParticleSystem requires { effects }');
91
+ }
92
+ const names = Object.keys(rawEffects);
93
+ if (names.length === 0) {
94
+ throw new ParticleError('createParticleSystem effects must not be empty');
95
+ }
96
+
97
+ // T15-F6: validate, clone, and freeze every definition at the boundary so
98
+ // callers cannot bypass defineParticleEffect or mutate after creation.
99
+ const definitions = new Map<string, ParticleEffectDefinition>();
100
+ for (const name of names) {
101
+ definitions.set(name, defineParticleEffect(rawEffects[name]!));
102
+ }
103
+
104
+ let status: 'running' | 'paused' | 'disposed' = 'running';
105
+ let generation = 0;
106
+ let spawnSequence = 0;
107
+ // Accumulated ACTIVE time — freezes while paused (T15-SF3).
108
+ let activeClock = 0;
109
+ // Emission registry revision (membership changes only).
110
+ let registryRevision = 0;
111
+ const emissionsLog: Map<string, ParticleEmissionRecord[]> = new Map();
112
+
113
+ const pools = new Map<string, MutableSlot[]>();
114
+ const diagnostics = new Map<string, ParticleDiagnostics>();
115
+ for (const [name, def] of definitions) {
116
+ pools.set(
117
+ name,
118
+ Array.from({ length: def.capacity }, () => ({
119
+ active: false,
120
+ age: 0,
121
+ bornAt: 0,
122
+ lifetime: 1,
123
+ originX: 0,
124
+ originY: 0,
125
+ vx: 0,
126
+ vy: 0,
127
+ rotation: 0,
128
+ rotationSpeed: 0,
129
+ scaleStart: 1,
130
+ scaleEnd: 1,
131
+ color: '#ffffff',
132
+ spawnSequence: -1,
133
+ })),
134
+ );
135
+ diagnostics.set(name, { active: 0, emitted: 0, dropped: 0, recycled: 0 });
136
+ emissionsLog.set(name, []);
137
+ }
138
+
139
+ function requireEffect(effect: string): ParticleEffectDefinition {
140
+ const def = definitions.get(effect);
141
+ if (def === undefined) {
142
+ throw new ParticleError(`unknown particle effect ${JSON.stringify(effect)}`);
143
+ }
144
+ return def;
145
+ }
146
+
147
+ function frozenSnapshot(effect: string): ParticleDiagnostics {
148
+ const d = diagnostics.get(effect);
149
+ return d === undefined
150
+ ? Object.freeze({ active: 0, emitted: 0, dropped: 0, recycled: 0 })
151
+ : Object.freeze({ ...d });
152
+ }
153
+
154
+ function emit(effect: string, command: unknown): void {
155
+ // T15-F6: validate effect key and command BEFORE the paused policy so
156
+ // malformed input is never silently swallowed as a paused drop.
157
+ const def = requireEffect(effect);
158
+ assertCommand(command);
159
+ if (status === 'disposed') {
160
+ throw new ParticleError('particle system is disposed');
161
+ }
162
+ if (status === 'paused') {
163
+ const d = diagnostics.get(effect)!;
164
+ diagnostics.set(effect, {
165
+ ...d,
166
+ dropped: d.dropped + def.burst.count,
167
+ });
168
+ return;
169
+ }
170
+
171
+ const pool = pools.get(effect)!;
172
+ const diag = diagnostics.get(effect)!;
173
+ const rng = createRng(command.seed >>> 0);
174
+ let emitted = 0;
175
+ let dropped = 0;
176
+ let recycled = 0;
177
+
178
+ for (let i = 0; i < def.burst.count; i++) {
179
+ let slotIndex = -1;
180
+ let oldestIndex = -1;
181
+ let oldestSeq = Number.POSITIVE_INFINITY;
182
+ for (let s = 0; s < pool.length; s++) {
183
+ const slot = pool[s]!;
184
+ if (!slot.active) {
185
+ slotIndex = s;
186
+ break;
187
+ }
188
+ if (slot.spawnSequence < oldestSeq) {
189
+ oldestSeq = slot.spawnSequence;
190
+ oldestIndex = s;
191
+ }
192
+ }
193
+ if (slotIndex === -1) {
194
+ if (def.overflow === 'drop-new') {
195
+ dropped++;
196
+ continue;
197
+ }
198
+ slotIndex = oldestIndex;
199
+ if (slotIndex === -1) {
200
+ dropped++;
201
+ continue;
202
+ }
203
+ recycled++;
204
+ }
205
+ emitted++;
206
+ const slot = pool[slotIndex]!;
207
+ const sampled = sampleInitialSlot(rng, def, command.position, spawnSequence++, effect);
208
+ slot.active = true;
209
+ slot.age = 0;
210
+ slot.bornAt = activeClock;
211
+ slot.lifetime = sampled.lifetime;
212
+ slot.originX = sampled.origin.x;
213
+ slot.originY = sampled.origin.y;
214
+ slot.vx = sampled.velocity.x;
215
+ slot.vy = sampled.velocity.y;
216
+ slot.rotation = sampled.rotation;
217
+ slot.rotationSpeed = sampled.rotationSpeed;
218
+ slot.scaleStart = sampled.scaleStart;
219
+ slot.scaleEnd = sampled.scaleEnd;
220
+ slot.color = sampled.color;
221
+ slot.spawnSequence = sampled.spawnSequence;
222
+ const log = emissionsLog.get(effect)!;
223
+ log.push(Object.freeze({
224
+ bornAt: activeClock,
225
+ originX: sampled.origin.x,
226
+ originY: sampled.origin.y,
227
+ vx: sampled.velocity.x,
228
+ vy: sampled.velocity.y,
229
+ rotation: sampled.rotation,
230
+ rotationSpeed: sampled.rotationSpeed,
231
+ scaleStart: sampled.scaleStart,
232
+ scaleEnd: sampled.scaleEnd,
233
+ lifetime: sampled.lifetime,
234
+ spawnSequence: sampled.spawnSequence,
235
+ }));
236
+ if (log.length > def.capacity * 4) log.splice(0, log.length - def.capacity * 4);
237
+ }
238
+
239
+ let active = 0;
240
+ for (const s of pool) if (s.active) active++;
241
+ diagnostics.set(effect, {
242
+ active,
243
+ emitted: diag.emitted + emitted,
244
+ dropped: diag.dropped + dropped,
245
+ recycled: diag.recycled + recycled,
246
+ });
247
+ // T15-RF3: an accepted emission may end an idle period — wake the driver.
248
+ if (emitted > 0) {
249
+ registryRevision++;
250
+ if (wakeListener) wakeListener();
251
+ }
252
+ }
253
+
254
+ function stepClock(deltaSeconds: number): void {
255
+ if (status === 'disposed') throw new ParticleError('particle system is disposed');
256
+ if (status === 'paused') return;
257
+ if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
258
+ throw new ParticleError('deltaSeconds must be a finite number >= 0');
259
+ }
260
+ activeClock += deltaSeconds;
261
+ for (const [name, pool] of pools) {
262
+ let active = 0;
263
+ for (const slot of pool) {
264
+ if (!slot.active) continue;
265
+ slot.age += deltaSeconds;
266
+ if (slot.age >= slot.lifetime) {
267
+ // T15-TF1: expiry IS a registry membership change — bump so the
268
+ // pruned registry ships before the driver sleeps.
269
+ slot.active = false;
270
+ registryRevision++;
271
+ continue;
272
+ }
273
+ active++;
274
+ }
275
+ const d = diagnostics.get(name)!;
276
+ if (d.active !== active) diagnostics.set(name, { ...d, active });
277
+ }
278
+ }
279
+
280
+ let driverOwned = false;
281
+ let releaseDriver: (() => void) | null = null;
282
+ let wakeListener: (() => void) | null = null;
283
+
284
+ function totalActive(): number {
285
+ let n = 0;
286
+ for (const pool of pools.values()) {
287
+ for (const slot of pool) if (slot.active) n++;
288
+ }
289
+ return n;
290
+ }
291
+
292
+ function advance(deltaSeconds: number): void {
293
+ stepClock(deltaSeconds);
294
+ }
295
+
296
+ /**
297
+ * Public headless step (GS-PARTICLE-01): rejected while a presentation
298
+ * driver owns the clock — stepping here as well would double-step every
299
+ * driver step. Release the driver to resume headless updates.
300
+ */
301
+ function update(deltaSeconds: number): void {
302
+ if (driverOwned) {
303
+ throw new ParticleError(
304
+ 'presentation clock is owned by an acquired driver — use the driver handle to step',
305
+ );
306
+ }
307
+ stepClock(deltaSeconds);
308
+ }
309
+
310
+ function buildRegistry(): ParticleUiRegistry {
311
+ const effects: Record<string, { capacity: number; particles: ParticleEmissionRecord[] }> = {};
312
+ for (const [name, def] of definitions) {
313
+ // GS-PARTICLE-01: the terminal registry after disposal is empty, not
314
+ // an error — the emissions log is cleared on dispose, and scheduled
315
+ // presentation callbacks may still read once before they detach.
316
+ const log = emissionsLog.get(name) ?? [];
317
+ const pool = pools.get(name)!;
318
+ // Only ACTIVE emissions are shipped; expired entries are pruned here so
319
+ // the registry stays bounded by live particles.
320
+ const activeSeqs = new Set<number>();
321
+ for (const slot of pool) if (slot.active) activeSeqs.add(slot.spawnSequence);
322
+ effects[name] = {
323
+ capacity: def.capacity,
324
+ particles: log.filter((r) => activeSeqs.has(r.spawnSequence)),
325
+ };
326
+ }
327
+ return { registryRevision, activeClock, effects };
328
+ }
329
+
330
+ const binding: ParticlePresentationBinding = {
331
+ systemGeneration: generation,
332
+ definition(effect: string): ParticleEffectDefinition {
333
+ return requireEffect(effect);
334
+ },
335
+ effects: Object.freeze([...definitions.keys()]),
336
+ tick(deltaSeconds: number): void {
337
+ if (driverOwned) {
338
+ throw new ParticleError(
339
+ 'presentation clock is owned by an acquired driver — use the driver handle to step',
340
+ );
341
+ }
342
+ if (status === 'disposed') return;
343
+ advance(deltaSeconds);
344
+ },
345
+ acquireDriver(): ParticleDriverHandle {
346
+ if (driverOwned) {
347
+ throw new ParticleError('presentation clock already owned — one driver per system');
348
+ }
349
+ if (status === 'disposed') {
350
+ throw new ParticleError('particle system is disposed');
351
+ }
352
+ driverOwned = true;
353
+ let released = false;
354
+ releaseDriver = () => {
355
+ driverOwned = false;
356
+ };
357
+ const handle: ParticleDriverHandle = {
358
+ step(deltaSeconds: number): void {
359
+ if (released || status === 'disposed') return;
360
+ advance(deltaSeconds);
361
+ },
362
+ isIdle(): boolean {
363
+ return totalActive() === 0;
364
+ },
365
+ setWakeListener(listener: (() => void) | null): void {
366
+ wakeListener = listener;
367
+ },
368
+ release(): void {
369
+ if (released) return;
370
+ released = true;
371
+ wakeListener = null;
372
+ if (releaseDriver) {
373
+ const r = releaseDriver;
374
+ releaseDriver = null;
375
+ r();
376
+ }
377
+ },
378
+ };
379
+ return handle;
380
+ },
381
+ get driverOwned() {
382
+ return driverOwned;
383
+ },
384
+ get registryRevision() {
385
+ return registryRevision;
386
+ },
387
+ get activeClock() {
388
+ return activeClock;
389
+ },
390
+ get activeCount() {
391
+ return status === 'disposed' ? 0 : totalActive();
392
+ },
393
+ buildUiRegistry(): ParticleUiRegistry {
394
+ return Object.freeze(buildRegistry());
395
+ },
396
+ emissions(effect: string) {
397
+ requireEffect(effect);
398
+ const log = emissionsLog.get(effect);
399
+ if (log === undefined) throw new ParticleError(`unknown particle effect ${JSON.stringify(effect)}`);
400
+ return log.slice();
401
+ },
402
+ };
403
+ Object.freeze(binding);
404
+
405
+ // Expose the UI registry builder to the presentation layer without adding
406
+ // it to the public binding type: the hook receives it via a typed symbol
407
+ // free accessor defined below.
408
+
409
+
410
+ const system: ParticleSystem<TEffects> = {
411
+ emit: emit as ParticleSystem<TEffects>['emit'],
412
+ update,
413
+ pause(): void {
414
+ if (status === 'disposed') throw new ParticleError('particle system is disposed');
415
+ status = 'paused';
416
+ },
417
+ resume(): void {
418
+ if (status === 'disposed') throw new ParticleError('particle system is disposed');
419
+ status = 'running';
420
+ },
421
+ dispose(): void {
422
+ if (status === 'disposed') return;
423
+ status = 'disposed';
424
+ generation++;
425
+ if (wakeListener) wakeListener = null;
426
+ if (releaseDriver) {
427
+ const r = releaseDriver;
428
+ releaseDriver = null;
429
+ driverOwned = false;
430
+ r();
431
+ }
432
+ for (const pool of pools.values()) {
433
+ for (const slot of pool) {
434
+ slot.active = false;
435
+ slot.age = 0;
436
+ slot.spawnSequence = -1;
437
+ }
438
+ }
439
+ emissionsLog.clear();
440
+ registryRevision++;
441
+ for (const [name, d] of diagnostics) {
442
+ diagnostics.set(name, { ...d, active: 0 });
443
+ }
444
+ },
445
+ getDiagnostics(effect?: keyof TEffects & string): ParticleDiagnostics {
446
+ if (effect !== undefined) {
447
+ requireEffect(effect);
448
+ return frozenSnapshot(effect);
449
+ }
450
+ let active = 0;
451
+ let emitted = 0;
452
+ let dropped = 0;
453
+ let recycled = 0;
454
+ for (const d of diagnostics.values()) {
455
+ active += d.active;
456
+ emitted += d.emitted;
457
+ dropped += d.dropped;
458
+ recycled += d.recycled;
459
+ }
460
+ return Object.freeze({ active, emitted, dropped, recycled });
461
+ },
462
+ getActiveParticles(effect: keyof TEffects & string): readonly ParticleSlotSnapshot[] {
463
+ requireEffect(effect);
464
+ if (status === 'disposed') return [];
465
+ const def = definitions.get(effect)!;
466
+ const out: ParticleSlotSnapshot[] = [];
467
+ for (const slot of pools.get(effect)!) {
468
+ if (!slot.active) continue;
469
+ const sampled = sampleSlotAtAge(ageView(slot), def, slot.age);
470
+ const origin = { x: slot.originX, y: slot.originY };
471
+ out.push(
472
+ Object.freeze({
473
+ active: true,
474
+ age: slot.age,
475
+ lifetime: slot.lifetime,
476
+ origin: Object.freeze(origin),
477
+ position: Object.freeze({ x: sampled.x, y: sampled.y }),
478
+ velocity: Object.freeze({ x: slot.vx, y: slot.vy }),
479
+ rotation: sampled.rotation,
480
+ scale: sampled.scale,
481
+ opacity: sampled.opacity,
482
+ color: slot.color,
483
+ spawnSequence: slot.spawnSequence,
484
+ effect,
485
+ }),
486
+ );
487
+ }
488
+ return out;
489
+ },
490
+ pauseIfRunning(): void {
491
+ if (status === 'running') status = 'paused';
492
+ },
493
+ resumeIfPaused(): void {
494
+ if (status === 'paused') status = 'running';
495
+ },
496
+ get status() {
497
+ return status;
498
+ },
499
+ bindPresentation(): ParticlePresentationBinding {
500
+ if (status === 'disposed') throw new ParticleError('particle system is disposed');
501
+ return binding;
502
+ },
503
+ };
504
+ return Object.freeze(system);
505
+ }
@@ -0,0 +1,164 @@
1
+ import { ParticleError } from './errors';
2
+ import type {
3
+ ParticleEffectDefinition,
4
+ ParticleEffectDefinitionInput,
5
+ ParticleScaleEnvelope,
6
+ Range,
7
+ } from './types';
8
+
9
+ function assertRange(range: unknown, name: string): void {
10
+ if (!range || typeof range !== 'object') throw new ParticleError(`${name} must be {min,max}`);
11
+ const r = range as { min: unknown; max: unknown };
12
+ if (typeof r.min !== 'number' || !Number.isFinite(r.min)) throw new ParticleError(`${name}.min must be finite`);
13
+ if (typeof r.max !== 'number' || !Number.isFinite(r.max)) throw new ParticleError(`${name}.max must be finite`);
14
+ if (r.min > r.max) throw new ParticleError(`${name}.min must be <= max`);
15
+ }
16
+
17
+ function assertPositiveSize(value: unknown, name: string): void {
18
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
19
+ throw new ParticleError(`${name} must be a finite number greater than zero`);
20
+ }
21
+ }
22
+
23
+ function assertNonnegative(value: unknown, name: string): void {
24
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
25
+ throw new ParticleError(`${name} must be a finite number greater than or equal to zero`);
26
+ }
27
+ }
28
+
29
+ function assertPoint(p: unknown, name: string): void {
30
+ if (!p || typeof p !== 'object') throw new ParticleError(`${name} must be {x,y}`);
31
+ const pt = p as { x: unknown; y: unknown };
32
+ if (typeof pt.x !== 'number' || !Number.isFinite(pt.x)) throw new ParticleError(`${name}.x must be finite`);
33
+ if (typeof pt.y !== 'number' || !Number.isFinite(pt.y)) throw new ParticleError(`${name}.y must be finite`);
34
+ }
35
+
36
+ /**
37
+ * Validate one T20.1 envelope endpoint and normalize it to a frozen Range.
38
+ * Endpoints are non-negative and finite (T20A-R1): zero collapses to a point;
39
+ * negative scale has no defined rendering semantics and is rejected.
40
+ */
41
+ function normalizeScaleEndpoint(value: unknown, name: string): Range {
42
+ if (typeof value === 'number') {
43
+ if (!Number.isFinite(value)) throw new ParticleError(`${name} must be finite`);
44
+ if (value < 0) throw new ParticleError(`${name} must be >= 0`);
45
+ return Object.freeze({ min: value, max: value });
46
+ }
47
+ assertRange(value, name);
48
+ const r = value as Range;
49
+ if (r.min < 0 || r.max < 0) throw new ParticleError(`${name} must be >= 0`);
50
+ return Object.freeze({ min: r.min, max: r.max });
51
+ }
52
+
53
+ export function defineParticleEffect(def: ParticleEffectDefinitionInput): ParticleEffectDefinition {
54
+ if (!def || typeof def !== 'object') throw new ParticleError('effect definition must be an object');
55
+ if (typeof def.capacity !== 'number' || !Number.isFinite(def.capacity) || def.capacity < 1 || def.capacity > 1024 || Math.floor(def.capacity) !== def.capacity) {
56
+ throw new ParticleError('capacity must be integer 1..1024');
57
+ }
58
+ if (def.space !== 'world' && def.space !== 'screen') throw new ParticleError('space must be world or screen');
59
+ if (def.overflow !== 'drop-new' && def.overflow !== 'recycle-oldest') throw new ParticleError('overflow must be drop-new or recycle-oldest');
60
+ if (!def.particle || typeof def.particle !== 'object') throw new ParticleError('particle must be object');
61
+ if (def.particle.kind === 'sprite') {
62
+ if (typeof def.particle.sheet !== 'string' || !def.particle.sheet) throw new ParticleError('sprite sheet must be non-empty string');
63
+ if (typeof def.particle.frame !== 'string' || !def.particle.frame) throw new ParticleError('sprite frame must be non-empty string');
64
+ // GS-PARTICLE-03: drawable dimensions only — NaN/Infinity/zero would
65
+ // reach native drawing or break the uniform-ratio check.
66
+ if (!def.particle.size || typeof def.particle.size !== 'object') {
67
+ throw new ParticleError('sprite size must be {width,height}');
68
+ }
69
+ assertPositiveSize(def.particle.size.width, 'sprite size.width');
70
+ assertPositiveSize(def.particle.size.height, 'sprite size.height');
71
+ } else if (def.particle.kind === 'shape') {
72
+ if (def.particle.shape !== 'circle' && def.particle.shape !== 'rectangle') throw new ParticleError('shape must be circle or rectangle');
73
+ // GS-PARTICLE-03: shape dimensions validate by presence — absent
74
+ // fields keep their render defaults, present fields must be finite and
75
+ // drawable (radius zero collapses to a point, like scale zero).
76
+ if (def.particle.shape === 'circle') {
77
+ if (def.particle.radius !== undefined) assertNonnegative(def.particle.radius, 'circle radius');
78
+ if (def.particle.width !== undefined || def.particle.height !== undefined) {
79
+ throw new ParticleError('circle particles take radius, not width/height');
80
+ }
81
+ } else {
82
+ if (def.particle.width !== undefined) assertPositiveSize(def.particle.width, 'rectangle width');
83
+ if (def.particle.height !== undefined) assertPositiveSize(def.particle.height, 'rectangle height');
84
+ if (def.particle.radius !== undefined) {
85
+ throw new ParticleError('rectangle particles take width/height, not radius');
86
+ }
87
+ }
88
+ if (def.particle.color !== undefined && typeof def.particle.color !== 'string') {
89
+ throw new ParticleError('shape color must be a string');
90
+ }
91
+ } else {
92
+ throw new ParticleError('particle.kind must be sprite or shape');
93
+ }
94
+ // GS-PARTICLE-03: integer counts only — a fractional count would emit
95
+ // ceil(count) particles from a loop bound that promises otherwise.
96
+ if (!def.burst || typeof def.burst.count !== 'number' || !Number.isFinite(def.burst.count) || !Number.isInteger(def.burst.count) || def.burst.count < 1 || def.burst.count > def.capacity) {
97
+ throw new ParticleError('burst.count must be an integer 1..capacity');
98
+ }
99
+ assertRange(def.lifetimeSeconds, 'lifetimeSeconds');
100
+ if (def.lifetimeSeconds.min <= 0) throw new ParticleError('lifetimeSeconds.min must be >0');
101
+ assertRange(def.speed, 'speed');
102
+ if (def.speed.min < 0) throw new ParticleError('speed.min must be >=0');
103
+ // GS-PARTICLE-03: optional ranges validate by PRESENCE — a null or
104
+ // otherwise present-but-invalid value must fail, not fall through.
105
+ if (def.direction !== undefined) assertRange(def.direction, 'direction');
106
+ if (def.rotation !== undefined) assertRange(def.rotation, 'rotation');
107
+ // T20A-R2: optional scale fields validate by PRESENCE, not truthiness —
108
+ // raw JavaScript values such as null, false, or 0 must fail at the boundary
109
+ // instead of silently falling back to the default scale.
110
+ const hasLegacyScale = def.scaleOverLife !== undefined;
111
+ const hasExplicitScale = def.scale !== undefined;
112
+ if (hasLegacyScale && hasExplicitScale) {
113
+ throw new ParticleError('scaleOverLife and scale are mutually exclusive — specify only one');
114
+ }
115
+ if (hasLegacyScale) {
116
+ const legacy = def.scaleOverLife;
117
+ if (legacy === null || typeof legacy !== 'object' || Array.isArray(legacy)) {
118
+ throw new ParticleError('scaleOverLife must be {min,max}');
119
+ }
120
+ assertRange(legacy, 'scaleOverLife');
121
+ }
122
+ let scale: ParticleScaleEnvelope | undefined;
123
+ if (hasExplicitScale) {
124
+ const raw = def.scale;
125
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
126
+ throw new ParticleError('scale must be an object with start and end endpoints');
127
+ }
128
+ if (!('start' in raw) || !('end' in raw)) {
129
+ throw new ParticleError('scale must specify start and end endpoints');
130
+ }
131
+ scale = Object.freeze({
132
+ start: normalizeScaleEndpoint(raw.start, 'scale.start'),
133
+ end: normalizeScaleEndpoint(raw.end, 'scale.end'),
134
+ });
135
+ }
136
+ assertPoint(def.gravity, 'gravity');
137
+ if (typeof def.fadeOut !== 'boolean') throw new ParticleError('fadeOut must be boolean');
138
+ return Object.freeze(
139
+ {
140
+ ...def,
141
+ // GS-PARTICLE-03: the nested size record is cloned, never aliased —
142
+ // caller mutation after definition cannot reach the system.
143
+ particle: Object.freeze({
144
+ ...def.particle,
145
+ ...('size' in def.particle && def.particle.size !== undefined
146
+ ? { size: Object.freeze({ ...def.particle.size as { width: number; height: number } }) }
147
+ : {}),
148
+ }),
149
+ burst: Object.freeze({ ...def.burst }),
150
+ lifetimeSeconds: Object.freeze({ ...def.lifetimeSeconds }),
151
+ speed: Object.freeze({ ...def.speed }),
152
+ gravity: Object.freeze({ ...def.gravity }),
153
+ // T20.1: every nested range in the normalized definition is frozen.
154
+ ...(def.direction ? { direction: Object.freeze({ ...def.direction }) } : {}),
155
+ ...(def.rotation ? { rotation: Object.freeze({ ...def.rotation }) } : {}),
156
+ // T20A-R2: normalized scale fields always overwrite the raw input
157
+ // values so an unchecked optional can never survive the input spread.
158
+ scaleOverLife: hasLegacyScale
159
+ ? Object.freeze({ ...def.scaleOverLife })
160
+ : undefined,
161
+ scale,
162
+ } as ParticleEffectDefinition,
163
+ );
164
+ }
@@ -0,0 +1,6 @@
1
+ export class ParticleError extends Error {
2
+ override name = 'ParticleError';
3
+ constructor(message: string) {
4
+ super(message);
5
+ }
6
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './errors';
3
+ export * from './defineParticleEffect';
4
+ export * from './createParticleSystem';
5
+ export * from './sampling';