@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,186 @@
1
+ /**
2
+ * SpeculativeEvaluator -- threshold proximity prefetching.
3
+ *
4
+ * When a boundary signal is near a threshold, pre-compute the next state.
5
+ * Uses hysteresis dead zone as the prefetch window and linear extrapolation
6
+ * from velocity (last 3-4 signal values) to predict crossings.
7
+ *
8
+ * Wrong prediction cost: ~80ns to recompute (negligible).
9
+ *
10
+ * @module
11
+ */
12
+
13
+ import { Boundary } from './boundary.js';
14
+ import type { StateUnion } from './type-utils.js';
15
+
16
+ // Speculative pre-computation: when signal velocity indicates an imminent threshold crossing, pre-evaluate the predicted next state.
17
+
18
+ interface SpeculativeResult<B extends Boundary.Shape> {
19
+ readonly current: StateUnion<B>;
20
+ readonly prefetched?: StateUnion<B>;
21
+ readonly confidence: number;
22
+ }
23
+
24
+ interface SpeculativeEvaluatorShape<B extends Boundary.Shape> {
25
+ evaluate(value: number, velocity?: number): SpeculativeResult<B>;
26
+ }
27
+
28
+ /**
29
+ * Creates a speculative evaluator for a boundary that prefetches the next state
30
+ * when the signal value is near a threshold and moving toward it.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const boundary = Boundary.make({
35
+ * thresholds: [768, 1024],
36
+ * states: ['mobile', 'tablet', 'desktop'] as const,
37
+ * hysteresis: 20,
38
+ * });
39
+ * const spec = SpeculativeEvaluator.make(boundary);
40
+ * const result = spec.evaluate(760, 2.0); // approaching 768 threshold
41
+ * result.current; // 'mobile'
42
+ * result.prefetched; // 'tablet' (pre-computed)
43
+ * result.confidence; // 0.0-1.0 likelihood of crossing
44
+ * ```
45
+ */
46
+ function _make<B extends Boundary.Shape>(boundary: B): SpeculativeEvaluatorShape<B> {
47
+ const thresholds = boundary.thresholds as readonly number[];
48
+ const hysteresis = boundary.hysteresis ?? 0;
49
+ const prefetchWindow = Math.max(hysteresis, 1); // Use hysteresis as window, min 1
50
+
51
+ // Compute epsilon from boundary scale rather than hardcoded constant
52
+ const minGap =
53
+ thresholds.length >= 2
54
+ ? Math.min(
55
+ ...Array.from(
56
+ { length: thresholds.length - 1 },
57
+ (_, i) => (thresholds[i + 1] as number) - (thresholds[i] as number),
58
+ ),
59
+ )
60
+ : 1;
61
+ const epsilon = Math.min(minGap * 0.001, hysteresis > 0 ? hysteresis * 0.01 : 0.001);
62
+
63
+ // Velocity estimation ring buffer (last 2 values)
64
+ const history: { value: number; time: number }[] = [];
65
+ // 2-sample velocity estimation buffer — gives instant responsiveness
66
+ const HISTORY_SIZE = 2;
67
+
68
+ // Boundary.make guarantees states is non-empty (readonly [string, ...string[]]).
69
+ let previousState: StateUnion<B> = boundary.states[0];
70
+
71
+ // Simple finite difference (not least-squares) — 2-sample gives instant responsiveness
72
+ // for UI prefetch.
73
+ function estimateVelocity(currentValue: number, explicitVelocity?: number): number {
74
+ if (explicitVelocity !== undefined) return explicitVelocity;
75
+ if (history.length < 2) return 0;
76
+
77
+ // Linear regression over recent samples
78
+ const last = history[history.length - 1]!;
79
+ const prev = history[history.length - 2]!;
80
+ const dt = last.time - prev.time;
81
+ if (dt <= 0) return 0;
82
+ return (last.value - prev.value) / dt;
83
+ }
84
+
85
+ function findNearestThreshold(
86
+ value: number,
87
+ ): { threshold: number; distance: number; direction: 'up' | 'down' } | null {
88
+ let nearest: { threshold: number; distance: number; direction: 'up' | 'down' } | null = null;
89
+
90
+ for (const t of thresholds) {
91
+ const dist = Math.abs(value - (t as number));
92
+ if (nearest === null || dist < nearest.distance) {
93
+ nearest = {
94
+ threshold: t as number,
95
+ distance: dist,
96
+ direction: value < (t as number) ? 'up' : 'down',
97
+ };
98
+ }
99
+ }
100
+
101
+ return nearest;
102
+ }
103
+
104
+ return {
105
+ evaluate(value: number, velocity?: number): SpeculativeResult<B> {
106
+ const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
107
+ history.push({ value, time: now });
108
+ if (history.length > HISTORY_SIZE) history.shift();
109
+
110
+ // Evaluate current state
111
+ const current = boundary.hysteresis
112
+ ? Boundary.evaluateWithHysteresis(boundary, value, previousState)
113
+ : Boundary.evaluate(boundary, value);
114
+ previousState = current;
115
+
116
+ // Find nearest threshold
117
+ const nearest = findNearestThreshold(value);
118
+ if (!nearest) {
119
+ return { current, confidence: 0 };
120
+ }
121
+
122
+ const vel = estimateVelocity(value, velocity);
123
+
124
+ // Check if moving toward the threshold
125
+ const movingToward = (nearest.direction === 'up' && vel > 0) || (nearest.direction === 'down' && vel < 0);
126
+
127
+ if (!movingToward || nearest.distance > prefetchWindow) {
128
+ return { current, confidence: 0 };
129
+ }
130
+
131
+ // Compute confidence: closer to threshold + faster velocity = higher confidence
132
+ const distanceFactor = 1 - nearest.distance / prefetchWindow;
133
+ // Distance weighted 70%, velocity 30% — distance is more reliable for prefetch confidence
134
+ const velocityFactor = Math.min(Math.abs(vel) * 10, 1); // Normalize velocity contribution
135
+ const confidence = distanceFactor * 0.7 + velocityFactor * 0.3;
136
+
137
+ // Below 30% confidence, skip prefetch — not worth the speculative cost
138
+ if (confidence < 0.3) {
139
+ return { current, confidence };
140
+ }
141
+
142
+ // Pre-compute the predicted next state (jump past hysteresis zone if present)
143
+ const hysteresisJump = boundary.hysteresis ?? 0;
144
+ const predictedValue =
145
+ nearest.direction === 'up'
146
+ ? nearest.threshold + hysteresisJump + epsilon
147
+ : nearest.threshold - hysteresisJump - epsilon;
148
+
149
+ const prefetched = boundary.hysteresis
150
+ ? Boundary.evaluateWithHysteresis(boundary, predictedValue, current)
151
+ : Boundary.evaluate(boundary, predictedValue);
152
+
153
+ // Only return prefetch if it's actually different
154
+ if (prefetched === current) {
155
+ return { current, confidence: 0 };
156
+ }
157
+
158
+ return { current, prefetched, confidence };
159
+ },
160
+ };
161
+ }
162
+
163
+ /**
164
+ * SpeculativeEvaluator -- threshold proximity prefetching for boundaries.
165
+ * Pre-computes the next discrete state when a signal is near a threshold,
166
+ * using velocity estimation and hysteresis-based prefetch windows.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const boundary = Boundary.make({
171
+ * thresholds: [600],
172
+ * states: ['small', 'large'] as const,
173
+ * });
174
+ * const spec = SpeculativeEvaluator.make(boundary);
175
+ * const { current, prefetched, confidence } = spec.evaluate(595, 1.5);
176
+ * // current='small', prefetched='large', confidence ~0.85
177
+ * ```
178
+ */
179
+ export const SpeculativeEvaluator = { make: _make };
180
+
181
+ export declare namespace SpeculativeEvaluator {
182
+ /** Structural shape of an evaluator bound to a specific {@link Boundary}. */
183
+ export type Shape<B extends Boundary.Shape> = SpeculativeEvaluatorShape<B>;
184
+ /** Prediction result from `evaluate()` — current state, optional prefetched next state, and confidence. */
185
+ export type Result<B extends Boundary.Shape> = SpeculativeResult<B>;
186
+ }
package/src/store.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `Store<S, Msg>` — TEA-style reducer store.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type { Stream } from 'effect';
8
+ import { Effect, SubscriptionRef, Semaphore } from 'effect';
9
+
10
+ interface StoreShape<S, Msg> {
11
+ readonly _tag: 'Store';
12
+ readonly get: Effect.Effect<S>;
13
+ readonly changes: Stream.Stream<S>;
14
+ dispatch(msg: Msg): Effect.Effect<void>;
15
+ }
16
+
17
+ interface EffectfulStoreShape<S, Msg, E = never, R = never> {
18
+ readonly _tag: 'Store';
19
+ readonly get: Effect.Effect<S>;
20
+ readonly changes: Stream.Stream<S>;
21
+ dispatch(msg: Msg): Effect.Effect<void, E, R>;
22
+ }
23
+
24
+ const _make = <S, Msg>(initial: S, reducer: (state: S, msg: Msg) => S): Effect.Effect<StoreShape<S, Msg>> =>
25
+ Effect.gen(function* () {
26
+ const ref = yield* SubscriptionRef.make(initial);
27
+
28
+ return {
29
+ _tag: 'Store' as const,
30
+ get: SubscriptionRef.get(ref),
31
+ changes: SubscriptionRef.changes(ref),
32
+ dispatch: (msg: Msg) => SubscriptionRef.update(ref, (state) => reducer(state, msg)),
33
+ };
34
+ });
35
+
36
+ const _makeWithEffect = <S, Msg, E, R>(
37
+ initial: S,
38
+ reducer: (state: S, msg: Msg) => Effect.Effect<S, E, R>,
39
+ ): Effect.Effect<EffectfulStoreShape<S, Msg, E, R>> =>
40
+ Effect.gen(function* () {
41
+ const ref = yield* SubscriptionRef.make(initial);
42
+ const mutex = yield* Semaphore.make(1);
43
+
44
+ return {
45
+ _tag: 'Store' as const,
46
+ get: SubscriptionRef.get(ref),
47
+ changes: SubscriptionRef.changes(ref),
48
+ dispatch: (msg: Msg) =>
49
+ mutex.withPermits(1)(
50
+ Effect.gen(function* () {
51
+ const current = yield* SubscriptionRef.get(ref);
52
+ const next = yield* reducer(current, msg);
53
+ yield* SubscriptionRef.set(ref, next);
54
+ }),
55
+ ),
56
+ };
57
+ });
58
+
59
+ /**
60
+ * Store — TEA-style state container.
61
+ * Build with an initial state and a pure `reducer(state, msg) => state`, then
62
+ * dispatch messages; the store publishes the resulting state via `changes`.
63
+ * Use `makeWithEffect` when the reducer is itself an `Effect`.
64
+ */
65
+ export const Store = {
66
+ /** Synchronous reducer store. */
67
+ make: _make,
68
+ /** Reducer store where state transitions are themselves `Effect`s. */
69
+ makeWithEffect: _makeWithEffect,
70
+ };
71
+
72
+ export declare namespace Store {
73
+ /** Structural shape of a synchronous store. */
74
+ export type Shape<S, Msg> = StoreShape<S, Msg>;
75
+ /** Structural shape of an effectful store; adds error channel `E` and requirements `R`. */
76
+ export type Effectful<S, Msg, E = never, R = never> = EffectfulStoreShape<S, Msg, E, R>;
77
+ }
package/src/style.ts ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * StyleDef -- adaptive style primitive for constraint-based rendering.
3
+ *
4
+ * A style binds a base style layer to optional boundary states with
5
+ * per-state overrides and transitions. Content-addressed via FNV-1a.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ContentAddress, Millis } from './brands.js';
11
+ import type { Boundary } from './boundary.js';
12
+ import type { StateUnion } from './type-utils.js';
13
+ import { CanonicalCbor } from './cbor.js';
14
+ import { fnv1aBytes } from './fnv.js';
15
+ import { CzapValidationError } from './validation-error.js';
16
+
17
+ /** Single `box-shadow` layer — compiled into a space-separated CSS value by {@link Style.tap}. */
18
+ export interface ShadowLayer {
19
+ readonly x: number;
20
+ readonly y: number;
21
+ readonly blur: number;
22
+ readonly spread?: number;
23
+ readonly color: string;
24
+ readonly inset?: boolean;
25
+ }
26
+
27
+ /**
28
+ * One layer of a {@link Style}: a flat property bag plus optional pseudo
29
+ * selectors (`:hover`, `::before`, …) and structured `box-shadow` layers.
30
+ */
31
+ export interface StyleLayer {
32
+ readonly properties: Record<string, string>;
33
+ readonly pseudo?: Record<string, Record<string, string>>;
34
+ readonly boxShadow?: readonly ShadowLayer[];
35
+ }
36
+
37
+ interface StyleDef<B extends Boundary.Shape = Boundary.Shape> {
38
+ readonly _tag: 'StyleDef';
39
+ readonly _version: 1;
40
+ readonly id: ContentAddress;
41
+ readonly boundary?: B;
42
+ readonly base: StyleLayer;
43
+ readonly states?: { readonly [S in StateUnion<B> & string]?: StyleLayer };
44
+ readonly transition?: {
45
+ readonly duration: Millis;
46
+ readonly easing?: string;
47
+ readonly properties?: readonly string[];
48
+ };
49
+ }
50
+
51
+ interface StyleFactory {
52
+ make<B extends Boundary.Shape>(config: {
53
+ readonly boundary?: B;
54
+ readonly base: StyleLayer;
55
+ readonly states?: { readonly [S in StateUnion<B> & string]?: StyleLayer };
56
+ readonly transition?: StyleDef['transition'];
57
+ }): StyleDef<B>;
58
+ }
59
+
60
+ function deterministicId<B extends Boundary.Shape>(
61
+ boundary: B | undefined,
62
+ base: StyleLayer,
63
+ states: StyleDef<B>['states'],
64
+ transition: StyleDef['transition'] | undefined,
65
+ ): ContentAddress {
66
+ return fnv1aBytes(
67
+ CanonicalCbor.encode({
68
+ _tag: 'StyleDef',
69
+ _version: 1,
70
+ boundaryId: boundary?.id ?? null,
71
+ base,
72
+ states: states ?? {},
73
+ transition: transition ?? null,
74
+ }),
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Deep merge two style layers: properties spread, pseudo merge per selector, boxShadow concat.
80
+ *
81
+ * Override properties win over base. Pseudo-element selectors are merged per
82
+ * key. Box shadows are concatenated (base first, then override).
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const base = { properties: { color: 'red', padding: '4px' } };
87
+ * const override = { properties: { color: 'blue', margin: '8px' } };
88
+ * const merged = Style.mergeLayers(base, override);
89
+ * // merged.properties === { color: 'blue', padding: '4px', margin: '8px' }
90
+ * ```
91
+ */
92
+ function _mergeLayers(base: StyleLayer, override: StyleLayer): StyleLayer {
93
+ const properties = { ...base.properties, ...override.properties };
94
+
95
+ let pseudo: Record<string, Record<string, string>> | undefined;
96
+ if (base.pseudo || override.pseudo) {
97
+ const allSelectors = new Set([...Object.keys(base.pseudo ?? {}), ...Object.keys(override.pseudo ?? {})]);
98
+ pseudo = {};
99
+ for (const sel of allSelectors) {
100
+ pseudo[sel] = { ...base.pseudo?.[sel], ...override.pseudo?.[sel] };
101
+ }
102
+ }
103
+
104
+ let boxShadow: readonly ShadowLayer[] | undefined;
105
+ if (base.boxShadow || override.boxShadow) {
106
+ boxShadow = [...(base.boxShadow ?? []), ...(override.boxShadow ?? [])];
107
+ }
108
+
109
+ return {
110
+ properties,
111
+ ...(pseudo !== undefined ? { pseudo } : {}),
112
+ ...(boxShadow !== undefined ? { boxShadow } : {}),
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Resolve a style to a flat `Record<string, string>` for the given state.
118
+ *
119
+ * Merges base layer with the state-specific override (if any), flattens
120
+ * pseudo selectors and box-shadow into the result map.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const style = Style.make({
125
+ * base: { properties: { color: 'black' } },
126
+ * states: { dark: { properties: { color: 'white' } } },
127
+ * });
128
+ * const props = Style.tap(style, 'dark');
129
+ * // props === { color: 'white' }
130
+ * const baseProps = Style.tap(style);
131
+ * // baseProps === { color: 'black' }
132
+ * ```
133
+ */
134
+ function _tap(style: StyleDef, state?: string): Record<string, string> {
135
+ let layer = style.base;
136
+
137
+ if (state && style.states) {
138
+ const stateLayer = style.states[state];
139
+ if (stateLayer) {
140
+ layer = _mergeLayers(layer, stateLayer);
141
+ }
142
+ }
143
+
144
+ const result: Record<string, string> = { ...layer.properties };
145
+
146
+ if (layer.pseudo) {
147
+ for (const [sel, props] of Object.entries(layer.pseudo)) {
148
+ for (const [prop, val] of Object.entries(props)) {
149
+ result[`${sel}::${prop}`] = val;
150
+ }
151
+ }
152
+ }
153
+
154
+ if (layer.boxShadow && layer.boxShadow.length > 0) {
155
+ result['box-shadow'] = layer.boxShadow
156
+ .map((s) => {
157
+ const parts: string[] = [];
158
+ if (s.inset) parts.push('inset');
159
+ parts.push(`${s.x}px`, `${s.y}px`, `${s.blur}px`);
160
+ if (s.spread !== undefined) parts.push(`${s.spread}px`);
161
+ parts.push(s.color);
162
+ return parts.join(' ');
163
+ })
164
+ .join(', ');
165
+ }
166
+
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Style namespace -- adaptive style primitive for constraint-based rendering.
172
+ *
173
+ * Bind base styles to optional boundary states with per-state overrides and
174
+ * CSS transitions. Resolve to flat property maps for any given state.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * import { Boundary, Style } from '@czap/core';
179
+ *
180
+ * const bp = Boundary.make({ input: 'viewport.width', at: [[0, 'sm'], [768, 'lg']] });
181
+ * const style = Style.make({
182
+ * boundary: bp,
183
+ * base: { properties: { 'font-size': '14px' } },
184
+ * states: { lg: { properties: { 'font-size': '18px' } } },
185
+ * transition: { duration: Millis(200) },
186
+ * });
187
+ * const resolved = Style.tap(style, 'lg');
188
+ * // resolved === { 'font-size': '18px' }
189
+ * ```
190
+ */
191
+ export const Style: StyleFactory & {
192
+ tap: typeof _tap;
193
+ mergeLayers: typeof _mergeLayers;
194
+ } = {
195
+ /**
196
+ * Create a new StyleDef from a configuration object.
197
+ *
198
+ * Validates that state keys match the boundary's states (if a boundary is
199
+ * provided). The resulting object is frozen and content-addressed.
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const style = Style.make({
204
+ * base: { properties: { display: 'flex', gap: '8px' } },
205
+ * });
206
+ * // style._tag === 'StyleDef'
207
+ * // style.id === 'fnv1a:...'
208
+ * ```
209
+ */
210
+ make<B extends Boundary.Shape>(config: {
211
+ readonly boundary?: B;
212
+ readonly base: StyleLayer;
213
+ readonly states?: { readonly [S in StateUnion<B> & string]?: StyleLayer };
214
+ readonly transition?: StyleDef['transition'];
215
+ }): StyleDef<B> {
216
+ if (config.boundary && config.states) {
217
+ const boundaryStates = config.boundary.states as readonly string[];
218
+ const stateKeys = Object.keys(config.states);
219
+ for (const key of stateKeys) {
220
+ if (!boundaryStates.includes(key)) {
221
+ throw new CzapValidationError(
222
+ 'Style.make',
223
+ `state "${key}" does not match boundary states [${boundaryStates.join(', ')}]`,
224
+ );
225
+ }
226
+ }
227
+ }
228
+
229
+ const id = deterministicId<B>(config.boundary, config.base, config.states, config.transition);
230
+
231
+ const def: StyleDef<B> = {
232
+ _tag: 'StyleDef',
233
+ _version: 1,
234
+ id,
235
+ ...(config.boundary !== undefined ? { boundary: config.boundary } : {}),
236
+ base: config.base,
237
+ ...(config.states !== undefined ? { states: config.states } : {}),
238
+ ...(config.transition !== undefined ? { transition: config.transition } : {}),
239
+ };
240
+ return Object.freeze(def);
241
+ },
242
+ tap: _tap,
243
+ mergeLayers: _mergeLayers,
244
+ };
245
+
246
+ export declare namespace Style {
247
+ /** Structural shape of a style definition parameterized by its governing {@link Boundary}. */
248
+ export type Shape<B extends Boundary.Shape = Boundary.Shape> = StyleDef<B>;
249
+ }
package/src/testing.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Test-only entrypoint for `@czap/core`. Imported as `@czap/core/testing`.
3
+ *
4
+ * These helpers mutate global registry state and would be footguns in
5
+ * production code paths (an edge worker warm-start that calls
6
+ * `resetCapsuleCatalog` would silently wipe every registered capsule,
7
+ * causing dispatch to fail intermittently). They are intentionally
8
+ * partitioned off the main package entry so a consumer cannot reach
9
+ * them by importing `@czap/core` directly.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ export { resetCapsuleCatalog } from './assembly.js';
package/src/theme.ts ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ThemeDef -- theme primitive for constraint-based adaptive rendering.
3
+ *
4
+ * A theme maps a set of token names to variant-keyed values, enabling
5
+ * coherent multi-variant token resolution. Content-addressed via FNV-1a.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ContentAddress } from './brands.js';
11
+ import { CanonicalCbor } from './cbor.js';
12
+ import { fnv1aBytes } from './fnv.js';
13
+ import { CzapValidationError } from './validation-error.js';
14
+
15
+ interface ThemeDef<V extends readonly string[] = readonly string[]> {
16
+ readonly _tag: 'ThemeDef';
17
+ readonly _version: 1;
18
+ readonly id: ContentAddress;
19
+ readonly name: string;
20
+ readonly variants: V;
21
+ readonly tokens: Record<string, Record<V[number] & string, unknown>>;
22
+ readonly meta?: Record<V[number] & string, { readonly label: string; readonly mode: 'light' | 'dark' }>;
23
+ }
24
+
25
+ interface ThemeFactory {
26
+ make<const V extends readonly [string, ...string[]]>(config: {
27
+ readonly name: string;
28
+ readonly variants: V;
29
+ readonly tokens: Record<string, Record<V[number] & string, unknown>>;
30
+ readonly meta?: ThemeDef<V>['meta'];
31
+ }): ThemeDef<V>;
32
+ }
33
+
34
+ function deterministicId<V extends readonly string[]>(
35
+ name: string,
36
+ variants: V,
37
+ tokens: ThemeDef<V>['tokens'],
38
+ meta: ThemeDef<V>['meta'] | undefined,
39
+ ): ContentAddress {
40
+ return fnv1aBytes(
41
+ CanonicalCbor.encode({
42
+ _tag: 'ThemeDef',
43
+ _version: 1,
44
+ name,
45
+ variants,
46
+ tokens,
47
+ meta: meta ?? null,
48
+ }),
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Resolve all tokens for a given variant, returning a map of token name to value.
54
+ *
55
+ * Iterates the theme's token map and extracts each token's value for the
56
+ * specified variant.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const theme = Theme.make({
61
+ * name: 'brand',
62
+ * variants: ['light', 'dark'] as const,
63
+ * tokens: { bg: { light: '#fff', dark: '#111' }, fg: { light: '#000', dark: '#eee' } },
64
+ * });
65
+ * const darkTokens = Theme.tap(theme, 'dark');
66
+ * // darkTokens === { bg: '#111', fg: '#eee' }
67
+ * ```
68
+ */
69
+ function _tap<V extends readonly string[]>(theme: ThemeDef<V>, variant: V[number] & string): Record<string, unknown> {
70
+ const result: Record<string, unknown> = {};
71
+ for (const [tokenName, variantMap] of Object.entries(theme.tokens)) {
72
+ result[tokenName] = variantMap[variant];
73
+ }
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Theme namespace -- theme primitive for constraint-based adaptive rendering.
79
+ *
80
+ * Map token names to variant-keyed values, enabling coherent multi-variant
81
+ * token resolution (e.g. light/dark themes). Content-addressed via FNV-1a.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * import { Theme } from '@czap/core';
86
+ *
87
+ * const theme = Theme.make({
88
+ * name: 'brand',
89
+ * variants: ['light', 'dark'] as const,
90
+ * tokens: {
91
+ * bg: { light: '#fff', dark: '#111' },
92
+ * fg: { light: '#000', dark: '#eee' },
93
+ * },
94
+ * });
95
+ * const lightTokens = Theme.tap(theme, 'light');
96
+ * // lightTokens === { bg: '#fff', fg: '#000' }
97
+ * ```
98
+ */
99
+ export const Theme: ThemeFactory & {
100
+ tap: typeof _tap;
101
+ } = {
102
+ /**
103
+ * Create a new ThemeDef from a configuration object.
104
+ *
105
+ * Validates that every token has a value for each declared variant.
106
+ * The resulting object is frozen and content-addressed.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * const theme = Theme.make({
111
+ * name: 'ocean',
112
+ * variants: ['light', 'dark'] as const,
113
+ * tokens: { primary: { light: '#0066cc', dark: '#3399ff' } },
114
+ * meta: { light: { label: 'Light', mode: 'light' }, dark: { label: 'Dark', mode: 'dark' } },
115
+ * });
116
+ * // theme._tag === 'ThemeDef'
117
+ * // theme.id === 'fnv1a:...'
118
+ * ```
119
+ */
120
+ make<const V extends readonly [string, ...string[]]>(config: {
121
+ readonly name: string;
122
+ readonly variants: V;
123
+ readonly tokens: Record<string, Record<V[number] & string, unknown>>;
124
+ readonly meta?: ThemeDef<V>['meta'];
125
+ }): ThemeDef<V> {
126
+ const variantSet = new Set(config.variants as readonly string[]);
127
+ for (const [tokenName, variantMap] of Object.entries(config.tokens)) {
128
+ for (const variant of variantSet) {
129
+ if (!(variant in variantMap)) {
130
+ throw new CzapValidationError('Theme.make', `Token "${tokenName}" is missing value for variant "${variant}"`);
131
+ }
132
+ }
133
+ }
134
+
135
+ const id = deterministicId<V>(config.name, config.variants, config.tokens, config.meta);
136
+
137
+ return Object.freeze({
138
+ _tag: 'ThemeDef' as const,
139
+ _version: 1 as const,
140
+ id,
141
+ name: config.name,
142
+ variants: config.variants,
143
+ tokens: config.tokens,
144
+ ...(config.meta !== undefined ? { meta: config.meta } : {}),
145
+ });
146
+ },
147
+ tap: _tap,
148
+ };
149
+
150
+ export declare namespace Theme {
151
+ /** Structural shape of a {@link Theme} definition, parameterized by its variant tuple `V`. */
152
+ export type Shape<V extends readonly string[] = readonly string[]> = ThemeDef<V>;
153
+ }