animl-cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1181) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +34 -0
  3. package/README.md +21 -2
  4. package/docs/AUTHORING.md +1530 -0
  5. package/docs/CLI.md +627 -0
  6. package/docs/parity/inventory.json +34570 -0
  7. package/examples/bell.ani +359 -0
  8. package/examples/bell.animl +114 -0
  9. package/examples/composite.ani +110 -0
  10. package/examples/composite.animl +21 -0
  11. package/examples/feather.ani +56 -0
  12. package/examples/feather.animl +20 -0
  13. package/examples/image-paints.ani +32 -0
  14. package/examples/image-paints.animl +8 -0
  15. package/examples/lib/cards.animl +9 -0
  16. package/examples/lib/shared.animl +10 -0
  17. package/examples/lib/ui.animl +8 -0
  18. package/examples/moon-toggle.png +0 -0
  19. package/examples/moon.ani +250 -0
  20. package/examples/moon.animl +55 -0
  21. package/examples/motion.ani +94 -0
  22. package/examples/motion.animl +24 -0
  23. package/examples/paint.ani +91 -0
  24. package/examples/paint.animl +21 -0
  25. package/examples/photo.ani +29 -0
  26. package/examples/photo.animl +6 -0
  27. package/examples/progress.ani +43 -0
  28. package/examples/progress.animl +6 -0
  29. package/examples/shapes.ani +109 -0
  30. package/examples/shapes.animl +16 -0
  31. package/examples/slice.ani +32 -0
  32. package/examples/slice.animl +9 -0
  33. package/examples/switch.ani +91 -0
  34. package/examples/switch.animl +22 -0
  35. package/examples/toolbar.ani +480 -0
  36. package/examples/toolbar.animl +115 -0
  37. package/examples/trash-ring.png +0 -0
  38. package/examples/trash.ani +357 -0
  39. package/examples/trash.animl +88 -0
  40. package/examples/web/index.html +520 -0
  41. package/examples/web/template.html +162 -0
  42. package/fixtures/bell/rive-reference/frames/00-rest.png +0 -0
  43. package/fixtures/bell/rive-reference/frames/01-hover.png +0 -0
  44. package/fixtures/bell/rive-reference/frames/02-ring-f3.png +0 -0
  45. package/fixtures/bell/rive-reference/frames/03-ring-f7.png +0 -0
  46. package/fixtures/bell/rive-reference/frames/04-ring-f17.png +0 -0
  47. package/fixtures/bell/rive-reference/frames/05-ring-f27.png +0 -0
  48. package/fixtures/bell/rive-reference/frames/06-ring-f57.png +0 -0
  49. package/fixtures/bell/rive-reference/frames/07-rested.png +0 -0
  50. package/fixtures/bell/rive-reference/frames/08-out.png +0 -0
  51. package/fixtures/bell/rive-reference/frames/commands.txt +24 -0
  52. package/llms.txt +30 -0
  53. package/modules/a11y/AUTHORING.md +108 -0
  54. package/modules/a11y/SPEC.md +304 -0
  55. package/modules/a11y/capabilities/a11y-focus-condition/example.ani +69 -0
  56. package/modules/a11y/capabilities/a11y-focus-condition/example.animl +17 -0
  57. package/modules/a11y/capabilities/a11y-inferred-text/example.ani +47 -0
  58. package/modules/a11y/capabilities/a11y-inferred-text/example.animl +13 -0
  59. package/modules/a11y/capabilities/a11y-property-heading-level/example.ani +26 -0
  60. package/modules/a11y/capabilities/a11y-property-heading-level/example.animl +6 -0
  61. package/modules/a11y/capabilities/a11y-tree-bounds/example.ani +88 -0
  62. package/modules/a11y/capabilities/a11y-tree-bounds/example.animl +18 -0
  63. package/modules/a11y/capabilities/a11y-tree-visibility/example.ani +16 -0
  64. package/modules/a11y/capabilities/a11y-tree-visibility/example.animl +4 -0
  65. package/modules/a11y/core/commands.source +135 -0
  66. package/modules/a11y/examples/contract.ani +74 -0
  67. package/modules/a11y/examples/contract.animl +15 -0
  68. package/modules/a11y/examples/focus-ancestor-policy.ani +72 -0
  69. package/modules/a11y/examples/focus-ancestor-policy.animl +16 -0
  70. package/modules/a11y/examples/painted-extents.ani +331 -0
  71. package/modules/a11y/examples/painted-extents.animl +62 -0
  72. package/modules/a11y/examples/painted-groups.ani +88 -0
  73. package/modules/a11y/examples/painted-groups.animl +18 -0
  74. package/modules/a11y/examples/slider-value-text.ani +53 -0
  75. package/modules/a11y/examples/slider-value-text.animl +11 -0
  76. package/modules/a11y/manifest.source +49 -0
  77. package/modules/a11y-actions/manifest.source +22 -0
  78. package/modules/a11y-dom/AUTHORING.md +35 -0
  79. package/modules/a11y-dom/SPEC.md +165 -0
  80. package/modules/a11y-dom/examples/mute.ani +28 -0
  81. package/modules/a11y-dom/examples/mute.animl +6 -0
  82. package/modules/a11y-dom/examples/navigation.ani +152 -0
  83. package/modules/a11y-dom/examples/navigation.animl +35 -0
  84. package/modules/a11y-dom/manifest.source +22 -0
  85. package/modules/assetio/SPEC.md +16 -0
  86. package/modules/assetio/manifest.source +12 -0
  87. package/modules/assets/SPEC.md +23 -0
  88. package/modules/assets/capabilities/assets-audio/example.ani +60 -0
  89. package/modules/assets/capabilities/assets-audio/example.animl +16 -0
  90. package/modules/assets/capabilities/assets-audio-clip/example.ani +62 -0
  91. package/modules/assets/capabilities/assets-audio-clip/example.animl +17 -0
  92. package/modules/assets/capabilities/assets-audio-export-source/example.ani +60 -0
  93. package/modules/assets/capabilities/assets-audio-export-source/example.animl +16 -0
  94. package/modules/assets/capabilities/assets-audio-import-flac/example.ani +63 -0
  95. package/modules/assets/capabilities/assets-audio-import-flac/example.animl +17 -0
  96. package/modules/assets/capabilities/assets-audio-import-mp3/example.ani +63 -0
  97. package/modules/assets/capabilities/assets-audio-import-mp3/example.animl +17 -0
  98. package/modules/assets/capabilities/assets-audio-import-wav/example.ani +60 -0
  99. package/modules/assets/capabilities/assets-audio-import-wav/example.animl +16 -0
  100. package/modules/assets/capabilities/assets-blob/example.ani +52 -0
  101. package/modules/assets/capabilities/assets-blob/example.animl +13 -0
  102. package/modules/assets/capabilities/assets-cdn-disable/example.ani +48 -0
  103. package/modules/assets/capabilities/assets-cdn-disable/example.animl +12 -0
  104. package/modules/assets/capabilities/assets-exclude-export/example.ani +49 -0
  105. package/modules/assets/capabilities/assets-exclude-export/example.animl +12 -0
  106. package/modules/assets/capabilities/assets-host-decode-audio/example.ani +60 -0
  107. package/modules/assets/capabilities/assets-host-decode-audio/example.animl +16 -0
  108. package/modules/assets/capabilities/assets-image-encoding-jpeg/example.ani +50 -0
  109. package/modules/assets/capabilities/assets-image-encoding-jpeg/example.animl +12 -0
  110. package/modules/assets/capabilities/assets-image-encoding-png/example.ani +50 -0
  111. package/modules/assets/capabilities/assets-image-encoding-png/example.animl +12 -0
  112. package/modules/assets/capabilities/assets-image-encoding-webp/example.ani +50 -0
  113. package/modules/assets/capabilities/assets-image-encoding-webp/example.animl +12 -0
  114. package/modules/assets/capabilities/assets-image-export-controls/example.ani +49 -0
  115. package/modules/assets/capabilities/assets-image-export-controls/example.animl +12 -0
  116. package/modules/assets/capabilities/assets-image-filter-bilinear/example.ani +49 -0
  117. package/modules/assets/capabilities/assets-image-filter-bilinear/example.animl +12 -0
  118. package/modules/assets/capabilities/assets-image-filter-nearest/example.ani +49 -0
  119. package/modules/assets/capabilities/assets-image-filter-nearest/example.animl +12 -0
  120. package/modules/assets/capabilities/assets-image-import-jpeg/example.ani +49 -0
  121. package/modules/assets/capabilities/assets-image-import-jpeg/example.animl +12 -0
  122. package/modules/assets/capabilities/assets-image-import-jpg/example.ani +49 -0
  123. package/modules/assets/capabilities/assets-image-import-jpg/example.animl +12 -0
  124. package/modules/assets/capabilities/assets-image-import-png/example.ani +49 -0
  125. package/modules/assets/capabilities/assets-image-import-png/example.animl +12 -0
  126. package/modules/assets/capabilities/assets-image-import-webp/example.ani +49 -0
  127. package/modules/assets/capabilities/assets-image-import-webp/example.animl +12 -0
  128. package/modules/assets/capabilities/assets-image-wrap-x-clamp/example.ani +50 -0
  129. package/modules/assets/capabilities/assets-image-wrap-x-clamp/example.animl +12 -0
  130. package/modules/assets/capabilities/assets-image-wrap-x-mirror/example.ani +50 -0
  131. package/modules/assets/capabilities/assets-image-wrap-x-mirror/example.animl +12 -0
  132. package/modules/assets/capabilities/assets-image-wrap-x-repeat/example.ani +50 -0
  133. package/modules/assets/capabilities/assets-image-wrap-x-repeat/example.animl +12 -0
  134. package/modules/assets/capabilities/assets-image-wrap-y-clamp/example.ani +50 -0
  135. package/modules/assets/capabilities/assets-image-wrap-y-clamp/example.animl +12 -0
  136. package/modules/assets/capabilities/assets-image-wrap-y-mirror/example.ani +50 -0
  137. package/modules/assets/capabilities/assets-image-wrap-y-mirror/example.animl +12 -0
  138. package/modules/assets/capabilities/assets-image-wrap-y-repeat/example.ani +50 -0
  139. package/modules/assets/capabilities/assets-image-wrap-y-repeat/example.animl +12 -0
  140. package/modules/assets/capabilities/assets-lottie-import/example.ani +58 -0
  141. package/modules/assets/capabilities/assets-lottie-import/example.animl +20 -0
  142. package/modules/assets/capabilities/assets-metadata/example.ani +53 -0
  143. package/modules/assets/capabilities/assets-metadata/example.animl +12 -0
  144. package/modules/assets/capabilities/assets-replacement-scope-image/example.ani +48 -0
  145. package/modules/assets/capabilities/assets-replacement-scope-image/example.animl +12 -0
  146. package/modules/assets/capabilities/assets-svg-import/example.ani +52 -0
  147. package/modules/assets/capabilities/assets-svg-import/example.animl +11 -0
  148. package/modules/assets/capabilities/host-api-asset-cdn-policy/example.ani +48 -0
  149. package/modules/assets/capabilities/host-api-asset-cdn-policy/example.animl +12 -0
  150. package/modules/assets/capabilities/host-api-asset-loader/example.ani +48 -0
  151. package/modules/assets/capabilities/host-api-asset-loader/example.animl +12 -0
  152. package/modules/assets/capabilities/host-api-asset-metadata/example.ani +53 -0
  153. package/modules/assets/capabilities/host-api-asset-metadata/example.animl +12 -0
  154. package/modules/assets/manifest.source +79 -0
  155. package/modules/bind/AUTHORING.md +49 -0
  156. package/modules/bind/capabilities/data-bind-listener-actions/example.ani +30 -0
  157. package/modules/bind/capabilities/data-bind-listener-actions/example.animl +5 -0
  158. package/modules/bind/core/expression.source +224 -0
  159. package/modules/bind/examples/filtering-list.ani +122 -0
  160. package/modules/bind/examples/filtering-list.animl +29 -0
  161. package/modules/bind/manifest.source +58 -0
  162. package/modules/canvas/capabilities/host-api-device-pixel-ratio/example.ani +16 -0
  163. package/modules/canvas/capabilities/host-api-device-pixel-ratio/example.animl +3 -0
  164. package/modules/canvas/capabilities/host-api-resize-backing-surface/example.ani +16 -0
  165. package/modules/canvas/capabilities/host-api-resize-backing-surface/example.animl +3 -0
  166. package/modules/canvas/capabilities/platforms-web-canvas/example.ani +16 -0
  167. package/modules/canvas/capabilities/platforms-web-canvas/example.animl +3 -0
  168. package/modules/canvas/manifest.source +16 -0
  169. package/modules/colormix/examples/switch.ani +51 -0
  170. package/modules/colormix/examples/switch.animl +13 -0
  171. package/modules/colormix/manifest.source +39 -0
  172. package/modules/componentpaint/capabilities/drawing-paint-paint-artboard/example.ani +72 -0
  173. package/modules/componentpaint/capabilities/drawing-paint-paint-artboard/example.animl +13 -0
  174. package/modules/componentpaint/examples/live.ani +72 -0
  175. package/modules/componentpaint/examples/live.animl +13 -0
  176. package/modules/componentpaint/manifest.source +29 -0
  177. package/modules/components/manifest.source +61 -0
  178. package/modules/composite/manifest.source +49 -0
  179. package/modules/contour/capabilities/drawing-paint-asymmetric-vertex/example.ani +32 -0
  180. package/modules/contour/capabilities/drawing-paint-asymmetric-vertex/example.animl +5 -0
  181. package/modules/contour/capabilities/drawing-paint-detached-vertex/example.ani +32 -0
  182. package/modules/contour/capabilities/drawing-paint-detached-vertex/example.animl +5 -0
  183. package/modules/contour/capabilities/drawing-paint-effect-order/example.ani +28 -0
  184. package/modules/contour/capabilities/drawing-paint-effect-order/example.animl +3 -0
  185. package/modules/contour/capabilities/drawing-paint-fill-rule-clockwise/example.ani +17 -0
  186. package/modules/contour/capabilities/drawing-paint-fill-rule-clockwise/example.animl +3 -0
  187. package/modules/contour/capabilities/drawing-paint-geometry-origin/example.ani +35 -0
  188. package/modules/contour/capabilities/drawing-paint-geometry-origin/example.animl +5 -0
  189. package/modules/contour/capabilities/drawing-paint-mirrored-vertex/example.ani +31 -0
  190. package/modules/contour/capabilities/drawing-paint-mirrored-vertex/example.animl +5 -0
  191. package/modules/contour/capabilities/drawing-paint-rounded-vertex/example.ani +31 -0
  192. package/modules/contour/capabilities/drawing-paint-rounded-vertex/example.animl +5 -0
  193. package/modules/contour/capabilities/drawing-paint-shared-effects/example.ani +26 -0
  194. package/modules/contour/capabilities/drawing-paint-shared-effects/example.animl +3 -0
  195. package/modules/contour/capabilities/drawing-paint-straight-vertex/example.ani +30 -0
  196. package/modules/contour/capabilities/drawing-paint-straight-vertex/example.animl +5 -0
  197. package/modules/contour/capabilities/drawing-paint-trim-sequential/example.ani +22 -0
  198. package/modules/contour/capabilities/drawing-paint-trim-sequential/example.animl +3 -0
  199. package/modules/contour/capabilities/drawing-paint-trim-synchronized/example.ani +22 -0
  200. package/modules/contour/capabilities/drawing-paint-trim-synchronized/example.animl +3 -0
  201. package/modules/contour/manifest.source +47 -0
  202. package/modules/convert/SPEC.md +140 -0
  203. package/modules/convert/capabilities/data-converter-color-formatting/example.ani +44 -0
  204. package/modules/convert/capabilities/data-converter-color-formatting/example.animl +8 -0
  205. package/modules/convert/capabilities/data-converter-trigger/example.ani +78 -0
  206. package/modules/convert/capabilities/data-converter-trigger/example.animl +22 -0
  207. package/modules/convert/core/math.source +126 -0
  208. package/modules/convert/examples/score.ani +60 -0
  209. package/modules/convert/examples/score.animl +12 -0
  210. package/modules/convert/examples/travelling-indicator.ani +41 -0
  211. package/modules/convert/examples/travelling-indicator.animl +8 -0
  212. package/modules/convert/manifest.source +53 -0
  213. package/modules/corners/examples/switch.ani +20 -0
  214. package/modules/corners/examples/switch.animl +3 -0
  215. package/modules/corners/manifest.source +25 -0
  216. package/modules/data/SPEC.md +216 -0
  217. package/modules/data/capabilities/data-bind-conditions/example.ani +52 -0
  218. package/modules/data/capabilities/data-bind-conditions/example.animl +17 -0
  219. package/modules/data/capabilities/data-bind-converter-options/example.ani +16 -0
  220. package/modules/data/capabilities/data-bind-converter-options/example.animl +4 -0
  221. package/modules/data/capabilities/data-bind-instance-values/example.ani +66 -0
  222. package/modules/data/capabilities/data-bind-instance-values/example.animl +17 -0
  223. package/modules/data/capabilities/data-bind-keyframes/example.ani +16 -0
  224. package/modules/data/capabilities/data-bind-keyframes/example.animl +4 -0
  225. package/modules/data/capabilities/data-bind-script-inputs/example.ani +16 -0
  226. package/modules/data/capabilities/data-bind-script-inputs/example.animl +4 -0
  227. package/modules/data/capabilities/data-bind-text/example.ani +16 -0
  228. package/modules/data/capabilities/data-bind-text/example.animl +4 -0
  229. package/modules/data/capabilities/data-binding-absolute/example.ani +43 -0
  230. package/modules/data/capabilities/data-binding-absolute/example.animl +13 -0
  231. package/modules/data/capabilities/data-binding-once/example.ani +58 -0
  232. package/modules/data/capabilities/data-binding-once/example.animl +20 -0
  233. package/modules/data/capabilities/data-binding-relative/example.ani +16 -0
  234. package/modules/data/capabilities/data-binding-relative/example.animl +4 -0
  235. package/modules/data/capabilities/data-binding-to-source/example.ani +58 -0
  236. package/modules/data/capabilities/data-binding-to-source/example.animl +20 -0
  237. package/modules/data/capabilities/data-binding-two-way-prefer-source/example.ani +59 -0
  238. package/modules/data/capabilities/data-binding-two-way-prefer-source/example.animl +20 -0
  239. package/modules/data/capabilities/data-binding-two-way-prefer-target/example.ani +59 -0
  240. package/modules/data/capabilities/data-binding-two-way-prefer-target/example.animl +20 -0
  241. package/modules/data/capabilities/data-component-public-data/example.ani +66 -0
  242. package/modules/data/capabilities/data-component-public-data/example.animl +17 -0
  243. package/modules/data/capabilities/data-custom-property-boolean/example.ani +35 -0
  244. package/modules/data/capabilities/data-custom-property-boolean/example.animl +9 -0
  245. package/modules/data/capabilities/data-custom-property-color/example.ani +38 -0
  246. package/modules/data/capabilities/data-custom-property-color/example.animl +9 -0
  247. package/modules/data/capabilities/data-custom-property-enum/example.ani +40 -0
  248. package/modules/data/capabilities/data-custom-property-enum/example.animl +9 -0
  249. package/modules/data/capabilities/data-custom-property-number/example.ani +35 -0
  250. package/modules/data/capabilities/data-custom-property-number/example.animl +9 -0
  251. package/modules/data/capabilities/data-custom-property-string/example.ani +35 -0
  252. package/modules/data/capabilities/data-custom-property-string/example.animl +9 -0
  253. package/modules/data/capabilities/data-custom-property-trigger/example.ani +52 -0
  254. package/modules/data/capabilities/data-custom-property-trigger/example.animl +15 -0
  255. package/modules/data/capabilities/data-default-instance/example.ani +43 -0
  256. package/modules/data/capabilities/data-default-instance/example.animl +13 -0
  257. package/modules/data/capabilities/data-editor-preview-instance/example.ani +16 -0
  258. package/modules/data/capabilities/data-editor-preview-instance/example.animl +4 -0
  259. package/modules/data/capabilities/data-enum-members/example.ani +41 -0
  260. package/modules/data/capabilities/data-enum-members/example.animl +8 -0
  261. package/modules/data/capabilities/data-global-model/example.ani +64 -0
  262. package/modules/data/capabilities/data-global-model/example.animl +17 -0
  263. package/modules/data/capabilities/data-instance-export/example.ani +41 -0
  264. package/modules/data/capabilities/data-instance-export/example.animl +8 -0
  265. package/modules/data/capabilities/data-list-items/example.ani +55 -0
  266. package/modules/data/capabilities/data-list-items/example.animl +16 -0
  267. package/modules/data/capabilities/data-model-definition/example.ani +43 -0
  268. package/modules/data/capabilities/data-model-definition/example.animl +13 -0
  269. package/modules/data/capabilities/data-named-instances/example.ani +43 -0
  270. package/modules/data/capabilities/data-named-instances/example.animl +13 -0
  271. package/modules/data/capabilities/data-operation-operand-view-model/example.ani +32 -0
  272. package/modules/data/capabilities/data-operation-operand-view-model/example.animl +9 -0
  273. package/modules/data/capabilities/data-property-artboard/example.ani +16 -0
  274. package/modules/data/capabilities/data-property-artboard/example.animl +4 -0
  275. package/modules/data/capabilities/data-property-asset-blob/example.ani +40 -0
  276. package/modules/data/capabilities/data-property-asset-blob/example.animl +10 -0
  277. package/modules/data/capabilities/data-property-asset-font/example.ani +40 -0
  278. package/modules/data/capabilities/data-property-asset-font/example.animl +10 -0
  279. package/modules/data/capabilities/data-property-asset-image/example.ani +40 -0
  280. package/modules/data/capabilities/data-property-asset-image/example.animl +12 -0
  281. package/modules/data/capabilities/data-property-boolean/example.ani +28 -0
  282. package/modules/data/capabilities/data-property-boolean/example.animl +7 -0
  283. package/modules/data/capabilities/data-property-color/example.ani +29 -0
  284. package/modules/data/capabilities/data-property-color/example.animl +7 -0
  285. package/modules/data/capabilities/data-property-enum-custom/example.ani +29 -0
  286. package/modules/data/capabilities/data-property-enum-custom/example.animl +7 -0
  287. package/modules/data/capabilities/data-property-enum-system/example.ani +29 -0
  288. package/modules/data/capabilities/data-property-enum-system/example.animl +7 -0
  289. package/modules/data/capabilities/data-property-list/example.ani +55 -0
  290. package/modules/data/capabilities/data-property-list/example.animl +16 -0
  291. package/modules/data/capabilities/data-property-number/example.ani +29 -0
  292. package/modules/data/capabilities/data-property-number/example.animl +7 -0
  293. package/modules/data/capabilities/data-property-string/example.ani +29 -0
  294. package/modules/data/capabilities/data-property-string/example.animl +7 -0
  295. package/modules/data/capabilities/data-property-symbol-list-index/example.ani +16 -0
  296. package/modules/data/capabilities/data-property-symbol-list-index/example.animl +4 -0
  297. package/modules/data/capabilities/data-property-text-style/example.ani +16 -0
  298. package/modules/data/capabilities/data-property-text-style/example.animl +4 -0
  299. package/modules/data/capabilities/data-property-trigger/example.ani +51 -0
  300. package/modules/data/capabilities/data-property-trigger/example.animl +17 -0
  301. package/modules/data/capabilities/data-property-view-model/example.ani +55 -0
  302. package/modules/data/capabilities/data-property-view-model/example.animl +16 -0
  303. package/modules/data/capabilities/data-script-model-api/example.ani +16 -0
  304. package/modules/data/capabilities/data-script-model-api/example.animl +4 -0
  305. package/modules/data/capabilities/data-scripted-converter/example.ani +16 -0
  306. package/modules/data/capabilities/data-scripted-converter/example.animl +4 -0
  307. package/modules/data/capabilities/data-state-action-trigger/example.ani +51 -0
  308. package/modules/data/capabilities/data-state-action-trigger/example.animl +17 -0
  309. package/modules/data/capabilities/data-system-enum-horizontal-text-align/example.ani +29 -0
  310. package/modules/data/capabilities/data-system-enum-horizontal-text-align/example.animl +7 -0
  311. package/modules/data/capabilities/data-system-enum-layout-alignment/example.ani +29 -0
  312. package/modules/data/capabilities/data-system-enum-layout-alignment/example.animl +7 -0
  313. package/modules/data/capabilities/data-system-enum-layout-direction/example.ani +29 -0
  314. package/modules/data/capabilities/data-system-enum-layout-direction/example.animl +7 -0
  315. package/modules/data/capabilities/data-system-enum-layout-display/example.ani +29 -0
  316. package/modules/data/capabilities/data-system-enum-layout-display/example.animl +7 -0
  317. package/modules/data/capabilities/data-system-enum-layout-flex-direction/example.ani +29 -0
  318. package/modules/data/capabilities/data-system-enum-layout-flex-direction/example.animl +7 -0
  319. package/modules/data/capabilities/data-system-enum-layout-position/example.ani +29 -0
  320. package/modules/data/capabilities/data-system-enum-layout-position/example.animl +7 -0
  321. package/modules/data/capabilities/data-system-enum-layout-position-units/example.ani +29 -0
  322. package/modules/data/capabilities/data-system-enum-layout-position-units/example.animl +7 -0
  323. package/modules/data/capabilities/data-system-enum-layout-scale-type/example.ani +29 -0
  324. package/modules/data/capabilities/data-system-enum-layout-scale-type/example.animl +7 -0
  325. package/modules/data/capabilities/data-system-enum-layout-wrap/example.ani +29 -0
  326. package/modules/data/capabilities/data-system-enum-layout-wrap/example.animl +7 -0
  327. package/modules/data/capabilities/data-system-enum-scroll-direction/example.ani +29 -0
  328. package/modules/data/capabilities/data-system-enum-scroll-direction/example.animl +7 -0
  329. package/modules/data/capabilities/data-system-enum-semantic-check-state/example.ani +29 -0
  330. package/modules/data/capabilities/data-system-enum-semantic-check-state/example.animl +7 -0
  331. package/modules/data/capabilities/data-system-enum-text-box/example.ani +29 -0
  332. package/modules/data/capabilities/data-system-enum-text-box/example.animl +7 -0
  333. package/modules/data/capabilities/data-system-enum-text-overflow/example.ani +29 -0
  334. package/modules/data/capabilities/data-system-enum-text-overflow/example.animl +7 -0
  335. package/modules/data/capabilities/data-system-enum-text-trim-bottom/example.ani +29 -0
  336. package/modules/data/capabilities/data-system-enum-text-trim-bottom/example.animl +7 -0
  337. package/modules/data/capabilities/data-system-enum-text-trim-top/example.ani +29 -0
  338. package/modules/data/capabilities/data-system-enum-text-trim-top/example.animl +7 -0
  339. package/modules/data/capabilities/data-system-enum-vertical-text-align/example.ani +29 -0
  340. package/modules/data/capabilities/data-system-enum-vertical-text-align/example.animl +7 -0
  341. package/modules/data/capabilities/data-system-enum-word-wrap/example.ani +29 -0
  342. package/modules/data/capabilities/data-system-enum-word-wrap/example.animl +7 -0
  343. package/modules/data/capabilities/drawing-paint-gradient-channel-binding/example.ani +46 -0
  344. package/modules/data/capabilities/drawing-paint-gradient-channel-binding/example.animl +14 -0
  345. package/modules/data/capabilities/input-data-listener/example.ani +41 -0
  346. package/modules/data/capabilities/input-data-listener/example.animl +10 -0
  347. package/modules/data/capabilities/input-write-data-constant/example.ani +41 -0
  348. package/modules/data/capabilities/input-write-data-constant/example.animl +10 -0
  349. package/modules/data/capabilities/input-write-data-copy/example.ani +39 -0
  350. package/modules/data/capabilities/input-write-data-copy/example.animl +10 -0
  351. package/modules/data/capabilities/input-write-focused/example.ani +39 -0
  352. package/modules/data/capabilities/input-write-focused/example.animl +11 -0
  353. package/modules/data/capabilities/input-write-gamepad-axis/example.ani +38 -0
  354. package/modules/data/capabilities/input-write-gamepad-axis/example.animl +9 -0
  355. package/modules/data/capabilities/input-write-gamepad-button-pressed/example.ani +38 -0
  356. package/modules/data/capabilities/input-write-gamepad-button-pressed/example.animl +9 -0
  357. package/modules/data/capabilities/input-write-gamepad-button-value/example.ani +38 -0
  358. package/modules/data/capabilities/input-write-gamepad-button-value/example.animl +9 -0
  359. package/modules/data/capabilities/input-write-gamepad-changed-value/example.ani +38 -0
  360. package/modules/data/capabilities/input-write-gamepad-changed-value/example.animl +9 -0
  361. package/modules/data/examples/data-extras.ani +109 -0
  362. package/modules/data/examples/data-extras.animl +28 -0
  363. package/modules/data/examples/scoreboard.ani +74 -0
  364. package/modules/data/examples/scoreboard.animl +18 -0
  365. package/modules/data/manifest.source +111 -0
  366. package/modules/data-artboard/SPEC.md +94 -0
  367. package/modules/data-artboard/capabilities/components-host-artboard-reference/example.ani +16 -0
  368. package/modules/data-artboard/capabilities/components-host-artboard-reference/example.animl +26 -0
  369. package/modules/data-artboard/capabilities/data-host-artboard/example.ani +16 -0
  370. package/modules/data-artboard/capabilities/data-host-artboard/example.animl +18 -0
  371. package/modules/data-artboard/capabilities/host-api-bindable-artboards/example.ani +16 -0
  372. package/modules/data-artboard/capabilities/host-api-bindable-artboards/example.animl +34 -0
  373. package/modules/data-artboard/capabilities/host-api-data-artboard/example.ani +16 -0
  374. package/modules/data-artboard/capabilities/host-api-data-artboard/example.animl +18 -0
  375. package/modules/data-artboard/manifest.source +41 -0
  376. package/modules/data-host/SPEC.md +50 -0
  377. package/modules/data-host/examples/nested.ani +82 -0
  378. package/modules/data-host/examples/nested.animl +21 -0
  379. package/modules/data-host/manifest.source +26 -0
  380. package/modules/derive/SPEC.md +66 -0
  381. package/modules/derive/examples/scoreboard.ani +121 -0
  382. package/modules/derive/examples/scoreboard.animl +27 -0
  383. package/modules/derive/manifest.source +24 -0
  384. package/modules/devtools/SPEC.md +25 -0
  385. package/modules/devtools/capabilities/host-api-deprecation-policy/example.ani +16 -0
  386. package/modules/devtools/capabilities/host-api-deprecation-policy/example.animl +3 -0
  387. package/modules/devtools/capabilities/host-api-fps-counter/example.ani +16 -0
  388. package/modules/devtools/capabilities/host-api-fps-counter/example.animl +9 -0
  389. package/modules/devtools/capabilities/host-api-performance-marks/example.ani +16 -0
  390. package/modules/devtools/capabilities/host-api-performance-marks/example.animl +3 -0
  391. package/modules/devtools/capabilities/host-api-stop/example.ani +16 -0
  392. package/modules/devtools/capabilities/host-api-stop/example.animl +3 -0
  393. package/modules/devtools/manifest.source +13 -0
  394. package/modules/events/examples/events.ani +75 -0
  395. package/modules/events/examples/events.animl +23 -0
  396. package/modules/events/examples/share-evidence/README.md +48 -0
  397. package/modules/events/examples/share-evidence/clicks.jsonl +51 -0
  398. package/modules/events/examples/share-evidence/clicks.script +102 -0
  399. package/modules/events/examples/share-evidence/interrupt.jsonl +3 -0
  400. package/modules/events/examples/share-evidence/interrupt.script +7 -0
  401. package/modules/events/examples/share-evidence/perf.txt +4 -0
  402. package/modules/events/examples/share-evidence/pulse.jsonl +10 -0
  403. package/modules/events/examples/share-evidence/pulse.script +20 -0
  404. package/modules/events/examples/share-evidence/rest-diff.txt +1 -0
  405. package/modules/events/examples/share-evidence/size.txt +18 -0
  406. package/modules/events/examples/share-evidence/verify.py +48 -0
  407. package/modules/events/examples/share-tone.wav +0 -0
  408. package/modules/events/examples/share.ani +186 -0
  409. package/modules/events/examples/share.animl +40 -0
  410. package/modules/events/manifest.source +74 -0
  411. package/modules/focus/capabilities/input-focus-clear/example.ani +116 -0
  412. package/modules/focus/capabilities/input-focus-clear/example.animl +28 -0
  413. package/modules/focus/capabilities/input-focus-down/example.ani +116 -0
  414. package/modules/focus/capabilities/input-focus-down/example.animl +28 -0
  415. package/modules/focus/capabilities/input-focus-edge-closed-loop/example.ani +116 -0
  416. package/modules/focus/capabilities/input-focus-edge-closed-loop/example.animl +28 -0
  417. package/modules/focus/capabilities/input-focus-edge-parent-scope/example.ani +116 -0
  418. package/modules/focus/capabilities/input-focus-edge-parent-scope/example.animl +28 -0
  419. package/modules/focus/capabilities/input-focus-edge-stop/example.ani +116 -0
  420. package/modules/focus/capabilities/input-focus-edge-stop/example.animl +28 -0
  421. package/modules/focus/capabilities/input-focus-enabled/example.ani +116 -0
  422. package/modules/focus/capabilities/input-focus-enabled/example.animl +28 -0
  423. package/modules/focus/capabilities/input-focus-left/example.ani +116 -0
  424. package/modules/focus/capabilities/input-focus-left/example.animl +28 -0
  425. package/modules/focus/capabilities/input-focus-next/example.ani +116 -0
  426. package/modules/focus/capabilities/input-focus-next/example.animl +28 -0
  427. package/modules/focus/capabilities/input-focus-pointer/example.ani +116 -0
  428. package/modules/focus/capabilities/input-focus-pointer/example.animl +28 -0
  429. package/modules/focus/capabilities/input-focus-previous/example.ani +116 -0
  430. package/modules/focus/capabilities/input-focus-previous/example.animl +28 -0
  431. package/modules/focus/capabilities/input-focus-right/example.ani +116 -0
  432. package/modules/focus/capabilities/input-focus-right/example.animl +28 -0
  433. package/modules/focus/capabilities/input-focus-target/example.ani +116 -0
  434. package/modules/focus/capabilities/input-focus-target/example.animl +28 -0
  435. package/modules/focus/capabilities/input-focus-traverse/example.ani +116 -0
  436. package/modules/focus/capabilities/input-focus-traverse/example.animl +28 -0
  437. package/modules/focus/capabilities/input-focus-up/example.ani +116 -0
  438. package/modules/focus/capabilities/input-focus-up/example.animl +28 -0
  439. package/modules/focus/capabilities/input-listen-blur/example.ani +116 -0
  440. package/modules/focus/capabilities/input-listen-blur/example.animl +28 -0
  441. package/modules/focus/capabilities/input-listen-focus/example.ani +116 -0
  442. package/modules/focus/capabilities/input-listen-focus/example.animl +28 -0
  443. package/modules/focus/examples/README.md +10 -0
  444. package/modules/focus/examples/traversal.ani +70 -0
  445. package/modules/focus/examples/traversal.animl +16 -0
  446. package/modules/focus/manifest.source +48 -0
  447. package/modules/fonts/SPEC.md +92 -0
  448. package/modules/fonts/capabilities/assets-font/example.ani +23 -0
  449. package/modules/fonts/capabilities/assets-font/example.animl +5 -0
  450. package/modules/fonts/capabilities/assets-host-decode-font/example.ani +23 -0
  451. package/modules/fonts/capabilities/assets-host-decode-font/example.animl +5 -0
  452. package/modules/fonts/capabilities/assets-replacement-scope-font/example.ani +23 -0
  453. package/modules/fonts/capabilities/assets-replacement-scope-font/example.animl +5 -0
  454. package/modules/fonts/capabilities/data-host-font/example.ani +23 -0
  455. package/modules/fonts/capabilities/data-host-font/example.animl +5 -0
  456. package/modules/fonts/capabilities/host-api-data-font/example.ani +23 -0
  457. package/modules/fonts/capabilities/host-api-data-font/example.animl +5 -0
  458. package/modules/fonts/capabilities/host-api-font-fallback/example.ani +23 -0
  459. package/modules/fonts/capabilities/host-api-font-fallback/example.animl +5 -0
  460. package/modules/fonts/capabilities/text-font-assets/example.ani +23 -0
  461. package/modules/fonts/capabilities/text-font-assets/example.animl +5 -0
  462. package/modules/fonts/capabilities/text-font-decoding/example.ani +43 -0
  463. package/modules/fonts/capabilities/text-font-decoding/example.animl +9 -0
  464. package/modules/fonts/capabilities/text-font-fallback/example.ani +23 -0
  465. package/modules/fonts/capabilities/text-font-fallback/example.animl +5 -0
  466. package/modules/fonts/capabilities/text-font-metrics/example.ani +40 -0
  467. package/modules/fonts/capabilities/text-font-metrics/example.animl +9 -0
  468. package/modules/fonts/capabilities/text-opentype-features/example.ani +23 -0
  469. package/modules/fonts/capabilities/text-opentype-features/example.animl +7 -0
  470. package/modules/fonts/capabilities/text-variable-axes/example.ani +23 -0
  471. package/modules/fonts/capabilities/text-variable-axes/example.animl +9 -0
  472. package/modules/fonts/manifest.source +57 -0
  473. package/modules/host/AUTHORING.md +33 -0
  474. package/modules/host/SPEC.md +76 -0
  475. package/modules/host/examples/page.html +23 -0
  476. package/modules/host/examples/score.ani +48 -0
  477. package/modules/host/examples/score.animl +10 -0
  478. package/modules/host/manifest.source +19 -0
  479. package/modules/host-edit/AUTHORING.md +66 -0
  480. package/modules/host-edit/SPEC.md +96 -0
  481. package/modules/host-edit/examples/board.ani +72 -0
  482. package/modules/host-edit/examples/board.animl +17 -0
  483. package/modules/host-edit/examples/page.html +34 -0
  484. package/modules/host-edit/manifest.source +21 -0
  485. package/modules/host-fit/capabilities/host-api-fit-layout/example.ani +55 -0
  486. package/modules/host-fit/capabilities/host-api-fit-layout/example.animl +11 -0
  487. package/modules/host-fit/examples/tile.ani +19 -0
  488. package/modules/host-fit/examples/tile.animl +3 -0
  489. package/modules/host-fit/manifest.source +22 -0
  490. package/modules/host-values/SPEC.md +9 -0
  491. package/modules/host-values/manifest.source +22 -0
  492. package/modules/ik/capabilities/rigging-ik/example.ani +16 -0
  493. package/modules/ik/capabilities/rigging-ik/example.animl +11 -0
  494. package/modules/ik/capabilities/rigging-ik-bend/example.ani +16 -0
  495. package/modules/ik/capabilities/rigging-ik-bend/example.animl +11 -0
  496. package/modules/ik/examples/reach.ani +100 -0
  497. package/modules/ik/examples/reach.animl +18 -0
  498. package/modules/ik/manifest.source +30 -0
  499. package/modules/input/SPEC.md +397 -0
  500. package/modules/input/capabilities/host-api-coordinate-mapping/example.ani +32 -0
  501. package/modules/input/capabilities/host-api-coordinate-mapping/example.animl +7 -0
  502. package/modules/input/capabilities/host-api-dom-listener-control/example.ani +32 -0
  503. package/modules/input/capabilities/host-api-dom-listener-control/example.animl +7 -0
  504. package/modules/input/capabilities/host-api-gamepad/example.ani +34 -0
  505. package/modules/input/capabilities/host-api-gamepad/example.animl +7 -0
  506. package/modules/input/capabilities/host-api-low-level-pointer/example.ani +32 -0
  507. package/modules/input/capabilities/host-api-low-level-pointer/example.animl +7 -0
  508. package/modules/input/capabilities/host-api-low-level-scroll/example.ani +32 -0
  509. package/modules/input/capabilities/host-api-low-level-scroll/example.animl +7 -0
  510. package/modules/input/capabilities/host-api-manual-gamepad-batch/example.ani +34 -0
  511. package/modules/input/capabilities/host-api-manual-gamepad-batch/example.animl +7 -0
  512. package/modules/input/capabilities/host-api-multitouch/example.ani +32 -0
  513. package/modules/input/capabilities/host-api-multitouch/example.animl +7 -0
  514. package/modules/input/capabilities/host-api-pointer-exit/example.ani +31 -0
  515. package/modules/input/capabilities/host-api-pointer-exit/example.animl +7 -0
  516. package/modules/input/capabilities/host-api-scroll-hit-policy/example.ani +32 -0
  517. package/modules/input/capabilities/host-api-scroll-hit-policy/example.animl +7 -0
  518. package/modules/input/capabilities/host-api-scroll-latching/example.ani +32 -0
  519. package/modules/input/capabilities/host-api-scroll-latching/example.animl +7 -0
  520. package/modules/input/capabilities/input-align-preserve/example.ani +43 -0
  521. package/modules/input/capabilities/input-align-preserve/example.animl +9 -0
  522. package/modules/input/capabilities/input-align-snap/example.ani +43 -0
  523. package/modules/input/capabilities/input-align-snap/example.animl +9 -0
  524. package/modules/input/capabilities/input-bounded-hit-tests/example.ani +32 -0
  525. package/modules/input/capabilities/input-bounded-hit-tests/example.animl +7 -0
  526. package/modules/input/capabilities/input-button-middle/example.ani +33 -0
  527. package/modules/input/capabilities/input-button-middle/example.animl +7 -0
  528. package/modules/input/capabilities/input-button-secondary/example.ani +33 -0
  529. package/modules/input/capabilities/input-button-secondary/example.animl +7 -0
  530. package/modules/input/capabilities/input-capture-phase/example.ani +60 -0
  531. package/modules/input/capabilities/input-capture-phase/example.animl +11 -0
  532. package/modules/input/capabilities/input-cursor-arrow/example.ani +33 -0
  533. package/modules/input/capabilities/input-cursor-arrow/example.animl +7 -0
  534. package/modules/input/capabilities/input-cursor-hidden/example.ani +33 -0
  535. package/modules/input/capabilities/input-cursor-hidden/example.animl +7 -0
  536. package/modules/input/capabilities/input-cursor-intent/example.ani +33 -0
  537. package/modules/input/capabilities/input-cursor-intent/example.animl +7 -0
  538. package/modules/input/capabilities/input-cursor-pointer/example.ani +33 -0
  539. package/modules/input/capabilities/input-cursor-pointer/example.animl +7 -0
  540. package/modules/input/capabilities/input-cursor-resize-horizontal/example.ani +33 -0
  541. package/modules/input/capabilities/input-cursor-resize-horizontal/example.animl +7 -0
  542. package/modules/input/capabilities/input-cursor-resize-vertical/example.ani +33 -0
  543. package/modules/input/capabilities/input-cursor-resize-vertical/example.animl +7 -0
  544. package/modules/input/capabilities/input-cursor-text/example.ani +33 -0
  545. package/modules/input/capabilities/input-cursor-text/example.animl +7 -0
  546. package/modules/input/capabilities/input-disable-listeners/example.ani +32 -0
  547. package/modules/input/capabilities/input-disable-listeners/example.animl +7 -0
  548. package/modules/input/capabilities/input-gamepad-axis/example.ani +34 -0
  549. package/modules/input/capabilities/input-gamepad-axis/example.animl +7 -0
  550. package/modules/input/capabilities/input-gamepad-button/example.ani +32 -0
  551. package/modules/input/capabilities/input-gamepad-button/example.animl +7 -0
  552. package/modules/input/capabilities/input-gamepad-button-phases/example.ani +33 -0
  553. package/modules/input/capabilities/input-gamepad-button-phases/example.animl +7 -0
  554. package/modules/input/capabilities/input-gamepad-connected/example.ani +31 -0
  555. package/modules/input/capabilities/input-gamepad-connected/example.animl +7 -0
  556. package/modules/input/capabilities/input-gamepad-disconnected/example.ani +31 -0
  557. package/modules/input/capabilities/input-gamepad-disconnected/example.animl +7 -0
  558. package/modules/input/capabilities/input-gamepad-host/example.ani +34 -0
  559. package/modules/input/capabilities/input-gamepad-host/example.animl +7 -0
  560. package/modules/input/capabilities/input-gamepad-index-mapping/example.ani +34 -0
  561. package/modules/input/capabilities/input-gamepad-index-mapping/example.animl +7 -0
  562. package/modules/input/capabilities/input-hit-occlusion-query/example.ani +42 -0
  563. package/modules/input/capabilities/input-hit-occlusion-query/example.animl +8 -0
  564. package/modules/input/capabilities/input-hit-parent-chain/example.ani +39 -0
  565. package/modules/input/capabilities/input-hit-parent-chain/example.animl +9 -0
  566. package/modules/input/capabilities/input-hit-shape-geometry/example.ani +32 -0
  567. package/modules/input/capabilities/input-hit-shape-geometry/example.animl +7 -0
  568. package/modules/input/capabilities/input-hit-test-host/example.ani +32 -0
  569. package/modules/input/capabilities/input-hit-test-host/example.animl +7 -0
  570. package/modules/input/capabilities/input-key-down/example.ani +32 -0
  571. package/modules/input/capabilities/input-key-down/example.animl +7 -0
  572. package/modules/input/capabilities/input-key-filter/example.ani +32 -0
  573. package/modules/input/capabilities/input-key-filter/example.animl +7 -0
  574. package/modules/input/capabilities/input-key-modifiers/example.ani +33 -0
  575. package/modules/input/capabilities/input-key-modifiers/example.animl +7 -0
  576. package/modules/input/capabilities/input-key-repeat/example.ani +32 -0
  577. package/modules/input/capabilities/input-key-repeat/example.animl +7 -0
  578. package/modules/input/capabilities/input-key-up/example.ani +32 -0
  579. package/modules/input/capabilities/input-key-up/example.animl +7 -0
  580. package/modules/input/capabilities/input-keyboard-bubbling/example.ani +45 -0
  581. package/modules/input/capabilities/input-keyboard-bubbling/example.animl +10 -0
  582. package/modules/input/capabilities/input-multi-touch/example.ani +32 -0
  583. package/modules/input/capabilities/input-multi-touch/example.animl +7 -0
  584. package/modules/input/capabilities/input-multi-trigger-listener/example.ani +32 -0
  585. package/modules/input/capabilities/input-multi-trigger-listener/example.animl +7 -0
  586. package/modules/input/capabilities/input-opaque-targets/example.ani +42 -0
  587. package/modules/input/capabilities/input-opaque-targets/example.animl +8 -0
  588. package/modules/input/capabilities/input-pointer-cancel/example.ani +31 -0
  589. package/modules/input/capabilities/input-pointer-cancel/example.animl +7 -0
  590. package/modules/input/capabilities/input-pointer-drag/example.ani +58 -0
  591. package/modules/input/capabilities/input-pointer-drag/example.animl +17 -0
  592. package/modules/input/capabilities/input-pointer-drag-end/example.ani +58 -0
  593. package/modules/input/capabilities/input-pointer-drag-end/example.animl +17 -0
  594. package/modules/input/capabilities/input-pointer-drag-start/example.ani +58 -0
  595. package/modules/input/capabilities/input-pointer-drag-start/example.animl +17 -0
  596. package/modules/input/capabilities/input-pointer-enable/example.ani +32 -0
  597. package/modules/input/capabilities/input-pointer-enable/example.animl +7 -0
  598. package/modules/input/capabilities/input-touch-page-scroll/example.ani +32 -0
  599. package/modules/input/capabilities/input-touch-page-scroll/example.animl +7 -0
  600. package/modules/input/capabilities/input-trackpad-scroll/example.ani +32 -0
  601. package/modules/input/capabilities/input-trackpad-scroll/example.animl +7 -0
  602. package/modules/input/capabilities/input-wheel-scroll/example.ani +32 -0
  603. package/modules/input/capabilities/input-wheel-scroll/example.animl +7 -0
  604. package/modules/input/capabilities/input-write-key-pressed/example.ani +39 -0
  605. package/modules/input/capabilities/input-write-key-pressed/example.animl +7 -0
  606. package/modules/input/capabilities/input-write-pointer-delta-x/example.ani +39 -0
  607. package/modules/input/capabilities/input-write-pointer-delta-x/example.animl +7 -0
  608. package/modules/input/capabilities/input-write-pointer-delta-y/example.ani +39 -0
  609. package/modules/input/capabilities/input-write-pointer-delta-y/example.animl +7 -0
  610. package/modules/input/capabilities/input-write-pointer-x/example.ani +39 -0
  611. package/modules/input/capabilities/input-write-pointer-x/example.animl +7 -0
  612. package/modules/input/capabilities/input-write-pointer-y/example.ani +39 -0
  613. package/modules/input/capabilities/input-write-pointer-y/example.animl +7 -0
  614. package/modules/input/capabilities/transforms-collapse/example.ani +39 -0
  615. package/modules/input/capabilities/transforms-collapse/example.animl +9 -0
  616. package/modules/input/capabilities/transforms-solo/example.ani +48 -0
  617. package/modules/input/capabilities/transforms-solo/example.animl +10 -0
  618. package/modules/input/core/commands.source +64 -0
  619. package/modules/input/core/types.source +97 -0
  620. package/modules/input/examples/README.md +9 -0
  621. package/modules/input/examples/drag-slider.ani +50 -0
  622. package/modules/input/examples/drag-slider.animl +11 -0
  623. package/modules/input/manifest.source +98 -0
  624. package/modules/layout/SPEC.md +210 -0
  625. package/modules/layout/capabilities/data-bind-layout/example.ani +60 -0
  626. package/modules/layout/capabilities/data-bind-layout/example.animl +10 -0
  627. package/modules/layout/capabilities/layout-alignment/example.ani +44 -0
  628. package/modules/layout/capabilities/layout-alignment/example.animl +6 -0
  629. package/modules/layout/capabilities/layout-animated-reflow/example.ani +62 -0
  630. package/modules/layout/capabilities/layout-animated-reflow/example.animl +12 -0
  631. package/modules/layout/capabilities/layout-containers/example.ani +62 -0
  632. package/modules/layout/capabilities/layout-containers/example.animl +9 -0
  633. package/modules/layout/capabilities/layout-fill-sizing/example.ani +45 -0
  634. package/modules/layout/capabilities/layout-fill-sizing/example.animl +6 -0
  635. package/modules/layout/capabilities/layout-fixed-sizing/example.ani +46 -0
  636. package/modules/layout/capabilities/layout-fixed-sizing/example.animl +6 -0
  637. package/modules/layout/capabilities/layout-flex-align-items/example.ani +43 -0
  638. package/modules/layout/capabilities/layout-flex-align-items/example.animl +6 -0
  639. package/modules/layout/capabilities/layout-flex-direction/example.ani +43 -0
  640. package/modules/layout/capabilities/layout-flex-direction/example.animl +6 -0
  641. package/modules/layout/capabilities/layout-flex-justify/example.ani +42 -0
  642. package/modules/layout/capabilities/layout-flex-justify/example.animl +6 -0
  643. package/modules/layout/capabilities/layout-hug-sizing/example.ani +42 -0
  644. package/modules/layout/capabilities/layout-hug-sizing/example.animl +6 -0
  645. package/modules/layout/capabilities/layout-intrinsic/example.ani +40 -0
  646. package/modules/layout/capabilities/layout-intrinsic/example.animl +6 -0
  647. package/modules/layout/capabilities/layout-paint/example.ani +60 -0
  648. package/modules/layout/capabilities/layout-paint/example.animl +8 -0
  649. package/modules/layout/capabilities/layout-participants/example.ani +40 -0
  650. package/modules/layout/capabilities/layout-participants/example.animl +6 -0
  651. package/modules/layout/capabilities/layout-self-alignment/example.ani +45 -0
  652. package/modules/layout/capabilities/layout-self-alignment/example.animl +6 -0
  653. package/modules/layout/capabilities/layout-size-bounds/example.ani +49 -0
  654. package/modules/layout/capabilities/layout-size-bounds/example.animl +6 -0
  655. package/modules/layout/capabilities/layout-spacing/example.ani +45 -0
  656. package/modules/layout/capabilities/layout-spacing/example.animl +6 -0
  657. package/modules/layout/capabilities/layout-visibility/example.ani +56 -0
  658. package/modules/layout/capabilities/layout-visibility/example.animl +7 -0
  659. package/modules/layout/examples/button.ani +61 -0
  660. package/modules/layout/examples/button.animl +12 -0
  661. package/modules/layout/examples/reflow.ani +62 -0
  662. package/modules/layout/examples/reflow.animl +12 -0
  663. package/modules/layout/examples/wrapped.ani +55 -0
  664. package/modules/layout/examples/wrapped.animl +11 -0
  665. package/modules/layout/manifest.source +73 -0
  666. package/modules/lists/SPEC.md +44 -0
  667. package/modules/lists/capabilities/components-explicit-context/example.ani +81 -0
  668. package/modules/lists/capabilities/components-explicit-context/example.animl +22 -0
  669. package/modules/lists/capabilities/components-inherited-context/example.ani +99 -0
  670. package/modules/lists/capabilities/components-inherited-context/example.animl +24 -0
  671. package/modules/lists/capabilities/components-list/example.ani +77 -0
  672. package/modules/lists/capabilities/components-list/example.animl +19 -0
  673. package/modules/lists/capabilities/components-list-map/example.ani +116 -0
  674. package/modules/lists/capabilities/components-list-map/example.animl +29 -0
  675. package/modules/lists/capabilities/components-list-size-override/example.ani +83 -0
  676. package/modules/lists/capabilities/components-list-size-override/example.animl +20 -0
  677. package/modules/lists/capabilities/data-binding-relative/example.ani +77 -0
  678. package/modules/lists/capabilities/data-binding-relative/example.animl +19 -0
  679. package/modules/lists/capabilities/data-converter-number-to-list/example.ani +109 -0
  680. package/modules/lists/capabilities/data-converter-number-to-list/example.animl +29 -0
  681. package/modules/lists/capabilities/data-property-symbol-list-index/example.ani +77 -0
  682. package/modules/lists/capabilities/data-property-symbol-list-index/example.animl +19 -0
  683. package/modules/lists/examples/players.ani +77 -0
  684. package/modules/lists/examples/players.animl +19 -0
  685. package/modules/lists/manifest.source +43 -0
  686. package/modules/machine/capabilities/state-machine-any-state/example.ani +62 -0
  687. package/modules/machine/capabilities/state-machine-any-state/example.animl +22 -0
  688. package/modules/machine/capabilities/state-machine-blend-1d-data/example.ani +74 -0
  689. package/modules/machine/capabilities/state-machine-blend-1d-data/example.animl +23 -0
  690. package/modules/machine/capabilities/state-machine-blend-1d-input/example.ani +70 -0
  691. package/modules/machine/capabilities/state-machine-blend-1d-input/example.animl +23 -0
  692. package/modules/machine/capabilities/state-machine-blend-additive/example.ani +69 -0
  693. package/modules/machine/capabilities/state-machine-blend-additive/example.animl +23 -0
  694. package/modules/machine/capabilities/state-machine-blend-direct/example.ani +64 -0
  695. package/modules/machine/capabilities/state-machine-blend-direct/example.animl +21 -0
  696. package/modules/machine/capabilities/state-machine-blend-exit-clock/example.ani +72 -0
  697. package/modules/machine/capabilities/state-machine-blend-exit-clock/example.animl +25 -0
  698. package/modules/machine/capabilities/state-machine-blend-weight-constant/example.ani +64 -0
  699. package/modules/machine/capabilities/state-machine-blend-weight-constant/example.animl +21 -0
  700. package/modules/machine/capabilities/state-machine-blend-weight-data/example.ani +71 -0
  701. package/modules/machine/capabilities/state-machine-blend-weight-data/example.animl +23 -0
  702. package/modules/machine/capabilities/state-machine-blend-weight-input/example.ani +69 -0
  703. package/modules/machine/capabilities/state-machine-blend-weight-input/example.animl +23 -0
  704. package/modules/machine/capabilities/state-machine-condition-artboard/example.ani +50 -0
  705. package/modules/machine/capabilities/state-machine-condition-artboard/example.animl +13 -0
  706. package/modules/machine/capabilities/state-machine-condition-component/example.ani +76 -0
  707. package/modules/machine/capabilities/state-machine-condition-component/example.animl +19 -0
  708. package/modules/machine/capabilities/state-machine-condition-data/example.ani +54 -0
  709. package/modules/machine/capabilities/state-machine-condition-data/example.animl +15 -0
  710. package/modules/machine/capabilities/state-machine-condition-focus/example.ani +49 -0
  711. package/modules/machine/capabilities/state-machine-condition-focus/example.animl +13 -0
  712. package/modules/machine/capabilities/state-machine-continue-on-exit/example.ani +58 -0
  713. package/modules/machine/capabilities/state-machine-continue-on-exit/example.animl +21 -0
  714. package/modules/machine/capabilities/state-machine-entry-state/example.ani +56 -0
  715. package/modules/machine/capabilities/state-machine-entry-state/example.animl +19 -0
  716. package/modules/machine/capabilities/state-machine-exit-state/example.ani +46 -0
  717. package/modules/machine/capabilities/state-machine-exit-state/example.animl +15 -0
  718. package/modules/machine/capabilities/state-machine-exit-time-relative/example.ani +52 -0
  719. package/modules/machine/capabilities/state-machine-exit-time-relative/example.animl +19 -0
  720. package/modules/machine/capabilities/state-machine-literal-asset/example.ani +62 -0
  721. package/modules/machine/capabilities/state-machine-literal-asset/example.animl +18 -0
  722. package/modules/machine/capabilities/state-machine-literal-boolean/example.ani +53 -0
  723. package/modules/machine/capabilities/state-machine-literal-boolean/example.animl +15 -0
  724. package/modules/machine/capabilities/state-machine-literal-color/example.ani +54 -0
  725. package/modules/machine/capabilities/state-machine-literal-color/example.animl +15 -0
  726. package/modules/machine/capabilities/state-machine-literal-enum/example.ani +54 -0
  727. package/modules/machine/capabilities/state-machine-literal-enum/example.animl +15 -0
  728. package/modules/machine/capabilities/state-machine-literal-string/example.ani +54 -0
  729. package/modules/machine/capabilities/state-machine-literal-string/example.animl +15 -0
  730. package/modules/machine/capabilities/state-machine-literal-trigger/example.ani +53 -0
  731. package/modules/machine/capabilities/state-machine-literal-trigger/example.animl +15 -0
  732. package/modules/machine/capabilities/state-machine-state-entry-actions/example.ani +56 -0
  733. package/modules/machine/capabilities/state-machine-state-entry-actions/example.animl +16 -0
  734. package/modules/machine/capabilities/state-machine-state-exit-actions/example.ani +56 -0
  735. package/modules/machine/capabilities/state-machine-state-exit-actions/example.animl +16 -0
  736. package/modules/machine/capabilities/state-machine-state-random/example.ani +53 -0
  737. package/modules/machine/capabilities/state-machine-state-random/example.animl +17 -0
  738. package/modules/machine/capabilities/state-machine-state-reset/example.ani +52 -0
  739. package/modules/machine/capabilities/state-machine-state-reset/example.animl +18 -0
  740. package/modules/machine/capabilities/state-machine-transition-actions/example.ani +56 -0
  741. package/modules/machine/capabilities/state-machine-transition-actions/example.animl +16 -0
  742. package/modules/machine/capabilities/state-machine-transition-disabled/example.ani +57 -0
  743. package/modules/machine/capabilities/state-machine-transition-disabled/example.animl +19 -0
  744. package/modules/machine/capabilities/state-machine-transition-duration-relative/example.ani +57 -0
  745. package/modules/machine/capabilities/state-machine-transition-duration-relative/example.animl +21 -0
  746. package/modules/machine/capabilities/state-machine-transition-end-actions/example.ani +56 -0
  747. package/modules/machine/capabilities/state-machine-transition-end-actions/example.animl +16 -0
  748. package/modules/machine/capabilities/state-machine-transition-random-weight/example.ani +53 -0
  749. package/modules/machine/capabilities/state-machine-transition-random-weight/example.animl +17 -0
  750. package/modules/machine/examples/README.md +12 -0
  751. package/modules/machine/examples/blend-pulse.ani +115 -0
  752. package/modules/machine/examples/blend-pulse.animl +32 -0
  753. package/modules/machine/manifest.source +73 -0
  754. package/modules/markup/manifest.source +26 -0
  755. package/modules/markup-animation/manifest.source +23 -0
  756. package/modules/markup-hooks/manifest.source +13 -0
  757. package/modules/markup-libraries/SPEC.md +56 -0
  758. package/modules/markup-libraries/capabilities/components-library-export/example.ani +45 -0
  759. package/modules/markup-libraries/capabilities/components-library-export/example.animl +4 -0
  760. package/modules/markup-libraries/capabilities/components-library-models/example.ani +45 -0
  761. package/modules/markup-libraries/capabilities/components-library-models/example.animl +4 -0
  762. package/modules/markup-libraries/capabilities/components-library-update/example.ani +45 -0
  763. package/modules/markup-libraries/capabilities/components-library-update/example.animl +4 -0
  764. package/modules/markup-libraries/manifest.source +38 -0
  765. package/modules/media/manifest.source +21 -0
  766. package/modules/mesh/capabilities/rigging-mesh/example.ani +50 -0
  767. package/modules/mesh/capabilities/rigging-mesh/example.animl +9 -0
  768. package/modules/mesh/capabilities/rigging-mesh-animation/example.ani +65 -0
  769. package/modules/mesh/capabilities/rigging-mesh-animation/example.animl +15 -0
  770. package/modules/mesh/examples/flag.ani +108 -0
  771. package/modules/mesh/examples/flag.animl +23 -0
  772. package/modules/mesh/manifest.source +37 -0
  773. package/modules/mix/capabilities/drawing-paint-additive-strength/example.ani +34 -0
  774. package/modules/mix/capabilities/drawing-paint-additive-strength/example.animl +4 -0
  775. package/modules/mix/capabilities/drawing-paint-blend-additive/example.ani +33 -0
  776. package/modules/mix/capabilities/drawing-paint-blend-additive/example.animl +4 -0
  777. package/modules/mix/capabilities/drawing-paint-paint-blend-override/example.ani +37 -0
  778. package/modules/mix/capabilities/drawing-paint-paint-blend-override/example.animl +4 -0
  779. package/modules/mix/capabilities/host-api-factory/example.ani +16 -0
  780. package/modules/mix/capabilities/host-api-factory/example.animl +3 -0
  781. package/modules/mix/capabilities/host-api-renderer/example.ani +16 -0
  782. package/modules/mix/capabilities/host-api-renderer/example.animl +3 -0
  783. package/modules/mix/manifest.source +36 -0
  784. package/modules/motion/capabilities/animation-cubic-value/example.ani +40 -0
  785. package/modules/motion/capabilities/animation-cubic-value/example.animl +9 -0
  786. package/modules/motion/capabilities/animation-elastic-in/example.ani +40 -0
  787. package/modules/motion/capabilities/animation-elastic-in/example.animl +9 -0
  788. package/modules/motion/capabilities/animation-elastic-in-out/example.ani +40 -0
  789. package/modules/motion/capabilities/animation-elastic-in-out/example.animl +9 -0
  790. package/modules/motion/capabilities/animation-host-loop-override/example.ani +28 -0
  791. package/modules/motion/capabilities/animation-host-loop-override/example.animl +6 -0
  792. package/modules/motion/capabilities/animation-host-play-pause-stop/example.ani +28 -0
  793. package/modules/motion/capabilities/animation-host-play-pause-stop/example.animl +6 -0
  794. package/modules/motion/capabilities/animation-keys-reference/example.ani +55 -0
  795. package/modules/motion/capabilities/animation-keys-reference/example.animl +12 -0
  796. package/modules/motion/capabilities/animation-keys-signed-integer/example.ani +46 -0
  797. package/modules/motion/capabilities/animation-keys-signed-integer/example.animl +11 -0
  798. package/modules/motion/capabilities/animation-keys-string/example.ani +46 -0
  799. package/modules/motion/capabilities/animation-keys-string/example.animl +11 -0
  800. package/modules/motion/capabilities/animation-keys-unsigned-integer/example.ani +46 -0
  801. package/modules/motion/capabilities/animation-keys-unsigned-integer/example.animl +11 -0
  802. package/modules/motion/capabilities/animation-mix-discrete/example.ani +38 -0
  803. package/modules/motion/capabilities/animation-mix-discrete/example.animl +9 -0
  804. package/modules/motion/capabilities/animation-mix-numeric/example.ani +42 -0
  805. package/modules/motion/capabilities/animation-mix-numeric/example.animl +11 -0
  806. package/modules/motion/capabilities/animation-quantized-playback/example.ani +41 -0
  807. package/modules/motion/capabilities/animation-quantized-playback/example.animl +9 -0
  808. package/modules/motion/capabilities/animation-reverse/example.ani +47 -0
  809. package/modules/motion/capabilities/animation-reverse/example.animl +12 -0
  810. package/modules/motion/capabilities/animation-seek/example.ani +28 -0
  811. package/modules/motion/capabilities/animation-seek/example.animl +6 -0
  812. package/modules/motion/capabilities/animation-work-area/example.ani +42 -0
  813. package/modules/motion/capabilities/animation-work-area/example.animl +9 -0
  814. package/modules/motion/capabilities/data-bind-keyframes/example.ani +52 -0
  815. package/modules/motion/capabilities/data-bind-keyframes/example.animl +15 -0
  816. package/modules/motion/capabilities/host-api-animation-metadata/example.ani +31 -0
  817. package/modules/motion/capabilities/host-api-animation-metadata/example.animl +6 -0
  818. package/modules/motion/capabilities/host-api-low-level-animation/example.ani +28 -0
  819. package/modules/motion/capabilities/host-api-low-level-animation/example.animl +6 -0
  820. package/modules/motion/capabilities/host-api-low-level-scene-query/example.ani +41 -0
  821. package/modules/motion/capabilities/host-api-low-level-scene-query/example.animl +9 -0
  822. package/modules/motion/capabilities/host-api-multi-state-machine/example.ani +62 -0
  823. package/modules/motion/capabilities/host-api-multi-state-machine/example.animl +22 -0
  824. package/modules/motion/capabilities/host-api-scrub/example.ani +29 -0
  825. package/modules/motion/capabilities/host-api-scrub/example.animl +6 -0
  826. package/modules/motion/capabilities/state-machine-exit-time-seconds/example.ani +60 -0
  827. package/modules/motion/capabilities/state-machine-exit-time-seconds/example.animl +15 -0
  828. package/modules/motion/capabilities/transforms-origin-animation/example.ani +45 -0
  829. package/modules/motion/capabilities/transforms-origin-animation/example.animl +10 -0
  830. package/modules/motion/examples/README.md +42 -0
  831. package/modules/motion/examples/data-keys.ani +52 -0
  832. package/modules/motion/examples/data-keys.animl +15 -0
  833. package/modules/motion/examples/evidence/hover.jsonl +8 -0
  834. package/modules/motion/examples/evidence/phase.jsonl +9 -0
  835. package/modules/motion/examples/evidence/reduced.jsonl +6 -0
  836. package/modules/motion/examples/evidence/rest.jsonl +1 -0
  837. package/modules/motion/examples/evidence/spin.jsonl +6 -0
  838. package/modules/motion/examples/evidence/stop.jsonl +10 -0
  839. package/modules/motion/examples/evidence/sweep.jsonl +26 -0
  840. package/modules/motion/examples/scripts/hover.txt +21 -0
  841. package/modules/motion/examples/scripts/phase.txt +28 -0
  842. package/modules/motion/examples/scripts/reduced.txt +16 -0
  843. package/modules/motion/examples/scripts/rest.txt +2 -0
  844. package/modules/motion/examples/scripts/spin.txt +14 -0
  845. package/modules/motion/examples/scripts/stop.txt +26 -0
  846. package/modules/motion/examples/scripts/sweep.txt +71 -0
  847. package/modules/motion/examples/sync.ani +272 -0
  848. package/modules/motion/examples/sync.animl +64 -0
  849. package/modules/motion/examples/verify.py +71 -0
  850. package/modules/motion/manifest.source +67 -0
  851. package/modules/nested/SPEC.md +79 -0
  852. package/modules/nested/capabilities/components-leaf-fit/example.ani +50 -0
  853. package/modules/nested/capabilities/components-leaf-fit/example.animl +9 -0
  854. package/modules/nested/capabilities/components-nested-quantize/example.ani +65 -0
  855. package/modules/nested/capabilities/components-nested-quantize/example.animl +15 -0
  856. package/modules/nested/capabilities/components-nested-remap/example.ani +65 -0
  857. package/modules/nested/capabilities/components-nested-remap/example.animl +15 -0
  858. package/modules/nested/capabilities/components-stateful-output/example.ani +51 -0
  859. package/modules/nested/capabilities/components-stateful-output/example.animl +12 -0
  860. package/modules/nested/capabilities/data-property-artboard/example.ani +63 -0
  861. package/modules/nested/capabilities/data-property-artboard/example.animl +14 -0
  862. package/modules/nested/capabilities/state-machine-literal-artboard/example.ani +76 -0
  863. package/modules/nested/capabilities/state-machine-literal-artboard/example.animl +19 -0
  864. package/modules/nested/examples/component-guard.ani +76 -0
  865. package/modules/nested/examples/component-guard.animl +19 -0
  866. package/modules/nested/examples/controls.ani +100 -0
  867. package/modules/nested/examples/controls.animl +21 -0
  868. package/modules/nested/manifest.source +39 -0
  869. package/modules/paint/capabilities/drawing-paint-gradient-angular/example.ani +33 -0
  870. package/modules/paint/capabilities/drawing-paint-gradient-angular/example.animl +8 -0
  871. package/modules/paint/capabilities/drawing-paint-gradient-transform/example.ani +34 -0
  872. package/modules/paint/capabilities/drawing-paint-gradient-transform/example.animl +8 -0
  873. package/modules/paint/capabilities/drawing-paint-image-fit-resize/example.ani +49 -0
  874. package/modules/paint/capabilities/drawing-paint-image-fit-resize/example.animl +12 -0
  875. package/modules/paint/capabilities/drawing-paint-image-wrap-clamp/example.ani +50 -0
  876. package/modules/paint/capabilities/drawing-paint-image-wrap-clamp/example.animl +12 -0
  877. package/modules/paint/capabilities/drawing-paint-image-wrap-mirror/example.ani +50 -0
  878. package/modules/paint/capabilities/drawing-paint-image-wrap-mirror/example.animl +12 -0
  879. package/modules/paint/capabilities/drawing-paint-image-wrap-repeat/example.ani +50 -0
  880. package/modules/paint/capabilities/drawing-paint-image-wrap-repeat/example.animl +12 -0
  881. package/modules/paint/manifest.source +77 -0
  882. package/modules/pathfollow/capabilities/rigging-follow-path/example.ani +16 -0
  883. package/modules/pathfollow/capabilities/rigging-follow-path/example.animl +7 -0
  884. package/modules/pathfollow/capabilities/rigging-list-follow-path/example.ani +16 -0
  885. package/modules/pathfollow/capabilities/rigging-list-follow-path/example.animl +9 -0
  886. package/modules/pathfollow/examples/orbit.ani +130 -0
  887. package/modules/pathfollow/examples/orbit.animl +17 -0
  888. package/modules/pathfollow/manifest.source +35 -0
  889. package/modules/posepad/examples/look.ani +124 -0
  890. package/modules/posepad/examples/look.animl +22 -0
  891. package/modules/posepad/manifest.source +43 -0
  892. package/modules/rig/AUTHORING.md +24 -0
  893. package/modules/rig/capabilities/rigging-bone-chains/example.ani +61 -0
  894. package/modules/rig/capabilities/rigging-bone-chains/example.animl +22 -0
  895. package/modules/rig/capabilities/rigging-constraint-order/example.ani +123 -0
  896. package/modules/rig/capabilities/rigging-constraint-order/example.animl +33 -0
  897. package/modules/rig/capabilities/rigging-constraint-strength/example.ani +105 -0
  898. package/modules/rig/capabilities/rigging-constraint-strength/example.animl +28 -0
  899. package/modules/rig/capabilities/rigging-distance-closer/example.ani +160 -0
  900. package/modules/rig/capabilities/rigging-distance-closer/example.animl +44 -0
  901. package/modules/rig/capabilities/rigging-distance-exact/example.ani +160 -0
  902. package/modules/rig/capabilities/rigging-distance-exact/example.animl +44 -0
  903. package/modules/rig/capabilities/rigging-distance-further/example.ani +160 -0
  904. package/modules/rig/capabilities/rigging-distance-further/example.animl +44 -0
  905. package/modules/rig/capabilities/rigging-root-bones/example.ani +63 -0
  906. package/modules/rig/capabilities/rigging-root-bones/example.animl +22 -0
  907. package/modules/rig/capabilities/rigging-rotation/example.ani +143 -0
  908. package/modules/rig/capabilities/rigging-rotation/example.animl +32 -0
  909. package/modules/rig/capabilities/rigging-scale/example.ani +144 -0
  910. package/modules/rig/capabilities/rigging-scale/example.animl +32 -0
  911. package/modules/rig/capabilities/rigging-transform/example.ani +132 -0
  912. package/modules/rig/capabilities/rigging-transform/example.animl +30 -0
  913. package/modules/rig/capabilities/rigging-translation/example.ani +248 -0
  914. package/modules/rig/capabilities/rigging-translation/example.animl +54 -0
  915. package/modules/rig/examples/arm.ani +120 -0
  916. package/modules/rig/examples/arm.animl +29 -0
  917. package/modules/rig/manifest.source +55 -0
  918. package/modules/scroll/capabilities/rigging-scroll/example.ani +16 -0
  919. package/modules/scroll/capabilities/rigging-scroll/example.animl +11 -0
  920. package/modules/scroll/examples/list.ani +115 -0
  921. package/modules/scroll/examples/list.animl +15 -0
  922. package/modules/scroll/manifest.source +31 -0
  923. package/modules/skin/capabilities/rigging-cubic-weights/example.ani +66 -0
  924. package/modules/skin/capabilities/rigging-cubic-weights/example.animl +17 -0
  925. package/modules/skin/capabilities/rigging-skin/example.ani +165 -0
  926. package/modules/skin/capabilities/rigging-skin/example.animl +33 -0
  927. package/modules/skin/capabilities/rigging-weights/example.ani +147 -0
  928. package/modules/skin/capabilities/rigging-weights/example.animl +37 -0
  929. package/modules/skin/examples/sleeve.ani +91 -0
  930. package/modules/skin/examples/sleeve.animl +14 -0
  931. package/modules/skin/manifest.source +41 -0
  932. package/modules/surface/manifest.source +52 -0
  933. package/modules/text/SPEC.md +269 -0
  934. package/modules/text/capabilities/data-bind-text/example.ani +63 -0
  935. package/modules/text/capabilities/data-bind-text/example.animl +8 -0
  936. package/modules/text/capabilities/data-property-text-style/example.ani +63 -0
  937. package/modules/text/capabilities/data-property-text-style/example.animl +8 -0
  938. package/modules/text/capabilities/host-api-nested-text-run/example.ani +44 -0
  939. package/modules/text/capabilities/host-api-nested-text-run/example.animl +10 -0
  940. package/modules/text/capabilities/host-api-text-run/example.ani +27 -0
  941. package/modules/text/capabilities/host-api-text-run/example.animl +6 -0
  942. package/modules/text/capabilities/text-fit-baseline/example.ani +30 -0
  943. package/modules/text/capabilities/text-fit-baseline/example.animl +6 -0
  944. package/modules/text/capabilities/text-fit-layout-measure/example.ani +30 -0
  945. package/modules/text/capabilities/text-fit-layout-measure/example.animl +6 -0
  946. package/modules/text/capabilities/text-flatten/example.ani +16 -0
  947. package/modules/text/capabilities/text-flatten/example.animl +6 -0
  948. package/modules/text/capabilities/text-font-size/example.ani +59 -0
  949. package/modules/text/capabilities/text-font-size/example.animl +15 -0
  950. package/modules/text/capabilities/text-horizontal-alignment/example.ani +28 -0
  951. package/modules/text/capabilities/text-horizontal-alignment/example.animl +6 -0
  952. package/modules/text/capabilities/text-line-spacing/example.ani +28 -0
  953. package/modules/text/capabilities/text-line-spacing/example.animl +6 -0
  954. package/modules/text/capabilities/text-origin/example.ani +32 -0
  955. package/modules/text/capabilities/text-origin/example.animl +6 -0
  956. package/modules/text/capabilities/text-overflow-clipped/example.ani +29 -0
  957. package/modules/text/capabilities/text-overflow-clipped/example.animl +6 -0
  958. package/modules/text/capabilities/text-overflow-ellipsis/example.ani +29 -0
  959. package/modules/text/capabilities/text-overflow-ellipsis/example.animl +6 -0
  960. package/modules/text/capabilities/text-overflow-fit/example.ani +29 -0
  961. package/modules/text/capabilities/text-overflow-fit/example.animl +6 -0
  962. package/modules/text/capabilities/text-overflow-fit-font-size/example.ani +29 -0
  963. package/modules/text/capabilities/text-overflow-fit-font-size/example.animl +6 -0
  964. package/modules/text/capabilities/text-overflow-hidden/example.ani +29 -0
  965. package/modules/text/capabilities/text-overflow-hidden/example.animl +7 -0
  966. package/modules/text/capabilities/text-overflow-visible/example.ani +29 -0
  967. package/modules/text/capabilities/text-overflow-visible/example.animl +6 -0
  968. package/modules/text/capabilities/text-paint/example.ani +30 -0
  969. package/modules/text/capabilities/text-paint/example.animl +6 -0
  970. package/modules/text/capabilities/text-paragraph-spacing/example.ani +28 -0
  971. package/modules/text/capabilities/text-paragraph-spacing/example.animl +7 -0
  972. package/modules/text/capabilities/text-rich-runs/example.ani +34 -0
  973. package/modules/text/capabilities/text-rich-runs/example.animl +8 -0
  974. package/modules/text/capabilities/text-run-list/example.ani +59 -0
  975. package/modules/text/capabilities/text-run-list/example.animl +7 -0
  976. package/modules/text/capabilities/text-script-hit-testing/example.ani +47 -0
  977. package/modules/text/capabilities/text-script-hit-testing/example.animl +10 -0
  978. package/modules/text/capabilities/text-sizing-auto-height/example.ani +28 -0
  979. package/modules/text/capabilities/text-sizing-auto-height/example.animl +6 -0
  980. package/modules/text/capabilities/text-sizing-auto-width/example.ani +27 -0
  981. package/modules/text/capabilities/text-sizing-auto-width/example.animl +6 -0
  982. package/modules/text/capabilities/text-sizing-fixed/example.ani +26 -0
  983. package/modules/text/capabilities/text-sizing-fixed/example.animl +6 -0
  984. package/modules/text/capabilities/text-style-reference/example.ani +31 -0
  985. package/modules/text/capabilities/text-style-reference/example.animl +7 -0
  986. package/modules/text/capabilities/text-tracking/example.ani +28 -0
  987. package/modules/text/capabilities/text-tracking/example.animl +6 -0
  988. package/modules/text/capabilities/text-vertical-alignment/example.ani +28 -0
  989. package/modules/text/capabilities/text-vertical-alignment/example.animl +6 -0
  990. package/modules/text/capabilities/text-vertical-trim/example.ani +29 -0
  991. package/modules/text/capabilities/text-vertical-trim/example.animl +6 -0
  992. package/modules/text/capabilities/text-word-break/example.ani +28 -0
  993. package/modules/text/capabilities/text-word-break/example.animl +6 -0
  994. package/modules/text/capabilities/text-wrap/example.ani +28 -0
  995. package/modules/text/capabilities/text-wrap/example.animl +6 -0
  996. package/modules/text/manifest.source +92 -0
  997. package/modules/text-effects/SPEC.md +134 -0
  998. package/modules/text-effects/capabilities/text-background/example.ani +32 -0
  999. package/modules/text-effects/capabilities/text-background/example.animl +7 -0
  1000. package/modules/text-effects/capabilities/text-modifier-follow-path/example.ani +34 -0
  1001. package/modules/text-effects/capabilities/text-modifier-follow-path/example.animl +8 -0
  1002. package/modules/text-effects/capabilities/text-modifier-opacity/example.ani +34 -0
  1003. package/modules/text-effects/capabilities/text-modifier-opacity/example.animl +8 -0
  1004. package/modules/text-effects/capabilities/text-modifier-origin/example.ani +36 -0
  1005. package/modules/text-effects/capabilities/text-modifier-origin/example.animl +8 -0
  1006. package/modules/text-effects/capabilities/text-modifier-rotation/example.ani +34 -0
  1007. package/modules/text-effects/capabilities/text-modifier-rotation/example.animl +8 -0
  1008. package/modules/text-effects/capabilities/text-modifier-scale/example.ani +35 -0
  1009. package/modules/text-effects/capabilities/text-modifier-scale/example.animl +8 -0
  1010. package/modules/text-effects/capabilities/text-modifier-translation/example.ani +35 -0
  1011. package/modules/text-effects/capabilities/text-modifier-translation/example.animl +8 -0
  1012. package/modules/text-effects/capabilities/text-modifier-variation/example.ani +36 -0
  1013. package/modules/text-effects/capabilities/text-modifier-variation/example.animl +8 -0
  1014. package/modules/text-effects/capabilities/text-range-combine/example.ani +47 -0
  1015. package/modules/text-effects/capabilities/text-range-combine/example.animl +11 -0
  1016. package/modules/text-effects/capabilities/text-range-easing/example.ani +38 -0
  1017. package/modules/text-effects/capabilities/text-range-easing/example.animl +8 -0
  1018. package/modules/text-effects/capabilities/text-range-falloff/example.ani +37 -0
  1019. package/modules/text-effects/capabilities/text-range-falloff/example.animl +8 -0
  1020. package/modules/text-effects/capabilities/text-range-run/example.ani +37 -0
  1021. package/modules/text-effects/capabilities/text-range-run/example.animl +8 -0
  1022. package/modules/text-effects/capabilities/text-range-units/example.ani +37 -0
  1023. package/modules/text-effects/capabilities/text-range-units/example.animl +8 -0
  1024. package/modules/text-effects/capabilities/text-script-direction/example.ani +30 -0
  1025. package/modules/text-effects/capabilities/text-script-direction/example.animl +7 -0
  1026. package/modules/text-effects/examples/title-wave.ani +68 -0
  1027. package/modules/text-effects/examples/title-wave.animl +15 -0
  1028. package/modules/text-effects/manifest.source +58 -0
  1029. package/modules/text-input/SPEC.md +189 -0
  1030. package/modules/text-input/capabilities/text-input-placeholder/example.ani +16 -0
  1031. package/modules/text-input/capabilities/text-input-placeholder/example.animl +13 -0
  1032. package/modules/text-input/capabilities/text-input-selection/example.ani +66 -0
  1033. package/modules/text-input/capabilities/text-input-selection/example.animl +13 -0
  1034. package/modules/text-input/examples/search-card.ani +195 -0
  1035. package/modules/text-input/examples/search-card.animl +49 -0
  1036. package/modules/text-input/examples/search.txt +6 -0
  1037. package/modules/text-input/manifest.source +79 -0
  1038. package/modules/tooling/capabilities/tooling-benchmark/example.ani +16 -0
  1039. package/modules/tooling/capabilities/tooling-benchmark/example.animl +13 -0
  1040. package/modules/tooling/capabilities/tooling-data-breakpoints/example.ani +75 -0
  1041. package/modules/tooling/capabilities/tooling-data-breakpoints/example.animl +13 -0
  1042. package/modules/tooling/capabilities/tooling-data-debugger/example.ani +16 -0
  1043. package/modules/tooling/capabilities/tooling-data-debugger/example.animl +13 -0
  1044. package/modules/tooling/capabilities/tooling-headless-command-session/example.ani +16 -0
  1045. package/modules/tooling/capabilities/tooling-headless-command-session/example.animl +13 -0
  1046. package/modules/tooling/capabilities/tooling-headless-data/example.ani +16 -0
  1047. package/modules/tooling/capabilities/tooling-headless-data/example.animl +13 -0
  1048. package/modules/tooling/capabilities/tooling-headless-data-dump/example.ani +16 -0
  1049. package/modules/tooling/capabilities/tooling-headless-data-dump/example.animl +13 -0
  1050. package/modules/tooling/capabilities/tooling-headless-data-filter/example.ani +16 -0
  1051. package/modules/tooling/capabilities/tooling-headless-data-filter/example.animl +13 -0
  1052. package/modules/tooling/capabilities/tooling-headless-data-stream/example.ani +16 -0
  1053. package/modules/tooling/capabilities/tooling-headless-data-stream/example.animl +13 -0
  1054. package/modules/tooling/capabilities/tooling-headless-gamepad/example.ani +16 -0
  1055. package/modules/tooling/capabilities/tooling-headless-gamepad/example.animl +13 -0
  1056. package/modules/tooling/capabilities/tooling-headless-key/example.ani +16 -0
  1057. package/modules/tooling/capabilities/tooling-headless-key/example.animl +13 -0
  1058. package/modules/tooling/capabilities/tooling-headless-pointer/example.ani +16 -0
  1059. package/modules/tooling/capabilities/tooling-headless-pointer/example.animl +13 -0
  1060. package/modules/tooling/capabilities/tooling-headless-touch/example.ani +16 -0
  1061. package/modules/tooling/capabilities/tooling-headless-touch/example.animl +13 -0
  1062. package/modules/tooling/capabilities/tooling-headless-wheel/example.ani +16 -0
  1063. package/modules/tooling/capabilities/tooling-headless-wheel/example.animl +13 -0
  1064. package/modules/tooling/capabilities/tooling-seed/example.ani +76 -0
  1065. package/modules/tooling/capabilities/tooling-seed/example.animl +21 -0
  1066. package/modules/tooling/capabilities/tooling-test-assertions/example.ani +16 -0
  1067. package/modules/tooling/capabilities/tooling-test-assertions/example.animl +13 -0
  1068. package/modules/tooling/examples/agent-tools.ani +75 -0
  1069. package/modules/tooling/examples/agent-tools.animl +13 -0
  1070. package/modules/tooling/manifest.source +15 -0
  1071. package/package.json +57 -4
  1072. package/packages/cli/package.json +4 -0
  1073. package/packages/cli/src/asset-info.d.ts +8 -0
  1074. package/packages/cli/src/asset-info.js +122 -0
  1075. package/packages/cli/src/assets.d.ts +8 -0
  1076. package/packages/cli/src/assets.js +66 -0
  1077. package/packages/cli/src/audio.d.ts +3 -0
  1078. package/packages/cli/src/audio.js +77 -0
  1079. package/packages/cli/src/bench.d.ts +32 -0
  1080. package/packages/cli/src/bench.js +46 -0
  1081. package/packages/cli/src/build-assets.d.ts +10 -0
  1082. package/packages/cli/src/build-assets.js +139 -0
  1083. package/packages/cli/src/bundle.d.ts +7 -0
  1084. package/packages/cli/src/bundle.js +53 -0
  1085. package/packages/cli/src/cli.d.ts +19 -0
  1086. package/packages/cli/src/cli.js +1532 -0
  1087. package/packages/cli/src/commands.d.ts +47 -0
  1088. package/packages/cli/src/commands.js +608 -0
  1089. package/packages/cli/src/compare-png.d.ts +21 -0
  1090. package/packages/cli/src/compare-png.js +39 -0
  1091. package/packages/cli/src/compare.d.ts +7 -0
  1092. package/packages/cli/src/compare.js +84 -0
  1093. package/packages/cli/src/completion.d.ts +2 -0
  1094. package/packages/cli/src/completion.js +153 -0
  1095. package/packages/cli/src/discover/api.d.ts +55 -0
  1096. package/packages/cli/src/discover/api.js +185 -0
  1097. package/packages/cli/src/discover/build.d.ts +14 -0
  1098. package/packages/cli/src/discover/build.js +590 -0
  1099. package/packages/cli/src/discover/synonyms.d.ts +1 -0
  1100. package/packages/cli/src/discover/synonyms.js +68 -0
  1101. package/packages/cli/src/docs-view.d.ts +4 -0
  1102. package/packages/cli/src/docs-view.js +279 -0
  1103. package/packages/cli/src/doctor.d.ts +11 -0
  1104. package/packages/cli/src/doctor.js +151 -0
  1105. package/packages/cli/src/encode.d.ts +9 -0
  1106. package/packages/cli/src/encode.js +100 -0
  1107. package/packages/cli/src/font/README.md +45 -0
  1108. package/packages/cli/src/font/binary.d.ts +8 -0
  1109. package/packages/cli/src/font/binary.js +54 -0
  1110. package/packages/cli/src/font/browser.d.ts +4 -0
  1111. package/packages/cli/src/font/browser.js +54 -0
  1112. package/packages/cli/src/font/cff.d.ts +6 -0
  1113. package/packages/cli/src/font/cff.js +154 -0
  1114. package/packages/cli/src/font/container.d.ts +5 -0
  1115. package/packages/cli/src/font/container.js +97 -0
  1116. package/packages/cli/src/font/document.d.ts +28 -0
  1117. package/packages/cli/src/font/document.js +143 -0
  1118. package/packages/cli/src/font/features.d.ts +1 -0
  1119. package/packages/cli/src/font/features.js +1 -0
  1120. package/packages/cli/src/font/flatten.d.ts +5 -0
  1121. package/packages/cli/src/font/flatten.js +146 -0
  1122. package/packages/cli/src/font/import.d.ts +12 -0
  1123. package/packages/cli/src/font/import.js +92 -0
  1124. package/packages/cli/src/font/index.d.ts +8 -0
  1125. package/packages/cli/src/font/index.js +27 -0
  1126. package/packages/cli/src/font/measure.d.ts +37 -0
  1127. package/packages/cli/src/font/measure.js +131 -0
  1128. package/packages/cli/src/font/reader.d.ts +13 -0
  1129. package/packages/cli/src/font/reader.js +26 -0
  1130. package/packages/cli/src/font/subset.d.ts +6 -0
  1131. package/packages/cli/src/font/subset.js +87 -0
  1132. package/packages/cli/src/font/svg.d.ts +3 -0
  1133. package/packages/cli/src/font/svg.js +109 -0
  1134. package/packages/cli/src/font/truetype.d.ts +3 -0
  1135. package/packages/cli/src/font/truetype.js +38 -0
  1136. package/packages/cli/src/font/variation.d.ts +1 -0
  1137. package/packages/cli/src/font/variation.js +1 -0
  1138. package/packages/cli/src/font/woff2.d.ts +3 -0
  1139. package/packages/cli/src/font/woff2.js +166 -0
  1140. package/packages/cli/src/glyphs.d.ts +5 -0
  1141. package/packages/cli/src/glyphs.js +59 -0
  1142. package/packages/cli/src/help.d.ts +5 -0
  1143. package/packages/cli/src/help.js +149 -0
  1144. package/packages/cli/src/html.d.ts +5 -0
  1145. package/packages/cli/src/html.js +119 -0
  1146. package/packages/cli/src/import-lottie.d.ts +2 -0
  1147. package/packages/cli/src/import-lottie.js +274 -0
  1148. package/packages/cli/src/import-svg.d.ts +8 -0
  1149. package/packages/cli/src/import-svg.js +300 -0
  1150. package/packages/cli/src/index.d.ts +1 -0
  1151. package/packages/cli/src/index.js +4 -0
  1152. package/packages/cli/src/inspect-summary.d.ts +7 -0
  1153. package/packages/cli/src/inspect-summary.js +158 -0
  1154. package/packages/cli/src/library.d.ts +6 -0
  1155. package/packages/cli/src/library.js +67 -0
  1156. package/packages/cli/src/locate.d.ts +13 -0
  1157. package/packages/cli/src/locate.js +118 -0
  1158. package/packages/cli/src/main.d.ts +2 -0
  1159. package/packages/cli/src/main.js +13 -0
  1160. package/packages/cli/src/playground.d.ts +11 -0
  1161. package/packages/cli/src/playground.js +169 -0
  1162. package/packages/cli/src/project.d.ts +15 -0
  1163. package/packages/cli/src/project.js +160 -0
  1164. package/packages/cli/src/render-options.d.ts +6 -0
  1165. package/packages/cli/src/render-options.js +40 -0
  1166. package/packages/cli/src/render.d.ts +30 -0
  1167. package/packages/cli/src/render.js +90 -0
  1168. package/packages/cli/src/scaffold.d.ts +17 -0
  1169. package/packages/cli/src/scaffold.js +96 -0
  1170. package/packages/cli/src/schema-view.d.ts +9 -0
  1171. package/packages/cli/src/schema-view.js +493 -0
  1172. package/packages/cli/src/session.d.ts +8 -0
  1173. package/packages/cli/src/session.js +96 -0
  1174. package/packages/cli/src/suggest.d.ts +3 -0
  1175. package/packages/cli/src/suggest.js +62 -0
  1176. package/packages/cli/src/video.d.ts +2 -0
  1177. package/packages/cli/src/video.js +73 -0
  1178. package/packages/core/src/script.source +897 -0
  1179. package/spec/ANIML.md +598 -0
  1180. package/spec/MODULES.md +406 -0
  1181. package/spec/SPEC.md +4083 -0
package/spec/SPEC.md ADDED
@@ -0,0 +1,4083 @@
1
+ # The `.ani` format — 0.1
2
+
3
+ Status: **0.1, frozen for the first implementation.** Sections marked *(normative)* define behaviour
4
+ every runtime must reproduce exactly; the conformance suite in `spec/conformance/` is the tie-breaker
5
+ when prose and tests disagree. §12 records the decisions taken for 0.1 and what is deferred.
6
+
7
+ `examples/bell.ani` is the reference document. Every construct below appears in it unless marked
8
+ *(not in bell)*.
9
+
10
+ ## 1. Goals
11
+
12
+ 1. **Agent-authored.** A language model writes and edits `.ani` files directly as text, checks them
13
+ with `anima validate`, and sees them with `anima sample` / `anima render`. Every mistake an author
14
+ can make is either impossible to express or a load error naming the JSON pointer and the fix.
15
+ 2. **Icon-scale and Glyphy-native.** Coordinates are glyph grid units. Glyphy's path data is embedded
16
+ byte for byte and animated in place: nodes pick subpaths and declare pivots instead of rewriting
17
+ geometry. The resting frame is identical to the static Glyphy SVG.
18
+ 3. **Deterministic.** One `step(dt)` function, fully specified, so a headless run and a browser run of
19
+ the same inputs produce the same values.
20
+ 4. **Small and web-first.** The reference runtime renders retained SVG DOM, themes with CSS, sleeps
21
+ when nothing moves, and is accessible by default.
22
+
23
+ Non-goals for 0.x: bones/meshes, text layout, scripting, nested artboards/components, raster images,
24
+ a binary encoding, importing `.riv`.
25
+
26
+ ## 2. Document
27
+
28
+ A `.ani` file is UTF-8 JSON (RFC 8259) with media type `application/vnd.anima+json`. Keys named
29
+ `$comment` are allowed on any object and ignored. `$schema` is allowed at the top level.
30
+
31
+ | Key | Type | Required | Meaning |
32
+ |---|---|---|---|
33
+ | `anima` | `"0.1"` | yes | Format version. A 0.1 runtime loads exactly `"0.1"`; before 1.0 every minor version may change the format, so any other value is `version-unsupported`. |
34
+ | `name` | kebab-case string | yes | Identifier, `^[a-z0-9]+(-[a-z0-9]+)*$`. Must not end in `-<digits>` (Glyphy reserves that for variants). |
35
+ | `title` | string | no | Human name. |
36
+ | `grid` | number > 0 | yes | Glyph grid size in units (Glyphy's `style.grid`, usually 20). |
37
+ | `viewBox` | `[x, y, w, h]` | no | Drawing area in grid units, `w, h > 0`; defaults to `[0, 0, grid, grid]`. May be larger than the grid so motion has room (bell: `[-2, -2, 24, 24]`). |
38
+ | `requires` | string[] | no | Optional features the file uses. A runtime that lacks one refuses the file (`unknown-feature`). 0.1 defines `"knockout"`; listing it is allowed but not needed. |
39
+ | `meta` | object | no | Free-form metadata (strings, numbers, booleans, arrays and objects of them) that runtimes ignore and tools preserve, e.g. Glyphy's `concept`, `kind`, `stroke`. |
40
+ | `colors` | `{slot: color}` | no | Colour slots, see §6. |
41
+ | `glyphs` | `{name: Glyph}` | no | Embedded path data, see §3. |
42
+ | `a11y` | object | no | See §10. |
43
+ | `inputs` | `{name: Input}` | no | See §8. |
44
+ | `nodes` | Node[] | yes | Scene tree, see §4. |
45
+ | `hit` | `{ "circle": [cx, cy, r] }` or `{ "rect": [x, y, w, h] }` | no | Hit area of the whole icon, see §9. Default: the `viewBox`. |
46
+ | `timelines` | `{name: Timeline}` | no | See §5. |
47
+ | `machine` | object | no | See §8. |
48
+ | `listeners` | Listener[] | no | See §9. |
49
+
50
+ **Names.** Node ids, input, slot, glyph, timeline, layer, state and event names all match
51
+ `^[a-z][a-zA-Z0-9-]*$` (`bad-name`). Names starting with `@` are reserved for built-ins.
52
+
53
+ **Order.** JSON member order is significant in exactly one place: `machine.layers` (layer order
54
+ decides who writes last, §7). Tools must preserve member order. Everything else that is ordered is
55
+ an array.
56
+
57
+ **Strictness** *(normative)*. Unknown keys, unknown node types, unknown properties, duplicate ids,
58
+ dangling references, type mismatches (a colour keyframe on a number property), non-increasing
59
+ keyframe times and unreachable references are **load errors**. A runtime never partially loads a
60
+ document: it either loads with no errors or reports every error it found and loads nothing.
61
+ Every diagnostic has a `code`, a `severity` (`error` | `warning`), a JSON `pointer` (RFC 6901) to the
62
+ offending value, a `message` and, where possible, a `hint` (`did you mean "rotate"?`). Warnings never
63
+ stop a load. The codes are listed in §11.3. A size-optimised runtime build may drop message and
64
+ hint text (the message is then the code) and skip computing warnings; it still reports every error
65
+ with its code, severity and pointer, and `anima validate` gives the full text. Duplicate object keys are invalid JSON for `.ani`;
66
+ `anima validate` reports them (`duplicate-key`), while runtimes built on a standard JSON parser may
67
+ keep the last value. Keys reserved for a later version (§12) are reported as `reserved`, not as
68
+ unknown keys, so the message can say which version brings them.
69
+
70
+ For an unavailable optional feature, verbose diagnostics include the web module import,
71
+ for example `import '@anima-format/web/machine' before loading this document`. CLI tools
72
+ register the full feature set; an accepted CLI document still needs its declared modules in the browser.
73
+
74
+ A document declaring `components` may have an empty `nodes` array: it is a library container
75
+ whose exported components can be instantiated as host roots. Ordinary documents need a node.
76
+
77
+ **Limits.** Loaders refuse documents beyond these limits with `limit-exceeded`: 1 MiB of text, nesting
78
+ depth 32 nodes, 96 levels of JSON nesting anywhere (free-form values such as `meta` included),
79
+ 2 048 nodes, 20 000 keyframes in total, 64 layers, 256 states per layer.
80
+
81
+ **Units** *(normative)*: lengths in grid units, y down; time in **seconds**; angles in **degrees**,
82
+ positive clockwise (y down), 0° along +x; opacity, scale and blend weights are plain numbers
83
+ (1 = 100 %). There are no frames or radians. The optional paint module adds percentage dash lengths (see its chapter).
84
+
85
+ ## 3. Glyphs
86
+
87
+ ```json
88
+ "glyphs": { "bell": { "source": "glyphy", "sha256": "…", "d": "M9.493 4.473C…Z" } }
89
+ ```
90
+
91
+ | Key | Required | Meaning |
92
+ |---|---|---|
93
+ | `d` | yes | SVG path data. Glyphy writes absolute `M L C Z` only; runtimes accept the full SVG 1.1 path grammar (all commands, relative forms, implicit repeats, arcs) but tools emit Glyphy's subset. |
94
+ | `source` | no | Free text naming where the data came from (`"glyphy"`). |
95
+ | `pick` | no | Glyphy's variant stem (`"bell-1"`). |
96
+ | `sha256` | no | Hex SHA-256 of the `d` string's UTF-8 bytes. Tools use it to detect a re-traced glyph; a mismatch is the warning `hash-mismatch` from `anima validate`, not a load error. |
97
+
98
+ Subpaths are numbered from 0 in the order their `M`/`m` commands appear. A path node takes
99
+ `"subpaths": [i, …]` (distinct, in range, any order — they are always drawn in `d` order); omitted
100
+ means all.
101
+
102
+ ## 4. Nodes
103
+
104
+ Every node has `id` (unique across the document) and `type`. Paint order is **document order, later
105
+ on top** (SVG order — the reverse of Rive).
106
+
107
+ Common properties (all animatable unless noted):
108
+
109
+ | Property | Type | Default | Meaning |
110
+ |---|---|---|---|
111
+ | `x`, `y` | number | 0 | Translation. |
112
+ | `rotate` | number | 0 | Degrees about `pivot`. |
113
+ | `scale` | number | 1 | Uniform scale about `pivot`; multiplies `scaleX`/`scaleY`. |
114
+ | `scaleX`, `scaleY` | number | 1 | Axis scales about `pivot`. |
115
+ | `opacity` | number | 1 | Group opacity, clamped to 0..1 when resolved. |
116
+ | `visible` | boolean | true | Hold-only. An invisible node draws nothing, hits nothing and cuts nothing. |
117
+ | `pivot` | `[x, y]` | `[0, 0]` | **Not animatable.** Point the transform turns and scales about. |
118
+
119
+ **Transform** *(normative)*: children are authored in the same grid coordinates as their parent (no
120
+ re-origining, so Glyphy geometry is used as-is). A node's local matrix is
121
+
122
+ `T(x, y) · T(px, py) · R(rotate) · S(scale·scaleX, scale·scaleY) · T(−px, −py)`
123
+
124
+ and its world matrix is `parent.world · local` (the root's parent is the identity in `viewBox`
125
+ coordinates). Because nothing is re-origined, the pivot is the same point in the node's and its
126
+ parent's coordinates at rest.
127
+
128
+ Types:
129
+
130
+ | `type` | Own keys | Notes |
131
+ |---|---|---|
132
+ | `group` | `children` (Node[]) | |
133
+ | `path` | `glyph` + optional `subpaths`, **or** inline `d` | Exactly one of `glyph` and `d` (`path-source`). |
134
+ | `circle` | `center` `[x, y]`, `r` (animatable, ≥ 0) | Its outline starts at angle 0 and runs in the direction of increasing angle (matters for trims). |
135
+ | `rect` | `x0`, `y0`, `w`, `h`, `rx` (`w`, `h`, `rx` animatable, ≥ 0; `rx` defaults to 0) | *(not in bell)* Outline starts at `(x0 + rx, y0)`, clockwise. |
136
+ | `arc` | `center`, `r`, `from`, `to` (`r`, `from`, `to` animatable) | Open circular arc, angles in degrees, drawn from `from` to `to` in the direction of increasing angle. At rest `0 < to − from ≤ 360` (`bad-value`). Stroke only (`arc-fill`). |
137
+
138
+ Paint (on `path`, `circle`, `rect`, `arc`):
139
+
140
+ - `fill`: `{ "color": slot-or-color, "rule": "nonzero" | "evenodd" }` — `rule` defaults to `nonzero`;
141
+ Glyphy glyphs need `evenodd`.
142
+ - `stroke`: `{ "color", "width", "cap", "join", "trimStart", "trimEnd", "trimOffset" }`.
143
+ `width` defaults to 1, `cap` (`butt`|`round`|`square`) and `join` (`miter`|`round`|`bevel`) default
144
+ to **`round`** — Glyphy styles carry no cap or join, and the reference sets are round. Trims are
145
+ fractions of the outline's length: the visible part runs from `trimStart + trimOffset` to
146
+ `trimEnd + trimOffset`, wrapping around 1 (defaults 0, 1, 0; start and end clamp to 0..1 when
147
+ resolved). When `trimEnd − trimStart ≤ 0` nothing is stroked (no dots from round caps). Trims are
148
+ allowed on `circle`, `rect`, `arc` and single-subpath `path`s (`trim-multi-subpath`).
149
+ - Animatable paint addresses: `<id>.fill.color`, `<id>.stroke.color`, `<id>.stroke.width`,
150
+ `<id>.stroke.trimStart|trimEnd|trimOffset`.
151
+ - A painted node with neither `fill` nor `stroke` draws nothing (warning `no-paint`); it can still be
152
+ a listener target.
153
+ - **Stroke width scales with the world transform** *(normative)*, as SVG and Glyphy ink do (§12.1).
154
+
155
+ **Knockout** *(normative)*: `"knockout": w` (number ≥ 0) on a painted node cuts the node's painted
156
+ area — its fill area if it has a fill, plus its stroke area if it has a stroke — grown by `w` units
157
+ (as if stroked with width `2w`, round joins and caps), out of every **earlier sibling** in the same
158
+ parent, following the node's animated transform. The cut's strength is the node's own resolved
159
+ `opacity`; an invisible node cuts nothing. Later knockouts cut earlier knockout nodes too. This
160
+ replaces the "paper-coloured disc" trick, so badges work on any background. (bell: the
161
+ notification dot cuts the body and clapper.)
162
+
163
+ ## 5. Timelines
164
+
165
+ ```json
166
+ "timelines": { "ring": { "duration": 1.4, "loop": "once", "reducedMotion": "end",
167
+ "tracks": { "swing.rotate": [[0, 0, "cubic(0.2, 0, 0.3, 1)"], [0.116667, 17.1887, "…"], [1.1, 0]] },
168
+ "events": [[0.05, "ding"]] } }
169
+ ```
170
+
171
+ - `duration` seconds (> 0). `loop`: `once` (default) | `loop` | `pingpong`. `reducedMotion`: §10.
172
+ - `tracks` maps a **property address** `"<node-id>.<property>"` to keyframes `[time, value, ease?]`
173
+ (at least one). Times strictly increase (`key-order`) and lie in `[0, duration]` (`key-range`).
174
+ The value type must match the property (number, colour string, boolean) (`value-type`).
175
+ - `events` *(not in bell)*: `[time, name]` pairs, `time` in `[0, duration]`, reported when playback
176
+ crosses them (§7).
177
+
178
+ **Clock boundaries** *(normative, every runtime)*. Time calculations use IEEE-754 binary64
179
+ seconds. Use the absolute constant `ε = 1e-9` seconds in the clock being compared (state,
180
+ fade, or unquantized timeline seconds, after speed/offset conversion). A clock `t` has
181
+ **reached** a boundary `b` iff `t ≥ b − ε`; it is strictly before it otherwise. This
182
+ applies to event crossings, key sampling, `@after`/`@end`, exit/exitFraction/exitNext,
183
+ fade completion (including entry carry), trigger-buffer expiry and settled detection.
184
+ A strict "later than" comparison means `!reached(b, t)` (`b < t − ε`). Never scale ε with
185
+ elapsed time, frame rate, speed or duration; never round the accumulated clock or `dt` to a time grid.
186
+ Carry remains `max(0, t − b)`, so early recognition introduces no negative target time.
187
+ Events, sampling, once completion and `exit`/`exitFraction` compare in source-timeline
188
+ seconds; `@after`, fades, buffer ages and armed `exitNext` state-time gates compare in
189
+ state/fade seconds. Authored times, input/value comparisons and validation ranges retain
190
+ their exact meaning.
191
+
192
+ For forward events use `!reached(previous, event) && reached(now, event)`; on entry replace
193
+ the first test with `reached(event, previous)`. For reverse events use
194
+ `!reached(event, previous) && reached(event, now)`, likewise including the previous bound
195
+ on entry. Thus an event recognized within ε fires once, including across `step(0)`;
196
+ nearby distinct authored events retain their identity and authored ordering. A pingpong
197
+ endpoint has one occurrence per full period: enumerate it with the authored endpoint
198
+ plus an integer multiple of the period, never both the forward and reflected formulas
199
+ (they can differ by a binary64 rounding bit). This does not merge distinct authored events.
200
+
201
+ For sampling choose the last key reached by `t` (or the first if none), snapping `t`
202
+ to that key when within ε before interpolating. For repeating clocks and work areas,
203
+ snap the offset from work start to the nearest integer multiple of the work span when
204
+ within ε, before folding; a loop boundary folds to the start and a pingpong turn to
205
+ its endpoint. Grid-index floor operations recognize that integer tick directly,
206
+ avoiding a second division rounding below it. Quantize similarly recognizes a sample
207
+ grid point within ε; otherwise it floors as usual. Halfway ties choose the greater
208
+ grid index. This local comparison rule preserves arbitrary authored sub-microsecond
209
+ times without imposing integer-microsecond rounding on all frames or changing clock speed.
210
+
211
+ **Sampling** *(normative)*, for track keys `k0 … kn` at local time `t`:
212
+ - `t ≤ t0` → `v0`; `t ≥ tn` → `vn`.
213
+ - Otherwise find `i` with `ti ≤ t < ti+1`; `u = (t − ti) / (ti+1 − ti)`; the **ease on key `i`**
214
+ (the left key of the segment) maps `u → w`; result `vi + (vi+1 − vi) · w`. The ease on the last key
215
+ is never read and should be omitted.
216
+ - Booleans take `vi` for the whole segment; their keys may only carry `hold` or no ease (`bad-ease`).
217
+ - **Colours** interpolate per channel in non-premultiplied sRGB, channels 0..255 including alpha:
218
+ `c = vi.c + (vi+1.c − vi.c) · w`, then `round(c) = floor(c + 0.5)` and clamp to 0..255 (§12.2).
219
+
220
+ **Eases** *(normative)*, written as strings:
221
+
222
+ | Ease | Definition |
223
+ |---|---|
224
+ | omitted / `linear` | `w = u` |
225
+ | `hold` | `w = 0` |
226
+ | `in`, `out`, `inOut` | `cubic(0.42, 0, 1, 1)`, `cubic(0, 0, 0.58, 1)`, `cubic(0.42, 0, 0.58, 1)` |
227
+ | `cubic(x1, y1, x2, y2)` | CSS `cubic-bezier`: with `Bx`, `By` the Bézier through `(0,0) (x1,y1) (x2,y2) (1,1)`, find the parameter `s ∈ [0, 1]` with `Bx(s) = u` and return `By(s)`. `x1, x2 ∈ [0, 1]` (so the root is unique); `y` unbounded (overshoot allowed). Implementations must be within 1e-7 of the exact root (Newton from the linear guess, falling back to bisection, is sufficient). `w(0) = 0` and `w(1) = 1` exactly. |
228
+ | `elastic(a, p)` | Ease-out elastic, `a ≥ 0`, `p > 0`: with `A = max(a, 1)`, `s = p / (2π) · asin(1 / A)`; `w = A · 2^(−10u) · sin((u − s) · 2π / p) + 1`; `w(0) = 0`, `w(1) = 1` exactly. |
229
+
230
+ Arguments are JSON numbers separated by commas; spaces are optional. `spring(…)` is reserved for a
231
+ later minor version (`reserved`). Golden vectors for every ease live in
232
+ `spec/conformance/eases.json`.
233
+
234
+ **Local time** *(normative)*. A timeline played for `τ` seconds (state time × `speed`, §8) is at local
235
+ time: `once` → `min(τ, d)`; `loop` → `τ − d·⌊τ/d⌋`; `pingpong` → with `q = τ − 2d·⌊τ/2d⌋`,
236
+ `q ≤ d ? q : 2d − q`. A `once` timeline has **finished** when `τ ≥ d`.
237
+
238
+ ## 6. Colours
239
+
240
+ `colors` declares named slots: `{ "ink": "currentColor", "accent": "#d97757" }`. A paint `color` is
241
+ either a slot name or a literal (`#rgb`, `#rrggbb`, `#rrggbbaa`, case-insensitive, or `currentColor`);
242
+ anything else is `bad-color`, and a name that is not a declared slot is `unknown-color-slot`.
243
+ In the web runtime a slot renders as `var(--anima-<slot>, <default>)`, so pages theme icons with CSS:
244
+ `anima-icon { color: …; --anima-accent: … }`. Static renders use the slot's default unless the
245
+ caller overrides it.
246
+
247
+ Slots are not animatable in 0.1. A paint colour that any track or pose writes must have a hex
248
+ literal as its document value, and every keyframe and pose value for it must be a hex literal
249
+ (`color-slot-animated`).
250
+
251
+ ### Optional slot mixing (`colormix`)
252
+
253
+ With `requires: ["colormix"]`, a node fill or stroke can replace `color` with
254
+ `"mix": { "a": "off", "b": "on", "t": 0.3 }`. The endpoints are hex literals or declared
255
+ slots with hex defaults; they remain static references. `currentColor` defaults are rejected
256
+ (`colormix-color`) because static renderers cannot resolve page inheritance. Page overrides may
257
+ still use `currentColor`. Slots themselves remain non-animatable.
258
+
259
+ The number address `<id>.<fill|stroke>.mix.t` is animatable and bindable through the ordinary
260
+ numeric channels. Its rest value must be in 0..1 (`colormix-range`); sampled, blended and bound
261
+ values clamp to that interval after resolution. Ordinary numeric key/pose validation applies.
262
+ AniML uses a child `<colorMix a="off" b="on" t="0.3"/>` (default fill), or
263
+ `<colorMix paint="stroke" .../>`; install `@anima-format/web/markup-colormix` for browser
264
+ compilation. Stroke width/cap/join keep their usual spelling. This module adds no syntax to the
265
+ base AniML colour parser. JSON Schema and CLI schema describe `fill.mix` and `stroke.mix`.
266
+
267
+ SVG renders `color-mix(in srgb, var(--anima-off, <default>), var(--anima-on, <default>) 30%)`.
268
+ The second colour has weight t and the first 1−t. Literal-only and static SVG renders resolve the
269
+ colours first; static renders use defaults or supplied slot overrides. Canvas resolves page slots
270
+ from its owning host, including offscreen effects surfaces. All interpolate **premultiplied** sRGB
271
+ channels and alpha, then unpremultiply (zero alpha gives zero RGB), round channels to the nearest
272
+ 8-bit value with ties upward, and clamp to 0..255. This matches CSS `color-mix(in srgb)`; it does
273
+ not change §5's existing non-premultiplied colour-track interpolation.
274
+
275
+ `mix` cannot accompany `color`, gradient `type`, or component `paint`, and is currently a node
276
+ fill/stroke source, not an ordered `paints` entry (`colormix-source`). Invalid endpoint names or
277
+ non-hex defaults are `colormix-color` at `/nodes/…/<paint>/mix/a` or `/b`, with a hex/default hint.
278
+ An out-of-range rest weight is `colormix-range` at `…/mix/t`, with a 0..1 hint. Malformed objects,
279
+ missing arguments, incorrect argument types and unknown fields retain registry diagnostics
280
+ (`type`, `missing-key`, `unknown-key`) at the object/argument. Absent declarations use
281
+ `undeclared-feature` at `…/mix`. A paint still needs exactly one colour/gradient/source.
282
+
283
+ ## 7. Playback, blending and the step function *(normative)*
284
+
285
+ A **player** holds: input values, pending triggers, and per layer: the current state, its **state
286
+ time** (seconds since entry), and an optional crossfade (a frozen *from* snapshot, its duration, ease
287
+ and elapsed time). A new player has every input at its default and every layer in its initial state
288
+ at time 0 with no crossfade; its values are resolved as by steps 3–4 below, and nothing else is
289
+ evaluated until the first `step`, which reports the initial states' entry events (`emit`s and
290
+ timeline events at time 0) before anything else.
291
+
292
+ **Resolve rule.** Each layer is a function from the values below it to the values above it. Start
293
+ from the **rest values** (each property's document value, or its registry default). Let `S(state)`
294
+ be the set of addresses a state writes: its pose's keys, or its timeline's tracks. A layer that is
295
+ not crossfading writes `sample(state, p)` for every `p ∈ S(state)` and passes every other property
296
+ through. A layer crossfading from snapshot `F` to state `T` with eased progress `w` writes, for every
297
+ `p ∈ dom(F) ∪ S(T)`, `lerp(a, b, w)` with `a = F[p]` if `p ∈ dom(F)` else the value below, and
298
+ `b = sample(T, p)` if `p ∈ S(T)` else the value below. So a property the target state does not set
299
+ blends back to what is below it — for a single layer, its rest value. `lerp` is linear for numbers,
300
+ per channel with rounding for colours (§5), and for booleans `a || b` while the crossfade runs and
301
+ `b` once it ends. Later layers win where two layers write the same property.
302
+
303
+ `step(dt)` with finite `dt ≥ 0` seconds does, in order:
304
+
305
+ 1. **Inputs.** Apply queued writes in arrival order (`set`, `fire`, `toggle`). Number writes clamp to
306
+ the input's `min`/`max`. A fired trigger becomes pending (firing a pending trigger again is a
307
+ no-op). Listener `emit` actions add their events now. A queued listener action evaluates its
308
+ expression-valued sets here, when that action is reached, using one pre-action input snapshot
309
+ for all its sets (§bind). Later actions see earlier writes. With `derive`, recompute
310
+ computed inputs once after this queue, before clocks and machine guards (derive chapter).
311
+ 2. **Clocks.** Motion clock expressions and `exitNext` gates prepare here (motion chapter). Advance every layer's state time and crossfade elapsed time by `dt`. A crossfade whose
312
+ elapsed time reaches its duration ends (using §5 clock boundaries).
313
+ 3. **Layers**, in declaration order, each against the values resolved by the layers before it:
314
+ 1. *Timeline events.* Report the current state's timeline events crossed by step 2 (below).
315
+ 2. *Transitions.* Unless a non-interruptible crossfade is running (§8), evaluate the current
316
+ state's `on` list in order; take the first transition whose
317
+ condition holds. Taking it: (a) freeze the layer's current output for `dom(F) ∪ S(current)` as
318
+ the new snapshot `F` (this is also what makes interrupting a crossfade smooth); (b) mark the
319
+ triggers the condition named as consumed by this layer; (c) enter the target: state time =
320
+ **carry** (the overshoot `t − threshold` of the `@end`/`@after` term that made the condition
321
+ hold, the largest one if several; else 0), crossfade elapsed = carry; a `blend` of 0 drops the
322
+ snapshot; (d) run the source exit, transition actions and target entry blocks (§bind), then
323
+ report the target's `emit`, then its timeline events in `[0, carry]`. Then
324
+ evaluate the new state's `on` list the same way. A layer takes at most **16** transitions per
325
+ step; reaching the cap reports the event `@limit` for that layer and stops its transitions for
326
+ this step. A trigger already consumed by this layer does not hold for it again in this step.
327
+ 3. *Write* the layer's output over the values (resolve rule).
328
+ 4. **Bindings, then clamp.** Evaluate declared `bind` expressions against the current inputs,
329
+ overriding the state-machine values (see the bind chapter). Clamp resolved values to their property's range (`opacity`, trims 0..1; `r`, `w`, `h`,
330
+ `rx`, `stroke.width` ≥ 0).
331
+ **Layout stage (optional).** After bindings and clamping, resolve participating boxes using
332
+ the layout contract, including nested-player/post-step updates, before drawing. Guards read
333
+ the previous step's boxes; layout never reruns transitions in the current step.
334
+ **Rig stage (optional).** After layout and before drawing, solve parent/constraint dependencies,
335
+ publish rest-relative bone and constrained transforms, then call the optional pose stage.
336
+ A pose write requests exactly one additional solve; see the rig chapter.
337
+ 5. **Triggers.** A pending trigger that some layer consumed this step is cleared. One that no layer
338
+ consumed is cleared too, unless its input declares `"buffer": seconds`: then it stays pending until
339
+ it has been pending for longer than `buffer` seconds of step time, or is consumed.
340
+ 6. Return `{ events, settled }`.
341
+
342
+ **Timeline events.** A play state reports each event whose time, mapped through the loop mode, lies
343
+ in `(τprev, τnow]` of played time, plus events at time 0 when the state is entered; for `loop` an
344
+ event at `te` occurs at `te + k·d`, for `pingpong` at `te + 2kd` and `2(k+1)d − te` (counted once
345
+ when those coincide); a `once` timeline reports nothing after it finishes. Events are reported in
346
+ time order. Each event is `{ name, layer, time }`: `time` is the event's timeline time, or 0 for an
347
+ `emit`; listener events have `layer: null`. Order within one step: listener events, then layer by
348
+ layer in declaration order as described in step 3.
349
+
350
+ **Settled.** `settled` is true when no trigger is pending and every layer is at rest: no crossfade
351
+ is running, the current state is a pose or a finished `once` timeline (or a timeline frozen by
352
+ reduced motion, §10), and none of the current state's transitions has an `@after(s)` term with
353
+ `s` greater than the state time. A settled player's values cannot change until an input changes, so
354
+ it needs no frames until then.
355
+
356
+ Large `dt` is processed in one step; runtimes clamp `dt` to 0.25 s per frame for display, but the
357
+ headless `sample` command does not.
358
+
359
+ ## 8. Inputs and the state machine
360
+
361
+ ```json
362
+ "inputs": { "ring": { "type": "trigger" }, "hover": { "type": "bool" }, "level": { "type": "number", "default": 0, "min": 0, "max": 1 } }
363
+ ```
364
+
365
+ Types: `bool` (`default` false), `number` (`default` 0, optional `min` ≤ `max`; the default must lie
366
+ in range), `trigger` (optional `buffer` ≥ 0 seconds, default 0). Inputs are the **only** host
367
+ interface: the page sets them, listeners set them, conditions read them. (Rive moved from
368
+ state-machine inputs to view models; at icon scale one flat typed namespace does both jobs.) Host
369
+ writes to an unknown input or with the wrong type are rejected by the API, never ignored.
370
+
371
+ ```json
372
+ "machine": { "layers": { "ring": { "initial": "idle", "states": {
373
+ "idle": { "pose": { "dot.scale": 0 }, "on": [{ "when": "ring", "to": "ringing" }] },
374
+ "ringing": { "play": "ring", "emit": "rang", "on": [{ "when": "@end", "to": "rested" }] } } } } }
375
+ ```
376
+
377
+ - A **layer** has `initial` (a state name) and `states`.
378
+ - A **state** has exactly one of `pose` (a map of property address → value, i.e. a timeline of one
379
+ instant; `{}` writes nothing) or `play` (a timeline name, optional `speed` > 0, default 1), plus
380
+ optional `emit` (event name on entry) and `on` (transitions) (`state-kind`).
381
+ - Entering a `play` state restarts its timeline at 0; its played time is state time × `speed`.
382
+ - A **transition** `{ "when", "to", "blend", "ease", "interruptible" }`: `when` and `to` are
383
+ required; `blend` is a crossfade duration in seconds (default 0); `ease` shapes the crossfade
384
+ (default `linear`). `interruptible` (default `true`): when `false`, the layer takes no transitions
385
+ while the crossfade this transition started is running, so the target pose is fully reached first
386
+ (a quick click still shows its press). Triggers are cleared as usual meanwhile — give the input a
387
+ `buffer` if a fire must survive the crossfade. `to` may
388
+ name the current state (re-entering it restarts its timeline) only if the condition contains a
389
+ trigger, `@end` or `@after` term (`self-transition`), so it cannot re-fire every step.
390
+ - **Conditions** (`when`), a deliberately tiny grammar (`bad-condition` otherwise):
391
+
392
+ ```
393
+ condition := term ( "&&" term )*
394
+ term := "!"? NAME | NAME OP NUMBER | EXPR OP EXPR | "@end" | "@after(" NUMBER ")"
395
+ OP := "==" | "!=" | "<" | "<=" | ">" | ">="
396
+ ```
397
+
398
+ `name` holds when the trigger is pending (and not yet consumed by this layer this step) or the
399
+ bool is true · `!name` when the bool is false · `name OP number` compares a number input ·
400
+ `@end` when the state's `once` timeline has finished · `@after(s)` when the state time is at least
401
+ `s`. All terms must hold. There is no `||`: write two transitions. Whitespace between tokens is
402
+ ignored. `EXPR OP EXPR` requires `bind` and uses its numeric expression language; legacy
403
+ `NAME OP NUMBER` keeps its number-input-only type rule, including when bind is declared.
404
+ Arithmetic comparisons may be joined with existing trigger/time/bool terms.
405
+ - Load errors: unknown input (`unknown-input`); a negated trigger, a bare number or a compared
406
+ bool/trigger (`input-type`); `@end` on a pose state or a `loop`/`pingpong` timeline (`bad-end`).
407
+ Warnings: states no transition can reach (`unreachable-state`), inputs nothing reads or writes
408
+ (`unused-input`).
409
+
410
+ ## 9. Listeners
411
+
412
+ ```json
413
+ "listeners": [ { "on": "click", "fire": "ring" }, { "on": "enter", "set": { "hover": true } } ]
414
+ ```
415
+
416
+ - `on`: `enter` | `leave` | `down` | `up` | `click` | `move` *(drag: later)*.
417
+ - `target`: a node id, or omitted for the **whole icon** (the top-level `hit` shape if given, else
418
+ the `viewBox`). A node target hits where its painted area is: fill area by its fill rule, plus the
419
+ stroke within half its (world) width, of the node or, for a group, of any painted descendant,
420
+ grown by the listener's optional `"hitPadding"` (grid units, ≥ 0). Invisible nodes do not hit.
421
+ Hit testing may approximate curves, but must be exact for points farther than 0.05 units from an
422
+ edge.
423
+ - Actions, at least one: `fire` (trigger name or list), `set` (`{ input: value }` for bool and
424
+ number inputs; expression strings additionally require `bind`), `toggle` (bool input name or list) *(not in bell)*, `emit` (event name)
425
+ *(not in bell)*. Action keys run in that order: `fire`, `set`, `toggle`, `emit`.
426
+ - **Pointer model** *(normative)*. The host reports pointer events in grid coordinates:
427
+ `move(x, y)`, `down(x, y)`, `up(x, y)` (primary button only) and `leave()` (pointer left the icon
428
+ or was cancelled). For each event, the player updates which targets are under the pointer, then
429
+ runs listeners, in document order within each group: first `leave` for targets no longer under
430
+ the pointer, then `enter` for targets newly under it, then `move`/`down`/`up` for targets under it,
431
+ then `click`. `click` fires on `up` for a target that was also under the `down`, when the pointer
432
+ moved less than 0.5 grid units in between. `leave()` cancels a press and leaves every target.
433
+ - Listener actions are queued as input writes for the next `step` (§7.1); runtimes call `step(0)`
434
+ immediately after each pointer event so a press is visible in the same frame.
435
+
436
+ ## 10. Accessibility and motion
437
+
438
+ ### Web element sizing
439
+
440
+ The SVG `<anima-icon>` host defaults to a width of `1em` and an automatic height
441
+ with CSS `aspect-ratio` from the loaded model's viewBox width/height. Selecting a
442
+ root `component` uses that component's viewBox. Author CSS width, height and
443
+ aspect-ratio declarations take precedence over these shadow stylesheet defaults;
444
+ setting both dimensions keeps the authored box. Reloading or changing the shown
445
+ component refreshes the default ratio. Fit/alignment control the drawing within
446
+ that box. In the default grid/bleed mapping the host's long axis spans `grid`
447
+ units and its short axis follows the viewBox aspect. For a host `W×H` with the
448
+ viewBox aspect `w/h`, the uniform scale is `s=W/grid` when `w>=h`, otherwise
449
+ `s=H/grid`. The SVG viewport is `(x*s,y*s,w*s,h*s)` relative to the host and
450
+ retains the source aspect. If author CSS gives the host another aspect, a
451
+ non-square source still uses the same source-long-axis scale rule and keeps its
452
+ own aspect; square sources retain their legacy per-axis viewport placement.
453
+ Canvas uses the same placement; pointer coordinates
454
+ invert the drawing transform and semantic overlay bounds use it forwards.
455
+ Legacy square-viewBox grid offsets remain unchanged. The shadow SVG exposes
456
+ `part="svg"`; the canvas backend's canvas exposes `part="canvas"`. These hooks must
457
+ survive every shadow rebuild, including text-run changes, reloads and renderer switches,
458
+ so page `::part()` rules continue to apply.
459
+
460
+ ### Host input queue
461
+
462
+ Web-host `set(name,value)`, `fire(name)` and `toggle(name)` calls made without a player are queued
463
+ in call order. They are applied once to the next player created by that host,
464
+ before its first step, including when asset preparation deferred creation.
465
+ Superseded load completions do not consume the queue. Names and values are validated
466
+ by the player's normal methods when the queue is applied.
467
+
468
+ ### Host settling
469
+
470
+ `icon.settle()` resolves current inputs, blends and once-timelines by stepping the
471
+ player first at `dt=0`, then at `dt=0.25` until settled or 60 simulated seconds have
472
+ elapsed. It returns whether rest was reached. Events from these steps are discarded;
473
+ no `anima:event` or `anima:state` is dispatched, and the settled state notification
474
+ baseline is updated so the next ordinary frame does not replay a suppressed change.
475
+ The result is drawn and ARIA updated. It preserves pause, speed and reduced-motion
476
+ settings, resets the display clock and re-arms normal playback when still busy.
477
+ Before a player exists it queues once, in order with host input calls, returns false,
478
+ and is resolved before that player's first draw. This supports mounting a control
479
+ already at rest after `set()` without animating from its initial state.
480
+
481
+ ### Semantics and motion
482
+
483
+ The optional browser semantic overlay, native controls, focus synchronization and
484
+ host tree/diff API are specified in [a11y-dom/SPEC.md](../modules/a11y-dom/SPEC.md).
485
+ It consumes node accessibility; root document `a11y` below is unchanged.
486
+
487
+ `"a11y": { "role": "button" | "switch" | "img", "label": "…", "activate": "<trigger or bool>", "pressed": "<bool>", "held": "<bool>" }`
488
+
489
+ - `button`/`switch`: the element is focusable and exposes the role and `label`. **Enter** activates
490
+ on key down (not on repeats); **Space** activates on key up — as native buttons do.
491
+ Activation fires the trigger named by `activate`, or toggles it if it is a bool.
492
+ While either key is held, the bool named by `held` (if any) is true. `pressed` names a bool input
493
+ that `aria-pressed` (`button`) or `aria-checked` (`switch`, where it is required) follows.
494
+ - `img` or no `a11y`: `role="img"` with the label, or `aria-hidden` when there is no label.
495
+ - `activate`, `pressed` and `held` must name inputs of the right type (`unknown-input`, `input-type`).
496
+ - **Reduced motion.** Each timeline declares `reducedMotion`: `play` | `end` | `skip`, default `end`.
497
+ With reduced motion on, entering a play state whose timeline says `end` freezes it at local time
498
+ `d`, reporting all its events at once; `skip` freezes it at local time 0 and reports none; `play`
499
+ plays normally. A frozen `once` timeline counts as finished (so `@end` holds at once) and a frozen
500
+ timeline of any loop mode counts as at rest. Every crossfade takes 0 s. The web runtime follows
501
+ Switching reduced motion on re-resolves all active play states at the next step: `end`
502
+ freezes at `d`, reporting only events in (current local time, `d`] once (the rest of the
503
+ current cycle for loops); `skip` freezes at 0 without events; `play` is unchanged.
504
+ Motion work ranges and reverse playback use the same start/finish endpoint as state entry.
505
+ In-progress crossfades complete immediately. Switching off resumes frozen loops from
506
+ their frozen local time; frozen `once` states stay finished. This implements the stop
507
+ request behind WCAG 2.2.2 even when the preference changes mid-animation (see
508
+ `docs/research/rive-web-runtime.md`, recommendation 7). The web runtime follows
509
+ `prefers-reduced-motion` unless the element sets `motion="full"` (or `motion="reduce"` to force it).
510
+
511
+ The host and semantic overlay do not stop DOM event propagation. Page listeners on
512
+ ancestors receive keyboard and pointer events that originate on an icon. Anima calls
513
+ `preventDefault()` only when its own handling consumes the browser default; internal
514
+ host routing guards prevent the same event from being handled twice.
515
+
516
+ ## 11. Conformance
517
+
518
+ ### 11.1 Cases
519
+
520
+ `spec/conformance/<case>/` holds `case.animl` (the authored source), `case.ani` (the compiled build
521
+ output), `script.txt` and `expected.jsonl`. `case.ani` is the runtime's normative input; hand-written
522
+ `.ani` remains valid. `npm run build:animl` rebuilds the sibling `.ani` files, and
523
+ `node tools/build-animl.ts --check` checks that they are current. Every runtime and the CLI run the
524
+ whole suite in CI. Numbers are compared at 1e-6, everything else exactly.
525
+
526
+ ### 11.2 Script language
527
+
528
+ One command per line; `#` starts a comment. The same language drives `anima sample`.
529
+
530
+ | Command | Does |
531
+ |---|---|
532
+ | `advance <seconds> [in <n>]` | `step(seconds / n)` `n` times (default 1). |
533
+ | `set <input> <true\|false\|number>` | Queue a write. |
534
+ | `fire <input>` / `toggle <input>` | Queue a fire / toggle. |
535
+ | `pointer move\|down\|up <x> <y>` | Report a pointer event in grid units, then `step(0)`. |
536
+ | `pointer click <x> <y>` | `move`, `down`, `up` at that point, each followed by `step(0)`. |
537
+ | `pointer leave` | Report `leave()`, then `step(0)`. |
538
+ | `focus target <id>` / `focus clear` | Change logical focus, then step(0); requires focus. |
539
+ | `focus traverse <direction>` | Traverse next/previous/left/right/up/down, then step(0). |
540
+ | `key down\|up <key> [shift]` | Deliver a deterministic focus key, then step(0); input module integration extends this dispatch. |
541
+ | `semantics [id]` | Print the semantic tree or subtree as one JSON line; requires a11y. |
542
+ | `semantic <id> tap\|increase\|decrease\|focus` | Perform an advertised semantic action, then step(0); requires a11y-actions. |
543
+ | `motion reduce\|full` | Turn reduced motion on or off at the next step, including active states. |
544
+ | `touch down\|move\|up <id> <x> <y>` | Input-module pointer API with an integer id ≥ 0, in grid units; `up` also leaves. Each delivery is followed by `step(0)`. |
545
+ | `pointer drag <x0> <y0> <x1> <y1> [in <n>]` | Move/down at start, `n` equally spaced moves, up at end; each delivery steps at 0. Default `n=1`. |
546
+ | `contact move\|down\|up <id> <x> <y> [button]` | Input-module pointer, preserving pointer ids. `contact leave\|cancel <id>` ends a contact. |
547
+ | `key down\|up\|repeat <key\|Space> [shift+control+alt+meta\|-] [target]` | Extended key API when input is required; otherwise the focus-only spelling above. |
548
+ | `scroll <x> <y> <dx> <dy> [start\|move\|end\|cancel] [seconds]` | Input-module scroll API in grid units, followed by `step(0)`. |
549
+ | `gamepads <JSON-array>` | Poll the input-module gamepad API, then `step(0)`; records have index, connected, optional mapping, axes and buttons. |
550
+ | `pointer-enable <id> <true\|false>` / `scroll-cancel` | Enable a pointer or cancel scroll latching, then `step(0)`. |
551
+ | `data set <$path> <value>` | Queue a typed public host-data handle write; applies on the next step. JSON strings, bools, numbers, colors and lists are accepted according to the property type. |
552
+ | `data dump [prefix …]` | Print `{data: {path: value}}` as one JSON line. No prefixes selects all; multiple prefixes form a union. Includes named instance paths and default scalar aliases. |
553
+ | `data watch <$path …>` | Remember current values. Each later snapshot carries a `data` object of watched values changed since the previous snapshot (or watch registration). |
554
+ | `data break <$path> [== value]` | Preflight each next step by instrumented replay on an isolated player. Stop before applying the step live at the first write (including same-value or overwritten writes, optionally matching the condition), reporting `script-breakpoint`, old/written values and the actual writer. |
555
+ | `data break clear` | Clear all breakpoints; a session can then advance the pending step. |
556
+ | `expect <address\|$path\|input> ==\|!=\|<\|>\|~ <value>` | Compare applied values; `~` is absolute numeric error ≤ 1e-6. Failure emits `script-expect` with pointer and hint; execution continues. |
557
+ | `group <name>` / `end` | Open/close a named assertion group; nested groups label assertion diagnostics. Session groups may span requests. |
558
+ | `expect-error <code>` | Assert that the next nonempty command throws or emits that diagnostic code. A missing or different code emits `script-expect-error`; matching diagnostics are consumed. |
559
+ | `seed <n>` | Reset the shared core uint32 random source for this player and nested stepping to n. The player constructor option `seed` and CLI `--seed` initialize the same source. Without an explicit seed, existing deterministic machine/converter defaults are preserved. |
560
+ | `snapshot [--all] [--semantics] [address …]` | Print one JSON line. `--all` adds unanimated animatable properties and persistent inputs. `--semantics` appends `player.semantics()` or errors clearly if unavailable. Explicit addresses accept static properties too. |
561
+
562
+ `instance <id> <timeline> [mix]`, `timeline <name> play|pause|stop|seek|scrub|speed|mix|loop [value]`
563
+ and `machine <name> play|pause|stop|mix|fire|set [value …]` retain the motion module's control API.
564
+ JSON quoted strings can contain spaces and `#`; unquoted hex colors are values rather than comments.
565
+ Data commands require data in the document and the host module loaded; scripts without data commands
566
+ do not access host data. Commands add no authoring fields or registry spellings. Both `expect` and selected
567
+ `snapshot` addresses resolve AniML property spellings (for example `volumeFill.width` →
568
+ `volumeFill.w`) through the registry, including slash-scoped nested addresses. Exact inputs
569
+ and JSON addresses take precedence over aliases. The CLI groups unknown `$path-number`
570
+ diagnostics at the same pointer into one `data-path` diagnostic with a spaced subtraction
571
+ hint; declared hyphenated paths retain their meaning.
572
+
573
+ `anima sample` prints outputs in script order, emits assertion/breakpoint diagnostics on stderr,
574
+ and returns 1 for those diagnostics, 2 for invalid arguments/script commands. Targeting an absent input with set/fire/toggle or a hidden/absent semantic node with
575
+ semantic emits `script-target`, a script pointer and inspection guidance. Other script syntax
576
+ errors carry `script-usage`, `/script/<zero-based-line>`, a message and hint.
577
+
578
+ `anima session <file>` keeps the interpreter alive. Each stdin line is one command or a JSON
579
+ object with a string `cmd`; each receives exactly one JSON reply containing `ok`, outputs,
580
+ snapshots and diagnostics. A bad request does not end the process. `quit` replies then exits;
581
+ `reload` reads and validates the source again, resets time/inputs/control state, restores compatible
582
+ data values through public handles and steps at 0. Its reply lists restored/skipped paths; failed
583
+ reload retains the old interpreter. Breakpoints pause the current command, and the next request
584
+ may inspect/edit data or clear them. Preflight replays all prior operations, so its cost grows with
585
+ history and includes the next step. Tooling instruments instance setters and queued operations in
586
+ the replay player, capturing writes before mutation, including same-value and transient writes.
587
+ Writers identify a script line, listener pointer, state/transition action pointer or the actual
588
+ binding write function. Timeline-driven reverse writes identify the binding that stores the value.
589
+ Instrumentation is absent from shipped data/host bundles.
590
+
591
+ `anima bench <file> [-s script] [--frames N] [--format=json]` measures source compilation/loading
592
+ once, then renderer-neutral `Player.step(1/60)` and `drawFrame` separately for N frames (default
593
+ 600, integer 1–1,000,000). It warms 50 frames on a separate player. Output includes `compileMs`,
594
+ frame count and step/draw median and p95 in microseconds, using sorted samples at indices
595
+ floor(N×0.5) and floor(N×0.95), capped at N−1. Script setup, filesystem I/O except source loading,
596
+ DOM and raster/GPU work are not frame costs. This follows the measurement boundary in
597
+ `tools/perf.ts`; see also `docs/research/rive-web-runtime.md` on native/GPU versus host overhead.
598
+ Bench additionally reports `memory` with `kind: "js-heap"`, `beforeBytes`, `afterBytes`,
599
+ `growthBytes` (after minus before), and `bytesPerFrame` (growth divided by N). Samples bracket
600
+ the measured frame loop after warming; GC can make growth negative. These are JS heap observations,
601
+ not retained allocation or WASM measurements. Both session and bench accept `--seed`, `--all`, `--semantics` and `--reduced-motion`; seed has
602
+ the shared source semantics above. Debugging and audio capture instrumentation are tooling-only.
603
+
604
+ A snapshot line is `{"time": t, "values": {address: value}, "states": {layer: state},
605
+ "events": [...], "settled": bool}`: `time` is the total step time so far, `values` holds every
606
+ address any track or pose writes (or the listed ones), `events` holds the events reported since the
607
+ previous snapshot, and `settled` is the last step's result.
608
+ Explicit input names appear in `values` under their own names (for example,
609
+ `snapshot dark knob.x`). They report the currently applied value; queued `set`/`toggle`
610
+ writes take effect only after a step. Triggers have no persistent value and cannot be
611
+ selected. Selected addresses accept both JSON paths and AniML property spellings (for example
612
+ `ring.stroke.trimEnd` and `ring.trimEnd`). Snapshot keys always use JSON paths, for both
613
+ selected and complete snapshots, including slash-scoped component addresses. Exact input
614
+ names and JSON paths take precedence over aliases. An unknown address or input is a script error. With no arguments, `snapshot`
615
+ continues to include only animated addresses, never inputs (unless `--all` is set).
616
+
617
+ ### 11.3 Diagnostic codes
618
+
619
+ | Code | Severity | When |
620
+ |---|---|---|
621
+ | `json-syntax` | error | The text is not JSON. The message carries line and column. |
622
+ | `duplicate-key` | error | An object repeats a key (`anima validate`). |
623
+ | `type` | error | A value has the wrong JSON type. |
624
+ | `missing-key` | error | A required key is absent. |
625
+ | `unknown-key` | error | A key is not defined here. Hint: the closest valid key. |
626
+ | `reserved` | error | A key, ease or address reserved for a later version. |
627
+ | `version-unsupported` | error | `anima` is not `"0.1"`. |
628
+ | `undeclared-feature` | error | A module key or address is used without its feature in `requires`; hint: add the feature to `requires`. |
629
+ | `unknown-feature` | error | `requires` names a feature this runtime lacks. |
630
+ | `bad-name` | error | A name breaks its pattern. |
631
+ | `duplicate-id` | error | Two nodes share an id. |
632
+ | `unknown-node-type` | error | `type` is not a node type. |
633
+ | `bad-value` | error | A value is out of range or not an allowed option. |
634
+ | `bad-color` | error | A colour string does not parse. |
635
+ | `unknown-color-slot` | error | A paint names a slot that is not declared. |
636
+ | `unknown-glyph` | error | A path names a glyph that is not declared. |
637
+ | `bad-path` | error | Path data does not parse. |
638
+ | `bad-subpath` | error | A subpath index is out of range or repeated. |
639
+ | `path-source` | error | A path has both or neither of `glyph` and `d`. |
640
+ | `arc-fill` | error | An arc has a fill. |
641
+ | `trim-multi-subpath` | error | A stroke with trims on a path of several subpaths. |
642
+ | `unknown-address` | error | A track or pose names an unknown node or property. |
643
+ | `not-animatable` | error | A track or pose writes a property that cannot be animated. |
644
+ | `value-type` | error | A keyframe or pose value has the wrong type. |
645
+ | `color-slot-animated` | error | An animated colour uses a slot or `currentColor`. |
646
+ | `empty-track` | error | A track has no keyframes. |
647
+ | `key-order` | error | Keyframe times do not strictly increase. |
648
+ | `key-range` | error | A keyframe or event time lies outside `[0, duration]`. |
649
+ | `bad-ease` | error | An ease does not parse, has invalid arguments, or eases a boolean. |
650
+ | `unknown-timeline` | error | A state plays a timeline that is not declared. |
651
+ | `unknown-state` | error | `initial` or `to` names a state that is not in the layer. |
652
+ | `state-kind` | error | A state has both or neither of `pose` and `play`, or `speed` without `play`. |
653
+ | `bad-condition` | error | A condition does not parse. |
654
+ | `guard-literal` | error | A module literal function uses the wrong property, name or operator; use ==/!= with a matching typed property. |
655
+ | `data-keyframe` | error | A key source path or live channel value has the wrong type; use compatible scalar data. |
656
+ | `unknown-input` | error | A condition, listener or `a11y` names an input that is not declared. |
657
+ | `input-type` | error | An input is used in a way its type does not allow (`a11y.activate` needs a trigger or bool; `pressed` and `held` need bools). |
658
+ | `bad-end` | error | `@end` on a pose state or a looping timeline. |
659
+ | `self-transition` | error | A transition to the current state with only level terms. |
660
+ | `unknown-target` | error | A listener targets a node that does not exist. |
661
+ | `limit-exceeded` | error | A §2 limit is exceeded. |
662
+ | `hash-mismatch` | warning | A glyph's `sha256` does not match its `d`. |
663
+ | `no-paint` | warning | A painted node has neither fill nor stroke. |
664
+ | `unreachable-state` | warning | No transition reaches a state. |
665
+ | `unused-input` | warning | Nothing reads or writes an input. |
666
+
667
+ ### 11.4 Local playground
668
+
669
+ `anima playground [file.animl] [--port N]` serves a local authoring inspector on `127.0.0.1`
670
+ (default port 4519, next free if occupied). With no file it opens the reference bell as a read-only
671
+ scratch example; Save cannot change it. AniML edits use the strict compiler and its source positions
672
+ (`spec/ANIML.md` §7). Errors keep the last valid document visible, marked as stale. The live stage
673
+ uses the web runtime and the same inputs, listeners and player semantics as a hosted icon.
674
+
675
+ Timeline scrubbing samples the chosen timeline at the clamped time `[0, duration]` using §5 over
676
+ the document rest pose. It pauses live playback and input controls; returning to live mode resumes
677
+ the saved live state. It does not advance the state machine or emit timeline events. This is an
678
+ inspector view, not a new player stepping mode. Save writes only the opened file; a changed disk
679
+ revision must be reloaded before saving. Details and keyboard controls: `docs/PLAYGROUND.md`.
680
+
681
+ ## 12. Decisions for 0.1, and what is deferred
682
+
683
+ These were the open questions of the draft. Each is settled for 0.1; the deferred ones are reserved
684
+ so a 0.1 loader rejects them with `reserved` and a pointer to the version that brings them.
685
+
686
+ 1. **Non-scaling strokes — no flag; strokes always scale.** Glyphy ink is a filled outline, which
687
+ scales with its node, so an authored stroke (the bell's waves) must scale the same way to keep
688
+ matching it. Glyphy's own check allows `stroke ± 0.12`, and the bell's poses (0.95–1.08) stay in
689
+ it (`docs/research/glyphy-compat.md` §4.5). SVG's `vector-effect: non-scaling-stroke` works in
690
+ screen pixels, not grid units, so it could not express "keep 1 unit" anyway. Revisit with a
691
+ grid-unit definition if an icon needs it.
692
+ 2. **Colour interpolation — non-premultiplied sRGB, 8-bit channels, `floor(c + 0.5)`, clamped.**
693
+ It is what CSS transitions and Rive do, so authors' expectations and the Rive reference agree,
694
+ and the rule is exactly reproducible. A timeline key `colorSpace` (e.g. `"oklab"`) is reserved for 0.2.
695
+ (Rive's lossy float round-trip, `docs/research/rive-animation-and-state.md` Lesson A.5, cannot
696
+ happen: values are integers.)
697
+ 3. **Keyboard → `down` — explicit.** `a11y.held` names the bool that is true while Enter or Space is
698
+ held; nothing is inferred from input names. The bell sets `"held": "down"`.
699
+ 4. **Additive layers — deferred to 0.2** (reserved layer key `mode`). 0.1 layers override; the
700
+ resolve rule (§7) passes unwritten properties through, so layers that write disjoint properties
701
+ compose already, which covers a breathing idle on one node under a hover tilt on another.
702
+ 5. **Path morphing** is available through the optional paint module for compatible command lists
703
+ (see the paint chapter). Automatic matching of differing Glyphy traces remains deferred.
704
+ 6. **Data binding.** Input expressions are available in the optional `bind` module below.
705
+ View models and general data sources remain for P4e.
706
+ 7. **Draw-on for Glyphy outlines — deferred to 0.2.** Trims work on authored strokes in 0.1. For
707
+ glyph ink the likely 0.2 design is a `reveal` mask: an authored centreline subpath, stroked wider
708
+ than the ink and trimmed, used as a mask so the visible ink stays the exact outline
709
+ (`docs/research/glyphy-compat.md` §4.5a). It needs a Glyphy centreline export to be practical.
710
+
711
+ Other decisions made while freezing 0.1:
712
+
713
+ - **Clocks advance before transitions** (§7). The draft evaluated transitions first, so a finished
714
+ timeline could report `settled` while its `@end` transition was still pending.
715
+ - **Transitions chain within a step** (cap 16), with time carried over from `@end`/`@after`, so one
716
+ large `dt` and many small ones reach the same state (Rive allows chains too, capped at 100;
717
+ `rive-animation-and-state.md` §2.3).
718
+ - **The crossfade source is always a frozen snapshot** of the layer's output at the moment of the
719
+ transition, not a still-playing timeline. One rule covers both normal transitions and
720
+ interruptions, and only one timeline is sampled per layer.
721
+ - **Triggers are consumed per layer** and dropped at the end of the step unless the input declares
722
+ `buffer` (Lesson A.11).
723
+ - **`hit`** is a top-level circle or rect for whole-icon listeners (bell: the Rive file's hit disc,
724
+ radius 10.8 around the glyph centre).
725
+ - **Exact version match** before 1.0, rather than minor-version feature negotiation.
726
+ - **Cap and join default to `round`**, matching the Glyphy reference sets rather than SVG's `butt`.
727
+
728
+ ## paint
729
+
730
+ The optional `paint` module (MODULES.md) extends 0.1 documents with `requires: ["paint"]`.
731
+ An unregistered module is `unknown-feature` at `/requires/<index>`, before structural checks.
732
+ Using its keys or animation addresses without declaring it is `undeclared-feature` at that key
733
+ or address, with hint `add "paint" to "requires"`. Ordinary 0.1 documents keep their behaviour.
734
+
735
+ ### Image nodes
736
+
737
+ With `requires: ["paint", "assets"]`, an image node has `id`, `type: "image"`, `asset`,
738
+ `x0`, `y0`, positive `width` and `height`, and optional `fit`, `align`, `sampling`.
739
+ It accepts the common transforms, opacity and visibility. Width and height are animatable
740
+ (nonnegative resolved values); layout options and the asset name are static.
741
+ `unknown-asset` at the node's `/asset` names an absent declaration; `asset-type` at that
742
+ pointer rejects a declaration whose type is not `image`.
743
+
744
+ Let the box be W×H and the intrinsic size I×J. The image is scaled and clipped to the box:
745
+
746
+ | fit | scale (x and y unless specified) |
747
+ |---|---|
748
+ | `contain` (default) | min(W/I, H/J) |
749
+ | `cover` | max(W/I, H/J) |
750
+ | `fill`, `resize` | x=W/I, y=H/J |
751
+ | `none` | 1 |
752
+ | `scale-down` | min(1, W/I, H/J) |
753
+ | `fit-width` | W/I |
754
+ | `fit-height` | H/J |
755
+
756
+ `align` is `center` (default), `top-left`, `top`, `top-right`, `left`, `right`,
757
+ `bottom-left`, `bottom`, or `bottom-right`. Each axis places the scaled image at 0,
758
+ (box−image)/2, or box−image. Overflow is always clipped, including `none`.
759
+ The fitted image rectangle has geometric edge coverage even at fractional device coordinates;
760
+ nearest sampling selects texels without snapping that rectangle to whole pixels.
761
+ `sampling` is `bilinear` (default) or `nearest`; SVG maps these to `image-rendering="auto"`
762
+ and `image-rendering="pixelated"`. The browser's final display scaling remains host-controlled.
763
+
764
+ AniML: `<image id="photo" asset="photo" x0="0" y0="0" width="24" height="24"
765
+ fit="contain" align="center" sampling="bilinear"/>`. `x0`/`y0` specify the untransformed
766
+ box origin; common `x`/`y` remain transform translations. `fit` and `align` deliberately keep
767
+ their JSON spellings: SVG `preserveAspectRatio="xMidYMid meet"` maps to contain/center,
768
+ `slice` to cover and `none` to fill. No `preserveAspectRatio` input alias exists, since SVG
769
+ cannot express all seven fit modes. Backends receive `Geometry.kind: "image"`, box coordinates,
770
+ and renderer-neutral image metadata (asset, source, intrinsic size, fit, align, sampling).
771
+ SVG uses a nested clipped viewport and an image leaf; transforms stay on the node's group.
772
+ The host resolves missing intrinsic dimensions before rendering; core draw data keeps them absent.
773
+
774
+ ### Image wrapping (P5b)
775
+
776
+ `wrap` is `clamp`, `repeat` or `mirror`. `wrapX`/`wrapY` (AniML `wrap-x`/`wrap-y`)
777
+ override their axis independently; an axis without its own value takes `wrap`, and `wrap` defaults to
778
+ `clamp` once any wrap control is authored. An image with **no** wrap control draws only its fitted
779
+ source rectangle, as before P5b (decided for compatibility: existing contain/none renders and their
780
+ conformance cases are unchanged). Fit and alignment first establish the fitted source rectangle.
781
+ Clamp extends the edge texels outside the fitted source rectangle to the box boundary. Repeat
782
+ tiles it in both directions; mirror reflects every odd tile, including negative tile indices.
783
+ Width/height animation
784
+ recomputes fit and pattern extent. Nearest clamp extends isolated edge texels on both backends,
785
+ including mixed periodic/clamped axes and centred alignment. `resize` is the `fill` alias: both axes stretch independently to the box.
786
+ The optional browser entry `@anima-format/web/paint-sampling` imports paint and installs SVG
787
+ wrapping. The ordinary paint entry declines explicit wrap controls with `unsupported-render` and an import
788
+ hint; it does not bundle pattern code. CLI/static SVG includes the backend. SVG uses user-space patterns and transformed groups for
789
+ reflected tiles, keeping transforms off
790
+ image leaves. Canvas uses equivalent source tiles. Filtering remains nearest/bilinear as above;
791
+ SVG declines bilinear `repeat` with `unsupported-render` at the repeating axis, a reason about
792
+ independent image filtering, and a nearest/canvas hint. A native UV-mesh probe measured maximum
793
+ channel delta 188 and 36,852/57,600 pixels above 16 before declining this combination (see
794
+ modules/assets/probes/native-measurements.json). Nearest repeat and mirror pass crisp native probes.
795
+ SVG also declines bilinear clamp when the fitted source leaves margins: stretching a clipped
796
+ texel would interpolate adjacent colours rather than hold the edge colour. This reports
797
+ `unsupported-render` at `/nodes/<index>/sampling` with a nearest/canvas hint. Canvas isolates and
798
+ pads texels before filtering; it implements bilinear clamp and mixed-axis wrapping.
799
+ GPU/rasterizer edge differences are not a claim of bit-identical rendering. Invalid wrap enums
800
+ use the existing `bad-value` structural diagnostic at the authored property. No new player state is
801
+ introduced. Both source syntax spellings and deterministic width tracks have conformance probes.
802
+
803
+ ### Gradients
804
+
805
+ A `fill` or `stroke` contains either `color` or a gradient, never both. A gradient has `type`:
806
+
807
+ | Type | Required coordinates (grid units) | Optional coordinates |
808
+ |---|---|---|
809
+ | `linear` | `x1`, `y1`, `x2`, `y2` (start and end) | — |
810
+ | `angular` | `x1`, `y1`, `x2`, `y2` (centre and initial ray) | — |
811
+ | `radial` | `cx`, `cy`, `r` (centre and radius, `r ≥ 0`) | `fx`, `fy` (focal point, each defaults to the corresponding resolved centre coordinate) |
812
+
813
+ All have `stops`, an array of at least two `{ position, color }` objects. `position` is 0..1,
814
+ nondecreasing at rest; repeated positions are permitted (hard edges). `color` follows §6, including
815
+ slots, `currentColor` and hex alpha. Fill rule and stroke width/cap/join/trim/dashes remain on the
816
+ paint object. Coordinates belonging to the other gradient type are errors.
817
+
818
+ Animatable addresses are `<id>.<fill|stroke>.<coordinate>`,
819
+ `<id>.<fill|stroke>.stops.<index>.position` and
820
+ `<id>.<fill|stroke>.stops.<index>.color`. Indices are zero-based decimal integers without leading
821
+ zeros and must exist. Coordinates must belong to the authored gradient; optional focal coordinates
822
+ may be animated even when omitted, with their rest defaults equal to the authored centre.
823
+ `type`, the stop array's length/order and the dash array are not animatable.
824
+
825
+ Coordinates and positions use §5 number sampling and §7 blending; positions clamp to 0..1 and
826
+ radius to ≥0 after resolving. Stop colours use the existing non-premultiplied sRGB channel sampling
827
+ (including alpha), blending, dirty slots and hex-only animation restriction (§6). A static slot stop
828
+ remains themeable. In SVG, stop positions that cross during animation are displayed at the maximum
829
+ of that position and all earlier positions; sampled values retain their independently clamped values.
830
+
831
+ Gradients render as `<linearGradient>` / `<radialGradient>` defs, unique per node, paint and icon,
832
+ with `gradientUnits="userSpaceOnUse"`, sRGB colour interpolation and SVG's default `spreadMethod`
833
+ (`pad`). A paint references its def. Coordinates are in the node's untransformed grid space;
834
+ the node transform carries the paint with its geometry. Degenerate gradients and focal points
835
+ outside the radius follow SVG gradient semantics. Knockout geometry and hit tests stay as in §4/§9;
836
+ they do not acquire gradient alpha sampling.
837
+
838
+ ### Angular gradients and affine gradient transforms
839
+
840
+ `type: "angular"` uses `(x1,y1)` as its centre and the ray to `(x2,y2)` as
841
+ position zero. Positions increase clockwise in SVG's y-down local coordinates over one full
842
+ turn. Coincident endpoints use the positive x axis. Stops pad to the first/last colour and
843
+ may repeat; the seam joins position one back to zero. Coordinates/stops animate like linear
844
+ gradients. AniML uses `<angularGradient x1="…" y1="…" x2="…" y2="…">` with `<stop>` children.
845
+ Canvas uses `createConicGradient`. SVG refuses angular gradient resources with
846
+ `unsupported-render`, a paint `/type` pointer and a hint to use canvas; it never substitutes
847
+ linear/radial interpolation or a finite wedge approximation.
848
+
849
+ Every gradient accepts static `gradientTransform` (AniML `gradient-transform`), six finite
850
+ numbers `[a,b,c,d,e,f]`. It maps gradient coordinates by
851
+ `(x,y) → (a*x+c*y+e,b*x+d*y+f)` before the node transform, affecting paint only.
852
+ Translations are grid units; linear coefficients are dimensionless (rotation coefficients
853
+ are cosine/sine of the desired angle). A singular matrix is `bad-value` at
854
+ `/gradientTransform`, with an invertibility hint. Non-gradient paints reject the field.
855
+ SVG emits `gradientTransform="matrix(…)"`; canvas paints the transformed gradient into a layer masked by the independently drawn
856
+ geometry, preserving stroke widths under nonuniform matrices. The matrix is static and theme slots still resolve normally.
857
+
858
+ ### Dashes
859
+
860
+ `stroke.dash` is a nonempty array, all nonnegative grid-unit numbers (`[2, 1]`), or all decimal
861
+ percentage strings (`["10%", "5%"]`). Percentage syntax is `0` or a positive decimal integer,
862
+ optionally followed by a decimal fraction, then `%`; signs and exponents are not accepted.
863
+ A percentage refers to the entire outline length, **not** the SVG viewport diagonal. Units cannot
864
+ be mixed, and at least one length must be positive. Odd arrays repeat twice (SVG behaviour).
865
+ The array is static. Each subpath restarts the pattern as in SVG.
866
+
867
+ `stroke.dashOffset` is an animatable number, default 0, positive or negative. With numeric dashes
868
+ it is in grid units; with percentage dashes it is a fraction of total path length (0.1 = 10%).
869
+ It uses the existing number sampling and blending. Every backend resolves relative patterns to
870
+ natural local outline lengths using the core length algorithm below. Live and static SVG emit
871
+ absolute dasharray and dashoffset values, without `pathLength` normalization: browser engines'
872
+ internal primitive-length approximations must not change authored dash or trim lengths.
873
+
874
+ **Trim wins:** when resolved `trimEnd − trimStart < 1`, §4's trim dasharray and offset replace the
875
+ authored dashes and offset. A nonpositive trim suppresses the stroke entirely. A full trim permits
876
+ the authored pattern again, using its own units. Animating the offset still samples normally while
877
+ a trim overrides its display. With no dash array, `dashOffset` has no visual effect.
878
+
879
+ ### Diagnostics and implementation
880
+
881
+ Registry structural errors retain §11.3 codes (`type`, `bad-value`, `missing-key`, `unknown-key`).
882
+ Missing coordinates/stops/paint kind are `missing-key` at the paint object with an add-key hint.
883
+ Unsorted stops, mixed/all-zero dashes, incompatible coordinates and flat colour plus gradient are
884
+ `bad-value` at the offending key with a corrective hint where possible. Invalid stop colours use
885
+ `bad-color` / `unknown-color-slot`; animated slot stops use `color-slot-animated`. Invalid addresses
886
+ are `unknown-address`, and static paint fields are `not-animatable`.
887
+
888
+ Module algorithms are independently implemented against SVG semantics. The registry approach
889
+ continues ARCHITECTURE §core.1 and the ideas recorded in
890
+ [format and graph research](../docs/research/rive-format-and-graph.md); the scene keeps the retained
891
+ DOM/dirty-node strategy in [web runtime research](../docs/research/rive-web-runtime.md).
892
+ No algorithm here closely follows Rive runtime source; no additional NOTICE attribution is needed.
893
+
894
+ ### Parametric outlines
895
+
896
+ The paint module adds painted ellipse (cx,cy,rx,ry), polygon (cx,cy,sides,radius,
897
+ rotation=0,cornerRadius=0), star (cx,cy,points,radius,innerRadius,cornerRadius=0),
898
+ and triangle (x0,y0,width,height). All listed numeric parameters are animatable;
899
+ all lengths use local grid units. AniML element and attribute names equal these names.
900
+ Radii and dimensions are nonnegative. Authored sides are integers 3..1024 and points
901
+ integers 2..1024; sampled counts clamp to these bounds and round to nearest integer,
902
+ ties upward. Polygon vertices advance clockwise from the top, rotated by rotation
903
+ degrees. Star vertices alternate outer/inner radii clockwise from the top outer vertex.
904
+ Triangle vertices are (x0+width/2,y0), (x0+width,y0+height), (x0,y0+height).
905
+ Ellipse paths use two half-ellipse SVG arcs; zero axes produce SVG degenerate outlines.
906
+
907
+ Polygon/star rounding cuts back cornerRadius on both incident edges, independently
908
+ limited to half each edge's length, then joins the cut points with a quadratic Bézier
909
+ controlled by the original vertex. Straight edges connect the corners. The parameter
910
+ is a cutback distance, rather than an osculating circle radius; concave vertices use
911
+ that same construction. All new shapes render as SVG paths. Hit tests use the current
912
+ outline and the existing 12-chord cubic flattening convention, including morphs.
913
+ Invalid geometry uses registry bad-value diagnostics at the parameter/item; missing
914
+ required geometry uses missing-key. New types without paint use undeclared-feature at type.
915
+
916
+ ### Rectangle corner radii
917
+
918
+ The small optional `corners` module also accepts this geometry without importing `paint`.
919
+ Declare either `paint` or `corners` (registry feature `paint|corners`); existing paint scenes
920
+ keep the same geometry and addresses. The browser entry is `@anima-format/web/corners`.
921
+
922
+ Rect corners is a four-item nonnegative array [tl,tr,br,bl], AniML corners="1 1 0 0".
923
+ Each value is animatable as tile.corners.0 through .3 (indices without leading zeros).
924
+ The rectangle renders as a path with circular quarter arcs approximated by cubic Béziers,
925
+ using k = 4(√2−1)/3 ≈ 0.5522847498. Each radius is independently clamped to half
926
+ the adjacent width/height; unequal clamped axes produce quarter ellipses. At each corner,
927
+ the two controls move k of the distance from each edge endpoint toward the corner vertex.
928
+ Polygon/star rounding keeps the quadratic construction above. This is an independent
929
+ standard circle approximation; no external runtime algorithm is ported.
930
+ Corners takes precedence over rx; rx remains unchanged without corners. An absent
931
+ corners array cannot be animated. Invalid addresses use unknown-address; invalid array
932
+ length or negative entries use bad-value at the array or offending item.
933
+
934
+ ### Compatible path morphs
935
+
936
+ The paint-module animation address pathId.d accepts SVG path strings, including when
937
+ rest geometry comes from a glyph and selected subpaths. Keys contain path data, not
938
+ glyph identifiers; compatible glyph data may be used verbatim. Static d needs no module.
939
+ Rest, all keys and all pose values require identical expanded command lists:
940
+ same case, letters and repetition count. Implicit coordinate repetitions expand; extra moveto pairs become
941
+ lineto. No reversal, resampling or automatic matching occurs. Numeric arguments
942
+ interpolate with eased weight, including relative coordinates and arc parameters.
943
+ Arc flags choose 0 below 0.5, otherwise 1. State blending uses the same rule.
944
+ Intermediate strings use the SVG formatter's five decimal places; exact endpoint
945
+ samples retain their authored strings. Hit tests follow the sampled outline.
946
+
947
+ A state pose may set pathId.d to an SVG path string, using the same validation and
948
+ interpolation as tracks. Invalid paths produce bad-path at the key or pose value with a
949
+ valid-path hint; non-strings produce value-type. Incompatible commands produce morph-mismatch at the key or pose value,
950
+ with a hint naming the first differing zero-based segment and expected/actual command
951
+ (or end). Every key and pose value is checked against rest, keeping state blends compatible.
952
+ AniML encloses path values in JSON double quotes, for example:
953
+ <track target="a.d">0 "M0 0L2 2"; 1 "M0 0L4 4"</track>.
954
+ In a pose attribute, use the path directly: <pose a.d="M0 0L4 4"/>.
955
+ The compiler, formatter and decompiler preserve spaces inside path values.
956
+
957
+ ### Transform-independent stroke
958
+
959
+ Stroke scale is normal (default) or fixed, AniML stroke-scale="fixed". It is static.
960
+ Fixed emits SVG vector-effect="non-scaling-stroke": the numeric width passes unchanged
961
+ while node and ancestor scale, rotation and shear transform the outline without
962
+ scaling its stroke. The host SVG viewport follows SVG vector-effect semantics,
963
+ including viewport resizing. This is not an inverse adjustment by one scalar for
964
+ nonuniform transforms. Geometry coordinates and sampled width stay unchanged.
965
+ Invalid scale values produce bad-value at the stroke scale field.
966
+
967
+ ### Shared outline lengths
968
+
969
+ All renderers use natural local outline lengths for relative dashes and trims: both
970
+ dasharray components and dashoffset multiply by core's length before output rounding.
971
+ SVG omits `pathLength`. The `toSVG` option `staticLengths` remains accepted for compatibility;
972
+ absolute lengths are now unconditional, including live retained SVG and default `toSVG`.
973
+ Measurements are cached per retained geometry owner; path/radius/dimension/corner changes
974
+ invalidate the measurement, while transforms, colours and trim/offset changes reuse it.
975
+ Lengths include all subpaths and closing edges. Circles use 2πr; rectangles account for their
976
+ clamped elliptical corners; other outlines use current path data, including morphs.
977
+
978
+ Core pathLength parses into absolute lines/cubics, then recursively subdivides cubics
979
+ by de Casteljau at t=0.5 until control polygon length minus chord is at most 0.0001
980
+ grid units per accepted piece, or depth 20. Accepted pieces contribute the mean of
981
+ polygon and chord length. Arc conversion first uses at most 90° cubic pieces (§3),
982
+ so this tolerance describes subdivision flatness, not the initial arc approximation.
983
+ These are independent constructions, extending the shared scene and registry designs
984
+ in the format/graph and web-runtime research cited above. No algorithm follows Rive
985
+ runtime source; no additional NOTICE attribution is required.
986
+
987
+
988
+ ## composite
989
+
990
+ The optional `composite` module extends 0.1 documents with `requires: ["composite"]`.
991
+ It owns blend modes, source masks, geometry clips, ordered paints/paint visibility,
992
+ stroke placement and sibling draw order. Gradients, dashes, additional geometry,
993
+ corners, morphs and fixed strokes belong to `paint`; using both sets requires
994
+ `requires: ["paint", "composite"]` (AniML `requires="paint composite"`).
995
+ An unavailable module is `unknown-feature`; using an undeclared feature is
996
+ `undeclared-feature` at the offending key/address with an add-feature hint (§11.3).
997
+ Composite alone supports ordinary 0.1 geometry and solid paints.
998
+
999
+ ### Blend modes
1000
+
1001
+ `node.blend` (AniML `mix-blend-mode`) selects normal (default), multiply, screen, overlay,
1002
+ darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue,
1003
+ saturation, color or luminosity. It applies CSS `mix-blend-mode` to the node's group, compositing
1004
+ against earlier siblings using SVG/CSS semantics. Blend is static. Additive is deferred: SVG
1005
+ renderers do not consistently implement CSS `plus-lighter`; source-over is not a faithful substitute.
1006
+
1007
+ ### Source masks
1008
+
1009
+ `mask: { source, mode, sourceDraws }` names a node, selects alpha, luminance, inverted-alpha or
1010
+ inverted-luminance, and explicitly controls whether the source also draws in its ordinary place.
1011
+ Any false `sourceDraws` use hides that source's ordinary drawing for the document's life.
1012
+ The source subtree is shared through a retained definition and SVG `use`; its geometry, local transforms,
1013
+ opacity and visibility animate normally. Its ancestors contribute coordinate transforms, but not
1014
+ ancestor opacity or visibility. Mask space is the target's parent space. The target transform
1015
+ stays inside a permanent, untransformed mask wrapper. Masks cover three viewBoxes, as knockouts do.
1016
+ Luminance uses SVG luminance masking. Inversion uses an SVG filter with `feComponentTransfer`
1017
+ alpha table `1 0`; inverted luminance first uses `feColorMatrix type="luminanceToAlpha"`, and
1018
+ then multiplies that alpha by `SourceGraphic` alpha with `feComposite operator="in"` before
1019
+ inverting it and using alpha masking. This preserves source opacity and antialias coverage,
1020
+ which `luminanceToAlpha` alone discards. Filter interpolation is sRGB. Source drawing is independent of hiding
1021
+ its ordinary copy. Unknown sources, self references and cycles are load errors.
1022
+
1023
+ ### Geometry clips
1024
+
1025
+ `clip: { source, rule? }` names a painted node's outline; optional `rule` is `nonzero`
1026
+ (default) or `evenodd`. A painted node or a group may take a clip. A group clip applies
1027
+ to its entire child drawing, including nested groups; it does not change the source outline.
1028
+ Fill, stroke, visibility, opacity and source compositing do not affect the clip. Source geometry and
1029
+ world transforms animate; coordinates are projected into the target parent space. The SVG
1030
+ `clipPath` contains an untransformed path (curves transformed control-point by control-point).
1031
+ This avoids nonportable nested groups in clip paths while preserving the Firefox retained-leaf
1032
+ rules. Singular target parent transforms yield an empty clip. SVG arcs are converted to cubic
1033
+ curves by Anima's existing parser. A group source is `clip-source-not-painted`.
1034
+
1035
+ ### Ordered paints and visibility
1036
+
1037
+ A painted node may replace single `fill` and `stroke` with a nonempty `paints` array. Each entry
1038
+ has exactly one `fill` or `stroke` object, using the same flat/gradient/width/dash fields; later
1039
+ entries paint over earlier entries. `visible` on any paint defaults true and is hold-only
1040
+ animatable. Addresses are `<id>.paints.<index>.<fill|stroke>.<field>`, including gradient stops.
1041
+ Indices must exist, and array structure is static. Single paint visibility addresses are
1042
+ `<id>.fill.visible` and `<id>.stroke.visible`. Multiple strokes currently require center position;
1043
+ noncenter positions on multiple paints are rejected. Paints and single fill/stroke cannot coexist.
1044
+
1045
+ ### Stroke placement
1046
+
1047
+ Single `stroke.position` is center (default), inside or outside. Center uses the original width.
1048
+ Inside doubles the width and clips the stroke to the shape's filled outline. Outside doubles the
1049
+ width and luminance-masks it by a white viewport with a black filled outline. Fill is drawn once,
1050
+ before the stroke. Definition geometry samples the same animated dimensions as the visible shape.
1051
+ Masks stay attached for the document's life. Open contours use SVG's implicit closing for the
1052
+ filled outline; fill rules apply to both clipping and exclusion. Position is static.
1053
+
1054
+ ### Sibling draw order
1055
+
1056
+ `drawOrder: { target, position, active? }` moves a node before or after a named sibling when
1057
+ active (default true). `drawOrder.active` is an ordinary hold-only boolean animation address;
1058
+ changing it reapplies rules to authored sibling order. Rules run in authored node order, once
1059
+ per frame, removing then inserting their node relative to the target. Multiple rules therefore
1060
+ have deterministic precedence. Cyclic target chains and nonsibling targets are rejected.
1061
+ Rules involving knockout siblings are rejected because knockout permanently groups earlier nodes.
1062
+ Static SVG serializes the current order; web reorders existing DOM siblings without replacing
1063
+ nodes or detaching their masks. The module's draw hook resolves sibling ranks before rendering.
1064
+ For models using `drawOrder`, composite supplies `Player.paintRank(node)`: the node's index
1065
+ in a pre-order tree walk whose child lists use those resolved sibling ranks, cached per player
1066
+ until `drawRevision` changes. Without `drawOrder` the hook is absent and node indices give the
1067
+ same authored pre-order walk. Input hit ordering, opaque blocking and pointer focus use this
1068
+ same key: **foremost** means latest painted. A nested player retains its local paint order
1069
+ inside its host instance's rank.
1070
+ Its registry factory binds to the active runtime's shared registry (MODULES §3.1).
1071
+ Cross-node draw changes conservatively invalidate dependent draw entries, keeping animated
1072
+ source references correct across node and ancestor boundaries.
1073
+
1074
+ ### Compositing diagnostics
1075
+
1076
+ `unknown-source` points at mask/clip source or draw target; `self-reference` and `reference-cycle`
1077
+ point at the referencing object. `clip-source-not-painted`, `bad-draw-target`, and `paint-conflict`
1078
+ identify incompatible sources, ordering and paint combinations. Each includes a corrective hint.
1079
+ Structural validation retains the ordinary registry diagnostics and JSON pointers. These features
1080
+ are independently implemented from SVG semantics; no Rive code or algorithms were used.
1081
+ The explicit references and retained ordering follow the lessons in
1082
+ [format/graph research](../docs/research/rive-format-and-graph.md) §6; the sleeping renderer remains
1083
+ as described in [web research](../docs/research/rive-web-runtime.md).
1084
+
1085
+ ## bind
1086
+
1087
+ Bind expressions include numeric comparisons `< <= > >=`, typed equality `== !=`, and boolean
1088
+ `and or not`. Ordering requires number operands; equality requires identical operand types.
1089
+ Logical operands must be bool. Precedence from low to high is or, and, not, comparison,
1090
+ addition/subtraction, multiplication/division, unary signs. Evaluation short-circuits logical
1091
+ operators and produces bool, coercible to 0/1 for number destinations. These rules apply to
1092
+ bindings, set actions and derive expressions. Guard scalar expressions use the same parser when
1093
+ bind is declared; trigger and clock guard terms retain their specialized semantics.
1094
+
1095
+ The optional `bind` module requires `requires: ["bind"]`. At document level,
1096
+ `bind: { "ring.stroke.trimEnd": "progress" }` maps animatable addresses to expressions.
1097
+ Property bindings evaluate after all state layers resolve and before property clamping (§7),
1098
+ so a binding overrides a pose, timeline or crossfade on the same address. Resolved bound
1099
+ addresses appear in `Player.values()`, `anima sample` snapshots, dirty slots and `anima inspect`
1100
+ (the `bound` list). Inputs still apply in queued arrival order. A settled binding sleeps until
1101
+ an input write wakes the player; its value changes on the next step, including `step(0)`.
1102
+
1103
+ A motion play state may also use `time: "expression"`; see **Declarative timeline control**.
1104
+ These are clock expressions evaluated before sampling, rather than property bindings.
1105
+
1106
+ ### Expressions and types
1107
+
1108
+ Sources are declared scalar input names (including `$` data paths when `data` is declared),
1109
+ finite JSON number literals, hex colours
1110
+ (`#rgb`, `#rrggbb`, `#rrggbbaa`), parentheses, unary `+`/`-`, binary `+ - * /`, and the functions
1111
+ `min(a,b)`, `max(a,b)`, `clamp(x,lo,hi)`, `mix(a,b,t)`, `floor(x)`, `mod(a,b)`,
1112
+ `contains(text,part)`, `lower(text)`. Double-quoted strings, typed comparisons and `and or not` use the grammar above.
1113
+ No scene property reads, boolean literals, assignments or arbitrary calls are allowed. Identifiers use
1114
+ the input-name grammar; a hyphen is part of a name, so write `level - other` for subtraction
1115
+ between inputs. Multiplication/division precede addition/subtraction; binary operators associate
1116
+ left; unary signs have highest precedence. Whitespace is insignificant between tokens.
1117
+
1118
+ A bare bool input retains its bool type for boolean targets. Numeric operations and number
1119
+ targets convert bools to 0/1. Numbers do not implicitly become bools. `min`, `max`, `clamp` and
1120
+ numeric `mix` return numbers; `mix` also accepts two hex colours and a numeric/bool weight,
1121
+ returning a colour. Mixing a number and colour is invalid. Colour interpolation is §5 sRGB
1122
+ channel interpolation (including alpha, rounding and channel clamping); weights may extrapolate.
1123
+ `floor(x)` returns the greatest integer ≤ x. `mod(a,b)` is `a - b*floor(a/b)`
1124
+ (the result has the divisor's sign; a zero divisor resolves to zero). Numeric `mix` is
1125
+ `a + (b-a)*t`; `clamp` is `min(max(x,lo),hi)`. Non-finite arithmetic results
1126
+ (including division by zero) resolve to zero when consumed by another operation or the target,
1127
+ before property clamping. Trigger inputs cannot
1128
+ be values. Evaluators accept a scope map of named inputs, reusable for component input bindings.
1129
+
1130
+ `contains(text, part)` requires two strings and returns bool: true exactly when `part` is a
1131
+ contiguous substring of `text` (case-sensitive ECMAScript `String.prototype.includes`, UTF-16
1132
+ code units). An empty `part` returns true, including for empty `text`.
1133
+ `lower(text)` requires one string and returns a string using Unicode default case conversion
1134
+ (ECMAScript `String.prototype.toLowerCase`, locale-independent). It does not normalize Unicode,
1135
+ trim whitespace or perform full case folding: for example, `lower("İ")` is `"i\u0307"`, and
1136
+ `lower("ẞ")` is `"ß"`, not `"ss"`. Use `contains(lower($label), lower($query))` for
1137
+ case-insensitive filtering. No number/bool/color-to-string coercion occurs. Both calls work in
1138
+ bindings, set actions, derive expressions and scalar guards through the same typed parser.
1139
+ A standalone bool call in a guard uses the existing parenthesized scalar form, e.g.
1140
+ `(contains(lower($label), lower($query)))`.
1141
+
1142
+ ### Validation
1143
+
1144
+ `bind-type` rejects incompatible operands or target types; `unknown-input` rejects unknown names.
1145
+ `bind-syntax` rejects malformed expressions, unknown calls, wrong arity or limits (512 tokens,
1146
+ 64 nested expressions). Diagnostics point to `/bind/<escaped-address>`, include the 1-based
1147
+ character offset in the expression message, and give a corrective hint. Parser nodes/errors
1148
+ retain zero-based UTF-16 offsets. `bind-cycle` rejects attempted cyclic property references,
1149
+ including self references. Valid expressions read only inputs and cannot form property cycles;
1150
+ acyclic property references are also rejected. Targets use the ordinary `unknown-address`,
1151
+ `not-animatable` and `undeclared-feature` diagnostics. The old object-valued `from/map` form is
1152
+ not supported. `anima schema bind` describes both spellings from the registry.
1153
+
1154
+ The grammar and evaluator are independently written. Strict unknown-name checks and a single
1155
+ post-state pass follow the lessons about silent unresolved bindings and feedback loops in
1156
+ [animation/state research](../docs/research/rive-animation-and-state.md) §C. No Rive source,
1157
+ documentation text or binary format was used.
1158
+
1159
+ <!-- capability: data.bind-listener-actions -->
1160
+ ### Input expressions in actions
1161
+
1162
+ Declare `bind`. Every action set (listener, bind machine block, or machine-module action group)
1163
+ uses this single expression/type/snapshot contract. Listener `set` values may be expression strings, e.g.
1164
+ `{"set":{"count":"count+1","previous":"count"}}`. Numeric/bool JSON literals keep their
1165
+ 0.1 meaning and type checks; a string is always an expression, never an implicit literal.
1166
+ Number targets accept numeric or bool expressions (bool becomes 0/1); bool targets require bool
1167
+ expressions. Colour expressions remain valid for colour properties and typed event fields;
1168
+ there are no colour inputs, so setting a bool/number input to a colour is `bind-type`.
1169
+ Unknown inputs are `unknown-input`; assigning a trigger is `input-type` (use `fire` instead).
1170
+ Trigger reads and incompatible result types are `bind-type`; parser/arity errors are
1171
+ `bind-syntax`. Diagnostics point to the set value, condition, or event field, with an expression
1172
+ character offset where applicable and a hint. No property reads or assignments are added to the
1173
+ expression language. No runtime dependencies or evaluator copies are introduced: the optional
1174
+ bind module supplies a compile hook to core, returning pure input-scope closures. Merely importing
1175
+ bind does not enable expressions in a document without `requires: ["bind"]`.
1176
+
1177
+ A listener is queued intact. When its action runs at §7.1, evaluate every set expression against
1178
+ one input snapshot **before any write in that action**, then apply `fire`, all sets (clamping each
1179
+ number result to its target min/max), `toggle`, and `emit`. Non-finite arithmetic becomes zero
1180
+ before clamping. Separate actions run sequentially and see prior writes, including host writes
1181
+ queued before them. Queuing two increments before one step increments twice.
1182
+
1183
+ The existing core machine also accepts state `entry` and `exit` blocks and transition `actions`
1184
+ blocks. Each is an object with the same optional `fire`, `set`, `toggle`, `emit` keys as listeners;
1185
+ State `entry`/`exit` blocks require bind even if their values are literals. Transition `actions`
1186
+ requires either bind or machine; without machine it follows the immediate order below, while
1187
+ with machine it follows that chapter's queued order. Bind blocks target local input names; scoped child writes remain a listener/host feature.
1188
+ On initial construction, run each
1189
+ initial state's entry in layer order before its entry emit and timeline events. On a transition,
1190
+ run source exit then transition actions before the existing state-end/transition-start signals;
1191
+ run target entry after the immediate transition-end signal (if any), before target emit and entry
1192
+ timeline events. Each block is a separate snapshot action; writes are immediate and visible to
1193
+ subsequent blocks, transitions in the chain, and later layers. Exit runs on reentry too; blend
1194
+ completion does not rerun actions. Existing chain limits and trigger consumption apply. Pending
1195
+ triggers fired from entry actions are checked during the next step; triggers fired in a step
1196
+ remain subject to that step's ordinary consumption/expiry rule.
1197
+
1198
+ AniML listeners use `set="count=count+1 previous=count"`; spaces and commas within expressions
1199
+ are allowed. Whitespace followed by an input name and `=` begins the next assignment. Machine
1200
+ blocks are JSON attributes, e.g. `<state name="idle" entry='{"set":{"count":0}}'>` and
1201
+ `<on when="tap" to="playing" actions='{"set":{"count":"count+1"}}'/>`.
1202
+ Arithmetic conditions use `when="count > 0 and mod(count, 5) == 0"`. `anima schema listener`,
1203
+ `state`, `transition` and `--json-schema` expose these forms. Input-only scopes avoid the
1204
+ feedback loops described in [animation/state research](../docs/research/rive-animation-and-state.md)
1205
+ §C; snapshot action maps avoid depending on JSON member order.
1206
+
1207
+ ## assets
1208
+
1209
+ The optional `assets` module adds an `assets` map and image draw data. Image nodes also
1210
+ require `paint`. Core validation and `Player.step` perform no IO. Fonts remain declarations until P6;
1211
+ audio/blob consumers are optional host modules.
1212
+
1213
+ Each named asset is `{ type, source, storage?, width?, height? }`. `type` is `image`, `font`,
1214
+ `audio` or `blob`. `source` is required and nonempty: a data URI or URL (relative URLs are
1215
+ relative to the document). Optional `storage` is `embedded` for data URIs or `referenced`
1216
+ for URLs; omitted storage is inferred. A disagreement is `asset-storage` at `/assets/<name>/storage`.
1217
+ `width` and `height`, when present, are positive intrinsic dimensions, in grid units for
1218
+ layout. Omitted dimensions are decoded by the host before drawing. Declared dimensions override
1219
+ decoded dimensions independently. Decoded pixel counts remain separate host resource information;
1220
+ authored width/height change layout, never the texel count used for clamped edge cropping.
1221
+ Browser and CLI decoders supply these counts without adding a document field. Data URIs use percent encoding or padded standard base64;
1222
+ malformed encoding is `bad-data-uri` at the source. An empty source is `missing-source`;
1223
+ an absent source is `missing-key` at the asset object. Messages carry a repair hint.
1224
+
1225
+ AniML declares `<asset name="photo" type="image" src="photo.png" storage="referenced"/>`
1226
+ at document or component level; registry `source` maps to `src`. All other field spellings match JSON.
1227
+ A component resolves asset names in its own catalog, then its enclosing component/document catalogs
1228
+ up to the root. A local declaration shadows the same outer name, even if its type is wrong.
1229
+ This scope applies to image nodes, image/audio `asset-ref` values and event audio clips. Missing or
1230
+ wrong-kind references report the offending node, event or data property/value JSON pointer;
1231
+ default data asset errors point to `/data/models/<model>/properties/<property>/default`.
1232
+ Image hit targets use their current rectangular bounds, including animated width/height.
1233
+
1234
+ The browser module `@anima-format/web/assets` registers assets and the SVG image backend;
1235
+ image documents also import `@anima-format/web/paint`. Images decode before `anima:load` and
1236
+ initial `ready` resolution. URLs resolve relative to the icon's `src`, or `document.baseURI`
1237
+ for inline/programmatic documents. `icon.assets = (name, info) => url | Blob | Promise<url | Blob>`
1238
+ replaces the source before decode; `info` is a copy of the authored declaration. A resolver rejection
1239
+ or image decode failure empties the icon and dispatches `anima:error` with `asset-load`, the
1240
+ source pointer, message and hint. Embedded data URIs require no network. Resolved data is private
1241
+ to the loaded model; source declarations are preserved. Superseded loads cannot install their scene.
1242
+ Audio/blob byte access and policy are specified below; fonts remain declarations.
1243
+
1244
+ `anima build --embed` embeds local referenced assets (paths relative to the source file), sets
1245
+ storage to `embedded`, and visits root and component-local catalogs, including imported libraries.
1246
+ Imported sources resolve relative to the file declaring that asset; diagnostics keep the complete
1247
+ component asset pointer. The built file's embedded assets work after the source tree is removed. It supports AniML and
1248
+ AniML scripts in HTML. Already embedded assets are unchanged. Other URL schemes cannot be read
1249
+ by this disk build and report `asset-load`; HTTP asset fetching is a browser host responsibility.
1250
+ `anima render` embeds local image sources and decodes intrinsic sizes through resvg before
1251
+ rendering; other asset types remain declarations. IO/decode failures report `asset-load` at
1252
+ `/assets`, with a message and hint. Build inspection and encoding are specified below.
1253
+
1254
+
1255
+ ### P5b asset pipeline
1256
+
1257
+ Asset declarations also accept `size` (nonnegative integer bytes), `mime` (MIME string),
1258
+ `metadata` (a JSON object, AniML `metadata='{"credit":"self"}'`), and the build-only
1259
+ `encode`, `quality` and `export` options. Resolver metadata includes every authored field;
1260
+ its copy is detached, including nested metadata. Source declarations are never mutated by host IO.
1261
+ `encode` is `png`, `jpeg` or `webp`; `quality` is in [0,1] and requires `encode`.
1262
+ These two options are image-only. PNG ignores quality; JPEG/WebP pass it to the browser encoder.
1263
+ Audio-only `start` and `end` are clip offsets in seconds; `duration` is the full source duration.
1264
+ Omitted start is zero and omitted end is decoded duration. Require `0 <= start < end <= duration`
1265
+ when the relevant bounds are known. Wrong-type options are `asset-option` at the option;
1266
+ invalid bounds are `asset-clip` at `/assets/<name>/start` or `/end`. Both carry repair hints.
1267
+ All fields have identical JSON/AniML names except `source` → `src`.
1268
+
1269
+ `icon.assetPolicy` is `local` (default) or `any`; other assignments throw RangeError.
1270
+ Without a custom resolver, local policy permits data URIs and same-origin HTTP(S)/blob URLs,
1271
+ relative to the document source URL. Any permits other origins, retaining the protocol allowlist.
1272
+ A custom `icon.assets(name, info)` resolver explicitly authorizes its returned URL or Blob;
1273
+ relative returned URLs still resolve against the document source URL. This policy applies to
1274
+ image loading and event audio; browser CORS and media permissions still apply. Failed policy,
1275
+ network or image decoding emits `asset-load` at the source with a resolver/policy repair hint.
1276
+ Changing policy affects subsequent loads, not already decoded resources.
1277
+
1278
+ Import `@anima-format/web/media` for lazy audio/blob byte access; it imports assets and contributes
1279
+ no new document fields. `icon.asset(name)` returns a promise of ArrayBuffer for audio/blob and
1280
+ source URL for images/fonts. Byte loads use the same resolver/policy, cache by model and asset,
1281
+ retry after a failure, and emit `asset-load` before rejecting with its diagnostics. Unknown names
1282
+ reject with RangeError. Audio decoding remains the consumer's responsibility; events uses Web
1283
+ Audio on demand after activation, as specified in events. Clip events start at `start` and play
1284
+ for `end-start`; invalid decoded bounds emit `event-audio-load`. Neither policy nor media IO changes
1285
+ Player.step output. The assets entry imports a separately budgeted `assetio` host-IO entry automatically, and preloads
1286
+ images in reachable component catalogs as well as the root. Keeping media in its own entry follows the optional consumer principle in
1287
+ `docs/research/rive-web-runtime.md` §1; no native code or document format is copied.
1288
+
1289
+ Build inspects local and embedded PNG/JPEG/WebP/WAV/MP3/FLAC signatures, dimensions or duration.
1290
+ Extension/MIME/signature disagreement or wrong media kind is `asset-format` at the asset source;
1291
+ read/truncation/decode errors are `asset-load`. Metadata `size`, `mime` and audio `duration` are
1292
+ written from inspected bytes; clip bounds are checked against inspected duration. PNG/JPEG pixels
1293
+ are validated with resvg, WebP with cached Chromium. Audio inspection validates container/frame
1294
+ headers, not complete audio decoding; host decoding remains authoritative. Disk build never fetches
1295
+ network URLs. Blob payloads remain opaque: their bytes are not decoded or container-validated,
1296
+ MIME comes from the declaration or URL hint (otherwise application/octet-stream), size is the
1297
+ byte length, and audio-only duration is never attached. Build embedding is byte-preserving without `encode`. PNG encoding runs in-process;
1298
+ WebP input needs Chromium to decode because resvg cannot decode WebP. JPEG/WebP encoding uses the
1299
+ cached headless Chromium canvas. A command owns one lazy browser shared by all images and HTML
1300
+ scripts; each operation closes its page and the command closes its browser on success or failure.
1301
+ PNG-only commands do not launch a browser unless they need WebP input decoding. Missing/unlaunchable Chromium is `build-encoder-unavailable` at
1302
+ `/assets/<name>/encode` (or `/source` for WebP validation), hinting `CHROMIUM_PATH`.
1303
+ `export=false` excludes bytes from embedding/encoding/inspection, preserving the source declaration
1304
+ and resolver name so references remain valid; the host supplies that source. An already embedded
1305
+ source becomes `anima:<name>` with referenced storage, so its bytes are actually excluded; this
1306
+ placeholder requires a resolver. It does not delete
1307
+ referenced names from the asset map. Build transformations apply equally to AniML and HTML scripts.
1308
+
1309
+ `anima import file.svg [-o out.animl]` imports paths, rects, circles, ellipses, lines/polygons,
1310
+ groups, inherited fills/strokes (hex, basic named and RGB/RGBA colours), user-space linear/radial gradients, and translate/scale/rotate
1311
+ or nonskew affine matrix transforms. Transform lists become nested groups to preserve order.
1312
+ Unsupported elements/attributes/paint servers/transforms emit `import-unsupported` warnings with
1313
+ SVG element/attribute paths. Unsupported geometry is omitted with a warning. The import never
1314
+ fetches external references. `anima import file.json` accepts Lottie shape layers, groups, paths,
1315
+ static rects/ellipses, fills/strokes, transforms and scalar/vector/path keyframes with hold or
1316
+ cubic easing; stroke trim paths map to normalized trim fractions. Layer order reverses to preserve
1317
+ Lottie's back-to-front order. Unsupported fields/features (including images, expressions, masks,
1318
+ effects, skew, spatial curves, partial layer time ranges, animated anchors/primitive sizes and
1319
+ paint opacity) warn at their JSON pointer. These warnings describe lossy imports, not exact parity.
1320
+ Malformed XML/JSON or invalid import roots/clocks report `import-invalid` at the empty root
1321
+ pointer with a source-format repair hint. All imported documents are validated before output. JSON command output includes diagnostics and
1322
+ text or output path. The importer is independent of Rive.
1323
+
1324
+ ## components
1325
+
1326
+ The optional `components` module requires `requires: ["components"]`. A document's `components`
1327
+ map holds named nested documents: `grid`, optional `viewBox`, `inputs`, `nodes`, `timelines`,
1328
+ `machine`, `listeners`, `colors`, `glyphs`, `a11y`, `hit` and `bind`. Definitions inherit the enclosing
1329
+ `requires` list. References to components resolve in the enclosing document's component map,
1330
+ including from other definitions; all definitions, even unused ones, are validated. Each placement
1331
+ owns a separate player; compiled definitions and static geometry may be shared.
1332
+ Component definition keys and literal references allow colon-separated name segments. Compiled
1333
+ library documents retain their stable lookup aliases in `meta.componentNames`, a map from qualified
1334
+ import name to definition key. Exact keys take precedence; an original name resolves when unique.
1335
+ Ambiguous original names report `ambiguous-component` at the reference, with qualified alternatives
1336
+ in the verbose hint. Legacy generated keys remain valid; a missing `import-<number>-<name>` lookup
1337
+ resolves by its original suffix when unique and reports the same ambiguity otherwise. See
1338
+ [the AniML naming scheme](ANIML.md#11-libraries-and-templates-build-time).
1339
+
1340
+ An instance node has `{id, type: "instance", component, inputs?, speed?, paused?}` and the ordinary
1341
+ node transform, opacity and visibility properties. `component` names a definition. `inputs` maps its
1342
+ bool/number inputs to literals or strings `{expression}`. Expressions use the bind grammar and
1343
+ require `bind`; their scope is the parent's applied inputs. Triggers cannot be initialized with
1344
+ values. Omitted inputs retain their component defaults. Literal initializers apply once, before
1345
+ subsequent queued writes. Number inputs clamp to their declared bounds.
1346
+
1347
+ Component inputs must not use any key in the instance node's registry shape. This includes `id`,
1348
+ `type`, `component`, `inputs`, transform keys, `opacity`, `visible`, `speed`, `paused`, and keys
1349
+ installed by other node modules such as `blend`, `mask` and `clip`. Those names retain their plain
1350
+ instance attributes and `<instanceId>.<property>` addresses. `reserved-input-name` points at the
1351
+ input declaration (`/components/<name>/inputs/<input>`, with source line/column in AniML), with the
1352
+ hint `rename the input; <name> is an instance property (e.g. use pos-x)`. The reserved set is derived
1353
+ from the active registry's instance shape, including module keys; it is not a separate hand list.
1354
+
1355
+ ### Stepping, writes and events
1356
+
1357
+ After §7 step 5, the parent evaluates instance input expressions, then resolved parent addresses
1358
+ that name instance inputs, and steps children in node order with `dt * speed` (`speed` defaults to
1359
+ 1, must be finite and ≥0). Each child's own state speeds multiply this speed. A `paused` instance
1360
+ (default false) retains its clocks and queued input actions without stepping. An invisible instance
1361
+ whose player is settled and has no queued writes is not stepped. Invisible running players finish
1362
+ normally. Reduced-motion policy propagates from the parent before stepping.
1363
+
1364
+ A parent's actions and host API may address `<instanceId>.<input>` in `set`, `toggle` and `fire`.
1365
+ Bindings, poses and tracks may target a bool/number instance input at the same address; explicit
1366
+ resolved addresses override inline instance expressions. Writes reach the child before its step.
1367
+ Nested action addresses continue with dots (`outer.inner.count`). Invalid inputs/types are load
1368
+ errors, just like local inputs.
1369
+
1370
+ Pointers are transformed through the inverse instance world matrix and routed to each child
1371
+ player. Invisible or singular instances receive `leave`. The child's own hit areas and listeners
1372
+ then operate in local grid units; parent listener targets on instances use their local viewBox.
1373
+ Instances do not establish a viewport clip or automatic fit by default; the optional `nested`
1374
+ module adds explicit fit/align into size. Use composite clipping when a viewport clip is needed.
1375
+
1376
+ A child's events are returned by that parent step as `<instanceId>.<event>`; nested names continue
1377
+ with dots. Event layers use slash scope (`outer/inner/layer`), retaining null for listener events.
1378
+ The parent queues the bubbled event as a one-step trigger and queues matching parent listeners
1379
+ (`on: "badge.selected"`). Those actions and parent transition conditions (`when: "badge.selected"`)
1380
+ are applied on the **next parent step**, including `step(0)`. Child events follow the parent's own
1381
+ events in deterministic instance order. Pending bubbled events/actions keep the parent unsettled.
1382
+ This single pass prevents event feedback from rerunning the state machine in one step. A parent is
1383
+ settled only when its own machine and all unpaused children are settled and no actions are queued.
1384
+
1385
+ ### Drawing and inspection
1386
+
1387
+ An instance contributes a renderer-neutral group with its local transform and the child's draw
1388
+ tree as children. Draw entries have scoped ids (`badge/dot`, `outer/inner/dot`); resources and
1389
+ source references are remapped into the enclosing draw tree. SVG uses scoped ids under the host's
1390
+ unique id prefix. Clip, mask, blend and paint behavior use the ordinary draw/backend contract.
1391
+
1392
+ `Player.states()` and `inspect()` include slash-scoped child layers. `Player.values()` and sample
1393
+ snapshots include child inputs (`badge/count`) and animated values (`badge/dot.r`), recursively;
1394
+ selected snapshots accept those same scoped addresses. `anima inspect` lists placed instances and
1395
+ their initial states. The playground's state panel shows live instance states.
1396
+
1397
+ ### Limits and diagnostics
1398
+
1399
+ No recursive definitions are allowed: direct/indirect cycles are `component-cycle`. Expanded
1400
+ placement depth is at most 8 and the total number of instances is at most 256, including repeated
1401
+ nested placements (`limit-exceeded`). Ordinary per-document node/key/layer limits still apply to
1402
+ each definition. `unknown-component` points at the instance's component name; `unknown-input` and
1403
+ `input-type` point at bad input initializers/actions. Bind expressions retain `bind-syntax`,
1404
+ `bind-type` and `unknown-input`. `unknown-event` rejects unresolved listener event names;
1405
+ `component-event-collision` rejects a component input and event sharing a scoped address.
1406
+ Diagnostics retain JSON pointers and corrective hints; errors inside definitions point under
1407
+ `/components/<name>`. No partially valid instance tree escapes a failed load.
1408
+
1409
+ The optional `nested` module adds explicit time/fps, fit/align, scalar outputs and component-valued
1410
+ selection. Its normative contract is [modules/nested/SPEC.md](../modules/nested/SPEC.md).
1411
+
1412
+ This implementation is independent. Explicit names, validation before references are used and
1413
+ retained nested transforms follow [graph research](../docs/research/rive-format-and-graph.md) §6;
1414
+ a single ordered update pass avoids the feedback loops discussed in
1415
+ [state research](../docs/research/rive-animation-and-state.md) §C. Sleep behavior follows
1416
+ [web runtime research](../docs/research/rive-web-runtime.md), with no separate instance scheduler.
1417
+
1418
+ ## surface
1419
+
1420
+ The optional `surface` module extends `paint` and `assets` with image paints, patch slicing and
1421
+ paint feathering. The independent algorithms below follow SVG semantics, not Rive source.
1422
+ The split keeps paint (5120 B), composite (4096 B) and assets (2048 B) within their existing
1423
+ budgets; surface has an 8192 B incremental budget. Browser import `@anima-format/web/surface`
1424
+ also registers paint; assets and components remain separate imports. This follows the optional
1425
+ backend and sleeping-renderer lessons in [web research](../docs/research/rive-web-runtime.md) §13
1426
+ and the renderer-neutral contract in [MODULES §3.1](MODULES.md#31-the-draw-layer).
1427
+
1428
+ ### Image paints
1429
+
1430
+ A fill or stroke with `type: "image"` requires `asset`, `imageWidth`, `imageHeight`; the last two
1431
+ are strictly positive tile sizes in grid units. It also needs `assets` in `requires` and a declared
1432
+ image asset. It replaces `color`, gradient coordinates and `stops`. Usual stroke width, cap, join,
1433
+ dash, trim, visibility and ordered-paint semantics still apply. Optional `imageX`, `imageY`
1434
+ (default 0), `imageRotate` (degrees, default 0), `imageScaleX`, `imageScaleY` (default 1) transform
1435
+ the tile in local coordinates: scale, then rotate about the origin, then translate. Static zero
1436
+ scales are load errors. Transform fields are animatable. The SVG backend uses a repeating
1437
+ `pattern` with `patternUnits="userSpaceOnUse"`, `patternTransform`, and an image stretched to the
1438
+ specified tile size. Intrinsic aspect ratio does not affect image paints.
1439
+
1440
+ Optional `modulate` is a literal hex colour. It multiplies non-premultiplied sRGB R, G, B and alpha
1441
+ channels independently by the colour channels divided by 255; SVG uses an sRGB `feColorMatrix`
1442
+ on the image inside the pattern. It is static. Defaults to identity. No slot modulation is defined.
1443
+ Draw data contains the resolved URL, tile dimensions, transform and normalized modulation channels.
1444
+ AniML uses `<imagePaint asset="tile" image-width="8" image-height="8" paint="stroke"/>`
1445
+ inside the painted node (`paint` defaults to fill); the registry supplies both spellings.
1446
+ See `examples/image-paints.animl` and `spec/draw-conformance/surface-image/`.
1447
+
1448
+ `unknown-asset`, `asset-type`, `undeclared-feature`, `missing-key`, `paint-conflict`, `bad-color`
1449
+ and `bad-value` point at the affected paint/field and give a corrective hint. Image-specific
1450
+ fields on another paint type are errors. Sources resolve through the same assets mechanism as
1451
+ image nodes. No IO occurs during stepping.
1452
+
1453
+ ### N-slice patches
1454
+
1455
+ `slice: { x, y, width, height, modes? }` on an image, group or component instance defines a source
1456
+ rectangle using the first and last entries of strictly increasing `x`/`y` cut arrays (2–9 entries
1457
+ per axis, grid units, including both bounds). There are at most 64 patches. `width`/`height` are
1458
+ positive target sizes. `modes` has one entry per patch in row-major order: `stretch`, `repeat` or
1459
+ `hidden`; omission means stretch everywhere. Slice structure and target size are static.
1460
+
1461
+ For four or more cuts on an axis, the first and last bands keep their authored lengths. Interior
1462
+ bands divide the remaining target length in proportion to their source lengths. If the target
1463
+ is smaller than the two border lengths, borders shrink proportionally and all interior bands
1464
+ collapse to zero. Two or three cuts divide the entire target proportionally, since there is no
1465
+ interior band bounded by two distinct border bands. Target origin is the first source cut for
1466
+ groups/instances, or the image's `x0`,`y0` for images. Image source cuts must begin at zero and end
1467
+ at the asset's declared intrinsic width/height; those dimensions are required. Slicing replaces
1468
+ image fit/alignment and node width/height layout. Node transforms apply to the resulting drawing.
1469
+
1470
+ Stretch maps each source rectangle to its target rectangle, independently on each axis. Repeat
1471
+ tiles that rectangle at its original grid size, from the target's upper-left corner, clipping the
1472
+ last partial tile; it does not scale either axis. Hidden contributes no pixels. SVG implements
1473
+ patches using clipped nested SVG viewports and patterns referencing a shared source drawing.
1474
+
1475
+ For vector groups and components the source is the live rendered local subtree, clipped at each
1476
+ cut. Each patch scales the *whole* source appearance, including strokes, filters and compositing.
1477
+ This is rendered-output slicing, not path deformation: geometry, hit testing, component inputs,
1478
+ state and pointer coordinates remain in source space. Child animations remain live. Fixed-width
1479
+ strokes are fixed in the source drawing before patch scaling. True geometry deformation and
1480
+ inverse patch pointer mapping are deferred; duplicating stateful component players per patch
1481
+ would change document semantics. The resolved draw data lists source/target rectangles and modes.
1482
+
1483
+ Diagnostics: `slice-cuts` for unordered cuts; `slice-limit` beyond 9 cuts; `slice-modes` for a wrong
1484
+ mode count; `slice-bounds` for image intrinsic-bound mismatch; `slice-type` for an unsupported node
1485
+ kind. Each points into `slice` and includes a hint. Structural errors use ordinary registry codes.
1486
+ `spec/draw-conformance/surface-slice/` records the 9-patch result, including repeat and hidden patches.
1487
+ AniML uses a `<slice x="…" y="…" width="…" height="…" modes="…"/>` node child.
1488
+ Cut lists decode to numbers and modes to strings. See [ANIML](ANIML.md#surface-module) and
1489
+ `examples/slice.animl` for the syntax and round-trip contract.
1490
+
1491
+ ### Feather falloff
1492
+
1493
+ A paint may have `feather: { inner?, outer?, offset?, enabled?, space? }`. Amounts are nonnegative
1494
+ grid distances, default 0. Offset is a two-number local vector, default `[0,0]`; enabled defaults
1495
+ true and disabling preserves all settings. Space defaults `local`. Inner, outer, offset components
1496
+ and enabled are animatable via `fill.feather.inner`, `stroke.feather.enabled`, etc., including
1497
+ ordered paint addresses. Feathering applies independently to each paint, before node opacity.
1498
+
1499
+ Let P be the premultiplied RGBA paint image, A its original alpha, and T the offset translation.
1500
+ Let B(r) be a Gaussian convolution of T(P) with standard deviation r/3, with B(0)=T(P).
1501
+ The output is `A * B(inner) + (1-A) * B(outer)`, componentwise in premultiplied sRGB; alpha is
1502
+ clamped to [0,1]. For a fully covered hard edge with no offset, Gaussian falloff has its midpoint
1503
+ at the edge and about 99.7% of its transition within three standard deviations on either side.
1504
+ This is an explicit Anima Gaussian model, not a claim of pixel parity with Rive's feather model.
1505
+ At zero inner/outer and zero offset the original paint is unchanged.
1506
+
1507
+ SVG uses `feOffset`, two `feGaussianBlur` primitives (`stdDeviation=amount/3`), `feComposite`
1508
+ in/out with SourceAlpha, then an arithmetic sum (`k2=k3=1`), all with sRGB filter interpolation.
1509
+ The filter region expands the document viewBox by three times the greatest amount plus the
1510
+ largest offset component. Root SVG viewport clipping still applies. Separate paint leaves prevent
1511
+ feathering a fill from blurring an unfeathered stroke.
1512
+
1513
+ World-space feathering cannot preserve isotropic falloff through arbitrary local nonuniform
1514
+ transforms in this SVG implementation. `space: "world"` is represented in draw data; when enabled,
1515
+ the SVG backend throws an error carrying `code: "unsupported-render"`, the source JSON pointer,
1516
+ a message and a hint to use local space. Canvas support is deferred to Pc. Disabled world feathering
1517
+ renders normally. Unknown spaces and negative/nonfinite amounts receive registry diagnostics.
1518
+ `spec/draw-conformance/surface-feather/` defines resolved draw data independent of browser rasterization;
1519
+ Chromium/Firefox pixel tests check the Gaussian halo, disabled state and independent stroke.
1520
+ AniML uses `<feather paint="fill|stroke" …/>` node children, or a feather child within an ordered
1521
+ `fillPaint`/`strokePaint`. It retains the parent paint and preserves empty and disabled settings;
1522
+ see [ANIML](ANIML.md#surface-module) and `examples/feather.animl`.
1523
+
1524
+ ## motion
1525
+
1526
+ `requires: ["motion"]` opts into this chapter; AniML uses `requires="motion"`.
1527
+ Loading the module alone does not change a 0.1 document's playback. It adds no renderer,
1528
+ network operations or runtime dependencies. All times and work bounds are seconds.
1529
+ The design follows the independent-playback, explicit composition and unit-consistency
1530
+ lessons in [animation research](../docs/research/rive-animation-and-state.md#1-linear-animations)
1531
+ and the small imperative handle in [web research](../docs/research/rive-web-runtime.md).
1532
+ No Rive code is used. The elastic formulas below are reflections of Anima's existing
1533
+ specified elastic-out formula, not an implementation of Rive's amplitude ramp or period-zero fallback.
1534
+
1535
+ ### Curves
1536
+
1537
+ <!-- capability: animation.elastic-in -->
1538
+ <!-- capability: animation.elastic-in-out -->
1539
+ `elastic-in(a,p)` is `1 - elastic(a,p)(1-u)`; `elastic-in-out(a,p)` is
1540
+ `elastic-in(a,p)(2u)/2` for `u < 1/2`, otherwise `1/2 + elastic(a,p)(2u-1)/2`.
1541
+ Both require finite `a >= 0`, `p > 0`, and have exact 0/1 endpoints. The same strings
1542
+ work on left keys and transition eases, with the same arguments in both syntaxes.
1543
+ Invalid motion curves report `bad-ease` at the ease; a missing requirement reports
1544
+ `undeclared-feature`, with a hint to add motion.
1545
+
1546
+ <!-- capability: animation.cubic-value -->
1547
+ `value-cubic(x1,y1,x2,y2)` on a **numeric** left key is a value curve rather than a
1548
+ weight. Solve the existing monotone cubic x curve for parameter `s`; evaluate the
1549
+ cubic with ordinate controls `(leftValue,y1,y2,rightValue)`. The time controls are
1550
+ normalized fractions in `[0,1]`; ordinate controls use the property's units. All
1551
+ arguments must be finite. Equal endpoints can therefore move between the keys.
1552
+ Endpoints, holds, before-first and after-last sampling retain §5 rules. A value curve
1553
+ on colours, discrete channels, or transition eases is `bad-ease`; it cannot shape a
1554
+ crossfade. Invalid controls are `bad-ease` with a syntax/range hint. The sampler is
1555
+ compiled per segment; there is no mutable interpolator shared between tracks.
1556
+
1557
+ ### Typed channels and mixing
1558
+
1559
+ <!-- capability: animation.keys-signed-integer -->
1560
+ <!-- capability: animation.keys-unsigned-integer -->
1561
+ <!-- capability: animation.keys-string -->
1562
+ <!-- capability: animation.keys-reference -->
1563
+ A node can declare `channels: { name: { type, value } }`, where `type` is `int`,
1564
+ `uint`, `string`, or `reference`. A channel is addressed `node.channels.name` in
1565
+ tracks and poses. `int` and `uint` hold 32-bit integers (signed or unsigned);
1566
+ `string` holds arbitrary strings, including whitespace and empty strings;
1567
+ `reference` holds an existing node id in this scene. They are typed scene data,
1568
+ readable through node handles; a reference channel does not implicitly swap geometry
1569
+ or a paint. Future consumers must explicitly define what they do with the reference.
1570
+ AniML declares `<channel name="label" type="string" value='"Ready"'/>` inside its
1571
+ node, and uses quoted JSON strings in keyframe text. Numerical channel values use
1572
+ ordinary numbers. A channel value in a pose also uses a quoted JSON string when
1573
+ string-valued. The registry defines both spellings and generates CLI/schema output.
1574
+
1575
+ All four types are hold-only: left value until the exact next key. An explicit ease
1576
+ other than `hold` is `bad-ease`. Invalid integer values are `motion-integer` at the
1577
+ value, with the allowed interval as hint; wrong string values are `value-type`;
1578
+ unresolved references are `motion-reference` with an existing-node hint. Validation
1579
+ covers document values, keyframes and poses. Structural channel/name errors use §11 codes.
1580
+
1581
+ <!-- capability: animation.mix-discrete -->
1582
+ <!-- capability: animation.mix-numeric -->
1583
+ A motion document's discrete channels and bool properties replace the incoming value
1584
+ for **any positive mix**, including at the start of a crossfade. An explicit instance mix of zero writes
1585
+ nothing. Numbers, colours and morphable paths use the existing interpolation rules.
1586
+ An explicit instance writes `mix(incoming, sample, weight)` in composition order;
1587
+ weights are clamped to `[0,1]`. These bool rules apply only to motion documents;
1588
+ ordinary 0.1 bool crossfades still use §7's OR rule. During a state crossfade, a
1589
+ channel absent from the target immediately passes through the value below it.
1590
+
1591
+ ### Timeline clock
1592
+
1593
+ <!-- capability: animation.quantized-playback -->
1594
+ <!-- capability: animation.work-area -->
1595
+ <!-- capability: animation.reverse -->
1596
+ A timeline may declare `speed` (finite, default 1), `quantize` (finite samples/second,
1597
+ strictly positive), and `work: [start,end]` with `0 <= start < end <= duration`.
1598
+ AniML uses attributes `speed`, `quantize`, `work="start end"`. Invalid work bounds
1599
+ report `motion-work` at `/timelines/<name>/work`, with an interval hint. Structural
1600
+ nonpositive quantization or nonfinite numbers use the existing range/type diagnostics.
1601
+
1602
+ A play state's speed multiplies the timeline's speed. Negative speed starts at the
1603
+ work end; positive or zero starts at its start. The source clock is this start plus
1604
+ `stateTime * effectiveSpeed`. Fold that clock into the work interval using §5 loop
1605
+ or pingpong rules, or clamp for once. Quantize only the **sample** time down on the
1606
+ absolute timeline grid (`floor(time*quantize)/quantize`), clamped to work start; an
1607
+ exact work end remains exact. Keys outside the work interval remain valid and can
1608
+ influence interpolation inside it. Events outside it never fire.
1609
+
1610
+ Event crossings use the unquantized source clock. Forward intervals exclude the
1611
+ previous time and include the new one; reverse intervals include the new time and
1612
+ exclude the previous time. On entry include both bounds. Loop and pingpong crossings
1613
+ retain carry, emit each authored event once per crossing, deduplicate pingpong turns,
1614
+ and sort in travel order; coincident events retain authored time/order. Start and end
1615
+ are distinct authored events even when their loop crossings coincide. The existing
1616
+ 10,000-period enumeration bound remains unchanged. A once state finishes after
1617
+ `workSpan / abs(effectiveSpeed)` state seconds. Zero speed holds and has infinite
1618
+ completion time. State-machine zero-speed play states stay busy because transitions
1619
+ may still depend on state time; explicit zero-speed timeline instances can sleep.
1620
+ Reduced end/skip uses the directional work endpoint/start; end reports only the
1621
+ work-area events, in travel order, including both ends. Frozen instances sample the
1622
+ endpoint without looping it back to the start.
1623
+
1624
+ <!-- capability: state-machine.exit-time-seconds -->
1625
+ A transition may add `exit` (nonnegative source-animation seconds) or `exitFraction`
1626
+ (a fraction in `[0,1]` of its work span), never both. AniML uses the same names.
1627
+ This is an additional gate on `when`, evaluated before entering the target. It measures
1628
+ absolute source-animation distance since entry, including loops, before quantization,
1629
+ so state and timeline speeds both matter; it is not wall/state time. It requires a
1630
+ play source and must lie within its work span (`motion-exit`, pointing at the exit,
1631
+ with a source/interval hint). Carry is the state-time overshoot of the gate, capped by
1632
+ any existing time-condition carry; an input-only condition uses the gate's carry.
1633
+ This avoids passing target time that predates the last gate becoming eligible.
1634
+
1635
+ ### Declarative timeline control
1636
+
1637
+ These forms require `motion`; omitted fields preserve existing playback, including all 0.1
1638
+ behaviour. AniML uses the JSON names verbatim. They extend §7's clock/transition passes,
1639
+ not the post-state property-binding pass.
1640
+
1641
+ - A transition's `sync: true` transfers the **unquantized local time** from source to target,
1642
+ instead of restarting at the directional start. Both states must play the same timeline
1643
+ (`motion-sync` at `/machine/layers/<layer>/states/<state>/on/<index>/sync`, with a same-timeline
1644
+ hint). Work-area folding/clamping happens before transfer; quantization happens only at sample.
1645
+ The target uses its own speed from that time. A once target completes after its remaining
1646
+ directional distance, so `speed: -1` reverses from the current position and entering the forward
1647
+ state again turns around immediately. State time and fade elapsed still reset to carry (§7);
1648
+ interruptible fades still freeze the current output as their source. Sync does not replay
1649
+ timeline entry events at the transfer point; events crossed during target carry are reported.
1650
+ - A play state's `hold: true` freezes its clock at the entry time (directional start normally,
1651
+ transferred local time with `sync`). It continues writing the same sampled tracks. State time,
1652
+ `@after`, input conditions and crossfades keep running. Holding does not itself satisfy `@end`.
1653
+ A held state can settle once its fade and future `@after` waits finish. `hold` and `time` are
1654
+ exclusive; either on a pose reports `motion-control` at the state (conflict at `/time`), with
1655
+ a hint to choose a play clock.
1656
+ - A transition's `exitNext: "step" | "loop"` waits for the next clock boundary while its `when`
1657
+ remains true. At the start of §7 step 2, after queued inputs apply, arm the gate from the
1658
+ source clock **before advancing dt**. `step` uses the next local quantize grid point in the
1659
+ direction of travel, or the work endpoint if earlier; without quantize it uses the work
1660
+ endpoint. `loop` uses the next work endpoint (each turning point for pingpong). A request
1661
+ observed exactly on a boundary is immediately eligible (using the §5 clock-boundary rule).
1662
+ Changing the condition to false cancels the armed gate; a later request
1663
+ computes a new boundary. Frozen reduced-motion and zero-speed clocks are immediately eligible.
1664
+ The source clock and its timeline event interval stop at that boundary, even for a large dt;
1665
+ excess state time carries into the target (and its fade). A synced held target therefore
1666
+ captures the boundary, not the overshooting frame. Transition priority and the 16-transition
1667
+ cap stay as in §7. Locked fades do not arm gates. `exitNext` requires an autonomous play
1668
+ source (no hold/time) and persistent bool/number terms only; it cannot combine with triggers,
1669
+ `@end`, `@after`, `exit` or `exitFraction` (`motion-exit` at `/exitNext`, with an autonomous-source
1670
+ and persistent-condition hint).
1671
+ - A play state's `time: "expression"` additionally requires `bind`. It evaluates the bind
1672
+ grammar against this player's inputs, producing local timeline seconds clamped to its work
1673
+ area. It owns the clock instead of advancing it; speed has no effect. It samples with the
1674
+ timeline's loop and quantize rules (a loop's end folds to its start). An ordinary entry
1675
+ evaluates the expression immediately. A synced entry keeps the transferred time and records
1676
+ the current expression value; the next **different evaluated value** seeks to that value.
1677
+ Thus completion can hold its angle until the author changes `phase`, without a snap to a
1678
+ stale phase on entry. A change that evaluates to the same clamped value is not a seek.
1679
+ Expressions update after inputs apply and before transitions/sample, including `step(0)`;
1680
+ they also initialize the constructor's initial state. Seeking emits no timeline events,
1681
+ does not complete `@end`, and can settle like a held clock. Errors use bind's existing codes
1682
+ at the state's `/time`; missing `bind` reports `undeclared-feature` there with an add-bind hint.
1683
+
1684
+ Held clocks report state `emit`s, but do not synthesize timeline events on entry or while held.
1685
+ The pre-existing `exit`/`exitFraction` gates retain their elapsed-state-time × absolute effective
1686
+ speed measurement; they do not observe seeks or the captured clock offset. `exitNext` instead
1687
+ observes the selected autonomous clock and is restricted to that clock kind above.
1688
+
1689
+ Reduced-motion entry policy takes precedence over these clock controls, using the same
1690
+ fixed directional endpoints and event policy as other motion play states. Time/hold states
1691
+ still obey that frozen entry pose. Ordinary property bindings retain their post-state priority.
1692
+ Named machines and component players own independent control clocks and input scopes.
1693
+
1694
+ Decision: put clock selection on states and clock handoff on transitions, keeping inputs as the
1695
+ single listener interface. This follows the separation of state time, animation time and fade
1696
+ snapshots discussed in [animation/state research](../docs/research/rive-animation-and-state.md)
1697
+ and the sleep-when-settled lesson in [web runtime research](../docs/research/rive-web-runtime.md).
1698
+ No animation is cloned and no property-read feedback is added to bind expressions.
1699
+
1700
+ ### Animated transform origin
1701
+
1702
+ <!-- capability: transforms.origin-animation -->
1703
+ `pivotX` and `pivotY` are numeric animatable node properties, with the same AniML names.
1704
+ Each falls back to the corresponding component of the existing `pivot` vector, then
1705
+ zero. They feed exactly the transform in §4, so drawing, world matrices and hit testing
1706
+ use the same animated origin. The vector `pivot` remains a static convenience;
1707
+ tracks address its independent scalar coordinates. Existing bind expressions may
1708
+ also target those coordinates when bind and motion are both required.
1709
+
1710
+ ### Explicit runtime instances
1711
+
1712
+ <!-- capability: animation.host-loop-override -->
1713
+ <!-- capability: animation.host-play-pause-stop -->
1714
+ <!-- capability: animation.seek -->
1715
+ <!-- capability: host-api.animation-metadata -->
1716
+ <!-- capability: host-api.low-level-animation -->
1717
+ <!-- capability: host-api.low-level-scene-query -->
1718
+ <!-- capability: host-api.multi-state-machine -->
1719
+ <!-- capability: host-api.scrub -->
1720
+ The optional module installs the following API on `Player` and `<anima-icon>`;
1721
+ see [WEB](../docs/WEB.md#motion-module) for signatures and host scheduling.
1722
+ Missing motion requirements reject calls; absent names and invalid finite-number
1723
+ arguments throw. No implicit timeline instance is created by document timelines.
1724
+ A default `timeline(name)` is created on first use; `createTimeline(name,{name,mix,
1725
+ speed,loop})` creates an independent clock. Instance names are unique across timelines
1726
+ and machines. Instances compose after ordinary layers and bindings, before clamping,
1727
+ in **creation order**. They restart from the values below them on every step; there
1728
+ is no accumulating weighted drift. Each named machine resolves its layers against the values below its instance, including
1729
+ return-to-underlying crossfades. Only the properties actually written by a named
1730
+ machine's active/fading states and bindings contribute, never all of its rest slots.
1731
+
1732
+ `seek(seconds)` clamps into the work area without emitting crossed events or changing
1733
+ play/pause; `scrub(progress)` clamps progress to `[0,1]`, seeks over that area, and
1734
+ pauses. A sought loop end folds to the start when sampled; reduced frozen endpoints
1735
+ are the exception above. `play` retains the clock, `pause` retains its writes, and
1736
+ `stop` resets to the directional start and removes its writes. Setting `loop` overrides
1737
+ the authored mode; null restores it. Negative instance speed reverses from the current
1738
+ clock. `advance(dt)` moves just this clock and returns crossed events; `apply(mix?)`
1739
+ enables its writes. Values resolve on the next `Player.step`, including `step(0)`;
1740
+ explicit manual advance does not dispatch DOM events. `didLoop` is reset on advance
1741
+ and indicates that this advance crossed a work boundary (including once completion).
1742
+ Changing metadata returned by `info` or `animations` cannot change the model.
1743
+
1744
+ `machines: { name: { layers } }` declares named machine definitions; AniML uses
1745
+ `<machine name="name"><layer ...>...</layer></machine>`. `machine(name)` instantiates
1746
+ one lazily with independent inputs and clocks over the shared scene. Its controls
1747
+ support play/pause/stop, mix, set/fire/get and states. Stop removes writes and suspends
1748
+ its state without resetting it; play resumes it. Paused machines hold their writes.
1749
+ Their events use `instance/layer` as layer name. Their states are queried on the
1750
+ instance and also appear as `instance/layer` in the player's states and inspector,
1751
+ so they do not collide with the default machine's layer names. Definitions
1752
+ are validated even before instantiation. `node(id,type?)` returns a typed read handle
1753
+ or undefined for an absent id/type; it reads resolved properties and local/world
1754
+ transforms. It does not fabricate bone or text handles before those node types exist.
1755
+
1756
+ ### Conformance, CLI and oracle limits
1757
+
1758
+ Each capability marker above maps to its same-named `modules/motion/capabilities/`
1759
+ example, test, and `spec/conformance/<hyphenated-id>/` case. Expected scalar values
1760
+ are hand-computed, including 6 for the equal-endpoint cubic midpoint, 1/32 for elastic-in
1761
+ at 1/2 with period 1/2, and sequential numeric mixes. Existing unmarked conformance
1762
+ sources, scripts and expectations are unchanged.
1763
+
1764
+ The §11 script also accepts `timeline <name> play|pause|stop`, `timeline <name>
1765
+ seek|scrub|speed|mix <number>`, `timeline <name> loop once|loop|pingpong|authored`,
1766
+ `instance <id> <timeline> [mix]`, and `machine <name> play|pause|stop|mix|set|fire`
1767
+ (with the input/value arguments for set/fire). Host operations resolve on an explicit
1768
+ `advance`, which can be zero. CLI validate/build/decompile/fmt/schema/inspect/sample/render
1769
+ use the same registry and player; no separate motion evaluator is introduced.
1770
+
1771
+ Native frame oracle mappings and unsupported observations are recorded per capability
1772
+ and in the oracle README. A browser/API test is not evidence of a Rive frame match.
1773
+
1774
+ ## canvas
1775
+
1776
+ <!-- capability: host-api.device-pixel-ratio -->
1777
+ ### Pixel ratio
1778
+
1779
+ The optional web Canvas2D backend reads `window.devicePixelRatio`. The HTML host's `pixel-ratio`
1780
+ attribute overrides it with a finite positive number; `devicePixelRatioUsed` exposes the chosen
1781
+ ratio when this backend is active. Invalid overrides emit `bad-pixel-ratio` at pointer `""` (the
1782
+ host, not a document field), with a message and hint, and fall back to browser DPR. A resolution
1783
+ media query and window resize notification track browser DPR changes, including monitor moves.
1784
+ No document JSON or AniML key, `requires` name, or player property is introduced by canvas.
1785
+
1786
+ <!-- capability: host-api.resize-backing-surface -->
1787
+ ### Backing surface
1788
+
1789
+ Canvas viewport CSS placement follows the SVG host's grid/viewBox bleed, fit and alignment.
1790
+ Backing dimensions are the rounded CSS viewport dimensions times the selected ratio, with a
1791
+ one-pixel minimum. A ResizeObserver resizes and redraws when the host's CSS content size changes.
1792
+ Size or pixel-ratio changes redraw without advancing the player or consuming queued input.
1793
+ Repeated player revisions do not redraw; explicit `invalidate()` redraws once and resolves current
1794
+ CSS colour slots. Playback, reduced motion, scheduler sleeping, visibility and hit outlines remain
1795
+ those of the existing web host. Disconnect/destroy removes observers and DPR listeners; reconnect
1796
+ resumes observation. Canvas contents are clipped to the viewBox viewport; SVG overflow is visible.
1797
+
1798
+ <!-- capability: platforms.web-canvas -->
1799
+ ### Canvas2D rendering
1800
+
1801
+ Import `@anima-format/web/canvas` and set `renderer="canvas"` on `<anima-icon>`. SVG remains the
1802
+ default (`renderer="svg"` or absent). Selection may change on a loaded element without resetting
1803
+ its player. An unavailable renderer emits `unsupported-render`, pointer `""`, with the renderer
1804
+ name and an import hint. Other rendering diagnostics use the source JSON pointer where available.
1805
+ This is a host selection, not a change to `.ani`, AniML, CLI render or schema.
1806
+
1807
+ The canvas backend consumes DrawFrame (§3.1 in MODULES) directly. Path2D geometry/primitives,
1808
+ ordered fills/strokes, colour slots/currentColor, linear/radial gradients, grid-unit dashes/trims,
1809
+ transforms, group opacity, source-relative clips, alpha/inverted/luminance masks, blend modes,
1810
+ images, image patterns/modulation, components, n-slice patches and feathering use Canvas2D.
1811
+ Group opacity and masks isolate into transparent offscreen surfaces; ordinary opaque groups do
1812
+ not. A canvas is a compositing boundary and cannot blend with page content outside itself.
1813
+ Fixed strokes use CSS viewport pixels (as SVG non-scaling-stroke does), without approximating
1814
+ nonuniform node transforms with a scalar inverse. Trims use core outline lengths.
1815
+
1816
+ Feather follows the existing premultiplied Gaussian formula using `ctx.filter` blur and in/out
1817
+ alpha composition. World space keeps the Gaussian isotropic after node transforms. The current
1818
+ backend declines nonuniform/sheared **local** feathering, nonuniform viewport feathering, and
1819
+ partial trims on fixed strokes with `unsupported-render` naming the feature, a pointer, message
1820
+ and hint. It never silently substitutes an inverse average scale. Disabled feathering is ordinary
1821
+ paint. Cross-origin image sources need CORS permission for canvas readback/patterns; failures emit
1822
+ `asset-load`. CSS slot changes on an idle canvas require `invalidate()`.
1823
+
1824
+ Cross-backend pixel differences are measured and retained by the canvas browser conformance suite;
1825
+ limits and findings are in `modules/canvas/test/web/README.md` and `RESULTS.md`. Renderer support
1826
+ is not a claim of exact rasterizer identity or a passing Rive oracle for host-only operations.
1827
+
1828
+ ## focus
1829
+
1830
+ The optional `focus` module declares `requires: ["focus"]`. It adds a static `focus`
1831
+ object to any node: `enabled` (bool, default true), `enabledInput` (optional bool input
1832
+ name), `pointer` (bool, default true), `order` (finite number, default 0), `scope`
1833
+ (bool, default false), `edge` (`stop`, default, `closed-loop`, or `parent-scope`),
1834
+ `label` (string, defaults to node id), `bounds` (local `[x,y,width,height]`, nonnegative
1835
+ sizes; omitted means the local origin), and `tabStops` (bool, default false).
1836
+ A scope container may set `enabled: false` while retaining its scope policy.
1837
+ Unknown enabled inputs report `unknown-input` and non-bool inputs report `input-type`
1838
+ at the node's `/focus/enabledInput`, with a message and corrective hint. Ordinary
1839
+ registry shape/type/range and undeclared-feature diagnostics apply to all fields.
1840
+
1841
+ `Player.focused` is a synchronous, read-only node id or null. This is the hook for
1842
+ machine `condition-focus`: compare the requested id with `player.focused`; no DOM
1843
+ query, step or input write is required. Each player owns independent focus state.
1844
+ `focusTarget(id|null)` changes the target if eligible; unknown/disabled/invisible
1845
+ ids leave it unchanged. `focusClear()` clears it. Both return whether it changed.
1846
+ `focusItems()` returns eligible targets; `focusItems(true)` includes unavailable
1847
+ configured targets for DOM reconciliation. A target needs a focus object, enabled
1848
+ true, enabledInput true when supplied, and visible ancestors (including itself).
1849
+ Opacity does not affect eligibility. Focus lost through a resolved visibility/input
1850
+ change clears after that step and queues blur actions; the step is unsettled until
1851
+ the next step has consumed them.
1852
+
1853
+ `focusTraverse(next|previous|left|right|up|down)` defaults to next. Sequential order
1854
+ is ascending `order`, then document node index. From no focus, forward directions
1855
+ select the first eligible target and backward directions the last. Within a scope,
1856
+ next/previous take the adjacent target. Spatial directions use centers of the local
1857
+ bounds transformed by current world matrices. Candidates strictly ahead on the
1858
+ requested axis are ranked by squared Euclidean distance, then sequential order.
1859
+ The nearest enclosing ancestor with scope true owns the boundary. `stop` retains
1860
+ the target; `closed-loop` wraps sequentially to the opposite end, or spatially to
1861
+ the opposite extreme on that axis (ties use authored node order); `parent-scope`
1862
+ retries in the enclosing scope, including its descendants. The outer icon boundary
1863
+ stops. Traversal returns whether the target changed. Invalid direction names throw.
1864
+
1865
+ Focusable component internals are exposed as dotted instance paths (`save.label`),
1866
+ with each component player's local traversal and scope rules. Root targets precede
1867
+ component targets; instances and their descendants follow authored instance order.
1868
+ Disabling a scope's own focus target does not hide its semantic subtree or disable
1869
+ eligible descendants. Semantic elements remain in the accessibility tree; only
1870
+ nonsemantic focus proxies are hidden when ineligible.
1871
+
1872
+ An instance is an implicit scope: after local traversal stops, its node's focus.edge
1873
+ must be parent-scope to traverse outside it. Nested transforms compose for spatial
1874
+ centers and pointer mapping. Focus changes route to the owning player's local
1875
+ listeners; the parent hook reports the dotted path. Component-local state machines
1876
+ read their local `focused` ids. Instance visibility excludes its focus descendants.
1877
+
1878
+ `focusPointer(x,y)` focuses the foremost eligible target (latest painted) under the point using
1879
+ §9 hit testing, excluding targets with pointer false. Primary pointer down calls it
1880
+ before ordinary pointer listener actions; up, move and leave do not change focus.
1881
+ Ranking uses composite's `Player.paintRank` when present (the pre-order walk with resolved
1882
+ `drawOrder` sibling lists), otherwise the authored node index. Nested players rank locally
1883
+ inside their host instance's paint rank. Opaque blockers use the same key and retain their
1884
+ ancestor chain.
1885
+ Component forwarding does not independently focus other overlapped instances.
1886
+
1887
+ Listeners add `on: "focus"` and `on: "blur"` (requires focus); `target` is a local node
1888
+ id or omitted to observe every local change. Changing targets queues old blur
1889
+ listeners then new focus listeners, each in authored order, with the existing §9
1890
+ action semantics. Re-selecting the same target does nothing. Nested listeners act
1891
+ inside their owning component. These hooks are independent of pointer enter/leave.
1892
+
1893
+ `focusKey(key, down=true, shift=false)` handles ArrowLeft/Right/Up/Down on down;
1894
+ key up is inert. Tab traverses next/previous only with an authored tabStops opt-in.
1895
+ This hook lets the input module share the same deterministic keyboard policy. Input key listeners
1896
+ consume normal keyboard delivery by default (`propagate: true` opts into continued delivery);
1897
+ this does not suppress the subsequent focus navigation hook or native Enter/Space activation.
1898
+
1899
+ ### AniML and browser mapping
1900
+
1901
+ AniML uses a child `<focus enabled="true" enabled-input="available" pointer="true"
1902
+ order="0" scope="true" edge="closed-loop" label="Save" bounds="0 0 4 4"
1903
+ tab-stops="false"/>`. Names otherwise match JSON. Registry descriptors drive both
1904
+ spellings, CLI/schema inspection, compilation and decompilation. Browser markup
1905
+ loads focus and markup; the focus entry supplies structural authoring hooks.
1906
+
1907
+ The browser entry `@anima-format/web/focus` maps logical targets to real focusable
1908
+ shadow-DOM elements, with button roles and labels. Focus is roving by default:
1909
+ Tab enters the host's first eligible target; arrows move within it; Tab/Shift+Tab
1910
+ leave to the surrounding document. Leaving clears logical focus. The icon remains
1911
+ one sequential stop. Any authored focus.tabStops true opts the document into native
1912
+ inner Tab stops (one per eligible target); the host then adds no extra stop. A parent
1913
+ icon's a11y role/label/activate/pressed/held and §10 Enter/Space behavior are retained.
1914
+ Pointer default focus is suppressed after logical pointer focus to prevent it from
1915
+ displacing the chosen shadow target. Explicit pause ignores arrow navigation.
1916
+
1917
+ Focus proxies are semantic targets, not extra painted shapes. The small semantic
1918
+ DOM and shared core input policy follow the guidance in
1919
+ `docs/research/rive-web-runtime.md` (lessons 2 and 6); no Rive implementation is used.
1920
+ Headless pixels alone do not observe focus. Native interaction oracles compare logical focus
1921
+ and traversal through semantics dumps and focus/blur view-model reports. Browser tests separately
1922
+ cover DOM focus and native Tab entry/exit; see `modules/input/ORACLE-RESULTS.md`.
1923
+
1924
+ ## machine
1925
+
1926
+
1927
+ `requires: ["machine"]` adds the fields below to 0.1 documents. Without that requirement,
1928
+ playback is unchanged, even when this module is installed. The implementation extends the
1929
+ base player's playback policy; it does not replace its input queue, transition chain cap,
1930
+ trigger consumption, interpolation, clamping or frame resolution. `motion`, `bind` and
1931
+ `components` order before machine when installed; none is imported as a runtime dependency.
1932
+
1933
+ This design independently implements the graph and deterministic-frame ideas discussed in
1934
+ `docs/research/rive-animation-and-state.md` §§A.6–A.8 and its pitfalls about shared mutable
1935
+ state. No Rive algorithm or source was copied; no additional NOTICE attribution is needed.
1936
+
1937
+ ### State kinds and priority
1938
+
1939
+ States retain exactly one `pose` or `play`. `kind` optionally names `entry`, `exit`, `any`, or
1940
+ `random`; these special states require `pose: {}`. An entry state is an ordinary empty pose
1941
+ whose ordered conditions choose the next state at the first `step`, including `step(0)`.
1942
+ An exit state is terminal and has no `on` list. It retains the outgoing state's own keyed samples at exit time as constant writes; its clock stops. With no outgoing state it writes nothing. Other layers continue, and post-state bindings retain priority.
1943
+
1944
+ An any state is a declaration, cannot be initial or a transition target, and does not run.
1945
+ Its transitions are searched before the current state's transitions, in declaration order of
1946
+ any states and then of their `on` lists. They are available in every nonterminal real state.
1947
+ An any edge targeting the current state is omitted unless it has a trigger or `change` guard,
1948
+ which permits restarting a pop animation on another increase without repeated re-entry.
1949
+ All transitions still count toward the base limit of 16 per layer per step.
1950
+
1951
+ `reset: true` explicitly requests the normal restart on every re-entry. Omission is identical.
1952
+ `reset: false` is rejected (`machine-state`); implicit pause/resume is not specified.
1953
+
1954
+ ### Transitions and previous values
1955
+
1956
+ Transition fields (JSON and AniML names are identical):
1957
+
1958
+ - `disabled: boolean`: keep the edge but never select it.
1959
+ - `exitRatio: number` in [0,1]: gate by this fraction of the source play duration. With motion,
1960
+ use its work span and effective speed. Spill the overshoot into the target, as in §7.
1961
+ - `blendRatio: number` ≥ 0: blend for that multiple of source play duration, including work
1962
+ span and effective speed with motion. Exclusive with `blend`.
1963
+ - `continue: boolean`: default false. True advances the outgoing timeline sampler during the
1964
+ crossfade instead of freezing its exit pose. It does not keep the outgoing state active or
1965
+ eligible for transitions. The target clock and transition fade advance as usual. Continued sources honor autonomous motion speed/work/quantization; held, expression-controlled or sync-entered sources are rejected with `machine-clock`. Outgoing timeline events stop at exit.
1966
+ - `change: {input, mode}`: `mode` is `changed`, `increased` or `decreased`. Compare the input's
1967
+ current applied value with its value at the end of the preceding step (defaults before the
1968
+ first step). `changed` accepts bool or number; ordered modes require number. Queued writes
1969
+ are coalesced by the ordinary input stage: only the final applied value is compared.
1970
+ Advance the previous-value baseline once, after all parent layers and before nested players advance, even if no edge consumed it. Scoped input changes are observed when their component stage makes them available.
1971
+ The same change edge may fire once per layer per step, across any-state and self re-entry;
1972
+ every layer sees the same signal. A locked or disabled edge does not buffer changes.
1973
+
1974
+ `when` remains required. For an otherwise unconditional change edge use `when: "@after(0)"`:
1975
+
1976
+ ```json
1977
+ {"when":"@after(0)","to":"pop","change":{"input":"count","mode":"increased"}}
1978
+ ```
1979
+
1980
+ A single such edge on an any state handles arbitrary counts, including another increment
1981
+ while popping; there is no ladder of numeric states. A zero-duration self-reentry does not
1982
+ produce `@limit`. A held unchanged value never retriggers. Change-gated transitions always enter the target
1983
+ at time zero, including their fade clock: the change is observed in this step, so time spent
1984
+ waiting in the source is not target overshoot, even with `@after`, `@end` or `exitRatio`.
1985
+ For a pulse on every increase, put this edge on an any state, play a once timeline
1986
+ starting at the rest value, and return to idle with `@end`. Another increase during the
1987
+ pulse restarts at its first key; decreases and unchanged values do not restart it.
1988
+ See `modules/machine/examples/blend-pulse.animl`.
1989
+
1990
+ ### Typed live guards and the focus seam
1991
+
1992
+ `guard: {left, op, right}` ANDs another predicate with `when` and `change`. Each operand is a
1993
+ number, bool, or exactly one of `{artboard: "width"|"height"|"aspect"}`, `{component: "id.input"}`,
1994
+ `{focus: "node-id"}`. Artboard dimensions are the document's viewBox dimensions in grid units,
1995
+ not CSS pixels. Component operands read a validated scoped bool/number input through
1996
+ `Player.get`, at the component stage already specified by the components module; they do not
1997
+ read rendered geometry or create layout feedback. Equality accepts matching bool or numeric
1998
+ operands; ordered comparisons require numbers. Operators are `== != < <= > >=`.
1999
+
2000
+ The focus module supplies `player.machineFocus = (nodeId: string) => boolean`, a pure live
2001
+ reader returning whether that document node owns focus. It calls `player.onChange?.()` after
2002
+ changes so a settled machine wakes. No focus owner or no installed reader means false.
2003
+ Machine owns neither DOM focus nor focus traversal, and does not import the focus runtime.
2004
+ A standalone headless host can install the same reader; both equality and inequality work.
2005
+
2006
+ ### Blends and bound data
2007
+
2008
+ A blend is a play state with `mix: {mode, children, axis?, exit?}`. `children` is a nonempty
2009
+ array of `{play: timelineName, at?: number, weight?: source}`. The state's `play` names the
2010
+ child used for `@end`, exitRatio and blendRatio; `exit`, if present, is that child's zero-based
2011
+ index and must agree with `play`. This makes the exit clock explicit even when its weight is 0.
2012
+ Every child samples its own timeline at state elapsed time × state speed, with its own loop
2013
+ mode. With motion installed and declared, each child also honors its own speed, work area and
2014
+ quantization. A blend's primary state clock must be autonomous: hold/time and incoming sync
2015
+ are rejected with `machine-clock`, rather than silently applying one child's clock to the others. A blend remains unsettled while any child timeline can move, including a zero-weight
2016
+ child. Reduced motion is recorded on entry for every child: end/skip freeze at its directional work endpoint, while play children keep advancing. A zero-speed child is at rest. Missing child tracks
2017
+ contribute the addressed property's rest value, never a value from an earlier frame.
2018
+
2019
+ A source is a number, a numeric input name string, or `{bind: "numeric expression"}`. The last
2020
+ form additionally requires `bind` and compiles the existing bind expression language against
2021
+ inputs. It evaluates at sampling time after queued input writes, before the ordinary
2022
+ post-state property bindings. This is P4c's data-source bridge until P4e view models arrive:
2023
+ there is no implicit view model, scene-property reference or binding feedback.
2024
+
2025
+ - `mode: "1d"`: requires `axis` and an `at` on every child, strictly increasing. Clamp the
2026
+ axis outside the first/last threshold to that endpoint. Within a segment interpolate its
2027
+ neighboring child samples. All other children have weight 0. One child is permitted.
2028
+ - `mode: "direct"`: sample each child in authored order and interpolate the accumulated
2029
+ value toward it by its independently clamped [0,1] weight (default 1), starting at rest.
2030
+ Weights are not normalized. A first child at weight 1 establishes a baseline.
2031
+ - `mode: "additive"`: apply the first child as the full baseline, then interpolate
2032
+ toward each later child's sample by its independent weight, in authored order.
2033
+ This is the direct-blend family exposed as Additive Blend in Rive's editor;
2034
+ it combines animations over a baseline, rather than accumulating numeric offsets
2035
+ from previous frames. The first child's authored weight is ignored.
2036
+
2037
+ The same base interpolation rules apply to colours and discrete values. State output still
2038
+ passes through the base crossfade, layer ordering, property bindings and clamping.
2039
+
2040
+ ### Deterministic random selection
2041
+
2042
+ `kind: "random"` selects among eligible outgoing transitions, using nonnegative `weight`
2043
+ (default 1). Ignore disabled, false-condition and zero-weight edges. The first gate evaluation
2044
+ selects once for that search; later probes do not redraw. No eligible edge consumes no random
2045
+ number. A taken edge invalidates that search; another visit can choose again. Trigger names
2046
+ already consumed in this layer/step remain ineligible. Declaration order resolves intervals.
2047
+
2048
+ Every player owns an unsigned 32-bit seed, `machineSeed` at the document root, default
2049
+ `0x6d2b79f5` (1831565813). Before each choice:
2050
+ `seed = (1664525 * seed + 1013904223) mod 2^32`, using integer multiplication;
2051
+ `u = seed / 4294967296`. Choose the first eligible edge whose cumulative weight exceeds
2052
+ `u * sum(weights)`. Without an explicit player/script seed, players, component instances and
2053
+ named-machine players keep independent RNG state. An explicitly seeded root shares its core
2054
+ source with nested stepping in authored order. Identical applied inputs, seed and step sequence produce identical choices. This
2055
+ portable LCG is independent of Rive's unseeded native selection. With an explicit player/script
2056
+ seed, machine and converter draws share one core source in evaluation order; the explicit seed
2057
+ overrides `machineSeed`. Without it, the machine stream and converter stream retain these legacy defaults.
2058
+
2059
+ ### Actions and completion
2060
+
2061
+ States support `enterActions` and `exitActions`; transitions support `actions` and `endActions`.
2062
+ Each is an object with the listener actions `fire`, `set`, `toggle`, `emit`.
2063
+ Sets use the shared **Input expressions in actions** contract (§bind), including expression
2064
+ types, one pre-action snapshot, clamping and sequential action visibility; expression strings
2065
+ require `bind`, while literal machine groups require only `machine`. Order on entry: outgoing exitActions, transition actions,
2066
+ incoming enterActions. When `machine` is declared it owns transition `actions`, so that
2067
+ block queues exactly once instead of also executing as a bind-only immediate block.
2068
+ Bind's separate `entry`/`exit` blocks retain their immediate timing. Queue in that order using the ordinary input queue. Effects become
2069
+ visible at the next step's input stage; emits have `layer: null`, as other queued actions.
2070
+ The initial state's enterActions are queued during player construction and apply on the
2071
+ first step. No action mutates the current transition search's inputs.
2072
+
2073
+ A positive blend's endActions queue when the fade completes at the clock stage. Zero-duration
2074
+ or reduced-motion transitions queue them after entry actions at entry. Overshoot at or beyond
2075
+ blend duration also completes immediately. An interrupted fade discards its endActions.
2076
+ Pending actions prevent settlement through the existing queue; they never run twice.
2077
+
2078
+ ### Syntax, diagnostics and tooling
2079
+
2080
+ Registry entries define JSON Schema, `anima schema`, both syntaxes and formatting. AniML keeps
2081
+ scalar fields as attributes. Object-valued fields (`mix`, `guard`, `change`, each action group)
2082
+ are JSON-valued attributes, with normal XML escaping; single quotes make authored JSON easier:
2083
+ `<on when="@after(0)" to="pop" change='{"input":"count","mode":"increased"}'/>`.
2084
+ `machineSeed` is an attribute of `<anima>`. Build/decompile/format use the same generic
2085
+ non-node registry extension documented in MODULES §3. Browser JSON requires only machine;
2086
+ browser AniML additionally loads markup, with no machine-specific compiler hook.
2087
+
2088
+ Shape errors retain `type`, `missing-key`, `bad-value`, `unknown-key` and declared-feature
2089
+ errors. Semantic errors provide pointer, message and hint:
2090
+
2091
+ | Code | Pointer | Meaning / correction |
2092
+ |---|---|---|
2093
+ | `machine-state` | state `/kind`, `/reset`, `/on` or edge `/to` | Special state needs empty pose; any cannot be initial/target; exit has no edges; reset must be true; random weights must have finite total. |
2094
+ | `machine-blend` | state `/mix`, `/mix/exit`, `/play` | Supply a numeric axis and sorted unique thresholds; choose an existing child exit clock matching play. |
2095
+ | `machine-clock` | edge or `/blendRatio` | Relative timing needs a play source with finite duration (zero ratios remain zero); choose blend or blendRatio. Blend/continued clocks must be autonomous and unsynchronized. |
2096
+ | `machine-guard` | edge `/guard` or operand | Choose one valid source and matching types; ordered comparisons require numbers. |
2097
+ | `unknown-input`, `input-type` | source, action or `/change/input` | Declare the correctly typed input, including scoped component inputs. |
2098
+ | `unknown-timeline` | `/mix/children/i/play` | Name an existing timeline. |
2099
+ | `unknown-target` | guard operand `/focus` | Name an existing document node. |
2100
+ | bind diagnostics | source `/bind` | Correct the numeric expression. |
2101
+
2102
+ The capability directories map rows to examples, validation, independently calculated
2103
+ conformance cases and native-oracle evidence. A documented oracle skip is not native parity.
2104
+
2105
+ <!-- capability: state-machine.any-state -->
2106
+
2107
+ <!-- capability: state-machine.blend-1d-data -->
2108
+
2109
+ <!-- capability: state-machine.blend-1d-input -->
2110
+
2111
+ <!-- capability: state-machine.blend-additive -->
2112
+
2113
+ <!-- capability: state-machine.blend-direct -->
2114
+
2115
+ <!-- capability: state-machine.blend-exit-clock -->
2116
+
2117
+ <!-- capability: state-machine.blend-weight-constant -->
2118
+
2119
+ <!-- capability: state-machine.blend-weight-data -->
2120
+
2121
+ <!-- capability: state-machine.blend-weight-input -->
2122
+
2123
+ <!-- capability: state-machine.condition-artboard -->
2124
+
2125
+ <!-- capability: state-machine.condition-component -->
2126
+
2127
+ <!-- capability: state-machine.condition-focus -->
2128
+
2129
+ <!-- capability: state-machine.continue-on-exit -->
2130
+
2131
+ <!-- capability: state-machine.entry-state -->
2132
+
2133
+ <!-- capability: state-machine.exit-state -->
2134
+
2135
+ <!-- capability: state-machine.exit-time-relative -->
2136
+
2137
+ <!-- capability: state-machine.state-entry-actions -->
2138
+
2139
+ <!-- capability: state-machine.state-exit-actions -->
2140
+
2141
+ <!-- capability: state-machine.state-random -->
2142
+
2143
+ <!-- capability: state-machine.state-reset -->
2144
+
2145
+ <!-- capability: state-machine.transition-actions -->
2146
+
2147
+ <!-- capability: state-machine.transition-disabled -->
2148
+
2149
+ <!-- capability: state-machine.transition-duration-relative -->
2150
+
2151
+ <!-- capability: state-machine.transition-end-actions -->
2152
+
2153
+ <!-- capability: state-machine.transition-random-weight -->
2154
+
2155
+
2156
+ <!-- capability: state-machine.condition-data -->
2157
+ <!-- capability: state-machine.literal-asset -->
2158
+ <!-- capability: state-machine.literal-boolean -->
2159
+ <!-- capability: state-machine.literal-color -->
2160
+ <!-- capability: state-machine.literal-enum -->
2161
+ <!-- capability: state-machine.literal-string -->
2162
+ <!-- capability: state-machine.literal-trigger -->
2163
+ ### Typed data guards
2164
+
2165
+ With `data`, a direct scalar path on the left accepts `==`/`!=` against JSON string/enum,
2166
+ boolean, hex colour or `asset("id")` literals. Enum members and typed assets must exist.
2167
+ Colors compare all four RGBA bytes; quoted `&&` stays inside a string. The existing JSON/AniML
2168
+ `when` field retains its spelling. Type/operator mismatches emit `data-guard` at the `/when`
2169
+ pointer with a correction hint. Bare `$trigger` guards consume signal occurrences; there is no
2170
+ integer trigger literal syntax. Details: [data contract](../modules/data/SPEC.md).
2171
+
2172
+ ## input
2173
+
2174
+ Optional P4c input behavior is specified in [the input contract](../modules/input/SPEC.md).
2175
+ Capture listeners run from the outermost ancestor down to the target, with authored order at
2176
+ equal depth. A `consume: true` capture listener stops delivery before inner capture or normal
2177
+ listeners. Normal keyboard listeners stop delivery by default; `propagate: true` (AniML
2178
+ `propagate="true"`) passes the key on, while `consume: true` takes precedence. Capture listeners
2179
+ only stop delivery with explicit consume — an intentional difference from Rive, where a capture
2180
+ listener claims the event by default (measured in `tests/oracle/input-capture-phase`): Anima keeps
2181
+ the DOM's rule that observing an event in the capture phase does not take it away from the target. This policy includes nested component keys and all
2182
+ three keyboard phases; unmatched key/modifier filters never claim a key. Focus navigation and
2183
+ native Enter/Space activation retain the focus/host policies above. These input
2184
+ consumption rules apply only to authored Anima listeners; DOM propagation to the
2185
+ surrounding page remains intact as specified in §10.
2186
+
2187
+ Preserve-offset alignment intentionally differs from the measured native Rive probe. Anima
2188
+ stores target origin minus grab point at pointer-down, then adds that offset to each pointer
2189
+ position in parent coordinates. In `tests/oracle/input-align-preserve`, the origin is x=80,
2190
+ the grab is x=88 (offset -8), and the move is x=104: Anima yields x=96 and retains it after
2191
+ release; native Rive yields x=80 at both observations (absolute delta 16). This is an
2192
+ intentional difference recorded separately, never a passing oracle or a relaxed tolerance.
2193
+ Snap alignment agrees. See the retained native command/number dumps and
2194
+ `docs/research/rive-web-runtime.md` lessons 2, 5 and 6 for the core/host boundary rationale.
2195
+
2196
+ Generic registry attributes and the sample command dispatcher are integrated. See per-row reports
2197
+ for native comparison evidence and recorded differences.
2198
+
2199
+ <!-- capability: host-api.coordinate-mapping -->
2200
+ <!-- capability: host-api.dom-listener-control -->
2201
+ <!-- capability: host-api.gamepad -->
2202
+ <!-- capability: host-api.low-level-pointer -->
2203
+ <!-- capability: host-api.low-level-scroll -->
2204
+ <!-- capability: host-api.manual-gamepad-batch -->
2205
+ <!-- capability: host-api.multitouch -->
2206
+ <!-- capability: host-api.pointer-exit -->
2207
+ <!-- capability: host-api.scroll-hit-policy -->
2208
+ <!-- capability: host-api.scroll-latching -->
2209
+ <!-- capability: input.align-preserve -->
2210
+ <!-- capability: input.align-snap -->
2211
+ <!-- capability: input.bounded-hit-tests -->
2212
+ <!-- capability: input.button-middle -->
2213
+ <!-- capability: input.button-secondary -->
2214
+ <!-- capability: input.capture-phase -->
2215
+ <!-- capability: input.cursor-arrow -->
2216
+ <!-- capability: input.cursor-hidden -->
2217
+ <!-- capability: input.cursor-intent -->
2218
+ <!-- capability: input.cursor-pointer -->
2219
+ <!-- capability: input.cursor-resize-horizontal -->
2220
+ <!-- capability: input.cursor-resize-vertical -->
2221
+ <!-- capability: input.cursor-text -->
2222
+ <!-- capability: input.disable-listeners -->
2223
+ <!-- capability: input.gamepad-axis -->
2224
+ <!-- capability: input.gamepad-button -->
2225
+ <!-- capability: input.gamepad-button-phases -->
2226
+ <!-- capability: input.gamepad-connected -->
2227
+ <!-- capability: input.gamepad-disconnected -->
2228
+ <!-- capability: input.gamepad-host -->
2229
+ <!-- capability: input.gamepad-index-mapping -->
2230
+ <!-- capability: input.hit-occlusion-query -->
2231
+ <!-- capability: input.hit-parent-chain -->
2232
+ <!-- capability: input.hit-shape-geometry -->
2233
+ <!-- capability: input.hit-test-host -->
2234
+ <!-- capability: input.key-down -->
2235
+ <!-- capability: input.key-filter -->
2236
+ <!-- capability: input.key-modifiers -->
2237
+ <!-- capability: input.key-repeat -->
2238
+ <!-- capability: input.key-up -->
2239
+ <!-- capability: input.keyboard-bubbling -->
2240
+ <!-- capability: input.multi-touch -->
2241
+ <!-- capability: input.multi-trigger-listener -->
2242
+ <!-- capability: input.opaque-targets -->
2243
+ <!-- capability: input.pointer-cancel -->
2244
+ <!-- capability: input.pointer-drag -->
2245
+ <!-- capability: input.pointer-drag-end -->
2246
+ <!-- capability: input.pointer-drag-start -->
2247
+ <!-- capability: input.pointer-enable -->
2248
+ <!-- capability: input.touch-page-scroll -->
2249
+ <!-- capability: input.trackpad-scroll -->
2250
+ <!-- capability: input.wheel-scroll -->
2251
+ <!-- capability: transforms.collapse -->
2252
+ <!-- capability: transforms.solo -->
2253
+
2254
+
2255
+
2256
+
2257
+
2258
+
2259
+ ## events
2260
+
2261
+ Declare `requires: ["events"]` and import the optional events runtime. The module adds
2262
+ `eventDefs`, a name-keyed map of event definitions, and `audioVolumes`, an audio-asset-name map
2263
+ of resource gains in [0,1]. Existing timeline `[time,name]`, state `emit` and listener `emit`
2264
+ spellings and ordering remain unchanged. Definitions enrich every occurrence of that name;
2265
+ undeclared names continue to report the original `{name,layer,time}` record.
2266
+
2267
+ A definition has optional `payload`, `url`, `target`, `audio` and `volume` fields. `payload`
2268
+ is a field-name map of `{type,value?,input?,address?}` objects. Type is `number`, `bool` or
2269
+ `string` or `color`; exactly one source is required. A literal must have the declared type. `input`
2270
+ references a bool/number input of the same type; `address` references an animatable property
2271
+ of the same type. Each reported event gets its own detached `payload` object. Values resolve
2272
+ at the end of the local player's step, after queued inputs, state resolution and bindings;
2273
+ all occurrences in that step observe that resolved snapshot, including time-zero entry events.
2274
+ Payloads survive component bubbling unchanged; parent definitions do not reinterpret child names.
2275
+ For number/bool/color fields, `value: "{expression}"` additionally requires bind and evaluates
2276
+ against the same final-step input snapshot as `input` sources; it does not capture the instant of
2277
+ an emit action. String fields always stay literal, including braces. Number fields accept bool
2278
+ expressions as 0/1; bool fields require bool; colour fields require a colour expression or hex
2279
+ literal and report a canonical hex string (including alpha when needed). Colours cannot reference
2280
+ inputs because inputs are bool/number/trigger only. `anima sample` snapshots include these fields
2281
+ verbatim. This is snapshot sampling, not scripting.
2282
+ `events.script-event-context` is deferred to P9 because no script VM/context exists.
2283
+
2284
+ `url` is an HTTP(S) URL or a relative reference. `target` is `_self` (default), `_blank`,
2285
+ `_parent` or `_top` and requires `url`. Core rejects non-HTTP schemes (`event-url`). Host URL
2286
+ parsing is authoritative: no navigation occurs without explicit `url-policy="allow"` or an
2287
+ `icon.urlPolicy(url,target,event)` callback returning exactly true. Relative references resolve
2288
+ against the host document and must stay on its origin; absolute HTTP(S) may cross origins.
2289
+ Protocol-relative or backslash references that resolve off-origin are rejected. Canonical
2290
+ protocol checks run even with a permissive policy. Delivery uses `window.open` with `noopener`;
2291
+ browser popup/target restrictions still apply. There is no navigation in core or CLI.
2292
+
2293
+ `audio` references an assets-module audio asset (`event-audio` otherwise); `volume` is an
2294
+ optional [0,1] event gain, default 1. The browser requires prior user activation; earlier events
2295
+ are dropped without fetching or decoding and are never replayed later. Decode is on demand and
2296
+ cached per element/load/asset. Each occurrence starts a separate source, honoring asset clip `start`/`end` (assets chapter). Effective gain is
2297
+ `event.volume * audioVolumes[asset] * icon.volume`, with each omitted gain equal to 1.
2298
+ For bubbled component events the owning child supplies the asset and resource gain; the host
2299
+ resolver receives its dot-scoped asset name (for example `child.tone`).
2300
+ The asset resolver is consulted as for image assets. Relative asset sources use the element's
2301
+ `src`, falling back to the host document. Reload/disconnect/destroy stops sources, closes the
2302
+ context and invalidates outstanding decodes. Browser resume/fetch/decode failures report an
2303
+ `anima:error` diagnostic `event-audio-load`; they do not change deterministic player output.
2304
+
2305
+ Local listeners may use `on: "event:<name>"` to queue their existing fire/set/toggle/emit
2306
+ actions for the next step. This is an **intentional difference** from Rive's same-frame
2307
+ listener response: a step cannot re-enter itself, so event-caused writes cannot affect
2308
+ its own transitions or recursively run an event cascade. All runtimes keep this queue
2309
+ boundary, including `step(0)` calls. Unknown local event names are `unknown-event`.
2310
+ Pending actions keep the player unsettled; callbacks never recursively re-enter a step.
2311
+ Components retain their existing scoped listener spelling and next-parent-step semantics.
2312
+
2313
+ Conditions and event fields that read an input changed by a listener action see the **new**
2314
+ value in the step that applies the action: a click that sets `count = count + 1` and a
2315
+ transition `when="mod(count, 5) == 0"` report the milestone at counts 5, 10, 15, … This is an
2316
+ **intentional difference** from the measured Rive behaviour (`tests/oracle/data-bind-listener-actions`),
2317
+ where the same scene reports at counts 6, 11, 16, … because its condition still sees the value
2318
+ from before the increment. Anima follows what the author wrote.
2319
+
2320
+ For documents requiring events, state exits additionally report `@state-end`, then
2321
+ `@transition-start`, each with `{payload:{state}}` (source state for end, target for start).
2322
+ A completed blend reports `@transition-end` once with the target state. A state may additionally name
2323
+ `endEmit`, and each transition may name `startEmit` and `endEmit`. Each named action reports an
2324
+ ordinary event immediately after its corresponding metadata signal, using the same definitions,
2325
+ payload resolution, URL/audio policy and next-step listeners as timeline and entry events.
2326
+ Interrupted transitions do not fire their end action. These optional fields require events and
2327
+ use the identical attribute names on AniML `<state>` and `<on>`. Component scopes expose the
2328
+ named action events to existing parent listeners. Each player's active action is independent.
2329
+ Zero-duration,
2330
+ reduced-motion and fully carried blends report end immediately before the target's entry emit.
2331
+ Interrupted blends do not report completion. Fade completion is checked before layer timeline
2332
+ crossings, after listener actions. These signals have timeline time 0 and the owning layer.
2333
+
2334
+ Repeating timeline states report `@loop` at each boundary: every duration for `loop`, every
2335
+ turn for `pingpong`. There is no loop signal at initial entry, held/seeked clocks, reduced-motion
2336
+ freezes or `once` completion. The existing motion crossing policy supplies work area, speed,
2337
+ reverse and carry behavior. Loop signals precede that state's ordinary timeline event block;
2338
+ ordinary event ordering is unchanged. Coincident authored endpoints remain distinct, while each
2339
+ loop boundary is reported once. Existing crossing caps remain unchanged.
2340
+
2341
+ `event-payload`, `event-input` and `event-address` reject ambiguous/mistyped sources, missing
2342
+ or mistyped input/property references. Diagnostics include a JSON pointer, message and hint.
2343
+ AniML uses root `<event-def name="…" payload='{"field":{"type":"number","input":"count"}}'
2344
+ url="…" target="_blank" audio="tone" volume="0.5"/>` and
2345
+ `<audio-volume asset="tone" value="0.4"/>`. Payload is JSON inside the XML attribute and obeys
2346
+ normal XML escaping. Neither form changes timeline `<event at="…" name="…"/>`.
2347
+
2348
+ The event delivery/subscription separation follows the host/runtime concerns recorded in
2349
+ `docs/research/rive-web-runtime.md` §§2–3; no Rive implementation or documentation text is used.
2350
+
2351
+
2352
+ Repeated event audio resolves its emitting player through both ordinary component placements and
2353
+ list indices (for example `rows.1.inside.play`). Decode caches use asset declaration identity, so
2354
+ reordering different component catalogs with the same clip name cannot replay another row's audio.
2355
+ The host still observes the scoped event and the ordinary Web Audio start call after activation.
2356
+
2357
+ ## contour
2358
+
2359
+ The optional `contour` module (5120 B browser budget) contains authoring geometry and path
2360
+ operations. Declare `requires: ["contour"]`; gradients additionally require paint, ordered
2361
+ paints additionally require composite. Core hooks contribute resolved geometry, never backend
2362
+ commands. This follows MODULES §3.1 and the separation discussed in
2363
+ [web runtime research](../docs/research/rive-web-runtime.md#lessons-for-anima).
2364
+ The corner-control distance below follows Rive’s MIT natural-rounding construction; see NOTICE.
2365
+ The other algorithms are independent constructions.
2366
+
2367
+ ### Editable vertex authoring forms and geometry origin
2368
+
2369
+ A `contour` node has `vertices`, at least two objects `{point:[x,y], kind, …}`, optional
2370
+ `closed` (default false), and optional `geometryOrigin:[x,y]` (default `[0,0]`). It accepts
2371
+ ordinary painted-node transforms/fills/strokes. Each vertex point is in grid units; origin is
2372
+ subtracted from every point before ordinary node transforms, independently of paint coordinates.
2373
+ AniML `<contour>` uses `vertices="[…JSON…]"`, `closed="true"`, and
2374
+ `geometry-origin="x y"`. The array is explicitly JSON-valued in the registry. Node/paint
2375
+ fields, schema and CLI names derive from the ordinary registry. Authoring fields are static;
2376
+ editing means reloading revised vertex data. Path morphing remains the paint module's `d` API.
2377
+
2378
+ Vertex kinds compile to an ordinary path shared by SVG, canvas and the outline hook:
2379
+
2380
+ | kind | Optional fields and construction |
2381
+ | --- | --- |
2382
+ | straight | No handles; adjacent edges are lines unless the neighbour supplies a cubic handle. |
2383
+ | mirrored | `handle:[dx,dy]`, default zero; outgoing is handle, incoming is its exact negative. |
2384
+ | asymmetric | `outgoing:[dx,dy]`, `incoming:[dx,dy]`, defaults zero. Outgoing supplies direction and length; incoming supplies length only, projected opposite outgoing. Nonzero incoming length requires a nonzero outgoing direction. |
2385
+ | detached | `incoming` and `outgoing` vectors, independently positioned, default zero. |
2386
+ | rounded | `radius ≥ 0`, default zero. Use one cutback min(radius, half previous length, half next length) and a cubic with the natural-rounding controls below. Open endpoints do not round. |
2387
+
2388
+ A cubic edge uses source outgoing and destination incoming controls (point plus vector).
2389
+ A closed contour joins the last vertex to the first, then closes. Fields belonging to another
2390
+ kind produce `bad-value` at that vertex field with a corrective hint; invalid array sizes,
2391
+ coordinates, kinds and negative radii retain structural diagnostics. This is an explicit,
2392
+ deterministic authoring contract. For rounded corners, let u/v be the vectors from the point to
2393
+ its previous/next neighbour (using the neighbour’s adjacent cubic control when present).
2394
+ Let θ = atan2(abs(cross(u,v)), dot(u,v)). With cutback r, set
2395
+ h = min(1, (4/3) tan(θ/4) × (θ < π/2 ? 1+cos θ : 2−sin θ)).
2396
+ The cubic’s controls lie fraction h from each cut toward the authored point. Zero-length
2397
+ neighbour vectors collapse the rounding. This mathematical construction closely follows
2398
+ [Rive’s MIT path implementation](https://github.com/rive-app/rive-runtime/blob/main/src/shapes/path.cpp),
2399
+ reimplemented here without source-text copying; attribution/license are in NOTICE. Native
2400
+ identity path-effect captures check acute, right, obtuse and degenerate corners independently.
2401
+
2402
+ ### Sequential and synchronized trim; ordered/shared effects
2403
+
2404
+ `stroke.trim` is `{start?,end?,offset?,mode?}`: defaults 0,1,0,`sequential`. Start/end are
2405
+ fractions in 0..1. Offset is an arbitrary fraction of one turn. Sequential trim measures the
2406
+ concatenation of all subpath lengths in authored order; synchronized trim applies the same
2407
+ fractions independently to each subpath. A nonpositive end−start draws nothing; a full span
2408
+ keeps all geometry; other spans begin at `(start+offset) mod 1`, wrapping at one.
2409
+
2410
+ `stroke.effects` is an ordered JSON array of trim/dash objects or shared-stack names.
2411
+ A trim is `{type:"trim",start?,end?,offset?,mode?}`. A dash is
2412
+ `{type:"dash",dash:[nonnegative grid lengths…],offset?}`. Odd dash arrays repeat twice;
2413
+ at least one entry must be positive. Positive offsets advance into the pattern. Dashes restart
2414
+ at each input subpath. Each operation consumes the previous operation's output, so trim after
2415
+ dash measures the remaining dash fragments, while dash after trim restarts at each trimmed
2416
+ fragment. Effects alter stroke geometry only, preserving fill geometry and the hit outline.
2417
+
2418
+ Document `effects` maps names to arrays of effect objects, expanded by name at each use.
2419
+ Shared arrays cannot contain references, so cycles are impossible. Each use starts from its
2420
+ own node geometry. AniML uses JSON-valued root `effects` and stroke `effects` / `trim`
2421
+ attributes, with ordinary XML escaping. Compound document fields explicitly opt in via their
2422
+ registry markup name; existing document containers retain their structural forms.
2423
+ `unknown-source` identifies a missing stack name at its exact array index. A dash without
2424
+ positive length or fields of the wrong effect kind is `bad-value`, with field pointer and hint.
2425
+ An effect stack and `trim`, or either plus legacy `dash` / `trimStart` / `trimEnd` /
2426
+ `trimOffset`, is `paint-conflict`; move all operations into one stack. Legacy stroke behaviour
2427
+ is unchanged. Effects are static; changing the stack requires reloading the document.
2428
+
2429
+ Cubic path segments are subdivided at the midpoint until control-polygon length minus chord
2430
+ is at most 0.0001 local grid units (maximum depth 20). Operations slice the resulting segments
2431
+ by Euclidean length. Geometry is compiled into resolved per-paint paths; neither backend uses
2432
+ its own dash/trim approximation. Existing path-length/dash algorithms are unchanged.
2433
+
2434
+ ### Clockwise fill
2435
+
2436
+ `fill.winding: "clockwise"` (AniML `fill-winding="clockwise"`) selects strictly positive
2437
+ winding, overriding the ordinary fill `rule` for rendering. In local SVG y-down coordinates,
2438
+ a clockwise contour contributes +1, counterclockwise −1. The filled region contains exactly
2439
+ points whose total winding is greater than zero; nonzero negative regions are empty. Open
2440
+ contours implicitly close. This applies to self-intersections, not merely signed whole-contour
2441
+ area. The operation runs in local geometry space before transforms (a reflected node remains
2442
+ filled). It compiles to nonoverlapping trapezoids split at endpoints and edge intersections;
2443
+ curves use the same subdivision rule as effects. Both backends fill that ordinary path nonzero.
2444
+ Stroke geometry is unchanged. Core pointer hit testing uses the same positive local winding,
2445
+ including implicit closure of open fill contours, holes and self-intersections. Reflections do
2446
+ not change the local winding test. Ordered fills contain a point if any fill contains it; stroke
2447
+ and listener padding continue to use the original outline distance.
2448
+
2449
+ ## mix
2450
+
2451
+ The optional `mix` module (5120 B browser budget) refines backend compositing. Declare
2452
+ `requires: ["mix"]`; base node blend and ordered paints still require composite.
2453
+
2454
+ ### Additive blend and strength
2455
+
2456
+ `node.mix: {mode, strength?}` selects a node compositor. Mode accepts the existing composite
2457
+ blend names plus `additive`; strength defaults one and is in 0..1. `mix` and node `blend`
2458
+ cannot coexist (`paint-conflict` at `/mix` with an instruction to choose one).
2459
+ `<group mix="{&quot;mode&quot;:&quot;additive&quot;,&quot;strength&quot;:0.5}">` illustrates the
2460
+ JSON-valued AniML spelling. `node.mix.strength` is an ordinary animatable number address.
2461
+
2462
+ Additive means Canvas2D `lighter`: sum premultiplied source and destination components,
2463
+ clamping to the output range. Strength interpolates between source-over (zero) and addition (one). For premultiplied
2464
+ source `S`, destination `D`, source alpha `Sa` and strength `a`, the result is
2465
+ `S + D * (1 - Sa * (1 - a))`, clamped per component (including alpha). Node opacity
2466
+ scales `S` and `Sa` before this operation. Canvas evaluates fractional strength once in premultiplied pixel space to avoid rounding
2467
+ the source contribution twice in 8-bit intermediate surfaces; strength zero/one use native
2468
+ source-over/lighter. Strength has no effect on other compositors. SVG refuses
2469
+ additive nodes or paints with `unsupported-render`, reason and a canvas hint because
2470
+ `plus-lighter` is not consistently supported by the supported SVG engines/static renderer.
2471
+
2472
+ ### Per-paint blend override
2473
+
2474
+ Fill and stroke objects accept static `blend`, default `inherit`, selecting any node mix mode,
2475
+ and animatable `blendAmount` in 0..1 (AniML `fill-blend`, `stroke-blend`,
2476
+ `fill-blend-amount`, `stroke-blend-amount`). If any paint overrides blending, the node blend
2477
+ is moved to each paint: inherit chooses the node blend, explicit mode replaces it. Paints
2478
+ compose in authored order, then ordinary node opacity/masking/group isolation applies.
2479
+ `blendAmount` selects the same source-over/addition interpolation for an additive paint; it
2480
+ has no effect on other modes. Canvas uses each paint's compositor. SVG gives each paint a
2481
+ separate blend group, with the geometry transform on an inner group. To avoid Firefox isolating
2482
+ transformed ancestors, documents with paint overrides move node transforms into these inner
2483
+ world-space groups; authored opacity, masks and node compositors retain their isolation. Mask
2484
+ sources and clip outlines use the same world coordinate space. Gradients and fixed strokes
2485
+ remain local to the inner geometry transform. Additive overrides retain the explicit SVG refusal.
2486
+ A node without paint overrides retains the existing group compositor exactly.
2487
+
2488
+ ### Host renderer and factory mapping
2489
+
2490
+ `AnimaIcon.renderers[name] = icon => renderer` is the host factory. It creates an `IconRenderer`
2491
+ whose `draw(DrawFrame,force)`, `layout()`, `point(x,y)`, `connect()`, `disconnect()` and
2492
+ `destroy()` implement the backend bridge. Factories own their paths, gradients, buffers, image
2493
+ decoding and resource caches; there is no mandatory global resource superclass or per-verb
2494
+ bridge. A custom backend may inject its own resource factory through that closure. The core
2495
+ contract is the draw frame, including transforms, geometry, paints, clipping and opacity, so
2496
+ playback is shared. Browser tests create and release host-owned paths and gradients in both
2497
+ engines. These host-only API rows have no observable Rive screenshot counterpart.
2498
+ ## data
2499
+
2500
+ The optional `data` module generalizes typed inputs with view models, scalar and reference properties,
2501
+ named instances, `$` paths, binding direction modes, data-writing listeners and node custom properties.
2502
+ Its normative contract, step position, spellings, diagnostics and limits are in
2503
+ [modules/data/SPEC.md](../modules/data/SPEC.md). `data` bindings resolve in §7's existing binding
2504
+ position, after layers and before clamping/reporting/children. Change listeners queue for the next
2505
+ step; no callback re-enters the machine. Existing 0.1 input behavior is unchanged.
2506
+
2507
+ Capability evidence and incomplete rows are enumerated in
2508
+ [modules/data/ROWS.json](../modules/data/ROWS.json). No phase-completion claim is made.
2509
+
2510
+ <!-- capability: data.bind-conditions -->
2511
+ <!-- capability: data.bind-converter-options -->
2512
+ <!-- capability: data.bind-instance-values -->
2513
+ <!-- capability: data.bind-keyframes -->
2514
+ With `data motion`, a keyframe value may be a direct scalar data path. Sampling resolves needed
2515
+ endpoints against the current player before interpolation, including blends and TimelineInstances,
2516
+ without mutating shared tracks. JSON `[0.5, "$peak"]` and AniML `<key t="0.5" value="$peak"/>`
2517
+ are equivalent. Full typing, diagnostics and timing: [data](../modules/data/SPEC.md#data-keyframe-values).
2518
+
2519
+ <!-- seam(P7-2): data.bind-layout is specified with layout below. -->
2520
+ <!-- capability: data.bind-script-inputs -->
2521
+ <!-- capability: data.bind-text -->
2522
+ <!-- capability: data.binding-absolute -->
2523
+ <!-- capability: data.binding-once -->
2524
+ <!-- capability: data.binding-relative -->
2525
+ <!-- capability: data.binding-to-source -->
2526
+ <!-- capability: data.binding-two-way-prefer-source -->
2527
+ <!-- capability: data.binding-two-way-prefer-target -->
2528
+ <!-- capability: data.component-public-data -->
2529
+ <!-- capability: data.custom-property-boolean -->
2530
+ <!-- capability: data.custom-property-color -->
2531
+ <!-- capability: data.custom-property-enum -->
2532
+ <!-- capability: data.custom-property-number -->
2533
+ <!-- capability: data.custom-property-string -->
2534
+ <!-- capability: data.custom-property-trigger -->
2535
+ <!-- capability: data.default-instance -->
2536
+ <!-- capability: data.editor-preview-instance -->
2537
+ <!-- capability: data.enum-members -->
2538
+ <!-- capability: data.global-model -->
2539
+ <!-- capability: data.instance-export -->
2540
+ <!-- capability: data.list-items -->
2541
+ <!-- capability: data.model-definition -->
2542
+ <!-- capability: data.named-instances -->
2543
+ <!-- capability: data.operation-operand-view-model -->
2544
+ <!-- capability: data.property-artboard -->
2545
+ <!-- capability: data.property-asset-blob -->
2546
+ <!-- capability: data.property-asset-font -->
2547
+ <!-- capability: data.property-asset-image -->
2548
+ <!-- capability: data.property-boolean -->
2549
+ <!-- capability: data.property-color -->
2550
+ <!-- capability: data.property-enum-custom -->
2551
+ <!-- capability: data.property-enum-system -->
2552
+ <!-- capability: data.property-list -->
2553
+ <!-- capability: data.property-number -->
2554
+ <!-- capability: data.property-string -->
2555
+ <!-- capability: data.property-symbol-list-index -->
2556
+ <!-- capability: data.property-text-style -->
2557
+ <!-- capability: data.property-trigger -->
2558
+ <!-- capability: data.property-view-model -->
2559
+ <!-- capability: data.script-model-api -->
2560
+ <!-- capability: data.scripted-converter -->
2561
+ <!-- capability: data.state-action-trigger -->
2562
+ <!-- capability: data.system-enum-horizontal-text-align -->
2563
+ <!-- capability: data.system-enum-layout-alignment -->
2564
+ <!-- capability: data.system-enum-layout-direction -->
2565
+ <!-- capability: data.system-enum-layout-display -->
2566
+ <!-- capability: data.system-enum-layout-flex-direction -->
2567
+ <!-- capability: data.system-enum-layout-position -->
2568
+ <!-- capability: data.system-enum-layout-position-units -->
2569
+ <!-- capability: data.system-enum-layout-scale-type -->
2570
+ <!-- capability: data.system-enum-layout-wrap -->
2571
+ <!-- capability: data.system-enum-scroll-direction -->
2572
+ <!-- capability: data.system-enum-semantic-check-state -->
2573
+ <!-- capability: data.system-enum-text-box -->
2574
+ <!-- capability: data.system-enum-text-overflow -->
2575
+ <!-- capability: data.system-enum-text-trim-bottom -->
2576
+ <!-- capability: data.system-enum-text-trim-top -->
2577
+ <!-- capability: data.system-enum-vertical-text-align -->
2578
+ <!-- capability: data.system-enum-word-wrap -->
2579
+ <!-- capability: drawing-paint.gradient-channel-binding -->
2580
+ <!-- capability: input.data-listener -->
2581
+ <!-- capability: input.write-data-constant -->
2582
+ <!-- capability: input.write-data-copy -->
2583
+ <!-- capability: input.write-focused -->
2584
+ <!-- capability: input.write-gamepad-axis -->
2585
+ <!-- capability: input.write-gamepad-button-pressed -->
2586
+ <!-- capability: input.write-gamepad-button-value -->
2587
+ <!-- capability: input.write-gamepad-changed-value -->
2588
+
2589
+ ## host
2590
+
2591
+ Optional host data scripting is specified in [modules/host/SPEC.md](../modules/host/SPEC.md).
2592
+ Import the host entry alongside `data`; it adds no document fields or AniML spellings. It provides
2593
+ typed handles, reflection, instances, binding, batched writes, change subscriptions delivered after
2594
+ the step that made the change, and the web `auto-bind` attribute.
2595
+
2596
+
2597
+ <!-- capability: assets.replacement-scope-image -->
2598
+ ### Image replacement scope
2599
+
2600
+ Host `image(path).set(URL | Blob)` decodes a player-local replacement and queues only that
2601
+ property's resource ID; image `asset` bindings follow it at the next step. Shared compiled
2602
+ components, other data properties and authored resources retain their identities. Static nodes
2603
+ without asset bindings keep legacy per-player resource replacement. See
2604
+ [host editing](../modules/host-edit/SPEC.md) and [data-host](../modules/data-host/SPEC.md).
2605
+
2606
+ ## host-edit
2607
+
2608
+ Optional page-data editing is specified in [modules/host-edit/SPEC.md](../modules/host-edit/SPEC.md).
2609
+ Import `host-edit` alongside `host` and `data`; it adds no document fields. It provides
2610
+ identity-preserving list mutation, default and named instance discovery, colour and enum helpers,
2611
+ image replacement on the web, nested component player access, component root selection and a
2612
+ constructor readiness promise. Its runtime registration key is `hostedit`.
2613
+
2614
+ ## convert
2615
+
2616
+ Optional feature `convert`, after `bind` and `data`; no runtime dependencies. JSON declarations are a
2617
+ `converters` map at the document root. Each value is `{type, ...options}` or `{chain: [names...]}`.
2618
+ Names in a group execute left to right; forward references are permitted, cycles and missing
2619
+ references are errors. A group occurrence has its own state key, including repeated names.
2620
+
2621
+ Named converters/groups register through the active data module’s `registerConverter(name, fn)`;
2622
+ `fn(value, options, ctx)` receives `{player, dt, direction, get, state, key}`. Definitions are selected
2623
+ from that player’s document, so equal names in different documents remain independent. Bindings
2624
+ require `data` and `convert`. Binding `options` override declaration options; whole `$path` strings
2625
+ resolve from the player’s current data scope. Formula `$` operands use the same resolver and bind
2626
+ parser. Unknown named converters and invalid numeric operand paths fail at load with pointers.
2627
+
2628
+ `ctx.state` is a shared player-local Map. Convert namespaces a separate map per stable binding
2629
+ `key` inside it, preserving external converters’ access to the shared map. Interpolation state is stored only
2630
+ there, keyed by group occurrence and direction, including repeated converter names. No state is
2631
+ stored on a loaded model or shared between players. The direct API `convertValue` and
2632
+ `createConverterRunner` accept `{player,time,state,key?,get?,direction?}`; `time` is milliseconds
2633
+ and direct temporal calls retain their context map. The runtime supplies `player.time * 1000`.
2634
+ Converter-free documents install no scheduler wrappers. Active interpolation and `random="always"`
2635
+ keep the player unsettled; `once`, `on-change`, and completed interpolation allow sleep.
2636
+ Bindings evaluate once per data binding evaluation; `mode="once"` retains its converted output.
2637
+
2638
+ | Type | Options and semantics |
2639
+ | --- | --- |
2640
+ | formula | `expression`, `random`: always (default), once, on-change. Variable `value` is the input. |
2641
+ | calculate | `operation`, numeric `operand` (default 1); arithmetic and registered math operations. |
2642
+ | range | `from` and `to` number pairs (default 0 1); map linearly. `modulo`, then `clamp`, then `reverse` apply to normalized progress. Reversed endpoints are permitted; zero-width source is invalid. Optional `ease` uses the existing timeline easing grammar (default linear). |
2643
+ | round | `decimals` (0–100, default 0), `mode`: round/floor/ceil; ECMAScript rounding semantics. |
2644
+ | negate | Boolean inversion; numeric inputs are errors. |
2645
+ | to-number | Number, boolean or string conversion using ECMAScript Number; nonnumeric strings become zero. |
2646
+ | to-string | ECMAScript String conversion. |
2647
+ | number-format | `decimals` (0–100, default 0), `grouping` boolean; fixed precision, dot decimal, comma thousands; locale independent. |
2648
+ | color-format | Hex colour or RGBA tuple → `format` hex/hexa/rgb/rgba (default hex), or component `pattern` (see below). |
2649
+ | pad | String `length` (0–10000), `fill` (nonempty string, default space), `side` start/end. UTF-16 length. |
2650
+ | trim | `mode`: start/end/all/none; ECMAScript whitespace. |
2651
+ | template | `text`, `open`/`close` delimiters (default braces), `grouping`, optional `args` map/array. Without `args`, scalar input is argument 0 and a list supplies positional arguments. Named keys, numeric positions and sequential `{}` placeholders are supported. `args` values may be `$` paths. `{points:2:8}` fixes two decimals and left-pads to width 8 (maximum 10000). Missing arguments are errors. |
2652
+ | remove-zeros | Strip redundant fractional zeros and empty decimal point; integer zeros remain. |
2653
+ | enum-to-index | `values`: ordered strings. Unknown values are errors. |
2654
+ | list-length | Return list length. |
2655
+ | number-to-list | Integer count 0–10000; produce index values or repeated `item`. Model instance creation awaits data integration. |
2656
+ | trigger | Boolean passthrough or truncated integer counter; a trigger binding destination fires when the converted output changes (next step, no initial pulse). |
2657
+ | degrees-to-radians | Multiply by pi/180 forward; by 180/pi in reverse. |
2658
+ | normalize | Normalize using `from` pair, default 0 1; reverse denormalizes with the same pair. No implicit clamp. |
2659
+ | interpolate | `duration` in milliseconds, default 0. First evaluation returns input immediately; changes retarget from the current sample. Numeric or rounded RGBA channel interpolation; optional `ease` uses the existing timeline easing grammar (default linear). No extrapolation before start or after completion. |
2660
+
2661
+ Expressions reuse bind precedence, token/depth limits, numeric/bool coercion and non-finite-to-zero
2662
+ rules without eval or dynamic code generation. Added calls: sin/cos/tan/asin/acos/atan/atan2,
2663
+ log (natural), exp, pow, sqrt, round, ceil, floor, abs, random(). Long aliases sine/cosine/tangent,
2664
+ asine/acosine/atangent/atangent2/power/squareRoot are accepted. Radians for trig. Existing
2665
+ min/max/clamp/mix/mod remain available. All math functions, including random(), register through the active bind module’s
2666
+ `registerFunction(name,arity,fn)` and formulas compile with its `parse` hook. There is no second
2667
+ expression parser. Random is evaluated within the formula context; unscoped random calls outside
2668
+ a formula return zero. Math functions are available to ordinary bindings whenever convert is
2669
+ installed. Random uses independently implemented uint32 LCG
2670
+ `seed = (1664525 * seed + 1013904223) mod 2^32`, output seed/2^32; the stream is shared per player in authored evaluation order, while sites/bindings cache separately.
2671
+ An explicit player/script seed joins this stream with machine draws through the core source, in
2672
+ evaluation order. Reseeding resets the source, preserving existing once/on-change site caches.
2673
+ `always` advances on evaluation, `once` caches per site, `on-change` invalidates when the incoming
2674
+ value changes under Object.is. Repeated evaluation of unchanged input retains the cached value.
2675
+
2676
+ Diagnostics: `convert-unknown-type`, `convert-options`, `convert-type`, `convert-cycle`,
2677
+ `convert-reference`; formula errors retain bind codes. Load-time diagnostics point at
2678
+ `/converters/<escaped-name>/<option>` where possible and include message and hint.
2679
+ `ConverterError` carries code and option for the data integration to attach a binding pointer.
2680
+
2681
+ AniML adapter: `convertMarkup.rootChild` accepts `<converter name="pct" type="range" from="0 1"
2682
+ to="0 100"/>` and `<converter-group name="fmt" chain="pct round label"/>`.
2683
+ The generated `convertCompiler` hook supplies declaration compilation/decompilation independently
2684
+ of events/data authoring. Browser `markup-convert` registers the `convert` slot. JSON declarations,
2685
+ AniML, CLI schema/inspect and `anima docs convert` use the same metadata and module chapter.
2686
+
2687
+ Remaining parity limits: retained model-instance list creation; scripted conversion (P9). Their canonical
2688
+ probes retain precise `todo.json` reasons. Existing scalar/index-list/boolean conversion functions
2689
+ are useful independently but do not complete those broader rows. See ROWS.json and REPORT.md.
2690
+
2691
+ ### Intentional differences and observation limits
2692
+
2693
+ These decisions retain the original oracle findings unchanged; none becomes a passing comparison.
2694
+
2695
+ - Boolean → string uses ECMAScript `"true"`/`"false"`, rather than the native numeric spelling
2696
+ (`"1"` for true). See [boolean text finding](../tests/oracle/data-converter-to-string/finding.json).
2697
+ - Interpolation initializes eagerly from the value at load. A later source change interpolates from
2698
+ that value rather than snapping on the first non-default change as native does. See
2699
+ [number interpolation finding](../tests/oracle/data-interpolate-number/finding.json).
2700
+ RGBA channels round half up, so a black/white midpoint is `#808080`, rather than native `#7f7f7f`;
2701
+ see [colour interpolation finding](../tests/oracle/data-interpolate-color/finding.json).
2702
+ - Decimal rounding and degrees → radians retain doubles. Their measured differences are solely
2703
+ native float32 storage precision, not different arithmetic contracts. See
2704
+ [decimal rounder finding](../tests/oracle/data-converter-rounder/finding.json) and
2705
+ [degrees → radians finding](../tests/oracle/data-converter-system-degs-to-rads/finding.json).
2706
+ - Enum → index returns the declared zero-based index. The native dump's unchanged numeric bridge
2707
+ is an observation limit, not evidence of a different enum conversion rule. See
2708
+ [enum observation finding](../tests/oracle/data-converter-enum-to-uint/finding.json).
2709
+
2710
+ Algorithms are independently implemented from standard arithmetic and ECMAScript functions.
2711
+ No Rive source or .riv files were read; no CLI documentation text was copied. The registry design follows
2712
+ the metadata rationale in `docs/research/rive-format-and-graph.md`; this is not a Rive algorithm port.
2713
+
2714
+
2715
+ <!-- capability: data.converter-color-formatting -->
2716
+ ### Colour component patterns
2717
+
2718
+ JSON `pattern` / AniML `pattern` overrides `format` on a `color-format` converter.
2719
+ Markers `{r} {g} {b} {a}` are rounded bytes, `{h}` rounded degrees (achromatic 0), `{s}`/`{l}`
2720
+ rounded HSL percentages, `{hex}` lowercase `#rrggbb`. Unknown markers produce `convert-options`
2721
+ at `/converters/<name>/pattern` with a hint. See [convert](../modules/convert/SPEC.md).
2722
+
2723
+ <!-- capability: data.converter-trigger -->
2724
+ ### Trigger destinations
2725
+
2726
+ A binding target naming a trigger queues one fire when its converted output changes after the
2727
+ initial baseline; transitions/listeners observe it on the following step. Magnitude/direction
2728
+ is irrelevant. `trigger` truncates numeric input and passes booleans unchanged. Forward-only
2729
+ modes `to-target`/`once` are valid; reverse modes produce `data-binding` at the `/mode` pointer.
2730
+
2731
+ ### Export discovery and keyframe bindings
2732
+
2733
+ JSON instance `exported: true` is AniML `export="true"` (legacy `exported` accepted).
2734
+ `dataState(player).exports()` lists only explicitly exported authored `{name,model}` records.
2735
+ Internal private instances remain addressable. Existing host APIs retain their all-instance list.
2736
+ Data-keyframe values use the player-aware sampler; converter evaluation is isolated per player.
2737
+
2738
+ ## derive
2739
+
2740
+ The optional `derive` module adds computed, read-only scalar model properties. The normative
2741
+ contract is [modules/derive/SPEC.md](../modules/derive/SPEC.md); its `value` and `convert` keys
2742
+ have identical JSON/AniML spellings. Dependencies resolve per instance and cycles are invalid.
2743
+
2744
+ ## lists
2745
+
2746
+ The optional `lists` module adds retained repeats and nested data contexts. The normative
2747
+ JSON/AniML spellings, ordering, bounds, identity, limits and diagnostics are defined in
2748
+ [modules/lists/SPEC.md](../modules/lists/SPEC.md). Both `components` and `data` are required.
2749
+
2750
+ <!-- capability: components.list -->
2751
+ ### List instances
2752
+ `type: "repeat"`, `list: "$items"`, `component: "row"` is
2753
+ `<repeat id="rows" list="$items" component="row"/>`. Surviving item identities retain players.
2754
+
2755
+ <!-- capability: components.list-map -->
2756
+ ### Model component maps
2757
+ `component: {Player:"row"}` is `component="Player:row"`; missing known mappings are `list-map`.
2758
+
2759
+ <!-- capability: components.list-size-override -->
2760
+ ### Item bounds
2761
+ `size: [w,h]` is `size="w h"`; both numbers are nonnegative. It changes instance hit bounds.
2762
+
2763
+ <!-- capability: components.explicit-context -->
2764
+ ### Explicit data context
2765
+ `data: "$team"` is `data="$team"` on an instance; the referenced model must match its public model.
2766
+
2767
+ <!-- capability: components.inherited-context -->
2768
+ ### Inherited data context
2769
+ A component with no `data` declaration shares the inherited document's live named data instances,
2770
+ recursively, including non-global models. This applies to host writes inside/outside `batch` and to
2771
+ document writes. An explicit child `data` model keeps local storage unless bound; global models
2772
+ retain their sharing contract. Each root player is independent. Descendant steps follow ancestor bindings; a shared data write
2773
+ during a descendant step keeps the ancestor unsettled so its bindings resample on the next step.
2774
+ With lists, a context-free
2775
+ component additionally sees its actual parent's default/item context.
2776
+
2777
+ <!-- capability: data.binding-relative -->
2778
+ ### Relative bindings
2779
+ `$parent.name` walks one context; `$parent.parent.name` walks two. Parent reads are read-only.
2780
+
2781
+ <!-- capability: data.property-symbol-list-index -->
2782
+ ### Item index
2783
+ `$index` is the current zero-based index and is read-only. Declaring `index` or `parent` is
2784
+ `reserved-name` at `/data/models/<model>/properties/<name>`, with a rename hint.
2785
+
2786
+ <!-- capability: data.converter-number-to-list -->
2787
+ ### Count repetition
2788
+ `count: "$n"` is `count="$n"`. It floors nonnegative counts and retains players by index.
2789
+ Counts above 256 are rejected; shrinking releases the removed indices. Any readable numeric data
2790
+ path is accepted, including `$items.length`, named/relative numeric paths and (with `derive`)
2791
+ `$title.length`. Nonnumeric paths report `list-source` at `/nodes/<index>/count` with the accepted
2792
+ source forms. `anima inspect` lists only the repeated input slots in the current initial list/count,
2793
+ including triggers; reserved potential slots for absent rows are omitted.
2794
+
2795
+ ## nested
2796
+
2797
+ The optional module's normative contract is [modules/nested/SPEC.md](../modules/nested/SPEC.md).
2798
+
2799
+ <!-- capability: components.nested-remap -->
2800
+ <!-- capability: components.nested-quantize -->
2801
+ <!-- capability: components.leaf-fit -->
2802
+ <!-- capability: components.stateful-output -->
2803
+ Instances gain absolute `time`, positive `fps`, `size`/`fit`/`align`, and read-only scalar `outputs`.
2804
+ The parent reads `badge.outputs.done`; the child declares `<output name="done" value="count > 1"/>`.
2805
+
2806
+ ## markup-libraries
2807
+
2808
+ <!-- capability: components.library-export -->
2809
+ <!-- capability: components.library-models -->
2810
+ <!-- capability: components.library-update -->
2811
+
2812
+ Local libraries share model definitions, authored data instances and enums through lexical,
2813
+ collision-free import expansion in both AniML and the resulting JSON data declarations.
2814
+ Named-instance expressions and enum references are rewritten; member keys and authored values
2815
+ are retained. Export selection and private dependencies follow the
2816
+ [library contract](../modules/markup-libraries/SPEC.md). Native library-service comparison is
2817
+ skip-noted because the required account commands are banned; this does not waive local tests.
2818
+
2819
+ Libraries support public export selection, model definitions and local vendoring updates.
2820
+ See [modules/markup-libraries/SPEC.md](../modules/markup-libraries/SPEC.md).
2821
+
2822
+ <!-- capability: state-machine.literal-artboard -->
2823
+ With `data components nested`, direct component-property guards accept `==`/`!=` against
2824
+ `component("row")`, a typed definition-name literal. See [nested](../modules/nested/SPEC.md).
2825
+ Independent guard/keyframe conformance and module-owned native comparisons cover this guard.
2826
+
2827
+ ## componentpaint
2828
+
2829
+ <!-- capability: drawing-paint.paint-artboard -->
2830
+ Declare `components paint componentpaint`. JSON `fill: {paint: "component:row"}` is AniML
2831
+ `paint="component:row"`: an independent live component Player supplies a repeating shape-local
2832
+ fill tile, clipped to its viewBox (including its origin). Its own default machine advances once
2833
+ per parent step and honors reduced motion. Flat color/type/ordered-paint conflicts, unknown
2834
+ components and recursive sources are errors. An ordinary stroke remains independent.
2835
+ The normative lifetime, sampling, diagnostic and backend contract is
2836
+ [modules/componentpaint/README.md](../modules/componentpaint/README.md).
2837
+
2838
+ ## CLI projects and capture (tooling)
2839
+
2840
+ These operations are authoring tools, not runtime features: they do not alter `Player.step` or
2841
+ require a feature declaration, registry field or conformance vector.
2842
+
2843
+ The CLI discovers `anima.json` from its working directory upward, taking the nearest file.
2844
+ Supported keys are `entry` (default input path), `sources` (array of AniML library paths),
2845
+ `exclude` (source globs), `constants` (named string/number/bool build-time values),
2846
+ `presets` (map of render-option objects), and `out` (default local export directory). Paths are
2847
+ relative to that configuration file. Unknown keys and malformed values fail with
2848
+ `project-configuration`, a JSON pointer and a repair hint. Commands needing an input use `entry`
2849
+ when no positional file is supplied. Explicit files take precedence. Configuration discovery is
2850
+ based on the working directory, not the input file's directory.
2851
+
2852
+ Excluded source paths are matched relative to the project file using Node path glob semantics;
2853
+ excluded libraries are omitted before reading or importing. An explicitly excluded input fails
2854
+ with `project-configuration` at `/exclude`. This supports test-only libraries outside exports.
2855
+ Constants replace `{{name}}` only in AniML attribute values, including imported sources; replacements
2856
+ are XML escaped. Text nodes are untouched. Unknown names fail at `/constants/<name>`. Constants
2857
+ never appear in runtime JSON and are not evaluated as expressions.
2858
+
2859
+ `sources` requires an AniML entry. Each source is an ordinary named `<library>` imported before
2860
+ compilation, with its declared name as namespace. For example a library named `ui` exposes
2861
+ `ui:button`. Existing import resolution, export selection, duplicate-name and cycle diagnostics
2862
+ apply. Sources are not concatenated into a second format. Imported asset origins remain subject
2863
+ to the existing compiler/build asset rules. A project file has no representation in `.ani`;
2864
+ the generated `.ani` schema describes runtime documents only.
2865
+
2866
+ Render presets accept `component`, `fit`, `viewport`, `scale`, `size`, `at`, `dark`, `color`,
2867
+ `current`, `background`, `sheet`, `over`, `frames`, `fps`, and `reduced-motion`. Numeric options may be numbers or
2868
+ strings, boolean options must be booleans, and `color` is an array of `slot=value` strings.
2869
+ `--preset name` supplies defaults; explicit flags override them. Presets cannot redirect files
2870
+ or execute scripts. Repeated `--preset a --preset b` runs a sequential queue in that order,
2871
+ stopping on failure. With multiple presets the output stem gains `-<preset>` to avoid collisions.
2872
+ `frames` (1–10000) renders at `fps` (default 60, finite >0 and ≤240) after script/at setup,
2873
+ stepping 1/fps between frames. PNG output gains zero-based `-0000` numbering. WebM uses CLI-only
2874
+ headless Chromium canvas capture and MediaRecorder; timestamps follow real-time encoder scheduling,
2875
+ so encoded bytes/timing are not a deterministic conformance output. WebM defaults to 60 frames.
2876
+ Unavailable Chromium or WebM support emits `build-encoder-unavailable` with a PNG fallback hint.
2877
+ Sequences reject sheet/compare and require PNG or WebM output.
2878
+
2879
+ `anima schema` accepts conjunctive `--animatable`, `--bindable`, `--inherited` filters.
2880
+ Inherited fields are the shared node properties, bindable fields are animatable addresses;
2881
+ static schema fields remain available in unfiltered lookup. `--editor-only` reports not applicable:
2882
+ Anima has no editor-only entries. `docs authoring` includes a contents list;
2883
+ `docs authoring <section-slug>` extracts a section. `docs spec --list` lists chapter and subsection
2884
+ slugs, and `docs spec <slug>` extracts one. Doctor checks Node, executable browsers, writable cache,
2885
+ module wiring, nearest project validity, the default playground port 4519, and Chromium mobile/touch
2886
+ emulation. Authentication is explicitly not applicable because Anima has no accounts.
2887
+
2888
+ `anima render` selects a component with `--component name`, using the same inherited assets,
2889
+ data, converters and component declarations as the web component root selector. Scripts address
2890
+ the selected root's own inputs and nodes. The selector accepts the same original, qualified and
2891
+ legacy component names as the web host. Ambiguous names are usage errors listing qualified
2892
+ alternatives; unknown names are usage errors.
2893
+ `--viewport WxH` sets positive integer output dimensions (default `--size` square).
2894
+ `--fit contain` (default) centres a uniform fit, `cover` centres and crops, `fill` stretches,
2895
+ and `none` centres the authored view box with one output pixel per view-box unit.
2896
+ An explicit background covers the entire output viewport, including letterbox regions.
2897
+ `--scale k` accepts finite `0 < k ≤ 1`, rounds each output dimension to the nearest integer,
2898
+ with a minimum of one pixel. Fit is computed before downscaling. These options apply to single
2899
+ frames; contact sheets reject viewport, fit and scale overrides.
2900
+
2901
+ `--probe 'x,y;x,y'` reports `{x,y,rgba:[r,g,b,a]}` for each point as one JSON object containing
2902
+ `probes`. Coordinates are zero-based integer **output pixels after viewport and scale**, with
2903
+ origin at the top left; channels are 0–255 including alpha. Out-of-bounds or malformed points
2904
+ are usage errors. Probes can be requested without an output file.
2905
+
2906
+ `--audio out.wav` mixes script audio events at their exact occurrence within each step, with clip start/end and per-event
2907
+ and document volume into stereo, 48 kHz signed 16-bit PCM using Chromium's `OfflineAudioContext`.
2908
+ The output duration is the player's final time, at least one sample; clips are truncated there.
2909
+ Audio sources must be local or base64 data URIs. Assets and document gains resolve in the emitting
2910
+ player’s catalog, including inherited and shadowed component/repeat catalogs. Tooling captures source
2911
+ clock crossing fractions before loop folding; the script session’s `onEvents` callback receives
2912
+ absolute times and the emitting model via symbol metadata. Ordinary event JSON and shipped browser
2913
+ bundles are unchanged. Unresolved assets fail explicitly.
2914
+ The Chromium launch is lazy and always closed, including failures. Missing/unlaunchable Chromium
2915
+ reports `build-encoder-unavailable` with a browser installation hint. It is not silently replaced
2916
+ by a muted WAV.
2917
+
2918
+ `anima compare a.png b.png [--tolerance 0..255] [-o diff.png]` compares decoded RGBA without
2919
+ resizing. Different dimensions fail. JSON reports `maxChannelDifference`,
2920
+ `meanChannelDifference` (absolute differences averaged over all four channels of all pixels),
2921
+ `differingPixels` (any channel differs), `pixelsBeyondTolerance` (any channel exceeds tolerance)
2922
+ and `pass`. The diff is opaque grayscale with the largest channel delta per pixel. Exit 1 means
2923
+ comparison failure; invalid tolerance, argument count or output extension is exit 2.
2924
+
2925
+ `anima schema --search words` searches registered node types, structural topics and fields
2926
+ (including both spellings), and the diagnostic table in this spec. Each query term scores 100
2927
+ for an exact name, 10 for a name substring, or 1 for a description substring. Scores add; zero
2928
+ scores are omitted. Results sort by descending score then ascending name (code-point order).
2929
+ `anima schema type.field` returns the matching registry field by JSON or markup spelling.
2930
+
2931
+ `anima inspect --summary` returns a wiring table (JSON: `{wiring:[…]}`) of unbound inputs,
2932
+ unused component/model/property declarations, listener targets and unreachable states. Each
2933
+ finding has code, pointer, message and hint. This is a conservative static aid: dynamic host writes
2934
+ and imported/dynamically selected components may require human interpretation. Invalid documents
2935
+ still report normal loader diagnostics rather than a partially linked graph.
2936
+
2937
+ `anima doctor` checks Node ≥24, cached Chromium/Firefox executables, cache write permission and,
2938
+ inside a repository containing `tools/modules.ts` and `spec/MODULES.md`, generated module
2939
+ wiring with `node tools/modules.ts --check`. It exits 1 if any check fails and supplies repair hints.
2940
+ It does not install browsers or rewrite wiring.
2941
+
2942
+ `anima new directory --sample bell|moon|progress` creates a fresh project directory containing
2943
+ `main.animl`, supporting local libraries, `anima.json` and agent instructions. An existing directory
2944
+ is never overwritten. Existing `new --glyph`/`--glyphy` single-document scaffolding is unchanged.
2945
+ `anima build [file] --out-dir directory` (or project `out`) exports canonical `.ani`, embedded local
2946
+ assets, `anima.js` and an `index.html` with inline document JSON. It performs no upload or package
2947
+ publication. HTML script text escapes `<` to preserve the raw-text boundary.
2948
+
2949
+ The renderer uses SVG viewports and the existing rasterizer; browser services supply audio.
2950
+ This retains the browser-host split discussed in `docs/research/rive-web-runtime.md` without
2951
+ copying runtime code or adopting a binary/native publishing path.
2952
+
2953
+ ### Browser host stop and diagnostics
2954
+
2955
+ <!-- capability: host-api.stop -->
2956
+ `icon.stop()` pauses playback and the shared clock, preserving the current frame.
2957
+ `icon.play()` resumes from that frame without accounting for stopped wall time.
2958
+ This host-only operation adds no JSON/AniML grammar, loader diagnostics or CLI operation.
2959
+
2960
+ <!-- capability: host-api.fps-counter -->
2961
+ <!-- capability: host-api.performance-marks -->
2962
+ <!-- capability: host-api.deprecation-policy -->
2963
+ The optional browser-only `devtools` contract is specified in
2964
+ [modules/devtools/SPEC.md](../modules/devtools/SPEC.md). It adds no document grammar.
2965
+
2966
+ ## a11y
2967
+
2968
+ Node `a11y` supports `progressbar`, `meter`, `status`, and a local semantic-node
2969
+ `controls` relation (Anima extensions without inventory rows). Progressbar/meter use
2970
+ numeric bound `value`, min/max (defaults 0/100); omit progressbar value for indeterminate
2971
+ progress. Status defaults to polite live announcements. `controls` maps to `aria-controls`.
2972
+ Dialog `modal` and button/link/group/list-item `expanded` already accept bool literals,
2973
+ inputs or data paths. `anima docs a11y` includes recipes; `anima schema a11y`
2974
+ shows the complete registry vocabulary. These are node fields, separate from root §10 a11y.
2975
+
2976
+ Node semantics are specified in [modules/a11y/SPEC.md](../modules/a11y/SPEC.md). Interaction,
2977
+ diffs and focus policy live in the companion `a11y-actions` (runtime key `a11yactions`); in-browser
2978
+ AniML forms in the `markup-a11y` web entry. The optional `a11y-dom` web entry supplies the platform overlay,
2979
+ including canvas positioning through the optional canvas backend's node-box adapter. Synthesized
2980
+ semantic focus targets use world geometry-box centers; directional candidates exclude ancestors
2981
+ of the current target. Unchanged semantic reads reuse retained dependencies before collecting the
2982
+ scene. Geometry/visibility, binding and owner dependencies govern recollection; paint-only changes and
2983
+ unchanged list steps preserve it. See the module contracts and `spec/conformance/a11y-spatial-focus`. Root §10 is unchanged.
2984
+
2985
+ ### Native composite roles and synchronous actions
2986
+
2987
+ Node roles include `toolbar`, `grid`, `row` and `gridcell`. With a11y-actions and
2988
+ focus, gridcells are implicit focus targets; the three structural roles are not.
2989
+ The optional `@anima-format/web/a11y-navigation` entry includes the DOM overlay and
2990
+ adds one tab stop per toolbar/grid. Toolbar arrows on both axes and Home/End use
2991
+ the tab-list roving policy. A grid contains semantic rows of cells: Left/Right stay
2992
+ within a row; Up/Down select the same authored column in the adjacent row (missing
2993
+ or unavailable cells retain focus). Home/End select row endpoints; Ctrl+Home/End
2994
+ select grid endpoints. Row `focus.edge` controls horizontal boundaries: stop,
2995
+ closed-loop within the row, or parent-scope using existing focus traversal.
2996
+ Vertical boundaries stop. Page Up/Down bubble unprevented for the page to handle.
2997
+ See [the native navigation contract](../modules/a11y-dom/SPEC.md) for availability,
2998
+ modifier and ordering rules. Module-owned conformance assertions are in
2999
+ `modules/a11y/test/core/issues.test.ts`, with the paired `navigation` example.
3000
+
3001
+ Accepted host semantic actions synchronously step(0) through the scheduled-step
3002
+ path: all resulting events (including nested component events) and state changes
3003
+ are dispatched as `anima:event` and `anima:state`, just as for `icon.fire()`.
3004
+ A focus-disabled scope retains its semantic role and subtree; enabled descendants
3005
+ remain eligible for native focus and roving.
3006
+
3007
+ <!-- capability: a11y.tree-bounds -->
3008
+ ### Semantic bounds
3009
+
3010
+ `nodes[].a11y.bounds` (AniML `bounds`) is a local `[x,y,w,h]` box with nonnegative width and height,
3011
+ transformed to world-grid coordinates. Without it, visible child boxes are unioned
3012
+ for unpainted groups; an empty node reports its transformed origin as a point.
3013
+ The optional painted calculator shared by tooling and the browser overlay bounds trimmed pieces
3014
+ with their caps, viewport-sized non-scaling strokes and feather falloff; it intersects clips with
3015
+ source geometry boxes and masks with source painted boxes. Explicit boxes win. Dash bounds
3016
+ intentionally use the undashed stroke (conservative), and text uses measured logical line boxes
3017
+ rather than ink, matching DOM text rectangles and screen-reader highlights. The full contract,
3018
+ including inverted-mask complements and finite three-sigma feather support, is specified in
3019
+ [modules/a11y/SPEC.md](../modules/a11y/SPEC.md#painted-geometric-bounds-p7-8).
3020
+ Native semantic rectangles omit stroke; the existing P7-8 capture records this intentional native
3021
+ difference in `tests/oracle/p7-8-semantic-bounds/`.
3022
+
3023
+
3024
+ <!-- capability: a11y.property-heading-level -->
3025
+ ### Heading hierarchy
3026
+
3027
+ A node with `a11y.role: "heading"` accepts `level` as an integer 1–6 or a
3028
+ number input/data path. AniML uses `<a11y role="heading" level="2"/>`; a bound
3029
+ level uses a JSON string attribute, for example `level="&quot;depth&quot;"`.
3030
+ Bound values must remain integers 1–6. Unknown names report `unknown-input`,
3031
+ wrong types `input-type`, and invalid literal levels `bad-value`, at that field's
3032
+ JSON pointer. See [the node semantics contract](../modules/a11y/SPEC.md), its
3033
+ `a11y-property-heading-level` conformance case and paired capability example.
3034
+
3035
+
3036
+ <!-- capability: a11y.focus-condition -->
3037
+ ### Focus transition condition
3038
+
3039
+ With `a11y-actions` and `focus`, a machine guard operand `{ "focus": "id" }`
3040
+ is true for the primary target and focusable semantic ancestors. Documents without
3041
+ node semantics retain exact-target focus conditions. The existing machine guard
3042
+ registry and AniML JSON `guard` attribute own both spellings and unknown-target
3043
+ validation. See `spec/conformance/a11y-focus-condition` and
3044
+ [the node semantics contract](../modules/a11y/SPEC.md). Repeated targets are covered by the module tests; six native PNG stages independently verify
3045
+ ancestor conditions, as recorded in the closure report.
3046
+
3047
+ ## data-host
3048
+
3049
+ <!-- capability: transforms.computed-transform-values -->
3050
+ <!-- capability: data.host-nested-paths -->
3051
+ <!-- capability: host-api.nested-models -->
3052
+ The optional `data-host` entry adds read-only computed geometry; the host API also resolves
3053
+ nested paths through `at(path)` and discovers authored exports with `exportedInstances()`.
3054
+ Host components, colors and batching use the existing data vocabulary in both syntaxes.
3055
+ Bindings can use validated `asset("name")` literals with the `datahost` feature.
3056
+ The normative contract is [modules/data-host/SPEC.md](../modules/data-host/SPEC.md).
3057
+
3058
+ <!-- capability: input.write-key-pressed -->
3059
+ <!-- capability: input.write-pointer-x -->
3060
+ <!-- capability: input.write-pointer-y -->
3061
+ <!-- capability: input.write-pointer-delta-x -->
3062
+ <!-- capability: input.write-pointer-delta-y -->
3063
+ With `input data`, JSON `write: "$x", value: "pointer-x"` is AniML
3064
+ `write="$x" value="pointer-x"`. A write map also accepts these source names as keys.
3065
+ Coordinates are grid units; deltas are from the preceding event for the same pointer, initially
3066
+ zero. `key-pressed` stores the triggering `KeyboardEvent.key` name (a string), on any selected
3067
+ key phase. Missing event fields leave data unchanged. Wrong/read-only destination types or
3068
+ unknown sources report `input-write` at `/listeners/<i>/write/<source>` with a type hint.
3069
+ Both data properties and ordinary inputs of the matching type are writable. See
3070
+ [input](../modules/input/SPEC.md) and its independent conformance/oracle probes.
3071
+
3072
+ ## Tool font processing (P6-2)
3073
+
3074
+ The CLI's optional font tooling adds no browser runtime code. Text measurements use the contract
3075
+ in `design/p6/DESIGN.md` §4: cumulative advances after each `Intl.Segmenter('und')` grapheme,
3076
+ kerning included, in grid units. Tracking is inserted between graphemes. Metrics are in grid units;
3077
+ descent is positive below the baseline. The tool uses OS/2 typographic metrics when USE_TYPO_METRICS
3078
+ is set, otherwise hhea metrics; absent cap/x heights fall back to ascent / half ascent. It never
3079
+ selects a system font for deterministic measurement. Missing registered families are `font-missing`.
3080
+ A family list selects the first registered face; style and nearest weight select among local faces.
3081
+ The weight supplies `wght` unless an explicit axis overrides it. Axis coordinates clamp to fvar bounds;
3082
+ avar maps normalized coordinates and gvar tuples vary outlines and phantom-point advances.
3083
+
3084
+ Tool sources are local TTF/OTF/WOFF/WOFF2 or base64 data URIs. Font collections, CFF2, HVAR metric
3085
+ variations and Type2 arithmetic/seac operators are currently unsupported. CFF1 Type2 contours,
3086
+ subroutines and CID FDSelect 0/3 are supported, along with TrueType simple and composite glyphs.
3087
+ WOFF uses bounded zlib decompression; WOFF2 uses bounded Brotli and inverse glyf/loca/hmtx transforms.
3088
+ The reader maps cmap 4/12, reads hhea/hmtx, OS/2, head, maxp, name, kern and GPOS kern pair adjustments
3089
+ 1/2 (including extension lookups). Pair placement and advances are supported; a lookup whose
3090
+ IgnoreBaseGlyphs/IgnoreLigatures/IgnoreMarks flag matches either glyph's GDEF class does not
3091
+ adjust that pair. Every cmap mapping, across all subtables, counts toward the table operation limit
3092
+ (`font-limit`). GPOS device/variation adjustments, mark placement and GSUB
3093
+ shaping are not. Arabic, Hebrew, Indic, combining marks and joining controls issue
3094
+ `text-shaping-approximate`; the tool does not claim shaped glyph equivalence.
3095
+
3096
+ `anima sample`, `validate`, `render` and the conformance runner install the tool measurer when text
3097
+ is installed. `--font family=path` registers or overrides local faces. Font asset names, explicit
3098
+ family names and name-table family names are aliases. The optional text module supplies
3099
+ `setTextMeasurer(TextMeasurer)` (or `setMeasurer`) from `modules/text/core/measure.ts`.
3100
+
3101
+ `render` converts SVG text/tspan output into glyph outline paths before the existing rasterizer.
3102
+ It preserves transforms, clipping and paints; scalar x/y/dx/dy and numeric grid-unit sizes are
3103
+ supported. SVG position lists, nonzero glyph rotation and RTL outline conversion currently give
3104
+ `font-unsupported`. `render --browser-text` explicitly selects cached Chromium for PNG shaping and
3105
+ rasterization; SVG output embeds local faces for browser display. It uses the image encoder's lazy
3106
+ browser and closes it after use. System-font fallback is browser-dependent in this explicit mode.
3107
+
3108
+ Font assets may request `glyphs: "all" | "used" | "custom"` (AniML `glyphs`), plus `chars` for custom
3109
+ coverage; these keys are registered by the fonts module. All preserves every glyph. Used includes
3110
+ literal authored text and string `default`/`value`/`placeholder` fields, including bound defaults.
3111
+ Custom includes each Unicode scalar in `chars`. Subsetting embeds an sfnt TTF/OTF asset even without `--embed`.
3112
+ TrueType subsetting retains glyph IDs, .notdef, composite dependencies and kerning/variation tables,
3113
+ removes unused glyf payloads and replaces cmap coverage. With GSUB present it retains every outline
3114
+ conservatively. CFF1 subsetting retains glyph IDs and subroutines, replaces unused charstrings with
3115
+ empty glyphs, and relocates offset-bearing top/FD/private dictionaries and INDEXes. Unsupported
3116
+ retained Type2 glyph operators diagnose instead of discarding implicit glyph dependencies.
3117
+
3118
+ `anima import-font <source> -o local.ttf|otf [--chars text]` normalizes a container to sfnt and emits
3119
+ font asset metadata. `--google "Family:wght@400;600"` instead explicitly fetches Google Fonts CSS
3120
+ and its font files at build time. Multiple sources use numbered output files. `--chars` requests
3121
+ custom coverage and local subsetting. Network, invalid CSS/font bytes and unsupported forms are
3122
+ errors; imports never require a network request at runtime. Downloads and font decompression are
3123
+ limited to 32 MiB; malformed offsets and bounded recursion produce structured diagnostics rather
3124
+ than uncaught CLI exceptions.
3125
+
3126
+ `anima flatten <file> [--node id] [-o out.ani|animl]` freezes authored/default text into editable path
3127
+ groups, retaining surrounding nodes and common transforms. Remaining references to text-only
3128
+ properties are rejected instead of silently dropped. Unsupported SVG features are rejected by the
3129
+ conversion. The text module must be installed for text documents; layout semantics remain owned by
3130
+ that module. The committed `fixtures/fonts/anima-test.ttf` is original generated test data;
3131
+ `node tools/test-font.ts` reproduces it deterministically. Third-party fonts are not fixtures.
3132
+
3133
+ Tool font diagnostics carry severity, code, JSON pointer, message and hint: `font-invalid`,
3134
+ `font-format`, `font-limit`, `font-unsupported`, `font-source`, `font-missing`, `font-option`,
3135
+ `font-glyphs`, `font-download`, `font-flatten`, `font-contract`, `text-shaping-approximate`.
3136
+
3137
+ The independent implementation follows the [OpenType specification](https://learn.microsoft.com/en-us/typography/opentype/spec/)
3138
+ and [WOFF2 specification §5](https://www.w3.org/TR/WOFF2/#compressed-data-format), not another reader's
3139
+ source code. `docs/research/rive-format-and-graph.md` §1's bounded-reader pitfall informs the
3140
+ allocation limits; `docs/research/glyphy-compat.md` §1's grid-unit convention informs the tool boundary.
3141
+
3142
+ ## fonts
3143
+
3144
+ <!-- capability: assets.font -->
3145
+ <!-- capability: assets.host-decode-font -->
3146
+ <!-- capability: assets.replacement-scope-font -->
3147
+ <!-- capability: data.host-font -->
3148
+ <!-- capability: host-api.data-font -->
3149
+ <!-- capability: host-api.font-fallback -->
3150
+ <!-- capability: text.font-fallback -->
3151
+ <!-- capability: text.font-assets -->
3152
+ <!-- capability: text.font-decoding -->
3153
+ <!-- capability: text.variable-axes -->
3154
+ <!-- capability: text.opentype-features -->
3155
+ <!-- capability: text.font-metrics -->
3156
+
3157
+ Optional runtime feature `fonts` uses the assets feature's existing `type: "font"` records.
3158
+ JSON `{assets: {body: {type: "font", source: "body.woff2"}}}` is AniML
3159
+ `<asset name="body" type="font" src="body.woff2"/>`; declare `requires="assets fonts"`.
3160
+ No new asset fields or deterministic player channels are added. Core accepts font resources without
3161
+ a browser; decoding is a web concern. This keeps the optional asset boundary described in
3162
+ `docs/research/rive-web-runtime.md` without adopting its shaping engine or resource format.
3163
+
3164
+ <!-- // seam(P7-8): optional font introspection normative link. -->
3165
+ Decoded metrics, coverage, effective options and derived glyph paths are specified in the
3166
+ [optional fonts-info contract](../modules/fonts/INFO.md).
3167
+
3168
+ ### Resources, decoding and readiness
3169
+
3170
+ The `@anima-format/web/fonts` entry loads font assets before `anima:load` and the first render.
3171
+ Fonts and asset I/O preserve each other's preload hooks in either entry order.
3172
+ Sources pass through `icon.resolveAsset`, including its local/any policy and host resolver.
3173
+ URLs and resolver-returned Blobs are read as bytes, decoded using `FontFace.load`, added to
3174
+ `document.fonts`, then awaited with `document.fonts.load`. Unsupported or corrupt fonts and
3175
+ policy failures emit `anima:warning` with a diagnostic array: code `font-load`, severity `warning`,
3176
+ JSON pointer `/assets/<escaped-name>/source`, message and recovery hint. The scene continues with
3177
+ CSS fallback. A failed replacement preserves the previous face. Browser decoding does not change
3178
+ `Player.step` values, timing or events; headless tools do not decode fonts through this module.
3179
+
3180
+ `await icon.fonts.decode(bytesOrURL, descriptors?)` returns a loaded FontFace. URLs obey the asset
3181
+ policy; direct bytes are explicit host input. `await icon.fonts.set(name, faceOrBytesOrURL,
3182
+ descriptors?)` replaces the named family for every consumer in that icon scope. Byte and URL
3183
+ sources receive private family names, preventing cross-icon collisions. The latest replacement
3184
+ wins; cleanup invalidates pending work. Faces stay retained until scope cleanup, except that a
3185
+ completed replacement releases the superseded face once no name in that scope uses its family. Faces already in
3186
+ `document.fonts` before adoption are never deleted by cleanup. Shared adopted faces use reference
3187
+ counts. Child component catalogs use `icon.fonts.forModel(model)` to distinguish same-named assets.
3188
+ Reload/dispose clears owned resources; imported pages without `requires: ["fonts"]` do not decode
3189
+ font assets.
3190
+
3191
+ ### CSS, fallback and metrics
3192
+
3193
+ `cssFont(spec)` accepts the structural FontSpec contract and returns CSS properties `font`,
3194
+ `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `letterSpacing`, `fontVariationSettings`, and
3195
+ `fontFeatureSettings`. The `font` property is the canvas/FontFaceSet shorthand. `axes` and `features`
3196
+ become quoted OpenType tags followed by numeric values; absent maps become `normal`.
3197
+ For live asset aliases and host fallback, browser consumers MUST use
3198
+ `icon.fonts.cssFont(spec)` (or `icon.fonts.forModel(model).cssFont(spec)` for children). Apply the
3199
+ individual CSS properties to SVG text; canvas consumers apply `font` and the supported individual
3200
+ properties. The text module owns field validation, layout, animation, measurement and backend
3201
+ application. A controller does not synthesize axis or feature introspection from font bytes.
3202
+
3203
+ `await icon.fonts.ready(spec, text?)` awaits the used CSS font selection before measuring the actual
3204
+ run text. `await icon.fonts.add(face, {weight}?)` adds a host fallback face and optional weight;
3205
+ faces sharing a family remain eligible for browser weight selection. Families are appended in
3206
+ insertion order after the authored CSS family list. Host fallback configuration survives source
3207
+ reloads (including configuration before the first load); `icon.fonts.clear()` forgets it. Cleanup
3208
+ releases owned document font registrations; the next CSS/readiness use restores configured faces.
3209
+ With the `fonts-text` consumer, a disconnect likewise parks named asset faces and aliases (including
3210
+ child scopes); the same player's next `forModel`/CSS use restores them, a reload discards them.
3211
+ `icon.fonts.revision` increases on font changes and cleanup. Text measurement/layout caches must
3212
+ invalidate on that revision even when a replacement preserves its CSS family name. Glyph coverage and fallback selection are
3213
+ browser behavior. Metrics come from the text module's browser measurer after readiness; no guessed
3214
+ metrics, coverage table, cap-height/x-height parser or outline extractor is supplied by `fonts`.
3215
+
3216
+ ### Data assignment and replacement scope
3217
+
3218
+ With the optional data and host modules, `await icon.data.font(path).set(face)` accepts a decoded
3219
+ FontFace. It allocates a player-local font resource id and queues the typed asset-reference write
3220
+ through the existing host API. Other data properties and other icons keep their previous resources.
3221
+ String assignment retains the existing typed font-asset validation. The public handle value remains
3222
+ the selected resource id. Text styles must resolve that id through the owning controller. Named
3223
+ resource replacement changes all uses in its resource scope; data-property assignment changes only
3224
+ consumers of the assigned property. Bound text rendering and complete font-metric parity require
3225
+ the text module; the fonts module alone does not add text nodes.
3226
+
3227
+ ## text
3228
+
3229
+ The optional `text` module adds styled, data-bound text nodes, measured layout, SVG/canvas
3230
+ drawing and named host run access. Its normative fields, defaults, validation, measurement
3231
+ contract, fit/overflow algorithms and verification limits are specified in
3232
+ [modules/text/SPEC.md](../modules/text/SPEC.md). Registry entries derive JSON Schema, CLI schema
3233
+ and AniML spellings from the same vocabulary. P6 parity remains planned pending native evidence.
3234
+
3235
+ <!-- capability: text.flatten -->
3236
+ ### Text outline conversion
3237
+
3238
+ <!-- // seam(P7-6): normative CLI behavior belongs to the owned text specification. -->
3239
+ `anima flatten` traverses root and component-definition text, preserves clips and common group
3240
+ properties, and rejects remaining text-only references. See [the normative text contract](../modules/text/SPEC.md#text-outline-conversion-p7-6).
3241
+
3242
+ ## text-effects
3243
+
3244
+ <!-- capability: text.background -->
3245
+ <!-- capability: text.modifier-follow-path -->
3246
+ <!-- capability: text.modifier-opacity -->
3247
+ <!-- capability: text.modifier-origin -->
3248
+ <!-- capability: text.modifier-rotation -->
3249
+ <!-- capability: text.modifier-scale -->
3250
+ <!-- capability: text.modifier-translation -->
3251
+ <!-- capability: text.modifier-variation -->
3252
+ <!-- capability: text.range-combine -->
3253
+ <!-- capability: text.range-easing -->
3254
+ <!-- capability: text.range-falloff -->
3255
+ <!-- capability: text.range-run -->
3256
+ <!-- capability: text.range-units -->
3257
+ <!-- capability: text.script-direction -->
3258
+
3259
+ The optional `texteffects` feature (`web/text-effects`) adds logical per-cluster translation,
3260
+ rotation, scaling, opacity, origins and font-axis effects; scoped glyph/word/line ranges with
3261
+ falloff, easing and combination; path positions/tangents; and run backgrounds. The text node's
3262
+ JSON `effects` array is AniML `text-effects` or structural `<text-effect>` children, with nested
3263
+ `<text-range>` children. `direction` maps whole runs to native SVG/canvas bidi.
3264
+
3265
+ [modules/text-effects/SPEC.md](../modules/text-effects/SPEC.md) defines defaults, precise range
3266
+ weights and matrix composition, animation addresses, validation, backend limits and evidence.
3267
+ The registry drives schema, CLI and both syntax spellings. Thirteen raw native captures are retained; accepted
3268
+ comparisons remain unresolved and these rows remain planned. Bidi's measured addition is 150 B min+gzip (owner guard: <=150 B).
3269
+
3270
+ <!-- capability: text.script-hit-testing -->
3271
+ <!-- capability: a11y.inferred-text -->
3272
+ ### Text hit testing and inferred semantics (P6)
3273
+
3274
+ The optional `text-hit` host adapter exposes grapheme hit positions and caret/selection boxes
3275
+ from the shared text layout. `input` listeners may write `text-index` on a text target. The
3276
+ optional `a11y-text` browser adapter supplies visible text names and layout bounds to the semantic
3277
+ tree; headless tools install this adapter with a11y. Existing JSON/AniML text, label, bounds and
3278
+ listener fields are unchanged. See [the normative text semantics contract](../modules/a11y/TEXT.md).
3279
+
3280
+ ## textinput
3281
+
3282
+ The heavy DOM-backed field feature is specified in [text input](../modules/text-input/SPEC.md).
3283
+ Its browser entry is `text-input`; its registry feature is `textinput` (module feature names cannot contain hyphens).
3284
+ Fields extend fixed text nodes. Registry descriptors define both spellings; no base compiler branch is added.
3285
+
3286
+
3287
+ <!-- capability: text.input-text -->
3288
+ ### Independent field text drawable
3289
+
3290
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3291
+
3292
+ <!-- capability: text.input-editable -->
3293
+ ### Editable text fields
3294
+
3295
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3296
+
3297
+ <!-- capability: text.input-cursor -->
3298
+ ### Styled input caret
3299
+
3300
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3301
+
3302
+ <!-- capability: text.input-selection -->
3303
+ ### Styled input selection
3304
+
3305
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3306
+
3307
+ <!-- capability: text.input-selection-rounding -->
3308
+ ### Rounded selection geometry
3309
+
3310
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3311
+
3312
+ <!-- capability: text.input-focus-selection -->
3313
+ ### Select all on focus
3314
+
3315
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3316
+
3317
+ <!-- capability: text.input-ime -->
3318
+ ### IME and dead-key text entry
3319
+
3320
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3321
+
3322
+ <!-- capability: text.input-clipboard -->
3323
+ ### Text clipboard integration
3324
+
3325
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3326
+
3327
+ <!-- capability: text.input-limits -->
3328
+ ### Text input limits
3329
+
3330
+ Text fields accept the bindable bool address `<text>.textInput.obscured`; its rest
3331
+ value is authored `obscured` or false. Resolved masking, semantic redaction and the
3332
+ native input type change together, preserving selection and caret. Static
3333
+ multiline+obscured remains a configuration error; bound obscuring is ineffective
3334
+ on multiline fields.
3335
+
3336
+ `caretBlink` (`caret-blink`, default true) alternates 530 ms visible / 530 ms
3337
+ hidden only for a focused browser field, restarting visible on edits and caret
3338
+ moves. Reduced motion, paused/static and CLI rendering keep it steady. Blink
3339
+ repaints do not advance Player time or keep an unfocused scene awake.
3340
+
3341
+ `selectionTextColor` (`selection-text-color`) colors selected glyphs over the
3342
+ `selectionColor` highlight; omission preserves foreground. The older
3343
+ `selectedTextColor` remains an alias; the new spelling wins if both are present.
3344
+
3345
+ The optional `text-input-extra` browser entry supplies blinking and scroll to
3346
+ caret: horizontal for single-line fields (which never wrap), vertical for
3347
+ multiline fields. Glyphs, highlight and caret clip to the fixed field box; the
3348
+ viewport translation does not reflow layout. Tool/CLI rendering includes the
3349
+ pure viewport projection and a steady caret.
3350
+
3351
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3352
+
3353
+ <!-- capability: text.input-multiline -->
3354
+ ### Multiline text input
3355
+
3356
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3357
+
3358
+ <!-- capability: text.input-obscured -->
3359
+ ### Obscured text input
3360
+
3361
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3362
+
3363
+ <!-- capability: text.input-placeholder -->
3364
+ ### Text input placeholder
3365
+
3366
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3367
+
3368
+ <!-- capability: host-api.text-input -->
3369
+ ### Text and IME bridge
3370
+
3371
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3372
+
3373
+ <!-- capability: host-api.clipboard-text -->
3374
+ ### Text selection for clipboard
3375
+
3376
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3377
+
3378
+ <!-- capability: input.text-listener -->
3379
+ ### Text input listeners
3380
+
3381
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3382
+
3383
+ <!-- capability: input.write-text -->
3384
+ ### Store text
3385
+
3386
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3387
+
3388
+ <!-- capability: a11y.role-text-field -->
3389
+ ### Semantic role: textField
3390
+
3391
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3392
+
3393
+ <!-- capability: a11y.state-multiline -->
3394
+ ### Semantic state: Multiline
3395
+
3396
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3397
+
3398
+ <!-- capability: a11y.state-obscured -->
3399
+ ### Semantic state: Obscured
3400
+
3401
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3402
+
3403
+ <!-- capability: a11y.text-proxy -->
3404
+ ### Accessible text proxy
3405
+
3406
+ See [the normative field contract](../modules/text-input/SPEC.md) for both spellings, selection, commits, native semantics, diagnostics and tooling. Tests live in `modules/text-input/test/`; native-reference closure remains planned.
3407
+
3408
+
3409
+ ## Host fit and artboard size (`host-fit`)
3410
+
3411
+ `@anima-format/web/host-fit` is an optional **host-only** entry. It adds no document
3412
+ keys, `requires` name, registry entries, AniML attributes or CLI render options. An
3413
+ AniML scene and its compiled `.ani` have identical host-fit behaviour; put host
3414
+ attributes on `<anima-icon>`, not on `<anima>`. Existing static CLI fit options are
3415
+ independent. This separation follows the host/scene distinction in
3416
+ `docs/research/rive-web-runtime.md` (input mapping and accessibility overlay).
3417
+
3418
+ The HTML `fit` and `alignment` attributes have reflecting string properties with the
3419
+ same names. `fit` accepts `contain`, `cover`, `fill`, `fit-width`, `fit-height`,
3420
+ `none`, `scale-down`, `layout`; absent/unknown means contain. `alignment` accepts
3421
+ `top-left`, `top-center`, `top-right`, `center-left`, `center`, `center-right`,
3422
+ `bottom-left`, `bottom-center`, `bottom-right`. Legacy `align` remains an alias;
3423
+ `alignment` takes precedence when both exist. `top`, `left`, `right`, `bottom`
3424
+ are accepted aliases for the middle edge positions. Unknown alignment means center.
3425
+ The properties return the attribute spelling, or `contain`/`center` when absent.
3426
+
3427
+ With no fit, alignment or size request, loading the entry preserves the existing SVG
3428
+ grid/bleed viewport and default host aspect ratio (B8). An explicit host-fit request
3429
+ fits the whole artboard rectangle into the CSS content box, including any nonzero
3430
+ viewBox origin. It clips drawing to that viewport in both backends. For a source
3431
+ rectangle `(x,y,w,h)` and host `W×H` CSS pixels, scale is:
3432
+
3433
+ | fit | scale |
3434
+ | --- | --- |
3435
+ | contain (including unknown) | `s = min(W/w,H/h)` |
3436
+ | cover | `s = max(W/w,H/h)` |
3437
+ | fill | `sx = W/w`, `sy = H/h` |
3438
+ | fit-width | `s = W/w` |
3439
+ | fit-height | `s = H/h` |
3440
+ | none | `s = 1` CSS pixel per grid unit |
3441
+ | scale-down | `s = min(1,W/w,H/h)` |
3442
+ | layout | `s = 1`; artboard size becomes `W×H` grid units |
3443
+
3444
+ Uniform fits use `sx=sy=s`. For alignment fractions `ax,ay ∈ {0,0.5,1}`, the source
3445
+ to host matrix is `[sx,0,0,sy,(W-w*sx)*ax-x*sx,(H-h*sy)*ay-y*sy]`. Negative free
3446
+ space aligns cropping by the same rule. SVG expresses this matrix with an equivalent
3447
+ virtual viewBox and `preserveAspectRatio="none"`; canvas uses the identical virtual
3448
+ viewBox before DPR scaling. Neither requires a transformed scene group. Pointer
3449
+ coordinates invert that matrix; semantic bounds use it forwards. Resize, fit,
3450
+ alignment, size overrides and renderer switches update these consumers together.
3451
+ Zero-sized hosts defer fitting until they become positive-sized. Disconnect and
3452
+ destroy stop resize observation; reconnect resumes it.
3453
+
3454
+ `icon.size` reads a detached `[width,height]` array of grid units (without a loaded
3455
+ document or explicit override, `[0,0]`). Assign a pair of finite positive numbers to override the
3456
+ artboard dimensions for this host, retaining its authored origin. The scene and shared compiled model
3457
+ are never mutated; another host is unaffected. Invalid writes preserve the previous
3458
+ size and dispatch `anima:error` with diagnostic `host-size`, pointer `""` (host API),
3459
+ message and hint. `resetSize()` removes the override. The override persists across
3460
+ loads on the same host. In layout fit, the host dimensions take precedence; resetting
3461
+ size clears the explicit override but keeps responsive sizing while layout is active.
3462
+
3463
+ <!-- seam(P7-4): real per-player root layout integration. -->
3464
+
3465
+ **Layout integration:** fitting writes `player.layoutSize?: [w,h]` per
3466
+ player, where undefined means authored root width/height. A layout provider consumes
3467
+ this override for root containers without mutating model defaults and exposes
3468
+ `player.layoutBox(id)`. On a changed override, the host calls `player.step(0)` when
3469
+ that reader exists, then redraws. The scale for layout fit is one CSS pixel per grid
3470
+ unit; DPR changes only the canvas backing store. Leaving layout restores the explicit
3471
+ size if set, otherwise authored root dimensions. The layout module now consumes the override for
3472
+ root containers through their normal bounds. Both engines compare real responsive child drawing
3473
+ against native layout-fit captures at two host sizes, in SVG and canvas.
3474
+
3475
+ ## dataartboard — bindable component data
3476
+
3477
+ <!-- capability: data.host-artboard -->
3478
+ <!-- capability: host-api.bindable-artboards -->
3479
+ <!-- capability: host-api.data-artboard -->
3480
+ <!-- capability: components.host-artboard-reference -->
3481
+
3482
+ The normative contract is [modules/data-artboard/SPEC.md](../modules/data-artboard/SPEC.md):
3483
+ `artboard` model properties, `instance component="$content"`, queued host handles and
3484
+ values referencing components from another loaded document. Re-instantiation follows
3485
+ the existing nested-player and data-context rules; a changed selection restarts the
3486
+ child clock, and the same selection retains it. The four deterministic probes are
3487
+ `data-host-artboard`, `host-api-bindable-artboards`, `host-api-data-artboard`, and
3488
+ `components-host-artboard-reference`. Host comparison limits are evidenced in
3489
+ [the native comparison note](../modules/data-artboard/NATIVE.md).
3490
+
3491
+ ## layout
3492
+
3493
+ `requires: ["layout"]` adds single-line row/column containers, child sizing and a deterministic
3494
+ layout stage. [The normative layout contract](../modules/layout/SPEC.md) defines both spellings,
3495
+ the CSS-based algorithm, units, caching, geometry, guard timing, diagnostics and intentional native
3496
+ differences. Animated reflow belongs to the separate `layout-reflow` entry.
3497
+
3498
+ <!-- capability: layout.containers -->
3499
+
3500
+ <!-- capability: layout.flex-direction -->
3501
+
3502
+ <!-- capability: layout.flex-justify -->
3503
+
3504
+ <!-- capability: layout.flex-align-items -->
3505
+
3506
+ <!-- capability: layout.self-alignment -->
3507
+
3508
+ <!-- capability: layout.alignment -->
3509
+
3510
+ <!-- capability: layout.spacing -->
3511
+
3512
+ <!-- capability: layout.fixed-sizing -->
3513
+
3514
+ <!-- capability: layout.hug-sizing -->
3515
+
3516
+ <!-- capability: layout.fill-sizing -->
3517
+
3518
+ <!-- capability: layout.size-bounds -->
3519
+
3520
+ <!-- capability: layout.visibility -->
3521
+
3522
+ <!-- capability: layout.participants -->
3523
+
3524
+ <!-- capability: layout.intrinsic -->
3525
+
3526
+ <!-- capability: layout.paint -->
3527
+
3528
+ <!-- capability: data.bind-layout -->
3529
+
3530
+
3531
+ <!-- seam(P7-4): optional animated-reflow contract and host-fit closure. -->
3532
+ <!-- capability: layout.animated-reflow -->
3533
+ ### Animated layout reflow
3534
+
3535
+ The normative [layout reflow contract](../modules/layout/SPEC.md#animated-reflow-layout-reflow)
3536
+ defines container/participant `reflow`, §5 easing, inherited settings, uninterrupted retargets,
3537
+ reduced motion and the target/drawn box readers. The optional `layout-reflow` web entry owns
3538
+ interpolation and diagnostics; CLI tooling includes it. Deterministic target/drawn observations
3539
+ are in `layout-animated-reflow`, with allocated text measurement in `layout-text-wrap`.
3540
+ Native timing and retarget mapping remain pending; raw captures are retained without a new tolerance.
3541
+
3542
+ <!-- capability: host-api.fit-layout -->
3543
+ ### Responsive host layout
3544
+
3545
+ `<anima-icon fit="layout">` / `icon.fit = "layout"` uses the host's CSS content size as root layout
3546
+ dimensions, at one CSS pixel per grid unit (DPR affects backing pixels only). Root layout reflows;
3547
+ it is not merely scaled. `player.layoutSize` is the per-player grid-unit override; clearing it
3548
+ restores authored sizing. The host adapter applies normal explicit-size/reset precedence as above.
3549
+ A host whose computed content size is not a resolved pixel length (for example `display:none` with a
3550
+ percentage size) is not measured; its layout size and viewport are kept until it has a layout box.
3551
+ Browser conformance and byte-exact native references at two host sizes live in the host-fit tests.
3552
+ This host-only control adds no scene registry keys, AniML attributes or static CLI rendering flag.
3553
+
3554
+ ## mesh
3555
+
3556
+ <!-- capability: rigging.mesh -->
3557
+
3558
+ `requires: ["mesh", "assets", "paint"]` adds `mesh` to `image` nodes only. Other hosts
3559
+ are `rig-mesh-host`, at the node's `/mesh` pointer. Meshes are opt-in heavy modules.
3560
+
3561
+ JSON `mesh` is either `{ "grid": [cols, rows] }` or
3562
+ `{ "vertices": [[x, y, u, v], ...], "triangles": [[a, b, c], ...] }`.
3563
+ The AniML spelling is the registry-generic JSON-valued `mesh` attribute, for example
3564
+ `<image ... mesh='{"grid":[4,2]}'/>`; no additional compiler entry is required.
3565
+ Choose exactly one form (`bad-value`). Grid counts are integer cell counts, each 1..128.
3566
+ Vertices are row-major, from top left, including both boundary rows/columns.
3567
+ Cell `(x,y)` has indices `a=y*(cols+1)+x`, `b=a+cols+1`, and triangles
3568
+ `[a,a+1,b+1]`, `[a,b+1,b]`. A 4×2 grid has 15 vertices and 16 triangles.
3569
+
3570
+ Grid vertices span the image box `x0,y0,width,height`. Their UVs invert the image's
3571
+ fit/align rectangle (the Image nodes contract), so a rest mesh reproduces a plain
3572
+ image, including transparent letterboxing. Automatically generated UVs can extend
3573
+ outside 0..1; the finite texture rectangle supplies transparent space there.
3574
+ Untracked grid vertices follow animated width/height; a vertex track or binding
3575
+ replaces its position in local grid units. Explicit meshes ignore fit and align.
3576
+ They need at least three `[x,y,u,v]` vertices; explicit u/v must lie in 0..1
3577
+ (`bad-value`). The mesh needs at least one triangle. Triangle indices must be
3578
+ integers in `[0,vertexCount)` (`rig-mesh-index`). Degenerate UV triangles draw
3579
+ nothing; collapsed destination triangles have zero area.
3580
+
3581
+ `drawFrame` publishes `DrawNode.mesh = {points, uv, triangles}`: points and UVs
3582
+ are flat coordinate arrays, triangles are index triples. Geometry also retains
3583
+ the ordinary image box and asset. If installed, `player.skinPoints(node, points)`
3584
+ deforms the resolved animated positions in place before publication (P8-4 seam).
3585
+
3586
+ The optional `@anima-format/web/canvas-mesh` entry draws triangles by affine
3587
+ `drawImage`, with their clip edges expanded outwards by 0.5 backing-store pixel.
3588
+ It owns a per-renderer image cache and extends the existing renderer readiness promise;
3589
+ image decode requests a redraw without stepping time. Canvas itself adds no mesh code.
3590
+ The retained browser SVG keeps the undeformed image and emits one warning per
3591
+ node per player: `unsupported-render`, feature `mesh`, pointer `/nodes/.../mesh`,
3592
+ with a hint to use `renderer="canvas"` and import `canvas-mesh`.
3593
+
3594
+ The tooling-only `toSVG` export from `@anima-format/core/mesh-static` accepts
3595
+ `meshTriangles: true`: each triangle becomes a clipPath plus an affine image.
3596
+ CLI `render` always enables this option. The default tooling serializer rejects mesh output with `unsupported-render`;
3597
+ the retained browser SVG keeps its undeformed fallback. No triangle serializer
3598
+ enters web bundles.
3599
+
3600
+ <!-- capability: rigging.mesh-animation -->
3601
+
3602
+ Each vertex has a tuple address `<image>.mesh.vertex.<k>` = `[x,y]`, with canonical
3603
+ zero-based indices. Tracks accept finite two-number arrays in JSON. AniML track
3604
+ text uses quoted JSON point literals, e.g. `0 "[10,5]"; 0.5 "[9.5,3.25]"`;
3605
+ the mesh value compiler decodes these to tuples. Invalid points are `value-type`.
3606
+ Sampling and state crossfades interpolate both coordinates linearly with the
3607
+ track/blend ease, preserving signed and fractional coordinates (no colour clamp
3608
+ or rounding). Public values and script snapshots expose arrays.
3609
+
3610
+ With `bind`, `[x-expression, y-expression]` binds a vertex from two scalar numeric
3611
+ expressions; nested scalar calls are supported, e.g. `[x, max(5,y)]`. Invalid tuple
3612
+ syntax is `bind-type`, with the binding's JSON pointer and a correction hint.
3613
+ Binding is post-state, following the existing bind stage; it overrides track output.
3614
+ Without mesh in requires, no mesh per-frame work is installed.
3615
+
3616
+ Pointer hit testing and semantic bounds of a meshed image use its image box, not the deformed
3617
+ triangles, whether the vertices are animated, bound or skinned.
3618
+
3619
+ Independent draw expectations cover grid and explicit meshes, fit/align UVs and
3620
+ fractional animated/bound vertices. Native captures use the original generated
3621
+ flag texture at 12 px/unit; browser pixels compare to the CLI triangle render.
3622
+ Probe locations: `spec/conformance/rigging-mesh{,-animation}` and
3623
+ `tests/oracle/rigging-mesh{,-animation}`. Oracle budgets remain pending.
3624
+
3625
+ Decisions follow `design/p8/DECISIONS.md` D21–D24 and D35, the Canvas2D mesh gap
3626
+ in `docs/research/rive-format-and-graph.md` §4.3, and backend separation/lifecycle
3627
+ notes in `docs/research/rive-web-runtime.md`. No native source was used.
3628
+
3629
+ ## rig
3630
+
3631
+ `requires: ["rig"]` adds bones and ordered transform constraints. The browser imports
3632
+ `@anima-format/web/rig`; distance constraints also import the optional
3633
+ `@anima-format/web/rig-distance`. Both entries are heavy and dependency-free.
3634
+ AniML uses `<bone>` and constraint child elements; JSON uses `type: "bone"` and an
3635
+ ordered `constraints: [{kind, ...}]` array. Generic JSON-valued constraint attributes
3636
+ are deliberately absent; structural children preserve authored order.
3637
+
3638
+ ### Bone frames and rigid art
3639
+
3640
+ A bone draws nothing and accepts `children`, `length` (nonnegative, default 0),
3641
+ `root` (static boolean, default false), and the common transform/visibility fields.
3642
+ Length, rotation, uniform/axis scales, opacity and visibility animate normally.
3643
+ Its pivot is always its start: authoring `pivot` is `rig-bone-pivot`.
3644
+
3645
+ Outside another bone a bone starts at `(x,y)` and is a root. Inside a bone it
3646
+ starts at its parent's current tip, with rotation relative to that parent's
3647
+ frame; `x` or `y`, even 0, is `rig-bone-offset`. A nested `root: true` instead
3648
+ starts at `(x,y)` in its parent's moving grid coordinates, anchoring a new chain.
3649
+ Per D38, a bone may be at document/component root level, or have a `group` or
3650
+ `bone` parent. Every other parent, including `layout`, `repeat`, `instance` and
3651
+ `text`, is `rig-bone-parent` at the bone's pointer, with hint
3652
+ "wrap the bones in a <group>". A group within layout can host a skeleton. A node that takes no
3653
+ children at all (a shape such as `circle` or `rect`) fails structurally first, with
3654
+ `unknown-key` at its `children`; the rig module adds nothing to core validation (D47).
3655
+
3656
+ Per D39, zero rest `scale`, `scaleX` or `scaleY` on a bone or any ancestor is
3657
+ `bad-value` at the affected bone, with a hint to use nonzero rest scales. Animation may
3658
+ collapse a bone from a nonzero rest pose.
3659
+
3660
+ Rest frames are computed once from document values, never the first sampled
3661
+ pose. A chained bone's frame is its parent's frame multiplied by
3662
+ `T(parent.length,0) R(rotate) S(scale*scaleX,scale*scaleY)`.
3663
+ A root uses its parent's grid transform multiplied by
3664
+ `T(x,y) R(rotate) S(scale*scaleX,scale*scaleY)`.
3665
+ Rigid child geometry remains in rest grid coordinates: the bone contributes
3666
+ `F(now) F(rest)^-1`, keeping every bone's drawing transform identity at rest
3667
+ (relative to its non-bone ancestors). A nested root uses this same rule.
3668
+ This preserves Glyphy geometry without re-origining (DECISIONS D3–D6;
3669
+ `docs/research/glyphy-compat.md`). A bone's world frame is its physical start
3670
+ frame, while the published drawing matrix is its rest-relative deformation.
3671
+
3672
+ ### Constraint fields and evaluation
3673
+
3674
+ All five kinds accept numeric `strength` in 0..1, default 1, animatable and
3675
+ bindable. Fields are addressed `<id>.constraints.<index>.<field>`; arrays are
3676
+ zero-based, ordered, and later full-strength constraints override earlier ones.
3677
+ All fields shown as numbers below are animatable/bindable. Static controls are
3678
+ booleans, references, enumerations and space choices.
3679
+
3680
+ | JSON / AniML | Fields and defaults |
3681
+ | --- | --- |
3682
+ | `copyPosition` / `<copy-position>` | optional `from`; `factorX`, `factorY` = 1; `copyX`, `copyY` = true; optional `minX`, `maxX`, `minY`, `maxY`; `offset` = false; `fromSpace`, `toSpace` = world; `limitSpace` defaults to `toSpace` |
3683
+ | `copyRotation` / `<copy-rotation>` | optional `from`; `factor` = 1; optional `min`, `max` in degrees; `offset` = false; spaces as above |
3684
+ | `copyScale` / `<copy-scale>` | position fields, operating on scale axes |
3685
+ | `copyTransform` / `<copy-transform>` | required `from`; optional numeric `origin: [u,v]`; `fromSpace`, `toSpace` = world |
3686
+ | `keepDistance` / `<keep-distance>` | required `to` and nonnegative `distance`; `mode`: within (default), beyond, exact |
3687
+
3688
+ Camel-case field names use hyphenated AniML attributes (`factor-x`, `from-space`,
3689
+ `limit-space`, etc.). The CLI accepts JSON kind names as schema topics as well
3690
+ as canonical AniML element names. Every kind validates only its declared fields.
3691
+ Unknown references are `rig-target` at the constraint, with a repair hint.
3692
+
3693
+ Position is the transformed pivot (the start for a bone). Rotation and scales
3694
+ come from QR decomposition: nonnegative X scale, signed Y scale and shear.
3695
+ Derived rotations are normalized to [-180,180). A zero X axis uses the Y axis
3696
+ for its angle. Blend decomposes around the same pivot, interpolates position,
3697
+ scale and shear, and rotates along the shortest arc; the positive half-turn
3698
+ wins an exact tie. Strength 0 skips, 1 replaces. Each partial constraint reads
3699
+ the preceding constraint's result, never the previous frame's result.
3700
+
3701
+ For copy constraints, source-space decomposition reads the target world frame
3702
+ (`world`) or the target relative to its own parent (`local`). With no `from`,
3703
+ world reads the parent's frame and pivot; local reads identity (D17).
3704
+ The copied components replace the host's corresponding components in destination
3705
+ space; disabled axes keep their current values. Factors multiply source values.
3706
+ `offset` adds the host's document-rest position/rotation, or multiplies its
3707
+ rest scale. Limits clamp the result in `limitSpace`, without changing uncopied
3708
+ components, then convert back to world. When limit and destination space match,
3709
+ limits act on the signed copied axes before canonical decomposition.
3710
+
3711
+ `copyTransform` copies position, rotation, scales and shear together. By default
3712
+ the target point is its pivot. `origin` selects a normalized point over the
3713
+ target's rest geometric bounds (no stroke); its transformed point replaces the
3714
+ host's pivot position. The point tuple uses the shared `point` value type and is
3715
+ animatable and bindable as one value. JSON track keys use `[u,v]`; AniML authors
3716
+ the rest tuple as `origin="u v"` and tuple track keys as `[u,v]`.
3717
+
3718
+ Rest bounds are evaluated from authored geometry before animation. Paths, circles,
3719
+ arcs, rects, ellipses, polygons, stars, triangles, contours, images, text and
3720
+ layout boxes contribute their geometric bounds. A container target recursively
3721
+ unions descendant geometry after each descendant's authored rest transform is
3722
+ mapped into the target's local space; structural nodes with no drawing geometry
3723
+ contribute no box. Stroke width is never included. Browser builds that use a
3724
+ non-default `copyTransform.origin` must install the optional heavy `rig-origin`
3725
+ entry; otherwise loading reports `rig-origin` at the origin field with a repair
3726
+ hint.
3727
+
3728
+ Distance constraints measure between world pivots and preserve the host's
3729
+ rotation, scale and shear. Within enforces a maximum radius, beyond a minimum,
3730
+ and exact the ring. A point already allowed is unchanged; a coincident point
3731
+ needing a nonzero radius moves along world +X. Strength uses the same transform
3732
+ blend as every other constraint. Distance is in grid units.
3733
+
3734
+ ### Rig stage and extension contract
3735
+
3736
+ After bindings, clamp and layout, solve nodes in a dependency order computed
3737
+ once at load: parents and targets before hosts; a solver's declared additional
3738
+ writes before their other descendants. Add constraints in authored order; a
3739
+ cycle is `rig-cycle` at the constraint closing it, with a hint to move its target
3740
+ outside the constrained subtree. No dirty-restart loop runs (D2, D15;
3741
+ `docs/research/rive-format-and-graph.md` §3 and
3742
+ `docs/research/rive-animation-and-state.md` §2.3).
3743
+
3744
+ Each pass starts from the layout-adjusted authored local values. It publishes
3745
+ per-player local matrices for rendering, hits, accessibility and pointer mapping.
3746
+ A pose pad reads that first solved result via `Model.rigPose(player)`; returning
3747
+ true performs exactly one more pass, with freshly posed values. Unchanged values
3748
+ and layout matrices skip the first solve, retaining its previous solved result;
3749
+ the pose callback may still request its second pass. Layout/guards never read
3750
+ same-step pose outputs. Rigging does not keep a settled player awake.
3751
+
3752
+ `modules/rig/core/api.ts` exposes `registerConstraint({kind, shape, targets,
3753
+ writes?, solve})` and `RigContext`. Independently bundled modules register through
3754
+ the active rig module's `registerConstraint` during `init`. `world`/`rest` use
3755
+ bone start frames for bones and grid matrices for ordinary nodes; `position`
3756
+ returns the world pivot. `setWorld` publishes a solver's declared writes;
3757
+ `num(node,k,key,fallback)` reads current constraint slots. Solvers return a desired
3758
+ host matrix for central strength blending, or explicitly blend multiple writes
3759
+ through `ctx.blend`. Each player owns its solved frames and local overrides.
3760
+
3761
+ Snapshots and `expect` select derived, read-only
3762
+ `<id>.world.x|y|rotate|scaleX|scaleY` and `<bone>.tip.x|y`.
3763
+ They are not animation destinations, binding destinations, or guard operands.
3764
+
3765
+ ### Evidence by capability
3766
+
3767
+ <!-- capability: rigging.bone-chains -->
3768
+ Independent numeric evidence: `spec/conformance/rigging-bone-chains/` (0, 0.5, 1 s).
3769
+ Oracle capture/integration: `tests/oracle/rigging-bone-chains/`; a pending marker is not a parity pass.
3770
+
3771
+ <!-- capability: rigging.root-bones -->
3772
+ Independent numeric evidence: `spec/conformance/rigging-root-bones/` (0, 0.5, 1 s).
3773
+ Oracle capture/integration: `tests/oracle/rigging-root-bones/`; a pending marker is not a parity pass.
3774
+
3775
+ <!-- capability: rigging.constraint-order -->
3776
+ Independent numeric evidence: `spec/conformance/rigging-constraint-order/` (0, 0.5, 1 s).
3777
+ Oracle capture/integration: `tests/oracle/rigging-constraint-order/`; a pending marker is not a parity pass.
3778
+
3779
+ <!-- capability: rigging.constraint-strength -->
3780
+ Independent numeric evidence: `spec/conformance/rigging-constraint-strength/` (0, 0.5, 1 s).
3781
+ Oracle capture/integration: `tests/oracle/rigging-constraint-strength/`; a pending marker is not a parity pass.
3782
+
3783
+ <!-- capability: rigging.translation -->
3784
+ Independent numeric evidence: `spec/conformance/rigging-translation/` (0, 0.5, 1 s).
3785
+ Oracle capture/integration: `tests/oracle/rigging-translation/`; a pending marker is not a parity pass.
3786
+
3787
+ <!-- capability: rigging.rotation -->
3788
+ Independent numeric evidence: `spec/conformance/rigging-rotation/` (0, 0.5, 1 s).
3789
+ Oracle capture/integration: `tests/oracle/rigging-rotation/`; a pending marker is not a parity pass.
3790
+
3791
+ <!-- capability: rigging.scale -->
3792
+ Independent numeric evidence: `spec/conformance/rigging-scale/` (0, 0.5, 1 s).
3793
+ Oracle capture/integration: `tests/oracle/rigging-scale/`; a pending marker is not a parity pass.
3794
+
3795
+ <!-- capability: rigging.transform -->
3796
+ Independent numeric evidence: `spec/conformance/rigging-transform/` (0, 0.5, 1 s).
3797
+ Oracle capture/integration: `tests/oracle/rigging-transform/`; a pending marker is not a parity pass.
3798
+
3799
+ <!-- capability: rigging.distance-closer -->
3800
+ Independent numeric evidence: `spec/conformance/rigging-distance-closer/` (0, 0.5, 1 s).
3801
+ Oracle capture/integration: `tests/oracle/rigging-distance-closer/`; a pending marker is not a parity pass.
3802
+
3803
+ <!-- capability: rigging.distance-further -->
3804
+ Independent numeric evidence: `spec/conformance/rigging-distance-further/` (0, 0.5, 1 s).
3805
+ Oracle capture/integration: `tests/oracle/rigging-distance-further/`; a pending marker is not a parity pass.
3806
+
3807
+ <!-- capability: rigging.distance-exact -->
3808
+ Independent numeric evidence: `spec/conformance/rigging-distance-exact/` (0, 0.5, 1 s).
3809
+ Oracle capture/integration: `tests/oracle/rigging-distance-exact/`; a pending marker is not a parity pass.
3810
+
3811
+ ## posepad
3812
+
3813
+ <!-- capability: rigging.joystick -->
3814
+ <!-- capability: animation.joystick-two-axis -->
3815
+ <!-- capability: animation.joystick-invert -->
3816
+ <!-- capability: animation.joystick-handle -->
3817
+
3818
+ `requires: ["posepad"]` enables `type: "posepad"`, AniML `<posepad>`. It draws nothing
3819
+ and has no hit area. The identical lowercase node name follows the existing node registry;
3820
+ no node-name alias or markup compiler extension is needed (P8 orchestrator decision).
3821
+
3822
+ | JSON | AniML | Meaning |
3823
+ |---|---|---|
3824
+ | `timelineX`, `timelineY` | `timeline-x`, `timeline-y` | Optional declared timeline names |
3825
+ | `valueX`, `valueY` | `value-x`, `value-y` | Numbers, default 0, clamped to −1..1; animatable/bindable |
3826
+ | `invertX`, `invertY` | `invert-x`, `invert-y` | Static booleans, default false |
3827
+ | `handle` | `handle` | Optional node id; declares the optional handle entry requirement |
3828
+ | `box` | `box` | `[x0, y0, width, height]` in pad coordinates, positive width/height |
3829
+
3830
+ After layout, pads run in authored node order, X before Y. Clamp control v to −1..1,
3831
+ negate when its axis is inverted, then sample at `(v + 1) / 2 * timeline.duration`.
3832
+ Sampling uses the active registry's track sampler (§5), including typed tracks, custom
3833
+ eases and per-player data key values. Loop/work-area/playback controls do not change this
3834
+ whole-duration mapping. Pad sampling never plays clocks or emits timeline events.
3835
+
3836
+ Sampled tracks form a per-player overlay above layers and bindings. Clamp numeric results
3837
+ to each target property's registry range; later axes/pads win collisions. `prop`, `num`,
3838
+ `value`, `values()` and snapshots expose the overlay. It does not mutate model rest values,
3839
+ tracks or another player's state. Layout sees the previous step's overlay; guards never
3840
+ read same-step posed output. Drawing revisions are invalidated for sampled target nodes.
3841
+ A pad introduces no ongoing clock and does not prevent settling.
3842
+
3843
+ A handle requires the optional heavy `@anima-format/web/posepad-handle` entry in addition
3844
+ to `@anima-format/web/posepad`. Declaring `handle` without that entry reports `posepad-handle`
3845
+ at the handle field, with an import hint (verbose builds); it never silently poses freely.
3846
+ Tooling includes handle support. A handle also requires `box`. Read the handle's current
3847
+ world pivot (including motion's animated pivot), map it through the inverse pad world matrix,
3848
+ then derive `valueX = clamp(2*(hx-x0)/width-1, -1, 1)` and similarly for Y. These outputs
3849
+ are uninverted; inversion affects sampling only. A singular pad matrix yields centered outputs.
3850
+ Both outputs are included in snapshots even when no track references them. Animate or bind
3851
+ the handle, not these outputs. Pointer drags use the input/bind modules' existing queued writes.
3852
+
3853
+ With `rig`, chain `model.rigPose` between its solve passes, returning true when the pad wrote;
3854
+ rig solves again exactly once. Without rig, chain a standalone `afterStep` stage after layout
3855
+ and read `p.world`. The distance-gated handle conformance probe remains pending until rig lands.
3856
+
3857
+ Diagnostics: `posepad-timeline` at `/nodes/…/timelineX|timelineY` names an unknown timeline;
3858
+ `posepad-handle` at `/nodes/…/handle` means an unknown handle or missing handle entry;
3859
+ `posepad-box` at `/nodes/…/box` means the required box is absent. Structural box errors use
3860
+ `bad-type` or `bad-value` at the offending field/coordinate. `posepad-output` points
3861
+ at a bind, track or state-pose write to a handle-driven value. `posepad-cycle` at the pad's
3862
+ axis timeline rejects a write to its handle, any handle ancestor, any transitive constraint
3863
+ target (and its ancestors), or its own coordinate frame/ancestors. These restrictions keep
3864
+ the bounded solve/pose/solve order independent of a dirt-restart loop.
3865
+
3866
+ Design rationale: [animation/state research](../docs/research/rive-animation-and-state.md) §2.3
3867
+ and [format/graph research](../docs/research/rive-format-and-graph.md) §3.2–3.4 describe update
3868
+ restarts; this explicit stage retains deterministic step order. No Rive source or binary format
3869
+ is used. Native oracle captures await orchestrator-owned budgets and remain pending evidence.
3870
+
3871
+ ## ik
3872
+
3873
+ <!-- capability: rigging.ik -->
3874
+ ### Inverse kinematics
3875
+
3876
+ The optional `ik` module registers the `ik` constraint kind with `rig`. In JSON an IK constraint is
3877
+ an item in a bone's `constraints` array:
3878
+
3879
+ ```json
3880
+ {"kind":"ik","target":"aim","bones":2,"bend":"cw","strength":1}
3881
+ ```
3882
+
3883
+ AniML writes the same object as a child element:
3884
+
3885
+ ```xml
3886
+ <ik target="aim" bones="2" bend="cw" strength="1"/>
3887
+ ```
3888
+
3889
+ `target` is a required node id and the target point is that node's world pivot. `bones` is a static
3890
+ integer of at least 1, defaults to 1, and counts the entire solved chain including the bone carrying
3891
+ the constraint. Thus `bones=1` aims only the host bone and `bones=2` solves the host plus its parent.
3892
+ This intentionally differs from Rive's parent-count field. `strength` is the common rig constraint
3893
+ strength: it is animatable and bindable at `<bone>.constraints.<k>.strength`, defaults to 1, and is
3894
+ clamped to 0..1. `target`, `bones`, and `bend` are structural fields.
3895
+
3896
+ The solve uses scaled world-space bone lengths and never changes a length. For one bone, the bone's
3897
+ direction aims from its start toward the target. For two bones, an analytic law-of-cosines solve
3898
+ places the joint on the selected bend side. When the target is farther away than the sum of the
3899
+ chain lengths, every bone straightens toward the target. A target coincident with the chain start
3900
+ leaves the current pose unchanged.
3901
+
3902
+ For chains longer than two bones, Anima independently reimplements the multi-link strategy in
3903
+ Rive's MIT runtime `src/constraints/ik_constraint.cpp`: in FK order, each of the first `n-1` links
3904
+ performs one analytic two-link solve against the final tip, with the downstream subchain rotated as
3905
+ a rigid remainder. The iteration count is therefore fixed by the chain length (`n-1`) and has no
3906
+ convergence test or early exit. No Rive source text or binary format is used. This choice fixes the
3907
+ otherwise non-unique multi-link solution while keeping results deterministic across runtimes.
3908
+
3909
+ The IK kind declares every solved ancestor bone through `writes()`, so rig dependency ordering runs
3910
+ the target first and the chain's descendants after the rewritten bones. The solver executes in the
3911
+ normal rig stage described in `## rig`. IK strength mixes only each chain bone's local rotation from
3912
+ its unconstrained value toward the fully solved value, using the shortest angular arc. The chain is
3913
+ then rebuilt in parent-to-child order from those mixed local rotations, so every child start remains
3914
+ at its parent's tip for every strength from 0 through 1. Bone lengths, local scale and local
3915
+ translation are unchanged. Strength 0 is the unconstrained pose and strength 1 is the full IK solve.
3916
+
3917
+ Validation adds two IK-specific errors in addition to rig's shared `rig-target` for an unknown
3918
+ target. `rig-ik-host` points at `/nodes/.../constraints/<k>` when `ik` is attached to a non-bone and
3919
+ hints to move it to the chain tip. `rig-ik-chain` points at the constraint's `/bones` field when the
3920
+ requested count is longer than the available parent-bone chain and hints to reduce the count or add
3921
+ parent bones. Using an IK constraint without declaring `ik` in `requires` is `undeclared-feature`.
3922
+ `anima schema ik` exposes `target`, `bones`, `bend`, and `strength` through the registered AniML
3923
+ element descriptor.
3924
+
3925
+
3926
+ <!-- capability: rigging.ik-bend -->
3927
+ ### IK bend direction
3928
+
3929
+ `bend` is `cw` (the default) or `ccw`. For an unmirrored chain, `cw` places the two-link joint on
3930
+ the screen-clockwise side of the directed line from the chain start to the target; `ccw` selects the
3931
+ opposite analytic solution. A negative-determinant ancestor mirrors that side so authored bend
3932
+ direction follows the mirrored chain. The same side choice is used for each deterministic pair solve
3933
+ in a chain longer than two bones.
3934
+
3935
+ ## pathfollow
3936
+
3937
+ The optional `pathfollow` module extends rig constraints with normalized outline following. Documents
3938
+ using these constraints declare both `rig` and `pathfollow` in `requires`. Path measurement is an
3939
+ internal runtime detail of this module; it does not add public APIs.
3940
+
3941
+ <!-- capability: rigging.follow-path -->
3942
+ ### Follow path constraint
3943
+
3944
+ JSON `{"kind":"alongPath","path":"track",...}` and AniML `<along-path path="track" .../>` place
3945
+ the constrained node's pivot on the target outline. `path` is required and names a measurable
3946
+ `path`, `contour`, `circle`, `rect`, or `arc`. `at` is an animatable normalized distance and defaults
3947
+ to 0. Open outlines clamp `at` to 0..1. Closed outlines wrap it modulo 1, so 1 and 0 name the same
3948
+ point and values outside the interval continue around the outline. `orient` defaults to false; when
3949
+ true, the constrained node's world rotation becomes the sampled tangent angle, with 0 degrees along
3950
+ +x. `offset` defaults to false; when true, the node's animated translation away from its authored
3951
+ rest translation is added after path placement. The inherited rig `strength` field defaults to 1 and
3952
+ blends the old and desired world transforms.
3953
+
3954
+ The target is a rig dependency and is measured in its current resolved local geometry, then its world
3955
+ transform is applied to the sampled point and tangent. Path data includes current morph values.
3956
+ Measurement uses the same cubic subdivision as `Shared outline lengths`: absolute segments, arc
3957
+ conversion into at-most-90-degree cubic pieces, recursive de Casteljau subdivision at t=0.5 until
3958
+ control-polygon length minus chord is at most 0.0001 grid units or depth 20, and the mean of polygon
3959
+ and chord for each accepted piece. The cumulative table supplies both arc-length position and tangent.
3960
+ Circles, arcs and rounded rectangles follow their equivalent outline geometry. A skinned target is a
3961
+ known limitation: following uses its undeformed outline rather than the deformed skin result.
3962
+
3963
+ If `path` resolves to a node that is not measurable, loading reports `rig-path-target` at the exact
3964
+ constraint `/path` field, with a message that the target is not a measurable shape and a hint to use
3965
+ a path, contour, circle, rect or arc. Unknown ids continue to use rig's normal target diagnostic.
3966
+ Constraint fields are addressable as `<node>.constraints.<k>.<field>` and participate in the normal
3967
+ rig solve order.
3968
+
3969
+ <!-- capability: rigging.list-follow-path -->
3970
+ ### List follow-path constraint
3971
+
3972
+ JSON `{"kind":"spreadAlongPath","path":"track",...}` and AniML `<spread-along-path
3973
+ path="track" .../>` distribute the constrained node's direct children along an outline. On a
3974
+ `repeat`, the same rule applies to its live reconciled items after list reconciliation. `start`,
3975
+ `end`, and `shift` are animatable normalized distances with defaults 0, 1, and 0. For child index
3976
+ `k` among `n` children, the sampled fraction is
3977
+ `start + (end - start) * k / max(n - 1, 1) + shift`. Therefore a single child uses `start + shift`.
3978
+ Open outlines clamp each resulting fraction and closed outlines wrap it by the same rule as
3979
+ `alongPath`. `orient` defaults to false and applies the sampled tangent rotation independently to
3980
+ each child. The inherited `strength` field blends each child's current and desired world transform.
3981
+
3982
+ A changing repeat count immediately recomputes all item fractions without rebuilding rig solve order.
3983
+ `spreadAlongPath` uses the same measurable-target set, `rig-path-target` diagnostic, private
3984
+ measurement table, transform handling, and undeformed-skinned-target limitation as `alongPath`.
3985
+
3986
+ ## skin
3987
+
3988
+ The optional `skin` module deforms authored geometry from bone rest frames. Documents using it declare
3989
+ `skin` and `rig` in `requires`, plus the ordinary feature modules needed by the host geometry. A
3990
+ `skin` is valid on a `path` backed by inline `d` or a glyph, a `contour`, or an `image` that also has
3991
+ a `mesh`. Any other host reports `rig-skin-host` at the node's `/skin` pointer with a hint to move the
3992
+ skin to supported geometry. A path whose `d` is animated or posed while skinned reports
3993
+ `rig-skin-morph`; bone animation is the supported way to deform a skinned path.
3994
+
3995
+ <!-- capability: rigging.skin -->
3996
+ ### Bind-pose skinning
3997
+
3998
+ JSON authors `skin: {"bones":[ids], "weights"?: [...]}`. AniML authors the same object structurally:
3999
+ `<skin bones="upper fore" weights='[...]'/>` as a child of the host node. `bones` is a
4000
+ whitespace-separated list; `weights`, when present, is one JSON array. The compiler and decompiler
4001
+ round-trip this child form through the registry. Bone ids must name bone nodes. An unknown id, a
4002
+ non-bone id, or an explicit influence that is not listed in `skin.bones` reports `rig-weight-bone`
4003
+ with the precise bone or influence pointer and a repair hint.
4004
+
4005
+ Binding uses the bones' document rest frames. No bind matrices are authored. For a node-local rest
4006
+ point `p`, let `W0` be the node's rest-world transform and let `Bi(rest)` and `Bi(now)` be a bone's
4007
+ rest and solved world transforms. Each bone contributes `Di = Bi(now) · Bi(rest)^-1`; `@rest`
4008
+ contributes identity. The deformed local point is
4009
+ `p' = W0^-1 · Σ_i wi · Di · W0 · p`. A non-invertible rest transform leaves the source point
4010
+ unchanged. The host draw transform remains its rest-world transform, so animated transforms on the
4011
+ skinned node and its ancestors do not add a second motion source and stroke width follows rest scale.
4012
+
4013
+ Path points are every M/L/C endpoint in order; Z adds no point. For a cubic, the first control is the
4014
+ previous anchor's `out` handle and the second control is the current anchor's `in` handle. Contour
4015
+ points are vertices; their handles deform before contour rounding is reconstructed. Mesh points are
4016
+ every mesh vertex. `draw.frame` publishes the deformed path/contour geometry, while meshed images use
4017
+ the `Player.skinPoints(node, xy)` seam before mesh points are published. Paint outline queries use the
4018
+ deformed path or contour, so pointer hit testing, semantic bounds, trims, and relative dashes observe
4019
+ the deformed outline rather than the bind outline.
4020
+
4021
+ The read-only derived address `<node>.skin.point.<k>` returns the deformed anchor/vertex in world
4022
+ coordinates. It is a snapshot/conformance output, not an authored animation target.
4023
+
4024
+ <!-- capability: rigging.weights -->
4025
+ ### Vertex bone weights
4026
+
4027
+ With explicit weights, `weights` has exactly one entry per path anchor, contour vertex, or mesh
4028
+ vertex. Each entry has `point`, an influence map from a bone id or `@rest` to a number in 0..1.
4029
+ Influence maps may contain at most four entries; more report `rig-weight-count` with a hint to keep at
4030
+ most four. A sum differing from 1 by more than 1e-6 reports `rig-weight-sum` as a warning and the
4031
+ runtime normalizes the map. A non-positive sum resolves to a full `@rest` influence after the warning.
4032
+
4033
+ When `weights` is absent, the loader computes deterministic automatic weights independently for each
4034
+ anchor and handle. It measures world-space distance from the rest point to every listed bone segment
4035
+ from bone start to its authored `length`, chooses the two nearest segments, assigns each
4036
+ `1 / (d + 0.001)^2`, and normalizes the two values. With one listed bone, that bone receives weight 1.
4037
+ The result is cached per player/rest geometry and does not change as the bones animate.
4038
+
4039
+ <!-- capability: rigging.cubic-weights -->
4040
+ ### Independent handle weights
4041
+
4042
+ An explicit weight entry may use `{ "point": {...}, "in": {...}, "out": {...} }`. `point` is
4043
+ required. `in` and `out` are independently validated and normalized by the same count, sum, and bone
4044
+ rules. If either handle map is omitted, that handle inherits its anchor's `point` map. This lets cubic
4045
+ handles bend independently while preserving the simpler anchor-only spelling for ordinary paths.
4046
+
4047
+ ## scroll
4048
+
4049
+ <!-- capability: rigging.scroll -->
4050
+ ### Scroll rig constraint
4051
+
4052
+ The optional `scroll` module extends rig with `scroll` and `scrollThumb` constraints. A `scroll`
4053
+ constraint is authored on the content node as `{kind:"scroll", viewport, axis, offsetX?, offsetY?,
4054
+ interactive?}`; AniML uses `<scroll viewport="…" axis="x|y|both" offset-x="…" offset-y="…"
4055
+ interactive="…"/>`. `viewport` is required, `axis` is `x`, `y` or `both`, offsets default to zero in
4056
+ grid units, and `interactive` defaults to true. The viewport extent is its layout box when it is a
4057
+ layout container, otherwise its rest local bounds. Content extent is the content node's layout box
4058
+ when present, otherwise the rest bounds of its subtree. Each enabled offset clamps to
4059
+ `[0, max(0, content - viewport)]` and translates the content by the negative clamped offset. A
4060
+ disabled axis resolves to zero.
4061
+
4062
+ Offsets are ordinary bindable/animatable constraint addresses. Interactive offsets are per-player
4063
+ state. Pointer and scroll input only queue a proposed state; phase 1 of the next `Player.step` applies
4064
+ the queue before rig solving. A primary-button drag that starts inside the viewport adds the pointer
4065
+ delta on enabled axes. Wheel or trackpad input over the viewport adds its delta and is handled only
4066
+ when clamping permits an actual offset change, so the host may scroll normally at an end stop. If an
4067
+ `offsetX` or `offsetY` address is bound or animated in the current step, that authored value wins over
4068
+ interactive state for that axis. The derived read-only snapshot addresses `percentX` and `percentY`
4069
+ are the clamped offset divided by its content range, or zero when the range is zero. They are output
4070
+ addresses only and are not guard operands.
4071
+
4072
+ `scrollThumb` is authored on a thumb node as `{kind:"scrollThumb", scroll, track, axis, resize?}` or
4073
+ `<scroll-thumb scroll="…" track="…" axis="x|y" resize="…"/>`. `scroll` names a content node carrying
4074
+ a `scroll` constraint and `track` names the track node. The thumb position follows the linked
4075
+ `percentX` or `percentY` across the available track travel. With `resize=true`, thumb length is
4076
+ `trackLength * viewportLength / contentLength`, clamped to the track and never smaller than the
4077
+ authored thumb length. Dragging the thumb maps pointer travel back to the linked offset.
4078
+
4079
+ A missing or unmeasurable viewport is `scroll-viewport` at that constraint's `/viewport` pointer,
4080
+ with a hint to target a layout container or a node/subtree with rest geometry. An unknown
4081
+ `scrollThumb.scroll` target uses rig's `rig-target` diagnostic. Authors clip visible scrolling content
4082
+ explicitly with composite's `clip`; scrolling does not add an implicit clip. This capability has no
4083
+ physics, inertia, elastic overscroll, snapping, virtualization or infinite-scroll semantics.