@czap/core 0.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 (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/addressed-digest.d.ts +15 -0
  4. package/dist/addressed-digest.d.ts.map +1 -0
  5. package/dist/addressed-digest.js +35 -0
  6. package/dist/addressed-digest.js.map +1 -0
  7. package/dist/animation.d.ts +46 -0
  8. package/dist/animation.d.ts.map +1 -0
  9. package/dist/animation.js +70 -0
  10. package/dist/animation.js.map +1 -0
  11. package/dist/assembly.d.ts +25 -0
  12. package/dist/assembly.d.ts.map +1 -0
  13. package/dist/assembly.js +58 -0
  14. package/dist/assembly.js.map +1 -0
  15. package/dist/av-bridge.d.ts +74 -0
  16. package/dist/av-bridge.d.ts.map +1 -0
  17. package/dist/av-bridge.js +107 -0
  18. package/dist/av-bridge.js.map +1 -0
  19. package/dist/av-renderer.d.ts +56 -0
  20. package/dist/av-renderer.d.ts.map +1 -0
  21. package/dist/av-renderer.js +65 -0
  22. package/dist/av-renderer.js.map +1 -0
  23. package/dist/blend.d.ts +61 -0
  24. package/dist/blend.d.ts.map +1 -0
  25. package/dist/blend.js +100 -0
  26. package/dist/blend.js.map +1 -0
  27. package/dist/boundary.d.ts +154 -0
  28. package/dist/boundary.d.ts.map +1 -0
  29. package/dist/boundary.js +269 -0
  30. package/dist/boundary.js.map +1 -0
  31. package/dist/brands.d.ts +63 -0
  32. package/dist/brands.d.ts.map +1 -0
  33. package/dist/brands.js +31 -0
  34. package/dist/brands.js.map +1 -0
  35. package/dist/caps.d.ts +49 -0
  36. package/dist/caps.d.ts.map +1 -0
  37. package/dist/caps.js +73 -0
  38. package/dist/caps.js.map +1 -0
  39. package/dist/capsule.d.ts +77 -0
  40. package/dist/capsule.d.ts.map +1 -0
  41. package/dist/capsule.js +18 -0
  42. package/dist/capsule.js.map +1 -0
  43. package/dist/capsules/boundary-evaluate.d.ts +28 -0
  44. package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
  45. package/dist/capsules/boundary-evaluate.js +117 -0
  46. package/dist/capsules/boundary-evaluate.js.map +1 -0
  47. package/dist/capsules/canonical-cbor.d.ts +13 -0
  48. package/dist/capsules/canonical-cbor.d.ts.map +1 -0
  49. package/dist/capsules/canonical-cbor.js +60 -0
  50. package/dist/capsules/canonical-cbor.js.map +1 -0
  51. package/dist/capsules/token-buffer.d.ts +24 -0
  52. package/dist/capsules/token-buffer.d.ts.map +1 -0
  53. package/dist/capsules/token-buffer.js +53 -0
  54. package/dist/capsules/token-buffer.js.map +1 -0
  55. package/dist/capture.d.ts +40 -0
  56. package/dist/capture.d.ts.map +1 -0
  57. package/dist/capture.js +10 -0
  58. package/dist/capture.js.map +1 -0
  59. package/dist/cbor.d.ts +33 -0
  60. package/dist/cbor.d.ts.map +1 -0
  61. package/dist/cbor.js +179 -0
  62. package/dist/cbor.js.map +1 -0
  63. package/dist/cell.d.ts +53 -0
  64. package/dist/cell.d.ts.map +1 -0
  65. package/dist/cell.js +83 -0
  66. package/dist/cell.js.map +1 -0
  67. package/dist/codec.d.ts +30 -0
  68. package/dist/codec.d.ts.map +1 -0
  69. package/dist/codec.js +25 -0
  70. package/dist/codec.js.map +1 -0
  71. package/dist/component.d.ts +52 -0
  72. package/dist/component.d.ts.map +1 -0
  73. package/dist/component.js +44 -0
  74. package/dist/component.js.map +1 -0
  75. package/dist/composable.d.ts +76 -0
  76. package/dist/composable.d.ts.map +1 -0
  77. package/dist/composable.js +221 -0
  78. package/dist/composable.js.map +1 -0
  79. package/dist/compositor-pool.d.ts +74 -0
  80. package/dist/compositor-pool.d.ts.map +1 -0
  81. package/dist/compositor-pool.js +119 -0
  82. package/dist/compositor-pool.js.map +1 -0
  83. package/dist/compositor.d.ts +90 -0
  84. package/dist/compositor.d.ts.map +1 -0
  85. package/dist/compositor.js +278 -0
  86. package/dist/compositor.js.map +1 -0
  87. package/dist/config.d.ts +72 -0
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +97 -0
  90. package/dist/config.js.map +1 -0
  91. package/dist/dag.d.ts +251 -0
  92. package/dist/dag.d.ts.map +1 -0
  93. package/dist/dag.js +450 -0
  94. package/dist/dag.js.map +1 -0
  95. package/dist/defaults.d.ts +45 -0
  96. package/dist/defaults.d.ts.map +1 -0
  97. package/dist/defaults.js +45 -0
  98. package/dist/defaults.js.map +1 -0
  99. package/dist/derived.d.ts +34 -0
  100. package/dist/derived.d.ts.map +1 -0
  101. package/dist/derived.js +101 -0
  102. package/dist/derived.js.map +1 -0
  103. package/dist/diagnostics.d.ts +77 -0
  104. package/dist/diagnostics.d.ts.map +1 -0
  105. package/dist/diagnostics.js +122 -0
  106. package/dist/diagnostics.js.map +1 -0
  107. package/dist/dirty.d.ts +55 -0
  108. package/dist/dirty.d.ts.map +1 -0
  109. package/dist/dirty.js +80 -0
  110. package/dist/dirty.js.map +1 -0
  111. package/dist/easing.d.ts +55 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +291 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/ecs.d.ts +105 -0
  116. package/dist/ecs.d.ts.map +1 -0
  117. package/dist/ecs.js +245 -0
  118. package/dist/ecs.js.map +1 -0
  119. package/dist/fnv.d.ts +14 -0
  120. package/dist/fnv.d.ts.map +1 -0
  121. package/dist/fnv.js +28 -0
  122. package/dist/fnv.js.map +1 -0
  123. package/dist/frame-budget.d.ts +73 -0
  124. package/dist/frame-budget.d.ts.map +1 -0
  125. package/dist/frame-budget.js +114 -0
  126. package/dist/frame-budget.js.map +1 -0
  127. package/dist/gen-frame.d.ts +102 -0
  128. package/dist/gen-frame.d.ts.map +1 -0
  129. package/dist/gen-frame.js +121 -0
  130. package/dist/gen-frame.js.map +1 -0
  131. package/dist/harness/arbitrary-from-schema.d.ts +28 -0
  132. package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
  133. package/dist/harness/arbitrary-from-schema.js +262 -0
  134. package/dist/harness/arbitrary-from-schema.js.map +1 -0
  135. package/dist/harness/cached-projection.d.ts +19 -0
  136. package/dist/harness/cached-projection.d.ts.map +1 -0
  137. package/dist/harness/cached-projection.js +39 -0
  138. package/dist/harness/cached-projection.js.map +1 -0
  139. package/dist/harness/index.d.ts +16 -0
  140. package/dist/harness/index.d.ts.map +1 -0
  141. package/dist/harness/index.js +15 -0
  142. package/dist/harness/index.js.map +1 -0
  143. package/dist/harness/policy-gate.d.ts +18 -0
  144. package/dist/harness/policy-gate.d.ts.map +1 -0
  145. package/dist/harness/policy-gate.js +46 -0
  146. package/dist/harness/policy-gate.js.map +1 -0
  147. package/dist/harness/pure-transform.d.ts +42 -0
  148. package/dist/harness/pure-transform.d.ts.map +1 -0
  149. package/dist/harness/pure-transform.js +76 -0
  150. package/dist/harness/pure-transform.js.map +1 -0
  151. package/dist/harness/receipted-mutation.d.ts +23 -0
  152. package/dist/harness/receipted-mutation.d.ts.map +1 -0
  153. package/dist/harness/receipted-mutation.js +52 -0
  154. package/dist/harness/receipted-mutation.js.map +1 -0
  155. package/dist/harness/scene-composition.d.ts +19 -0
  156. package/dist/harness/scene-composition.d.ts.map +1 -0
  157. package/dist/harness/scene-composition.js +47 -0
  158. package/dist/harness/scene-composition.js.map +1 -0
  159. package/dist/harness/site-adapter.d.ts +18 -0
  160. package/dist/harness/site-adapter.d.ts.map +1 -0
  161. package/dist/harness/site-adapter.js +38 -0
  162. package/dist/harness/site-adapter.js.map +1 -0
  163. package/dist/harness/state-machine.d.ts +19 -0
  164. package/dist/harness/state-machine.d.ts.map +1 -0
  165. package/dist/harness/state-machine.js +44 -0
  166. package/dist/harness/state-machine.js.map +1 -0
  167. package/dist/hlc.d.ts +99 -0
  168. package/dist/hlc.d.ts.map +1 -0
  169. package/dist/hlc.js +219 -0
  170. package/dist/hlc.js.map +1 -0
  171. package/dist/index.d.ts +104 -0
  172. package/dist/index.d.ts.map +1 -0
  173. package/dist/index.js +137 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/interpolate.d.ts +14 -0
  176. package/dist/interpolate.d.ts.map +1 -0
  177. package/dist/interpolate.js +31 -0
  178. package/dist/interpolate.js.map +1 -0
  179. package/dist/live-cell.d.ts +46 -0
  180. package/dist/live-cell.d.ts.map +1 -0
  181. package/dist/live-cell.js +154 -0
  182. package/dist/live-cell.js.map +1 -0
  183. package/dist/op.d.ts +58 -0
  184. package/dist/op.d.ts.map +1 -0
  185. package/dist/op.js +171 -0
  186. package/dist/op.js.map +1 -0
  187. package/dist/plan.d.ts +195 -0
  188. package/dist/plan.d.ts.map +1 -0
  189. package/dist/plan.js +211 -0
  190. package/dist/plan.js.map +1 -0
  191. package/dist/protocol.d.ts +33 -0
  192. package/dist/protocol.d.ts.map +1 -0
  193. package/dist/protocol.js +10 -0
  194. package/dist/protocol.js.map +1 -0
  195. package/dist/quantizer-types.d.ts +28 -0
  196. package/dist/quantizer-types.d.ts.map +1 -0
  197. package/dist/quantizer-types.js +9 -0
  198. package/dist/quantizer-types.js.map +1 -0
  199. package/dist/receipt.d.ts +294 -0
  200. package/dist/receipt.d.ts.map +1 -0
  201. package/dist/receipt.js +352 -0
  202. package/dist/receipt.js.map +1 -0
  203. package/dist/runtime-coordinator.d.ts +75 -0
  204. package/dist/runtime-coordinator.d.ts.map +1 -0
  205. package/dist/runtime-coordinator.js +149 -0
  206. package/dist/runtime-coordinator.js.map +1 -0
  207. package/dist/scheduler.d.ts +58 -0
  208. package/dist/scheduler.d.ts.map +1 -0
  209. package/dist/scheduler.js +109 -0
  210. package/dist/scheduler.js.map +1 -0
  211. package/dist/ship-capsule.d.ts +54 -0
  212. package/dist/ship-capsule.d.ts.map +1 -0
  213. package/dist/ship-capsule.js +142 -0
  214. package/dist/ship-capsule.js.map +1 -0
  215. package/dist/ship-manifest.d.ts +45 -0
  216. package/dist/ship-manifest.d.ts.map +1 -0
  217. package/dist/ship-manifest.js +175 -0
  218. package/dist/ship-manifest.js.map +1 -0
  219. package/dist/signal.d.ts +149 -0
  220. package/dist/signal.d.ts.map +1 -0
  221. package/dist/signal.js +277 -0
  222. package/dist/signal.js.map +1 -0
  223. package/dist/speculative.d.ts +67 -0
  224. package/dist/speculative.d.ts.map +1 -0
  225. package/dist/speculative.js +139 -0
  226. package/dist/speculative.js.map +1 -0
  227. package/dist/store.d.ts +39 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +42 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/style.d.ts +119 -0
  232. package/dist/style.d.ts.map +1 -0
  233. package/dist/style.js +168 -0
  234. package/dist/style.js.map +1 -0
  235. package/dist/testing.d.ts +14 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +14 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme.d.ts +78 -0
  240. package/dist/theme.d.ts.map +1 -0
  241. package/dist/theme.js +109 -0
  242. package/dist/theme.js.map +1 -0
  243. package/dist/timeline.d.ts +45 -0
  244. package/dist/timeline.d.ts.map +1 -0
  245. package/dist/timeline.js +101 -0
  246. package/dist/timeline.js.map +1 -0
  247. package/dist/token-buffer.d.ts +43 -0
  248. package/dist/token-buffer.d.ts.map +1 -0
  249. package/dist/token-buffer.js +112 -0
  250. package/dist/token-buffer.js.map +1 -0
  251. package/dist/token.d.ts +107 -0
  252. package/dist/token.d.ts.map +1 -0
  253. package/dist/token.js +143 -0
  254. package/dist/token.js.map +1 -0
  255. package/dist/tuple.d.ts +16 -0
  256. package/dist/tuple.d.ts.map +1 -0
  257. package/dist/tuple.js +16 -0
  258. package/dist/tuple.js.map +1 -0
  259. package/dist/type-utils.d.ts +41 -0
  260. package/dist/type-utils.d.ts.map +1 -0
  261. package/dist/type-utils.js +10 -0
  262. package/dist/type-utils.js.map +1 -0
  263. package/dist/typed-ref.d.ts +50 -0
  264. package/dist/typed-ref.d.ts.map +1 -0
  265. package/dist/typed-ref.js +59 -0
  266. package/dist/typed-ref.js.map +1 -0
  267. package/dist/ui-quality.d.ts +50 -0
  268. package/dist/ui-quality.d.ts.map +1 -0
  269. package/dist/ui-quality.js +64 -0
  270. package/dist/ui-quality.js.map +1 -0
  271. package/dist/validation-error.d.ts +25 -0
  272. package/dist/validation-error.d.ts.map +1 -0
  273. package/dist/validation-error.js +32 -0
  274. package/dist/validation-error.js.map +1 -0
  275. package/dist/vector-clock.d.ts +46 -0
  276. package/dist/vector-clock.d.ts.map +1 -0
  277. package/dist/vector-clock.js +91 -0
  278. package/dist/vector-clock.js.map +1 -0
  279. package/dist/video.d.ts +62 -0
  280. package/dist/video.d.ts.map +1 -0
  281. package/dist/video.js +59 -0
  282. package/dist/video.js.map +1 -0
  283. package/dist/wasm-dispatch.d.ts +52 -0
  284. package/dist/wasm-dispatch.d.ts.map +1 -0
  285. package/dist/wasm-dispatch.js +204 -0
  286. package/dist/wasm-dispatch.js.map +1 -0
  287. package/dist/wasm-fallback.d.ts +19 -0
  288. package/dist/wasm-fallback.d.ts.map +1 -0
  289. package/dist/wasm-fallback.js +93 -0
  290. package/dist/wasm-fallback.js.map +1 -0
  291. package/dist/wire.d.ts +49 -0
  292. package/dist/wire.d.ts.map +1 -0
  293. package/dist/wire.js +201 -0
  294. package/dist/wire.js.map +1 -0
  295. package/dist/zap.d.ts +42 -0
  296. package/dist/zap.d.ts.map +1 -0
  297. package/dist/zap.js +172 -0
  298. package/dist/zap.js.map +1 -0
  299. package/package.json +71 -0
  300. package/src/addressed-digest.ts +48 -0
  301. package/src/animation.ts +103 -0
  302. package/src/assembly.ts +76 -0
  303. package/src/av-bridge.ts +161 -0
  304. package/src/av-renderer.ts +118 -0
  305. package/src/blend.ts +135 -0
  306. package/src/boundary.ts +363 -0
  307. package/src/brands.ts +86 -0
  308. package/src/caps.ts +100 -0
  309. package/src/capsule.ts +95 -0
  310. package/src/capsules/boundary-evaluate.ts +128 -0
  311. package/src/capsules/canonical-cbor.ts +60 -0
  312. package/src/capsules/token-buffer.ts +57 -0
  313. package/src/capture.ts +48 -0
  314. package/src/cbor.ts +199 -0
  315. package/src/cell.ts +130 -0
  316. package/src/codec.ts +39 -0
  317. package/src/component.ts +102 -0
  318. package/src/composable.ts +328 -0
  319. package/src/compositor-pool.ts +162 -0
  320. package/src/compositor.ts +387 -0
  321. package/src/config.ts +157 -0
  322. package/src/dag.ts +527 -0
  323. package/src/defaults.ts +60 -0
  324. package/src/derived.ts +164 -0
  325. package/src/diagnostics.ts +186 -0
  326. package/src/dirty.ts +101 -0
  327. package/src/easing.ts +334 -0
  328. package/src/ecs.ts +382 -0
  329. package/src/fnv.ts +31 -0
  330. package/src/frame-budget.ts +149 -0
  331. package/src/gen-frame.ts +229 -0
  332. package/src/harness/arbitrary-from-schema.ts +270 -0
  333. package/src/harness/cached-projection.ts +46 -0
  334. package/src/harness/index.ts +16 -0
  335. package/src/harness/policy-gate.ts +51 -0
  336. package/src/harness/pure-transform.ts +121 -0
  337. package/src/harness/receipted-mutation.ts +59 -0
  338. package/src/harness/scene-composition.ts +54 -0
  339. package/src/harness/site-adapter.ts +43 -0
  340. package/src/harness/state-machine.ts +49 -0
  341. package/src/hlc.ts +238 -0
  342. package/src/index.ts +274 -0
  343. package/src/interpolate.ts +37 -0
  344. package/src/live-cell.ts +199 -0
  345. package/src/op.ts +233 -0
  346. package/src/plan.ts +317 -0
  347. package/src/protocol.ts +49 -0
  348. package/src/quantizer-types.ts +29 -0
  349. package/src/receipt.ts +444 -0
  350. package/src/runtime-coordinator.ts +230 -0
  351. package/src/scheduler.ts +161 -0
  352. package/src/ship-capsule.ts +191 -0
  353. package/src/signal.ts +345 -0
  354. package/src/speculative.ts +186 -0
  355. package/src/store.ts +77 -0
  356. package/src/style.ts +249 -0
  357. package/src/testing.ts +14 -0
  358. package/src/theme.ts +153 -0
  359. package/src/timeline.ts +146 -0
  360. package/src/token-buffer.ts +151 -0
  361. package/src/token.ts +197 -0
  362. package/src/tuple.ts +19 -0
  363. package/src/type-utils.ts +48 -0
  364. package/src/typed-ref.ts +79 -0
  365. package/src/ui-quality.ts +105 -0
  366. package/src/validation-error.ts +34 -0
  367. package/src/vector-clock.ts +111 -0
  368. package/src/video.ts +106 -0
  369. package/src/wasm-dispatch.ts +300 -0
  370. package/src/wasm-fallback.ts +102 -0
  371. package/src/wire.ts +274 -0
  372. package/src/zap.ts +241 -0
package/src/easing.ts ADDED
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Easing -- pure math easing functions.
3
+ *
4
+ * All functions map t in [0,1] to value in [0,1].
5
+ * Zero dependencies -- pure arithmetic.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { EASING_SPRING_STEPS } from './defaults.js';
11
+
12
+ type EasingFnShape = (t: number) => number;
13
+
14
+ interface SpringConfigShape {
15
+ readonly stiffness: number;
16
+ readonly damping: number;
17
+ readonly mass?: number;
18
+ }
19
+
20
+ interface EasingFns {
21
+ readonly linear: EasingFnShape;
22
+ readonly easeInCubic: EasingFnShape;
23
+ readonly easeOutCubic: EasingFnShape;
24
+ readonly easeInOutCubic: EasingFnShape;
25
+ readonly easeOutExpo: EasingFnShape;
26
+ readonly easeOutBack: EasingFnShape;
27
+ readonly easeOutElastic: EasingFnShape;
28
+ readonly easeOutBounce: EasingFnShape;
29
+ readonly ease: EasingFnShape;
30
+ readonly easeIn: EasingFnShape;
31
+ readonly easeOut: EasingFnShape;
32
+ readonly easeInOut: EasingFnShape;
33
+ spring(config: SpringConfigShape): EasingFnShape;
34
+ cubicBezier(x1: number, y1: number, x2: number, y2: number): EasingFnShape;
35
+ springToLinearCSS(config: SpringConfigShape, sampleCount?: number): string;
36
+ springNaturalDuration(config: SpringConfigShape, epsilon?: number): number;
37
+ }
38
+
39
+ /**
40
+ * Linear easing -- no acceleration, constant rate of change.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * Easing.linear(0.5); // 0.5
45
+ * ```
46
+ */
47
+ const linear: EasingFnShape = (t) => t;
48
+
49
+ /**
50
+ * Cubic ease-in -- starts slow, accelerates.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * Easing.easeInCubic(0.5); // 0.125
55
+ * ```
56
+ */
57
+ const easeInCubic: EasingFnShape = (t) => t * t * t;
58
+
59
+ /**
60
+ * Cubic ease-out -- starts fast, decelerates.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * Easing.easeOutCubic(0.5); // 0.875
65
+ * ```
66
+ */
67
+ const easeOutCubic: EasingFnShape = (t) => {
68
+ const u = 1 - t;
69
+ return 1 - u * u * u;
70
+ };
71
+
72
+ /**
73
+ * Cubic ease-in-out -- slow start and end, fast middle.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * Easing.easeInOutCubic(0.25); // 0.0625
78
+ * Easing.easeInOutCubic(0.75); // 0.9375
79
+ * ```
80
+ */
81
+ const easeInOutCubic: EasingFnShape = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
82
+
83
+ /**
84
+ * Exponential ease-out -- very fast deceleration.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * Easing.easeOutExpo(0.5); // ~0.969
89
+ * ```
90
+ */
91
+ const easeOutExpo: EasingFnShape = (t) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t));
92
+
93
+ /**
94
+ * Ease-out with overshoot -- overshoots target, then settles.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * Easing.easeOutBack(0.8); // ~1.037 (overshoots past 1.0)
99
+ * Easing.easeOutBack(1.0); // 1.0
100
+ * ```
101
+ */
102
+ const easeOutBack: EasingFnShape = (t) => {
103
+ /** Penner's back easing overshoot coefficient — controls how far the animation overshoots before settling. */
104
+ const BACK_OVERSHOOT = 1.70158;
105
+ const c3 = BACK_OVERSHOOT + 1;
106
+ return 1 + c3 * Math.pow(t - 1, 3) + BACK_OVERSHOOT * Math.pow(t - 1, 2);
107
+ };
108
+
109
+ /**
110
+ * Elastic ease-out -- spring-like oscillation that settles at 1.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * Easing.easeOutElastic(0.5); // ~1.015 (oscillates around target)
115
+ * Easing.easeOutElastic(1.0); // 1.0
116
+ * ```
117
+ */
118
+ const easeOutElastic: EasingFnShape = (t) => {
119
+ if (t === 0 || t === 1) return t;
120
+ /** One-third rotation for elastic oscillation — produces ~3 bounces in elastic easing. */
121
+ const ELASTIC_PERIOD = (2 * Math.PI) / 3;
122
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * ELASTIC_PERIOD) + 1;
123
+ };
124
+
125
+ /**
126
+ * Bounce ease-out -- simulates a bouncing ball settling at target.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * Easing.easeOutBounce(0.5); // 0.765625
131
+ * Easing.easeOutBounce(1.0); // 1.0
132
+ * ```
133
+ */
134
+ const easeOutBounce: EasingFnShape = (t) => {
135
+ /** Penner bounce easing coefficients — BOUNCE_AMPLITUDE controls peak height, BOUNCE_DIVISOR controls number of bounces. */
136
+ const BOUNCE_AMPLITUDE = 7.5625;
137
+ const BOUNCE_DIVISOR = 2.75;
138
+ if (t < 1 / BOUNCE_DIVISOR) return BOUNCE_AMPLITUDE * t * t;
139
+ if (t < 2 / BOUNCE_DIVISOR) {
140
+ const u = t - 1.5 / BOUNCE_DIVISOR;
141
+ return BOUNCE_AMPLITUDE * u * u + 0.75;
142
+ }
143
+ if (t < 2.5 / BOUNCE_DIVISOR) {
144
+ const u = t - 2.25 / BOUNCE_DIVISOR;
145
+ return BOUNCE_AMPLITUDE * u * u + 0.9375;
146
+ }
147
+ const u = t - 2.625 / BOUNCE_DIVISOR;
148
+ return BOUNCE_AMPLITUDE * u * u + 0.984375;
149
+ };
150
+
151
+ /**
152
+ * Creates a CSS cubic-bezier easing function from four control points.
153
+ * Uses binary search to approximate the bezier curve evaluation.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const customEase = Easing.cubicBezier(0.42, 0, 0.58, 1);
158
+ * customEase(0.5); // ~0.5 (equivalent to CSS ease-in-out)
159
+ * customEase(0.0); // 0.0
160
+ * customEase(1.0); // 1.0
161
+ * ```
162
+ */
163
+ function cubicBezier(x1: number, y1: number, x2: number, y2: number): EasingFnShape {
164
+ return (t: number): number => {
165
+ if (t <= 0) return 0;
166
+ if (t >= 1) return 1;
167
+ let lo = 0,
168
+ hi = 1;
169
+ /** Binary search iterations for cubic-bezier — 20 iterations converges to `<0.001` error for any control points. */
170
+ const BEZIER_ITERATIONS = 20;
171
+ for (let i = 0; i < BEZIER_ITERATIONS; i++) {
172
+ const mid = (lo + hi) / 2;
173
+ const x = 3 * (1 - mid) * (1 - mid) * mid * x1 + 3 * (1 - mid) * mid * mid * x2 + mid * mid * mid;
174
+ if (x < t) lo = mid;
175
+ else hi = mid;
176
+ }
177
+ const u = (lo + hi) / 2;
178
+ return 3 * (1 - u) * (1 - u) * u * y1 + 3 * (1 - u) * u * u * y2 + u * u * u;
179
+ };
180
+ }
181
+
182
+ const ease = cubicBezier(0.25, 0.1, 0.25, 1.0);
183
+ const easeIn = cubicBezier(0.42, 0, 1, 1);
184
+ const easeOut = cubicBezier(0, 0, 0.58, 1);
185
+ const easeInOut = cubicBezier(0.42, 0, 0.58, 1);
186
+
187
+ /**
188
+ * Evaluate raw spring physics at time `t` (in the spring's natural time domain,
189
+ * not normalized). Used internally by both `spring` and `springNaturalDuration`
190
+ * to avoid circular dependency.
191
+ */
192
+ function springRaw(t: number, omega: number, zeta: number): number {
193
+ if (zeta < 1) {
194
+ // Underdamped
195
+ const omegaD = omega * Math.sqrt(1 - zeta * zeta);
196
+ return 1 - Math.exp(-zeta * omega * t) * (Math.cos(omegaD * t) + ((zeta * omega) / omegaD) * Math.sin(omegaD * t));
197
+ }
198
+ if (zeta === 1) {
199
+ // Critically damped
200
+ return 1 - (1 + omega * t) * Math.exp(-omega * t);
201
+ }
202
+ // Overdamped (zeta > 1)
203
+ const s = Math.sqrt(zeta * zeta - 1);
204
+ const r1 = -omega * (zeta + s);
205
+ const r2 = -omega * (zeta - s);
206
+ const c1 = r2 / (r2 - r1);
207
+ const c2 = -r1 / (r2 - r1);
208
+ return 1 - (c1 * Math.exp(r1 * t) + c2 * Math.exp(r2 * t));
209
+ }
210
+
211
+ /**
212
+ * Find the natural settling duration of a spring (in the spring's own time domain).
213
+ * Returns the smallest `t` where `|springRaw(t')|` is within epsilon of 1 for all `t' >= t`.
214
+ * Scans backward at 0.5ms resolution up to 2 seconds.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * const duration = Easing.springNaturalDuration({ stiffness: 200, damping: 15 });
219
+ * // duration is ~0.4 (seconds in spring time domain)
220
+ * ```
221
+ */
222
+ function _validateSpringConfig(config: SpringConfigShape): void {
223
+ if (config.stiffness <= 0 || !Number.isFinite(config.stiffness)) {
224
+ throw new RangeError(`Easing.spring: stiffness must be a positive finite number, got ${config.stiffness}`);
225
+ }
226
+ if (config.damping < 0 || !Number.isFinite(config.damping)) {
227
+ throw new RangeError(`Easing.spring: damping must be a non-negative finite number, got ${config.damping}`);
228
+ }
229
+ if (config.mass !== undefined && (config.mass <= 0 || !Number.isFinite(config.mass))) {
230
+ throw new RangeError(`Easing.spring: mass must be a positive finite number, got ${config.mass}`);
231
+ }
232
+ }
233
+
234
+ function springNaturalDuration(config: SpringConfigShape, epsilon = 0.001): number {
235
+ _validateSpringConfig(config);
236
+ const { stiffness, damping, mass = 1 } = config;
237
+ const omega = Math.sqrt(stiffness / mass);
238
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
239
+ // Scan steps (at 1ms resolution) to find spring settling point
240
+ const steps = EASING_SPRING_STEPS;
241
+ for (let ms = steps; ms > 0; ms--) {
242
+ const t = ms / steps;
243
+ if (Math.abs(springRaw(t, omega, zeta) - 1) >= epsilon) return t + 0.025;
244
+ }
245
+ // Fallback 300ms duration if spring never settles within scan window
246
+ return 0.3;
247
+ }
248
+
249
+ /**
250
+ * Creates a physics-based spring easing function.
251
+ * Maps t in [0,1] through a damped spring simulation.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const bounce = Easing.spring({ stiffness: 200, damping: 10 });
256
+ * bounce(0.0); // 0.0
257
+ * bounce(0.5); // overshoots past 1.0 before settling
258
+ * bounce(1.0); // 1.0
259
+ * ```
260
+ */
261
+ function spring(config: SpringConfigShape): EasingFnShape {
262
+ _validateSpringConfig(config);
263
+ const { stiffness, damping, mass = 1 } = config;
264
+ const omega = Math.sqrt(stiffness / mass);
265
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
266
+ const naturalDuration = springNaturalDuration(config);
267
+
268
+ return (t: number): number => {
269
+ if (t <= 0) return 0;
270
+ if (t >= 1) return 1;
271
+ const scaledT = t * naturalDuration;
272
+ return springRaw(scaledT, omega, zeta);
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Sample a spring easing at `sampleCount` evenly-spaced points and
278
+ * return a CSS `linear()` timing function string for off-main-thread animation.
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * const css = Easing.springToLinearCSS({ stiffness: 200, damping: 15 }, 16);
283
+ * // css is 'linear(0.0000, 0.1234, ..., 1.0000)' with 17 sample points
284
+ * element.style.transitionTimingFunction = css;
285
+ * ```
286
+ */
287
+ function springToLinearCSS(config: SpringConfigShape, sampleCount = 32): string {
288
+ const fn = spring(config);
289
+ const points: string[] = [];
290
+ for (let i = 0; i <= sampleCount; i++) {
291
+ points.push(fn(i / sampleCount).toFixed(4));
292
+ }
293
+ return `linear(${points.join(', ')})`;
294
+ }
295
+
296
+ /**
297
+ * Easing -- pure math easing functions mapping t in [0,1] to value in [0,1].
298
+ * Includes standard CSS easings, cubic-bezier, spring physics, and CSS linear() export.
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * const t = 0.5;
303
+ * Easing.easeOutCubic(t); // 0.875
304
+ * Easing.linear(t); // 0.5
305
+ * const spring = Easing.spring({ stiffness: 200, damping: 15 });
306
+ * spring(t); // spring-physics interpolated value
307
+ * const css = Easing.springToLinearCSS({ stiffness: 200, damping: 15 });
308
+ * ```
309
+ */
310
+ export const Easing: EasingFns = {
311
+ linear,
312
+ easeInCubic,
313
+ easeOutCubic,
314
+ easeInOutCubic,
315
+ easeOutExpo,
316
+ easeOutBack,
317
+ easeOutElastic,
318
+ easeOutBounce,
319
+ ease,
320
+ easeIn,
321
+ easeOut,
322
+ easeInOut,
323
+ spring,
324
+ cubicBezier,
325
+ springToLinearCSS,
326
+ springNaturalDuration,
327
+ };
328
+
329
+ export declare namespace Easing {
330
+ /** Signature of an easing function: `(t: [0..1]) => [0..1]`. */
331
+ export type Fn = EasingFnShape;
332
+ /** Spring parameters: stiffness, damping, mass. */
333
+ export type Config = SpringConfigShape;
334
+ }
package/src/ecs.ts ADDED
@@ -0,0 +1,382 @@
1
+ /**
2
+ * ECS -- Entity, Part, System, World.
3
+ *
4
+ * Composition over inheritance. Entities are bags of parts,
5
+ * systems operate on entities matching part queries.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { Scope, Schema } from 'effect';
11
+ import { Effect, Ref } from 'effect';
12
+
13
+ /** Nominal-typed identifier for an ECS entity — a branded string minted via the {@link EntityId} helper. */
14
+ export type EntityId = string & { readonly _brand: 'EntityId' };
15
+
16
+ /** Brand an arbitrary string as an `EntityId`. Sanctioned single-site cast. */
17
+ export const EntityId = (value: string): EntityId => value as EntityId;
18
+
19
+ import { fnv1a } from './fnv.js';
20
+
21
+ interface EntityShape {
22
+ readonly id: EntityId;
23
+ readonly components: ReadonlyMap<string, unknown>;
24
+ }
25
+
26
+ interface PartShape<T = unknown> {
27
+ readonly name: string;
28
+ readonly schema: Schema.Schema<T>;
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Dense Component Storage -- Float64Array-backed, zero-allocation iteration
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const DENSE_SENTINEL = -Infinity;
36
+
37
+ interface DenseStoreShape {
38
+ readonly name: string;
39
+ readonly capacity: number;
40
+ readonly _dense: true;
41
+ /** Entity ID `->` index in the data array */
42
+ readonly entityToIndex: Map<EntityId, number>;
43
+ /** Index `->` Entity ID (for iteration) */
44
+ readonly indexToEntity: EntityId[];
45
+ /** The raw Float64Array backing store */
46
+ readonly data: Float64Array;
47
+ /** Current number of live entries */
48
+ count: number;
49
+
50
+ get(entityId: EntityId): number | undefined;
51
+ set(entityId: EntityId, value: number): void;
52
+ has(entityId: EntityId): boolean;
53
+ delete(entityId: EntityId): boolean;
54
+ reset(): void;
55
+ /** Direct typed array view for tight-loop iteration (length = count) */
56
+ view(): Float64Array;
57
+ /** All entity IDs with values, in dense order */
58
+ entities(): readonly EntityId[];
59
+ }
60
+
61
+ function _makeDenseStore(name: string, capacity: number): DenseStoreShape {
62
+ const entityToIndex = new Map<EntityId, number>();
63
+ const indexToEntity: EntityId[] = [];
64
+ const data = new Float64Array(capacity);
65
+ data.fill(DENSE_SENTINEL);
66
+
67
+ const store: DenseStoreShape = {
68
+ name,
69
+ capacity,
70
+ _dense: true,
71
+ entityToIndex,
72
+ indexToEntity,
73
+ data,
74
+ count: 0,
75
+
76
+ get(entityId: EntityId): number | undefined {
77
+ const idx = entityToIndex.get(entityId);
78
+ if (idx === undefined) return undefined;
79
+ return data[idx];
80
+ },
81
+
82
+ set(entityId: EntityId, value: number): void {
83
+ let idx = entityToIndex.get(entityId);
84
+ if (idx !== undefined) {
85
+ data[idx] = value;
86
+ return;
87
+ }
88
+ if (store.count >= capacity) {
89
+ throw new RangeError(`Dense store "${name}" at capacity (${capacity}). Cannot add entity ${entityId}.`);
90
+ }
91
+ idx = store.count;
92
+ entityToIndex.set(entityId, idx);
93
+ indexToEntity[idx] = entityId;
94
+ data[idx] = value;
95
+ store.count++;
96
+ },
97
+
98
+ has(entityId: EntityId): boolean {
99
+ return entityToIndex.has(entityId);
100
+ },
101
+
102
+ delete(entityId: EntityId): boolean {
103
+ const idx = entityToIndex.get(entityId);
104
+ if (idx === undefined) return false;
105
+
106
+ const lastIdx = store.count - 1;
107
+ if (idx !== lastIdx) {
108
+ // Swap-remove: move last element into the vacated slot
109
+ const lastEntity = indexToEntity[lastIdx]!;
110
+ data[idx] = data[lastIdx]!;
111
+ indexToEntity[idx] = lastEntity;
112
+ entityToIndex.set(lastEntity, idx);
113
+ }
114
+ data[lastIdx] = DENSE_SENTINEL;
115
+ indexToEntity.length = lastIdx;
116
+ entityToIndex.delete(entityId);
117
+ store.count--;
118
+ return true;
119
+ },
120
+
121
+ reset(): void {
122
+ entityToIndex.clear();
123
+ indexToEntity.length = 0;
124
+ data.fill(DENSE_SENTINEL);
125
+ store.count = 0;
126
+ },
127
+
128
+ view(): Float64Array {
129
+ return data.subarray(0, store.count);
130
+ },
131
+
132
+ entities(): readonly EntityId[] {
133
+ return indexToEntity;
134
+ },
135
+ };
136
+
137
+ return store;
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Dense System -- operates directly on Float64Array data
142
+ // ---------------------------------------------------------------------------
143
+
144
+ interface DenseSystemShape {
145
+ readonly name: string;
146
+ readonly query: readonly string[];
147
+ readonly _denseSystem: true;
148
+ /**
149
+ * Execute receives dense stores keyed by component name.
150
+ * Systems iterate the typed arrays directly -- zero allocation per tick.
151
+ */
152
+ execute(stores: ReadonlyMap<string, DenseStoreShape>): Effect.Effect<void>;
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // System types
157
+ // ---------------------------------------------------------------------------
158
+
159
+ interface SystemShape {
160
+ readonly name: string;
161
+ readonly query: readonly string[];
162
+ readonly _denseSystem?: undefined;
163
+ /** Second argument is the world — use it to write computed output components back. */
164
+ execute(entities: readonly EntityShape[], world?: WorldShape): Effect.Effect<void>;
165
+ }
166
+
167
+ type AnySystemShape = SystemShape | DenseSystemShape;
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // World
171
+ // ---------------------------------------------------------------------------
172
+
173
+ interface WorldShape {
174
+ spawn(components?: Record<string, unknown>): Effect.Effect<EntityId>;
175
+ despawn(id: EntityId): Effect.Effect<void>;
176
+ addComponent<T>(id: EntityId, component: PartShape<T>, value: T): Effect.Effect<void>;
177
+ /** Schema-free component write — used by systems to persist computed output values. */
178
+ setComponent(id: EntityId, name: string, value: unknown): Effect.Effect<void>;
179
+ removeComponent(id: EntityId, name: string): Effect.Effect<void>;
180
+ query(...componentNames: string[]): Effect.Effect<readonly EntityShape[]>;
181
+ addSystem(system: AnySystemShape): Effect.Effect<void>;
182
+ tick(): Effect.Effect<void>;
183
+ /** Register a dense store so the world can wire it into dense systems */
184
+ addDenseStore(store: DenseStoreShape): Effect.Effect<void>;
185
+ }
186
+
187
+ function _makeWorld(): Effect.Effect<WorldShape, never, Scope.Scope> {
188
+ return Effect.gen(function* () {
189
+ const entitiesRef = yield* Ref.make<Map<EntityId, Map<string, unknown>>>(new Map());
190
+ const systemsRef = yield* Ref.make<AnySystemShape[]>([]);
191
+ const denseStoresRef = yield* Ref.make<Map<string, DenseStoreShape>>(new Map());
192
+ let nextEntitySeq = 0;
193
+
194
+ const world: WorldShape = {
195
+ spawn(components?: Record<string, unknown>): Effect.Effect<EntityId> {
196
+ return Effect.gen(function* () {
197
+ const seq = nextEntitySeq++;
198
+ const id = EntityId(`entity-${seq}:${fnv1a(JSON.stringify(components ?? {}))}`);
199
+ const componentMap = new Map<string, unknown>();
200
+ if (components) {
201
+ for (const [name, value] of Object.entries(components)) {
202
+ componentMap.set(name, value);
203
+ }
204
+ }
205
+ yield* Ref.update(entitiesRef, (m) => {
206
+ const next = new Map(m);
207
+ next.set(id, componentMap);
208
+ return next;
209
+ });
210
+ return id;
211
+ });
212
+ },
213
+
214
+ despawn(id: EntityId): Effect.Effect<void> {
215
+ return Effect.gen(function* () {
216
+ // Remove from entity map
217
+ yield* Ref.update(entitiesRef, (m) => {
218
+ const next = new Map(m);
219
+ next.delete(id);
220
+ return next;
221
+ });
222
+ // Remove from all dense stores
223
+ const denseStores = yield* Ref.get(denseStoresRef);
224
+ for (const store of denseStores.values()) {
225
+ store.delete(id);
226
+ }
227
+ });
228
+ },
229
+
230
+ addComponent<T>(id: EntityId, component: PartShape<T>, value: T): Effect.Effect<void> {
231
+ return Ref.update(entitiesRef, (m) => {
232
+ const next = new Map(m);
233
+ const entity = next.get(id);
234
+ if (entity) {
235
+ const updated = new Map(entity);
236
+ updated.set(component.name, value);
237
+ next.set(id, updated);
238
+ }
239
+ return next;
240
+ });
241
+ },
242
+
243
+ setComponent(id: EntityId, name: string, value: unknown): Effect.Effect<void> {
244
+ return Ref.update(entitiesRef, (m) => {
245
+ const next = new Map(m);
246
+ const entity = next.get(id);
247
+ if (entity) {
248
+ const updated = new Map(entity);
249
+ updated.set(name, value);
250
+ next.set(id, updated);
251
+ }
252
+ return next;
253
+ });
254
+ },
255
+
256
+ removeComponent(id: EntityId, name: string): Effect.Effect<void> {
257
+ return Ref.update(entitiesRef, (m) => {
258
+ const next = new Map(m);
259
+ const entity = next.get(id);
260
+ if (entity) {
261
+ const updated = new Map(entity);
262
+ updated.delete(name);
263
+ next.set(id, updated);
264
+ }
265
+ return next;
266
+ });
267
+ },
268
+
269
+ query(...componentNames: string[]): Effect.Effect<readonly EntityShape[]> {
270
+ return Effect.gen(function* () {
271
+ const entities = yield* Ref.get(entitiesRef);
272
+ const results: EntityShape[] = [];
273
+
274
+ for (const [id, components] of entities) {
275
+ const hasAll = componentNames.every((name) => components.has(name));
276
+ if (hasAll) {
277
+ const componentsCopy = new Map(components) as ReadonlyMap<string, unknown>;
278
+ // Spread component values as direct properties so systems can access
279
+ // computed output fields (e.g. `_opacity`, `_phase`, `_blend`) directly.
280
+ const entity = Object.assign(
281
+ { id, components: componentsCopy },
282
+ Object.fromEntries(componentsCopy),
283
+ ) as EntityShape;
284
+ results.push(entity);
285
+ }
286
+ }
287
+
288
+ return results;
289
+ });
290
+ },
291
+
292
+ addSystem(system: AnySystemShape): Effect.Effect<void> {
293
+ return Ref.update(systemsRef, (systems) => [...systems, system]);
294
+ },
295
+
296
+ addDenseStore(store: DenseStoreShape): Effect.Effect<void> {
297
+ return Ref.update(denseStoresRef, (m) => {
298
+ const next = new Map(m);
299
+ next.set(store.name, store);
300
+ return next;
301
+ });
302
+ },
303
+
304
+ tick(): Effect.Effect<void> {
305
+ return Effect.gen(function* () {
306
+ const systems = yield* Ref.get(systemsRef);
307
+ const denseStores = yield* Ref.get(denseStoresRef);
308
+
309
+ for (const system of systems) {
310
+ if (isDenseSystem(system)) {
311
+ // Dense path: collect the stores this system queries
312
+ const queriedStores = new Map<string, DenseStoreShape>();
313
+ for (const name of system.query) {
314
+ const store = denseStores.get(name);
315
+ if (store) queriedStores.set(name, store);
316
+ }
317
+ // Only execute if all queried stores exist
318
+ if (queriedStores.size === system.query.length) {
319
+ yield* system.execute(queriedStores);
320
+ }
321
+ } else {
322
+ // Regular path: entity-component query
323
+ const matched = yield* world.query(...system.query);
324
+ yield* system.execute(matched, world);
325
+ }
326
+ }
327
+ });
328
+ },
329
+ };
330
+
331
+ return world;
332
+ });
333
+ }
334
+
335
+ function isDenseSystem(system: AnySystemShape): system is DenseSystemShape {
336
+ return '_denseSystem' in system && system._denseSystem === true;
337
+ }
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Part namespace -- factories and types
341
+ // ---------------------------------------------------------------------------
342
+
343
+ function _makeDensePart(name: string, capacity: number): DenseStoreShape {
344
+ return _makeDenseStore(name, capacity);
345
+ }
346
+
347
+ /**
348
+ * Part namespace — factories for ECS component stores.
349
+ *
350
+ * Currently exposes the dense `Float64Array`-backed store used for hot-path
351
+ * numeric state; sparse/object-valued parts are registered ad-hoc via
352
+ * {@link World}.`addComponent`.
353
+ */
354
+ export const Part = {
355
+ /** Allocate a dense component store with fixed capacity. */
356
+ dense: _makeDensePart,
357
+ } as { dense: (name: string, capacity: number) => DenseStoreShape } & Record<string, never>;
358
+
359
+ /** World namespace — construct the ECS world that ticks systems over entities. */
360
+ export const World = {
361
+ /** Scoped Effect that produces a fresh ECS {@link World.Shape}. */
362
+ make: _makeWorld,
363
+ };
364
+
365
+ export declare namespace Part {
366
+ /** Structural shape of a typed component definition (`name` + schema). */
367
+ export type Shape<T = unknown> = PartShape<T>;
368
+ /** Alias for the dense `Float64Array`-backed store. */
369
+ export type Dense = DenseStoreShape;
370
+ }
371
+
372
+ export declare namespace World {
373
+ /** Structural shape of an ECS world: spawn/despawn, components, queries, systems, tick. */
374
+ export type Shape = WorldShape;
375
+ }
376
+
377
+ export type {
378
+ EntityShape as Entity,
379
+ SystemShape as System,
380
+ DenseSystemShape as DenseSystem,
381
+ DenseStoreShape as DenseStore,
382
+ };