@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,856 @@
1
+ /**
2
+ * Layout regions (D-041 Phase 2): a `LayoutSpec` names regions per form factor, the engine lays
3
+ * them out with one CSS grid inside the safe area of a CSS-px overlay, and widgets register once
4
+ * with a region per form factor. The layout moves them when the form factor changes (a phone
5
+ * turned upright) and reports `freeRect`, the space the UI leaves, for the scene's camera.
6
+ *
7
+ * Everything is in CSS px relative to the overlay root. The geometry is pure (`resolveLayout`)
8
+ * and the DOM grid uses the same resolved track sizes, so the rects the layout reports are the
9
+ * rects the browser draws.
10
+ */
11
+ import { h } from './dom.js';
12
+ export const FORM_FACTORS = [
13
+ 'phone-landscape',
14
+ 'phone-portrait',
15
+ 'tablet',
16
+ 'desktop',
17
+ ];
18
+ export const REGIONS = [
19
+ 'top',
20
+ 'left',
21
+ 'right',
22
+ 'bottom',
23
+ 'cardLane',
24
+ 'captionLane',
25
+ 'modal',
26
+ 'fill',
27
+ ];
28
+ export const NO_INSETS = { top: 0, right: 0, bottom: 0, left: 0 };
29
+ /** `size` in `ff`: the number itself, else the form's entry, `phone` on a phone, `default`. Pure. */
30
+ export function formSize(size, ff) {
31
+ if (typeof size === 'number')
32
+ return size;
33
+ const own = size[ff];
34
+ if (own !== undefined)
35
+ return own;
36
+ if (ff.startsWith('phone') && size.phone !== undefined)
37
+ return size.phone;
38
+ return size.default;
39
+ }
40
+ /** Largest short side (CSS px) a phone has; tablets start around 744. */
41
+ export const PHONE_MAX_SHORT_SIDE = 600;
42
+ /** A window whose short side is under this is phone-sized, whatever the pointer. */
43
+ export const PHONE_MAX_VIEW_SHORT = 480;
44
+ /**
45
+ * Default form-factor choice. A phone is a coarse-pointer screen whose short side is at most
46
+ * `PHONE_MAX_SHORT_SIDE` (the `deviceTraits().handheld` rule), or any window whose short side is
47
+ * under `PHONE_MAX_VIEW_SHORT` (a tiny desktop window gets phone chrome rather than clipped
48
+ * desktop chrome). Phones split by orientation. Other coarse pointers are tablets; the rest are
49
+ * desktops.
50
+ */
51
+ export function classifyFormFactor(i) {
52
+ const viewShort = Math.min(i.width, i.height);
53
+ const handheld = i.coarsePointer && i.shortSide > 0 && i.shortSide <= PHONE_MAX_SHORT_SIDE;
54
+ if (handheld || (viewShort > 0 && viewShort < PHONE_MAX_VIEW_SHORT)) {
55
+ return i.height > i.width ? 'phone-portrait' : 'phone-landscape';
56
+ }
57
+ return i.coarsePointer ? 'tablet' : 'desktop';
58
+ }
59
+ const WIDE_AREAS = [
60
+ 'top top top top',
61
+ 'left fill cardLane right',
62
+ 'left captionLane cardLane right',
63
+ 'bottom bottom bottom bottom',
64
+ ];
65
+ /**
66
+ * The default regions. They are deliberately sparse: empty tracks collapse, so a phone shows
67
+ * only the chrome a scene registers, and the world gets the rest.
68
+ *
69
+ * - **desktop / tablet**: full-width top and bottom bars; left and right rails; a card lane
70
+ * beside the world on the right; the caption lane under the world.
71
+ * - **phone-landscape**: height is the scarce axis, so left and right are full-height rails
72
+ * (56 px: a 44 px tap target plus padding) and top/bottom sit between them; the card lane is
73
+ * beside the world; the caption lane is a short strip under it.
74
+ * - **phone-portrait**: width is scarce, so the rails are slim and the card lane and caption lane
75
+ * are full-width strips stacked under the world, above the bottom bar.
76
+ */
77
+ export const defaultForms = {
78
+ desktop: {
79
+ columns: [
80
+ { frac: 0.16, min: 180, max: 300 },
81
+ 'fill',
82
+ { frac: 0.18, min: 200, max: 320 },
83
+ { frac: 0.16, min: 180, max: 300 },
84
+ ],
85
+ rows: [64, 'fill', { frac: 0.16, min: 96, max: 168 }, 72],
86
+ areas: WIDE_AREAS,
87
+ tapMin: 32,
88
+ textMin: 0,
89
+ pad: 12,
90
+ modalMargin: 24,
91
+ units: 'frame',
92
+ },
93
+ tablet: {
94
+ columns: [
95
+ { frac: 0.16, min: 160, max: 280 },
96
+ 'fill',
97
+ { frac: 0.2, min: 200, max: 300 },
98
+ { frac: 0.16, min: 160, max: 280 },
99
+ ],
100
+ rows: [68, 'fill', { frac: 0.16, min: 104, max: 168 }, 76],
101
+ areas: WIDE_AREAS,
102
+ tapMin: 44,
103
+ textMin: 0,
104
+ pad: 12,
105
+ modalMargin: 20,
106
+ units: 'frame',
107
+ },
108
+ 'phone-landscape': {
109
+ columns: [56, 'fill', { frac: 0.24, min: 140, max: 200 }, 56],
110
+ rows: [52, 'fill', { frac: 0.24, min: 64, max: 96 }, 52],
111
+ areas: [
112
+ 'left top top right',
113
+ 'left fill cardLane right',
114
+ 'left captionLane cardLane right',
115
+ 'left bottom bottom right',
116
+ ],
117
+ tapMin: 44,
118
+ textMin: 12,
119
+ pad: 4,
120
+ modalMargin: 8,
121
+ units: 'css',
122
+ },
123
+ 'phone-portrait': {
124
+ columns: [56, 'fill', 56],
125
+ rows: [56, 'fill', { frac: 0.16, min: 96, max: 140 }, { frac: 0.14, min: 80, max: 120 }, 60],
126
+ areas: [
127
+ 'top top top',
128
+ 'left fill right',
129
+ 'cardLane cardLane cardLane',
130
+ 'captionLane captionLane captionLane',
131
+ 'bottom bottom bottom',
132
+ ],
133
+ tapMin: 44,
134
+ textMin: 12,
135
+ pad: 4,
136
+ modalMargin: 8,
137
+ units: 'css',
138
+ },
139
+ };
140
+ /** The spec for `ff` with `spec.forms[ff]` merged over the default. */
141
+ export function formSpec(ff, spec = {}) {
142
+ const form = { ...defaultForms[ff], ...spec.forms?.[ff] };
143
+ if (spec.bars)
144
+ form.bars = { ...spec.bars, ...form.bars };
145
+ return form;
146
+ }
147
+ /** Parses `areas` into a cell grid; throws on ragged rows, unknown names or non-rectangles. */
148
+ export function parseAreas(areas) {
149
+ const cells = areas.map((r) => r.trim().split(/\s+/));
150
+ const w = cells[0]?.length ?? 0;
151
+ if (!w || cells.some((r) => r.length !== w))
152
+ throw new Error('layout: ragged areas');
153
+ const boxes = new Map();
154
+ cells.forEach((row, r) => {
155
+ row.forEach((name, c) => {
156
+ if (name === '.')
157
+ return;
158
+ if (!REGIONS.includes(name) || name === 'modal') {
159
+ throw new Error(`layout: unknown grid area "${name}"`);
160
+ }
161
+ const b = boxes.get(name);
162
+ if (!b)
163
+ boxes.set(name, { r0: r, r1: r, c0: c, c1: c, n: 1 });
164
+ else {
165
+ b.r0 = Math.min(b.r0, r);
166
+ b.r1 = Math.max(b.r1, r);
167
+ b.c0 = Math.min(b.c0, c);
168
+ b.c1 = Math.max(b.c1, c);
169
+ b.n++;
170
+ }
171
+ });
172
+ });
173
+ for (const [name, b] of boxes) {
174
+ if ((b.r1 - b.r0 + 1) * (b.c1 - b.c0 + 1) !== b.n) {
175
+ throw new Error(`layout: area "${name}" is not a rectangle`);
176
+ }
177
+ }
178
+ return cells;
179
+ }
180
+ /**
181
+ * Resolves track sizes on one axis. `collapsed[i]` forces track i to 0. Fixed and fractional
182
+ * tracks take their size; when they overflow, fractional tracks shrink toward `min`, then the
183
+ * fill tracks get what is left (at least 0), shared equally.
184
+ */
185
+ export function resolveTracks(tracks, length, collapsed = []) {
186
+ const size = tracks.map((t, i) => {
187
+ if (collapsed[i] || t === 'fill')
188
+ return 0;
189
+ if (typeof t === 'number')
190
+ return Math.max(0, t);
191
+ const v = t.frac * length;
192
+ return Math.max(t.min ?? 0, Math.min(t.max ?? Number.POSITIVE_INFINITY, v));
193
+ });
194
+ let over = size.reduce((a, b) => a + b, 0) - length;
195
+ if (over > 0) {
196
+ const slack = tracks.map((t, i) => typeof t === 'object' && !collapsed[i] ? Math.max(0, size[i] - (t.min ?? 0)) : 0);
197
+ const total = slack.reduce((a, b) => a + b, 0);
198
+ if (total > 0) {
199
+ const k = Math.min(1, over / total);
200
+ for (let i = 0; i < size.length; i++)
201
+ size[i] = size[i] - slack[i] * k;
202
+ over -= Math.min(over, total);
203
+ }
204
+ }
205
+ const fills = tracks.map((t, i) => t === 'fill' && !collapsed[i]);
206
+ const nFill = fills.filter(Boolean).length;
207
+ const left = Math.max(0, length - size.reduce((a, b) => a + b, 0));
208
+ return size.map((s, i) => Math.round((fills[i] ? left / nFill : s) * 100) / 100);
209
+ }
210
+ /** The insets a rect at `rect` inside a `viewport` of the given size actually touches. */
211
+ export function effectiveInsets(rect, viewport, insets) {
212
+ return {
213
+ top: Math.max(0, insets.top - rect.y),
214
+ left: Math.max(0, insets.left - rect.x),
215
+ bottom: Math.max(0, insets.bottom - (viewport.height - (rect.y + rect.height))),
216
+ right: Math.max(0, insets.right - (viewport.width - (rect.x + rect.width))),
217
+ };
218
+ }
219
+ const ROW_BARS = ['top', 'bottom'];
220
+ const COL_BARS = ['left', 'right'];
221
+ const BAR_NAMES = ['top', 'bottom', 'left', 'right'];
222
+ /**
223
+ * The outer track of `bar` in `cells`: the first row for `top`, the last for `bottom`, the first
224
+ * column for `left`, the last for `right`; -1 when the bar is not in the grid.
225
+ */
226
+ function outerTrack(cells, bar) {
227
+ const rows = bar === 'top' || bar === 'bottom';
228
+ const n = rows ? cells.length : (cells[0]?.length ?? 0);
229
+ let t = -1;
230
+ for (let i = 0; i < n; i++) {
231
+ const has = rows ? cells[i].includes(bar) : cells.some((row) => row[i] === bar);
232
+ if (has && (t < 0 || bar === 'bottom' || bar === 'right'))
233
+ t = i;
234
+ }
235
+ return t;
236
+ }
237
+ /** The track a `content` bar or rail wants (its content plus `pad` each side, clamped by `max`), or 0. */
238
+ function contentWant(form, content, bar) {
239
+ const b = form.bars?.[bar];
240
+ const v = content?.[bar];
241
+ if (b?.size !== 'content' || v === undefined || !(v > 0))
242
+ return 0;
243
+ return Math.min(v + 2 * form.pad, b.max ?? Number.POSITIVE_INFINITY);
244
+ }
245
+ /**
246
+ * `cells` with the corner rule applied (ws/ui-9, `BarSize`): where a content bar and a content
247
+ * rail meet, the corner cell goes to the one that wants the larger track. A tie, or a swap that
248
+ * would leave a region not a rectangle, keeps the cell. Pure; returns `cells` itself when nothing
249
+ * moves.
250
+ */
251
+ function cornerCells(form, cells, content) {
252
+ if (!content || !form.bars)
253
+ return cells;
254
+ let out = cells;
255
+ for (const bar of ROW_BARS) {
256
+ const wb = contentWant(form, content, bar);
257
+ if (!wb)
258
+ continue;
259
+ for (const rail of COL_BARS) {
260
+ const wr = contentWant(form, content, rail);
261
+ if (!wr || wr === wb)
262
+ continue;
263
+ const r = outerTrack(out, bar);
264
+ const c = outerTrack(out, rail);
265
+ if (r < 0 || c < 0)
266
+ continue;
267
+ const owner = out[r][c];
268
+ const winner = wb > wr ? bar : rail;
269
+ if ((owner !== bar && owner !== rail) || owner === winner)
270
+ continue;
271
+ const next = out.map((row) => [...row]);
272
+ next[r][c] = winner;
273
+ if (!next.some((row) => row.includes(owner)))
274
+ continue;
275
+ try {
276
+ parseAreas(next.map((row) => row.join(' ')));
277
+ }
278
+ catch {
279
+ continue; // not a rectangle any more: keep the form's cell
280
+ }
281
+ out = next;
282
+ }
283
+ }
284
+ return out;
285
+ }
286
+ /**
287
+ * The tracks on one axis of `form` with each `content` bar's (rows: `top`, `bottom`; ws/ui-8) or
288
+ * rail's (columns: `left`, `right`; ws/ui-9) outer track grown to `contentWant`, never below the
289
+ * track's own size. Pure; returns the form's own tracks when nothing grows.
290
+ */
291
+ function contentTracks(form, cells, content, axis) {
292
+ const base = form[axis];
293
+ if (!content || !form.bars)
294
+ return base;
295
+ let tracks = base;
296
+ for (const bar of axis === 'rows' ? ROW_BARS : COL_BARS) {
297
+ const want = contentWant(form, content, bar);
298
+ if (!want)
299
+ continue;
300
+ const i = outerTrack(cells, bar);
301
+ const t = tracks[i];
302
+ if (t === undefined || t === 'fill')
303
+ continue;
304
+ let next;
305
+ if (typeof t === 'number') {
306
+ if (want <= t)
307
+ continue;
308
+ next = want;
309
+ }
310
+ else {
311
+ if (want <= (t.min ?? 0))
312
+ continue;
313
+ next = { ...t, min: want, max: Math.max(t.max ?? Number.POSITIVE_INFINITY, want) };
314
+ }
315
+ if (tracks === base)
316
+ tracks = [...base];
317
+ tracks[i] = next;
318
+ }
319
+ return tracks;
320
+ }
321
+ /** The `grid-template-areas` rows `resolveLayout` lays out for `content` (the corner rule applied). */
322
+ function resolvedAreas(form, content) {
323
+ const cells = parseAreas(form.areas);
324
+ const out = cornerCells(form, cells, content);
325
+ return out === cells ? form.areas : out.map((row) => row.join(' '));
326
+ }
327
+ /**
328
+ * Lays out one form at one size. `occupied` names the grid regions that hold widgets; with
329
+ * `collapseEmpty`, a track collapses when every region in it is empty or also spans another
330
+ * track on that axis (so a full-width bar never holds a side rail open). `fill` is never empty.
331
+ * `content` is the tallest widget per bar (`top`, `bottom`) and the widest per rail (`left`,
332
+ * `right`), CSS px, for the form's `content` bars and rails (`BarSize`, with its corner rule).
333
+ * Pure.
334
+ */
335
+ export function resolveLayout(formFactor, form, width, height, safeArea = NO_INSETS, occupied = null, content = null) {
336
+ const cells = cornerCells(form, parseAreas(form.areas), content);
337
+ if (cells.length !== form.rows.length || cells[0].length !== form.columns.length) {
338
+ throw new Error('layout: rows/columns do not match areas');
339
+ }
340
+ const innerW = Math.max(0, width - safeArea.left - safeArea.right);
341
+ const innerH = Math.max(0, height - safeArea.top - safeArea.bottom);
342
+ const spans = new Map();
343
+ cells.forEach((row, r) => {
344
+ row.forEach((n, c) => {
345
+ if (n === '.')
346
+ return;
347
+ const b = spans.get(n);
348
+ if (!b)
349
+ spans.set(n, { r0: r, r1: r, c0: c, c1: c });
350
+ else {
351
+ b.r1 = Math.max(b.r1, r);
352
+ b.c1 = Math.max(b.c1, c);
353
+ }
354
+ });
355
+ });
356
+ const empty = (n) => n !== 'fill' && occupied !== null && !occupied.has(n);
357
+ const collapseAxis = (count, axis) => Array.from({ length: count }, (_, i) => {
358
+ if (occupied === null)
359
+ return false;
360
+ const names = new Set(axis === 'r' ? cells[i] : cells.map((row) => row[i]));
361
+ for (const n of names) {
362
+ if (n === '.')
363
+ continue;
364
+ const s = spans.get(n);
365
+ const wide = axis === 'r' ? s.r1 > s.r0 : s.c1 > s.c0;
366
+ if (n === 'fill' || (!empty(n) && !wide))
367
+ return false;
368
+ }
369
+ return true;
370
+ });
371
+ const colCollapsed = collapseAxis(form.columns.length, 'c');
372
+ const rowCollapsed = collapseAxis(form.rows.length, 'r');
373
+ // An occupied region never ends up with every track collapsed: reopen its first one.
374
+ for (const [n, s] of spans) {
375
+ if (empty(n))
376
+ continue;
377
+ if (colCollapsed.slice(s.c0, s.c1 + 1).every(Boolean))
378
+ colCollapsed[s.c0] = false;
379
+ if (rowCollapsed.slice(s.r0, s.r1 + 1).every(Boolean))
380
+ rowCollapsed[s.r0] = false;
381
+ }
382
+ const columns = resolveTracks(contentTracks(form, cells, content, 'columns'), innerW, colCollapsed);
383
+ const rows = resolveTracks(contentTracks(form, cells, content, 'rows'), innerH, rowCollapsed);
384
+ const offs = (t, i) => t.slice(0, i).reduce((a, b) => a + b, 0);
385
+ const regions = Object.fromEntries(REGIONS.map((n) => [n, null]));
386
+ for (const [n, s] of spans) {
387
+ const x = safeArea.left + offs(columns, s.c0);
388
+ const y = safeArea.top + offs(rows, s.r0);
389
+ regions[n] = {
390
+ x,
391
+ y,
392
+ width: offs(columns, s.c1 + 1) - offs(columns, s.c0),
393
+ height: offs(rows, s.r1 + 1) - offs(rows, s.r0),
394
+ };
395
+ }
396
+ const m = form.modalMargin;
397
+ regions.modal = {
398
+ x: safeArea.left + m,
399
+ y: safeArea.top + m,
400
+ width: Math.max(0, innerW - 2 * m),
401
+ height: Math.max(0, innerH - 2 * m),
402
+ };
403
+ const freeRect = regions.fill ?? {
404
+ x: safeArea.left,
405
+ y: safeArea.top,
406
+ width: innerW,
407
+ height: innerH,
408
+ };
409
+ return {
410
+ formFactor,
411
+ units: form.units,
412
+ width,
413
+ height,
414
+ safeArea,
415
+ columns,
416
+ rows,
417
+ regions,
418
+ freeRect: { ...freeRect },
419
+ };
420
+ }
421
+ /** Where `place` puts a widget in `ff`. Pure. */
422
+ export function placementFor(place, ff) {
423
+ return place[ff] ?? place.default ?? 'hidden';
424
+ }
425
+ /**
426
+ * Pads the layout's regions by a host's safe-area insets (ws/host-mobile): sets
427
+ * `layout.setSafeArea(source.insets())` now and again whenever `source` reports a change, so the
428
+ * `top` region starts below the notch and the `bottom` region ends above the home indicator.
429
+ * `createGame` calls it with `host.safeArea` when the host has one; without it the layout reads
430
+ * `env(safe-area-inset-*)` itself. Returns unsubscribe, which also hands the page's own insets
431
+ * back to the layout.
432
+ */
433
+ export function followSafeArea(layout, source) {
434
+ const apply = (i) => layout.setSafeArea({ top: i.top, right: i.right, bottom: i.bottom, left: i.left });
435
+ apply(source.insets());
436
+ const off = source.watch(apply);
437
+ return () => {
438
+ off();
439
+ layout.setSafeArea(null);
440
+ };
441
+ }
442
+ /** CSS px safe-area insets of `doc`'s viewport (`env(safe-area-inset-*)`, or the `--tc-safe-*` overrides). */
443
+ export function readSafeArea(doc = document) {
444
+ const probe = doc.createElement('div');
445
+ probe.style.cssText =
446
+ 'position:fixed;left:0;top:0;width:0;height:0;visibility:hidden;pointer-events:none;' +
447
+ 'padding:var(--tc-safe-top,env(safe-area-inset-top,0px)) var(--tc-safe-right,env(safe-area-inset-right,0px)) ' +
448
+ 'var(--tc-safe-bottom,env(safe-area-inset-bottom,0px)) var(--tc-safe-left,env(safe-area-inset-left,0px));';
449
+ doc.body.append(probe);
450
+ const cs = doc.defaultView?.getComputedStyle(probe);
451
+ const px = (v) => Number.parseFloat(v ?? '') || 0;
452
+ const out = {
453
+ top: px(cs?.paddingTop),
454
+ right: px(cs?.paddingRight),
455
+ bottom: px(cs?.paddingBottom),
456
+ left: px(cs?.paddingLeft),
457
+ };
458
+ probe.remove();
459
+ return out;
460
+ }
461
+ /** Slack before a measured box counts as larger than its region, CSS px (sub-pixel rounding). */
462
+ const OVERFLOW_SLACK = 0.5;
463
+ const r2 = (v) => Math.round(v * 100) / 100;
464
+ const HIDDEN_ATTR = 'data-tc-layout-hidden';
465
+ /**
466
+ * Lays out regions over the overlay (`createOverlay` calls it; read it as `overlay.layout`).
467
+ *
468
+ * The density tokens `--tc-tap-min` and `--tc-text-min` (from the form spec), `data-form-factor`
469
+ * and the form's `units` go on the overlay root, so the base widgets in the panels layer follow
470
+ * them too.
471
+ */
472
+ export function createLayout(overlay, spec = {}) {
473
+ const root = overlay.root;
474
+ const doc = root.ownerDocument;
475
+ const win = doc.defaultView;
476
+ const classify = spec.classify ?? classifyFormFactor;
477
+ const collapseEmpty = spec.collapseEmpty !== false;
478
+ let insetOverride = spec.safeArea ?? null;
479
+ let formOverride = null;
480
+ const regionEls = Object.fromEntries(REGIONS.map((n) => [
481
+ n,
482
+ h('div', {
483
+ class: 'tc-region',
484
+ dataset: { region: n },
485
+ style: n === 'modal' ? {} : { gridArea: n },
486
+ }),
487
+ ]));
488
+ const free = h('div', { class: 'tc-layout-free' });
489
+ const element = h('div', { class: 'tc-layout' }, ...REGIONS.filter((n) => n !== 'modal').map((n) => regionEls[n]));
490
+ overlay.layers.layout.append(element, regionEls.modal, free);
491
+ const entries = [];
492
+ const subs = new Set();
493
+ let seq = 0;
494
+ let state;
495
+ let freeUses = [];
496
+ let overflows = [];
497
+ const measureInsets = () => {
498
+ const vp = {
499
+ width: win?.innerWidth ?? overlay.rect.width,
500
+ height: win?.innerHeight ?? overlay.rect.height,
501
+ };
502
+ const raw = insetOverride ?? readSafeArea(doc);
503
+ // The overlay's rect is relative to its parent; place it in the viewport.
504
+ const pb = root.parentElement?.getBoundingClientRect();
505
+ const r = overlay.rect;
506
+ const inView = {
507
+ x: (pb?.left ?? 0) + r.x,
508
+ y: (pb?.top ?? 0) + r.y,
509
+ width: r.width,
510
+ height: r.height,
511
+ };
512
+ return effectiveInsets(inView, vp, raw);
513
+ };
514
+ const classifyNow = () => {
515
+ const mq = (q) => typeof win?.matchMedia === 'function' && win.matchMedia(q).matches;
516
+ const s = win?.screen;
517
+ return classify({
518
+ width: overlay.rect.width,
519
+ height: overlay.rect.height,
520
+ coarsePointer: mq('(pointer: coarse)'),
521
+ shortSide: s ? Math.min(s.width, s.height) : 0,
522
+ });
523
+ };
524
+ const apply = () => {
525
+ const ff = classifyNow();
526
+ const form = { ...formSpec(ff, spec), ...formOverride?.[ff] };
527
+ // Which grid regions hold a widget in this form factor.
528
+ const occupied = new Set();
529
+ for (const e of entries) {
530
+ const p = placementFor(e.place, ff);
531
+ if (typeof p === 'string' && p !== 'hidden')
532
+ occupied.add(p);
533
+ }
534
+ const insets = measureInsets();
535
+ const resolve = (content) => resolveLayout(ff, form, overlay.rect.width, overlay.rect.height, insets, collapseEmpty ? occupied : null, content);
536
+ state = resolve(null);
537
+ writeGrid(ff, form, insets, occupied);
538
+ for (const e of entries)
539
+ e.onForm?.(ff);
540
+ // Widgets. A widget already in its region after the one placed before it stays put
541
+ // (ws/ui-11): re-inserting a node cancels its CSS transitions and drops its focus, so a
542
+ // re-layout moves only the elements whose region or order changed, and tells them (`onMove`).
543
+ freeUses = [];
544
+ const sorted = [...entries].sort((a, b) => a.order - b.order || a.seq - b.seq);
545
+ const freed = [];
546
+ const moved = [];
547
+ const lastIn = new Map();
548
+ for (const e of sorted) {
549
+ const was = e.current;
550
+ let p = placementFor(e.place, ff);
551
+ if (typeof p === 'string' && p !== 'hidden' && !state.regions[p]) {
552
+ console.warn(`[layout] ${e.id}: no "${p}" region in ${ff}; hidden`);
553
+ p = 'hidden';
554
+ }
555
+ e.el.toggleAttribute(HIDDEN_ATTR, p === 'hidden');
556
+ e.el.style.order = e.order ? String(e.order) : '';
557
+ if (typeof p === 'object') {
558
+ if (!e.loggedFree) {
559
+ console.info(`[layout] free placement: ${e.id} (${p.reason})`);
560
+ e.loggedFree = true;
561
+ }
562
+ freeUses.push({ id: e.id, reason: p.reason });
563
+ if (e.caption)
564
+ e.caption.dock(null);
565
+ if (e.el.parentNode !== free)
566
+ free.append(e.el);
567
+ freed.push([e, p]);
568
+ e.current = 'free';
569
+ if (was !== 'free')
570
+ moved.push(e);
571
+ continue;
572
+ }
573
+ if (e.current === 'free') {
574
+ for (const k of ['position', 'left', 'top', 'width', 'height'])
575
+ e.el.style[k] = '';
576
+ }
577
+ if (p === 'hidden') {
578
+ if (e.caption)
579
+ e.caption.dock(null);
580
+ e.current = 'hidden';
581
+ if (was !== 'hidden')
582
+ moved.push(e);
583
+ continue;
584
+ }
585
+ const r = regionEls[p];
586
+ const prev = lastIn.get(r);
587
+ let move = was !== p;
588
+ if (e.caption)
589
+ e.caption.dock(r);
590
+ else if (e.el.parentNode !== r ||
591
+ (prev && !(prev.compareDocumentPosition(e.el) & Node.DOCUMENT_POSITION_FOLLOWING))) {
592
+ if (prev)
593
+ prev.after(e.el);
594
+ else
595
+ r.prepend(e.el);
596
+ move = true;
597
+ }
598
+ lastIn.set(r, e.caption ? e.caption.element : e.el);
599
+ e.current = p;
600
+ if (move)
601
+ moved.push(e);
602
+ }
603
+ // Content bars and rails (ws/ui-8, ws/ui-9): one read of the bar widgets' heights and the
604
+ // rail widgets' widths, then the grid re-flows once if a track or a corner moved. A content
605
+ // rail is measured at `max-content` (ws/ui-10): for the read it is as wide as its widest
606
+ // widget wants and does not stretch them, so a widget reports its intrinsic width rather than
607
+ // the rail's current one, and the rail follows its content both ways.
608
+ const bars = form.bars;
609
+ if (bars && BAR_NAMES.some((b) => bars[b]?.size === 'content')) {
610
+ const content = {};
611
+ const left = bars.left?.size === 'content' ? intrinsic(regionEls.left) : null;
612
+ const right = bars.right?.size === 'content' ? intrinsic(regionEls.right) : null;
613
+ for (const e of sorted) {
614
+ const p = e.current;
615
+ if (p !== 'top' && p !== 'bottom' && p !== 'left' && p !== 'right')
616
+ continue;
617
+ if (bars[p]?.size !== 'content')
618
+ continue;
619
+ const box = e.el.getBoundingClientRect?.();
620
+ const v = (p === 'left' || p === 'right' ? box?.width : box?.height) ?? 0;
621
+ content[p] = Math.max(content[p] ?? 0, v);
622
+ }
623
+ left?.();
624
+ right?.();
625
+ const next = resolve(content);
626
+ const areas = resolvedAreas(form, content);
627
+ if (areas !== form.areas ||
628
+ next.rows.some((r, i) => r !== state.rows[i]) ||
629
+ next.columns.some((c, i) => c !== state.columns[i])) {
630
+ state = next;
631
+ writeGrid(ff, form, insets, occupied, areas);
632
+ }
633
+ }
634
+ for (const [e, p] of freed) {
635
+ const r = typeof p.rect === 'function' ? p.rect(state) : p.rect;
636
+ Object.assign(e.el.style, {
637
+ position: 'absolute',
638
+ left: `${r.x}px`,
639
+ top: `${r.y}px`,
640
+ width: `${r.width}px`,
641
+ height: `${r.height}px`,
642
+ });
643
+ }
644
+ // Overflow: one read pass after the writes above (one reflow per re-layout, none per frame).
645
+ overflows = [];
646
+ for (const e of sorted) {
647
+ const p = e.current;
648
+ if (p === 'free' || p === 'hidden')
649
+ continue;
650
+ const r = state.regions[p];
651
+ const box = e.el.getBoundingClientRect?.();
652
+ if (!r || !box)
653
+ continue;
654
+ const rw = r2(Math.max(0, r.width - 2 * form.pad));
655
+ const rh = r2(Math.max(0, r.height - 2 * form.pad));
656
+ const w = r2(box.width);
657
+ const bh = r2(box.height);
658
+ if (w <= rw + OVERFLOW_SLACK && bh <= rh + OVERFLOW_SLACK)
659
+ continue;
660
+ overflows.push({ id: e.id, region: p, w, h: bh, rw, rh });
661
+ if (!e.warnedOverflow) {
662
+ e.warnedOverflow = true;
663
+ console.warn(`[layout] ${e.id} overflows its "${p}" region in ${ff}: ${w}x${bh} px in ${rw}x${rh} px`);
664
+ }
665
+ }
666
+ for (const e of moved)
667
+ e.onMove?.(e.current);
668
+ for (const f of subs)
669
+ f(state);
670
+ };
671
+ /**
672
+ * Lay a rail out at `max-content` for a measure (ws/ui-10): the region as wide as its widest
673
+ * widget wants, its widgets at their own width (`align-items: flex-start` instead of the rail's
674
+ * stretch). Returns the restore. Inline values a game set on the region come back.
675
+ */
676
+ const intrinsic = (el) => {
677
+ const s = el.style;
678
+ const width = s.width;
679
+ const align = s.alignItems;
680
+ s.width = 'max-content';
681
+ s.alignItems = 'flex-start';
682
+ return () => {
683
+ s.width = width;
684
+ s.alignItems = align;
685
+ };
686
+ };
687
+ /** Tokens, grid tracks and region visibility for the current `state`. */
688
+ const writeGrid = (ff, form, insets, occupied,
689
+ /** The grid areas `state` was laid out with (the corner rule may move a cell; ws/ui-9). */
690
+ areas = form.areas) => {
691
+ root.dataset.formFactor = ff;
692
+ root.style.setProperty('--tc-tap-min', `${form.tapMin}px`);
693
+ root.style.setProperty('--tc-text-min', `${form.textMin}px`);
694
+ root.style.setProperty('--tc-region-pad', `${form.pad}px`);
695
+ for (const side of ['top', 'right', 'bottom', 'left']) {
696
+ root.style.setProperty(`--tc-safe-${side}-px`, `${insets[side]}px`);
697
+ }
698
+ const m = state.regions.modal;
699
+ root.style.setProperty('--tc-modal-top', `${m.y}px`);
700
+ root.style.setProperty('--tc-modal-left', `${m.x}px`);
701
+ root.style.setProperty('--tc-modal-right', `${state.width - m.x - m.width}px`);
702
+ root.style.setProperty('--tc-modal-bottom', `${state.height - m.y - m.height}px`);
703
+ Object.assign(element.style, {
704
+ padding: `${insets.top}px ${insets.right}px ${insets.bottom}px ${insets.left}px`,
705
+ gridTemplateColumns: state.columns.map((c) => `${c}px`).join(' '),
706
+ gridTemplateRows: state.rows.map((r) => `${r}px`).join(' '),
707
+ gridTemplateAreas: areas.map((a) => `"${a.trim().split(/\s+/).join(' ')}"`).join(' '),
708
+ });
709
+ for (const n of REGIONS) {
710
+ const r = state.regions[n];
711
+ const el = regionEls[n];
712
+ const absent = !r || r.width <= 0 || r.height <= 0;
713
+ el.toggleAttribute('data-empty', absent || (n !== 'fill' && !occupied.has(n)));
714
+ // A content bar or rail (ws/ui-11): widgets that otherwise shrink to fit their region
715
+ // (the minimap) hold their own size here, and the track grows to them instead.
716
+ el.toggleAttribute('data-content', form.bars?.[n]?.size === 'content');
717
+ el.hidden = absent;
718
+ if (n === 'modal' && r) {
719
+ Object.assign(el.style, {
720
+ left: `${r.x}px`,
721
+ top: `${r.y}px`,
722
+ width: `${r.width}px`,
723
+ height: `${r.height}px`,
724
+ });
725
+ }
726
+ }
727
+ };
728
+ const unwatch = overlay.onResize(() => apply());
729
+ const layout = {
730
+ element,
731
+ get formFactor() {
732
+ return state.formFactor;
733
+ },
734
+ get state() {
735
+ return state;
736
+ },
737
+ get freeRect() {
738
+ return state.freeRect;
739
+ },
740
+ get safeArea() {
741
+ return state.safeArea;
742
+ },
743
+ get freeUses() {
744
+ return freeUses;
745
+ },
746
+ region(name) {
747
+ return regionEls[name];
748
+ },
749
+ overflows: () => overflows,
750
+ add(el, opts) {
751
+ const e = {
752
+ id: opts.id ?? (el.id || `widget-${seq + 1}`),
753
+ el,
754
+ place: { ...opts.place },
755
+ order: opts.order ?? 0,
756
+ seq: seq++,
757
+ current: 'hidden',
758
+ home: el.parentNode ? { parent: el.parentNode, next: el.nextSibling } : undefined,
759
+ loggedFree: false,
760
+ warnedOverflow: false,
761
+ onForm: opts.onForm,
762
+ onMove: opts.onMove,
763
+ };
764
+ entries.push(e);
765
+ apply();
766
+ return handle(e);
767
+ },
768
+ dockCaption(caption = overlay.caption, place = { default: 'captionLane' }) {
769
+ const e = {
770
+ id: 'caption',
771
+ el: caption.element,
772
+ place: { ...place },
773
+ order: 0,
774
+ seq: seq++,
775
+ current: 'hidden',
776
+ caption,
777
+ loggedFree: false,
778
+ warnedOverflow: false,
779
+ };
780
+ entries.push(e);
781
+ apply();
782
+ return handle(e);
783
+ },
784
+ setSafeArea(insets) {
785
+ insetOverride = insets ? { ...insets } : null;
786
+ apply();
787
+ },
788
+ setForms(forms) {
789
+ formOverride = forms ? { ...forms } : null;
790
+ apply();
791
+ },
792
+ refresh: () => apply(),
793
+ onChange(fn) {
794
+ subs.add(fn);
795
+ return () => subs.delete(fn);
796
+ },
797
+ destroy() {
798
+ unwatch();
799
+ subs.clear();
800
+ for (const e of entries.splice(0)) {
801
+ if (e.caption)
802
+ e.caption.dock(null);
803
+ else if (e.home)
804
+ goHome(e);
805
+ e.el.removeAttribute(HIDDEN_ATTR);
806
+ }
807
+ element.remove();
808
+ regionEls.modal.remove();
809
+ free.remove();
810
+ delete root.dataset.formFactor;
811
+ },
812
+ };
813
+ /** Back where it was before `add`, or out of the document if it had no parent then. */
814
+ function goHome(e) {
815
+ const home = e.home;
816
+ if (!home?.parent) {
817
+ e.el.remove();
818
+ return;
819
+ }
820
+ const next = home.next && home.next.parentNode === home.parent ? home.next : null;
821
+ home.parent.insertBefore(e.el, next);
822
+ }
823
+ function handle(e) {
824
+ return {
825
+ element: e.el,
826
+ get region() {
827
+ return e.current;
828
+ },
829
+ setPlace(place) {
830
+ e.place = { ...place };
831
+ e.loggedFree = false;
832
+ e.warnedOverflow = false;
833
+ apply();
834
+ },
835
+ remove() {
836
+ const i = entries.indexOf(e);
837
+ if (i < 0)
838
+ return;
839
+ entries.splice(i, 1);
840
+ if (e.caption)
841
+ e.caption.dock(null);
842
+ else
843
+ goHome(e);
844
+ e.el.removeAttribute(HIDDEN_ATTR);
845
+ e.el.style.order = '';
846
+ if (e.current === 'free') {
847
+ for (const k of ['position', 'left', 'top', 'width', 'height'])
848
+ e.el.style[k] = '';
849
+ }
850
+ apply();
851
+ },
852
+ };
853
+ }
854
+ apply();
855
+ return layout;
856
+ }