rn-gamekit 0.1.0 → 0.2.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 (601) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +95 -14
  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 +94 -4
  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
@@ -1,3 +1,4 @@
1
+ import type { AssetGroupMap } from '../../assets/types';
1
2
  import type { GameDefinition, InputMap, SceneMap } from '../../definition/types';
2
3
  import type { SceneTransitionController } from '../../scene/types';
3
4
  import { createAnimationFrameDriver, type FrameDriver, type FrameHandle } from '../frameDriver';
@@ -14,8 +15,54 @@ import {
14
15
  } from './types';
15
16
  import type { SessionDiagnostics } from './diagnostics';
16
17
  import { createDeepFreeze, type DeepFreezer } from './deepFreeze';
18
+ import type { GameEventDescriptor } from '../../events/types';
19
+ import type { AnyGameEventEnvelope } from '../../events/types';
20
+ import { GameEventError } from '../../events/errors';
21
+ import { cloneAndValidatePayload } from '../../events/payload';
17
22
 
18
23
  const DEFAULT_FIXED_STEP_MS = 1000 / 60;
24
+
25
+ /**
26
+ * A captured failure: an explicit boolean record so an arbitrary thrown
27
+ * value (including `undefined`) is never confused with "no failure".
28
+ */
29
+ interface CapturedFailure {
30
+ readonly failed: true;
31
+ readonly value: unknown;
32
+ }
33
+
34
+ type FailureRecord = { readonly failed: false } | CapturedFailure;
35
+
36
+ const NO_FAILURE: FailureRecord = { failed: false };
37
+
38
+ /** Run a closure and capture its thrown value, whatever its type. */
39
+ function captureFailure(run: () => void): FailureRecord {
40
+ try {
41
+ run();
42
+ return NO_FAILURE;
43
+ } catch (error) {
44
+ return { failed: true, value: error };
45
+ }
46
+ }
47
+
48
+ /** Surface a captured failure, throwing `undefined` if that was the value. */
49
+ function throwFailure(record: FailureRecord): void {
50
+ if (record.failed) {
51
+ throw record.value;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Immutably compose two failures into one lossless error: an AggregateError
57
+ * with a stable message and ordered `errors`. Neither thrown value is
58
+ * mutated or wrapped away.
59
+ */
60
+ function composeFailures(first: unknown, second: unknown): unknown {
61
+ return new AggregateError(
62
+ [first, second],
63
+ 'Lifecycle operation failed with multiple errors',
64
+ );
65
+ }
19
66
  const DEFAULT_MAX_CATCH_UP_STEPS = 5;
20
67
 
21
68
  interface SessionOptions {
@@ -30,11 +77,14 @@ interface SessionOptions {
30
77
  interface ErasedScene {
31
78
  readonly actions: readonly string[];
32
79
  readonly transitions: readonly string[];
80
+ readonly emits?: readonly string[];
81
+ readonly events?: unknown;
33
82
  create(): unknown;
34
83
  update(frame: {
35
84
  readonly state: unknown;
36
85
  readonly input: InputFrame<string>;
37
86
  readonly transition: SceneTransitionController<string>;
87
+ readonly events: { emit(name: string, payload: unknown): void };
38
88
  readonly tick: number;
39
89
  readonly sceneTick: number;
40
90
  readonly deltaSeconds: number;
@@ -50,6 +100,11 @@ interface ActiveScene {
50
100
  readonly definition: ErasedScene;
51
101
  state: unknown;
52
102
  sceneTick: number;
103
+ /**
104
+ * Set before user cleanup runs: a throwing disposer never runs again —
105
+ * not on retry, not on session disposal — for this scene instance.
106
+ */
107
+ disposalAttempted: boolean;
53
108
  }
54
109
 
55
110
  type TransitionIntent =
@@ -84,9 +139,10 @@ export function createGameSession<
84
139
  const TScenes extends SceneMap,
85
140
  const TInput extends InputMap,
86
141
  const TInitialScene extends keyof TScenes,
142
+ const TEventDefs extends Record<string, GameEventDescriptor<unknown>> = Record<string, never>,
87
143
  >(
88
- definition: GameDefinition<TScenes, TInput, TInitialScene>,
89
- ): GameSession<TScenes, TInput> {
144
+ definition: GameDefinition<TScenes, TInput, TInitialScene, AssetGroupMap, TEventDefs>,
145
+ ): GameSession<TScenes, TInput, TEventDefs> {
90
146
  return createGameSessionWithDriver(definition, {
91
147
  frameDriver: createAnimationFrameDriver(),
92
148
  });
@@ -97,10 +153,11 @@ export function createGameSessionWithDriver<
97
153
  const TScenes extends SceneMap,
98
154
  const TInput extends InputMap,
99
155
  const TInitialScene extends keyof TScenes,
156
+ const TEventDefs extends Record<string, GameEventDescriptor<unknown>> = Record<string, never>,
100
157
  >(
101
- definition: GameDefinition<TScenes, TInput, TInitialScene>,
158
+ definition: GameDefinition<TScenes, TInput, TInitialScene, AssetGroupMap, TEventDefs>,
102
159
  options: SessionOptions,
103
- ): GameSession<TScenes, TInput> {
160
+ ): GameSession<TScenes, TInput, TEventDefs> {
104
161
  const fixedStepMs = options.fixedStepMs ?? DEFAULT_FIXED_STEP_MS;
105
162
  const maxCatchUpSteps = options.maxCatchUpSteps ?? DEFAULT_MAX_CATCH_UP_STEPS;
106
163
  const maxFrameDeltaMs = options.maxFrameDeltaMs ?? fixedStepMs * DEFAULT_MAX_CATCH_UP_STEPS;
@@ -119,6 +176,37 @@ export function createGameSessionWithDriver<
119
176
  throw new RangeError('maxFrameDeltaMs must be a finite positive number');
120
177
  }
121
178
 
179
+ // T13: event definitions and per-scene emit declarations.
180
+ const eventDefs = (definition as unknown as { events?: Record<string, GameEventDescriptor<unknown>> }).events;
181
+ const hasEvents = eventDefs !== undefined && eventDefs !== null;
182
+ const allowedEventNames = hasEvents ? new Set(Object.keys(eventDefs!)) : new Set<string>();
183
+ const allowedEmitsByScene = new Map<string, Set<string>>();
184
+ // Validate per-scene emits are declared events and that the scene's `events` reference matches the game's.
185
+ for (const [name, scene] of Object.entries(definition.scenes)) {
186
+ const erased = scene as unknown as ErasedScene;
187
+ const emits = erased.emits ?? [];
188
+ for (const ev of emits) {
189
+ if (!allowedEventNames.has(ev)) {
190
+ throw new Error(`Scene "${name}" declares unknown event "${ev}"`);
191
+ }
192
+ }
193
+ allowedEmitsByScene.set(name, new Set(emits as string[]));
194
+ // Runtime identity check for the branded event definitions.
195
+ if (erased.events !== undefined) {
196
+ if (!hasEvents) {
197
+ throw new Error(`Scene "${name}" is bound to events but the game has no events map`);
198
+ }
199
+ if (erased.events !== eventDefs) {
200
+ throw new Error(
201
+ `Scene "${name}" is bound to a different events object than the game. Pass the same defineGameEvents() result to both defineScene({ events }) and defineGame({ events }).`,
202
+ );
203
+ }
204
+ } else if (emits.length > 0) {
205
+ throw new Error(
206
+ `Scene "${name}" declares emits but is not bound to the game's events. Pass \`events\` to defineScene.`,
207
+ );
208
+ }
209
+ }
122
210
  const erasedSceneMap: Record<string, ErasedScene> = {};
123
211
  for (const [name, scene] of Object.entries(definition.scenes)) {
124
212
  const erased = scene as unknown as ErasedScene;
@@ -144,9 +232,16 @@ export function createGameSessionWithDriver<
144
232
  const initialState = freezeObject(initialDefinition.create());
145
233
  let initialSnapshot: DeepReadonly<unknown>;
146
234
  try {
147
- initialSnapshot = deepFreeze(initialDefinition.snapshot({ state: initialState }));
235
+ initialSnapshot = deepFreeze(initialDefinition.snapshot({ state: initialState }), {
236
+ scene: initialSceneName,
237
+ });
148
238
  } catch (error) {
149
- initialDefinition.dispose?.(initialState);
239
+ // GS-SCENE-02: the cleanup failure must not replace the snapshot
240
+ // failure — compose losslessly in a stable order instead.
241
+ const cleanupFailure = captureFailure(() => initialDefinition.dispose?.(initialState));
242
+ if (cleanupFailure.failed) {
243
+ throw composeFailures(error, cleanupFailure.value);
244
+ }
150
245
  throw error;
151
246
  }
152
247
 
@@ -155,6 +250,7 @@ export function createGameSessionWithDriver<
155
250
  definition: initialDefinition,
156
251
  state: initialState,
157
252
  sceneTick: 0,
253
+ disposalAttempted: false,
158
254
  };
159
255
  let sceneName: string = initialSceneName;
160
256
  let currentSnapshot: DeepReadonly<unknown> = initialSnapshot;
@@ -167,6 +263,12 @@ export function createGameSessionWithDriver<
167
263
  let accumulatorMs = 0;
168
264
  let tick = 0;
169
265
  let pendingTransition: TransitionIntent | undefined;
266
+ /**
267
+ * Set when an outgoing scene disposal throws: the retained scene is
268
+ * explicitly non-resumable — `start()` refuses to update it until a
269
+ * transition or restart replaces it. Cleared on every successful install.
270
+ */
271
+ let sceneFaulted = false;
170
272
  let hardCutPending = false;
171
273
  let publishedThisCallback = false;
172
274
  let updateInProgress = false;
@@ -182,6 +284,129 @@ export function createGameSessionWithDriver<
182
284
  stepMs: fixedStepMs,
183
285
  }) as unknown as CommitFrame<TScenes>;
184
286
  const listeners = new Set<(frame: CommitFrame<TScenes>) => void>();
287
+ const statusListeners = new Set<(status: GameSessionStatus) => void>();
288
+ let deliveringStatus = false;
289
+ const queuedStatuses: GameSessionStatus[] = [];
290
+ let releaseStatusListeners = false;
291
+
292
+ // T13: per-session event listeners.
293
+ const eventListeners = new Map<string, Set<(event: AnyGameEventEnvelope) => void | Promise<void>>>();
294
+
295
+ // Centralized, non-recursive error sink for game event listeners.
296
+ // A failing reporter must not pause the session or escape into the fixed-step loop.
297
+ const reportGameEventListenerError = (name: string, error: unknown): void => {
298
+ try {
299
+ console.error(`Game event listener for "${name}" threw:`, error);
300
+ } catch {
301
+ // Swallow reporter failures — the session must remain running and siblings must still run.
302
+ }
303
+ };
304
+
305
+ const deliverEvents = (envelopes: readonly AnyGameEventEnvelope[]): void => {
306
+ // Snapshot per tick: a listener added during this tick's delivery receives
307
+ // only later ticks, and a removal prevents only future ticks.
308
+ const snapshots = new Map<string, readonly ((event: AnyGameEventEnvelope) => void | Promise<void>)[]>();
309
+ for (const env of envelopes) {
310
+ if (!snapshots.has(env.name)) {
311
+ const set = eventListeners.get(env.name);
312
+ snapshots.set(env.name, set ? [...set] : []);
313
+ }
314
+ }
315
+ for (const envelope of envelopes) {
316
+ const snapshot = snapshots.get(envelope.name) ?? [];
317
+ for (const listener of snapshot) {
318
+ let result: unknown;
319
+ try {
320
+ result = listener(envelope);
321
+ } catch (error) {
322
+ reportGameEventListenerError(envelope.name, error);
323
+ continue;
324
+ }
325
+ if (result !== null && typeof result === 'object' && typeof (result as Promise<unknown>).then === 'function') {
326
+ (result as Promise<unknown>).catch((error) => {
327
+ reportGameEventListenerError(envelope.name, error);
328
+ });
329
+ }
330
+ }
331
+ }
332
+ };
333
+
334
+ const NO_EVENT_EMITTER = Object.freeze({
335
+ emit() {
336
+ throw new GameEventError('This game has no events or this scene does not emit any events');
337
+ },
338
+ }) as { emit(name: string, payload: unknown): void };
339
+
340
+ // Deliver one transition pass to a snapshot. A throwing listener never
341
+ // aborts the pass: the remaining snapshot listeners still run, and the
342
+ // first failure is rethrown once every queued transition has been
343
+ // delivered (the transition itself is already authoritative).
344
+ const deliverStatusPass = (current: GameSessionStatus): FailureRecord => {
345
+ let firstFailure: FailureRecord = NO_FAILURE;
346
+ for (const listener of [...statusListeners]) {
347
+ try {
348
+ listener(current);
349
+ } catch (error) {
350
+ if (!firstFailure.failed) {
351
+ firstFailure = { failed: true, value: error };
352
+ }
353
+ }
354
+ }
355
+ return firstFailure;
356
+ };
357
+
358
+ const notifyStatus = (): void => {
359
+ if (deliveringStatus) {
360
+ // Re-entrant transition from inside a listener: queue it so every
361
+ // listener observes complete states, never a half-applied transition.
362
+ queuedStatuses.push(status);
363
+ return;
364
+ }
365
+ deliveringStatus = true;
366
+ let firstFailure: FailureRecord = NO_FAILURE;
367
+ try {
368
+ const passFailure = deliverStatusPass(status);
369
+ if (!firstFailure.failed) {
370
+ firstFailure = passFailure;
371
+ }
372
+ while (queuedStatuses.length > 0) {
373
+ const queued = queuedStatuses.shift() as GameSessionStatus;
374
+ const queuedFailure = deliverStatusPass(queued);
375
+ if (!firstFailure.failed) {
376
+ firstFailure = queuedFailure;
377
+ }
378
+ }
379
+ } finally {
380
+ deliveringStatus = false;
381
+ if (releaseStatusListeners) {
382
+ releaseStatusListeners = false;
383
+ statusListeners.clear();
384
+ diagnostics?.onStatusListenerCount?.(statusListeners.size);
385
+ }
386
+ }
387
+ throwFailure(firstFailure);
388
+ };
389
+
390
+ // Status becomes authoritative only after the command's scheduling and
391
+ // input side effects are safe; listeners then observe the committed value.
392
+ const setStatus = (next: GameSessionStatus): void => {
393
+ if (status === next) {
394
+ return;
395
+ }
396
+ status = next;
397
+ notifyStatus();
398
+ };
399
+
400
+ // Error-path pause: complete the transition and notify, but never let a
401
+ // status-listener failure mask the original error that caused the pause.
402
+ const pauseAndRethrow = (original: unknown): never => {
403
+ try {
404
+ pauseInternal();
405
+ } catch {
406
+ // The original error is the actionable one; listeners were notified.
407
+ }
408
+ throw original;
409
+ };
185
410
 
186
411
  const assertLive = () => {
187
412
  if (status === 'disposed') {
@@ -189,7 +414,9 @@ export function createGameSessionWithDriver<
189
414
  }
190
415
  };
191
416
  const isDisposed = () => status === 'disposed';
192
- const inputBuffer = createInputBuffer(definition.input, assertLive);
417
+ // T10.4: gameplay input is rejected at the shared boundary while paused
418
+ // (before the queue and before the accepted-input diagnostics).
419
+ const inputBuffer = createInputBuffer(definition.input, assertLive, () => status === 'paused');
193
420
 
194
421
  // Simulation commits are the only presentation updates (T5): a commit
195
422
  // happens on the initial baseline, after every presentation callback that
@@ -226,7 +453,6 @@ export function createGameSessionWithDriver<
226
453
  if (status !== 'running') {
227
454
  return;
228
455
  }
229
- status = 'paused';
230
456
  generation += 1;
231
457
  if (frameHandle !== undefined) {
232
458
  options.frameDriver.cancelFrame(frameHandle);
@@ -235,15 +461,28 @@ export function createGameSessionWithDriver<
235
461
  previousTimestampMs = undefined;
236
462
  accumulatorMs = 0;
237
463
  inputBuffer.reset();
464
+ setStatus('paused');
238
465
  };
239
466
 
240
467
  const commitOrPause = () => {
241
468
  try {
242
469
  commit();
243
470
  } catch (error) {
244
- pauseInternal();
245
- throw error;
471
+ pauseAndRethrow(error);
472
+ }
473
+ };
474
+
475
+ /**
476
+ * Dispose one scene instance exactly once. The attempt flag is set before
477
+ * user cleanup runs, so a throwing disposer is never retried for this
478
+ * instance — by transition retries or by session disposal.
479
+ */
480
+ const disposeSceneInstance = (scene: ActiveScene, state: unknown): void => {
481
+ if (scene.disposalAttempted) {
482
+ return;
246
483
  }
484
+ scene.disposalAttempted = true;
485
+ scene.definition.dispose?.(state);
247
486
  };
248
487
 
249
488
  /**
@@ -277,7 +516,9 @@ export function createGameSessionWithDriver<
277
516
  try {
278
517
  targetState = freezeObject(targetDefinition.create());
279
518
  targetCreated = true;
280
- targetSnapshot = deepFreeze(targetDefinition.snapshot({ state: targetState }));
519
+ targetSnapshot = deepFreeze(targetDefinition.snapshot({ state: targetState }), {
520
+ scene: targetName,
521
+ });
281
522
  } catch (error) {
282
523
  if (targetCreated) {
283
524
  try {
@@ -290,15 +531,17 @@ export function createGameSessionWithDriver<
290
531
  }
291
532
 
292
533
  try {
293
- activeScene.definition.dispose?.(outgoingFinalState);
534
+ disposeSceneInstance(activeScene, outgoingFinalState);
294
535
  } catch (error) {
295
- // The outgoing scene's dispose already ran (possibly partially) and is
296
- // not retried; the old scene remains active with that honest caveat.
536
+ // The outgoing scene's dispose already ran (possibly partially) and
537
+ // is never retried; the old scene remains active but explicitly
538
+ // faulted. The prepared target is still cleaned up, and a failing
539
+ // target cleanup composes losslessly with the outgoing failure.
540
+ sceneFaulted = true;
297
541
  if (targetCreated) {
298
- try {
299
- targetDefinition.dispose?.(targetState);
300
- } catch {
301
- // Best effort.
542
+ const targetFailure = captureFailure(() => targetDefinition.dispose?.(targetState));
543
+ if (targetFailure.failed) {
544
+ throw composeFailures(error, targetFailure.value);
302
545
  }
303
546
  }
304
547
  throw error;
@@ -312,7 +555,9 @@ export function createGameSessionWithDriver<
312
555
  definition: targetDefinition,
313
556
  state: targetState,
314
557
  sceneTick: 0,
558
+ disposalAttempted: false,
315
559
  };
560
+ sceneFaulted = false;
316
561
  sceneName = targetName;
317
562
  currentSnapshot = targetSnapshot;
318
563
  previousSnapshot = targetSnapshot;
@@ -383,6 +628,31 @@ export function createGameSessionWithDriver<
383
628
  });
384
629
  };
385
630
 
631
+ // One scheduler-failure policy for successor scheduling (GS-SESSION-02).
632
+ // The initial `start()` path keeps its own restore-previous-status policy
633
+ // because no running state was ever published there. On failure: clear the
634
+ // handle, invalidate the generation so late callbacks are no-ops,
635
+ // neutralize input via the pause path, and publish paused while preserving
636
+ // the scheduling error — a throwing status listener composes with it
637
+ // instead of masking it. Never retries automatically.
638
+ const failScheduling = (error: unknown): never => {
639
+ frameHandle = undefined;
640
+ try {
641
+ pauseInternal();
642
+ } catch (statusError) {
643
+ throw composeFailures(error, statusError);
644
+ }
645
+ throw error;
646
+ };
647
+
648
+ const scheduleSuccessor = (activeGeneration: number): void => {
649
+ try {
650
+ schedule(activeGeneration);
651
+ } catch (error) {
652
+ failScheduling(error);
653
+ }
654
+ };
655
+
386
656
  const schedule = (activeGeneration: number) => {
387
657
  frameHandle = options.frameDriver.requestFrame((timestampMs) => {
388
658
  if (status !== 'running' || generation !== activeGeneration) {
@@ -394,16 +664,17 @@ export function createGameSessionWithDriver<
394
664
  diagnostics.onDisplayCallback();
395
665
  }
396
666
 
397
- // A pending external transition commits at the next fixed-step boundary
398
- // without advancing simulation tick/time.
667
+ // A pending external transition commits at the next frame-driver
668
+ // callback — a zero-step callback may carry it — without advancing
669
+ // simulation tick/time. The target scene first updates at the next
670
+ // due fixed step; accumulated timing debt is preserved.
399
671
  if (pendingTransition !== undefined) {
400
672
  const pending = pendingTransition;
401
673
  pendingTransition = undefined;
402
674
  try {
403
675
  commitTransition(pending, activeScene.state, undefined);
404
676
  } catch (error) {
405
- pauseInternal();
406
- throw error;
677
+ pauseAndRethrow(error);
407
678
  }
408
679
  commitOrPause();
409
680
  if (status !== 'running' || generation !== activeGeneration) {
@@ -417,7 +688,7 @@ export function createGameSessionWithDriver<
417
688
  commitOrPause();
418
689
  }
419
690
  if (status === 'running' && generation === activeGeneration) {
420
- schedule(activeGeneration);
691
+ scheduleSuccessor(activeGeneration);
421
692
  }
422
693
  return;
423
694
  }
@@ -439,6 +710,42 @@ export function createGameSessionWithDriver<
439
710
  stepsRan = true;
440
711
  const nextTick = tick + 1;
441
712
  const nextSceneTick = activeScene.sceneTick + 1;
713
+ const sceneNameForTick = activeScene.name;
714
+ const allowedEmits = allowedEmitsByScene.get(sceneNameForTick) ?? new Set<string>();
715
+
716
+ // T13: per-tick transactional emitter.
717
+ let stagedThisTick: AnyGameEventEnvelope[] = [];
718
+ let ordinal = 0;
719
+ let emitterInvalidated = false;
720
+ const emitter: { emit(name: string, payload: unknown): void } = hasEvents
721
+ ? {
722
+ emit(name: string, payload: unknown) {
723
+ if (emitterInvalidated) {
724
+ throw new GameEventError('Game event emitter is only valid during its owning scene update');
725
+ }
726
+ if (!allowedEventNames.has(name)) {
727
+ throw new GameEventError(`Event "${name}" is not declared by this game`);
728
+ }
729
+ if (!allowedEmits.has(name)) {
730
+ throw new GameEventError(`Scene "${sceneNameForTick}" cannot emit event "${name}" (missing from its emits list)`);
731
+ }
732
+ const cloned = cloneAndValidatePayload(payload, name, 'payload');
733
+ const envelope = Object.freeze({
734
+ name,
735
+ payload: cloned,
736
+ tick: nextTick,
737
+ scene: sceneNameForTick,
738
+ sceneTick: nextSceneTick,
739
+ ordinal: ordinal++,
740
+ }) as AnyGameEventEnvelope;
741
+ stagedThisTick.push(envelope);
742
+ },
743
+ }
744
+ : (NO_EVENT_EMITTER as { emit(name: string, payload: unknown): void });
745
+ if (hasEvents) {
746
+ Object.freeze(emitter);
747
+ }
748
+
442
749
  const sampleStart = diagnostics === undefined ? 0 : now();
443
750
  const input = inputBuffer.sample();
444
751
  if (diagnostics !== undefined) {
@@ -448,18 +755,31 @@ export function createGameSessionWithDriver<
448
755
  activeUpdateScope = scope;
449
756
  updateInProgress = true;
450
757
  const updateStart = diagnostics === undefined ? 0 : now();
451
- const nextState = freezeObject(
452
- activeScene.definition.update({
453
- state: activeScene.state,
454
- input,
455
- transition: makeTransitionController(scope),
456
- tick: nextTick,
457
- sceneTick: nextSceneTick,
458
- deltaSeconds: fixedStepMs / 1000,
459
- elapsedSeconds: (nextTick * fixedStepMs) / 1000,
460
- sceneElapsedSeconds: (nextSceneTick * fixedStepMs) / 1000,
461
- }),
462
- );
758
+ let nextState: unknown;
759
+ try {
760
+ nextState = freezeObject(
761
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
762
+ (activeScene.definition.update as any)({
763
+ state: activeScene.state,
764
+ input,
765
+ transition: makeTransitionController(scope),
766
+ events: emitter,
767
+ tick: nextTick,
768
+ sceneTick: nextSceneTick,
769
+ deltaSeconds: fixedStepMs / 1000,
770
+ elapsedSeconds: (nextTick * fixedStepMs) / 1000,
771
+ sceneElapsedSeconds: (nextSceneTick * fixedStepMs) / 1000,
772
+ }),
773
+ );
774
+ } catch (error) {
775
+ // Update failed: discard staged events, invalidate emitter.
776
+ stagedThisTick = [];
777
+ emitterInvalidated = true;
778
+ activeUpdateScope = undefined;
779
+ updateInProgress = false;
780
+ throw error;
781
+ }
782
+ emitterInvalidated = true;
463
783
  activeUpdateScope = undefined;
464
784
  updateInProgress = false;
465
785
  if (diagnostics !== undefined) {
@@ -473,7 +793,17 @@ export function createGameSessionWithDriver<
473
793
  // A transition requested during update commits after this update
474
794
  // completes successfully. The update consumed one fixed step; the
475
795
  // hard-cut frame publishes at the end of this presentation callback.
476
- commitTransition(intent, nextState, nextTick, true);
796
+ try {
797
+ commitTransition(intent, nextState, nextTick, true);
798
+ } catch (error) {
799
+ // Transition preparation/disposal failed: discard staged events.
800
+ stagedThisTick = [];
801
+ throw error;
802
+ }
803
+ // Successful transition tick: staged events commit with source tick/scene.
804
+ if (stagedThisTick.length > 0) {
805
+ deliverEvents(stagedThisTick);
806
+ }
477
807
  accumulatorMs = Math.max(0, accumulatorMs - fixedStepMs);
478
808
  // F4: the transition consumed one fixed step; report it as such
479
809
  // and never as a zero-step callback (zero-step fires only when
@@ -484,12 +814,24 @@ export function createGameSessionWithDriver<
484
814
  break;
485
815
  }
486
816
  const snapshotStart = diagnostics === undefined ? 0 : now();
487
- const rawSnapshot = activeScene.definition.snapshot({ state: nextState });
817
+ let rawSnapshot: unknown;
818
+ try {
819
+ rawSnapshot = activeScene.definition.snapshot({ state: nextState });
820
+ } catch (error) {
821
+ stagedThisTick = [];
822
+ throw error;
823
+ }
488
824
  if (diagnostics !== undefined) {
489
825
  diagnostics.onSnapshot(now() - snapshotStart);
490
826
  }
491
827
  const freezeStart = diagnostics === undefined ? 0 : now();
492
- const nextSnapshot = deepFreeze(rawSnapshot);
828
+ let nextSnapshot: DeepReadonly<unknown>;
829
+ try {
830
+ nextSnapshot = deepFreeze(rawSnapshot, { scene: sceneNameForTick, tick: nextTick });
831
+ } catch (error) {
832
+ stagedThisTick = [];
833
+ throw error;
834
+ }
493
835
  if (diagnostics !== undefined) {
494
836
  diagnostics.onDeepFreeze(now() - freezeStart);
495
837
  }
@@ -501,6 +843,10 @@ export function createGameSessionWithDriver<
501
843
  previousSnapshot = currentSnapshot;
502
844
  currentSnapshot = nextSnapshot;
503
845
  tick = nextTick;
846
+ // Successful tick: deliver staged events in tick order, then emission order.
847
+ if (stagedThisTick.length > 0) {
848
+ deliverEvents(stagedThisTick);
849
+ }
504
850
  accumulatorMs = Math.max(0, accumulatorMs - fixedStepMs);
505
851
  catchUpSteps += 1;
506
852
  if (diagnostics !== undefined) {
@@ -513,8 +859,7 @@ export function createGameSessionWithDriver<
513
859
  } catch (error) {
514
860
  activeUpdateScope = undefined;
515
861
  updateInProgress = false;
516
- pauseInternal();
517
- throw error;
862
+ pauseAndRethrow(error);
518
863
  }
519
864
 
520
865
  if (catchUpSteps === maxCatchUpSteps && accumulatorMs >= fixedStepMs) {
@@ -539,12 +884,12 @@ export function createGameSessionWithDriver<
539
884
  commitOrPause();
540
885
  }
541
886
  if (status === 'running' && generation === activeGeneration) {
542
- schedule(activeGeneration);
887
+ scheduleSuccessor(activeGeneration);
543
888
  }
544
889
  });
545
890
  };
546
891
 
547
- const session: GameSession<TScenes, TInput> = {
892
+ const session: GameSession<TScenes, TInput, TEventDefs> = {
548
893
  get status() {
549
894
  return status;
550
895
  },
@@ -558,21 +903,44 @@ export function createGameSessionWithDriver<
558
903
  if (status === 'running') {
559
904
  return;
560
905
  }
561
- status = 'running';
906
+ if (sceneFaulted) {
907
+ throw new GameSessionLifecycleError(
908
+ 'The active scene failed disposal and is explicitly non-resumable: ' +
909
+ 'replace it with setScene() or restartScene() before start(), or dispose the session.',
910
+ );
911
+ }
912
+ const previous = status;
562
913
  previousTimestampMs = undefined;
563
914
  accumulatorMs = 0;
564
915
  const activeGeneration = ++generation;
565
- schedule(activeGeneration);
916
+ try {
917
+ schedule(activeGeneration);
918
+ } catch (error) {
919
+ // Do not publish `running` when scheduling could not be established.
920
+ status = previous;
921
+ throw error;
922
+ }
923
+ setStatus('running');
566
924
  },
567
925
  pause() {
568
926
  assertLive();
569
- pauseInternal();
570
- if (pendingTransition !== undefined) {
571
- const pending = pendingTransition;
572
- pendingTransition = undefined;
573
- commitTransition(pending, activeScene.state, undefined);
574
- commitOrPause();
927
+ const pauseFailure = captureFailure(() => pauseInternal());
928
+ // The command must complete: a pending external transition is resolved
929
+ // at the pause boundary even when a status listener threw, so a later
930
+ // start() can never commit a stale transition on an unexpected frame.
931
+ const transitionFailure = captureFailure(() => {
932
+ if (pendingTransition !== undefined) {
933
+ const pending = pendingTransition;
934
+ pendingTransition = undefined;
935
+ commitTransition(pending, activeScene.state, undefined);
936
+ commitOrPause();
937
+ }
938
+ });
939
+ if (pauseFailure.failed && transitionFailure.failed) {
940
+ throw composeFailures(pauseFailure.value, transitionFailure.value);
575
941
  }
942
+ throwFailure(pauseFailure);
943
+ throwFailure(transitionFailure);
576
944
  },
577
945
  setScene(name: keyof TScenes) {
578
946
  assertLive();
@@ -617,14 +985,29 @@ export function createGameSessionWithDriver<
617
985
  options.frameDriver.cancelFrame(frameHandle);
618
986
  frameHandle = undefined;
619
987
  }
620
- status = 'disposed';
621
988
  generation += 1;
622
989
  previousTimestampMs = undefined;
623
990
  accumulatorMs = 0;
624
991
  pendingTransition = undefined;
625
992
  inputBuffer.reset();
626
- listeners.clear();
627
- activeScene.definition.dispose?.(activeScene.state);
993
+ eventListeners.clear();
994
+ // Terminal cleanup is unconditional: `disposed` is delivered to the
995
+ // full listener snapshot (including the re-entrant queued pass), then
996
+ // every listener and the scene are released exactly once even when a
997
+ // status listener throws. The release flag is set BEFORE the
998
+ // notification so notifyStatus's finally performs the clear; for the
999
+ // re-entrant path the clear happens after the queued pass drains.
1000
+ releaseStatusListeners = true;
1001
+ const notificationFailure = captureFailure(() => setStatus('disposed'));
1002
+ const cleanupFailure = captureFailure(() => {
1003
+ listeners.clear();
1004
+ disposeSceneInstance(activeScene, activeScene.state);
1005
+ });
1006
+ if (notificationFailure.failed && cleanupFailure.failed) {
1007
+ throw composeFailures(notificationFailure.value, cleanupFailure.value);
1008
+ }
1009
+ throwFailure(notificationFailure);
1010
+ throwFailure(cleanupFailure);
628
1011
  },
629
1012
  getRenderFrame() {
630
1013
  const alpha = commitFrame.hardCut
@@ -646,6 +1029,50 @@ export function createGameSessionWithDriver<
646
1029
  },
647
1030
  });
648
1031
  },
1032
+ addStatusListener(listener) {
1033
+ assertLive();
1034
+ statusListeners.add(listener);
1035
+ diagnostics?.onStatusListenerCount?.(statusListeners.size);
1036
+ let removed = false;
1037
+ return Object.freeze({
1038
+ remove() {
1039
+ if (removed) {
1040
+ return;
1041
+ }
1042
+ removed = true;
1043
+ statusListeners.delete(listener);
1044
+ diagnostics?.onStatusListenerCount?.(statusListeners.size);
1045
+ },
1046
+ });
1047
+ },
1048
+ addGameEventListener: ((name: string, listener: (event: AnyGameEventEnvelope) => void | Promise<void>) => {
1049
+ assertLive();
1050
+ if (!hasEvents || !allowedEventNames.has(name)) {
1051
+ throw new GameEventError(`Event "${String(name)}" is not declared by this game`);
1052
+ }
1053
+ let set = eventListeners.get(name);
1054
+ if (set === undefined) {
1055
+ set = new Set();
1056
+ eventListeners.set(name, set);
1057
+ }
1058
+ set.add(listener as (event: AnyGameEventEnvelope) => void | Promise<void>);
1059
+ let removed = false;
1060
+ return Object.freeze({
1061
+ remove() {
1062
+ if (removed) {
1063
+ return;
1064
+ }
1065
+ removed = true;
1066
+ const existing = eventListeners.get(name);
1067
+ if (existing) {
1068
+ existing.delete(listener as (event: AnyGameEventEnvelope) => void | Promise<void>);
1069
+ if (existing.size === 0) {
1070
+ eventListeners.delete(name);
1071
+ }
1072
+ }
1073
+ },
1074
+ });
1075
+ }) as unknown as GameSession<TScenes, TInput, TEventDefs>['addGameEventListener'],
649
1076
  };
650
1077
 
651
1078
  return Object.freeze(session);