@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,762 @@
1
+ "use strict";
2
+ // SPEC-NATIVE-DRIVER §Requirements — the reference in-core Driver (REQ-DRIVER-013/020). A concrete,
3
+ // host-agnostic `Driver` the fake host adapter and the deterministic conformance drive; the native worklet
4
+ // and web drivers are the substrate-bound peers of this same interface. It composes the built ladder and
5
+ // forks nothing (REQ-DRIVER-003): the SPRING/TIMING generators (via the COMPONENT seconds→ms converters)
6
+ // supply every trajectory, `retargetSpring` supplies spring continuity, and the pure `stepProp` advances
7
+ // each frame. A command seeds state once (O(1)); `step` reads only seeded state — zero per-frame JS logic
8
+ // beyond the pure advance (REQ-DRIVER-013). An element quiesces when all its animations settle
9
+ // (REQ-DRIVER-020). Host-agnostic (REQ-CORE-003): relative imports only.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.referenceFoldRefusal = referenceFoldRefusal;
12
+ exports.referenceRetargetFoldRefusal = referenceRetargetFoldRefusal;
13
+ exports.createReferenceDriver = createReferenceDriver;
14
+ const component_1 = require("../component/index.cjs");
15
+ const transition_1 = require("../component/transition.cjs");
16
+ const delay_1 = require("../delay.cjs");
17
+ const inertia_1 = require("../inertia.cjs");
18
+ const instant_1 = require("../instant.cjs");
19
+ const keyframes_1 = require("../keyframes.cjs");
20
+ const repeat_1 = require("../repeat.cjs");
21
+ const timing_1 = require("../timing.cjs");
22
+ const transition_2 = require("../transition.cjs");
23
+ const motion_arc_1 = require("../motion-arc.cjs");
24
+ const prepare_1 = require("./prepare.cjs");
25
+ const step_1 = require("./step.cjs");
26
+ // Clear the gesture hold on ONE prop (the matching release/stop command owns it now). Idempotent —
27
+ // clearing an unheld prop is a no-op, so the heldCount stays exact (review r7 major 23).
28
+ function releaseHold(element, key) {
29
+ if (element.heldVelocity[key] !== undefined) {
30
+ delete element.heldVelocity[key];
31
+ element.heldCount--;
32
+ }
33
+ }
34
+ // Build a fresh generator for a `start`: from the resolved base to the target, under the transition. Springs
35
+ // and tweens are selected by `transition.type` — with one pin-mandated addition (REQ-TIMING-006): an
36
+ // authored duration-ONLY bag (type omitted) also takes the timing generator, the pin's
37
+ // `type = keyframesGenerator` default for any DEFINED transition. Spring remains the fallback for
38
+ // everything else, reusing the COMPONENT seconds→ms
39
+ // converters + the built generators — no forked math (REQ-DRIVER-003). `velocity` is the REQ-DRIVER-023
40
+ // seed (the gesture release's platform handoff); a tween is position-based and ignores it by construction,
41
+ // kept explicit here so the contract reads at the seam.
42
+ // Normalize a PropTransition to the authored inputs the generators consume (R8 M2, consult
43
+ // agent-2026-07-19-e715fd). A controller-resolved default (`DefaultTransition`) carries shapes the
44
+ // authored `Transition` cannot: a `type:'keyframes'` default normalizes to the equivalent tween — an
45
+ // ABSENT `ease` is preserved so the keyframes generator keeps its intrinsic per-segment easeInOut (the
46
+ // 800ms default); a `type:'spring'` default carries `restSpeed` (Motion's default-transitions
47
+ // override), returned alongside to thread into the spring's settle threshold. Since U7c
48
+ // (REQ-SPRING-014) an AUTHORED spring may also carry restSpeed/restDelta — the extraction below
49
+ // reads the same key either way, and `springConfigWithRest` threads it over `toSpringConfig`'s own
50
+ // passthrough, so authored and default thresholds reach the solver identically.
51
+ function normalizeTransition(transition) {
52
+ if (transition.type === 'keyframes') {
53
+ return {
54
+ authored: {
55
+ type: 'tween',
56
+ duration: transition.duration,
57
+ ...(transition.ease !== undefined ? { ease: transition.ease } : {}),
58
+ ...(transition.delay !== undefined ? { delay: transition.delay } : {}),
59
+ ...(transition.repeat !== undefined ? { repeat: transition.repeat } : {}),
60
+ ...(transition.repeatType !== undefined ? { repeatType: transition.repeatType } : {}),
61
+ ...(transition.repeatDelay !== undefined ? { repeatDelay: transition.repeatDelay } : {}),
62
+ },
63
+ restSpeed: undefined,
64
+ };
65
+ }
66
+ const restSpeed = transition.type === 'spring' && 'restSpeed' in transition ? transition.restSpeed : undefined;
67
+ return { authored: transition, restSpeed };
68
+ }
69
+ // Thread a default spring's `restSpeed` (settle threshold) into the resolved SpringConfig;
70
+ // authored restDelta/restSpeed (U7c) already flow through `toSpringConfig`'s passthrough.
71
+ function springConfigWithRest(transition, restSpeed) {
72
+ return restSpeed !== undefined
73
+ ? { ...(0, component_1.toSpringConfig)(transition), restSpeed }
74
+ : (0, component_1.toSpringConfig)(transition);
75
+ }
76
+ /**
77
+ * T18-a (REQ-API-049 / REQ-DRIVER-031): apply the repeat fold around whatever trajectory the
78
+ * transition resolves to. The fold is generator-agnostic, so this wraps the ONE unrepeated
79
+ * construction below rather than teaching any generator about repetition, and it delegates to
80
+ * core's shared `buildRepeatedGenerator` — the same call the worklet driver makes. Sharing the call
81
+ * is necessary but NOT sufficient for parity: both backends shared it and still skewed, because
82
+ * only the start seam folded. What actually holds the two backends together is the executing floor,
83
+ * `driverParity.differential.test.ts`. (`check:resolution-skew` compares package.json export
84
+ * conditions — module ENTRY-POINT resolution — and has never inspected either driver.)
85
+ *
86
+ * An inertia transition never folds — and is not REFUSED, which an earlier draft of this comment
87
+ * claimed (fix-up review). There is nothing to refuse: `inertia` is absent from the public
88
+ * `TransitionType` (`'spring' | 'tween' | false`), so it is unauthorable, reaching this seam only as the
89
+ * command layer's own gesture-release settle, which carries no authored options. The branch is
90
+ * therefore a defensive skip over a shape that cannot carry a fold, not a severity boundary.
91
+ */
92
+ function startGenerator(transition, to, from, velocity) {
93
+ const { authored } = normalizeTransition(transition);
94
+ // U7a + transition-default-selection F5: the instant lane — the pin's makeAnimationInstant
95
+ // (motion-dom@12.42.2 motion-value.ts:89-98). The guard is the pin's, type-independent:
96
+ // `type: false` (U7a) OR `duration === 0 && !repeatDelay` (F5). NO trajectory generator, NO
97
+ // fold: the final keyframe commits on the driver's next update tick (never synchronously), and
98
+ // the authored delay is honored through the shared rebase. A repeatDelay-bearing duration-zero
99
+ // bag is exempt and keeps its generator below (the zero-length timing base there takes the same
100
+ // instant SHAPE so the fold's plays stay zero-length). The inertia narrow keeps the union
101
+ // discriminated for the delay crossing.
102
+ if (authored.type !== 'inertia' &&
103
+ (authored.type === false || (0, transition_1.isDurationZeroInstantTransition)(authored))) {
104
+ return (0, delay_1.delayGenerator)((0, instant_1.instantGenerator)((0, instant_1.instantFinalKeyframe)(to))({ from, velocity: 0 }), (0, component_1.toDelayMs)(authored));
105
+ }
106
+ const fold = authored.type === 'inertia' ? null : (0, component_1.toRepeatFoldOptions)(authored);
107
+ // T18-b (REQ-TIMING-004 / REQ-DRIVER-032): the delay rebase wraps AROUND the fold-or-generator —
108
+ // core's shared `delayGenerator`, the ONE rebase seam the worklet driver calls identically at
109
+ // BOTH phases. An inertia release is command-layer-only and carries no authored delay (mirroring
110
+ // the fold's defensive skip, not a severity boundary). `toDelayMs` is the seconds→ms crossing.
111
+ // Sharing the call is not parity on its own — `driverParity.differential.test.ts` executes both
112
+ // backends over delayed commands and compares frame for frame.
113
+ const delayMs = authored.type === 'inertia' ? 0 : (0, component_1.toDelayMs)(authored);
114
+ if (fold === null)
115
+ return (0, delay_1.delayGenerator)(unrepeatedGenerator(transition, to, from, velocity, false), delayMs);
116
+ // Legs are built EAGERLY and passed as values — never a factory the fold would invoke, which on
117
+ // the UI runtime is the wrong-runtime crash class (`check:worklet-closures`). The worklet driver
118
+ // mirrors this shape exactly.
119
+ const leg = fold.repeatType === 'mirror'
120
+ ? { type: 'mirror', mirrored: unrepeatedGenerator(transition, to, from, velocity, true) }
121
+ : { type: fold.repeatType };
122
+ const folded = (0, repeat_1.buildRepeatedGenerator)(fold.repeat, leg, fold.repeatDelayMs, unrepeatedGenerator(transition, to, from, velocity, false), knownIterationDurationMs(transition, to));
123
+ if (folded instanceof Error)
124
+ throw repeatRefusal(fold, folded);
125
+ return (0, delay_1.delayGenerator)(folded, delayMs);
126
+ }
127
+ /**
128
+ * The fold's refusal, TYPED (T18-a fix-up, review MAJOR). Whether a trajectory ever settles depends
129
+ * on the DISTANCE travelled and the seed velocity, so it is not decidable from the authored
130
+ * transition and validation cannot own it — this seam, which holds the real generator, does. It
131
+ * arrived here as a bare `Error`, which is what let the failure surface as an anonymous driver-lane
132
+ * fault instead of a refusal naming the offending property.
133
+ */
134
+ // alloc-ok: lifecycle-edge — built only on the refusal path, once per rejected animate command.
135
+ function repeatRefusal(fold, refusal) {
136
+ return new component_1.InvalidTransitionError(undefined, 'repeat', fold.repeat, refusal.message);
137
+ }
138
+ /**
139
+ * The transition's own resolved iteration length in ms, or null when only a scan can measure it —
140
+ * mirroring the pin's `calculatedDuration` rule (`spring.ts:378`: `isResolvedFromDuration ?
141
+ * duration || null : null`; the keyframes generator always knows its own `duration`).
142
+ */
143
+ function knownIterationDurationMs(transition, to) {
144
+ const { authored } = normalizeTransition(transition);
145
+ // Narrows `authored` off the inertia arm as well as answering the lane's first question.
146
+ if (authored.type === 'inertia')
147
+ return null;
148
+ // The lane comes from core's ONE rule, never re-derived here (T18-a fix-up review MAJOR: three
149
+ // parallel copies of it drifted across four rounds).
150
+ const lane = (0, repeat_1.iterationMeasurementLane)(typeof to === 'number', authored.type === 'spring',
151
+ // REQ-TIMING-006 + F6: the untyped timing-lane bag (duration-only OR ease-only) measures the
152
+ // timing lane (the authored duration, or the generator's 300ms default), never the
153
+ // duration-spring's resolved settle.
154
+ authored.type === 'tween' || (0, transition_1.isTimingLaneTransition)(authored));
155
+ if (lane === 'keyframes')
156
+ return (0, component_1.toKeyframesConfig)(authored).duration ?? null;
157
+ if (lane === 'timing')
158
+ return (0, component_1.toTimingConfig)(authored).duration ?? null;
159
+ const resolved = (0, transition_2.resolveSpring)((0, component_1.toSpringConfig)(authored));
160
+ // `duration || null`: a zero-length resolution is NOT a known duration — the scan measures it,
161
+ // and the fold's own refusal then names the degenerate iteration (T18-a L3).
162
+ return resolved.calculatedDuration !== null && resolved.calculatedDuration > 0
163
+ ? resolved.calculatedDuration
164
+ : null;
165
+ }
166
+ /**
167
+ * One unrepeated trajectory. `reversed` builds the MIRRORED leg (the pin's second generator): the
168
+ * origin and target swap and the seed velocity negates, and a keyframe list is reversed AFTER its
169
+ * nulls resolve — `times`/`ease` are deliberately untouched, exactly as the pin leaves them, so a
170
+ * mirrored leg is a genuinely different trajectory rather than a time-reflection.
171
+ */
172
+ function unrepeatedGenerator(transition, to, from, velocity, reversed) {
173
+ const { authored, restSpeed } = normalizeTransition(transition);
174
+ if (typeof to !== 'number') {
175
+ if (reversed && authored.type !== 'inertia') {
176
+ // Resolve from-current nulls BEFORE reversing (R8-F2): the pin mirrors the RESOLVED
177
+ // keyframes, and a null that slid to the tail would re-read `from` at the wrong end.
178
+ const resolvedKeyframes = to.map((value) => (value === null ? from : value));
179
+ const mirroredKeyframes = [...resolvedKeyframes].reverse();
180
+ const mirroredFrom = mirroredKeyframes[0] ?? from;
181
+ if (authored.type === 'spring') {
182
+ return springKeyframeGenerator(authored, restSpeed, mirroredKeyframes, mirroredFrom, -velocity);
183
+ }
184
+ return (0, keyframes_1.keyframesGenerator)(mirroredKeyframes, (0, component_1.toKeyframesConfig)(authored))({ from: mirroredFrom, velocity: 0 });
185
+ }
186
+ }
187
+ if (typeof to !== 'number') {
188
+ if (authored.type === 'inertia') {
189
+ // R16 shape totality (review major 3, G-INV-8/-10): a keyframe array under an inertia
190
+ // transition is a command-shape defect — the worklet driver throws the identical class;
191
+ // never silently run inertia ignoring the array.
192
+ throw new Error('a keyframe array cannot run under an inertia transition (REQ-GESTURE-023, R16).');
193
+ }
194
+ // A keyframe ARRAY HONORS `transition.type` (REQ-API-033, R8-F1). An EXPLICIT spring interpolates
195
+ // exactly two keyframes (Motion's `assertTwoKeyframes`): two → spring physics; a >2 spring FAILS
196
+ // LOUD naming the successor — it never reaches `resolveSpringGenerator` to be silently accepted. A
197
+ // tween or the DEFAULT routes the array THROUGH the keyframes generator (even offsets, per-segment
198
+ // ease); position-based, the seed velocity is dropped and a null FIRST element reads `from` (R8-F2).
199
+ if (authored.type === 'spring')
200
+ return springKeyframeGenerator(authored, restSpeed, to, from, velocity);
201
+ return (0, keyframes_1.keyframesGenerator)(to, (0, component_1.toKeyframesConfig)(authored))({ from, velocity: 0 });
202
+ }
203
+ if (authored.type === 'inertia') {
204
+ // R16 (REQ-GESTURE-023): the gesture free-drag release's two-phase generator. `to` is the
205
+ // mirror terminal (the clamped ideal); the generator recomputes its own ideal from the seed
206
+ // and owns the walls via the boundary spring. The seed velocity is the platform release
207
+ // velocity, momentum-gated upstream (REQ-GESTURE-027).
208
+ return (0, inertia_1.inertiaGenerator)({
209
+ power: authored.power,
210
+ timeConstant: authored.timeConstant,
211
+ bounceStiffness: authored.bounceStiffness,
212
+ bounceDamping: authored.bounceDamping,
213
+ restDelta: authored.restDelta,
214
+ restSpeed: authored.restSpeed,
215
+ ...(authored.min !== undefined ? { min: authored.min } : {}),
216
+ ...(authored.max !== undefined ? { max: authored.max } : {}),
217
+ })({ from, velocity });
218
+ }
219
+ // The scalar mirrored leg: origin and target swap, and the seed velocity negates (the pin builds
220
+ // its second generator with `velocity: -velocity`).
221
+ const scalarTo = reversed ? from : to;
222
+ const scalarFrom = reversed ? to : from;
223
+ const scalarVelocity = reversed ? -velocity : velocity;
224
+ // REQ-TIMING-006 + transition-default-selection F1/F6: an authored duration-ONLY bag (type
225
+ // omitted) OR a defined ease-only bag takes the pin's keyframes/timing generator — monotone,
226
+ // `easeOut` by default when NO ease spelling is authored, the authored ease verbatim for the
227
+ // ease-only form (the pin's defined-typeless lane at the 300ms default, JSAnimation.ts:106-116) —
228
+ // never the old omitted-type duration-spring (U7a finding A, device peak 1.046) and never the
229
+ // REQ-SPRING-003 generic spring for an ease-only bag. F1 gives the TYPED tween the same easeOut
230
+ // base through the ONE config builder (component/transition.ts `toTimingLaneConfig`);
231
+ // `isTimingLaneTransition` is the single-sourced untyped arm.
232
+ if (authored.type === 'tween' || (0, transition_1.isTimingLaneTransition)(authored)) {
233
+ // F5: a zero-length timing base takes the pin's makeAnimationInstant SHAPE — hold the seed at
234
+ // t ≤ 0, commit the final keyframe past it (motion-value.ts:89-98). Only repeatDelay-bearing
235
+ // bags reach this (a zero duration without one routes to the instant lane above); it keeps
236
+ // the fold's zero-length plays from committing at the command edge.
237
+ if (authored.duration === 0) {
238
+ return (0, instant_1.instantGenerator)(scalarTo)({ from: scalarFrom, velocity: 0 });
239
+ }
240
+ return (0, timing_1.timingGenerator)(scalarTo, (0, transition_1.toTimingLaneConfig)(authored))({ from: scalarFrom, velocity: 0 });
241
+ }
242
+ return (0, transition_2.resolveSpringGenerator)(scalarTo, springConfigWithRest(authored, restSpeed))({ from: scalarFrom, velocity: scalarVelocity });
243
+ }
244
+ // R8-F1: a keyframe ARRAY under an EXPLICIT spring. A spring interpolates EXACTLY two keyframes — the
245
+ // pinned Motion throws `spring-two-frames` for more. Two keyframes run spring physics from keyframes[0]
246
+ // (the explicit origin; a null first element reads `from`, from-current R8-F2) to keyframes[1] (the
247
+ // target); a longer array FAILS LOUD naming the successor so it never reaches `resolveSpringGenerator`.
248
+ function springKeyframeGenerator(transition, restSpeed, keyframes, from, velocity) {
249
+ const countRefusal = (0, transition_2.springKeyframeCountRefusal)(keyframes.length);
250
+ if (countRefusal !== null)
251
+ throw countRefusal;
252
+ const origin = keyframes[0];
253
+ const target = keyframes[1];
254
+ // Validation guarantees a length-2 array whose only legal null is index 0 and whose target parses;
255
+ // the guard keeps this TOTAL under noUncheckedIndexedAccess (never hit for a valid target).
256
+ if (origin === undefined || target === undefined || target === null) {
257
+ throw new Error(`malformed two-keyframe spring array — a non-null numeric target is required (REQ-API-033).`);
258
+ }
259
+ const seedFrom = origin === null ? from : origin;
260
+ return (0, transition_2.resolveSpringGenerator)(target, springConfigWithRest(transition, restSpeed))({
261
+ from: seedFrom,
262
+ velocity,
263
+ });
264
+ }
265
+ // Build a generator that CONTINUES from a live property's current value+velocity to a new target (REQ-API-
266
+ // 003 interruption continuity). Springs use SPRING's own `retargetSpring` (carries the analytic velocity);
267
+ // tweens are position-based, so they reseed from the live value with no carried velocity.
268
+ function unrepeatedRetargetGenerator(transition, live, to) {
269
+ // Normalize first, exactly as `unrepeatedGenerator` does: the authored form is what the config
270
+ // converters accept, and a `type:'keyframes'` default normalizes to its equivalent tween rather
271
+ // than falling through to the spring branch. The disjoint inertia arm runs FIRST so the union
272
+ // narrows to `Transition` for the converter accept below (arms are mutually exclusive; the
273
+ // order swap changes nothing observable).
274
+ const { authored, restSpeed } = normalizeTransition(transition);
275
+ if (authored.type === 'inertia') {
276
+ // R16: a retarget INTO an inertia transition re-seeds the two-phase generator from the LIVE
277
+ // sample — the same continuity law as a spring retarget (C0/C1). The worklet driver has always
278
+ // done this; the reference driver used to fall through to the spring branch and hand an inertia
279
+ // transition to `toSpringConfig`. Pinned as a parity row in driverParity.differential.test.ts.
280
+ const at = live.generator.sample(live.elapsed);
281
+ return unrepeatedGenerator(transition, to, at.value, at.velocity, false);
282
+ }
283
+ if (authored.type === 'tween' || (0, transition_1.isTimingLaneTransition)(authored)) {
284
+ // F5: the zero-length timing base takes the instant SHAPE here too — the same routing as
285
+ // `unrepeatedGenerator`, seeded from the live value (no cross-seam skew).
286
+ if (authored.duration === 0) {
287
+ return (0, instant_1.instantGenerator)(to)({ from: live.value, velocity: 0 });
288
+ }
289
+ return (0, timing_1.timingGenerator)(to, (0, transition_1.toTimingLaneConfig)(authored))({ from: live.value, velocity: 0 });
290
+ }
291
+ return (0, transition_2.retargetSpring)(live.generator, live.elapsed, to, springConfigWithRest(authored, restSpeed));
292
+ }
293
+ /**
294
+ * T18-a fix-up (review BLOCKER, two independent reviewers): a retarget carries the repeat fold,
295
+ * exactly as `start` does. For a repeating animation this seam is not an edge case, it is the
296
+ * COMMON one — an endless repeat is never `done`, so every changed scalar animate key routes here
297
+ * instead of to `start`. Dropping the fold stopped the loop permanently on the first retarget and
298
+ * then fired the completion the transition says can never come. The internal tell was that the
299
+ * gesture-HELD branch already folds (it builds through `startGenerator`), so the same authoring
300
+ * repeated or not depending on whether a finger happened to be down.
301
+ *
302
+ * The pin rebuilds every retarget as a fresh animation spreading `...valueTransition`
303
+ * (`motion-dom/src/animation/interfaces/motion-value.ts`), so repeat/repeatType/repeatDelay ride
304
+ * through unchanged. The fold's BASE here is the continuity-carrying retarget generator, so
305
+ * iteration 0 keeps the live value and analytic velocity (REQ-API-003) and each later iteration
306
+ * replays from that same seed.
307
+ */
308
+ function retargetGenerator(transition, live, to) {
309
+ const { authored } = normalizeTransition(transition);
310
+ // U7a + F5: the instant lane at the retarget seam — the pin's guard is type-independent
311
+ // (`type: false` OR `duration === 0 && !repeatDelay`, motion-value.ts:89-98). Commit the new
312
+ // target on the next update tick from the live value, honoring the retarget's OWN authored
313
+ // delay (the pin starts a fresh animation per retarget). No fold, exactly as `startGenerator`.
314
+ // The inertia narrow keeps the union discriminated for the delay crossing.
315
+ if (authored.type !== 'inertia' &&
316
+ (authored.type === false || (0, transition_1.isDurationZeroInstantTransition)(authored))) {
317
+ return (0, delay_1.delayGenerator)((0, instant_1.instantGenerator)(to)({ from: live.value, velocity: 0 }), (0, component_1.toDelayMs)(authored));
318
+ }
319
+ const base = unrepeatedRetargetGenerator(transition, live, to);
320
+ const fold = authored.type === 'inertia' ? null : (0, component_1.toRepeatFoldOptions)(authored);
321
+ // T18-b (REQ-DRIVER-032): a retarget applies its OWN delay, as the pin starts a fresh animation
322
+ // per retarget. The rebase wraps the (possibly folded) retarget trajectory — AROUND the fold,
323
+ // the same core seam as `startGenerator`. A mid-delay retarget reseeds from the held first
324
+ // keyframe at velocity 0, because the outgoing delay wrapper reports exactly that while held
325
+ // (packet L2: no motion to be continuous WITH).
326
+ const delayMs = authored.type === 'inertia' ? 0 : (0, component_1.toDelayMs)(authored);
327
+ if (fold === null)
328
+ return (0, delay_1.delayGenerator)(base, delayMs);
329
+ // Legs eager, never a factory the fold would invoke — the worklet driver mirrors this shape
330
+ // exactly (`check:worklet-closures`). The mirrored leg swaps origin/target around the LIVE value.
331
+ const leg = fold.repeatType === 'mirror'
332
+ ? { type: 'mirror', mirrored: unrepeatedGenerator(transition, to, live.value, 0, true) }
333
+ : { type: fold.repeatType };
334
+ const folded = (0, repeat_1.buildRepeatedGenerator)(fold.repeat, leg, fold.repeatDelayMs, base, knownIterationDurationMs(transition, to));
335
+ if (folded instanceof Error)
336
+ throw repeatRefusal(fold, folded);
337
+ return (0, delay_1.delayGenerator)(folded, delayMs);
338
+ }
339
+ // A CONTENDING target drifts FROM the committed finger value — never a backward jump off the finger
340
+ // (REQ-DRIVER-026 / G-INV-2, review major 25). A keyframe array's non-null FIRST element would make the
341
+ // generator restart near keyframes[0], rewinding the drift; re-seed the first keyframe from-current
342
+ // (null-first, R8-F2) so the trajectory runs the live value → remaining keyframes. A scalar (springs/
343
+ // tweens already seed from `from`) and an already-null-first array are unchanged. `to` is a captured
344
+ // snapshot, so this reads it once more into a fresh frozen array only at a command/gesture edge.
345
+ function anchorContentionToLive(to) {
346
+ if (typeof to === 'number' || to[0] === null)
347
+ return to;
348
+ return Object.freeze([null, ...to.slice(1)]);
349
+ }
350
+ /**
351
+ * This backend's OWN verdict on a fold, for one transition and one target, with the boundary
352
+ * validation `command()` runs first deliberately NOT in the path.
353
+ *
354
+ * It exists because the parity floor that guards this rung was structurally half-blind without it
355
+ * (fix-up review MAJOR): `prepareDriverCommand` calls `validateTransitionRefusal` before reaching
356
+ * any generator, so a floor that reads `command()`'s throw sees validation's own refusal echoed back
357
+ * as "the driver refused". Every over-refusal therefore passed — a mutation that made validation
358
+ * reject 36 executable configs left the floor green. Only a verdict taken from BELOW validation can
359
+ * decide whether validation is measuring the fold the way this backend does.
360
+ *
361
+ * Returns the fold's refusal, or null when this backend would construct and run the generator.
362
+ *
363
+ * `from` is a real parameter, not a convenience: a null-first keyframe array (`[null, 100]`, the
364
+ * from-current form) resolves its first keyframe FROM the live value, so with `from` pinned at 0 it
365
+ * is indistinguishable from `[0, 100]` and the target axis loses a shape it claims to sweep (round-5
366
+ * review MINOR).
367
+ *
368
+ * Test seam for this package's own parity floor — NOT part of any published entry: exporting it from
369
+ * `internal-driver` would pull this whole backend into the seam the worklet driver imports.
370
+ */
371
+ function referenceFoldRefusal(transition, to, from = 0) {
372
+ try {
373
+ startGenerator(transition, to, from, 0);
374
+ return null;
375
+ }
376
+ catch (error) {
377
+ // Only the fold's own refusal answers this question. Anything else is a different fault and must
378
+ // not be laundered into a verdict — it propagates.
379
+ if (error instanceof component_1.InvalidTransitionError && error.key === 'repeat')
380
+ return error;
381
+ throw error;
382
+ }
383
+ }
384
+ /**
385
+ * The same verdict at the RETARGET seam — the second half of the parity floor's oracle.
386
+ *
387
+ * Rounds 4-6 each fixed one axis pinned to a constant (`repeat`, then `repeatDelay`), and round 7
388
+ * found the pattern one level up: the floor's ORACLE was itself pinned to a constant — the `start`
389
+ * seam of the reference backend — while `buildRepeatedGenerator` has four seams (two backends x two
390
+ * phases). The retarget seam is not a duplicate of `start`:
391
+ *
392
+ * - it reseeds the scan from the live VALUE, so the distance it measures is the distance from
393
+ * wherever the animation currently IS — a different trajectory than `start` measured, which is
394
+ * why it can refuse where `start` ran (round-7 review MAJOR). It also carries the live velocity
395
+ * into the reseed; that is structurally present but NOT the demonstrated variable — sweeping the
396
+ * dwell from 0 to 480ms at a fixed distance changes no verdict, and removing the dwell entirely
397
+ * leaves the floor green (round-8 review MINOR). Distance is what moves it;
398
+ * - it cannot reach the keyframes lane at all — `DriverCommand`'s retarget targets are numbers, so
399
+ * `targetIsNumeric` is constantly true here. The floor's target axis is structurally
400
+ * inexpressible at this seam, and its axis is the LIVE STATE instead.
401
+ *
402
+ * Taken from BELOW validation for the same reason as `referenceFoldRefusal`: `command()` validates
403
+ * first, so reading its throw would make the driver echo the boundary and the floor could only ever
404
+ * catch over-acceptance.
405
+ *
406
+ * A fold that cannot even START is reported as refused — "would this backend run it at a retarget"
407
+ * is answered `no` either way, and conflating them here would make the phase axis lie about which
408
+ * seam refused.
409
+ *
410
+ * Test seam for this package's own parity floor — NOT part of any published entry.
411
+ */
412
+ function referenceRetargetFoldRefusal(transition, startTo, from, retargetTo, dwellMs) {
413
+ let generator;
414
+ try {
415
+ generator = startGenerator(transition, startTo, from, 0);
416
+ }
417
+ catch (error) {
418
+ if (error instanceof component_1.InvalidTransitionError && error.key === 'repeat')
419
+ return error;
420
+ throw error;
421
+ }
422
+ // Reach a genuinely live state the way the driver does, so the retarget carries a real velocity.
423
+ const live = { generator, elapsed: 0, value: from, done: false };
424
+ (0, step_1.stepProp)(live, dwellMs);
425
+ try {
426
+ retargetGenerator(transition, live, retargetTo);
427
+ return null;
428
+ }
429
+ catch (error) {
430
+ if (error instanceof component_1.InvalidTransitionError && error.key === 'repeat')
431
+ return error;
432
+ throw error;
433
+ }
434
+ }
435
+ function createReferenceDriver() {
436
+ // Indexed by handle (a plain number, worklet-friendly). register() appends; the index is the handle.
437
+ const elements = [];
438
+ function elementAt(handle) {
439
+ const element = elements[handle];
440
+ if (element === undefined) {
441
+ throw new Error(`unknown element handle ${String(handle)}: it was never registered on this driver.`);
442
+ }
443
+ return element;
444
+ }
445
+ const backend = {
446
+ register(initial) {
447
+ const handle = elements.length;
448
+ elements.push({
449
+ active: false,
450
+ heldCount: 0,
451
+ committed: { ...initial },
452
+ heldVelocity: {},
453
+ props: new Map(),
454
+ contending: {},
455
+ path: null,
456
+ });
457
+ return handle;
458
+ },
459
+ command(handle, command) {
460
+ const element = elementAt(handle);
461
+ switch (command.kind) {
462
+ case 'start': {
463
+ // Only a gesture RELEASE (`releasesGesture`) or a `stop` ends a hold. A declarative start on
464
+ // a gesture-held prop CONTENDS instead of being skipped (REQ-DRIVER-026, Motion's shared-
465
+ // value model): it seeds a generator that drifts the live value while the finger still owns
466
+ // the prop, so a mid-drag `animate` change moves the element and the release reads the
467
+ // drifted origin (the snap-selection parity fix). The finger's release velocity is preserved
468
+ // (liveFor held-velocity precedence, REQ-DRIVER-025); the hold clears only at release/stop.
469
+ const releasesGesture = command.releasesGesture;
470
+ // PHASE 1 — build every prop's generator WITHOUT mutating element state. A construction that
471
+ // throws (an explicit spring on a >2-keyframe array, R8-F1) aborts the WHOLE command here, so
472
+ // a rejected multi-prop start applies NOTHING (fail-closed atomicity, review major 19).
473
+ const plans = [];
474
+ for (const key of Object.keys(command.targets)) {
475
+ const target = command.targets[key]; // key came from Object.keys
476
+ // A gesture-RELEASE start takes over the prop (its hold clears in phase 2), so it is treated
477
+ // as NOT still-held and uses the command's nominal `from`. A start CONTENDING on a prop the
478
+ // finger still holds drifts from the CURRENT live value (never `from`) so committed does not
479
+ // jump off the finger. An ordinary non-held start uses `from`.
480
+ const stillHeld = !releasesGesture && element.heldVelocity[key] !== undefined;
481
+ const from = stillHeld ? element.committed[key] : target.from;
482
+ // `target` is already the complete immutable REQ-DRIVER-030 snapshot. No caller-owned
483
+ // PropTarget or fallback semantics reach this backend.
484
+ const to = target.to;
485
+ const transition = target.transition;
486
+ // A still-held (contending) start drifts from the finger value: seed the first keyframe
487
+ // from-current so it never rewinds to keyframes[0] (major 25). A non-held start keeps the
488
+ // array's own origin (a plain keyframe start plays [k0 … kN] verbatim).
489
+ const generator = startGenerator(transition, stillHeld ? anchorContentionToLive(to) : to, from, target.velocity);
490
+ // Seed committed/value from the generator's TICK-0, not the raw `from`: a keyframe array's
491
+ // origin is keyframes[0] (or the null-first seed), so committed() matches liveFor()'s
492
+ // zero-sample and never shows a one-frame `from` jump (review major dfeaa4bf4bcf). A scalar
493
+ // generator samples `from` at t=0 (no-op). A still-held start keeps the finger-authoritative
494
+ // committed. sample(0) is a pure read — no mutation, so phase 1 stays side-effect-free.
495
+ const seededValue = stillHeld ? from : generator.sample(0).value;
496
+ plans.push({ key, stillHeld, seededValue, generator, to, transition });
497
+ }
498
+ // PHASE 2 — commit every plan. No throw path here, so element state mutates atomically.
499
+ for (const plan of plans) {
500
+ if (releasesGesture)
501
+ releaseHold(element, plan.key);
502
+ element.props.set(plan.key, {
503
+ generator: plan.generator,
504
+ elapsed: 0,
505
+ value: plan.seededValue,
506
+ done: false,
507
+ });
508
+ element.committed[plan.key] = plan.seededValue;
509
+ // Track the contention target so a later finger write re-anchors this generator (major 29).
510
+ // `plan.to` is ALREADY the phase-1 snapshot the generator was built from (captured once,
511
+ // review major 20 + 21), so this stores the SAME frozen array — no second read, no skew; a
512
+ // released/non-held start owns the prop outright, so clear any prior contention.
513
+ if (plan.stillHeld)
514
+ element.contending[plan.key] = {
515
+ to: plan.to,
516
+ transition: plan.transition,
517
+ };
518
+ else
519
+ delete element.contending[plan.key];
520
+ }
521
+ if (command.path !== undefined) {
522
+ const geometry = command.path;
523
+ const generator = startGenerator(geometry.transition, 1000, 0, 0);
524
+ const seeded = generator.sample(0).value;
525
+ element.path = {
526
+ progress: { generator, elapsed: 0, value: seeded, done: false },
527
+ geometry,
528
+ };
529
+ element.props.delete('x');
530
+ element.props.delete('y');
531
+ element.props.delete('pathRotation');
532
+ const sample = (0, motion_arc_1.applyPathProgress)(seeded, geometry, false);
533
+ if ('x' in element.committed)
534
+ element.committed.x = sample.x;
535
+ if ('y' in element.committed)
536
+ element.committed.y = sample.y;
537
+ if (geometry.rotationScale && 'pathRotation' in element.committed)
538
+ element.committed.pathRotation = sample.pathRotation;
539
+ }
540
+ else if (element.path !== null) {
541
+ if (element.path.geometry.rotationScale && 'pathRotation' in element.committed)
542
+ element.committed.pathRotation = 0;
543
+ element.path = null;
544
+ }
545
+ element.active = element.props.size > 0 || element.heldCount > 0 || element.path !== null;
546
+ break;
547
+ }
548
+ case 'retarget': {
549
+ // BUILD every key first, COMMIT only once all of them succeeded — the same atomicity law
550
+ // `start` already carries (review major 36). Building can REFUSE (the fold re-measures from
551
+ // the live value, so a `repeat` executable at start can be refused here), and a per-key
552
+ // commit left the earlier keys already retargeted when a later one threw. A refused command
553
+ // must mutate nothing; the controller's severity lane depends on it, because it reports the
554
+ // refusal and lets the remaining keys proceed.
555
+ const staged = [];
556
+ for (const key of Object.keys(command.targets)) {
557
+ const to = command.targets[key]; // key came from Object.keys
558
+ const transition = command.targetTransitions?.[key] ?? command.transition;
559
+ // A retarget is NEVER the gesture release (the release is a seeded `start`). A retarget on
560
+ // a gesture-HELD prop CONTENDS (REQ-DRIVER-026): it seeds a fresh generator from the
561
+ // finger-authoritative COMMITTED value (not the possibly-stale generator `live.value` — a
562
+ // finger write may have advanced committed past it, review r10 major 29) toward the new
563
+ // target, and records the contention so a later write re-anchors it. A non-held prop
564
+ // retargets normally with velocity continuity.
565
+ const held = element.heldVelocity[key] !== undefined;
566
+ let from;
567
+ let generator;
568
+ if (held) {
569
+ from = element.committed[key]; // held ⇒ written ⇒ committed exists
570
+ generator = startGenerator(transition, to, from, 0);
571
+ staged.push({ key, from, generator, contendTo: to });
572
+ continue;
573
+ }
574
+ else {
575
+ // Retarget carries no base — it derives `from` from the in-flight value (continuity), or
576
+ // for a registered-but-idle prop, its last committed value. A key that is neither
577
+ // in-flight nor committed is a malformed retarget: fail loud, never silently seed 0.
578
+ const live = element.props.get(key);
579
+ if (live !== undefined) {
580
+ from = live.value;
581
+ generator = retargetGenerator(transition, live, to);
582
+ }
583
+ else {
584
+ const base = element.committed[key];
585
+ if (base === undefined) {
586
+ throw new Error(`cannot retarget '${key}': it was never registered or started on this element.`);
587
+ }
588
+ from = base;
589
+ generator = startGenerator(transition, to, from, 0);
590
+ }
591
+ }
592
+ staged.push({ key, from, generator });
593
+ }
594
+ // Past this line nothing can refuse, so the commit is all-or-nothing.
595
+ if (element.path !== null &&
596
+ staged.some(({ key }) => key === 'x' || key === 'y' || key === 'pathRotation')) {
597
+ if (element.path.geometry.rotationScale && 'pathRotation' in element.committed)
598
+ element.committed.pathRotation = 0;
599
+ element.path = null;
600
+ }
601
+ for (const { key, from, generator, contendTo } of staged) {
602
+ if (contendTo === undefined)
603
+ delete element.contending[key];
604
+ else
605
+ element.contending[key] = {
606
+ to: contendTo,
607
+ transition: command.targetTransitions?.[key] ?? command.transition,
608
+ };
609
+ element.props.set(key, { generator, elapsed: 0, value: from, done: false });
610
+ element.committed[key] = from;
611
+ }
612
+ // Recompute from what remains in flight: a held retarget seeds a contending generator
613
+ // (props.size grows) and a held element is active regardless (heldCount > 0).
614
+ element.active = element.props.size > 0 || element.heldCount > 0 || element.path !== null;
615
+ break;
616
+ }
617
+ case 'stop': {
618
+ // Halt every animation AND release every gesture hold (stop = halt everything); the
619
+ // committed values are held as-is (REQ-DRIVER-013). Pin onStop zeroes pathRotation.
620
+ element.props.clear();
621
+ for (const key of Object.keys(element.heldVelocity))
622
+ delete element.heldVelocity[key];
623
+ for (const key of Object.keys(element.contending))
624
+ delete element.contending[key];
625
+ if (element.path !== null) {
626
+ if (element.path.geometry.rotationScale && 'pathRotation' in element.committed)
627
+ element.committed.pathRotation = 0;
628
+ element.path = null;
629
+ }
630
+ element.heldCount = 0;
631
+ element.active = false;
632
+ break;
633
+ }
634
+ }
635
+ },
636
+ step(elapsedMs) {
637
+ for (const element of elements) {
638
+ if (!element.active)
639
+ continue;
640
+ let anyActive = false;
641
+ for (const [key, prop] of element.props) {
642
+ (0, step_1.stepProp)(prop, elapsedMs);
643
+ element.committed[key] = prop.value;
644
+ if (!prop.done)
645
+ anyActive = true;
646
+ }
647
+ if (element.path !== null) {
648
+ const path = element.path;
649
+ (0, step_1.stepProp)(path.progress, elapsedMs);
650
+ const sample = (0, motion_arc_1.applyPathProgress)(path.progress.value, path.geometry, path.progress.done);
651
+ if ('x' in element.committed)
652
+ element.committed.x = sample.x;
653
+ if ('y' in element.committed)
654
+ element.committed.y = sample.y;
655
+ if (path.geometry.rotationScale && 'pathRotation' in element.committed)
656
+ element.committed.pathRotation = sample.pathRotation;
657
+ if (path.progress.done)
658
+ element.path = null;
659
+ else
660
+ anyActive = true;
661
+ }
662
+ // Quiesce once every animation settles (REQ-DRIVER-020) — UNLESS a gesture still holds a
663
+ // prop (a finger is down, no animation but unsettled, G-INV-7). heldCount is O(1), so this
664
+ // stays allocation-free. A held element stays active until the matching release clears it.
665
+ if (!anyActive && element.heldCount === 0)
666
+ element.active = false;
667
+ }
668
+ },
669
+ committed(handle) {
670
+ return { ...elementAt(handle).committed };
671
+ },
672
+ setActive(handle, active) {
673
+ elementAt(handle).active = active;
674
+ },
675
+ isActive(handle) {
676
+ return elementAt(handle).active;
677
+ },
678
+ // The gesture write lane (REQ-DRIVER-024): commit event-driven values and interrupt any
679
+ // in-flight animation on the written props through ONE entry point — committed state and
680
+ // animation state can never skew. Never emits a settle edge for a prop that was not
681
+ // animating (the active flag recomputes from what actually remains in flight).
682
+ write(handle, values, velocities) {
683
+ const element = elementAt(handle);
684
+ for (const key of Object.keys(values)) {
685
+ if (!(key in element.committed)) {
686
+ throw new Error(`cannot write '${key}': it was never registered on this element.`);
687
+ }
688
+ }
689
+ // Validate ALL keys before mutating ANY (fail-closed: a malformed write never half-applies).
690
+ for (const key of Object.keys(values)) {
691
+ const finger = values[key];
692
+ if (element.heldVelocity[key] === undefined)
693
+ element.heldCount++; // count a NEW hold once
694
+ element.committed[key] = finger;
695
+ // Refresh the per-prop held platform velocity so liveFor reports it while held (G-INV-3 C1
696
+ // seam, held-velocity precedence over any contending generator); omitted ⇒ 0.
697
+ element.heldVelocity[key] = velocities?.[key] ?? 0;
698
+ // Reconcile the prop's generator with the finger write (REQ-DRIVER-026). A LIVE declarative
699
+ // contention is RE-ANCHORED to the freshly-committed finger value so it drifts FROM here —
700
+ // never a backward jump to the generator's stale trajectory (review r10 major 29, G-INV-2).
701
+ // Otherwise the finger owns the value outright: a plain grab interrupts the pre-existing
702
+ // animation (REQ-GESTURE-012), and a SETTLED contention is finished, so its generator is
703
+ // dropped (else `step` would keep forcing its value and the finger could never move it).
704
+ const contention = element.contending[key];
705
+ const prop = element.props.get(key);
706
+ if (contention !== undefined && prop !== undefined && !prop.done) {
707
+ element.props.set(key, {
708
+ // Re-anchor FROM the finger: seed a keyframe array's first element from-current so the
709
+ // drift continues from `finger`, never rewinding to keyframes[0] (major 25).
710
+ generator: startGenerator(contention.transition, anchorContentionToLive(contention.to), finger, 0),
711
+ elapsed: 0,
712
+ value: finger,
713
+ done: false,
714
+ });
715
+ }
716
+ else {
717
+ if (prop !== undefined)
718
+ element.props.delete(key);
719
+ delete element.contending[key];
720
+ }
721
+ }
722
+ // A gesture write holds the element unsettled (G-INV-7): a finger is down driving it, so it
723
+ // stays active until the matching release/stop clears the hold — never a spurious settle edge
724
+ // mid-drag (the reviewer-reproduced re-grab defect), even after a contending animation settles.
725
+ element.active = true;
726
+ },
727
+ // The grab live-read (REQ-DRIVER-025). An in-flight prop re-samples its generator at the
728
+ // current elapsed — pure by elapsed time (REQ-CORE-001), scalars copied out immediately (the
729
+ // sample record is generator-owned and reused). Settled/never-animated ⇒ velocity 0.
730
+ liveFor(handle, key) {
731
+ const element = elementAt(handle);
732
+ // HELD-VELOCITY PRECEDENCE (REQ-DRIVER-025): a held (finger-down) prop reports the platform
733
+ // finger velocity its write carried EVEN when a contending declarative generator is in flight
734
+ // (REQ-DRIVER-026) — the release seed is the finger's number, never the generator's. The value
735
+ // is the live committed number (the finger position, or the contending animation's drift). This
736
+ // read comes BEFORE the generator branch so contention can't leak the generator velocity into
737
+ // the release seam (the two-sided G-INV-3 C1 seam; per-prop since review r7 major 23).
738
+ const heldV = element.heldVelocity[key];
739
+ if (heldV !== undefined) {
740
+ const base = element.committed[key];
741
+ if (base === undefined) {
742
+ throw new Error(`cannot read '${key}': it was never registered on this element.`);
743
+ }
744
+ return { value: base, velocity: heldV };
745
+ }
746
+ const live = element.props.get(key);
747
+ if (live !== undefined && !live.done) {
748
+ const sample = live.generator.sample(live.elapsed);
749
+ return { value: sample.value, velocity: sample.velocity };
750
+ }
751
+ const base = element.committed[key];
752
+ if (base === undefined) {
753
+ throw new Error(`cannot read '${key}': it was never registered on this element.`);
754
+ }
755
+ // A settled or never-animated non-held prop has velocity 0 (REQ-DRIVER-025).
756
+ return { value: base, velocity: 0 };
757
+ },
758
+ };
759
+ return (0, prepare_1.adaptPreparedDriver)(backend, (handle) => {
760
+ elementAt(handle);
761
+ });
762
+ }