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
@@ -10,12 +10,22 @@
10
10
  * - `acquire` resolves only with a complete usable lease.
11
11
  * - Reference counts keep a shared source alive across leases; the native
12
12
  * handle is disposed exactly once when the final lease releases it.
13
+ * - Each reference token releases exactly once, decrements its captured
14
+ * entry (never a fresh URI lookup), and only removes the cache mapping
15
+ * when the mapping still holds that exact entry (GS-ASSET-01/02).
16
+ * - Each acquisition stage has one cleanup owner: a reference transfers to
17
+ * the attempt only after successful validation, so a failed validation
18
+ * releases exactly once and never touches another owner's reference.
13
19
  * - A failed/abandoned attempt releases every handle it acquired; entries
14
20
  * still leased elsewhere are preserved.
15
- * - Attempts carry an epoch token: stale completion after retry/unmount is
16
- * ignored and can never double-dispose.
21
+ * - Stale completion after retry/unmount is rejected by the caller's abort
22
+ * signal and the store's disposed flag; late decodes dispose their own
23
+ * handle without touching a successor entry.
17
24
  * - `AbortSignal` detaches an imperative caller immediately; late results
18
25
  * from the underlying work are ignored.
26
+ * - Abort/disposed state is checked after the final awaited step and before
27
+ * the lease is published, so an abort racing completion never surfaces
28
+ * a lease for a cancelled request.
19
29
  * - Progress is monotonic and counts requested logical resources (a
20
30
  * deduplicated source still counts once per logical descriptor).
21
31
  * - The store rejects acquisitions after disposal; disposal is idempotent.
@@ -67,13 +77,12 @@ interface ResourceEntry {
67
77
  readonly uri: string;
68
78
  handle: NativeImageHandle | undefined;
69
79
  inFlight: Promise<NativeImageHandle> | undefined;
70
- /** Logical descriptor keys sharing this resolved source. */
71
- readonly logicalKeys: Set<string>;
72
80
  refCount: number;
73
81
  }
74
82
 
83
+ /** References owned by one in-progress acquisition (GS-ASSET-04: no epoch
84
+ * token — staleness is decided by the caller's abort signal, not a counter). */
75
85
  interface Attempt {
76
- readonly token: number;
77
86
  /** Idempotent release closures for every reference this attempt owns. */
78
87
  readonly acquired: ResourceRef[];
79
88
  }
@@ -138,10 +147,8 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
138
147
  } {
139
148
  const groups = new Set<string>(Object.keys(manifest));
140
149
  const logical = logicalAssetsOf(manifest);
141
- const byKey = new Map<string, LogicalAsset>(logical.map((asset) => [asset.key, asset]));
142
150
  const resources = new Map<string, ResourceEntry>();
143
151
  let disposed = false;
144
- let nextAttemptToken = 1;
145
152
  /** Per-attempt ownership is explicit: the attempt object is passed through
146
153
  * every resolve/decode/validate operation; no shared singleton (R4). */
147
154
 
@@ -175,67 +182,63 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
175
182
  });
176
183
  }
177
184
 
178
- /** Drop one reference held by a caller that never recorded a logical key. */
179
- function dropResourceRef(uri: string): void {
180
- const entry = resources.get(uri);
181
- if (entry === undefined) {
182
- return;
183
- }
185
+ /**
186
+ * Drop one reference to the captured entry (GS-ASSET-01/02). The entry
187
+ * object — not a fresh URI lookup — is decremented, and the cache mapping
188
+ * is removed only when it still holds this exact entry, so a stale token
189
+ * can never affect a successor generation for the same URI.
190
+ */
191
+ function dropResourceRef(entry: ResourceEntry): void {
184
192
  entry.refCount -= 1;
185
193
  if (entry.refCount <= 0) {
186
194
  if (entry.handle !== undefined) {
187
195
  entry.handle.dispose();
188
196
  }
189
- resources.delete(uri);
197
+ if (resources.get(entry.uri) === entry) {
198
+ resources.delete(entry.uri);
199
+ }
190
200
  }
191
201
  }
192
202
 
193
203
  /**
194
- * Begin one owned reference to a resource. Every waiter — cache miss,
195
- * in-flight share, or completed cache hit — goes through this single
196
- * accounting path and receives an idempotent release closure (RF5). The
197
- * caller must either commit the ref to the attempt or call release()
198
- * exactly once; the final release disposes the native handle.
204
+ * Begin one owned reference to a resource (GS-ASSET-01). Every waiter —
205
+ * cache miss, in-flight share, or completed cache hit — goes through this
206
+ * single accounting path and receives a token bound to the exact entry
207
+ * object. The token releases exactly once: a flag guards the decrement,
208
+ * so a validation failure (released by its stage) followed by attempt
209
+ * cleanup (releasing the attempt's tokens) can never double-release.
210
+ * The caller must either transfer the token to the attempt or let its
211
+ * owning stage release it exactly once; the final release disposes the
212
+ * native handle.
199
213
  */
200
- function beginResourceRef(uri: string): ResourceRef {
214
+ function beginResourceRef(uri: string): { readonly ref: ResourceRef; readonly entry: ResourceEntry } {
201
215
  const existing = resources.get(uri);
202
- let entry: ResourceEntry;
203
216
  if (existing !== undefined && existing.handle !== undefined) {
204
217
  // Completed cache hit.
205
- entry = existing;
206
- entry.refCount += 1;
207
- const handle = existing.handle;
208
- return {
209
- release: () => dropResourceRef(uri),
210
- ready: async () => handle,
211
- };
218
+ existing.refCount += 1;
219
+ return { ref: idempotentRef(existing), entry: existing };
212
220
  }
213
221
  if (existing !== undefined && existing.inFlight !== undefined) {
214
222
  // Shared in-flight decode.
215
- entry = existing;
216
- entry.refCount += 1;
217
- const shared = existing.inFlight;
218
- return {
219
- release: () => dropResourceRef(uri),
220
- ready: async () => shared,
221
- };
223
+ existing.refCount += 1;
224
+ return { ref: idempotentRef(existing), entry: existing };
222
225
  }
223
226
  // Cache miss: this waiter starts the decode.
224
- entry = {
227
+ const entry: ResourceEntry = {
225
228
  uri,
226
229
  handle: undefined,
227
230
  inFlight: undefined,
228
- logicalKeys: new Set(),
229
231
  refCount: 1,
230
232
  };
231
233
  resources.set(uri, entry);
232
234
  const promise = (async () => {
233
235
  const handle = await pipelines.decode(uri);
234
236
  // A late completion must never resurrect a disposed store or an entry
235
- // whose last waiter aborted while the decode was in flight.
237
+ // whose last waiter aborted while the decode was in flight. Identity
238
+ // matters here too: the mapping may now hold a successor entry.
236
239
  if (disposed || entry.refCount <= 0) {
237
240
  handle.dispose();
238
- if (entry.refCount <= 0) {
241
+ if (entry.refCount <= 0 && resources.get(uri) === entry) {
239
242
  resources.delete(uri);
240
243
  }
241
244
  throw new AssetStoreError('ASSET_ABORTED', [], 'asset acquisition aborted');
@@ -244,36 +247,63 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
244
247
  return handle;
245
248
  })();
246
249
  entry.inFlight = promise;
247
- void promise.then(
248
- () => {
249
- entry.inFlight = undefined;
250
- },
251
- () => {
250
+ const cleanup = (): void => {
251
+ if (entry.inFlight === promise) {
252
252
  entry.inFlight = undefined;
253
- },
254
- );
253
+ }
254
+ };
255
+ promise.then(cleanup, cleanup);
256
+ const ref = idempotentRef(entry);
257
+ return { ref, entry };
258
+ }
259
+
260
+ /** One release closure bound to its entry; safe to call repeatedly. */
261
+ function idempotentRef(entry: ResourceEntry): ResourceRef {
262
+ let released = false;
263
+ const ready = async (): Promise<NativeImageHandle> => {
264
+ if (entry.handle !== undefined) {
265
+ return entry.handle;
266
+ }
267
+ const inFlight = entry.inFlight;
268
+ if (inFlight === undefined) {
269
+ throw new AssetStoreError(
270
+ 'ASSET_DECODE_FAILED',
271
+ [],
272
+ `resource ${JSON.stringify(entry.uri)} has no decoded handle`,
273
+ );
274
+ }
275
+ return inFlight;
276
+ };
255
277
  return {
256
- release: () => dropResourceRef(uri),
257
- ready: async () => promise,
278
+ release: () => {
279
+ if (released) {
280
+ return;
281
+ }
282
+ released = true;
283
+ dropResourceRef(entry);
284
+ },
285
+ ready,
258
286
  };
259
287
  }
260
288
 
289
+ /**
290
+ * Acquire one logical asset (GS-ASSET-01). This stage owns its reference
291
+ * until successful validation transfers it to the attempt: on any
292
+ * failure the stage releases exactly once and the token never enters
293
+ * `attempt.acquired`, so attempt cleanup can never release it again.
294
+ * Returns the exact entry backing this asset for the lease table.
295
+ */
261
296
  async function acquireOne(
262
297
  asset: LogicalAsset,
263
298
  attempt: Attempt,
264
299
  signal: AbortSignal | undefined,
265
- ): Promise<void> {
300
+ ): Promise<ResourceEntry> {
266
301
  const uri = await raceWithAbort(pipelines.resolve(asset.descriptor.source), signal);
267
302
  // RF5: the reference token exists before any cancellable await; abort
268
303
  // can never leave a positive reference behind.
269
- const ref = beginResourceRef(uri);
304
+ const { ref, entry } = beginResourceRef(uri);
270
305
  try {
271
306
  const handle = await raceWithAbort(ref.ready(), signal);
272
- attempt.acquired.push(ref);
273
- const entry = resources.get(uri);
274
- if (entry !== undefined) {
275
- entry.logicalKeys.add(asset.key);
276
- }
277
307
  if (asset.descriptor.kind === 'sprite-sheet') {
278
308
  validateFrames(
279
309
  [asset.group, asset.name, 'frames'],
@@ -282,9 +312,11 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
282
312
  handle.height(),
283
313
  );
284
314
  }
315
+ // Transfer ownership to the attempt only after validation succeeds.
316
+ attempt.acquired.push(ref);
317
+ return entry;
285
318
  } catch (error) {
286
- // Every failure, abort, and stale completion releases this waiter's
287
- // reference exactly once; surviving owners keep theirs.
319
+ // The single owner releases exactly once; surviving owners keep theirs.
288
320
  ref.release();
289
321
  throw error;
290
322
  }
@@ -321,21 +353,22 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
321
353
  return createLease(new Map(), () => undefined);
322
354
  }
323
355
 
324
- const token = nextAttemptToken;
325
- nextAttemptToken += 1;
326
- const attempt: Attempt = { token, acquired: [] };
356
+ const attempt: Attempt = { acquired: [] };
327
357
 
328
- const loaded = new Map<string, string>();
358
+ const loaded = new Map<string, { readonly asset: LogicalAsset; readonly entry: ResourceEntry }>();
329
359
  try {
330
360
  let completed = 0;
331
361
  const total = requested.length;
332
362
  for (const asset of requested) {
333
363
  throwIfAborted();
334
- await acquireOne(asset, attempt, signal);
335
- loaded.set(asset.key, asset.key);
364
+ const entry = await acquireOne(asset, attempt, signal);
365
+ loaded.set(asset.key, { asset, entry });
336
366
  completed += 1;
337
367
  options.onProgress?.(completed / total);
338
368
  }
369
+ // GS-ASSET-02: an abort racing the final step must surface instead of
370
+ // publishing a lease for a cancelled request.
371
+ throwIfAborted();
339
372
  return createLease(loaded, () => {
340
373
  for (const ref of attempt.acquired) {
341
374
  ref.release();
@@ -351,17 +384,51 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
351
384
  }
352
385
  };
353
386
 
387
+ /**
388
+ * Build one validated loaded value from the exact entry that backed the
389
+ * acquisition (GS-ASSET-04). The handle is always present: `ready()` only
390
+ * resolves after the entry's handle is set.
391
+ */
392
+ function loadedValueFor(asset: LogicalAsset, entry: ResourceEntry): LoadedImage | LoadedSpriteSheet {
393
+ const handle = entry.handle;
394
+ if (handle === undefined) {
395
+ // Defensive: reachable only if the store's own accounting is broken.
396
+ // Fail clearly rather than publishing an imageless loaded value.
397
+ throw new AssetStoreError(
398
+ 'ASSET_DECODE_FAILED',
399
+ [asset.group, asset.name],
400
+ `resource ${JSON.stringify(entry.uri)} resolved without a decoded handle`,
401
+ );
402
+ }
403
+ if (asset.descriptor.kind === 'image') {
404
+ return {
405
+ descriptor: asset.descriptor,
406
+ width: handle.width(),
407
+ height: handle.height(),
408
+ image: handle,
409
+ };
410
+ }
411
+ return {
412
+ descriptor: asset.descriptor,
413
+ frames: asset.descriptor.frames,
414
+ width: handle.width(),
415
+ height: handle.height(),
416
+ image: handle,
417
+ };
418
+ }
419
+
354
420
  function createLease(
355
- loadedKeys: ReadonlyMap<string, string>,
421
+ loaded: ReadonlyMap<string, { readonly asset: LogicalAsset; readonly entry: ResourceEntry }>,
356
422
  onDispose: () => void,
357
423
  ): GameAssetLease<TManifest> {
358
424
  let leaseDisposed = false;
359
- const loaded = new Map<string, LogicalAsset>();
360
- for (const key of loadedKeys.keys()) {
361
- const asset = byKey.get(key);
362
- if (asset !== undefined) {
363
- loaded.set(key, asset);
364
- }
425
+ // GS-ASSET-04: the lease holds a direct descriptor→validated value
426
+ // table built once from the captured entries. Lookup is a single map
427
+ // hit: no per-get scans of loaded descriptors or resource key sets,
428
+ // and no wrapper allocated per call.
429
+ const table = new Map<ImageDescriptor | SpriteSheetDescriptor, LoadedImage | LoadedSpriteSheet>();
430
+ for (const { asset, entry } of loaded.values()) {
431
+ table.set(asset.descriptor, loadedValueFor(asset, entry));
365
432
  }
366
433
  const assets: LoadedAssets<TManifest> = {
367
434
  manifest,
@@ -372,8 +439,8 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
372
439
  if (leaseDisposed) {
373
440
  throw new AssetStoreError('ASSET_STORE_DISPOSED', [], 'lease is disposed');
374
441
  }
375
- const entry = findLoadedFor(descriptor, loaded);
376
- if (entry === undefined) {
442
+ const value = table.get(descriptor as ImageDescriptor | SpriteSheetDescriptor);
443
+ if (value === undefined) {
377
444
  // R9: v1 lookup is descriptor-reference membership — the exact
378
445
  // descriptor object the manifest declared — not a nominal manifest
379
446
  // identity. Identically shaped manifests share the structural type,
@@ -385,7 +452,7 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
385
452
  'descriptor is not a reference declared by this manifest and group selection',
386
453
  );
387
454
  }
388
- return entry as TDescriptor extends { readonly kind: 'sprite-sheet' }
455
+ return value as TDescriptor extends { readonly kind: 'sprite-sheet' }
389
456
  ? LoadedSpriteSheet
390
457
  : LoadedImage;
391
458
  },
@@ -402,40 +469,6 @@ export function createGameAssetStoreCore<TManifest extends AssetGroupMap>(
402
469
  };
403
470
  }
404
471
 
405
- function findLoadedFor(
406
- descriptor: ImageDescriptor | SpriteSheetDescriptor,
407
- loaded: ReadonlyMap<string, LogicalAsset>,
408
- ): unknown {
409
- for (const asset of loaded.values()) {
410
- if (asset.descriptor === descriptor) {
411
- const entry = entryFor(asset.key);
412
- const handle = entry?.handle;
413
- const width = handle?.width() ?? 0;
414
- const height = handle?.height() ?? 0;
415
- if (asset.descriptor.kind === 'image') {
416
- return { descriptor: asset.descriptor, width, height, image: handle };
417
- }
418
- return {
419
- descriptor: asset.descriptor,
420
- frames: asset.descriptor.frames,
421
- width,
422
- height,
423
- image: handle,
424
- };
425
- }
426
- }
427
- return undefined;
428
- }
429
-
430
- function entryFor(logicalKey: string): ResourceEntry | undefined {
431
- for (const entry of resources.values()) {
432
- if (entry.logicalKeys.has(logicalKey)) {
433
- return entry;
434
- }
435
- }
436
- return undefined;
437
- }
438
-
439
472
  return {
440
473
  acquire,
441
474
  dispose: () => {
@@ -13,12 +13,18 @@
13
13
  * Lifecycle:
14
14
  * - The requested group list is normalized (sorted) so a recreated
15
15
  * equivalent array does not reload.
16
+ * - Every render executes all hooks unconditionally; when the rendered
17
+ * request differs from the state's request, the hook exposes a fresh
18
+ * loading view synchronously instead of a stale ready lease (GS-ASSET-03).
19
+ * - Request identity combines manifest identity, factory identity,
20
+ * normalized groups, and retry attempt, so a new manifest with identical
21
+ * group names can never expose the previous manifest's lease.
16
22
  * - Retry starts a new attempt; late completion from an older attempt can
17
23
  * never replace the new state.
18
24
  * - Unmount invalidates the attempt, releases the lease, and disposes the
19
25
  * store — hook-owned resources are released exactly once.
20
26
  */
21
- import { useCallback, useEffect, useRef, useState } from 'react';
27
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
28
 
23
29
  import { GameAssetError } from '../../assets/errors';
24
30
  import type { AssetGroupMap, GameAssetLease, LoadedAssets } from '../../assets/types';
@@ -59,6 +65,23 @@ export function dedupeGroups(groups: readonly string[]): readonly string[] {
59
65
  return [...new Set(groups)].sort();
60
66
  }
61
67
 
68
+ /** Stable numeric identities for request participants (GS-ASSET-03): object
69
+ * identity, so a new manifest object with identical group names is a new
70
+ * request even when every group string matches. */
71
+ const manifestIdentities = new WeakMap<object, number>();
72
+ const factoryIdentities = new WeakMap<object, number>();
73
+ let nextParticipantId = 1;
74
+
75
+ function participantId(table: WeakMap<object, number>, value: object): number {
76
+ let id = table.get(value);
77
+ if (id === undefined) {
78
+ id = nextParticipantId;
79
+ nextParticipantId += 1;
80
+ table.set(value, id);
81
+ }
82
+ return id;
83
+ }
84
+
62
85
  /** Order-independent group key: equivalent arrays map to one key. */
63
86
  export function stableGroupsKey(groups: readonly string[]): string {
64
87
  return dedupeGroups(groups).join('\u0000');
@@ -71,11 +94,22 @@ export function useGameAssets<TManifest extends AssetGroupMap>(
71
94
  ): GameAssetsState<TManifest> {
72
95
  const groupsKey = stableGroupsKey(options.groups);
73
96
  const [attempt, setAttempt] = useState(0);
97
+ // A memoized normalized array: stable across equivalent recreated group
98
+ // arrays, so the effect below never reloads for one (R6).
99
+ const normalizedGroups = useMemo(() => dedupeGroups(options.groups), [groupsKey]);
100
+ // `??` selects a reference, never a new closure, so the default factory
101
+ // keeps a stable identity across renders.
102
+ const factory = storeFactory ?? defaultStoreFactory;
103
+ // GS-ASSET-03/04: the full request identity. Manifest and factory use
104
+ // object identity; groups are normalized; the attempt drives retries.
105
+ const requestKey =
106
+ `${participantId(manifestIdentities, manifest)}:` +
107
+ `${participantId(factoryIdentities, factory)}:${groupsKey}:${attempt}`;
74
108
  const [state, setState] = useState<GameAssetsState<TManifest>>({
75
109
  status: 'loading',
76
110
  progress: 0,
77
111
  retry: () => undefined,
78
- requestKey: groupsKey,
112
+ requestKey,
79
113
  });
80
114
  const storeRef = useRef<HookStore<TManifest> | undefined>(undefined);
81
115
  const leaseRef = useRef<GameAssetLease<TManifest> | undefined>(undefined);
@@ -85,27 +119,17 @@ export function useGameAssets<TManifest extends AssetGroupMap>(
85
119
  setAttempt((current) => current + 1);
86
120
  }, []);
87
121
 
88
- // Ref mirror of the attempt so stale-effect completions are rejected
89
- // after a retry re-renders with a higher attempt.
90
- const attemptRef = useRef(attempt);
91
- attemptRef.current = attempt;
92
-
93
- // RF7: if the rendered request differs from the state's request, expose
94
- // loading synchronously instead of the previous ready lease — the old
95
- // lease is disposed by the effect cleanup, so no consumer can observe it
96
- // under the new request.
97
- if (state.requestKey !== groupsKey && state.status === 'ready') {
98
- return { status: 'loading', progress: 0, retry, requestKey: groupsKey };
99
- }
100
-
101
122
  useEffect(() => {
102
- const store = (storeFactory ?? defaultStoreFactory)(manifest);
123
+ const store = factory(manifest);
103
124
  storeRef.current = store;
125
+ // Per-effect invalidation (GS-ASSET-03): cleanup sets `disposed` before
126
+ // any newer effect runs, so stale completions are rejected without a
127
+ // render-time ref mutation standing in for ownership.
104
128
  let disposed = false;
105
- const attemptAtStart = attempt;
106
- const isCurrent = (): boolean => attemptRef.current === attemptAtStart;
107
- const setIfCurrent = (updater: (previous: GameAssetsState<TManifest>) => GameAssetsState<TManifest>): void => {
108
- if (!disposed && isCurrent()) {
129
+ const setIfCurrent = (
130
+ updater: (previous: GameAssetsState<TManifest>) => GameAssetsState<TManifest>,
131
+ ): void => {
132
+ if (!disposed) {
109
133
  setState(updater);
110
134
  }
111
135
  };
@@ -118,38 +142,40 @@ export function useGameAssets<TManifest extends AssetGroupMap>(
118
142
  status: 'loading',
119
143
  progress: 0,
120
144
  retry: retry,
121
- requestKey: groupsKey,
145
+ requestKey,
122
146
  });
123
147
 
124
148
  store
125
149
  .acquire({
126
- groups: dedupeGroups(options.groups),
150
+ groups: normalizedGroups,
127
151
  signal: controller.signal,
128
152
  onProgress: (progress) => {
129
153
  setIfCurrent((previous) =>
130
- previous.status === 'loading' ? { ...previous, progress } : previous,
154
+ previous.status === 'loading' && previous.requestKey === requestKey
155
+ ? { ...previous, progress }
156
+ : previous,
131
157
  );
132
158
  },
133
159
  })
134
160
  .then((lease) => {
135
- if (disposed || !isCurrent()) {
161
+ if (disposed) {
136
162
  // Stale completion (retry/unmount): release immediately.
137
163
  lease.dispose();
138
164
  return;
139
165
  }
140
166
  leaseRef.current?.dispose();
141
167
  leaseRef.current = lease;
142
- setState({ status: 'ready', assets: lease.assets, requestKey: groupsKey });
168
+ setState({ status: 'ready', assets: lease.assets, requestKey });
143
169
  })
144
170
  .catch((error: unknown) => {
145
- if (disposed || !isCurrent()) {
171
+ if (disposed) {
146
172
  return;
147
173
  }
148
174
  const structured =
149
175
  error instanceof GameAssetError
150
176
  ? error
151
177
  : new GameAssetError('ASSET_DECODE_FAILED', [], error instanceof Error ? error.message : String(error));
152
- setState({ status: 'error', error: structured, retry, requestKey: groupsKey });
178
+ setState({ status: 'error', error: structured, retry, requestKey });
153
179
  });
154
180
 
155
181
  return () => {
@@ -162,10 +188,16 @@ export function useGameAssets<TManifest extends AssetGroupMap>(
162
188
  storeRef.current = undefined;
163
189
  store.dispose();
164
190
  };
165
- // The groups key is order-independent, so a recreated equivalent array
166
- // does not reload; the attempt state drives retries. The store factory
167
- // is the internal test seam and must be stable across renders.
168
- }, [attempt, groupsKey, manifest, storeFactory]);
191
+ // The effect re-runs only when the request identity (or its stable
192
+ // derivatives) changes; equivalent recreated group arrays do not reload.
193
+ }, [requestKey, manifest, factory, normalizedGroups, groupsKey, retry]);
194
+
195
+ // GS-ASSET-03: every hook above runs unconditionally. When the rendered
196
+ // request differs from the state's request, expose a fresh loading view
197
+ // synchronously — never a stale ready lease, error, or progress.
198
+ if (state.requestKey !== requestKey) {
199
+ return { status: 'loading', progress: 0, retry, requestKey };
200
+ }
169
201
 
170
202
  return state;
171
203
  }
@@ -1,4 +1,5 @@
1
- import type { GameSessionStatus } from '../core/session/types';
1
+ import type { GameSessionStatus, GameSubscription } from '../core/session/types';
2
+ import { createCleanupList } from './cleanupList';
2
3
 
3
4
  /** Minimal AppState-like source used to keep this seam platform-neutral. */
4
5
  export interface AppLifecycleSource {
@@ -18,6 +19,12 @@ export interface AppLifecycleSession {
18
19
  pause(): void;
19
20
  /** Resume a paused session. */
20
21
  resume(): void;
22
+ /**
23
+ * Optional status observation (T10.6): when present, the binder also
24
+ * guards against an external `start()` while the host is inactive, so a
25
+ * frame loop can never escape into the background.
26
+ */
27
+ addStatusListener?(listener: (status: GameSessionStatus) => void): GameSubscription;
21
28
  }
22
29
 
23
30
  /**
@@ -36,7 +43,14 @@ export function bindAppLifecycle(
36
43
  source: AppLifecycleSource,
37
44
  session: AppLifecycleSession,
38
45
  ): () => void {
46
+ // GS-REACT-02: setup is transactional — every stage registers its release
47
+ // as it completes, so a later failure unwinds everything acquired so far.
48
+ const cleanups = createCleanupList();
39
49
  let pausedByLifecycle = false;
50
+ let currentState = source.currentState ?? 'active';
51
+
52
+ const isInactive = (state: string | null | undefined): boolean =>
53
+ state === 'inactive' || state === 'background';
40
54
 
41
55
  const pauseForBackground = () => {
42
56
  if (session.getStatus() === 'running') {
@@ -45,27 +59,49 @@ export function bindAppLifecycle(
45
59
  }
46
60
  };
47
61
 
48
- // Synchronize the initial app state before any change event arrives.
49
- if (source.currentState === 'inactive' || source.currentState === 'background') {
50
- pauseForBackground();
51
- }
52
-
53
- const subscription = source.addEventListener('change', (next) => {
54
- if (next === 'active') {
55
- if (pausedByLifecycle) {
56
- pausedByLifecycle = false;
57
- // Resume only if the session is still paused; a different actor may
58
- // have resumed or disposed it while the app was backgrounded.
59
- if (session.getStatus() === 'paused') {
60
- session.resume();
62
+ // T10.6: an external start() while the host is inactive is deterministically
63
+ // returned to paused. The binder observes status transitions and re-pauses
64
+ // any running transition that happens while the app is not active, so a
65
+ // frame loop cannot escape into the background.
66
+ let statusSubscription: GameSubscription | undefined;
67
+ try {
68
+ if (session.addStatusListener !== undefined) {
69
+ statusSubscription = session.addStatusListener((status) => {
70
+ if (status === 'running' && isInactive(currentState)) {
71
+ session.pause();
72
+ pausedByLifecycle = true;
61
73
  }
62
- }
63
- return;
74
+ });
75
+ cleanups.add(() => statusSubscription?.remove());
64
76
  }
65
- if (next === 'inactive' || next === 'background') {
77
+
78
+ // Synchronize the initial app state before any change event arrives.
79
+ if (isInactive(source.currentState)) {
66
80
  pauseForBackground();
67
81
  }
68
- });
82
+
83
+ const subscription = source.addEventListener('change', (next) => {
84
+ currentState = next;
85
+ if (next === 'active') {
86
+ if (pausedByLifecycle) {
87
+ pausedByLifecycle = false;
88
+ // Resume only if the session is still paused; a different actor may
89
+ // have resumed or disposed it while the app was backgrounded.
90
+ if (session.getStatus() === 'paused') {
91
+ session.resume();
92
+ }
93
+ }
94
+ return;
95
+ }
96
+ if (isInactive(next)) {
97
+ pauseForBackground();
98
+ }
99
+ });
100
+ cleanups.add(() => subscription.remove());
101
+ } catch (error) {
102
+ cleanups.releaseAll();
103
+ throw error;
104
+ }
69
105
 
70
106
  let cleanedUp = false;
71
107
  return () => {
@@ -73,7 +109,7 @@ export function bindAppLifecycle(
73
109
  return;
74
110
  }
75
111
  cleanedUp = true;
76
- subscription.remove();
112
+ cleanups.releaseAll();
77
113
  pausedByLifecycle = false;
78
114
  };
79
115
  }