@unrulysystems/native-motion-core 0.1.0-alpha.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 (326) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +48 -0
  4. package/dist/clock.cjs +71 -0
  5. package/dist/clock.d.cts +23 -0
  6. package/dist/clock.d.ts +23 -0
  7. package/dist/clock.js +66 -0
  8. package/dist/collect-reads.cjs +68 -0
  9. package/dist/collect-reads.d.cts +23 -0
  10. package/dist/collect-reads.d.ts +23 -0
  11. package/dist/collect-reads.js +62 -0
  12. package/dist/component/boundedArray.cjs +74 -0
  13. package/dist/component/boundedArray.d.cts +25 -0
  14. package/dist/component/boundedArray.d.ts +25 -0
  15. package/dist/component/boundedArray.js +68 -0
  16. package/dist/component/index.cjs +57 -0
  17. package/dist/component/index.d.cts +12 -0
  18. package/dist/component/index.d.ts +12 -0
  19. package/dist/component/index.js +22 -0
  20. package/dist/component/orchestration.cjs +129 -0
  21. package/dist/component/orchestration.d.cts +66 -0
  22. package/dist/component/orchestration.d.ts +66 -0
  23. package/dist/component/orchestration.js +124 -0
  24. package/dist/component/resolve.cjs +104 -0
  25. package/dist/component/resolve.d.cts +11 -0
  26. package/dist/component/resolve.d.ts +11 -0
  27. package/dist/component/resolve.js +97 -0
  28. package/dist/component/transition.cjs +352 -0
  29. package/dist/component/transition.d.cts +33 -0
  30. package/dist/component/transition.d.ts +33 -0
  31. package/dist/component/transition.js +339 -0
  32. package/dist/component/types.cjs +114 -0
  33. package/dist/component/types.d.cts +60 -0
  34. package/dist/component/types.d.ts +60 -0
  35. package/dist/component/types.js +111 -0
  36. package/dist/component/validate.cjs +1015 -0
  37. package/dist/component/validate.d.cts +37 -0
  38. package/dist/component/validate.d.ts +37 -0
  39. package/dist/component/validate.js +1002 -0
  40. package/dist/component/variants.cjs +333 -0
  41. package/dist/component/variants.d.cts +106 -0
  42. package/dist/component/variants.d.ts +106 -0
  43. package/dist/component/variants.js +321 -0
  44. package/dist/config/constants.cjs +41 -0
  45. package/dist/config/constants.d.cts +29 -0
  46. package/dist/config/constants.d.ts +29 -0
  47. package/dist/config/constants.js +38 -0
  48. package/dist/delay.cjs +72 -0
  49. package/dist/delay.d.cts +26 -0
  50. package/dist/delay.d.ts +26 -0
  51. package/dist/delay.js +70 -0
  52. package/dist/derived.cjs +143 -0
  53. package/dist/derived.d.cts +41 -0
  54. package/dist/derived.d.ts +41 -0
  55. package/dist/derived.js +139 -0
  56. package/dist/driver/index.cjs +22 -0
  57. package/dist/driver/index.d.cts +7 -0
  58. package/dist/driver/index.d.ts +7 -0
  59. package/dist/driver/index.js +14 -0
  60. package/dist/driver/keyframeTiming.cjs +130 -0
  61. package/dist/driver/keyframeTiming.d.cts +18 -0
  62. package/dist/driver/keyframeTiming.d.ts +18 -0
  63. package/dist/driver/keyframeTiming.js +124 -0
  64. package/dist/driver/keyframeTimingConfig.cjs +24 -0
  65. package/dist/driver/keyframeTimingConfig.d.cts +5 -0
  66. package/dist/driver/keyframeTimingConfig.d.ts +5 -0
  67. package/dist/driver/keyframeTimingConfig.js +22 -0
  68. package/dist/driver/prepare.cjs +459 -0
  69. package/dist/driver/prepare.d.cts +4 -0
  70. package/dist/driver/prepare.d.ts +4 -0
  71. package/dist/driver/prepare.js +454 -0
  72. package/dist/driver/reference.cjs +762 -0
  73. package/dist/driver/reference.d.cts +53 -0
  74. package/dist/driver/reference.d.ts +53 -0
  75. package/dist/driver/reference.js +757 -0
  76. package/dist/driver/step.cjs +55 -0
  77. package/dist/driver/step.d.cts +9 -0
  78. package/dist/driver/step.d.ts +9 -0
  79. package/dist/driver/step.js +52 -0
  80. package/dist/driver/tiers.cjs +40 -0
  81. package/dist/driver/tiers.d.cts +2 -0
  82. package/dist/driver/tiers.d.ts +2 -0
  83. package/dist/driver/tiers.js +37 -0
  84. package/dist/driver/types.cjs +9 -0
  85. package/dist/driver/types.d.cts +68 -0
  86. package/dist/driver/types.d.ts +68 -0
  87. package/dist/driver/types.js +8 -0
  88. package/dist/external-animation-ledger.cjs +1250 -0
  89. package/dist/external-animation-ledger.d.cts +416 -0
  90. package/dist/external-animation-ledger.d.ts +416 -0
  91. package/dist/external-animation-ledger.js +1243 -0
  92. package/dist/gesture/directionLock.cjs +25 -0
  93. package/dist/gesture/directionLock.d.cts +8 -0
  94. package/dist/gesture/directionLock.d.ts +8 -0
  95. package/dist/gesture/directionLock.js +21 -0
  96. package/dist/gesture/dragConfig.cjs +634 -0
  97. package/dist/gesture/dragConfig.d.cts +298 -0
  98. package/dist/gesture/dragConfig.d.ts +298 -0
  99. package/dist/gesture/dragConfig.js +624 -0
  100. package/dist/gesture/elastic.cjs +44 -0
  101. package/dist/gesture/elastic.d.cts +4 -0
  102. package/dist/gesture/elastic.d.ts +4 -0
  103. package/dist/gesture/elastic.js +39 -0
  104. package/dist/gesture/handoffSession.cjs +161 -0
  105. package/dist/gesture/handoffSession.d.cts +35 -0
  106. package/dist/gesture/handoffSession.d.ts +35 -0
  107. package/dist/gesture/handoffSession.js +158 -0
  108. package/dist/gesture/index.cjs +37 -0
  109. package/dist/gesture/index.d.cts +12 -0
  110. package/dist/gesture/index.d.ts +12 -0
  111. package/dist/gesture/index.js +11 -0
  112. package/dist/gesture/projection.cjs +95 -0
  113. package/dist/gesture/projection.d.cts +7 -0
  114. package/dist/gesture/projection.d.ts +7 -0
  115. package/dist/gesture/projection.js +87 -0
  116. package/dist/gesture/session.cjs +162 -0
  117. package/dist/gesture/session.d.cts +29 -0
  118. package/dist/gesture/session.d.ts +29 -0
  119. package/dist/gesture/session.js +158 -0
  120. package/dist/gesture/types.cjs +5 -0
  121. package/dist/gesture/types.d.cts +13 -0
  122. package/dist/gesture/types.d.ts +13 -0
  123. package/dist/gesture/types.js +4 -0
  124. package/dist/gesture/viewportConstraints.cjs +38 -0
  125. package/dist/gesture/viewportConstraints.d.cts +6 -0
  126. package/dist/gesture/viewportConstraints.d.ts +6 -0
  127. package/dist/gesture/viewportConstraints.js +34 -0
  128. package/dist/graph.cjs +226 -0
  129. package/dist/graph.d.cts +2 -0
  130. package/dist/graph.d.ts +2 -0
  131. package/dist/graph.js +223 -0
  132. package/dist/index.cjs +140 -0
  133. package/dist/index.d.cts +33 -0
  134. package/dist/index.d.ts +33 -0
  135. package/dist/index.js +70 -0
  136. package/dist/inertia.cjs +214 -0
  137. package/dist/inertia.d.cts +53 -0
  138. package/dist/inertia.d.ts +53 -0
  139. package/dist/inertia.js +212 -0
  140. package/dist/instant.cjs +66 -0
  141. package/dist/instant.d.cts +16 -0
  142. package/dist/instant.d.ts +16 -0
  143. package/dist/instant.js +63 -0
  144. package/dist/internal-driver.cjs +81 -0
  145. package/dist/internal-driver.d.cts +19 -0
  146. package/dist/internal-driver.d.ts +19 -0
  147. package/dist/internal-driver.js +35 -0
  148. package/dist/keyframes.cjs +191 -0
  149. package/dist/keyframes.d.cts +13 -0
  150. package/dist/keyframes.d.ts +13 -0
  151. package/dist/keyframes.js +188 -0
  152. package/dist/layout/commitDetector.cjs +67 -0
  153. package/dist/layout/commitDetector.d.cts +21 -0
  154. package/dist/layout/commitDetector.d.ts +21 -0
  155. package/dist/layout/commitDetector.js +64 -0
  156. package/dist/layout/compose.cjs +67 -0
  157. package/dist/layout/compose.d.cts +30 -0
  158. package/dist/layout/compose.d.ts +30 -0
  159. package/dist/layout/compose.js +65 -0
  160. package/dist/layout/constants.cjs +13 -0
  161. package/dist/layout/constants.d.cts +6 -0
  162. package/dist/layout/constants.d.ts +6 -0
  163. package/dist/layout/constants.js +10 -0
  164. package/dist/layout/identity.cjs +700 -0
  165. package/dist/layout/identity.d.cts +67 -0
  166. package/dist/layout/identity.d.ts +67 -0
  167. package/dist/layout/identity.js +698 -0
  168. package/dist/layout/index.cjs +36 -0
  169. package/dist/layout/index.d.cts +18 -0
  170. package/dist/layout/index.d.ts +18 -0
  171. package/dist/layout/index.js +13 -0
  172. package/dist/layout/measure.cjs +81 -0
  173. package/dist/layout/measure.d.cts +43 -0
  174. package/dist/layout/measure.d.ts +43 -0
  175. package/dist/layout/measure.js +78 -0
  176. package/dist/layout/projection.cjs +99 -0
  177. package/dist/layout/projection.d.cts +23 -0
  178. package/dist/layout/projection.d.ts +23 -0
  179. package/dist/layout/projection.js +97 -0
  180. package/dist/layout/scroll.cjs +27 -0
  181. package/dist/layout/scroll.d.cts +13 -0
  182. package/dist/layout/scroll.d.ts +13 -0
  183. package/dist/layout/scroll.js +24 -0
  184. package/dist/layout/session.cjs +207 -0
  185. package/dist/layout/session.d.cts +73 -0
  186. package/dist/layout/session.d.ts +73 -0
  187. package/dist/layout/session.js +205 -0
  188. package/dist/layout/tree.cjs +826 -0
  189. package/dist/layout/tree.d.cts +70 -0
  190. package/dist/layout/tree.d.ts +70 -0
  191. package/dist/layout/tree.js +823 -0
  192. package/dist/layout/types.cjs +36 -0
  193. package/dist/layout/types.d.cts +31 -0
  194. package/dist/layout/types.d.ts +31 -0
  195. package/dist/layout/types.js +35 -0
  196. package/dist/motion-arc.cjs +184 -0
  197. package/dist/motion-arc.d.cts +78 -0
  198. package/dist/motion-arc.d.ts +78 -0
  199. package/dist/motion-arc.js +183 -0
  200. package/dist/motion-mix.cjs +205 -0
  201. package/dist/motion-mix.d.cts +3 -0
  202. package/dist/motion-mix.d.ts +3 -0
  203. package/dist/motion-mix.js +202 -0
  204. package/dist/motion-value-driver-port.cjs +636 -0
  205. package/dist/motion-value-driver-port.d.cts +406 -0
  206. package/dist/motion-value-driver-port.d.ts +406 -0
  207. package/dist/motion-value-driver-port.js +627 -0
  208. package/dist/motion-value.cjs +189 -0
  209. package/dist/motion-value.d.cts +51 -0
  210. package/dist/motion-value.d.ts +51 -0
  211. package/dist/motion-value.js +185 -0
  212. package/dist/presence/controller.cjs +657 -0
  213. package/dist/presence/controller.d.cts +6 -0
  214. package/dist/presence/controller.d.ts +6 -0
  215. package/dist/presence/controller.js +652 -0
  216. package/dist/presence/index.cjs +19 -0
  217. package/dist/presence/index.d.cts +4 -0
  218. package/dist/presence/index.d.ts +4 -0
  219. package/dist/presence/index.js +12 -0
  220. package/dist/presence/machine.cjs +50 -0
  221. package/dist/presence/machine.d.cts +10 -0
  222. package/dist/presence/machine.d.ts +10 -0
  223. package/dist/presence/machine.js +46 -0
  224. package/dist/presence/types.cjs +6 -0
  225. package/dist/presence/types.d.cts +32 -0
  226. package/dist/presence/types.d.ts +32 -0
  227. package/dist/presence/types.js +5 -0
  228. package/dist/repeat.cjs +311 -0
  229. package/dist/repeat.d.cts +174 -0
  230. package/dist/repeat.d.ts +174 -0
  231. package/dist/repeat.js +300 -0
  232. package/dist/spring.cjs +128 -0
  233. package/dist/spring.d.cts +18 -0
  234. package/dist/spring.d.ts +18 -0
  235. package/dist/spring.js +125 -0
  236. package/dist/subscriptions.cjs +74 -0
  237. package/dist/subscriptions.d.cts +19 -0
  238. package/dist/subscriptions.d.ts +19 -0
  239. package/dist/subscriptions.js +69 -0
  240. package/dist/subset/index.cjs +23 -0
  241. package/dist/subset/index.d.cts +6 -0
  242. package/dist/subset/index.d.ts +6 -0
  243. package/dist/subset/index.js +13 -0
  244. package/dist/subset/normalize.cjs +90 -0
  245. package/dist/subset/normalize.d.cts +10 -0
  246. package/dist/subset/normalize.d.ts +10 -0
  247. package/dist/subset/normalize.js +85 -0
  248. package/dist/subset/registry.cjs +259 -0
  249. package/dist/subset/registry.d.cts +25 -0
  250. package/dist/subset/registry.d.ts +25 -0
  251. package/dist/subset/registry.js +256 -0
  252. package/dist/subset/resolve.cjs +98 -0
  253. package/dist/subset/resolve.d.cts +24 -0
  254. package/dist/subset/resolve.d.ts +24 -0
  255. package/dist/subset/resolve.js +90 -0
  256. package/dist/timing.cjs +206 -0
  257. package/dist/timing.d.cts +17 -0
  258. package/dist/timing.d.ts +17 -0
  259. package/dist/timing.js +201 -0
  260. package/dist/transformTemplate.cjs +219 -0
  261. package/dist/transformTemplate.d.cts +29 -0
  262. package/dist/transformTemplate.d.ts +29 -0
  263. package/dist/transformTemplate.js +215 -0
  264. package/dist/transition.cjs +231 -0
  265. package/dist/transition.d.cts +89 -0
  266. package/dist/transition.d.ts +89 -0
  267. package/dist/transition.js +223 -0
  268. package/dist/types.cjs +4 -0
  269. package/dist/types.d.cts +175 -0
  270. package/dist/types.d.ts +175 -0
  271. package/dist/types.js +3 -0
  272. package/dist/value-types/color.cjs +220 -0
  273. package/dist/value-types/color.d.cts +19 -0
  274. package/dist/value-types/color.d.ts +19 -0
  275. package/dist/value-types/color.js +218 -0
  276. package/dist/value-types/complex.cjs +160 -0
  277. package/dist/value-types/complex.d.cts +19 -0
  278. package/dist/value-types/complex.d.ts +19 -0
  279. package/dist/value-types/complex.js +155 -0
  280. package/dist/value-types/constants.cjs +15 -0
  281. package/dist/value-types/constants.d.cts +6 -0
  282. package/dist/value-types/constants.d.ts +6 -0
  283. package/dist/value-types/constants.js +12 -0
  284. package/dist/value-types/discrete.cjs +62 -0
  285. package/dist/value-types/discrete.d.cts +4 -0
  286. package/dist/value-types/discrete.d.ts +4 -0
  287. package/dist/value-types/discrete.js +56 -0
  288. package/dist/value-types/index.cjs +59 -0
  289. package/dist/value-types/index.d.cts +14 -0
  290. package/dist/value-types/index.d.ts +14 -0
  291. package/dist/value-types/index.js +24 -0
  292. package/dist/value-types/measure-resolve.cjs +325 -0
  293. package/dist/value-types/measure-resolve.d.cts +91 -0
  294. package/dist/value-types/measure-resolve.d.ts +91 -0
  295. package/dist/value-types/measure-resolve.js +313 -0
  296. package/dist/value-types/mix.cjs +90 -0
  297. package/dist/value-types/mix.d.cts +27 -0
  298. package/dist/value-types/mix.d.ts +27 -0
  299. package/dist/value-types/mix.js +85 -0
  300. package/dist/value-types/named-colors.cjs +61 -0
  301. package/dist/value-types/named-colors.d.cts +2 -0
  302. package/dist/value-types/named-colors.d.ts +2 -0
  303. package/dist/value-types/named-colors.js +58 -0
  304. package/dist/value-types/numeric.cjs +86 -0
  305. package/dist/value-types/numeric.d.cts +16 -0
  306. package/dist/value-types/numeric.d.ts +16 -0
  307. package/dist/value-types/numeric.js +79 -0
  308. package/dist/worklet-layout/config/constants.js +39 -0
  309. package/dist/worklet-layout/layout/constants.js +11 -0
  310. package/dist/worklet-layout/layout/identity.js +699 -0
  311. package/dist/worklet-layout/layout/projection.js +98 -0
  312. package/dist/worklet-layout/layout/session.js +206 -0
  313. package/dist/worklet-layout/layout/tree.js +824 -0
  314. package/dist/worklet-layout/layout/types.js +36 -0
  315. package/dist/worklet-layout/spring.js +125 -0
  316. package/dist/worklet-layout/timing.js +201 -0
  317. package/dist/worklet-layout/transition.js +223 -0
  318. package/dist/worklet-layout.cjs +47 -0
  319. package/dist/worklet-layout.d.cts +15 -0
  320. package/dist/worklet-layout.d.ts +15 -0
  321. package/dist/worklet-layout.js +28 -0
  322. package/dist/wrap.cjs +7 -0
  323. package/dist/wrap.d.cts +1 -0
  324. package/dist/wrap.d.ts +1 -0
  325. package/dist/wrap.js +4 -0
  326. package/package.json +45 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ // @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
3
+ // Analytic scalar spring solver (SPEC-SPRING REQ-SPRING-001/002/008/010/011). A pure closed-form
4
+ // evaluator — value(t)/velocity(t) computed directly per damping regime, never stepped by Euler/RK.
5
+ // Host-agnostic (REQ-CORE-003): imports nothing from motion/react. Numerics are pinned to
6
+ // motion@12.42.2 (see specs/SPEC-SPRING.md) and proven by golden parity in spring.test.ts.
7
+ //
8
+ // Unit/sign convention (REQ-SPRING-008): time `t` is ms; the undamped angular frequency ω0 is per-ms
9
+ // (rad/ms); internal velocity is px/ms. The public boundary is px/s. The solver seeds internal
10
+ // v0 = −(seed.velocity_px_per_s / 1000) so the position's initial derivative x'(0) equals the true
11
+ // incoming public velocity; returned velocity is internal px/ms × 1000. A sign flip here inverts the
12
+ // first-bounce direction (a conformance failure), so it is asserted by the sign test.
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.assertTwoKeyframes = assertTwoKeyframes;
15
+ exports.springGenerator = springGenerator;
16
+ // REQ-SPRING-013 / REQ-API-005: the scalar spring/inertia generator is two-keyframe ([from, to]). More
17
+ // than two keyframes is a dev-time error — fail loudly, never silently truncate.
18
+ function assertTwoKeyframes(keyframes) {
19
+ if (keyframes.length > 2) {
20
+ throw new Error(`Native Motion spring is scalar two-keyframe ([from, to]); got ${keyframes.length} keyframes.`);
21
+ }
22
+ }
23
+ // Rest thresholds (REQ-SPRING-003), pinned from motion source (springDefaults). The scale switches to
24
+ // the granular values when the move is tiny (REQ-SPRING-011). Owned here — SPEC-SPRING tightens the
25
+ // core seed constants.
26
+ const REST_SPEED = { default: 2, granular: 0.01 };
27
+ const REST_DELTA = { default: 0.5, granular: 0.005 };
28
+ const GRANULAR_SCALE = 5;
29
+ /**
30
+ * Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
31
+ * (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
32
+ * (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
33
+ */
34
+ // alloc-ok: lifecycle-edge — the factory resolves physics once per animate command; per-frame work
35
+ // is sample() below, which mutates the reused record and allocates nothing.
36
+ function springGenerator(target, physics, settle) {
37
+ const { stiffness, damping, mass } = physics;
38
+ const omega0 = Math.sqrt(stiffness / mass) / 1000; // undamped angular freq, rad/ms
39
+ const z = damping / (2 * Math.sqrt(stiffness * mass)); // damping ratio; regime by strict <1/===1/>1
40
+ const settleDuration = settle?.settleDuration ?? null;
41
+ // alloc-ok: lifecycle-edge — generator construction at command/interruption time; the closed
42
+ // forms and the one reused sample record are built here, never per frame.
43
+ return (seed) => {
44
+ const origin = seed.from;
45
+ const delta = target - origin; // initialDelta
46
+ const v0 = -(seed.velocity / 1000); // internal initial velocity, px/ms (REQ-SPRING-008)
47
+ // Rest thresholds by move scale (REQ-SPRING-011); per-context overrides win over the scale default.
48
+ const granular = Math.abs(delta) < GRANULAR_SCALE;
49
+ // Pinned zero-as-absent (r15 c6e31fd509a8): Motion 12.42.2 reads these thresholds falsy —
50
+ // an explicit 0 falls back to the scale defaults, never an unbounded active spring.
51
+ const restSpeed = settle?.restSpeed || (granular ? REST_SPEED.granular : REST_SPEED.default);
52
+ const restDelta = settle?.restDelta || (granular ? REST_DELTA.granular : REST_DELTA.default);
53
+ // Regime-specific closed forms. value(t) → px; velocityPerMs(t) → px/ms (= x'(t)).
54
+ // Each is a NAMED function (frame-path manifest anonymous-sampler repair): the qualified
55
+ // symbols are springGenerator.factory.<regime>Value/<regime>Velocity.
56
+ let value;
57
+ let velocityPerMs;
58
+ if (z < 1) {
59
+ // Underdamped (REQ-SPRING-002).
60
+ const wd = omega0 * Math.sqrt(1 - z * z); // damped angular freq
61
+ const a = (v0 + z * omega0 * delta) / wd;
62
+ const sinCoeff = z * omega0 * a + delta * wd;
63
+ const cosCoeff = z * omega0 * delta - a * wd;
64
+ function underdampedValue(t) {
65
+ const env = Math.exp(-z * omega0 * t);
66
+ return target - env * (a * Math.sin(wd * t) + delta * Math.cos(wd * t));
67
+ }
68
+ function underdampedVelocity(t) {
69
+ const env = Math.exp(-z * omega0 * t);
70
+ return env * (sinCoeff * Math.sin(wd * t) + cosCoeff * Math.cos(wd * t));
71
+ }
72
+ value = underdampedValue;
73
+ velocityPerMs = underdampedVelocity;
74
+ }
75
+ else if (z === 1) {
76
+ // Critically damped (REQ-SPRING-002).
77
+ const c = v0 + omega0 * delta;
78
+ function criticalValue(t) {
79
+ return target - Math.exp(-omega0 * t) * (delta + c * t);
80
+ }
81
+ function criticalVelocity(t) {
82
+ return Math.exp(-omega0 * t) * (omega0 * c * t - v0);
83
+ }
84
+ value = criticalValue;
85
+ velocityPerMs = criticalVelocity;
86
+ }
87
+ else {
88
+ // Overdamped — pure-exponential form (REQ-SPRING-010). Both λ < 0 → no overflow, no sinh/cosh
89
+ // cap; the capped form (Motion #1207) is never used. λslow uses −ω0/(z+√(z²−1)) to avoid
90
+ // catastrophic cancellation at large z.
91
+ const root = Math.sqrt(z * z - 1);
92
+ const wd = omega0 * root;
93
+ const lambdaSlow = -omega0 / (z + root);
94
+ const lambdaFast = -omega0 * (z + root);
95
+ const cSlow = (v0 + z * omega0 * delta + wd * delta) / (2 * wd);
96
+ const cFast = delta - cSlow;
97
+ function overdampedValue(t) {
98
+ return target - (cSlow * Math.exp(lambdaSlow * t) + cFast * Math.exp(lambdaFast * t));
99
+ }
100
+ function overdampedVelocity(t) {
101
+ return -(cSlow * lambdaSlow * Math.exp(lambdaSlow * t) +
102
+ cFast * lambdaFast * Math.exp(lambdaFast * t));
103
+ }
104
+ value = overdampedValue;
105
+ velocityPerMs = overdampedVelocity;
106
+ }
107
+ // One sample record per generator, mutated and returned every call — motion's own generators
108
+ // return their reused `state` the same way. Per-frame sampling must not allocate
109
+ // (REQ-DRIVER-015); callers read the scalars immediately and never retain the record.
110
+ const out = { value: seed.from, velocity: seed.velocity, done: false };
111
+ return {
112
+ sample(elapsedMs) {
113
+ const current = value(elapsedMs);
114
+ const velocity = velocityPerMs(elapsedMs) * 1000; // → px/s (REQ-SPRING-008)
115
+ // Settle (REQ-SPRING-011): findSpring (duration) springs finish deterministically at
116
+ // t ≥ settleDuration; physics + visualDuration springs rest by speed AND delta. On done, snap to
117
+ // target (matches motion's next()), so an interruption re-seeds from an exact endpoint.
118
+ const done = settleDuration !== null
119
+ ? elapsedMs >= settleDuration
120
+ : Math.abs(velocity) <= restSpeed && Math.abs(target - current) <= restDelta;
121
+ out.value = done ? target : current;
122
+ out.velocity = velocity;
123
+ out.done = done;
124
+ return out;
125
+ },
126
+ };
127
+ };
128
+ }
@@ -0,0 +1,18 @@
1
+ import type { GeneratorFactory } from "./types.cjs";
2
+ export interface SpringPhysics {
3
+ stiffness: number;
4
+ damping: number;
5
+ mass: number;
6
+ }
7
+ export interface SpringSettleOptions {
8
+ restSpeed?: number;
9
+ restDelta?: number;
10
+ settleDuration?: number | null;
11
+ }
12
+ export declare function assertTwoKeyframes(keyframes: readonly unknown[]): void;
13
+ /**
14
+ * Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
15
+ * (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
16
+ * (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
17
+ */
18
+ export declare function springGenerator(target: number, physics: SpringPhysics, settle?: SpringSettleOptions): GeneratorFactory;
@@ -0,0 +1,18 @@
1
+ import type { GeneratorFactory } from "./types.js";
2
+ export interface SpringPhysics {
3
+ stiffness: number;
4
+ damping: number;
5
+ mass: number;
6
+ }
7
+ export interface SpringSettleOptions {
8
+ restSpeed?: number;
9
+ restDelta?: number;
10
+ settleDuration?: number | null;
11
+ }
12
+ export declare function assertTwoKeyframes(keyframes: readonly unknown[]): void;
13
+ /**
14
+ * Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
15
+ * (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
16
+ * (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
17
+ */
18
+ export declare function springGenerator(target: number, physics: SpringPhysics, settle?: SpringSettleOptions): GeneratorFactory;
package/dist/spring.js ADDED
@@ -0,0 +1,125 @@
1
+ 'worklet';
2
+ // @frame-path — executes inside the UI-runtime frame step; allocation-gated (REQ-DRIVER-021).
3
+ // Analytic scalar spring solver (SPEC-SPRING REQ-SPRING-001/002/008/010/011). A pure closed-form
4
+ // evaluator — value(t)/velocity(t) computed directly per damping regime, never stepped by Euler/RK.
5
+ // Host-agnostic (REQ-CORE-003): imports nothing from motion/react. Numerics are pinned to
6
+ // motion@12.42.2 (see specs/SPEC-SPRING.md) and proven by golden parity in spring.test.ts.
7
+ //
8
+ // Unit/sign convention (REQ-SPRING-008): time `t` is ms; the undamped angular frequency ω0 is per-ms
9
+ // (rad/ms); internal velocity is px/ms. The public boundary is px/s. The solver seeds internal
10
+ // v0 = −(seed.velocity_px_per_s / 1000) so the position's initial derivative x'(0) equals the true
11
+ // incoming public velocity; returned velocity is internal px/ms × 1000. A sign flip here inverts the
12
+ // first-bounce direction (a conformance failure), so it is asserted by the sign test.
13
+ // REQ-SPRING-013 / REQ-API-005: the scalar spring/inertia generator is two-keyframe ([from, to]). More
14
+ // than two keyframes is a dev-time error — fail loudly, never silently truncate.
15
+ export function assertTwoKeyframes(keyframes) {
16
+ if (keyframes.length > 2) {
17
+ throw new Error(`Native Motion spring is scalar two-keyframe ([from, to]); got ${keyframes.length} keyframes.`);
18
+ }
19
+ }
20
+ // Rest thresholds (REQ-SPRING-003), pinned from motion source (springDefaults). The scale switches to
21
+ // the granular values when the move is tiny (REQ-SPRING-011). Owned here — SPEC-SPRING tightens the
22
+ // core seed constants.
23
+ const REST_SPEED = { default: 2, granular: 0.01 };
24
+ const REST_DELTA = { default: 0.5, granular: 0.005 };
25
+ const GRANULAR_SCALE = 5;
26
+ /**
27
+ * Build a spring `GeneratorFactory` from a target and resolved physics. The seed supplies the origin
28
+ * (`from`) and the incoming public velocity (`velocity`, px/s); transition-config resolution
29
+ * (findSpring / visualDuration / defaults) is a separate concern (SPEC-SPRING Milestone 2).
30
+ */
31
+ // alloc-ok: lifecycle-edge — the factory resolves physics once per animate command; per-frame work
32
+ // is sample() below, which mutates the reused record and allocates nothing.
33
+ export function springGenerator(target, physics, settle) {
34
+ const { stiffness, damping, mass } = physics;
35
+ const omega0 = Math.sqrt(stiffness / mass) / 1000; // undamped angular freq, rad/ms
36
+ const z = damping / (2 * Math.sqrt(stiffness * mass)); // damping ratio; regime by strict <1/===1/>1
37
+ const settleDuration = settle?.settleDuration ?? null;
38
+ // alloc-ok: lifecycle-edge — generator construction at command/interruption time; the closed
39
+ // forms and the one reused sample record are built here, never per frame.
40
+ return (seed) => {
41
+ const origin = seed.from;
42
+ const delta = target - origin; // initialDelta
43
+ const v0 = -(seed.velocity / 1000); // internal initial velocity, px/ms (REQ-SPRING-008)
44
+ // Rest thresholds by move scale (REQ-SPRING-011); per-context overrides win over the scale default.
45
+ const granular = Math.abs(delta) < GRANULAR_SCALE;
46
+ // Pinned zero-as-absent (r15 c6e31fd509a8): Motion 12.42.2 reads these thresholds falsy —
47
+ // an explicit 0 falls back to the scale defaults, never an unbounded active spring.
48
+ const restSpeed = settle?.restSpeed || (granular ? REST_SPEED.granular : REST_SPEED.default);
49
+ const restDelta = settle?.restDelta || (granular ? REST_DELTA.granular : REST_DELTA.default);
50
+ // Regime-specific closed forms. value(t) → px; velocityPerMs(t) → px/ms (= x'(t)).
51
+ // Each is a NAMED function (frame-path manifest anonymous-sampler repair): the qualified
52
+ // symbols are springGenerator.factory.<regime>Value/<regime>Velocity.
53
+ let value;
54
+ let velocityPerMs;
55
+ if (z < 1) {
56
+ // Underdamped (REQ-SPRING-002).
57
+ const wd = omega0 * Math.sqrt(1 - z * z); // damped angular freq
58
+ const a = (v0 + z * omega0 * delta) / wd;
59
+ const sinCoeff = z * omega0 * a + delta * wd;
60
+ const cosCoeff = z * omega0 * delta - a * wd;
61
+ function underdampedValue(t) {
62
+ const env = Math.exp(-z * omega0 * t);
63
+ return target - env * (a * Math.sin(wd * t) + delta * Math.cos(wd * t));
64
+ }
65
+ function underdampedVelocity(t) {
66
+ const env = Math.exp(-z * omega0 * t);
67
+ return env * (sinCoeff * Math.sin(wd * t) + cosCoeff * Math.cos(wd * t));
68
+ }
69
+ value = underdampedValue;
70
+ velocityPerMs = underdampedVelocity;
71
+ }
72
+ else if (z === 1) {
73
+ // Critically damped (REQ-SPRING-002).
74
+ const c = v0 + omega0 * delta;
75
+ function criticalValue(t) {
76
+ return target - Math.exp(-omega0 * t) * (delta + c * t);
77
+ }
78
+ function criticalVelocity(t) {
79
+ return Math.exp(-omega0 * t) * (omega0 * c * t - v0);
80
+ }
81
+ value = criticalValue;
82
+ velocityPerMs = criticalVelocity;
83
+ }
84
+ else {
85
+ // Overdamped — pure-exponential form (REQ-SPRING-010). Both λ < 0 → no overflow, no sinh/cosh
86
+ // cap; the capped form (Motion #1207) is never used. λslow uses −ω0/(z+√(z²−1)) to avoid
87
+ // catastrophic cancellation at large z.
88
+ const root = Math.sqrt(z * z - 1);
89
+ const wd = omega0 * root;
90
+ const lambdaSlow = -omega0 / (z + root);
91
+ const lambdaFast = -omega0 * (z + root);
92
+ const cSlow = (v0 + z * omega0 * delta + wd * delta) / (2 * wd);
93
+ const cFast = delta - cSlow;
94
+ function overdampedValue(t) {
95
+ return target - (cSlow * Math.exp(lambdaSlow * t) + cFast * Math.exp(lambdaFast * t));
96
+ }
97
+ function overdampedVelocity(t) {
98
+ return -(cSlow * lambdaSlow * Math.exp(lambdaSlow * t) +
99
+ cFast * lambdaFast * Math.exp(lambdaFast * t));
100
+ }
101
+ value = overdampedValue;
102
+ velocityPerMs = overdampedVelocity;
103
+ }
104
+ // One sample record per generator, mutated and returned every call — motion's own generators
105
+ // return their reused `state` the same way. Per-frame sampling must not allocate
106
+ // (REQ-DRIVER-015); callers read the scalars immediately and never retain the record.
107
+ const out = { value: seed.from, velocity: seed.velocity, done: false };
108
+ return {
109
+ sample(elapsedMs) {
110
+ const current = value(elapsedMs);
111
+ const velocity = velocityPerMs(elapsedMs) * 1000; // → px/s (REQ-SPRING-008)
112
+ // Settle (REQ-SPRING-011): findSpring (duration) springs finish deterministically at
113
+ // t ≥ settleDuration; physics + visualDuration springs rest by speed AND delta. On done, snap to
114
+ // target (matches motion's next()), so an interruption re-seeds from an exact endpoint.
115
+ const done = settleDuration !== null
116
+ ? elapsedMs >= settleDuration
117
+ : Math.abs(velocity) <= restSpeed && Math.abs(target - current) <= restDelta;
118
+ out.value = done ? target : current;
119
+ out.velocity = velocity;
120
+ out.done = done;
121
+ return out;
122
+ },
123
+ };
124
+ };
125
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ // Shared subscription registry for motion values: value-change listeners (`subscribe`) and named
3
+ // lifecycle events (`on`). Neither path triggers a host render. Extracted so `MotionValueImpl` and
4
+ // `DerivedValue` share one implementation instead of duplicating it (REQ-VALUE-001/002).
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Subscriptions = void 0;
7
+ exports.brandDerivedDependentListener = brandDerivedDependentListener;
8
+ // Derived-dependent branding (REQ-VALUE-015): the MotionValue driver port classifies whether a
9
+ // transported write reached a derived dependent without holding a graph handle. Derived values
10
+ // brand their input subscription so observer presence is queryable without invoking listeners.
11
+ const derivedDependentListeners = new WeakSet();
12
+ /** @internal Brand a change listener as a derived value's input subscription. */
13
+ function brandDerivedDependentListener(listener) {
14
+ derivedDependentListeners.add(listener);
15
+ return listener;
16
+ }
17
+ class Subscriptions {
18
+ #change = new Set();
19
+ // `on('change')` listeners take the latest value (the pinned payload, r15 a62d87e5f419);
20
+ // lifecycle listeners take none — one storage, lifecycle emission simply passes nothing.
21
+ #events = new Map();
22
+ subscribe(listener) {
23
+ this.#change.add(listener);
24
+ return () => {
25
+ this.#change.delete(listener);
26
+ };
27
+ }
28
+ on(event, listener) {
29
+ let listeners = this.#events.get(event);
30
+ if (!listeners) {
31
+ listeners = new Set();
32
+ this.#events.set(event, listeners);
33
+ }
34
+ listeners.add(listener);
35
+ return () => {
36
+ listeners.delete(listener);
37
+ };
38
+ }
39
+ /** Notify change subscribers and any `on('change')` listeners with the current value. */
40
+ emitChange(value) {
41
+ for (const listener of this.#change)
42
+ listener(value);
43
+ const listeners = this.#events.get('change');
44
+ if (listeners)
45
+ for (const listener of listeners)
46
+ listener(value);
47
+ }
48
+ /** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
49
+ emit(event) {
50
+ const listeners = this.#events.get(event);
51
+ // Lifecycle listeners are payload-free by contract (the overloads above); the stored shape
52
+ // carries T only for the 'change' set.
53
+ if (listeners)
54
+ for (const listener of listeners)
55
+ listener();
56
+ }
57
+ /** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
58
+ hasChangeObservers() {
59
+ return this.#change.size > 0 || (this.#events.get('change')?.size ?? 0) > 0;
60
+ }
61
+ /** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
62
+ hasDerivedDependentObserver() {
63
+ for (const listener of this.#change) {
64
+ if (derivedDependentListeners.has(listener))
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ clear() {
70
+ this.#change.clear();
71
+ this.#events.clear();
72
+ }
73
+ }
74
+ exports.Subscriptions = Subscriptions;
@@ -0,0 +1,19 @@
1
+ import type { MotionValueEvent, ValueChangeListener } from "./types.cjs";
2
+ /** @internal Brand a change listener as a derived value's input subscription. */
3
+ export declare function brandDerivedDependentListener<L extends (...args: never[]) => void>(listener: L): L;
4
+ export declare class Subscriptions<T> {
5
+ #private;
6
+ subscribe(listener: ValueChangeListener<T>): () => void;
7
+ on(event: 'change', listener: (latest: T) => void): () => void;
8
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
9
+ on(event: MotionValueEvent, listener: (latest: T) => void): () => void;
10
+ /** Notify change subscribers and any `on('change')` listeners with the current value. */
11
+ emitChange(value: T): void;
12
+ /** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
13
+ emit(event: Exclude<MotionValueEvent, 'change'>): void;
14
+ /** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
15
+ hasChangeObservers(): boolean;
16
+ /** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
17
+ hasDerivedDependentObserver(): boolean;
18
+ clear(): void;
19
+ }
@@ -0,0 +1,19 @@
1
+ import type { MotionValueEvent, ValueChangeListener } from "./types.js";
2
+ /** @internal Brand a change listener as a derived value's input subscription. */
3
+ export declare function brandDerivedDependentListener<L extends (...args: never[]) => void>(listener: L): L;
4
+ export declare class Subscriptions<T> {
5
+ #private;
6
+ subscribe(listener: ValueChangeListener<T>): () => void;
7
+ on(event: 'change', listener: (latest: T) => void): () => void;
8
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
9
+ on(event: MotionValueEvent, listener: (latest: T) => void): () => void;
10
+ /** Notify change subscribers and any `on('change')` listeners with the current value. */
11
+ emitChange(value: T): void;
12
+ /** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
13
+ emit(event: Exclude<MotionValueEvent, 'change'>): void;
14
+ /** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
15
+ hasChangeObservers(): boolean;
16
+ /** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
17
+ hasDerivedDependentObserver(): boolean;
18
+ clear(): void;
19
+ }
@@ -0,0 +1,69 @@
1
+ // Shared subscription registry for motion values: value-change listeners (`subscribe`) and named
2
+ // lifecycle events (`on`). Neither path triggers a host render. Extracted so `MotionValueImpl` and
3
+ // `DerivedValue` share one implementation instead of duplicating it (REQ-VALUE-001/002).
4
+ // Derived-dependent branding (REQ-VALUE-015): the MotionValue driver port classifies whether a
5
+ // transported write reached a derived dependent without holding a graph handle. Derived values
6
+ // brand their input subscription so observer presence is queryable without invoking listeners.
7
+ const derivedDependentListeners = new WeakSet();
8
+ /** @internal Brand a change listener as a derived value's input subscription. */
9
+ export function brandDerivedDependentListener(listener) {
10
+ derivedDependentListeners.add(listener);
11
+ return listener;
12
+ }
13
+ export class Subscriptions {
14
+ #change = new Set();
15
+ // `on('change')` listeners take the latest value (the pinned payload, r15 a62d87e5f419);
16
+ // lifecycle listeners take none — one storage, lifecycle emission simply passes nothing.
17
+ #events = new Map();
18
+ subscribe(listener) {
19
+ this.#change.add(listener);
20
+ return () => {
21
+ this.#change.delete(listener);
22
+ };
23
+ }
24
+ on(event, listener) {
25
+ let listeners = this.#events.get(event);
26
+ if (!listeners) {
27
+ listeners = new Set();
28
+ this.#events.set(event, listeners);
29
+ }
30
+ listeners.add(listener);
31
+ return () => {
32
+ listeners.delete(listener);
33
+ };
34
+ }
35
+ /** Notify change subscribers and any `on('change')` listeners with the current value. */
36
+ emitChange(value) {
37
+ for (const listener of this.#change)
38
+ listener(value);
39
+ const listeners = this.#events.get('change');
40
+ if (listeners)
41
+ for (const listener of listeners)
42
+ listener(value);
43
+ }
44
+ /** Fire a named lifecycle event (e.g. `animationStart`/`animationComplete`). */
45
+ emit(event) {
46
+ const listeners = this.#events.get(event);
47
+ // Lifecycle listeners are payload-free by contract (the overloads above); the stored shape
48
+ // carries T only for the 'change' set.
49
+ if (listeners)
50
+ for (const listener of listeners)
51
+ listener();
52
+ }
53
+ /** @internal Driver-port seam: whether any change observer (`subscribe`/`on('change')`) exists. */
54
+ hasChangeObservers() {
55
+ return this.#change.size > 0 || (this.#events.get('change')?.size ?? 0) > 0;
56
+ }
57
+ /** @internal Driver-port seam: whether a branded derived dependent is among the change listeners. */
58
+ hasDerivedDependentObserver() {
59
+ for (const listener of this.#change) {
60
+ if (derivedDependentListeners.has(listener))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ clear() {
66
+ this.#change.clear();
67
+ this.#events.clear();
68
+ }
69
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // Public surface of the SPEC-UNIVERSAL-SUBSET subsystem: the frozen universal-subset registry and its
3
+ // entry schema types. Host-agnostic (REQ-SUBSET-010) — the native runtime, the web shim, and the
4
+ // conformance runner all read this one object. Re-exported additively from the core index. The loud-fail
5
+ // resolver (Milestone 2) and the normalizer (Milestone 3) join this surface as they land.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.normalizeCapability = exports.epsilonFor = exports.resolveTargetCapabilities = exports.resolveCapability = exports.hostCapabilities = exports.UnsupportedCapabilityError = exports.UNIVERSAL_SUBSET = void 0;
8
+ var registry_1 = require("./registry.cjs");
9
+ Object.defineProperty(exports, "UNIVERSAL_SUBSET", { enumerable: true, get: function () { return registry_1.UNIVERSAL_SUBSET; } });
10
+ // Loud-fail capability resolver (REQ-SUBSET-014): the fail-closed subset-membership gate + typed error.
11
+ // The verdict-shaped resolution/normalization forms are CORE-INTERNAL (M3 r8 major
12
+ // 4a5b686f1f5f): the ratified SUBSET public seam enumerates the throwing APIs only —
13
+ // internal consumers import the verdicts from the modules directly.
14
+ var resolve_1 = require("./resolve.cjs");
15
+ Object.defineProperty(exports, "UnsupportedCapabilityError", { enumerable: true, get: function () { return resolve_1.UnsupportedCapabilityError; } });
16
+ Object.defineProperty(exports, "hostCapabilities", { enumerable: true, get: function () { return resolve_1.hostCapabilities; } });
17
+ Object.defineProperty(exports, "resolveCapability", { enumerable: true, get: function () { return resolve_1.resolveCapability; } });
18
+ Object.defineProperty(exports, "resolveTargetCapabilities", { enumerable: true, get: function () { return resolve_1.resolveTargetCapabilities; } });
19
+ // Per-value-type normalizer + epsilon floors (REQ-SUBSET-016): reduces engine-native encodings to one
20
+ // comparable value, delegating to VALUE-TYPES (one parser, one set of floors).
21
+ var normalize_1 = require("./normalize.cjs");
22
+ Object.defineProperty(exports, "epsilonFor", { enumerable: true, get: function () { return normalize_1.epsilonFor; } });
23
+ Object.defineProperty(exports, "normalizeCapability", { enumerable: true, get: function () { return normalize_1.normalizeCapability; } });
@@ -0,0 +1,6 @@
1
+ export { UNIVERSAL_SUBSET } from "./registry.cjs";
2
+ export type { CapabilityClass, CapabilityEntry, ReferenceDisposition, ShippingStatus, Universality, ValueType, WebEnginePath, } from "./registry.cjs";
3
+ export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.cjs";
4
+ export type { HostCapabilities, UnsupportedReason } from "./resolve.cjs";
5
+ export { epsilonFor, normalizeCapability } from "./normalize.cjs";
6
+ export type { NormalizedValue } from "./normalize.cjs";
@@ -0,0 +1,6 @@
1
+ export { UNIVERSAL_SUBSET } from "./registry.js";
2
+ export type { CapabilityClass, CapabilityEntry, ReferenceDisposition, ShippingStatus, Universality, ValueType, WebEnginePath, } from "./registry.js";
3
+ export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.js";
4
+ export type { HostCapabilities, UnsupportedReason } from "./resolve.js";
5
+ export { epsilonFor, normalizeCapability } from "./normalize.js";
6
+ export type { NormalizedValue } from "./normalize.js";
@@ -0,0 +1,13 @@
1
+ // Public surface of the SPEC-UNIVERSAL-SUBSET subsystem: the frozen universal-subset registry and its
2
+ // entry schema types. Host-agnostic (REQ-SUBSET-010) — the native runtime, the web shim, and the
3
+ // conformance runner all read this one object. Re-exported additively from the core index. The loud-fail
4
+ // resolver (Milestone 2) and the normalizer (Milestone 3) join this surface as they land.
5
+ export { UNIVERSAL_SUBSET } from "./registry.js";
6
+ // Loud-fail capability resolver (REQ-SUBSET-014): the fail-closed subset-membership gate + typed error.
7
+ // The verdict-shaped resolution/normalization forms are CORE-INTERNAL (M3 r8 major
8
+ // 4a5b686f1f5f): the ratified SUBSET public seam enumerates the throwing APIs only —
9
+ // internal consumers import the verdicts from the modules directly.
10
+ export { UnsupportedCapabilityError, hostCapabilities, resolveCapability, resolveTargetCapabilities, } from "./resolve.js";
11
+ // Per-value-type normalizer + epsilon floors (REQ-SUBSET-016): reduces engine-native encodings to one
12
+ // comparable value, delegating to VALUE-TYPES (one parser, one set of floors).
13
+ export { epsilonFor, normalizeCapability } from "./normalize.js";
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ // SPEC-UNIVERSAL-SUBSET §3 — the per-value-type normalizer + epsilon floors (REQ-SUBSET-016). Reduces an
3
+ // engine-native encoding (RN unitless dp number, web `px`/`deg`/`rad` string, any color syntax) to ONE
4
+ // comparable value per value type so the cross-engine comparator compares like for like. It DELEGATES to
5
+ // VALUE-TYPES for every parse and for the epsilon floors — there is exactly one parser and one set of
6
+ // floors in the codebase (REQ-SUBSET-016 "adopted verbatim"); this file re-declares neither.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.normalizeCapabilityVerdict = normalizeCapabilityVerdict;
9
+ exports.normalizeCapability = normalizeCapability;
10
+ exports.epsilonFor = epsilonFor;
11
+ const value_types_1 = require("../value-types/index.cjs");
12
+ const color_1 = require("../value-types/color.cjs");
13
+ const numeric_1 = require("../value-types/numeric.cjs");
14
+ const DEG_PER_RAD = 180 / Math.PI;
15
+ // The units each numeric value type may carry and still reduce to ONE comparable value. `''` is the
16
+ // engine-native unitless encoding (RN dp for length, a bare scalar for unitless, degrees for angle).
17
+ // length is px + dp ONLY — vw/vh/% are viewport-/parent-relative and NOT px-comparable without a layout
18
+ // context (they are excluded from the guaranteed subset, REQ-SUBSET-011/§8); an angle unit on a length
19
+ // (or vice versa) is a category error. Anything outside the set fails loud rather than being silently
20
+ // reduced to a bare number that would make incompatible encodings compare equal (REQ-SUBSET-016).
21
+ const COMPATIBLE_UNITS = {
22
+ length: new Set(['', 'px']),
23
+ unitless: new Set(['']),
24
+ angle: new Set(['', 'deg', 'rad']),
25
+ };
26
+ const refuseNormalize = (message) => ({
27
+ value: null,
28
+ refusal: new Error(message),
29
+ });
30
+ function scalarVerdict(kind, engineValue) {
31
+ if (typeof engineValue === 'number')
32
+ return { value: engineValue, refusal: null };
33
+ const parsed = (0, numeric_1.parseNumericVerdict)(engineValue);
34
+ if (parsed.refusal !== null)
35
+ return refuseNormalize(parsed.refusal);
36
+ const { value, unit } = parsed.parsed;
37
+ if (!COMPATIBLE_UNITS[kind].has(unit)) {
38
+ const allowed = [...COMPATIBLE_UNITS[kind]].map((u) => u || '<unitless>').join(', ');
39
+ return refuseNormalize(`Incompatible unit '${unit}' for a ${kind} value ${JSON.stringify(engineValue)}: it cannot be ` +
40
+ `normalized as ${kind} (compatible units: ${allowed}).`);
41
+ }
42
+ return { value: kind === 'angle' && unit === 'rad' ? value * DEG_PER_RAD : value, refusal: null };
43
+ }
44
+ function normalizeCapabilityVerdict(entry, engineValue) {
45
+ switch (entry.valueType) {
46
+ case 'length':
47
+ case 'unitless':
48
+ case 'angle':
49
+ return scalarVerdict(entry.valueType, engineValue);
50
+ case 'rgba': {
51
+ const parsed = (0, color_1.parseColorVerdict)(String(engineValue));
52
+ return parsed.refusal !== null
53
+ ? refuseNormalize(parsed.refusal)
54
+ : { value: parsed.parsed, refusal: null };
55
+ }
56
+ case 'complex':
57
+ case 'gesture':
58
+ case 'discrete':
59
+ // Web-only compositor effects (complex), gestures, and discrete keywords (T23 B — compared
60
+ // by exact keyword equality, never a normalized scalar) have no cross-engine numeric value to
61
+ // compare — asking for one is a caller error, not a silent zero. Fail loud (REQ-SUBSET-014 ethos).
62
+ return refuseNormalize(`Capability '${entry.keys[0]}' (valueType '${entry.valueType}') has no cross-engine ` +
63
+ `normalized value — it is not a comparable animated scalar/color.`);
64
+ }
65
+ return refuseNormalize(`unreachable valueType`);
66
+ }
67
+ function normalizeCapability(entry, engineValue) {
68
+ const verdict = normalizeCapabilityVerdict(entry, engineValue);
69
+ if (verdict.refusal !== null)
70
+ throw verdict.refusal;
71
+ return verdict.value;
72
+ }
73
+ // The comparison epsilon FLOOR for a value type, imported verbatim from VALUE-TYPES (REQ-SUBSET-016).
74
+ // complex/gesture have no comparison tolerance — requesting one is a loud failure, not a default.
75
+ function epsilonFor(valueType) {
76
+ switch (valueType) {
77
+ case 'length':
78
+ return value_types_1.EPS_LENGTH;
79
+ case 'unitless':
80
+ return value_types_1.EPS_UNITLESS;
81
+ case 'angle':
82
+ return value_types_1.EPS_ANGLE;
83
+ case 'rgba':
84
+ return value_types_1.EPS_COLOR_CHANNEL;
85
+ case 'complex':
86
+ case 'gesture':
87
+ case 'discrete':
88
+ throw new Error(`valueType '${valueType}' has no comparison epsilon — it is not a cross-engine numeric value.`);
89
+ }
90
+ }
@@ -0,0 +1,10 @@
1
+ import type { RGBA } from "../value-types/color.cjs";
2
+ import type { CapabilityEntry, ValueType } from "./registry.cjs";
3
+ export type NormalizedValue = number | RGBA;
4
+ export interface NormalizeVerdict {
5
+ readonly value: NormalizedValue | null;
6
+ readonly refusal: Error | null;
7
+ }
8
+ export declare function normalizeCapabilityVerdict(entry: CapabilityEntry, engineValue: string | number): NormalizeVerdict;
9
+ export declare function normalizeCapability(entry: CapabilityEntry, engineValue: string | number): NormalizedValue;
10
+ export declare function epsilonFor(valueType: ValueType): number;
@@ -0,0 +1,10 @@
1
+ import type { RGBA } from "../value-types/color.js";
2
+ import type { CapabilityEntry, ValueType } from "./registry.js";
3
+ export type NormalizedValue = number | RGBA;
4
+ export interface NormalizeVerdict {
5
+ readonly value: NormalizedValue | null;
6
+ readonly refusal: Error | null;
7
+ }
8
+ export declare function normalizeCapabilityVerdict(entry: CapabilityEntry, engineValue: string | number): NormalizeVerdict;
9
+ export declare function normalizeCapability(entry: CapabilityEntry, engineValue: string | number): NormalizedValue;
10
+ export declare function epsilonFor(valueType: ValueType): number;