@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,105 @@
1
+ /**
2
+ * UIQuality -- maps buffer occupancy + device capability to UI complexity tier.
3
+ *
4
+ * Same pattern as video ABR (Adaptive Bitrate). Composite signal from
5
+ * buffer occupancy and device tier determines rendering fidelity.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { MotionTier as _MotionTier } from '@czap/_spine';
11
+ import { Boundary } from './boundary.js';
12
+
13
+ /**
14
+ * Motion tier — re-anchored from `@czap/_spine` (the canonical declaration
15
+ * per ADR-0010). The ladder runs from lowest capability (`none`, forced by
16
+ * `prefers-reduced-motion: reduce` regardless of GPU tier) to highest
17
+ * (`compute`, which unlocks the Rust/WASM kernels).
18
+ */
19
+ export type MotionTier = _MotionTier;
20
+
21
+ /**
22
+ * Coarse UI-complexity ladder, in increasing fidelity: `skeleton` (placeholder
23
+ * blocks only) up through `rich` (full interactive styled content).
24
+ */
25
+ export type UIQualityTier = 'skeleton' | 'text-only' | 'styled' | 'interactive' | 'rich';
26
+
27
+ /** {@link Boundary.Shape} instantiation used by {@link UIQuality} — input = `buffer-occupancy`, states = the {@link UIQualityTier} ladder. */
28
+ export type UIQualityBoundary = Boundary.Shape<
29
+ 'buffer-occupancy',
30
+ readonly ['skeleton', 'text-only', 'styled', 'interactive', 'rich']
31
+ >;
32
+
33
+ /**
34
+ * Pre-built boundary for UI quality based on buffer occupancy signal.
35
+ * Thresholds tuned for streaming UI: aggressive degradation when buffer low.
36
+ */
37
+ const uiQualityBoundary: UIQualityBoundary = Boundary.make({
38
+ input: 'buffer-occupancy',
39
+ at: [
40
+ [0.0, 'skeleton'],
41
+ [0.15, 'text-only'],
42
+ [0.35, 'styled'],
43
+ [0.6, 'interactive'],
44
+ [0.85, 'rich'],
45
+ ] as const,
46
+ hysteresis: 0.1,
47
+ });
48
+
49
+ /**
50
+ * Motion tier to normalized device capability score (0-1).
51
+ */
52
+ const DEVICE_CAPABILITY_SCORES: Record<MotionTier, number> = {
53
+ none: 0.0,
54
+ transitions: 0.25,
55
+ animations: 0.5,
56
+ physics: 0.75,
57
+ compute: 1.0,
58
+ };
59
+
60
+ interface UIQualityEvaluatorShape {
61
+ evaluate(bufferOccupancy: number, deviceTier?: MotionTier): UIQualityTier;
62
+ readonly boundary: UIQualityBoundary;
63
+ }
64
+
65
+ function _make(): UIQualityEvaluatorShape {
66
+ let previousTier: UIQualityTier = 'skeleton';
67
+
68
+ return {
69
+ evaluate(bufferOccupancy: number, deviceTier?: MotionTier): UIQualityTier {
70
+ // Composite signal: buffer occupancy weighted more heavily
71
+ const deviceScore = deviceTier ? DEVICE_CAPABILITY_SCORES[deviceTier] : 0.5;
72
+ const composite = bufferOccupancy * 0.7 + deviceScore * 0.3;
73
+
74
+ // Boundary.evaluateWithHysteresis now returns the exact state literal union ('skeleton' | ... | 'rich')
75
+ // because uiQualityBoundary's S parameter is narrowed via `as const`.
76
+ const result = Boundary.evaluateWithHysteresis(uiQualityBoundary, composite, previousTier);
77
+
78
+ previousTier = result;
79
+ return result;
80
+ },
81
+
82
+ boundary: uiQualityBoundary,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * UIQuality — adaptive-bitrate-style UI fidelity gate.
88
+ *
89
+ * Combines buffer occupancy (how far ahead the generator is) and device
90
+ * {@link MotionTier} into a composite score and maps it via {@link Boundary}
91
+ * with hysteresis to a {@link UIQualityTier}.
92
+ */
93
+ export const UIQuality = {
94
+ /** Build a stateful evaluator that remembers the previous tier for hysteresis. */
95
+ make: _make,
96
+ /** The pre-built boundary — exposed so callers can compile it to CSS/GLSL directly. */
97
+ boundary: uiQualityBoundary,
98
+ };
99
+
100
+ export declare namespace UIQuality {
101
+ /** Structural shape of a UIQuality evaluator. */
102
+ export type Shape = UIQualityEvaluatorShape;
103
+ /** Alias for {@link UIQualityTier}. */
104
+ export type Tier = UIQualityTier;
105
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Structured validation error for factory/constructor failures.
3
+ *
4
+ * Thrown (not Effect.fail'd) because all factory functions are synchronous.
5
+ * Callers can catch and `instanceof CzapValidationError` to distinguish
6
+ * czap validation failures from other errors.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ /**
12
+ * Structured validation error thrown by czap factory/constructor functions.
13
+ *
14
+ * Carries a `module` identifier (e.g. `'Boundary.make'`) and a human-readable
15
+ * `detail` message. Synchronous factories throw this directly so callers can
16
+ * `catch` and branch via {@link isValidationError} without Effect plumbing.
17
+ */
18
+ export class CzapValidationError extends Error {
19
+ readonly _tag = 'CzapValidationError' as const;
20
+ readonly module: string;
21
+ readonly detail: string;
22
+
23
+ constructor(module: string, detail: string) {
24
+ super(`${module}: ${detail}`);
25
+ this.name = 'CzapValidationError';
26
+ this.module = module;
27
+ this.detail = detail;
28
+ }
29
+ }
30
+
31
+ /** Type guard for CzapValidationError */
32
+ export function isValidationError(error: unknown): error is CzapValidationError {
33
+ return error instanceof CzapValidationError;
34
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * VectorClock -- causality tracking for distributed systems.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ interface VectorClockShape {
8
+ readonly _tag: 'VectorClock';
9
+ readonly entries: ReadonlyMap<string, number>;
10
+ }
11
+
12
+ const _make = (): VectorClockShape => ({
13
+ _tag: 'VectorClock',
14
+ entries: new Map(),
15
+ });
16
+
17
+ const _from = (entries: Record<string, number>): VectorClockShape => ({
18
+ _tag: 'VectorClock',
19
+ entries: new Map(Object.entries(entries)),
20
+ });
21
+
22
+ const _get = (vc: VectorClockShape, peerId: string): number => vc.entries.get(peerId) ?? 0;
23
+
24
+ const _tick = (vc: VectorClockShape, peerId: string): VectorClockShape => {
25
+ const newEntries = new Map(vc.entries);
26
+ newEntries.set(peerId, _get(vc, peerId) + 1);
27
+ return { _tag: 'VectorClock', entries: newEntries };
28
+ };
29
+
30
+ const _merge = (a: VectorClockShape, b: VectorClockShape): VectorClockShape => {
31
+ const newEntries = new Map(a.entries);
32
+ for (const [peerId, counter] of b.entries) {
33
+ const existing = newEntries.get(peerId) ?? 0;
34
+ newEntries.set(peerId, Math.max(existing, counter));
35
+ }
36
+ return { _tag: 'VectorClock', entries: newEntries };
37
+ };
38
+
39
+ const _happensBefore = (a: VectorClockShape, b: VectorClockShape): boolean => {
40
+ const allPeers = new Set([...a.entries.keys(), ...b.entries.keys()]);
41
+ let hasStrictlyLess = false;
42
+
43
+ for (const peerId of allPeers) {
44
+ const aValue = _get(a, peerId);
45
+ const bValue = _get(b, peerId);
46
+ if (aValue > bValue) return false;
47
+ if (aValue < bValue) hasStrictlyLess = true;
48
+ }
49
+
50
+ return hasStrictlyLess;
51
+ };
52
+
53
+ const _equals = (a: VectorClockShape, b: VectorClockShape): boolean => {
54
+ const allPeers = new Set([...a.entries.keys(), ...b.entries.keys()]);
55
+ for (const peerId of allPeers) {
56
+ if (_get(a, peerId) !== _get(b, peerId)) return false;
57
+ }
58
+ return true;
59
+ };
60
+
61
+ const _concurrent = (a: VectorClockShape, b: VectorClockShape): boolean =>
62
+ !_happensBefore(a, b) && !_happensBefore(b, a) && !_equals(a, b);
63
+
64
+ const _compare = (a: VectorClockShape, b: VectorClockShape): -1 | 0 | 1 => {
65
+ if (_happensBefore(a, b)) return -1;
66
+ if (_happensBefore(b, a)) return 1;
67
+ return 0;
68
+ };
69
+
70
+ const _toObject = (vc: VectorClockShape): Record<string, number> => Object.fromEntries(vc.entries);
71
+
72
+ const _peers = (vc: VectorClockShape): string[] => [...vc.entries.keys()];
73
+
74
+ const _size = (vc: VectorClockShape): number => vc.entries.size;
75
+
76
+ /**
77
+ * VectorClock — per-peer counter algebra for causal ordering.
78
+ * Pairs with {@link HLC} when you need exact happens-before rather than HLC's
79
+ * hybrid ordering.
80
+ */
81
+ export const VectorClock = {
82
+ /** Build an empty vector clock. */
83
+ make: _make,
84
+ /** Build a vector clock from an existing `Record<peer, counter>`. */
85
+ from: _from,
86
+ /** Read the counter for a single peer. */
87
+ get: _get,
88
+ /** Increment the counter for the given peer, returning a new clock. */
89
+ tick: _tick,
90
+ /** Pointwise-max merge of two clocks. */
91
+ merge: _merge,
92
+ /** `true` iff `a` strictly happens-before `b`. */
93
+ happensBefore: _happensBefore,
94
+ /** `true` iff `a` and `b` are causally concurrent. */
95
+ concurrent: _concurrent,
96
+ /** Exact structural equality. */
97
+ equals: _equals,
98
+ /** `-1 | 0 | 1` comparator suitable for `sort`; `0` when concurrent. */
99
+ compare: _compare,
100
+ /** Convert to a plain `Record<peer, counter>`. */
101
+ toObject: _toObject,
102
+ /** List peers known to the clock. */
103
+ peers: _peers,
104
+ /** Number of peers. */
105
+ size: _size,
106
+ };
107
+
108
+ export declare namespace VectorClock {
109
+ /** Structural shape of a vector clock: a `Map<peer, counter>` wrapper. */
110
+ export type Shape = VectorClockShape;
111
+ }
package/src/video.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * VideoRenderer -- fixed-step frame generator for deterministic video rendering.
3
+ *
4
+ * Same compositor, same state pipeline -- different clock. The VideoRenderer
5
+ * drives a FixedStepScheduler at target fps, producing VideoFrameOutput
6
+ * per frame with the full CompositeState snapshot.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { Scheduler } from './scheduler.js';
12
+ import { Scheduler as SchedulerImpl } from './scheduler.js';
13
+ import type { CompositeState, Compositor } from './compositor.js';
14
+ import type { Signal } from './signal.js';
15
+ import type { Millis } from './brands.js';
16
+ import { Effect } from 'effect';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /** Configuration for a {@link VideoRenderer}: resolution, target fps, and total duration. */
23
+ export interface VideoConfig {
24
+ readonly fps: number;
25
+ readonly width: number;
26
+ readonly height: number;
27
+ readonly durationMs: Millis;
28
+ }
29
+
30
+ /**
31
+ * Single frame yielded by `VideoRenderer.frames()`: frame index, timestamp,
32
+ * normalized progress, and the {@link CompositeState} snapshot captured at that tick.
33
+ */
34
+ export interface VideoFrameOutput {
35
+ readonly frame: number;
36
+ readonly timestamp: number;
37
+ readonly progress: number;
38
+ readonly state: CompositeState;
39
+ }
40
+
41
+ interface VideoRendererShape {
42
+ readonly config: VideoConfig;
43
+ readonly totalFrames: number;
44
+ readonly scheduler: Scheduler.FixedStep;
45
+ frames(): AsyncGenerator<VideoFrameOutput>;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Factory
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Create a video renderer that produces deterministic frames from a Compositor.
54
+ *
55
+ * Each call to `frames()` returns an async generator yielding one
56
+ * `VideoFrameOutput` per frame at the configured fps/duration.
57
+ *
58
+ * When a `signal` is provided it is seeked to each frame's timestamp before
59
+ * the compositor evaluates, so quantizers that read from that signal advance
60
+ * deterministically with the render clock.
61
+ */
62
+ function _make(
63
+ config: VideoConfig,
64
+ compositor: Compositor.Shape,
65
+ signal?: Signal.Controllable<number>,
66
+ ): VideoRendererShape {
67
+ const totalFrames = Math.ceil((config.durationMs / 1000) * config.fps);
68
+ const scheduler = SchedulerImpl.fixedStep(config.fps);
69
+
70
+ return {
71
+ config,
72
+ totalFrames,
73
+ scheduler,
74
+ async *frames(): AsyncGenerator<VideoFrameOutput> {
75
+ for (let i = 0; i < totalFrames; i++) {
76
+ scheduler.step();
77
+ const timestamp = (i * 1000) / config.fps;
78
+ if (signal) {
79
+ Effect.runSync(signal.seek(timestamp));
80
+ }
81
+ const state = Effect.runSync(compositor.compute());
82
+ yield {
83
+ frame: i,
84
+ timestamp,
85
+ progress: totalFrames > 1 ? i / (totalFrames - 1) : 1,
86
+ state,
87
+ };
88
+ }
89
+ },
90
+ };
91
+ }
92
+
93
+ /**
94
+ * VideoRenderer — fixed-step frame generator for deterministic offline rendering.
95
+ * Drives a {@link Compositor} at the configured fps and optionally seeks a
96
+ * controllable time {@link Signal} so every frame is reproducible.
97
+ */
98
+ export const VideoRenderer = {
99
+ /** Create a renderer bound to the given compositor and optional seekable time signal. */
100
+ make: _make,
101
+ };
102
+
103
+ export declare namespace VideoRenderer {
104
+ /** Structural shape of a renderer instance returned by {@link VideoRenderer.make}. */
105
+ export type Shape = VideoRendererShape;
106
+ }
@@ -0,0 +1,300 @@
1
+ /**
2
+ * WASM escape hatch -- capability detection, module loading, and dispatch.
3
+ *
4
+ * Detects WebAssembly availability, loads the czap-compute WASM module,
5
+ * and provides a unified kernel interface that transparently falls back
6
+ * to pure TypeScript implementations when WASM is unavailable.
7
+ *
8
+ * Usage:
9
+ * const kernels = WASMDispatch.kernels(); // TS fallbacks
10
+ * await WASMDispatch.load(wasmUrl); // upgrade to WASM
11
+ * const kernels2 = WASMDispatch.kernels(); // now WASM-backed
12
+ *
13
+ * @module
14
+ */
15
+
16
+ import { fallbackKernels } from './wasm-fallback.js';
17
+ import { WASM_SCRATCH_BASE } from './defaults.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Public kernel interface
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Kernel functions available from both WASM and TS fallback. */
24
+ export interface WASMKernels {
25
+ /**
26
+ * Sample a spring easing at `samples` evenly-spaced points in [0, 1].
27
+ * Returns Float32Array of length `samples + 1`.
28
+ */
29
+ springCurve(stiffness: number, damping: number, mass: number, samples: number): Float32Array;
30
+
31
+ /**
32
+ * Batch boundary evaluation. For each value, returns the index of the
33
+ * highest threshold where `value >= threshold`.
34
+ * Thresholds must be sorted ascending.
35
+ */
36
+ batchBoundaryEval(thresholds: Float64Array, values: Float64Array): Uint32Array;
37
+
38
+ /**
39
+ * Normalize weights in-place so positive values sum to 1.0.
40
+ * Negative weights clamped to 0. Returns the (modified) input array.
41
+ */
42
+ blendNormalize(weights: Float32Array): Float32Array;
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // WASM instance state
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /** Raw WASM export signatures matching C-ABI from czap-compute. */
50
+ interface WASMExports {
51
+ memory: WebAssembly.Memory;
52
+ spring_curve(stiffness: number, damping: number, mass: number, samples: number): number;
53
+ batch_boundary_eval(thresholds_ptr: number, thresholds_len: number, values_ptr: number, values_len: number): number;
54
+ blend_normalize(weights_ptr: number, len: number): void;
55
+ }
56
+
57
+ /**
58
+ * Runtime-verified cast from WebAssembly's opaque exports to the typed interface.
59
+ *
60
+ * `WebAssembly.Instance.exports` is structurally `Record<string, unknown>` — functions,
61
+ * memories, tables, and globals are all opaque at the type level. This helper
62
+ * asserts the expected functions are present as callable values and that the
63
+ * memory export is a WebAssembly.Memory instance, then narrows. If any required
64
+ * export is missing or the wrong shape, it throws — making the cast runtime-safe.
65
+ *
66
+ * This is a sanctioned cast containment point (cf. tuple.ts, cell.ts, boundary.ts,
67
+ * typed-ref.ts).
68
+ */
69
+ const validateWASMExports = (exports: WebAssembly.Exports): WASMExports => {
70
+ const requiredFunctions = ['spring_curve', 'batch_boundary_eval', 'blend_normalize'] as const;
71
+ for (const name of requiredFunctions) {
72
+ if (typeof exports[name] !== 'function') {
73
+ throw new Error(
74
+ `WASM module missing required export: "${name}". Available exports: [${Object.keys(exports).join(', ')}]`,
75
+ );
76
+ }
77
+ }
78
+ const mem = exports['memory'];
79
+ const isMemoryShape =
80
+ mem !== null &&
81
+ typeof mem === 'object' &&
82
+ 'buffer' in mem &&
83
+ Reflect.get(mem, 'buffer') instanceof ArrayBuffer;
84
+ /* v8 ignore next 5 — sanctioned cast containment: the czap-compute WASM module always
85
+ exports `memory` as a WebAssembly.Memory (whose `.buffer` is an ArrayBuffer); this
86
+ guard exists only so the cast to WASMExports stays runtime-safe if a caller ever
87
+ supplies a drift/tampered module. Cannot be reached by valid instantiate output. */
88
+ if (!isMemoryShape) {
89
+ throw new Error(
90
+ `WASM module missing required memory export. Available exports: [${Object.keys(exports).join(', ')}]`,
91
+ );
92
+ }
93
+ return exports as unknown as WASMExports;
94
+ };
95
+
96
+ /** Loaded WASM module state. */
97
+ let wasmInstance: WASMExports | null = null;
98
+
99
+ type WASMAvailabilityProbe =
100
+ | { readonly status: 'ok'; readonly available: boolean }
101
+ | { readonly status: 'unavailable' }
102
+ | { readonly status: 'error'; readonly error: unknown };
103
+
104
+ function wasmAvailable(available: boolean): WASMAvailabilityProbe {
105
+ return { status: 'ok', available };
106
+ }
107
+
108
+ function wasmUnavailable(): WASMAvailabilityProbe {
109
+ return { status: 'unavailable' };
110
+ }
111
+
112
+ function wasmProbeError(error: unknown): WASMAvailabilityProbe {
113
+ return { status: 'error', error };
114
+ }
115
+
116
+ function probeWASMAvailability(): WASMAvailabilityProbe {
117
+ try {
118
+ if (typeof WebAssembly === 'undefined') {
119
+ return wasmUnavailable();
120
+ }
121
+
122
+ return wasmAvailable(typeof WebAssembly.instantiate === 'function' && typeof WebAssembly.Module === 'function');
123
+ } catch (error) {
124
+ return wasmProbeError(error);
125
+ }
126
+ }
127
+
128
+ function isWASMAvailable(probe: WASMAvailabilityProbe): boolean {
129
+ return probe.status === 'ok' ? probe.available : false;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // WASM-backed kernel wrappers
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function createWASMKernels(wasm: WASMExports): WASMKernels {
137
+ return {
138
+ springCurve(stiffness: number, damping: number, mass: number, samples: number): Float32Array {
139
+ const count = Math.min(Math.max(0, samples | 0), 255);
140
+ const ptr = wasm.spring_curve(stiffness, damping, mass, count);
141
+ const view = new Float32Array(wasm.memory.buffer, ptr, count + 1);
142
+ return new Float32Array(view);
143
+ },
144
+
145
+ batchBoundaryEval(thresholds: Float64Array, values: Float64Array): Uint32Array {
146
+ const tLen = thresholds.length;
147
+ const vLen = values.length;
148
+
149
+ const SCRATCH_BASE = WASM_SCRATCH_BASE;
150
+ const thresholdsOffset = SCRATCH_BASE;
151
+ const valuesOffset = thresholdsOffset + tLen * 4;
152
+
153
+ const needed = valuesOffset + vLen * 4;
154
+ const currentSize = wasm.memory.buffer.byteLength;
155
+ if (needed > currentSize) {
156
+ const pages = Math.ceil((needed - currentSize) / 65536);
157
+ wasm.memory.grow(pages);
158
+ }
159
+
160
+ const threshF32 = new Float32Array(wasm.memory.buffer, thresholdsOffset, tLen);
161
+ for (let i = 0; i < tLen; i++) {
162
+ threshF32[i] = thresholds[i]!;
163
+ }
164
+
165
+ const valF32 = new Float32Array(wasm.memory.buffer, valuesOffset, vLen);
166
+ for (let i = 0; i < vLen; i++) {
167
+ valF32[i] = values[i]!;
168
+ }
169
+
170
+ const resultPtr = wasm.batch_boundary_eval(thresholdsOffset, tLen, valuesOffset, vLen);
171
+ const view = new Uint32Array(wasm.memory.buffer, resultPtr, vLen);
172
+ return new Uint32Array(view);
173
+ },
174
+
175
+ blendNormalize(weights: Float32Array): Float32Array {
176
+ const len = weights.length;
177
+ if (len === 0) return weights;
178
+
179
+ const SCRATCH_BASE = WASM_SCRATCH_BASE;
180
+ const needed = SCRATCH_BASE + len * 4;
181
+ const currentSize = wasm.memory.buffer.byteLength;
182
+ if (needed > currentSize) {
183
+ const pages = Math.ceil((needed - currentSize) / 65536);
184
+ wasm.memory.grow(pages);
185
+ }
186
+
187
+ const wasmWeights = new Float32Array(wasm.memory.buffer, SCRATCH_BASE, len);
188
+ wasmWeights.set(weights);
189
+
190
+ wasm.blend_normalize(SCRATCH_BASE, len);
191
+
192
+ const result = new Float32Array(wasm.memory.buffer, SCRATCH_BASE, len);
193
+ weights.set(result);
194
+ return weights;
195
+ },
196
+ };
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Public API
201
+ // ---------------------------------------------------------------------------
202
+
203
+ /**
204
+ * Public API of the {@link WASMDispatch} singleton: probe for WebAssembly,
205
+ * asynchronously load the Rust compute module, and hand back either WASM or
206
+ * {@link fallbackKernels} via {@link WASMDispatchAPI.kernels}.
207
+ */
208
+ export interface WASMDispatchAPI {
209
+ detect(): boolean;
210
+ load(wasmUrl: string | ArrayBuffer): Promise<WASMKernels>;
211
+ kernels(): WASMKernels;
212
+ isLoaded(): boolean;
213
+ unload(): void;
214
+ }
215
+
216
+ let wasmKernels: WASMKernels | null = null;
217
+ let loadingPromise: Promise<WASMKernels> | null = null;
218
+ let loadingToken: object | null = null;
219
+ let loadSession = 0;
220
+
221
+ /**
222
+ * WASMDispatch — singleton that wires the Rust compute crate (spring, boundary,
223
+ * blend kernels) into the runtime, falling back to {@link fallbackKernels}
224
+ * when WebAssembly is unavailable or the module fails to load.
225
+ */
226
+ export const WASMDispatch: WASMDispatchAPI = {
227
+ detect(): boolean {
228
+ return isWASMAvailable(probeWASMAvailability());
229
+ },
230
+
231
+ load(wasmUrl: string | ArrayBuffer): Promise<WASMKernels> {
232
+ if (!WASMDispatch.detect()) {
233
+ return Promise.reject(new Error('WebAssembly is not available in this environment'));
234
+ }
235
+
236
+ if (loadingPromise !== null) {
237
+ return loadingPromise;
238
+ }
239
+
240
+ const currentSession = ++loadSession;
241
+ const currentToken = {};
242
+ loadingToken = currentToken;
243
+ const promise = (async () => {
244
+ try {
245
+ let source: BufferSource;
246
+ if (typeof wasmUrl === 'string') {
247
+ const response = await fetch(wasmUrl);
248
+ if (!response.ok) {
249
+ throw new Error(`Failed to fetch WASM module: ${response.status} ${response.statusText}`);
250
+ }
251
+ source = await response.arrayBuffer();
252
+ } else {
253
+ source = wasmUrl;
254
+ }
255
+
256
+ const { instance } = await WebAssembly.instantiate(source, {
257
+ env: {},
258
+ });
259
+
260
+ wasmInstance = validateWASMExports(instance.exports);
261
+
262
+ wasmKernels = createWASMKernels(wasmInstance);
263
+
264
+ // If unload() ran while we were awaiting fetch/instantiate, it will
265
+ // bump the session and clear the just-installed WASM state.
266
+ if (loadSession !== currentSession) {
267
+ wasmInstance = null;
268
+ wasmKernels = null;
269
+ return fallbackKernels;
270
+ }
271
+
272
+ return wasmKernels;
273
+ } finally {
274
+ if (loadingToken === currentToken) {
275
+ loadingPromise = null;
276
+ loadingToken = null;
277
+ }
278
+ }
279
+ })();
280
+
281
+ loadingPromise = promise;
282
+ return promise;
283
+ },
284
+
285
+ kernels(): WASMKernels {
286
+ return wasmKernels ?? fallbackKernels;
287
+ },
288
+
289
+ isLoaded(): boolean {
290
+ return wasmKernels !== null;
291
+ },
292
+
293
+ unload(): void {
294
+ loadSession += 1;
295
+ wasmInstance = null;
296
+ wasmKernels = null;
297
+ loadingPromise = null;
298
+ loadingToken = null;
299
+ },
300
+ };