@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,189 @@
1
+ "use strict";
2
+ // MotionValue implementation (REQ-VALUE-001/010/011/012). Clock-timestamped velocity follows
3
+ // Motion 12.42.2's `motionValue.getVelocity` formula (the parity target pinned by M1b conformance):
4
+ // velocity from the last two timed samples, zero once the last sample is older than the staleness
5
+ // window, zero for non-numeric values. `subscribe` never triggers a host render.
6
+ //
7
+ // Two write paths, deliberately distinct (see M1-BUILD-PACKET gotchas): `set`/`jump` are the manual,
8
+ // clock-timestamped path; `applyAnimationSample` is the animation path that writes value+velocity
9
+ // straight from a generator without re-deriving velocity from the clock. While an animation drives
10
+ // this value, `getVelocity` reports the animation velocity; a manual `set`/`jump` reverts to the
11
+ // clock-sampled velocity. Methods below the public interface are internal seams for the graph.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.MotionValueImpl = void 0;
14
+ const collect_reads_1 = require("./collect-reads.cjs");
15
+ const constants_1 = require("./config/constants.cjs");
16
+ const subscriptions_1 = require("./subscriptions.cjs");
17
+ class MotionValueImpl {
18
+ #clock;
19
+ #subs = new subscriptions_1.Subscriptions();
20
+ #current;
21
+ // Previous *numeric* sample and the two timestamps used for clock velocity. `#prev` is undefined
22
+ // whenever the prior value was non-numeric or after a teleport, which forces velocity to 0.
23
+ #prev;
24
+ #updatedAt;
25
+ #prevUpdatedAt;
26
+ // Velocity supplied by the driving animation, if any; takes precedence over the clock sample.
27
+ #animatedVelocity;
28
+ // Stop hook installed by the graph while an animation drives this value (REQ from `stop`).
29
+ #stopAnimation;
30
+ constructor(initial, clock) {
31
+ this.#current = initial;
32
+ this.#clock = clock;
33
+ this.#updatedAt = clock.now();
34
+ this.#prev = undefined;
35
+ this.#prevUpdatedAt = undefined;
36
+ this.#animatedVelocity = undefined;
37
+ this.#stopAnimation = undefined;
38
+ }
39
+ get() {
40
+ (0, collect_reads_1.reportMotionValueRead)(this);
41
+ return this.#current;
42
+ }
43
+ set(value) {
44
+ // Manual write reverts velocity tracking to the clock path.
45
+ this.#animatedVelocity = undefined;
46
+ // The pinned FRAME window (motion-dom updateAndNotify at the SS B pin, r15 4cf8a7d291e3):
47
+ // the previous sample rolls ONLY when time moved, so every same-frame write keeps
48
+ // comparing against the frame-START value and their velocities COMPOUND like the pin —
49
+ // never a per-write replacement that hides intra-frame motion.
50
+ const now = this.#clock.now();
51
+ if (now !== this.#updatedAt) {
52
+ this.#prev = typeof this.#current === 'number' ? this.#current : undefined;
53
+ this.#prevUpdatedAt = this.#updatedAt;
54
+ this.#updatedAt = now;
55
+ }
56
+ const previous = this.#current;
57
+ this.#current = value;
58
+ // Pinned suppression: an identical-value write does not notify (strict !== like the pin).
59
+ if (this.#current !== previous)
60
+ this.#subs.emitChange(this.#current);
61
+ }
62
+ jump(value) {
63
+ // Pinned jump ORDER (motion-dom `jump(v, endAnimation = true)`, r16/r17 — sealed by the
64
+ // motion-seams canary): (1) updateAndNotify — EXACTLY the set() path, so the change
65
+ // listener reads the teleport write's CLOCK velocity (never the generator's) with the
66
+ // animation still live; (2) the velocity window resets; (3) stop() — whose cancellation
67
+ // is notified while the animation is still observable and reads velocity 0. A dead flight
68
+ // can never overwrite the teleported value on the next frame (r15 59b71ef82c44).
69
+ this.set(value);
70
+ this.#prev = undefined;
71
+ this.#prevUpdatedAt = undefined;
72
+ this.#updatedAt = this.#clock.now();
73
+ this.stop();
74
+ }
75
+ subscribe(listener) {
76
+ return this.#subs.subscribe(listener);
77
+ }
78
+ getVelocity() {
79
+ if (this.#animatedVelocity !== undefined)
80
+ return this.#animatedVelocity;
81
+ const current = this.#current;
82
+ const prev = this.#prev;
83
+ const prevUpdatedAt = this.#prevUpdatedAt;
84
+ if (typeof current !== 'number' || prev === undefined || prevUpdatedAt === undefined)
85
+ return 0;
86
+ // Stale: no fresh sample within the window → treat as at rest (REQ-VALUE-011).
87
+ if (this.#clock.now() - this.#updatedAt > constants_1.MAX_VELOCITY_DELTA_MS)
88
+ return 0;
89
+ const dt = Math.min(this.#updatedAt - prevUpdatedAt, constants_1.MAX_VELOCITY_DELTA_MS);
90
+ if (dt <= 0)
91
+ return 0;
92
+ return ((current - prev) * 1000) / dt;
93
+ }
94
+ on(event, listener) {
95
+ // The implementation signature stays broad for delegation; it is NOT a public overload
96
+ // (r16 major 5 — lifecycle listeners are payload-free by contract).
97
+ return this.#subs.on(event, listener);
98
+ }
99
+ // The stop hook exists exactly while an animation drives this value (installed at animate,
100
+ // removed at endAnimation) — it IS the animation-liveness state, so no second flag can skew.
101
+ isAnimating() {
102
+ return this.#stopAnimation !== undefined;
103
+ }
104
+ stop() {
105
+ const stopper = this.#stopAnimation;
106
+ if (stopper === undefined)
107
+ return;
108
+ // Pinned lifecycle ORDER (r17, sealed by the motion-seams canary): the cancellation is
109
+ // notified while the animation is STILL OBSERVABLE (the pin clears `animation` only after
110
+ // notifying); the stopper — which also clears liveness through endAnimation — runs after.
111
+ // The emission lives here, not in destroy(), whose teardown is not a cancel.
112
+ this.#subs.emit('animationCancel');
113
+ stopper();
114
+ }
115
+ destroy() {
116
+ // Pinned teardown ORDER (r16 major 1): destroy announces itself FIRST — the listener
117
+ // observes the still-live animation — then the silent stop (never an animationCancel from
118
+ // teardown), then subscriptions drop.
119
+ this.#subs.emit('destroy');
120
+ this.#stopAnimation?.();
121
+ this.#subs.clear();
122
+ }
123
+ // --- internal seams for the graph (not part of the public MotionValue contract) ---------------
124
+ /**
125
+ * @internal Write an animation-produced sample: value and velocity straight from the generator,
126
+ * bypassing the clock-velocity path so velocity is not double-counted.
127
+ */
128
+ applyAnimationSample(value, velocity) {
129
+ this.#current = value;
130
+ this.#animatedVelocity = velocity;
131
+ this.#subs.emitChange(this.#current);
132
+ }
133
+ /** @internal Fire a lifecycle event (`animationStart`/`animationComplete`) on this value. */
134
+ emitEvent(event) {
135
+ this.#subs.emit(event);
136
+ }
137
+ /** @internal Install the stop hook the graph uses to wire `value.stop()` to the registry. */
138
+ attachAnimationStopper(stop) {
139
+ this.#stopAnimation = stop;
140
+ }
141
+ /**
142
+ * @internal Driver-port seam (REQ-VALUE-015): drop the stop hook WITHOUT invoking it — the
143
+ * token-blind public-control-slot clear changes `isAnimating()` only and must never stop the
144
+ * retained physical execution (pinned post-listener slot clear).
145
+ */
146
+ detachAnimationStopper() {
147
+ this.#stopAnimation = undefined;
148
+ }
149
+ /** @internal Driver-port seam: change-observer presence for transported-write classification. */
150
+ hasChangeObservers() {
151
+ return this.#subs.hasChangeObservers();
152
+ }
153
+ /** @internal Driver-port seam: derived-dependent presence among the change listeners. */
154
+ hasDerivedDependentObserver() {
155
+ return this.#subs.hasDerivedDependentObserver();
156
+ }
157
+ /**
158
+ * @internal Driver-port seam: adopt an externally transported numeric sample — the value plus
159
+ * its OWNED sample history (never clock re-derived) — with change notification under the
160
+ * caller's control: transported writes notify; guarded candidate-cache terminals and
161
+ * synchronous snapshots adopt silently. A throwing observer propagates unchanged (the pinned
162
+ * notify law); the caller owns drain progress accounting across that abort.
163
+ */
164
+ applyExternalNumericSample(value, previousValue, currentTimestampMs, previousTimestampMs, notifyChange) {
165
+ this.#animatedVelocity = undefined;
166
+ this.#current = value;
167
+ this.#prev = previousValue;
168
+ this.#prevUpdatedAt = previousTimestampMs;
169
+ this.#updatedAt = currentTimestampMs;
170
+ if (notifyChange)
171
+ this.#subs.emitChange(this.#current);
172
+ }
173
+ /**
174
+ * @internal Called when the animation driving this value ends (natural completion or stop). Puts
175
+ * the value at rest at its current value as of now: clears the animation velocity AND resets the
176
+ * clock-velocity samples (so `getVelocity` cannot leak a stale manual velocity from a `set` that
177
+ * preceded the animation, nor synthesize one from the animation's final value against a stale
178
+ * previous sample). Removes the stop hook. NOT called on interruption, where the current velocity
179
+ * must carry into the replacement generator's seed.
180
+ */
181
+ endAnimation() {
182
+ this.#animatedVelocity = undefined;
183
+ this.#prev = undefined;
184
+ this.#prevUpdatedAt = undefined;
185
+ this.#updatedAt = this.#clock.now();
186
+ this.#stopAnimation = undefined;
187
+ }
188
+ }
189
+ exports.MotionValueImpl = MotionValueImpl;
@@ -0,0 +1,51 @@
1
+ import type { Clock, MotionValue, MotionValueEvent, ValueChangeListener } from "./types.cjs";
2
+ export declare class MotionValueImpl<T> implements MotionValue<T> {
3
+ #private;
4
+ constructor(initial: T, clock: Clock);
5
+ get(): T;
6
+ set(value: T): void;
7
+ jump(value: T): void;
8
+ subscribe(listener: ValueChangeListener<T>): () => void;
9
+ getVelocity(): number;
10
+ on(event: 'change', listener: (latest: T) => void): () => void;
11
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
12
+ isAnimating(): boolean;
13
+ stop(): void;
14
+ destroy(): void;
15
+ /**
16
+ * @internal Write an animation-produced sample: value and velocity straight from the generator,
17
+ * bypassing the clock-velocity path so velocity is not double-counted.
18
+ */
19
+ applyAnimationSample(value: T, velocity: number): void;
20
+ /** @internal Fire a lifecycle event (`animationStart`/`animationComplete`) on this value. */
21
+ emitEvent(event: Exclude<MotionValueEvent, 'change'>): void;
22
+ /** @internal Install the stop hook the graph uses to wire `value.stop()` to the registry. */
23
+ attachAnimationStopper(stop: () => void): void;
24
+ /**
25
+ * @internal Driver-port seam (REQ-VALUE-015): drop the stop hook WITHOUT invoking it — the
26
+ * token-blind public-control-slot clear changes `isAnimating()` only and must never stop the
27
+ * retained physical execution (pinned post-listener slot clear).
28
+ */
29
+ detachAnimationStopper(): void;
30
+ /** @internal Driver-port seam: change-observer presence for transported-write classification. */
31
+ hasChangeObservers(): boolean;
32
+ /** @internal Driver-port seam: derived-dependent presence among the change listeners. */
33
+ hasDerivedDependentObserver(): boolean;
34
+ /**
35
+ * @internal Driver-port seam: adopt an externally transported numeric sample — the value plus
36
+ * its OWNED sample history (never clock re-derived) — with change notification under the
37
+ * caller's control: transported writes notify; guarded candidate-cache terminals and
38
+ * synchronous snapshots adopt silently. A throwing observer propagates unchanged (the pinned
39
+ * notify law); the caller owns drain progress accounting across that abort.
40
+ */
41
+ applyExternalNumericSample(value: T, previousValue: number | undefined, currentTimestampMs: number, previousTimestampMs: number | undefined, notifyChange: boolean): void;
42
+ /**
43
+ * @internal Called when the animation driving this value ends (natural completion or stop). Puts
44
+ * the value at rest at its current value as of now: clears the animation velocity AND resets the
45
+ * clock-velocity samples (so `getVelocity` cannot leak a stale manual velocity from a `set` that
46
+ * preceded the animation, nor synthesize one from the animation's final value against a stale
47
+ * previous sample). Removes the stop hook. NOT called on interruption, where the current velocity
48
+ * must carry into the replacement generator's seed.
49
+ */
50
+ endAnimation(): void;
51
+ }
@@ -0,0 +1,51 @@
1
+ import type { Clock, MotionValue, MotionValueEvent, ValueChangeListener } from "./types.js";
2
+ export declare class MotionValueImpl<T> implements MotionValue<T> {
3
+ #private;
4
+ constructor(initial: T, clock: Clock);
5
+ get(): T;
6
+ set(value: T): void;
7
+ jump(value: T): void;
8
+ subscribe(listener: ValueChangeListener<T>): () => void;
9
+ getVelocity(): number;
10
+ on(event: 'change', listener: (latest: T) => void): () => void;
11
+ on(event: Exclude<MotionValueEvent, 'change'>, listener: () => void): () => void;
12
+ isAnimating(): boolean;
13
+ stop(): void;
14
+ destroy(): void;
15
+ /**
16
+ * @internal Write an animation-produced sample: value and velocity straight from the generator,
17
+ * bypassing the clock-velocity path so velocity is not double-counted.
18
+ */
19
+ applyAnimationSample(value: T, velocity: number): void;
20
+ /** @internal Fire a lifecycle event (`animationStart`/`animationComplete`) on this value. */
21
+ emitEvent(event: Exclude<MotionValueEvent, 'change'>): void;
22
+ /** @internal Install the stop hook the graph uses to wire `value.stop()` to the registry. */
23
+ attachAnimationStopper(stop: () => void): void;
24
+ /**
25
+ * @internal Driver-port seam (REQ-VALUE-015): drop the stop hook WITHOUT invoking it — the
26
+ * token-blind public-control-slot clear changes `isAnimating()` only and must never stop the
27
+ * retained physical execution (pinned post-listener slot clear).
28
+ */
29
+ detachAnimationStopper(): void;
30
+ /** @internal Driver-port seam: change-observer presence for transported-write classification. */
31
+ hasChangeObservers(): boolean;
32
+ /** @internal Driver-port seam: derived-dependent presence among the change listeners. */
33
+ hasDerivedDependentObserver(): boolean;
34
+ /**
35
+ * @internal Driver-port seam: adopt an externally transported numeric sample — the value plus
36
+ * its OWNED sample history (never clock re-derived) — with change notification under the
37
+ * caller's control: transported writes notify; guarded candidate-cache terminals and
38
+ * synchronous snapshots adopt silently. A throwing observer propagates unchanged (the pinned
39
+ * notify law); the caller owns drain progress accounting across that abort.
40
+ */
41
+ applyExternalNumericSample(value: T, previousValue: number | undefined, currentTimestampMs: number, previousTimestampMs: number | undefined, notifyChange: boolean): void;
42
+ /**
43
+ * @internal Called when the animation driving this value ends (natural completion or stop). Puts
44
+ * the value at rest at its current value as of now: clears the animation velocity AND resets the
45
+ * clock-velocity samples (so `getVelocity` cannot leak a stale manual velocity from a `set` that
46
+ * preceded the animation, nor synthesize one from the animation's final value against a stale
47
+ * previous sample). Removes the stop hook. NOT called on interruption, where the current velocity
48
+ * must carry into the replacement generator's seed.
49
+ */
50
+ endAnimation(): void;
51
+ }
@@ -0,0 +1,185 @@
1
+ // MotionValue implementation (REQ-VALUE-001/010/011/012). Clock-timestamped velocity follows
2
+ // Motion 12.42.2's `motionValue.getVelocity` formula (the parity target pinned by M1b conformance):
3
+ // velocity from the last two timed samples, zero once the last sample is older than the staleness
4
+ // window, zero for non-numeric values. `subscribe` never triggers a host render.
5
+ //
6
+ // Two write paths, deliberately distinct (see M1-BUILD-PACKET gotchas): `set`/`jump` are the manual,
7
+ // clock-timestamped path; `applyAnimationSample` is the animation path that writes value+velocity
8
+ // straight from a generator without re-deriving velocity from the clock. While an animation drives
9
+ // this value, `getVelocity` reports the animation velocity; a manual `set`/`jump` reverts to the
10
+ // clock-sampled velocity. Methods below the public interface are internal seams for the graph.
11
+ import { reportMotionValueRead } from "./collect-reads.js";
12
+ import { MAX_VELOCITY_DELTA_MS } from "./config/constants.js";
13
+ import { Subscriptions } from "./subscriptions.js";
14
+ export class MotionValueImpl {
15
+ #clock;
16
+ #subs = new Subscriptions();
17
+ #current;
18
+ // Previous *numeric* sample and the two timestamps used for clock velocity. `#prev` is undefined
19
+ // whenever the prior value was non-numeric or after a teleport, which forces velocity to 0.
20
+ #prev;
21
+ #updatedAt;
22
+ #prevUpdatedAt;
23
+ // Velocity supplied by the driving animation, if any; takes precedence over the clock sample.
24
+ #animatedVelocity;
25
+ // Stop hook installed by the graph while an animation drives this value (REQ from `stop`).
26
+ #stopAnimation;
27
+ constructor(initial, clock) {
28
+ this.#current = initial;
29
+ this.#clock = clock;
30
+ this.#updatedAt = clock.now();
31
+ this.#prev = undefined;
32
+ this.#prevUpdatedAt = undefined;
33
+ this.#animatedVelocity = undefined;
34
+ this.#stopAnimation = undefined;
35
+ }
36
+ get() {
37
+ reportMotionValueRead(this);
38
+ return this.#current;
39
+ }
40
+ set(value) {
41
+ // Manual write reverts velocity tracking to the clock path.
42
+ this.#animatedVelocity = undefined;
43
+ // The pinned FRAME window (motion-dom updateAndNotify at the SS B pin, r15 4cf8a7d291e3):
44
+ // the previous sample rolls ONLY when time moved, so every same-frame write keeps
45
+ // comparing against the frame-START value and their velocities COMPOUND like the pin —
46
+ // never a per-write replacement that hides intra-frame motion.
47
+ const now = this.#clock.now();
48
+ if (now !== this.#updatedAt) {
49
+ this.#prev = typeof this.#current === 'number' ? this.#current : undefined;
50
+ this.#prevUpdatedAt = this.#updatedAt;
51
+ this.#updatedAt = now;
52
+ }
53
+ const previous = this.#current;
54
+ this.#current = value;
55
+ // Pinned suppression: an identical-value write does not notify (strict !== like the pin).
56
+ if (this.#current !== previous)
57
+ this.#subs.emitChange(this.#current);
58
+ }
59
+ jump(value) {
60
+ // Pinned jump ORDER (motion-dom `jump(v, endAnimation = true)`, r16/r17 — sealed by the
61
+ // motion-seams canary): (1) updateAndNotify — EXACTLY the set() path, so the change
62
+ // listener reads the teleport write's CLOCK velocity (never the generator's) with the
63
+ // animation still live; (2) the velocity window resets; (3) stop() — whose cancellation
64
+ // is notified while the animation is still observable and reads velocity 0. A dead flight
65
+ // can never overwrite the teleported value on the next frame (r15 59b71ef82c44).
66
+ this.set(value);
67
+ this.#prev = undefined;
68
+ this.#prevUpdatedAt = undefined;
69
+ this.#updatedAt = this.#clock.now();
70
+ this.stop();
71
+ }
72
+ subscribe(listener) {
73
+ return this.#subs.subscribe(listener);
74
+ }
75
+ getVelocity() {
76
+ if (this.#animatedVelocity !== undefined)
77
+ return this.#animatedVelocity;
78
+ const current = this.#current;
79
+ const prev = this.#prev;
80
+ const prevUpdatedAt = this.#prevUpdatedAt;
81
+ if (typeof current !== 'number' || prev === undefined || prevUpdatedAt === undefined)
82
+ return 0;
83
+ // Stale: no fresh sample within the window → treat as at rest (REQ-VALUE-011).
84
+ if (this.#clock.now() - this.#updatedAt > MAX_VELOCITY_DELTA_MS)
85
+ return 0;
86
+ const dt = Math.min(this.#updatedAt - prevUpdatedAt, MAX_VELOCITY_DELTA_MS);
87
+ if (dt <= 0)
88
+ return 0;
89
+ return ((current - prev) * 1000) / dt;
90
+ }
91
+ on(event, listener) {
92
+ // The implementation signature stays broad for delegation; it is NOT a public overload
93
+ // (r16 major 5 — lifecycle listeners are payload-free by contract).
94
+ return this.#subs.on(event, listener);
95
+ }
96
+ // The stop hook exists exactly while an animation drives this value (installed at animate,
97
+ // removed at endAnimation) — it IS the animation-liveness state, so no second flag can skew.
98
+ isAnimating() {
99
+ return this.#stopAnimation !== undefined;
100
+ }
101
+ stop() {
102
+ const stopper = this.#stopAnimation;
103
+ if (stopper === undefined)
104
+ return;
105
+ // Pinned lifecycle ORDER (r17, sealed by the motion-seams canary): the cancellation is
106
+ // notified while the animation is STILL OBSERVABLE (the pin clears `animation` only after
107
+ // notifying); the stopper — which also clears liveness through endAnimation — runs after.
108
+ // The emission lives here, not in destroy(), whose teardown is not a cancel.
109
+ this.#subs.emit('animationCancel');
110
+ stopper();
111
+ }
112
+ destroy() {
113
+ // Pinned teardown ORDER (r16 major 1): destroy announces itself FIRST — the listener
114
+ // observes the still-live animation — then the silent stop (never an animationCancel from
115
+ // teardown), then subscriptions drop.
116
+ this.#subs.emit('destroy');
117
+ this.#stopAnimation?.();
118
+ this.#subs.clear();
119
+ }
120
+ // --- internal seams for the graph (not part of the public MotionValue contract) ---------------
121
+ /**
122
+ * @internal Write an animation-produced sample: value and velocity straight from the generator,
123
+ * bypassing the clock-velocity path so velocity is not double-counted.
124
+ */
125
+ applyAnimationSample(value, velocity) {
126
+ this.#current = value;
127
+ this.#animatedVelocity = velocity;
128
+ this.#subs.emitChange(this.#current);
129
+ }
130
+ /** @internal Fire a lifecycle event (`animationStart`/`animationComplete`) on this value. */
131
+ emitEvent(event) {
132
+ this.#subs.emit(event);
133
+ }
134
+ /** @internal Install the stop hook the graph uses to wire `value.stop()` to the registry. */
135
+ attachAnimationStopper(stop) {
136
+ this.#stopAnimation = stop;
137
+ }
138
+ /**
139
+ * @internal Driver-port seam (REQ-VALUE-015): drop the stop hook WITHOUT invoking it — the
140
+ * token-blind public-control-slot clear changes `isAnimating()` only and must never stop the
141
+ * retained physical execution (pinned post-listener slot clear).
142
+ */
143
+ detachAnimationStopper() {
144
+ this.#stopAnimation = undefined;
145
+ }
146
+ /** @internal Driver-port seam: change-observer presence for transported-write classification. */
147
+ hasChangeObservers() {
148
+ return this.#subs.hasChangeObservers();
149
+ }
150
+ /** @internal Driver-port seam: derived-dependent presence among the change listeners. */
151
+ hasDerivedDependentObserver() {
152
+ return this.#subs.hasDerivedDependentObserver();
153
+ }
154
+ /**
155
+ * @internal Driver-port seam: adopt an externally transported numeric sample — the value plus
156
+ * its OWNED sample history (never clock re-derived) — with change notification under the
157
+ * caller's control: transported writes notify; guarded candidate-cache terminals and
158
+ * synchronous snapshots adopt silently. A throwing observer propagates unchanged (the pinned
159
+ * notify law); the caller owns drain progress accounting across that abort.
160
+ */
161
+ applyExternalNumericSample(value, previousValue, currentTimestampMs, previousTimestampMs, notifyChange) {
162
+ this.#animatedVelocity = undefined;
163
+ this.#current = value;
164
+ this.#prev = previousValue;
165
+ this.#prevUpdatedAt = previousTimestampMs;
166
+ this.#updatedAt = currentTimestampMs;
167
+ if (notifyChange)
168
+ this.#subs.emitChange(this.#current);
169
+ }
170
+ /**
171
+ * @internal Called when the animation driving this value ends (natural completion or stop). Puts
172
+ * the value at rest at its current value as of now: clears the animation velocity AND resets the
173
+ * clock-velocity samples (so `getVelocity` cannot leak a stale manual velocity from a `set` that
174
+ * preceded the animation, nor synthesize one from the animation's final value against a stale
175
+ * previous sample). Removes the stop hook. NOT called on interruption, where the current velocity
176
+ * must carry into the replacement generator's seed.
177
+ */
178
+ endAnimation() {
179
+ this.#animatedVelocity = undefined;
180
+ this.#prev = undefined;
181
+ this.#prevUpdatedAt = undefined;
182
+ this.#updatedAt = this.#clock.now();
183
+ this.#stopAnimation = undefined;
184
+ }
185
+ }