@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,43 @@
1
+ /**
2
+ * External links (D-036): URL validation, embed detection and the result types. Opening and
3
+ * copying are host services: `Host.openExternal` and `Host.copyText` (engine/host; the web
4
+ * implementation is host/web-links.ts). The widget is `externalLink` in engine/ui.
5
+ */
6
+ /**
7
+ * The URL if it is a plain absolute `https:` link (no credentials, no whitespace), else null.
8
+ * Callers hide the control on null, so a blank or bad constant never renders a dead link.
9
+ */
10
+ export function safeExternalUrl(url) {
11
+ const t = (url ?? '').trim();
12
+ if (!t || /\s/.test(t) || !/^https:\/\//i.test(t))
13
+ return null;
14
+ let u;
15
+ try {
16
+ u = new URL(t);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ if (u.protocol !== 'https:' || !u.hostname || u.username || u.password)
22
+ return null;
23
+ return t;
24
+ }
25
+ /** Detects (does not assume) whether the page is framed, and whether by another origin. */
26
+ export function embedContext(win = window) {
27
+ let framed;
28
+ try {
29
+ framed = win.self !== win.top;
30
+ }
31
+ catch {
32
+ return 'cross-origin-frame';
33
+ }
34
+ if (!framed)
35
+ return 'top';
36
+ try {
37
+ // Reading a cross-origin parent's location throws (or yields nothing).
38
+ return win.top?.location.href ? 'same-origin-frame' : 'cross-origin-frame';
39
+ }
40
+ catch {
41
+ return 'cross-origin-frame';
42
+ }
43
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Full-screen DOM message screens for fatal states (no WebGPU, device lost).
3
+ * Styled dark slate + gold. Text is generic; the game passes its own title.
4
+ */
5
+ export interface MessageScreenAction {
6
+ label: string;
7
+ onClick: () => void;
8
+ }
9
+ export interface MessageScreenOptions {
10
+ /** Small caps line above the heading (e.g. the game's name). */
11
+ eyebrow?: string;
12
+ heading: string;
13
+ /** Paragraphs of body text. */
14
+ body: string[];
15
+ /** A bulleted list after the body. */
16
+ list?: string[];
17
+ /** Paragraphs after the list, in small print. */
18
+ footer?: string[];
19
+ actions?: MessageScreenAction[];
20
+ }
21
+ /** Show a modal message screen over `root`. Returns a function that removes it. */
22
+ export declare function showMessageScreen(root: HTMLElement, opts: MessageScreenOptions): () => void;
23
+ export type PlatformFamily = 'ios' | 'android' | 'desktop';
24
+ /** The visitor's platform, for advice only (never to gate anything). */
25
+ export declare function platformFamily(ua?: string, touchPoints?: number): PlatformFamily;
26
+ /** "Your browser can't run this" screen: where it does run, the visitor's platform first. */
27
+ export declare function showUnsupportedScreen(root: HTMLElement, opts?: {
28
+ title?: string;
29
+ detail?: string;
30
+ }): () => void;
31
+ /** GPU device lost: offer a reload. */
32
+ export declare function showDeviceLostScreen(root: HTMLElement, opts?: {
33
+ title?: string;
34
+ detail?: string;
35
+ onReload?: () => void;
36
+ }): () => void;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Full-screen DOM message screens for fatal states (no WebGPU, device lost).
3
+ * Styled dark slate + gold. Text is generic; the game passes its own title.
4
+ */
5
+ const STYLE_ID = 'engine-message-screen-style';
6
+ const CSS = `
7
+ .engine-msg{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
8
+ background:radial-gradient(ellipse at 50% 40%,#1c2130 0%,#0b0d14 70%);z-index:1000;
9
+ font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif;color:#d9d3c3;padding:24px;box-sizing:border-box}
10
+ .engine-msg__card{max-width:560px;width:100%;padding:36px 40px;border:1px solid #a8863a;border-radius:10px;
11
+ background:linear-gradient(180deg,#191d29,#12151f);box-shadow:0 0 0 4px #0b0d14,0 0 0 5px #5a4a24,0 20px 60px #000a}
12
+ .engine-msg__eyebrow{letter-spacing:.25em;text-transform:uppercase;font-size:12px;color:#a8863a;margin:0 0 10px}
13
+ .engine-msg__heading{font-size:28px;line-height:1.2;margin:0 0 16px;color:#f0cf74;
14
+ text-shadow:0 2px 0 #4b2a6b,0 0 18px #f0cf7433}
15
+ .engine-msg__body{font-size:16px;line-height:1.55;margin:0 0 12px}
16
+ .engine-msg__actions{margin-top:22px;display:flex;gap:12px}
17
+ .engine-msg__btn{font:inherit;font-size:16px;padding:10px 22px;border-radius:6px;cursor:pointer;
18
+ color:#1a1408;background:linear-gradient(180deg,#f5d77f,#c99a2e);border:1px solid #7a5c1a}
19
+ .engine-msg__btn:focus-visible{outline:2px solid #f0cf74;outline-offset:3px}
20
+ .engine-msg__list{font-size:16px;line-height:1.5;margin:0 0 12px;padding-left:1.2em}
21
+ .engine-msg__list li{margin:0 0 6px}
22
+ .engine-msg__small{font-size:13px;color:#9a9484;overflow-wrap:anywhere}
23
+ @media (max-height:520px),(max-width:600px){
24
+ .engine-msg{align-items:flex-start;overflow-y:auto;padding:12px}
25
+ .engine-msg__card{padding:18px 20px;margin:auto}
26
+ .engine-msg__heading{font-size:22px;margin-bottom:10px}
27
+ .engine-msg__body,.engine-msg__list{font-size:15px;line-height:1.45;margin-bottom:8px}
28
+ .engine-msg__eyebrow{margin-bottom:6px}
29
+ }
30
+ `;
31
+ function ensureStyle() {
32
+ if (document.getElementById(STYLE_ID))
33
+ return;
34
+ const s = document.createElement('style');
35
+ s.id = STYLE_ID;
36
+ s.textContent = CSS;
37
+ document.head.appendChild(s);
38
+ }
39
+ /** Show a modal message screen over `root`. Returns a function that removes it. */
40
+ export function showMessageScreen(root, opts) {
41
+ ensureStyle();
42
+ const wrap = document.createElement('div');
43
+ wrap.className = 'engine-msg';
44
+ wrap.setAttribute('role', 'alertdialog');
45
+ wrap.setAttribute('aria-live', 'assertive');
46
+ const card = document.createElement('div');
47
+ card.className = 'engine-msg__card';
48
+ if (opts.eyebrow) {
49
+ const e = document.createElement('p');
50
+ e.className = 'engine-msg__eyebrow';
51
+ e.textContent = opts.eyebrow;
52
+ card.appendChild(e);
53
+ }
54
+ const h = document.createElement('h1');
55
+ h.className = 'engine-msg__heading';
56
+ h.textContent = opts.heading;
57
+ card.appendChild(h);
58
+ for (const para of opts.body) {
59
+ const p = document.createElement('p');
60
+ p.className = 'engine-msg__body';
61
+ p.textContent = para;
62
+ card.appendChild(p);
63
+ }
64
+ if (opts.list?.length) {
65
+ const ul = document.createElement('ul');
66
+ ul.className = 'engine-msg__list';
67
+ for (const item of opts.list) {
68
+ const li = document.createElement('li');
69
+ li.textContent = item;
70
+ ul.appendChild(li);
71
+ }
72
+ card.appendChild(ul);
73
+ }
74
+ for (const para of opts.footer ?? []) {
75
+ const p = document.createElement('p');
76
+ p.className = 'engine-msg__body engine-msg__small';
77
+ p.textContent = para;
78
+ card.appendChild(p);
79
+ }
80
+ if (opts.actions?.length) {
81
+ const row = document.createElement('div');
82
+ row.className = 'engine-msg__actions';
83
+ for (const a of opts.actions) {
84
+ const b = document.createElement('button');
85
+ b.className = 'engine-msg__btn';
86
+ b.type = 'button';
87
+ b.textContent = a.label;
88
+ b.addEventListener('click', a.onClick);
89
+ row.appendChild(b);
90
+ }
91
+ card.appendChild(row);
92
+ }
93
+ wrap.appendChild(card);
94
+ root.appendChild(wrap);
95
+ wrap.querySelector('button')?.focus();
96
+ return () => wrap.remove();
97
+ }
98
+ /** The visitor's platform, for advice only (never to gate anything). */
99
+ export function platformFamily(ua = typeof navigator !== 'undefined' ? navigator.userAgent : '', touchPoints = typeof navigator !== 'undefined' ? navigator.maxTouchPoints : 0) {
100
+ // iPadOS asks for desktop sites and reports itself as a Mac with touch.
101
+ if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && touchPoints > 1))
102
+ return 'ios';
103
+ if (/Android/.test(ua))
104
+ return 'android';
105
+ return 'desktop';
106
+ }
107
+ /** Where WebGPU runs (docs/design/mobile.md has the matrix and its sources). */
108
+ const WHERE = {
109
+ ios: 'iPhone and iPad: Safari on iOS 26 or newer. Opened from inside another app? Open the link in Safari.',
110
+ android: 'Android: the latest Chrome on Android 12 or newer. Firefox and in-app browsers cannot run it yet, so open the link in Chrome.',
111
+ desktop: 'Computer: the latest Chrome or Edge, or Safari 26 or newer on a Mac. On Linux, Chrome may need hardware acceleration turned on.',
112
+ };
113
+ /** "Your browser can't run this" screen: where it does run, the visitor's platform first. */
114
+ export function showUnsupportedScreen(root, opts = {}) {
115
+ const mine = platformFamily();
116
+ const others = ['ios', 'android', 'desktop'].filter((p) => p !== mine);
117
+ return showMessageScreen(root, {
118
+ eyebrow: opts.title,
119
+ heading: 'WebGPU is not available',
120
+ body: [
121
+ 'This game draws with WebGPU, and this browser (or this device) did not give us access to it. It runs on:',
122
+ ],
123
+ list: [WHERE[mine], ...others.map((p) => WHERE[p])],
124
+ footer: opts.detail ? [`Details: ${opts.detail}`] : [],
125
+ });
126
+ }
127
+ /** GPU device lost: offer a reload. */
128
+ export function showDeviceLostScreen(root, opts = {}) {
129
+ const body = ['The graphics device stopped responding. Your progress is saved locally.'];
130
+ if (opts.detail)
131
+ body.push(`Details: ${opts.detail}`);
132
+ return showMessageScreen(root, {
133
+ eyebrow: opts.title,
134
+ heading: 'The lights went out',
135
+ body,
136
+ actions: [{ label: 'Reload', onClick: opts.onReload ?? (() => location.reload()) }],
137
+ });
138
+ }
@@ -0,0 +1,72 @@
1
+ import { type BackbufferSize, type Letterbox, type Vec2 } from './letterbox.js';
2
+ export interface SurfaceOptions {
3
+ /** Logical design width. Default 1920. */
4
+ logicalWidth?: number;
5
+ /** Logical design height. Default 1080. */
6
+ logicalHeight?: number;
7
+ /** Cap on devicePixelRatio. Default 2. */
8
+ maxDpr?: number;
9
+ /** Cap on drawing-buffer pixel count. Default 2560×1440. */
10
+ maxPixels?: number;
11
+ /** CSS colour of the letterbox bars (the container background). Default near-black ink. */
12
+ barColor?: string;
13
+ }
14
+ export interface SurfaceSize {
15
+ /** Letterboxed rect in CSS px relative to the container. */
16
+ rect: Letterbox;
17
+ /** Drawing-buffer size in physical pixels. */
18
+ backbuffer: BackbufferSize;
19
+ }
20
+ /**
21
+ * What a surface is to the engine (ws/platform-surface): a letterboxed logical design space, the
22
+ * drawing-buffer size, and the resize events and coordinate maps over them. `EngineCtx.surface`,
23
+ * `BootResult.surface` and `mountSurface` are typed with it.
24
+ *
25
+ * `new Surface(root, opts)` (the DOM canvas below) implements it, and so can anything else with
26
+ * these members, with no cast: core's no-DOM surface (`createGame({ overlay: false })`) and a
27
+ * game's own surface. A surface with no DOM throws from `root` and `canvas`.
28
+ */
29
+ export interface Surface {
30
+ /** The container the canvas letterboxes in. */
31
+ readonly root: HTMLElement;
32
+ /** The canvas the renderer draws to. */
33
+ readonly canvas: HTMLCanvasElement;
34
+ /** Logical design width. */
35
+ readonly logicalWidth: number;
36
+ /** Logical design height. */
37
+ readonly logicalHeight: number;
38
+ /** Letterboxed rect, CSS px, relative to `root`. Updated in place on resize. */
39
+ rect: Letterbox;
40
+ /** The plain contain fit for the whole container (what `rect` is unless a frame is set). */
41
+ fitRect: Letterbox;
42
+ /** Drawing-buffer size in device px, and the pixel ratio used. */
43
+ backbuffer: BackbufferSize;
44
+ /** Subscribe to size changes. Called immediately with the current size. Returns unsubscribe. */
45
+ onResize(fn: (size: SurfaceSize) => void): () => void;
46
+ /** Viewport coordinates (e.g. `PointerEvent.clientX/Y`) to logical design space. */
47
+ clientToLogical(clientX: number, clientY: number, out?: Vec2): Vec2;
48
+ /** Logical coordinates to viewport coordinates (CSS px, same space as clientX/Y). */
49
+ logicalToClient(x: number, y: number, out?: Vec2): Vec2;
50
+ /** True if a logical point is inside the design space. */
51
+ containsLogical(p: Vec2): boolean;
52
+ /** Letterbox inside a part of the container (CSS px, relative to `root`); `null` is all of it. */
53
+ setFrame(frame: {
54
+ x: number;
55
+ y: number;
56
+ width: number;
57
+ height: number;
58
+ } | null): void;
59
+ /** Force a re-layout (e.g. after fullscreen toggles). */
60
+ layout(): void;
61
+ /** Stop listening and release what the surface owns. */
62
+ destroy(): void;
63
+ }
64
+ /**
65
+ * The DOM surface's constructor: `new Surface(root, opts)` mounts a letterboxed canvas in
66
+ * `root`. A class may extend it.
67
+ */
68
+ export declare const Surface: {
69
+ new (root: HTMLElement, opts?: SurfaceOptions): Surface;
70
+ readonly prototype: Surface;
71
+ };
72
+ export declare function mountSurface(root: HTMLElement, opts?: SurfaceOptions): Surface;
@@ -0,0 +1,157 @@
1
+ import { clientToLogical, computeBackbuffer, computeLetterbox, logicalToClient, } from './letterbox.js';
2
+ /**
3
+ * A canvas mounted in a container, letterboxed to a fixed logical aspect, DPR-aware.
4
+ * Owns the canvas element and keeps its CSS box + drawing buffer in sync with the container.
5
+ *
6
+ * Other systems (input, DOM overlay) hook in via `rect`, `onResize` and `clientToLogical`.
7
+ * Exported as the value `Surface`; its private state stays off the `Surface` type.
8
+ */
9
+ class DomSurface {
10
+ root;
11
+ canvas;
12
+ logicalWidth;
13
+ logicalHeight;
14
+ /** Letterboxed rect, CSS px, relative to `root`. Updated in place on resize. */
15
+ rect = { x: 0, y: 0, width: 0, height: 0, scale: 0 };
16
+ /** The plain contain fit for the whole container (what `rect` is unless a frame is set). */
17
+ fitRect = { x: 0, y: 0, width: 0, height: 0, scale: 0 };
18
+ backbuffer = { width: 1, height: 1, pixelRatio: 1 };
19
+ frame = null;
20
+ maxDpr;
21
+ maxPixels;
22
+ listeners = new Set();
23
+ ro;
24
+ dprQuery = null;
25
+ onDprChange = () => {
26
+ this.watchDpr();
27
+ this.layout();
28
+ };
29
+ onWindowResize = () => this.layout();
30
+ constructor(root, opts = {}) {
31
+ this.root = root;
32
+ this.logicalWidth = opts.logicalWidth ?? 1920;
33
+ this.logicalHeight = opts.logicalHeight ?? 1080;
34
+ this.maxDpr = opts.maxDpr ?? 2;
35
+ this.maxPixels = opts.maxPixels ?? 2560 * 1440;
36
+ if (getComputedStyle(root).position === 'static')
37
+ root.style.position = 'relative';
38
+ root.style.overflow = 'hidden';
39
+ if (opts.barColor)
40
+ root.style.background = opts.barColor;
41
+ const canvas = document.createElement('canvas');
42
+ canvas.style.position = 'absolute';
43
+ canvas.style.display = 'block';
44
+ canvas.style.touchAction = 'none';
45
+ canvas.tabIndex = 0; // focusable so key handling can be scoped to the game (itch iframe)
46
+ canvas.style.outline = 'none';
47
+ canvas.setAttribute('aria-label', 'Game view');
48
+ root.appendChild(canvas);
49
+ this.canvas = canvas;
50
+ this.ro = new ResizeObserver(() => this.layout());
51
+ this.ro.observe(root);
52
+ window.addEventListener('resize', this.onWindowResize);
53
+ this.watchDpr();
54
+ this.layout();
55
+ }
56
+ /** Subscribe to size changes. Called immediately with the current size. Returns unsubscribe. */
57
+ onResize(fn) {
58
+ this.listeners.add(fn);
59
+ fn({ rect: this.rect, backbuffer: this.backbuffer });
60
+ return () => this.listeners.delete(fn);
61
+ }
62
+ /**
63
+ * Convert viewport coordinates (e.g. `PointerEvent.clientX/Y`) to logical design space.
64
+ * Pass `out` to avoid allocation in hot paths.
65
+ */
66
+ clientToLogical(clientX, clientY, out) {
67
+ const r = this.root.getBoundingClientRect();
68
+ return clientToLogical(this.rect, this.logicalWidth, this.logicalHeight, clientX - r.left, clientY - r.top, out);
69
+ }
70
+ /** Convert logical coordinates to viewport coordinates (CSS px, same space as clientX/Y). */
71
+ logicalToClient(x, y, out) {
72
+ const r = this.root.getBoundingClientRect();
73
+ const p = logicalToClient(this.rect, this.logicalWidth, this.logicalHeight, x, y, out);
74
+ p.x += r.left;
75
+ p.y += r.top;
76
+ return p;
77
+ }
78
+ /** True if a logical point is inside the design space. */
79
+ containsLogical(p) {
80
+ return p.x >= 0 && p.y >= 0 && p.x <= this.logicalWidth && p.y <= this.logicalHeight;
81
+ }
82
+ /**
83
+ * Keep the canvas inside a part of the container (CSS px, relative to `root`), e.g. the
84
+ * world's region of a layout (`Layout.freeRect`) when UI regions take the rest of the screen.
85
+ * The letterbox is computed inside it; `null` (the default) uses the whole
86
+ * container. `fitRect` is always the whole container's fit, so device-mode decisions do not
87
+ * depend on the frame. Lays out again at once if the result changes.
88
+ */
89
+ setFrame(frame) {
90
+ const f = this.frame;
91
+ if (f === frame ||
92
+ (f &&
93
+ frame &&
94
+ f.x === frame.x &&
95
+ f.y === frame.y &&
96
+ f.width === frame.width &&
97
+ f.height === frame.height))
98
+ return;
99
+ this.frame = frame
100
+ ? { x: frame.x, y: frame.y, width: frame.width, height: frame.height }
101
+ : null;
102
+ this.layout();
103
+ }
104
+ /** Force a re-layout (e.g. after fullscreen toggles). */
105
+ layout() {
106
+ const cw = this.root.clientWidth;
107
+ const ch = this.root.clientHeight;
108
+ this.fitRect = computeLetterbox(cw, ch, this.logicalWidth, this.logicalHeight);
109
+ const fr = this.frame;
110
+ const w = fr ? Math.max(1, Math.min(fr.width, cw)) : cw;
111
+ const h = fr ? Math.max(1, Math.min(fr.height, ch)) : ch;
112
+ let rect = fr ? computeLetterbox(w, h, this.logicalWidth, this.logicalHeight) : this.fitRect;
113
+ if (fr)
114
+ rect = { ...rect, x: rect.x + fr.x, y: rect.y + fr.y };
115
+ const bb = computeBackbuffer(rect.width, rect.height, window.devicePixelRatio, this.maxDpr, this.maxPixels);
116
+ const same = rect.x === this.rect.x &&
117
+ rect.y === this.rect.y &&
118
+ rect.width === this.rect.width &&
119
+ rect.height === this.rect.height &&
120
+ bb.width === this.backbuffer.width &&
121
+ bb.height === this.backbuffer.height;
122
+ if (same)
123
+ return;
124
+ this.rect = rect;
125
+ this.backbuffer = bb;
126
+ const s = this.canvas.style;
127
+ s.left = `${rect.x}px`;
128
+ s.top = `${rect.y}px`;
129
+ s.width = `${rect.width}px`;
130
+ s.height = `${rect.height}px`;
131
+ this.canvas.width = bb.width;
132
+ this.canvas.height = bb.height;
133
+ const size = { rect, backbuffer: bb };
134
+ for (const fn of this.listeners)
135
+ fn(size);
136
+ }
137
+ destroy() {
138
+ this.ro.disconnect();
139
+ window.removeEventListener('resize', this.onWindowResize);
140
+ this.dprQuery?.removeEventListener('change', this.onDprChange);
141
+ this.listeners.clear();
142
+ this.canvas.remove();
143
+ }
144
+ watchDpr() {
145
+ this.dprQuery?.removeEventListener('change', this.onDprChange);
146
+ this.dprQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
147
+ this.dprQuery.addEventListener('change', this.onDprChange);
148
+ }
149
+ }
150
+ /**
151
+ * The DOM surface's constructor: `new Surface(root, opts)` mounts a letterboxed canvas in
152
+ * `root`. A class may extend it.
153
+ */
154
+ export const Surface = DomSurface;
155
+ export function mountSurface(root, opts) {
156
+ return new Surface(root, opts);
157
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * A fake WebXR stack for unit tests (ws/xr): `XRSystem`, `XRSession`, `XRFrame`, input sources
3
+ * and the WebXR/WebGPU binding, stepped by hand. `installFakeXr(device)` puts a `navigator.xr`
4
+ * and a `globalThis.XRGPUBinding` in place over a fake GPU device (`createFakeDevice`), so
5
+ * `createGame`'s `ctx.xr.enter()`, `XrSurface` and `XrPointerSource` run end to end in Node;
6
+ * the returned `uninstall` restores the globals. Not exported from any module index.
7
+ *
8
+ * Geometry: two eyes at ±`ipd / 2` on x, looking down -z with a 90° projection, side by side in
9
+ * one `textureType: 'texture'` layer texture (`2 * eyeWidth × eyeHeight`); a `texture-array`
10
+ * layer gives each eye its own array layer. Input sources carry a position and point down -z.
11
+ */
12
+ /// <reference path="./xr-types.d.ts" preserve="true" />
13
+ export interface FakeXrOptions {
14
+ /** Per-eye texture size. Default 64 × 32. */
15
+ eyeWidth?: number;
16
+ eyeHeight?: number;
17
+ /** Interpupillary distance (metres). Default 0.064. */
18
+ ipd?: number;
19
+ /** What `isSessionSupported('immersive-vr')` answers. Default true. */
20
+ supported?: boolean;
21
+ /** A new layer texture every frame (a real compositor rotates them). Default false. */
22
+ rotateTextures?: boolean;
23
+ }
24
+ /** A rigid transform that is a pure translation, with its inverse. */
25
+ export declare function fakeTransform(x: number, y: number, z: number): XRRigidTransform;
26
+ export declare class FakeXrSpace implements XRSpace {
27
+ readonly name: string;
28
+ constructor(name: string);
29
+ }
30
+ export declare class FakeXrReferenceSpace extends FakeXrSpace implements XRReferenceSpace {
31
+ getOffsetReferenceSpace(): XRReferenceSpace;
32
+ }
33
+ export declare class FakeXrJointSpace extends FakeXrSpace implements XRJointSpace {
34
+ readonly jointName: XRHandJoint;
35
+ constructor(jointName: XRHandJoint);
36
+ }
37
+ /** A hand whose joint positions a test sets (`joints.set('thumb-tip', [x, y, z])`). */
38
+ export declare class FakeXrHand implements XRHand {
39
+ readonly joints: Map<XRHandJoint, [number, number, number]>;
40
+ private readonly spaces;
41
+ get size(): number;
42
+ get(joint: XRHandJoint): XRJointSpace | undefined;
43
+ }
44
+ export declare class FakeXrInputSource implements XRInputSource {
45
+ readonly handedness: XRHandedness;
46
+ readonly targetRayMode: XRTargetRayMode;
47
+ readonly targetRaySpace: FakeXrSpace;
48
+ readonly gripSpace: XRSpace | null;
49
+ readonly profiles: readonly string[];
50
+ /** Where the target ray starts, in the reference space; it points down -z. */
51
+ position: [number, number, number];
52
+ /** `buttons[0]` trigger, `buttons[1]` squeeze. */
53
+ readonly buttons: {
54
+ pressed: boolean;
55
+ touched: boolean;
56
+ value: number;
57
+ }[];
58
+ /** Null for a source without a gamepad (events only). */
59
+ gamepad: Gamepad | null;
60
+ hand: FakeXrHand | null;
61
+ constructor(handedness: XRHandedness, opts?: {
62
+ gamepad?: boolean;
63
+ hand?: boolean;
64
+ });
65
+ }
66
+ export declare class FakeXrFrame implements XRFrame {
67
+ readonly session: FakeXrSession;
68
+ readonly predictedDisplayTime: number;
69
+ constructor(session: FakeXrSession, predictedDisplayTime: number);
70
+ getViewerPose(referenceSpace: XRReferenceSpace): XRViewerPose | undefined;
71
+ getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
72
+ getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose | undefined;
73
+ }
74
+ export declare class FakeXrSession extends EventTarget implements XRSession {
75
+ readonly mode: XRSessionMode;
76
+ readonly init: XRSessionInit | undefined;
77
+ readonly opts: Required<FakeXrOptions>;
78
+ readonly inputSources: FakeXrInputSource[];
79
+ readonly referenceSpace: FakeXrReferenceSpace;
80
+ renderState: XRRenderState;
81
+ visibilityState: 'visible' | 'visible-blurred' | 'hidden';
82
+ headPosition: [number, number, number];
83
+ /** False makes `getViewerPose` return nothing (tracking lost). */
84
+ tracking: boolean;
85
+ ended: boolean;
86
+ /** Every `requestReferenceSpace` type asked for. */
87
+ readonly referenceSpaceRequests: XRReferenceSpaceType[];
88
+ readonly renderStateUpdates: XRRenderStateInit[];
89
+ private readonly pending;
90
+ private nextHandle;
91
+ private frameTime;
92
+ constructor(mode: XRSessionMode, init: XRSessionInit | undefined, opts: Required<FakeXrOptions>);
93
+ /** The clock of the frame running now (the last `step`), ms. */
94
+ get now(): number;
95
+ /** Callbacks waiting for the next frame. */
96
+ get pendingFrames(): number;
97
+ requestReferenceSpace(type: XRReferenceSpaceType): Promise<XRReferenceSpace>;
98
+ updateRenderState(state?: XRRenderStateInit): void;
99
+ requestAnimationFrame(callback: XRFrameRequestCallback): number;
100
+ cancelAnimationFrame(handle: number): void;
101
+ /** Run the pending frame callbacks with a new `XRFrame`, `dtMs` after the last one. */
102
+ step(dtMs?: number): FakeXrFrame;
103
+ /** Add an input source (fires `inputsourceschange`). */
104
+ addInputSource(src: FakeXrInputSource): void;
105
+ removeInputSource(src: FakeXrInputSource): void;
106
+ /** Fire `selectstart` / `selectend` (or `squeeze…`) for a source, as a headset would. */
107
+ fire(kind: 'select' | 'squeeze', src: FakeXrInputSource, down: boolean): void;
108
+ end(): Promise<void>;
109
+ }
110
+ export declare class FakeXrSystem implements XRSystem {
111
+ readonly opts: Required<FakeXrOptions>;
112
+ readonly sessions: FakeXrSession[];
113
+ constructor(opts: Required<FakeXrOptions>);
114
+ isSessionSupported(mode: XRSessionMode): Promise<boolean>;
115
+ requestSession(mode: XRSessionMode, init?: XRSessionInit): Promise<XRSession>;
116
+ /** The session requested last. */
117
+ get session(): FakeXrSession;
118
+ }
119
+ export declare class FakeXrProjectionLayer implements XRProjectionLayer {
120
+ readonly binding: FakeXRGPUBinding;
121
+ readonly init: XRGPUProjectionLayerInit;
122
+ readonly layout: 'stereo';
123
+ readonly mipLevels = 1;
124
+ needsRedraw: boolean;
125
+ readonly ignoreDepthValues = false;
126
+ fixedFoveation: number | null;
127
+ destroyed: boolean;
128
+ /** Textures handed out so far (one per frame when rotating). */
129
+ readonly colorTextures: GPUTexture[];
130
+ readonly depthTextures: GPUTexture[];
131
+ private color;
132
+ private depth;
133
+ private lastFrameTime;
134
+ constructor(binding: FakeXRGPUBinding, init: XRGPUProjectionLayerInit);
135
+ get textureType(): 'texture' | 'texture-array';
136
+ get textureWidth(): number;
137
+ get textureHeight(): number;
138
+ get textureArrayLength(): number;
139
+ /** This frame's textures, made on first use and, when rotating, anew each frame. */
140
+ textures(frameTime: number): {
141
+ color: GPUTexture;
142
+ depth: GPUTexture | null;
143
+ };
144
+ destroy(): void;
145
+ }
146
+ export declare class FakeXRGPUBinding implements XRGPUBinding {
147
+ readonly session: FakeXrSession;
148
+ readonly device: GPUDevice;
149
+ readonly nativeProjectionScaleFactor = 1;
150
+ readonly layers: FakeXrProjectionLayer[];
151
+ constructor(session: FakeXrSession, device: GPUDevice);
152
+ createProjectionLayer(init?: XRGPUProjectionLayerInit): XRProjectionLayer;
153
+ getViewSubImage(layer: XRProjectionLayer, view: XRView): XRGPUSubImage;
154
+ getPreferredColorFormat(): GPUTextureFormat;
155
+ }
156
+ export interface FakeXr {
157
+ readonly system: FakeXrSystem;
158
+ /** Every binding `new XRGPUBinding(session, device)` made. */
159
+ readonly bindings: FakeXRGPUBinding[];
160
+ /** Advance the session's frame clock and run its pending frame callbacks. */
161
+ step(dtMs?: number): FakeXrFrame;
162
+ uninstall(): void;
163
+ }
164
+ /**
165
+ * Install `navigator.xr` and `XRGPUBinding` fakes. `supported: false` keeps `navigator.xr` but
166
+ * answers no to `immersive-vr`; to test a browser with no WebXR at all, install nothing.
167
+ */
168
+ export declare function installFakeXr(opts?: FakeXrOptions): FakeXr;