@thunderclap-engine/engine 0.0.0-stage → 1.1.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 (566) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/LICENSE +21 -0
  3. package/README.md +333 -2
  4. package/dist/analytics/analytics.d.ts +80 -0
  5. package/dist/analytics/analytics.js +129 -0
  6. package/dist/analytics/index.d.ts +1 -0
  7. package/dist/analytics/index.js +1 -0
  8. package/dist/anim/aim.d.ts +43 -0
  9. package/dist/anim/aim.js +157 -0
  10. package/dist/anim/clip.d.ts +98 -0
  11. package/dist/anim/clip.js +282 -0
  12. package/dist/anim/compress.d.ts +65 -0
  13. package/dist/anim/compress.js +339 -0
  14. package/dist/anim/foot-ik.d.ts +74 -0
  15. package/dist/anim/foot-ik.js +241 -0
  16. package/dist/anim/graph.d.ts +153 -0
  17. package/dist/anim/graph.js +532 -0
  18. package/dist/anim/ik.d.ts +105 -0
  19. package/dist/anim/ik.js +435 -0
  20. package/dist/anim/index.d.ts +36 -0
  21. package/dist/anim/index.js +21 -0
  22. package/dist/anim/match.d.ts +66 -0
  23. package/dist/anim/match.js +153 -0
  24. package/dist/anim/mixer.d.ts +113 -0
  25. package/dist/anim/mixer.js +314 -0
  26. package/dist/anim/morph.d.ts +41 -0
  27. package/dist/anim/morph.js +94 -0
  28. package/dist/anim/pose.d.ts +74 -0
  29. package/dist/anim/pose.js +254 -0
  30. package/dist/anim/quat.d.ts +44 -0
  31. package/dist/anim/quat.js +187 -0
  32. package/dist/anim/retarget.d.ts +97 -0
  33. package/dist/anim/retarget.js +366 -0
  34. package/dist/anim/root-motion.d.ts +44 -0
  35. package/dist/anim/root-motion.js +224 -0
  36. package/dist/anim/skin-pose.d.ts +39 -0
  37. package/dist/anim/skin-pose.js +53 -0
  38. package/dist/anim/socket.d.ts +47 -0
  39. package/dist/anim/socket.js +84 -0
  40. package/dist/anim/spring-chain.d.ts +73 -0
  41. package/dist/anim/spring-chain.js +294 -0
  42. package/dist/assets/atlas.d.ts +90 -0
  43. package/dist/assets/atlas.js +212 -0
  44. package/dist/assets/baked.d.ts +33 -0
  45. package/dist/assets/baked.js +38 -0
  46. package/dist/assets/bundle.d.ts +88 -0
  47. package/dist/assets/bundle.js +464 -0
  48. package/dist/assets/cache.d.ts +56 -0
  49. package/dist/assets/cache.js +123 -0
  50. package/dist/assets/gltf-load.d.ts +252 -0
  51. package/dist/assets/gltf-load.js +661 -0
  52. package/dist/assets/gltf-skin.d.ts +36 -0
  53. package/dist/assets/gltf-skin.js +109 -0
  54. package/dist/assets/gltf.d.ts +706 -0
  55. package/dist/assets/gltf.js +1376 -0
  56. package/dist/assets/index.d.ts +20 -0
  57. package/dist/assets/index.js +10 -0
  58. package/dist/assets/ktx2.d.ts +71 -0
  59. package/dist/assets/ktx2.js +213 -0
  60. package/dist/assets/manifest.d.ts +79 -0
  61. package/dist/assets/manifest.js +7 -0
  62. package/dist/assets/meshopt.d.ts +23 -0
  63. package/dist/assets/meshopt.js +403 -0
  64. package/dist/audio/audio.d.ts +733 -0
  65. package/dist/audio/audio.js +2013 -0
  66. package/dist/audio/dsp.d.ts +61 -0
  67. package/dist/audio/dsp.js +159 -0
  68. package/dist/audio/duck.d.ts +44 -0
  69. package/dist/audio/duck.js +101 -0
  70. package/dist/audio/envelope.d.ts +53 -0
  71. package/dist/audio/envelope.js +106 -0
  72. package/dist/audio/index.d.ts +13 -0
  73. package/dist/audio/index.js +14 -0
  74. package/dist/audio/music.d.ts +113 -0
  75. package/dist/audio/music.js +548 -0
  76. package/dist/audio/occlusion.d.ts +55 -0
  77. package/dist/audio/occlusion.js +61 -0
  78. package/dist/audio/persist.d.ts +23 -0
  79. package/dist/audio/persist.js +27 -0
  80. package/dist/audio/recipes.d.ts +55 -0
  81. package/dist/audio/recipes.js +227 -0
  82. package/dist/audio/send.d.ts +41 -0
  83. package/dist/audio/send.js +118 -0
  84. package/dist/audio/sprites.d.ts +59 -0
  85. package/dist/audio/sprites.js +43 -0
  86. package/dist/audio/synth.d.ts +103 -0
  87. package/dist/audio/synth.js +197 -0
  88. package/dist/audio/variations.d.ts +25 -0
  89. package/dist/audio/variations.js +55 -0
  90. package/dist/audio/zones.d.ts +100 -0
  91. package/dist/audio/zones.js +167 -0
  92. package/dist/core/detached-surface.d.ts +44 -0
  93. package/dist/core/detached-surface.js +104 -0
  94. package/dist/core/events.d.ts +24 -0
  95. package/dist/core/events.js +43 -0
  96. package/dist/core/game.d.ts +409 -0
  97. package/dist/core/game.js +530 -0
  98. package/dist/core/hash.d.ts +24 -0
  99. package/dist/core/hash.js +49 -0
  100. package/dist/core/index.d.ts +17 -0
  101. package/dist/core/index.js +9 -0
  102. package/dist/core/loop.d.ts +113 -0
  103. package/dist/core/loop.js +185 -0
  104. package/dist/core/math.d.ts +58 -0
  105. package/dist/core/math.js +210 -0
  106. package/dist/core/noise.d.ts +40 -0
  107. package/dist/core/noise.js +247 -0
  108. package/dist/core/pool.d.ts +42 -0
  109. package/dist/core/pool.js +85 -0
  110. package/dist/core/random.d.ts +61 -0
  111. package/dist/core/random.js +199 -0
  112. package/dist/core/scene.d.ts +79 -0
  113. package/dist/core/scene.js +195 -0
  114. package/dist/core/timer.d.ts +66 -0
  115. package/dist/core/timer.js +286 -0
  116. package/dist/fx/billboard-wgsl.d.ts +29 -0
  117. package/dist/fx/billboard-wgsl.js +158 -0
  118. package/dist/fx/billboard.d.ts +249 -0
  119. package/dist/fx/billboard.js +702 -0
  120. package/dist/fx/debug-draw.d.ts +127 -0
  121. package/dist/fx/debug-draw.js +523 -0
  122. package/dist/fx/debug-wgsl.d.ts +7 -0
  123. package/dist/fx/debug-wgsl.js +32 -0
  124. package/dist/fx/decals-wgsl.d.ts +15 -0
  125. package/dist/fx/decals-wgsl.js +173 -0
  126. package/dist/fx/decals.d.ts +216 -0
  127. package/dist/fx/decals.js +607 -0
  128. package/dist/fx/effect.d.ts +129 -0
  129. package/dist/fx/effect.js +349 -0
  130. package/dist/fx/effects/hit-spark.json +48 -0
  131. package/dist/fx/effects/index.d.ts +8 -0
  132. package/dist/fx/effects/index.js +9 -0
  133. package/dist/fx/effects/magic-aura.json +47 -0
  134. package/dist/fx/effects/smoke-puff.json +34 -0
  135. package/dist/fx/emit-surface.d.ts +40 -0
  136. package/dist/fx/emit-surface.js +124 -0
  137. package/dist/fx/flicker.d.ts +30 -0
  138. package/dist/fx/flicker.js +42 -0
  139. package/dist/fx/index.d.ts +31 -0
  140. package/dist/fx/index.js +20 -0
  141. package/dist/fx/particles-wgsl.d.ts +57 -0
  142. package/dist/fx/particles-wgsl.js +828 -0
  143. package/dist/fx/particles.d.ts +566 -0
  144. package/dist/fx/particles.js +1533 -0
  145. package/dist/fx/random.d.ts +15 -0
  146. package/dist/fx/random.js +20 -0
  147. package/dist/fx/sky-wgsl.d.ts +23 -0
  148. package/dist/fx/sky-wgsl.js +127 -0
  149. package/dist/fx/sky.d.ts +96 -0
  150. package/dist/fx/sky.js +175 -0
  151. package/dist/fx/text-atlas.d.ts +143 -0
  152. package/dist/fx/text-atlas.js +327 -0
  153. package/dist/fx/trail-wgsl.d.ts +11 -0
  154. package/dist/fx/trail-wgsl.js +42 -0
  155. package/dist/fx/trail.d.ts +194 -0
  156. package/dist/fx/trail.js +555 -0
  157. package/dist/fx/water-wgsl.d.ts +34 -0
  158. package/dist/fx/water-wgsl.js +197 -0
  159. package/dist/fx/water.d.ts +176 -0
  160. package/dist/fx/water.js +371 -0
  161. package/dist/geom/collide/bvh.d.ts +151 -0
  162. package/dist/geom/collide/bvh.js +493 -0
  163. package/dist/geom/collide/debug.d.ts +14 -0
  164. package/dist/geom/collide/debug.js +74 -0
  165. package/dist/geom/collide/grid.d.ts +26 -0
  166. package/dist/geom/collide/grid.js +91 -0
  167. package/dist/geom/collide/index.d.ts +8 -0
  168. package/dist/geom/collide/index.js +8 -0
  169. package/dist/geom/collide/queries.d.ts +70 -0
  170. package/dist/geom/collide/queries.js +583 -0
  171. package/dist/geom/collide/serialize.d.ts +14 -0
  172. package/dist/geom/collide/serialize.js +145 -0
  173. package/dist/geom/collide/shapes.d.ts +161 -0
  174. package/dist/geom/collide/shapes.js +686 -0
  175. package/dist/geom/collide/slide.d.ts +66 -0
  176. package/dist/geom/collide/slide.js +375 -0
  177. package/dist/geom/collide/tile-slide.d.ts +88 -0
  178. package/dist/geom/collide/tile-slide.js +255 -0
  179. package/dist/geom/geometry.d.ts +95 -0
  180. package/dist/geom/geometry.js +322 -0
  181. package/dist/geom/heightfield.d.ts +72 -0
  182. package/dist/geom/heightfield.js +295 -0
  183. package/dist/geom/hull.d.ts +27 -0
  184. package/dist/geom/hull.js +279 -0
  185. package/dist/geom/index.d.ts +35 -0
  186. package/dist/geom/index.js +23 -0
  187. package/dist/geom/iso.d.ts +74 -0
  188. package/dist/geom/iso.js +89 -0
  189. package/dist/geom/lod.d.ts +29 -0
  190. package/dist/geom/lod.js +27 -0
  191. package/dist/geom/ops.d.ts +47 -0
  192. package/dist/geom/ops.js +285 -0
  193. package/dist/geom/primitives.d.ts +97 -0
  194. package/dist/geom/primitives.js +359 -0
  195. package/dist/geom/shapes.d.ts +43 -0
  196. package/dist/geom/shapes.js +88 -0
  197. package/dist/geom/simplify.d.ts +38 -0
  198. package/dist/geom/simplify.js +519 -0
  199. package/dist/geom/split.d.ts +37 -0
  200. package/dist/geom/split.js +413 -0
  201. package/dist/geom/sweep.d.ts +83 -0
  202. package/dist/geom/sweep.js +534 -0
  203. package/dist/geom/tangents.d.ts +16 -0
  204. package/dist/geom/tangents.js +124 -0
  205. package/dist/geom/triangulate.d.ts +19 -0
  206. package/dist/geom/triangulate.js +147 -0
  207. package/dist/host/default.d.ts +10 -0
  208. package/dist/host/default.js +10 -0
  209. package/dist/host/index.d.ts +4 -0
  210. package/dist/host/index.js +3 -0
  211. package/dist/host/types.d.ts +144 -0
  212. package/dist/host/types.js +1 -0
  213. package/dist/host/web-links.d.ts +38 -0
  214. package/dist/host/web-links.js +110 -0
  215. package/dist/host/web.d.ts +30 -0
  216. package/dist/host/web.js +216 -0
  217. package/dist/host/xr.d.ts +24 -0
  218. package/dist/host/xr.js +59 -0
  219. package/dist/i18n/i18n.d.ts +59 -0
  220. package/dist/i18n/i18n.js +241 -0
  221. package/dist/i18n/index.d.ts +3 -0
  222. package/dist/i18n/index.js +3 -0
  223. package/dist/i18n/message.d.ts +72 -0
  224. package/dist/i18n/message.js +369 -0
  225. package/dist/i18n/negotiate.d.ts +17 -0
  226. package/dist/i18n/negotiate.js +58 -0
  227. package/dist/index.d.ts +7 -0
  228. package/dist/index.js +1 -0
  229. package/dist/input/bindings.d.ts +31 -0
  230. package/dist/input/bindings.js +141 -0
  231. package/dist/input/clock.d.ts +44 -0
  232. package/dist/input/clock.js +49 -0
  233. package/dist/input/follow.d.ts +133 -0
  234. package/dist/input/follow.js +218 -0
  235. package/dist/input/gestures.d.ts +281 -0
  236. package/dist/input/gestures.js +662 -0
  237. package/dist/input/glyphs.d.ts +35 -0
  238. package/dist/input/glyphs.js +216 -0
  239. package/dist/input/gyro.d.ts +85 -0
  240. package/dist/input/gyro.js +140 -0
  241. package/dist/input/index.d.ts +19 -0
  242. package/dist/input/index.js +16 -0
  243. package/dist/input/input.d.ts +543 -0
  244. package/dist/input/input.js +1492 -0
  245. package/dist/input/look.d.ts +327 -0
  246. package/dist/input/look.js +681 -0
  247. package/dist/input/magnify.d.ts +177 -0
  248. package/dist/input/magnify.js +131 -0
  249. package/dist/input/pointer-dispatch.d.ts +138 -0
  250. package/dist/input/pointer-dispatch.js +180 -0
  251. package/dist/input/pointer-router.d.ts +204 -0
  252. package/dist/input/pointer-router.js +533 -0
  253. package/dist/input/pointer-sources.d.ts +164 -0
  254. package/dist/input/pointer-sources.js +306 -0
  255. package/dist/input/rays.d.ts +138 -0
  256. package/dist/input/rays.js +615 -0
  257. package/dist/input/record.d.ts +209 -0
  258. package/dist/input/record.js +601 -0
  259. package/dist/input/touch-zones.d.ts +147 -0
  260. package/dist/input/touch-zones.js +300 -0
  261. package/dist/input/types.d.ts +158 -0
  262. package/dist/input/types.js +1 -0
  263. package/dist/input/xr-source.d.ts +53 -0
  264. package/dist/input/xr-source.js +164 -0
  265. package/dist/nav/astar.d.ts +45 -0
  266. package/dist/nav/astar.js +182 -0
  267. package/dist/nav/corridor.d.ts +40 -0
  268. package/dist/nav/corridor.js +95 -0
  269. package/dist/nav/crowd.d.ts +94 -0
  270. package/dist/nav/crowd.js +624 -0
  271. package/dist/nav/flow.d.ts +61 -0
  272. package/dist/nav/flow.js +193 -0
  273. package/dist/nav/grid.d.ts +108 -0
  274. package/dist/nav/grid.js +210 -0
  275. package/dist/nav/heap.d.ts +22 -0
  276. package/dist/nav/heap.js +92 -0
  277. package/dist/nav/index.d.ts +37 -0
  278. package/dist/nav/index.js +19 -0
  279. package/dist/nav/mesh/areas.d.ts +44 -0
  280. package/dist/nav/mesh/areas.js +98 -0
  281. package/dist/nav/mesh/bake-options.d.ts +33 -0
  282. package/dist/nav/mesh/bake-options.js +76 -0
  283. package/dist/nav/mesh/contour.d.ts +22 -0
  284. package/dist/nav/mesh/contour.js +323 -0
  285. package/dist/nav/mesh/flow3.d.ts +35 -0
  286. package/dist/nav/mesh/flow3.js +229 -0
  287. package/dist/nav/mesh/links.d.ts +38 -0
  288. package/dist/nav/mesh/links.js +72 -0
  289. package/dist/nav/mesh/mesh-path.d.ts +152 -0
  290. package/dist/nav/mesh/mesh-path.js +643 -0
  291. package/dist/nav/mesh/mesh-reach.d.ts +78 -0
  292. package/dist/nav/mesh/mesh-reach.js +450 -0
  293. package/dist/nav/mesh/navmesh.d.ts +117 -0
  294. package/dist/nav/mesh/navmesh.js +647 -0
  295. package/dist/nav/mesh/polymesh.d.ts +24 -0
  296. package/dist/nav/mesh/polymesh.js +384 -0
  297. package/dist/nav/mesh/regions.d.ts +12 -0
  298. package/dist/nav/mesh/regions.js +367 -0
  299. package/dist/nav/mesh/serialize.d.ts +13 -0
  300. package/dist/nav/mesh/serialize.js +197 -0
  301. package/dist/nav/mesh/voxelize.d.ts +111 -0
  302. package/dist/nav/mesh/voxelize.js +552 -0
  303. package/dist/nav/obstacles.d.ts +75 -0
  304. package/dist/nav/obstacles.js +294 -0
  305. package/dist/nav/path.d.ts +59 -0
  306. package/dist/nav/path.js +177 -0
  307. package/dist/nav/query-queue.d.ts +46 -0
  308. package/dist/nav/query-queue.js +308 -0
  309. package/dist/nav/reach.d.ts +53 -0
  310. package/dist/nav/reach.js +148 -0
  311. package/dist/nav/steer.d.ts +36 -0
  312. package/dist/nav/steer.js +78 -0
  313. package/dist/platform/boot.d.ts +28 -0
  314. package/dist/platform/boot.js +43 -0
  315. package/dist/platform/device-mode.d.ts +40 -0
  316. package/dist/platform/device-mode.js +104 -0
  317. package/dist/platform/device.d.ts +31 -0
  318. package/dist/platform/device.js +91 -0
  319. package/dist/platform/environment.d.ts +42 -0
  320. package/dist/platform/environment.js +82 -0
  321. package/dist/platform/gpu.d.ts +40 -0
  322. package/dist/platform/gpu.js +57 -0
  323. package/dist/platform/index.d.ts +18 -0
  324. package/dist/platform/index.js +10 -0
  325. package/dist/platform/letterbox.d.ts +43 -0
  326. package/dist/platform/letterbox.js +57 -0
  327. package/dist/platform/links.d.ts +20 -0
  328. package/dist/platform/links.js +43 -0
  329. package/dist/platform/screens.d.ts +36 -0
  330. package/dist/platform/screens.js +138 -0
  331. package/dist/platform/surface.d.ts +72 -0
  332. package/dist/platform/surface.js +157 -0
  333. package/dist/platform/xr-fake.d.ts +168 -0
  334. package/dist/platform/xr-fake.js +434 -0
  335. package/dist/platform/xr-surface.d.ts +120 -0
  336. package/dist/platform/xr-surface.js +214 -0
  337. package/dist/platform/xr-types.d.ts +262 -0
  338. package/dist/post/ao.d.ts +12 -0
  339. package/dist/post/ao.js +136 -0
  340. package/dist/post/blend.d.ts +27 -0
  341. package/dist/post/blend.js +129 -0
  342. package/dist/post/depth-pass.d.ts +43 -0
  343. package/dist/post/depth-pass.js +118 -0
  344. package/dist/post/dof.d.ts +1 -0
  345. package/dist/post/dof.js +66 -0
  346. package/dist/post/exposure.d.ts +80 -0
  347. package/dist/post/exposure.js +322 -0
  348. package/dist/post/index.d.ts +12 -0
  349. package/dist/post/index.js +11 -0
  350. package/dist/post/lens-dirt.d.ts +13 -0
  351. package/dist/post/lens-dirt.js +91 -0
  352. package/dist/post/lut.d.ts +19 -0
  353. package/dist/post/lut.js +50 -0
  354. package/dist/post/motion-blur.d.ts +9 -0
  355. package/dist/post/motion-blur.js +78 -0
  356. package/dist/post/post-stack.d.ts +302 -0
  357. package/dist/post/post-stack.js +1137 -0
  358. package/dist/post/settings.d.ts +485 -0
  359. package/dist/post/settings.js +416 -0
  360. package/dist/post/shaders.d.ts +25 -0
  361. package/dist/post/shaders.js +410 -0
  362. package/dist/post/ssr.d.ts +16 -0
  363. package/dist/post/ssr.js +155 -0
  364. package/dist/post/taa.d.ts +74 -0
  365. package/dist/post/taa.js +235 -0
  366. package/dist/post/white-balance.d.ts +25 -0
  367. package/dist/post/white-balance.js +98 -0
  368. package/dist/render/bindings.d.ts +98 -0
  369. package/dist/render/bindings.js +281 -0
  370. package/dist/render/blend.d.ts +13 -0
  371. package/dist/render/blend.js +26 -0
  372. package/dist/render/camera-2d.d.ts +72 -0
  373. package/dist/render/camera-2d.js +92 -0
  374. package/dist/render/camera.d.ts +302 -0
  375. package/dist/render/camera.js +671 -0
  376. package/dist/render/color.d.ts +16 -0
  377. package/dist/render/color.js +41 -0
  378. package/dist/render/compressed.d.ts +104 -0
  379. package/dist/render/compressed.js +248 -0
  380. package/dist/render/diagnostics.d.ts +15 -0
  381. package/dist/render/diagnostics.js +43 -0
  382. package/dist/render/dynamic-resolution.d.ts +70 -0
  383. package/dist/render/dynamic-resolution.js +126 -0
  384. package/dist/render/fake-gpu.d.ts +204 -0
  385. package/dist/render/fake-gpu.js +426 -0
  386. package/dist/render/flipbook.d.ts +56 -0
  387. package/dist/render/flipbook.js +87 -0
  388. package/dist/render/frame.d.ts +278 -0
  389. package/dist/render/frame.js +174 -0
  390. package/dist/render/fullscreen.d.ts +54 -0
  391. package/dist/render/fullscreen.js +91 -0
  392. package/dist/render/gpu-timer.d.ts +73 -0
  393. package/dist/render/gpu-timer.js +186 -0
  394. package/dist/render/index.d.ts +53 -0
  395. package/dist/render/index.js +29 -0
  396. package/dist/render/instance-list.d.ts +185 -0
  397. package/dist/render/instance-list.js +547 -0
  398. package/dist/render/lights.d.ts +181 -0
  399. package/dist/render/lights.js +233 -0
  400. package/dist/render/lod.d.ts +54 -0
  401. package/dist/render/lod.js +52 -0
  402. package/dist/render/material.d.ts +165 -0
  403. package/dist/render/material.js +292 -0
  404. package/dist/render/mesh.d.ts +284 -0
  405. package/dist/render/mesh.js +472 -0
  406. package/dist/render/mipmap.d.ts +16 -0
  407. package/dist/render/mipmap.js +121 -0
  408. package/dist/render/parallax.d.ts +50 -0
  409. package/dist/render/parallax.js +47 -0
  410. package/dist/render/pbr-wgsl.d.ts +31 -0
  411. package/dist/render/pbr-wgsl.js +119 -0
  412. package/dist/render/ping-pong.d.ts +42 -0
  413. package/dist/render/ping-pong.js +74 -0
  414. package/dist/render/renderer.d.ts +741 -0
  415. package/dist/render/renderer.js +2486 -0
  416. package/dist/render/scene-pipelines.d.ts +44 -0
  417. package/dist/render/scene-pipelines.js +92 -0
  418. package/dist/render/scene-target.d.ts +170 -0
  419. package/dist/render/scene-target.js +450 -0
  420. package/dist/render/shadow.d.ts +74 -0
  421. package/dist/render/shadow.js +208 -0
  422. package/dist/render/skin.d.ts +112 -0
  423. package/dist/render/skin.js +240 -0
  424. package/dist/render/snapshot.d.ts +36 -0
  425. package/dist/render/snapshot.js +34 -0
  426. package/dist/render/standard.d.ts +162 -0
  427. package/dist/render/standard.js +263 -0
  428. package/dist/render/texture.d.ts +142 -0
  429. package/dist/render/texture.js +228 -0
  430. package/dist/render/tile-layer.d.ts +110 -0
  431. package/dist/render/tile-layer.js +304 -0
  432. package/dist/render/transform.d.ts +115 -0
  433. package/dist/render/transform.js +318 -0
  434. package/dist/render/wgsl.d.ts +131 -0
  435. package/dist/render/wgsl.js +528 -0
  436. package/dist/save/codec.d.ts +49 -0
  437. package/dist/save/codec.js +169 -0
  438. package/dist/save/index.d.ts +8 -0
  439. package/dist/save/index.js +8 -0
  440. package/dist/save/journal.d.ts +24 -0
  441. package/dist/save/journal.js +102 -0
  442. package/dist/save/migrate.d.ts +15 -0
  443. package/dist/save/migrate.js +37 -0
  444. package/dist/save/schema.d.ts +72 -0
  445. package/dist/save/schema.js +154 -0
  446. package/dist/save/settings.d.ts +28 -0
  447. package/dist/save/settings.js +59 -0
  448. package/dist/save/slots.d.ts +56 -0
  449. package/dist/save/slots.js +200 -0
  450. package/dist/save/storage.d.ts +35 -0
  451. package/dist/save/storage.js +62 -0
  452. package/dist/save/store.d.ts +114 -0
  453. package/dist/save/store.js +269 -0
  454. package/dist/testing/fake-host.d.ts +103 -0
  455. package/dist/testing/fake-host.js +163 -0
  456. package/dist/testing/fixed-clock.d.ts +25 -0
  457. package/dist/testing/fixed-clock.js +30 -0
  458. package/dist/testing/index.d.ts +4 -0
  459. package/dist/testing/index.js +5 -0
  460. package/dist/testing/test-game.d.ts +76 -0
  461. package/dist/testing/test-game.js +92 -0
  462. package/dist/tween/ease.d.ts +77 -0
  463. package/dist/tween/ease.js +192 -0
  464. package/dist/tween/index.d.ts +8 -0
  465. package/dist/tween/index.js +7 -0
  466. package/dist/tween/timeline.d.ts +78 -0
  467. package/dist/tween/timeline.js +269 -0
  468. package/dist/tween/tween.d.ts +168 -0
  469. package/dist/tween/tween.js +320 -0
  470. package/dist/ui/anchor.d.ts +206 -0
  471. package/dist/ui/anchor.js +307 -0
  472. package/dist/ui/caption.d.ts +122 -0
  473. package/dist/ui/caption.js +286 -0
  474. package/dist/ui/cursor.d.ts +105 -0
  475. package/dist/ui/cursor.js +261 -0
  476. package/dist/ui/dom.d.ts +30 -0
  477. package/dist/ui/dom.js +92 -0
  478. package/dist/ui/floats.d.ts +86 -0
  479. package/dist/ui/floats.js +260 -0
  480. package/dist/ui/hud.d.ts +198 -0
  481. package/dist/ui/hud.js +431 -0
  482. package/dist/ui/index.d.ts +37 -0
  483. package/dist/ui/index.js +34 -0
  484. package/dist/ui/inventory-card.d.ts +52 -0
  485. package/dist/ui/inventory-card.js +173 -0
  486. package/dist/ui/inventory-context.d.ts +29 -0
  487. package/dist/ui/inventory-context.js +125 -0
  488. package/dist/ui/inventory-drag.d.ts +63 -0
  489. package/dist/ui/inventory-drag.js +394 -0
  490. package/dist/ui/inventory-grid.d.ts +66 -0
  491. package/dist/ui/inventory-grid.js +625 -0
  492. package/dist/ui/inventory-radial.d.ts +39 -0
  493. package/dist/ui/inventory-radial.js +168 -0
  494. package/dist/ui/inventory.d.ts +128 -0
  495. package/dist/ui/inventory.js +154 -0
  496. package/dist/ui/layout.d.ts +356 -0
  497. package/dist/ui/layout.js +856 -0
  498. package/dist/ui/link.d.ts +64 -0
  499. package/dist/ui/link.js +155 -0
  500. package/dist/ui/loading.d.ts +40 -0
  501. package/dist/ui/loading.js +157 -0
  502. package/dist/ui/loupe.d.ts +74 -0
  503. package/dist/ui/loupe.js +164 -0
  504. package/dist/ui/menu-nav.d.ts +33 -0
  505. package/dist/ui/menu-nav.js +28 -0
  506. package/dist/ui/minimap-canvas.d.ts +145 -0
  507. package/dist/ui/minimap-canvas.js +460 -0
  508. package/dist/ui/notify.d.ts +40 -0
  509. package/dist/ui/notify.js +141 -0
  510. package/dist/ui/orb.d.ts +53 -0
  511. package/dist/ui/orb.js +304 -0
  512. package/dist/ui/overlay.d.ts +213 -0
  513. package/dist/ui/overlay.js +394 -0
  514. package/dist/ui/padnav.d.ts +53 -0
  515. package/dist/ui/padnav.js +146 -0
  516. package/dist/ui/perf.d.ts +130 -0
  517. package/dist/ui/perf.js +329 -0
  518. package/dist/ui/rotate-card.d.ts +54 -0
  519. package/dist/ui/rotate-card.js +97 -0
  520. package/dist/ui/stack.d.ts +128 -0
  521. package/dist/ui/stack.js +312 -0
  522. package/dist/ui/styles.d.ts +13 -0
  523. package/dist/ui/styles.js +200 -0
  524. package/dist/ui/text-scale.d.ts +67 -0
  525. package/dist/ui/text-scale.js +102 -0
  526. package/dist/ui/theme.d.ts +70 -0
  527. package/dist/ui/theme.js +98 -0
  528. package/dist/ui/toast.d.ts +23 -0
  529. package/dist/ui/toast.js +46 -0
  530. package/dist/ui/tooltip.d.ts +89 -0
  531. package/dist/ui/tooltip.js +333 -0
  532. package/dist/ui/widgets-2.d.ts +134 -0
  533. package/dist/ui/widgets-2.js +399 -0
  534. package/dist/ui/widgets-3.d.ts +91 -0
  535. package/dist/ui/widgets-3.js +212 -0
  536. package/dist/ui/widgets.d.ts +34 -0
  537. package/dist/ui/widgets.js +50 -0
  538. package/dist/ui/world-panel/index.d.ts +5 -0
  539. package/dist/ui/world-panel/index.js +5 -0
  540. package/dist/ui/world-panel/layout.d.ts +38 -0
  541. package/dist/ui/world-panel/layout.js +68 -0
  542. package/dist/ui/world-panel/paint.d.ts +96 -0
  543. package/dist/ui/world-panel/paint.js +136 -0
  544. package/dist/ui/world-panel/panel.d.ts +235 -0
  545. package/dist/ui/world-panel/panel.js +762 -0
  546. package/dist/ui/world-panel/wgsl.d.ts +10 -0
  547. package/dist/ui/world-panel/wgsl.js +52 -0
  548. package/dist/ui/world-panel/widgets.d.ts +207 -0
  549. package/dist/ui/world-panel/widgets.js +569 -0
  550. package/dist/voxel/chunks.d.ts +77 -0
  551. package/dist/voxel/chunks.js +149 -0
  552. package/dist/voxel/grid.d.ts +84 -0
  553. package/dist/voxel/grid.js +223 -0
  554. package/dist/voxel/index.d.ts +22 -0
  555. package/dist/voxel/index.js +7 -0
  556. package/dist/voxel/mesher-worker.d.ts +14 -0
  557. package/dist/voxel/mesher-worker.js +47 -0
  558. package/dist/voxel/mesher.d.ts +73 -0
  559. package/dist/voxel/mesher.js +306 -0
  560. package/dist/voxel/ray.d.ts +32 -0
  561. package/dist/voxel/ray.js +73 -0
  562. package/dist/voxel/wgsl.d.ts +16 -0
  563. package/dist/voxel/wgsl.js +28 -0
  564. package/dist/voxel/worker-client.d.ts +79 -0
  565. package/dist/voxel/worker-client.js +122 -0
  566. package/package.json +177 -3
@@ -0,0 +1,269 @@
1
+ import { defaultHost } from '../host/default.js';
2
+ import { decodeExport, encodeExport, isEnvelope, saveReplacer, saveReviver, } from './codec.js';
3
+ import { assertMigrationChain, runMigrations } from './migrate.js';
4
+ import { isQuotaError } from './storage.js';
5
+ const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/i;
6
+ /** `thunderclap:<game>:<slot>` */
7
+ export function saveKey(game, slot) {
8
+ if (!NAME_RE.test(game))
9
+ throw new Error(`invalid save game id "${game}"`);
10
+ if (!NAME_RE.test(slot))
11
+ throw new Error(`invalid save slot "${slot}"`);
12
+ return `thunderclap:${game}:${slot}`;
13
+ }
14
+ /** Slot names that currently have data for `game` (excludes backups). */
15
+ export function listSlots(game, storage = defaultHost().storage.open()) {
16
+ if (!storage)
17
+ return [];
18
+ const prefix = `thunderclap:${game}:`;
19
+ const out = [];
20
+ try {
21
+ for (let i = 0; i < storage.length; i++) {
22
+ const k = storage.key(i);
23
+ if (k?.startsWith(prefix) && !k.endsWith(':backup') && !k.endsWith(':meta'))
24
+ out.push(k.slice(prefix.length));
25
+ }
26
+ }
27
+ catch {
28
+ return [];
29
+ }
30
+ return out.sort();
31
+ }
32
+ export function createSaveStore(opts) {
33
+ const key = saveKey(opts.game, opts.slot ?? 'main');
34
+ const backupKey = `${key}:backup`;
35
+ const metaKey = `${key}:meta`;
36
+ const version = opts.version;
37
+ if (!Number.isInteger(version) || version < 1)
38
+ throw new Error('save version must be an integer ≥ 1');
39
+ const migrations = opts.migrations ?? {};
40
+ assertMigrationChain(version, migrations);
41
+ const debounceMs = opts.debounceMs ?? 250;
42
+ const now = opts.now ?? Date.now;
43
+ const check = typeof opts.schema === 'function'
44
+ ? (v) => opts.schema(v) ? null : 'save data failed schema check'
45
+ : (v) => opts.schema.validate(v);
46
+ const source = opts.storage === undefined ? (opts.host ?? defaultHost()).storage : null;
47
+ let storage = source ? source.open() : (opts.storage ?? null);
48
+ const status = {
49
+ persistent: storage !== null,
50
+ loadResult: 'fresh',
51
+ error: storage === null ? 'storage unavailable; progress is kept in memory only' : null,
52
+ dirty: false,
53
+ };
54
+ const listeners = new Set();
55
+ let data;
56
+ let savedAt = 0;
57
+ let timer = null;
58
+ const notify = () => {
59
+ for (const fn of listeners)
60
+ fn(status);
61
+ };
62
+ /** Turn an envelope into current-version data, or throw with a reason. */
63
+ const adopt = (env) => {
64
+ const value = runMigrations(env.d, env.v, version, migrations);
65
+ const err = check(value);
66
+ if (err)
67
+ throw new Error(err);
68
+ return { value: value, migrated: env.v !== version };
69
+ };
70
+ const readRaw = () => {
71
+ if (!storage)
72
+ return null;
73
+ try {
74
+ return storage.getItem(key);
75
+ }
76
+ catch (err) {
77
+ status.persistent = false;
78
+ status.error = `storage read failed: ${String(err)}`;
79
+ return null;
80
+ }
81
+ };
82
+ const backup = (raw) => {
83
+ try {
84
+ storage?.setItem(backupKey, raw);
85
+ }
86
+ catch {
87
+ // Best effort.
88
+ }
89
+ };
90
+ // ---- initial load ----
91
+ const raw = readRaw();
92
+ data = opts.defaults();
93
+ if (raw !== null) {
94
+ try {
95
+ const parsed = JSON.parse(raw, saveReviver);
96
+ if (!isEnvelope(parsed))
97
+ throw new Error('stored value has no version envelope');
98
+ const { value, migrated } = adopt(parsed);
99
+ data = value;
100
+ savedAt = parsed.t;
101
+ status.loadResult = migrated ? 'migrated' : 'loaded';
102
+ status.dirty = migrated;
103
+ // Keep the pre-migration bytes until the player has played on the new version a while.
104
+ if (migrated)
105
+ backup(raw);
106
+ }
107
+ catch (err) {
108
+ backup(raw);
109
+ status.loadResult = 'recovered';
110
+ status.error = `could not load save (kept a backup): ${err instanceof Error ? err.message : String(err)}`;
111
+ }
112
+ }
113
+ const write = () => {
114
+ if (timer !== null) {
115
+ clearTimeout(timer);
116
+ timer = null;
117
+ }
118
+ if (!status.dirty)
119
+ return status.persistent;
120
+ savedAt = now();
121
+ const env = { v: version, t: savedAt, d: data };
122
+ if (!storage) {
123
+ // Re-probe: storage may have become available (e.g. user changed a browser setting).
124
+ if (source)
125
+ storage = source.open();
126
+ }
127
+ if (storage) {
128
+ try {
129
+ const text = JSON.stringify(env, saveReplacer);
130
+ storage.setItem(key, text);
131
+ status.persistent = true;
132
+ status.error = null;
133
+ if (opts.meta)
134
+ writeMeta(storage, text.length);
135
+ }
136
+ catch (err) {
137
+ status.persistent = false;
138
+ status.error = isQuotaError(err)
139
+ ? 'storage is full; progress is kept in memory only'
140
+ : `storage write failed; progress is kept in memory only (${String(err)})`;
141
+ }
142
+ }
143
+ else {
144
+ status.persistent = false;
145
+ }
146
+ status.dirty = false;
147
+ notify();
148
+ return status.persistent;
149
+ };
150
+ const writeMeta = (target, size) => {
151
+ try {
152
+ const m = { ...opts.meta(data), v: version, t: savedAt, size };
153
+ target.setItem(metaKey, JSON.stringify(m));
154
+ }
155
+ catch {
156
+ // Best effort: the data is saved; the menu falls back to reading it.
157
+ }
158
+ };
159
+ const schedule = () => {
160
+ status.dirty = true;
161
+ if (debounceMs <= 0) {
162
+ write();
163
+ return;
164
+ }
165
+ if (timer === null)
166
+ timer = setTimeout(write, debounceMs);
167
+ notify();
168
+ };
169
+ const set = (next) => {
170
+ const err = check(next);
171
+ if (err) {
172
+ status.error = `rejected invalid save data: ${err}`;
173
+ console.warn(`[save] ${status.error}`);
174
+ notify();
175
+ return false;
176
+ }
177
+ data = next;
178
+ schedule();
179
+ return true;
180
+ };
181
+ // ---- auto flush on page hide ----
182
+ const autoFlush = opts.autoFlush ?? typeof window !== 'undefined';
183
+ const onHide = () => {
184
+ if (typeof document === 'undefined' || document.visibilityState === 'hidden')
185
+ write();
186
+ };
187
+ const onPageHide = () => write();
188
+ if (autoFlush && typeof window !== 'undefined') {
189
+ window.addEventListener('pagehide', onPageHide);
190
+ document.addEventListener('visibilitychange', onHide);
191
+ // Chrome on Android freezes background tabs and may later discard them with no other event.
192
+ document.addEventListener('freeze', onPageHide);
193
+ }
194
+ const store = {
195
+ key,
196
+ get persistent() {
197
+ return status.persistent;
198
+ },
199
+ get status() {
200
+ return status;
201
+ },
202
+ get savedAt() {
203
+ return savedAt;
204
+ },
205
+ get: () => data,
206
+ set,
207
+ update(fn) {
208
+ const draft = structuredClone(data);
209
+ const result = fn(draft);
210
+ return set(result === undefined ? draft : result);
211
+ },
212
+ flush: write,
213
+ reset() {
214
+ data = opts.defaults();
215
+ status.dirty = true;
216
+ write();
217
+ },
218
+ clear() {
219
+ if (timer !== null)
220
+ clearTimeout(timer);
221
+ timer = null;
222
+ try {
223
+ storage?.removeItem(key);
224
+ storage?.removeItem(backupKey);
225
+ storage?.removeItem(metaKey);
226
+ }
227
+ catch {
228
+ // ignore
229
+ }
230
+ data = opts.defaults();
231
+ savedAt = 0;
232
+ status.dirty = false;
233
+ status.loadResult = 'fresh';
234
+ notify();
235
+ },
236
+ exportString() {
237
+ return encodeExport(opts.game, { v: version, t: status.dirty ? now() : savedAt, d: data });
238
+ },
239
+ importString(text) {
240
+ const decoded = decodeExport(opts.game, text);
241
+ if (!decoded.ok)
242
+ return decoded;
243
+ try {
244
+ const { value, migrated } = adopt(decoded.envelope);
245
+ data = value;
246
+ status.dirty = true;
247
+ write();
248
+ return { ok: true, migrated };
249
+ }
250
+ catch (err) {
251
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
252
+ }
253
+ },
254
+ subscribe(fn) {
255
+ listeners.add(fn);
256
+ return () => listeners.delete(fn);
257
+ },
258
+ dispose() {
259
+ write();
260
+ listeners.clear();
261
+ if (autoFlush && typeof window !== 'undefined') {
262
+ window.removeEventListener('pagehide', onPageHide);
263
+ document.removeEventListener('visibilitychange', onHide);
264
+ document.removeEventListener('freeze', onPageHide);
265
+ }
266
+ },
267
+ };
268
+ return store;
269
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Test-only `Host` (ws/testing-kit): everything a page or shell offers the game, in memory and
3
+ * under the test's control. Storage is a `MemoryStorage`, the lifecycle moves on `setState`,
4
+ * haptics, fullscreen, links, copies and wake-lock requests are written down, the safe area is
5
+ * set by hand, and frames run only when the test calls `runFrame`. Nothing touches the DOM.
6
+ */
7
+ import type { HapticImpact, HapticNotification, Host, HostHaptics, HostInsets, HostKind, HostLifecycle, HostLifecycleState, HostSafeArea } from '../host/index.js';
8
+ import type { CopyResult, EmbedContext, LinkActivation, OpenResult } from '../platform/index.js';
9
+ import { type StorageLike } from '../save/index.js';
10
+ /** `createFakeHost` options. Every field has a default that makes the host fully capable. */
11
+ export interface FakeHostOptions {
12
+ /** `Host.kind`. Default `'web'`. */
13
+ kind?: HostKind;
14
+ /** `Host.embed`. Default `'top'`. */
15
+ embed?: EmbedContext;
16
+ /**
17
+ * The save backend `storage.open()` returns. Default a fresh `MemoryStorage`; `null` is a host
18
+ * with no working storage (`open()` returns null), as when the browser blocks it.
19
+ */
20
+ storage?: StorageLike | null;
21
+ /** The lifecycle state at creation. Default `'active'`. */
22
+ state?: HostLifecycleState;
23
+ /** The safe-area insets at creation, CSS px. Missing sides are 0. */
24
+ safeArea?: Partial<HostInsets>;
25
+ /** `fullscreen.available()`. Default true. When false, `toggle` resolves false and changes nothing. */
26
+ fullscreen?: boolean;
27
+ /** `haptics.available()`. Default true. Calls are recorded either way. */
28
+ haptics?: boolean;
29
+ /** What `openExternal` answers. Default `'opened'`. */
30
+ openResult?: OpenResult;
31
+ /** What `copyText` resolves to. Default `'copied'`. */
32
+ copyResult?: CopyResult;
33
+ /** `clipboardWriteAllowed()`. Default true. */
34
+ clipboardWriteAllowed?: boolean;
35
+ /** Timestamp of the first `runFrame` that is given none, ms. Default 0. */
36
+ startTime?: number;
37
+ /** How far a `runFrame` without a timestamp moves frame time, ms. Default 1000 / 60. */
38
+ frameMs?: number;
39
+ }
40
+ /** One haptic call, in the order made. */
41
+ export type FakeHapticCall = {
42
+ readonly op: 'impact';
43
+ readonly style: HapticImpact;
44
+ } | {
45
+ readonly op: 'notification';
46
+ readonly kind: HapticNotification;
47
+ } | {
48
+ readonly op: 'vibrate';
49
+ readonly ms: number;
50
+ };
51
+ /**
52
+ * A wake-lock stub. `Host` has no wake-lock facet yet; this records what a game that keeps the
53
+ * screen on through its own code would ask for, so the test can assert it.
54
+ */
55
+ export interface FakeWakeLock {
56
+ /** True between a `request()` and the next `release()`. */
57
+ readonly held: boolean;
58
+ /** Every call, in order. */
59
+ readonly calls: readonly ('request' | 'release')[];
60
+ /** Takes the lock. Resolves true. */
61
+ request(): Promise<boolean>;
62
+ /** Drops the lock; a second release does nothing more than record the call. */
63
+ release(): void;
64
+ }
65
+ /** A full `Host` the test drives. Every optional facet is present. */
66
+ export interface FakeHost extends Host {
67
+ readonly lifecycle: HostLifecycle;
68
+ readonly haptics: HostHaptics;
69
+ readonly safeArea: HostSafeArea;
70
+ /** The backend `storage.open()` returns (the same object every call), or null. */
71
+ readonly memory: StorageLike | null;
72
+ /** Moves the lifecycle; watchers run only when the state changes. */
73
+ setState(state: HostLifecycleState): void;
74
+ /** Sets some or all insets; watchers run only when a value changes. */
75
+ setSafeArea(insets: Partial<HostInsets>): void;
76
+ /** Every haptic call, in order. */
77
+ readonly hapticCalls: readonly FakeHapticCall[];
78
+ /** Every `fullscreen.toggle` call with its options, in order. */
79
+ readonly fullscreenCalls: readonly {
80
+ readonly lockLandscape?: boolean;
81
+ }[];
82
+ /** Every `openExternal` call, in order. */
83
+ readonly opened: readonly {
84
+ readonly url: string;
85
+ readonly activation: LinkActivation;
86
+ }[];
87
+ /** Every `copyText` argument, in order. */
88
+ readonly copied: readonly string[];
89
+ /** The wake-lock stub. */
90
+ readonly wakeLock: FakeWakeLock;
91
+ /** Frame requests waiting for the next `runFrame`. */
92
+ readonly pendingFrames: number;
93
+ /** The timestamp of the last `runFrame`, ms (`startTime` before the first one). */
94
+ readonly frameTime: number;
95
+ /**
96
+ * Runs every frame requested before this call, with `timeMs` (default: the last frame time
97
+ * plus `frameMs`). A callback that requests another frame is queued for the next call, so one
98
+ * `runFrame` is one display frame. Returns how many callbacks ran.
99
+ */
100
+ runFrame(timeMs?: number): number;
101
+ }
102
+ /** A recording, fully controllable `Host` for unit tests. See `FakeHostOptions`. */
103
+ export declare function createFakeHost(opts?: FakeHostOptions): FakeHost;
@@ -0,0 +1,163 @@
1
+ import { MemoryStorage } from '../save/index.js';
2
+ /** A recording, fully controllable `Host` for unit tests. See `FakeHostOptions`. */
3
+ export function createFakeHost(opts = {}) {
4
+ const memory = opts.storage === undefined ? new MemoryStorage() : opts.storage;
5
+ const frameMs = opts.frameMs ?? 1000 / 60;
6
+ let frameTime = opts.startTime ?? 0;
7
+ let firstFrame = true;
8
+ let nextHandle = 1;
9
+ let queue = new Map();
10
+ /** The batch `runFrame` is running, so a callback can cancel one later in it. */
11
+ let running = null;
12
+ let state = opts.state ?? 'active';
13
+ const stateWatchers = new Set();
14
+ const lifecycle = {
15
+ get state() {
16
+ return state;
17
+ },
18
+ watch(fn) {
19
+ stateWatchers.add(fn);
20
+ return () => stateWatchers.delete(fn);
21
+ },
22
+ };
23
+ const insets = { top: 0, right: 0, bottom: 0, left: 0, ...opts.safeArea };
24
+ const insetWatchers = new Set();
25
+ const safeArea = {
26
+ insets: () => insets,
27
+ watch(fn) {
28
+ insetWatchers.add(fn);
29
+ return () => insetWatchers.delete(fn);
30
+ },
31
+ };
32
+ const hapticCalls = [];
33
+ const hapticsOn = opts.haptics ?? true;
34
+ const haptics = {
35
+ available: () => hapticsOn,
36
+ impact: (style) => hapticCalls.push({ op: 'impact', style }),
37
+ notification: (kind) => hapticCalls.push({ op: 'notification', kind }),
38
+ vibrate: (ms) => hapticCalls.push({ op: 'vibrate', ms }),
39
+ };
40
+ const fullscreenCalls = [];
41
+ const fullscreenOn = opts.fullscreen ?? true;
42
+ let fullscreenActive = false;
43
+ const fullscreenWatchers = new Set();
44
+ const fullscreen = {
45
+ available: () => fullscreenOn,
46
+ get active() {
47
+ return fullscreenActive;
48
+ },
49
+ toggle(topts) {
50
+ fullscreenCalls.push(topts?.lockLandscape === undefined ? {} : { lockLandscape: topts.lockLandscape });
51
+ if (!fullscreenOn)
52
+ return Promise.resolve(false);
53
+ fullscreenActive = !fullscreenActive;
54
+ for (const fn of [...fullscreenWatchers])
55
+ fn(fullscreenActive);
56
+ return Promise.resolve(fullscreenActive);
57
+ },
58
+ watch(fn) {
59
+ fullscreenWatchers.add(fn);
60
+ return () => fullscreenWatchers.delete(fn);
61
+ },
62
+ };
63
+ const storage = { kind: 'memory', open: () => memory };
64
+ const wakeCalls = [];
65
+ let wakeHeld = false;
66
+ const wakeLock = {
67
+ get held() {
68
+ return wakeHeld;
69
+ },
70
+ calls: wakeCalls,
71
+ request() {
72
+ wakeCalls.push('request');
73
+ wakeHeld = true;
74
+ return Promise.resolve(true);
75
+ },
76
+ release() {
77
+ wakeCalls.push('release');
78
+ wakeHeld = false;
79
+ },
80
+ };
81
+ const opened = [];
82
+ const copied = [];
83
+ const openResult = opts.openResult ?? 'opened';
84
+ const copyResult = opts.copyResult ?? 'copied';
85
+ const clipboardOk = opts.clipboardWriteAllowed ?? true;
86
+ return {
87
+ kind: opts.kind ?? 'web',
88
+ embed: opts.embed ?? 'top',
89
+ requestFrame(cb) {
90
+ const handle = nextHandle++;
91
+ queue.set(handle, cb);
92
+ return handle;
93
+ },
94
+ cancelFrame(handle) {
95
+ queue.delete(handle);
96
+ running?.delete(handle);
97
+ },
98
+ openExternal(url, o) {
99
+ opened.push({ url, activation: o.activation });
100
+ return openResult;
101
+ },
102
+ copyText(text) {
103
+ copied.push(text);
104
+ return Promise.resolve(copyResult);
105
+ },
106
+ clipboardWriteAllowed: () => clipboardOk,
107
+ fullscreen,
108
+ storage,
109
+ lifecycle,
110
+ haptics,
111
+ safeArea,
112
+ memory,
113
+ setState(next) {
114
+ if (next === state)
115
+ return;
116
+ state = next;
117
+ for (const fn of [...stateWatchers])
118
+ fn(next);
119
+ },
120
+ setSafeArea(patch) {
121
+ let changed = false;
122
+ for (const side of ['top', 'right', 'bottom', 'left']) {
123
+ const v = patch[side];
124
+ if (v !== undefined && v !== insets[side]) {
125
+ insets[side] = v;
126
+ changed = true;
127
+ }
128
+ }
129
+ if (changed)
130
+ for (const fn of [...insetWatchers])
131
+ fn(insets);
132
+ },
133
+ hapticCalls,
134
+ fullscreenCalls,
135
+ opened,
136
+ copied,
137
+ wakeLock,
138
+ get pendingFrames() {
139
+ return queue.size;
140
+ },
141
+ get frameTime() {
142
+ return frameTime;
143
+ },
144
+ runFrame(timeMs) {
145
+ frameTime = timeMs ?? (firstFrame ? frameTime : frameTime + frameMs);
146
+ firstFrame = false;
147
+ const run = queue;
148
+ queue = new Map();
149
+ running = run;
150
+ let count = 0;
151
+ try {
152
+ for (const cb of run.values()) {
153
+ count++;
154
+ cb(frameTime);
155
+ }
156
+ }
157
+ finally {
158
+ running = null;
159
+ }
160
+ return count;
161
+ },
162
+ };
163
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Test-only fixed-step clock (ws/testing-kit). It is a `TickClock`, so it goes wherever
3
+ * `ctx.clock` goes: `createGestures({ clock })`, `createLook({ clock })`,
4
+ * `createInputPlayback(input, rec, { clock })`, and as `GameLoop`'s `now`. A test moves it one
5
+ * fixed step at a time, and N steps read exactly N × step, with no float drift.
6
+ */
7
+ import type { TickClock } from '../input/index.js';
8
+ /** A `TickClock` that moves in whole fixed steps, plus any loose `advance(ms)`. */
9
+ export interface FixedClock extends TickClock {
10
+ /** The step length, ms. */
11
+ readonly stepMs: number;
12
+ /**
13
+ * Whole steps taken so far: each `step`, and each `advance(ms)` of exactly `stepMs` (how
14
+ * `createGame`'s loop moves its clock once per fixed update, ws/core-8). Other advances do
15
+ * not count.
16
+ */
17
+ readonly ticks: number;
18
+ /** Moves the clock by `n` steps (default 1). Negative, fractional and non-finite `n` are ignored. */
19
+ step(n?: number): void;
20
+ }
21
+ /**
22
+ * A fixed-step clock starting at `start` ms (default 0) with steps of `stepMs` (default
23
+ * 1000 / 60, the engine's default fixed step). `now()` is `start + advanced + ticks × stepMs`.
24
+ */
25
+ export declare function createFixedClock(stepMs?: number, start?: number): FixedClock;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A fixed-step clock starting at `start` ms (default 0) with steps of `stepMs` (default
3
+ * 1000 / 60, the engine's default fixed step). `now()` is `start + advanced + ticks × stepMs`.
4
+ */
5
+ export function createFixedClock(stepMs = 1000 / 60, start = 0) {
6
+ if (!(stepMs > 0) || !Number.isFinite(stepMs)) {
7
+ throw new RangeError(`createFixedClock: stepMs must be a positive finite number, got ${stepMs}`);
8
+ }
9
+ const origin = Number.isFinite(start) ? start : 0;
10
+ let ticks = 0;
11
+ let loose = 0;
12
+ return {
13
+ stepMs,
14
+ get ticks() {
15
+ return ticks;
16
+ },
17
+ now: () => origin + loose + ticks * stepMs,
18
+ advance(ms) {
19
+ // The loop's own step is a whole step, so N fixed updates read exactly N × step.
20
+ if (ms === stepMs)
21
+ ticks++;
22
+ else if (ms > 0 && Number.isFinite(ms))
23
+ loose += ms;
24
+ },
25
+ step(n = 1) {
26
+ if (Number.isInteger(n) && n > 0)
27
+ ticks += n;
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,4 @@
1
+ export { createFakeDevice, type FakeBindGroup, type FakeBuffer, type FakeBufferCopy, type FakeDevice, FakeGpuValidationError, type FakePass, type FakeQuerySet, type FakeTexture, type FakeView, fakeCanvasContext, fakeRendererGpu, installGpuGlobals, type PassCommand, } from '../render/fake-gpu.js';
2
+ export { createFakeHost, type FakeHapticCall, type FakeHost, type FakeHostOptions, type FakeWakeLock, } from './fake-host.js';
3
+ export { createFixedClock, type FixedClock } from './fixed-clock.js';
4
+ export { createTestGame, type TestGame, type TestGameOptions, } from './test-game.js';
@@ -0,0 +1,5 @@
1
+ // @thunderclap-engine/engine/testing: helpers for a game's unit tests. Never imported by the engine.
2
+ export { createFakeDevice, FakeGpuValidationError, fakeCanvasContext, fakeRendererGpu, installGpuGlobals, } from '../render/fake-gpu.js';
3
+ export { createFakeHost, } from './fake-host.js';
4
+ export { createFixedClock } from './fixed-clock.js';
5
+ export { createTestGame, } from './test-game.js';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `createTestGame` (ws/core-8): `createGame` itself, headless, on the fake GPU, the fake host
3
+ * and the fixed clock. `step(n)` runs the game's own loop for `n` frames of one fixed step
4
+ * each, so a test presses an action, steps and asserts state with the engine's real frame
5
+ * order. Needs a DOM for the overlay (`// @vitest-environment happy-dom`), unless
6
+ * `overlay: false`, which is `createGame`'s no-DOM mode (ws/core-9) and runs in plain Node.
7
+ */
8
+ import type { CreateGameOptions, EngineCtx, FlushableSave } from '../core/game.js';
9
+ import type { Input } from '../input/index.js';
10
+ import { type FakeDevice } from '../render/fake-gpu.js';
11
+ import { type FakeHost } from './fake-host.js';
12
+ import { type FixedClock } from './fixed-clock.js';
13
+ /**
14
+ * `createTestGame` options: `createGame`'s, without `gpu`, `host` and `clock` (the kit makes
15
+ * them; pass `host` or `device` to bring your own) and with `root` optional.
16
+ */
17
+ export interface TestGameOptions<A extends string, S extends FlushableSave | null = null, P = any> extends Omit<CreateGameOptions<A, S, P>, 'gpu' | 'host' | 'clock' | 'root'> {
18
+ /** Where the canvas and overlay mount. Default a new `<div>` appended to `document.body`. */
19
+ root?: HTMLElement;
20
+ /** The host. Default `createFakeHost()`. */
21
+ host?: FakeHost;
22
+ /** The fake GPU device. Default `createFakeDevice()`. */
23
+ device?: FakeDevice;
24
+ /**
25
+ * The CSS px size the root reports (happy-dom lays nothing out), so the surface letterboxes
26
+ * and pointers map as on a page of that size. Default the design size: client px are design
27
+ * px. The root sits at the viewport's top-left.
28
+ */
29
+ viewport?: {
30
+ width: number;
31
+ height: number;
32
+ };
33
+ /**
34
+ * `false`: the test needs no DOM (ws/testing-2). The game runs in `createGame`'s no-DOM mode
35
+ * (`CreateGameOptions.overlay`, ws/core-9), in plain Node (Vitest's default `node`
36
+ * environment) or under happy-dom alike, and nothing touches `document` or `window`: no
37
+ * overlay (reading `ctx.ui` throws), no canvas (`ctx.surface` letterboxes a virtual
38
+ * container of `viewport`), no DOM listeners. `root` is not used. Drive the game through
39
+ * `input`. Default true: the overlay needs a DOM (`// @vitest-environment happy-dom`).
40
+ */
41
+ overlay?: boolean;
42
+ }
43
+ /** What `createTestGame` returns. */
44
+ export interface TestGame<A extends string, S extends FlushableSave | null = null, C = unknown, P = unknown> {
45
+ /** The game's ctx, exactly as `createGame` built it. */
46
+ readonly ctx: EngineCtx<A, S, C, P>;
47
+ /** `ctx.input`. */
48
+ readonly input: Input<A>;
49
+ /** The fake host (`ctx.host`): storage in memory, `setState`, recorded haptics. */
50
+ readonly host: FakeHost;
51
+ /** `ctx.clock`: N steps read exactly N × the fixed step. */
52
+ readonly clock: FixedClock;
53
+ /** The fake GPU device the renderer records into. */
54
+ readonly device: FakeDevice;
55
+ /**
56
+ * Run `n` frames (default 1) through the game's loop (`ctx.loop.tickOnce(step)`), each one
57
+ * fixed update (replay, clock, input, pad navigation, `hooks.update`, particles, scenes) and
58
+ * one render. With `sim: false` each frame updates once with the step as its delta. A halted
59
+ * loop (device lost) runs nothing.
60
+ */
61
+ step(n?: number): void;
62
+ /**
63
+ * Lose the GPU device (ws/testing-2): `device.loseDevice(reason, message)`, then wait until the
64
+ * engine has handled it. Afterwards the loop is halted for good (`ctx.loop.halted`, `step`
65
+ * runs nothing), `hooks.deviceLost` and every `ctx.onDeviceLost` listener ran with the info,
66
+ * audio is halted, captions are suspended and the save flushed, as on a page. Default reason
67
+ * `'unknown'`; `'destroyed'` is an explicit `device.destroy()`, which the engine ignores.
68
+ */
69
+ loseDevice(reason?: GPUDeviceLostReason, message?: string): Promise<void>;
70
+ }
71
+ /**
72
+ * `createGame` with the testing kit's fakes: a `FakeDevice` as the GPU, a `FakeHost` as the
73
+ * host (save storage in memory) and a `FixedClock` as `ctx.clock`, on the step of `opts.sim`
74
+ * (default 1/60 s). The loop is not started; `step(n)` drives it.
75
+ */
76
+ export declare function createTestGame<A extends string, S extends FlushableSave | null = null, C = unknown, P = unknown>(opts: TestGameOptions<A, S, P>): Promise<TestGame<A, S, C, P>>;