@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
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Compositor -- merge multiple quantizers into composite state.
3
+ *
4
+ * The compositor aggregates discrete + blended state from all
5
+ * active quantizers into a single CompositeState, producing
6
+ * typed output channels (css, glsl, aria).
7
+ *
8
+ * Wired: DirtyFlags (selective recomputation), CompositorStatePool
9
+ * (zero-allocation), FrameBudget (priority scheduling), microtask batching,
10
+ * and RuntimeCoordinator (Plan + ECS-backed runtime bookkeeping).
11
+ *
12
+ * Hot path (computeStateSync) is plain JS — no Effect overhead.
13
+ * Effect is used only for resource lifecycle (create/scope) and
14
+ * reactive stream (SubscriptionRef.changes).
15
+ *
16
+ * @module
17
+ */
18
+
19
+ import type { Scope, Stream } from 'effect';
20
+ import { Effect, SubscriptionRef } from 'effect';
21
+ import type { Boundary } from './boundary.js';
22
+ import { COMPOSITOR_POOL_CAP, DIRTY_FLAGS_MAX } from './defaults.js';
23
+ import { CompositorStatePool, accessCompositeState } from './compositor-pool.js';
24
+ import { DirtyFlags } from './dirty.js';
25
+ import type { FrameBudget } from './frame-budget.js';
26
+ import type { Quantizer } from './quantizer-types.js';
27
+ import { RuntimeCoordinator } from './runtime-coordinator.js';
28
+ import { SpeculativeEvaluator } from './speculative.js';
29
+
30
+ /**
31
+ * Snapshot of the compositor's output per tick: discrete state names for each
32
+ * quantizer, their blend-weight vectors, and the compiled per-target output
33
+ * maps (`css` / `glsl` / `aria`).
34
+ */
35
+ export interface CompositeState {
36
+ readonly discrete: Record<string, string>;
37
+ readonly blend: Record<string, Record<string, number>>;
38
+ readonly outputs: {
39
+ readonly css: Record<string, number | string>;
40
+ readonly glsl: Record<string, number>;
41
+ readonly aria: Record<string, string>;
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Options accepted by `Compositor.create`: pool capacity, optional
47
+ * frame-budget gating, and whether to enable speculative pre-evaluation.
48
+ */
49
+ export interface CompositorConfig {
50
+ readonly poolCapacity?: number;
51
+ readonly frameBudget?: FrameBudget.Shape;
52
+ readonly speculative?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Widen a Quantizer's boundary parameter to Boundary.Shape for storage in
57
+ * a heterogeneous registry. Safe because Quantizer<B> is covariant in B
58
+ * (B only appears in return positions on Quantizer).
59
+ */
60
+ function widenQuantizer<B extends Boundary.Shape>(q: Quantizer<B>): Quantizer<Boundary.Shape> {
61
+ return q as unknown as Quantizer<Boundary.Shape>;
62
+ }
63
+
64
+ interface CompositorShape {
65
+ add<B extends Boundary.Shape>(name: string, quantizer: Quantizer<B>): Effect.Effect<void>;
66
+ remove(name: string): Effect.Effect<void>;
67
+ compute(): Effect.Effect<CompositeState>;
68
+ setBlendWeights(name: string, weights: Record<string, number>): Effect.Effect<void>;
69
+ evaluateSpeculative(name: string, value: number, velocity?: number): void;
70
+ scheduleBatch(): void;
71
+ readonly changes: Stream.Stream<CompositeState>;
72
+ readonly runtime: RuntimeCoordinator.Shape;
73
+ }
74
+
75
+ interface CompositorFactory {
76
+ create(config?: CompositorConfig): Effect.Effect<CompositorShape, never, Scope.Scope>;
77
+ }
78
+
79
+ interface QuantizerMeta {
80
+ readonly cssKey: string;
81
+ readonly glslKey: string;
82
+ readonly ariaKey: string;
83
+ readonly oneHotWeights: Readonly<Record<string, Readonly<Record<string, number>>>>;
84
+ }
85
+
86
+ function emptyCompositeState(): CompositeState {
87
+ return {
88
+ discrete: {},
89
+ blend: {},
90
+ outputs: { css: {}, glsl: {}, aria: {} },
91
+ };
92
+ }
93
+
94
+ const MAX_DIRTY_KEYS = DIRTY_FLAGS_MAX;
95
+
96
+ /**
97
+ * Compositor — the live merge point for every attached {@link Quantizer}.
98
+ *
99
+ * `Compositor.create` hands back a scoped Effect that, when run inside a
100
+ * `Scope`, produces a compositor bound to a {@link RuntimeCoordinator}. Adding
101
+ * quantizers, marking dirty flags, and emitting CSS/GLSL/ARIA outputs all flow
102
+ * through the zero-allocation hot path backed by {@link CompositorStatePool}.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { Effect } from 'effect';
107
+ * import { Compositor } from '@czap/core';
108
+ *
109
+ * const program = Effect.scoped(Effect.gen(function* () {
110
+ * const compositor = yield* Compositor.create({ poolCapacity: 64, speculative: true });
111
+ * yield* compositor.add('viewport', viewportQuantizer);
112
+ * const state = yield* compositor.compute();
113
+ * // state.discrete.viewport === 'tablet'
114
+ * // state.outputs.css['--czap-viewport'] === 'tablet'
115
+ * }));
116
+ * ```
117
+ */
118
+ export const Compositor: CompositorFactory = {
119
+ /** Build a scoped compositor bound to a fresh {@link RuntimeCoordinator}. */
120
+ create(config?: CompositorConfig): Effect.Effect<CompositorShape, never, Scope.Scope> {
121
+ return Effect.gen(function* () {
122
+ const stateRef = yield* SubscriptionRef.make<CompositeState>(emptyCompositeState());
123
+
124
+ const qMap = new Map<string, Quantizer<Boundary.Shape>>();
125
+ const metaMap = new Map<string, QuantizerMeta>();
126
+ const overrides = new Map<string, Record<string, number>>();
127
+
128
+ const pool = CompositorStatePool.make(config?.poolCapacity ?? COMPOSITOR_POOL_CAP);
129
+ const frameBudget = config?.frameBudget;
130
+ const useSpeculative = config?.speculative ?? false;
131
+ const runtime = RuntimeCoordinator.create({
132
+ capacity: Math.max(config?.poolCapacity ?? COMPOSITOR_POOL_CAP, MAX_DIRTY_KEYS + 8),
133
+ name: 'czap-compositor-runtime',
134
+ });
135
+
136
+ const speculativeEvaluators = new Map<string, SpeculativeEvaluator.Shape<Boundary.Shape>>();
137
+ const prefetchedStates = new Map<string, string>();
138
+
139
+ let nameList: string[] = [];
140
+ let dirty: DirtyFlags.Shape<string> | null = null;
141
+ let recomputeAll = false;
142
+ let previousState: CompositeState = emptyCompositeState();
143
+ let priorPreviousState: CompositeState | null = null;
144
+ let batchScheduled = false;
145
+ function rebuildDirtyFlags(): void {
146
+ if (nameList.length > MAX_DIRTY_KEYS) {
147
+ dirty = null;
148
+ recomputeAll = true;
149
+ return;
150
+ }
151
+
152
+ dirty = DirtyFlags.make(nameList);
153
+ recomputeAll = false;
154
+ for (const name of nameList) {
155
+ dirty.mark(name);
156
+ runtime.markDirty(name);
157
+ }
158
+ }
159
+
160
+ function computeStateSync(): CompositeState {
161
+ const dirtyFlags = dirty;
162
+ const dirtyNames = recomputeAll || dirtyFlags === null ? Array.from(qMap.keys()) : dirtyFlags.getDirty();
163
+ const shouldRecompute =
164
+ recomputeAll || dirtyFlags === null ? () => true : (name: string) => dirtyFlags.isDirty(name);
165
+
166
+ const state = pool.acquire();
167
+ const { discrete, blend, css, glsl, aria } = accessCompositeState(state);
168
+
169
+ for (const [name] of qMap) {
170
+ if (shouldRecompute(name)) {
171
+ continue;
172
+ }
173
+
174
+ const meta = metaMap.get(name)!;
175
+ const previousDiscrete = previousState.discrete[name];
176
+ if (previousDiscrete !== undefined) {
177
+ discrete[name] = previousDiscrete;
178
+ }
179
+
180
+ const previousBlend = previousState.blend[name]!;
181
+ blend[name] = previousBlend;
182
+
183
+ const previousCss = previousState.outputs.css[meta.cssKey];
184
+ if (previousCss !== undefined) {
185
+ css[meta.cssKey] = previousCss;
186
+ }
187
+
188
+ const previousGlsl = previousState.outputs.glsl[meta.glslKey];
189
+ if (previousGlsl !== undefined) {
190
+ glsl[meta.glslKey] = previousGlsl;
191
+ }
192
+
193
+ const previousAria = previousState.outputs.aria[meta.ariaKey];
194
+ if (previousAria !== undefined) {
195
+ aria[meta.ariaKey] = previousAria;
196
+ }
197
+ }
198
+
199
+ for (const phase of runtime.phases) {
200
+ switch (phase) {
201
+ case 'compute-discrete':
202
+ for (const name of dirtyNames) {
203
+ const quantizer = qMap.get(name)!;
204
+
205
+ const prefetched = prefetchedStates.get(name);
206
+ const stateStr =
207
+ prefetched ?? (quantizer.stateSync ? quantizer.stateSync() : Effect.runSync(quantizer.state));
208
+ discrete[name] = stateStr;
209
+ runtime.setState(name, stateStr);
210
+ prefetchedStates.delete(name);
211
+ }
212
+ break;
213
+
214
+ case 'compute-blend':
215
+ for (const name of dirtyNames) {
216
+ const meta = metaMap.get(name)!;
217
+
218
+ const override = overrides.get(name);
219
+ if (override !== undefined) {
220
+ blend[name] = override;
221
+ continue;
222
+ }
223
+
224
+ blend[name] = meta.oneHotWeights[discrete[name] ?? ''] ?? {};
225
+ }
226
+ break;
227
+
228
+ case 'emit-css':
229
+ for (const name of dirtyNames) {
230
+ const meta = metaMap.get(name);
231
+ const stateStr = discrete[name];
232
+ if (stateStr !== undefined && meta) {
233
+ css[meta.cssKey] = stateStr;
234
+ }
235
+ }
236
+ break;
237
+
238
+ case 'emit-glsl':
239
+ if (!frameBudget || frameBudget.canRun('high')) {
240
+ for (const name of dirtyNames) {
241
+ const meta = metaMap.get(name)!;
242
+ glsl[meta.glslKey] = runtime.getStateIndex(name);
243
+ }
244
+ }
245
+ break;
246
+
247
+ case 'emit-aria':
248
+ if (!frameBudget || frameBudget.canRun('low')) {
249
+ for (const name of dirtyNames) {
250
+ const meta = metaMap.get(name)!;
251
+ const stateStr = discrete[name];
252
+ if (stateStr !== undefined) {
253
+ aria[meta.ariaKey] = stateStr;
254
+ }
255
+ }
256
+ }
257
+ break;
258
+ }
259
+ }
260
+
261
+ if (dirty) {
262
+ dirty.clearAll();
263
+ }
264
+
265
+ // Two-slot rotation: the most-recently-published state stays readable for one
266
+ // more tick (so consumers who hold a reference returned from compute() see live
267
+ // data until the *next-next* publish). Without this rotation, every tick takes
268
+ // the overflow path in CompositorStatePool.acquire and the pool grows unboundedly.
269
+ const releasable = priorPreviousState;
270
+ priorPreviousState = previousState;
271
+ previousState = state;
272
+ Effect.runSync(SubscriptionRef.set(stateRef, state));
273
+ if (releasable && releasable !== state) pool.release(releasable);
274
+ return state;
275
+ }
276
+
277
+ const compositor: CompositorShape = {
278
+ add: <B extends Boundary.Shape>(name: string, quantizer: Quantizer<B>) =>
279
+ Effect.sync(() => {
280
+ // Quantizer<B> is covariant in B (B only appears in return types), so widening
281
+ // to Quantizer<Boundary.Shape> is sound; wrap in a named helper to document.
282
+ qMap.set(name, widenQuantizer(quantizer));
283
+ metaMap.set(name, {
284
+ cssKey: `--czap-${name}`,
285
+ glslKey: `u_${name}`,
286
+ ariaKey: `data-czap-${name}`,
287
+ oneHotWeights: Object.fromEntries(
288
+ quantizer.boundary.states.map((activeState) => [
289
+ activeState as string,
290
+ Object.freeze(
291
+ Object.fromEntries(
292
+ quantizer.boundary.states.map((stateName) => [
293
+ stateName as string,
294
+ stateName === activeState ? 1 : 0,
295
+ ]),
296
+ ),
297
+ ),
298
+ ]),
299
+ ),
300
+ });
301
+ runtime.registerQuantizer(name, quantizer.boundary.states as readonly string[]);
302
+ runtime.markDirty(name);
303
+
304
+ if (!nameList.includes(name)) {
305
+ nameList.push(name);
306
+ rebuildDirtyFlags();
307
+ }
308
+ if (dirty) {
309
+ dirty.mark(name);
310
+ }
311
+
312
+ if (useSpeculative) {
313
+ speculativeEvaluators.set(name, SpeculativeEvaluator.make(quantizer.boundary));
314
+ }
315
+
316
+ computeStateSync();
317
+ }),
318
+
319
+ remove: (name: string) =>
320
+ Effect.sync(() => {
321
+ qMap.delete(name);
322
+ metaMap.delete(name);
323
+ nameList = nameList.filter((entry) => entry !== name);
324
+ rebuildDirtyFlags();
325
+ runtime.removeQuantizer(name);
326
+ speculativeEvaluators.delete(name);
327
+ prefetchedStates.delete(name);
328
+ computeStateSync();
329
+ }),
330
+
331
+ compute: () => Effect.sync(() => computeStateSync()),
332
+
333
+ setBlendWeights: (name: string, weights: Record<string, number>) =>
334
+ Effect.sync(() => {
335
+ overrides.set(name, weights);
336
+ if (dirty) {
337
+ dirty.mark(name);
338
+ }
339
+ runtime.markDirty(name);
340
+ }),
341
+
342
+ evaluateSpeculative(name: string, value: number, velocity?: number): void {
343
+ const speculative = speculativeEvaluators.get(name);
344
+ if (!speculative) {
345
+ return;
346
+ }
347
+
348
+ const result = speculative.evaluate(value, velocity);
349
+ if (result.prefetched && result.confidence > 0.7) {
350
+ prefetchedStates.set(name, result.prefetched as string);
351
+ runtime.markDirty(name);
352
+ if (dirty) {
353
+ dirty.mark(name);
354
+ }
355
+ return;
356
+ }
357
+
358
+ prefetchedStates.delete(name);
359
+ },
360
+
361
+ scheduleBatch(): void {
362
+ if (batchScheduled) {
363
+ return;
364
+ }
365
+
366
+ batchScheduled = true;
367
+ queueMicrotask(() => {
368
+ batchScheduled = false;
369
+ computeStateSync();
370
+ });
371
+ },
372
+
373
+ changes: SubscriptionRef.changes(stateRef),
374
+ runtime,
375
+ };
376
+
377
+ return compositor;
378
+ });
379
+ },
380
+ };
381
+
382
+ export declare namespace Compositor {
383
+ /** Structural shape of a live compositor instance. */
384
+ export type Shape = CompositorShape;
385
+ /** Alias for {@link CompositorConfig}. */
386
+ export type Config = CompositorConfig;
387
+ }
package/src/config.ts ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Config -- unified project configuration hub.
3
+ *
4
+ * Config.make() produces a frozen, FNV-1a content-addressed Config.Shape.
5
+ * Projection functions are pure — no side effects, no I/O.
6
+ */
7
+
8
+ import type { ContentAddress } from './brands.js';
9
+ import type { Boundary } from './boundary.js';
10
+ import type { Token } from './token.js';
11
+ import type { Theme } from './theme.js';
12
+ import type { Style } from './style.js';
13
+ import { fnv1a } from './fnv.js';
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Public types
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+
19
+ /** Top-level discriminator for czap primitives: which bucket a declaration belongs to. */
20
+ export type PrimitiveKind = 'boundary' | 'token' | 'theme' | 'style';
21
+
22
+ /**
23
+ * Vite-plugin slice of a czap {@link Config.Shape}: source directories per
24
+ * primitive kind, HMR opt-in, environment targeting, and optional WASM hints.
25
+ */
26
+ export interface PluginConfig {
27
+ readonly dirs?: Partial<Record<PrimitiveKind, string>>;
28
+ readonly hmr?: boolean;
29
+ readonly environments?: readonly ('browser' | 'server' | 'shader')[];
30
+ readonly wasm?: { readonly enabled?: boolean; readonly path?: string };
31
+ }
32
+
33
+ /** Astro-integration slice of a czap {@link Config.Shape}. */
34
+ export interface AstroConfig {
35
+ readonly satellite?: boolean;
36
+ readonly edgeRuntime?: boolean;
37
+ }
38
+
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+ // Config namespace + value object (declaration merging — same pattern as Boundary)
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Config namespace — the single hub that every czap adapter (Vite, Astro, test
45
+ * runners, edge runtime) projects from. {@link Config.make} produces a frozen,
46
+ * FNV-1a content-addressed {@link Config.Shape}; every projection function
47
+ * (`toViteConfig`, `toAstroConfig`, `toTestAliases`) is pure.
48
+ */
49
+ export const Config = {
50
+ /** Build a frozen, content-addressed {@link Config.Shape} from raw input. */
51
+ make(input: Config.Input): Config.Shape {
52
+ // Sort named collection keys so insertion order doesn't affect the hash.
53
+ const sortKeys = <V>(obj: Record<string, V>): Record<string, V> =>
54
+ Object.fromEntries(Object.entries(obj).sort());
55
+ const canonical = JSON.stringify({
56
+ boundaries: sortKeys(input.boundaries ?? {}),
57
+ tokens: sortKeys(input.tokens ?? {}),
58
+ themes: sortKeys(input.themes ?? {}),
59
+ styles: sortKeys(input.styles ?? {}),
60
+ vite: input.vite,
61
+ astro: input.astro,
62
+ });
63
+ const id = fnv1a(canonical);
64
+ return Object.freeze({
65
+ _tag: 'ConfigDef' as const,
66
+ id,
67
+ boundaries: input.boundaries ?? {},
68
+ tokens: input.tokens ?? {},
69
+ themes: input.themes ?? {},
70
+ styles: input.styles ?? {},
71
+ vite: input.vite,
72
+ astro: input.astro,
73
+ });
74
+ },
75
+
76
+ /** Project the Vite-plugin slice of a config for `@czap/vite`. */
77
+ toViteConfig(cfg: Config.Shape): PluginConfig {
78
+ return {
79
+ ...(cfg.vite?.dirs !== undefined && { dirs: cfg.vite.dirs }),
80
+ ...(cfg.vite?.hmr !== undefined && { hmr: cfg.vite.hmr }),
81
+ ...(cfg.vite?.environments !== undefined && { environments: cfg.vite.environments }),
82
+ ...(cfg.vite?.wasm !== undefined && { wasm: cfg.vite.wasm }),
83
+ };
84
+ },
85
+
86
+ /** Project the Astro-integration slice of a config for `@czap/astro`. */
87
+ toAstroConfig(cfg: Config.Shape): AstroConfig {
88
+ return {
89
+ ...(cfg.astro?.satellite !== undefined && { satellite: cfg.astro.satellite }),
90
+ ...(cfg.astro?.edgeRuntime !== undefined && { edgeRuntime: cfg.astro.edgeRuntime }),
91
+ };
92
+ },
93
+
94
+ /** Materialize the `@czap/*` → source-path alias map used by the vitest runner. */
95
+ toTestAliases(cfg: Config.Shape, repoRoot: string): Record<string, string> {
96
+ void cfg; // cfg reserved for future per-project customisation
97
+ // Use forward-slash join so paths are portable across platforms.
98
+ const r = (sub: string) => `${repoRoot.replace(/\\/g, '/')}/${sub}`;
99
+ // NOTE: longer prefixes MUST come before shorter ones — vitest's alias
100
+ // resolver matches the first prefix in iteration order, so e.g.
101
+ // `@czap/core/testing` would be intercepted by `@czap/core` if listed first.
102
+ return {
103
+ '@czap/core/testing': r('packages/core/src/testing.ts'),
104
+ '@czap/core/harness': r('packages/core/src/harness/index.ts'),
105
+ '@czap/core': r('packages/core/src/index.ts'),
106
+ '@czap/quantizer/testing': r('packages/quantizer/src/testing.ts'),
107
+ '@czap/quantizer': r('packages/quantizer/src/index.ts'),
108
+ '@czap/compiler': r('packages/compiler/src/index.ts'),
109
+ '@czap/web/lite': r('packages/web/src/lite.ts'),
110
+ '@czap/web': r('packages/web/src/index.ts'),
111
+ '@czap/detect': r('packages/detect/src/index.ts'),
112
+ '@czap/vite/html-transform': r('packages/vite/src/html-transform.ts'),
113
+ '@czap/vite': r('packages/vite/src/index.ts'),
114
+ '@czap/astro/runtime': r('packages/astro/src/runtime/index.ts'),
115
+ '@czap/astro': r('packages/astro/src/index.ts'),
116
+ '@czap/remotion': r('packages/remotion/src/index.ts'),
117
+ '@czap/scene/dev': r('packages/scene/src/dev/server.ts'),
118
+ '@czap/scene': r('packages/scene/src/index.ts'),
119
+ '@czap/assets/testing': r('packages/assets/src/testing.ts'),
120
+ '@czap/assets': r('packages/assets/src/index.ts'),
121
+ '@czap/cli': r('packages/cli/src/index.ts'),
122
+ '@czap/mcp-server': r('packages/mcp-server/src/index.ts'),
123
+ '@czap/edge': r('packages/edge/src/index.ts'),
124
+ '@czap/worker': r('packages/worker/src/index.ts'),
125
+ '@czap/_spine': r('packages/_spine'),
126
+ };
127
+ },
128
+ };
129
+
130
+ export declare namespace Config {
131
+ /** Raw user-facing input to {@link Config.make} — every field is optional. */
132
+ interface Input {
133
+ readonly boundaries?: Record<string, Boundary.Shape>;
134
+ readonly tokens?: Record<string, Token.Shape>;
135
+ readonly themes?: Record<string, Theme.Shape>;
136
+ readonly styles?: Record<string, Style.Shape>;
137
+ readonly vite?: Partial<PluginConfig>;
138
+ readonly astro?: Partial<AstroConfig>;
139
+ }
140
+
141
+ /** Frozen, content-addressed result of {@link Config.make}. */
142
+ interface Shape {
143
+ readonly _tag: 'ConfigDef';
144
+ readonly id: ContentAddress;
145
+ readonly boundaries: Record<string, Boundary.Shape>;
146
+ readonly tokens: Record<string, Token.Shape>;
147
+ readonly themes: Record<string, Theme.Shape>;
148
+ readonly styles: Record<string, Style.Shape>;
149
+ readonly vite?: Partial<PluginConfig>;
150
+ readonly astro?: Partial<AstroConfig>;
151
+ }
152
+ }
153
+
154
+ /** Thin alias for {@link Config.make} — matches the `defineConfig(...)` ergonomics other tools use. */
155
+ export function defineConfig(input: Config.Input): Config.Shape {
156
+ return Config.make(input);
157
+ }