rig-c 0.0.0-stage → 2.20.4

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 (213) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/LICENSE +30 -0
  4. package/NOTICE.md +145 -0
  5. package/README.md +817 -3
  6. package/bin/rigc.cjs +83 -0
  7. package/cli.ts +61 -0
  8. package/cli_core.ts +46 -0
  9. package/docs/AUTHORING.md +9923 -0
  10. package/docs/FACE.md +1948 -0
  11. package/docs/INGEST.md +1488 -0
  12. package/docs/MOTION.md +1241 -0
  13. package/docs/PROMPTING.md +109 -0
  14. package/docs/RIGGING.md +1441 -0
  15. package/docs/SPEC_COVERAGE.md +357 -0
  16. package/package.json +108 -4
  17. package/skills/rigc/SKILL.md +133 -0
  18. package/skills/rigc-face/SKILL.md +60 -0
  19. package/skills/rigc-ingest/SKILL.md +78 -0
  20. package/skills/rigc-motion/SKILL.md +51 -0
  21. package/skills/rigc-rigging/SKILL.md +49 -0
  22. package/src/areaband.ts +159 -0
  23. package/src/assertions/bodies/a01.ts +23 -0
  24. package/src/assertions/bodies/a02.ts +21 -0
  25. package/src/assertions/bodies/a03.ts +27 -0
  26. package/src/assertions/bodies/a04.ts +40 -0
  27. package/src/assertions/bodies/a05.ts +56 -0
  28. package/src/assertions/bodies/a06.ts +245 -0
  29. package/src/assertions/bodies/a07.ts +68 -0
  30. package/src/assertions/bodies/a08.ts +76 -0
  31. package/src/assertions/bodies/a09.ts +82 -0
  32. package/src/assertions/bodies/a10.ts +116 -0
  33. package/src/assertions/bodies/a11.ts +15 -0
  34. package/src/assertions/bodies/a12.ts +30 -0
  35. package/src/assertions/bodies/a13.ts +51 -0
  36. package/src/assertions/bodies/a14.ts +35 -0
  37. package/src/assertions/bodies/a15.ts +97 -0
  38. package/src/assertions/bodies/a16.ts +24 -0
  39. package/src/assertions/bodies/a17.ts +26 -0
  40. package/src/assertions/bodies/a18.ts +62 -0
  41. package/src/assertions/bodies/a19.ts +404 -0
  42. package/src/assertions/bodies/a20.ts +122 -0
  43. package/src/assertions/bodies/a21.ts +190 -0
  44. package/src/assertions/bodies/a22.ts +39 -0
  45. package/src/assertions/bodies/a23.ts +305 -0
  46. package/src/assertions/bodies/a24.ts +68 -0
  47. package/src/assertions/bodies/a25.ts +39 -0
  48. package/src/assertions/bodies/a26.ts +61 -0
  49. package/src/assertions/bodies/a27.ts +33 -0
  50. package/src/assertions/bodies/a28.ts +70 -0
  51. package/src/assertions/bodies/a29.ts +34 -0
  52. package/src/assertions/bodies/a30.ts +50 -0
  53. package/src/assertions/bodies/a31.ts +61 -0
  54. package/src/assertions/bodies/a32.ts +44 -0
  55. package/src/assertions/bodies/a33.ts +110 -0
  56. package/src/assertions/bodies/a34.ts +133 -0
  57. package/src/assertions/bodies/a35.ts +160 -0
  58. package/src/assertions/bodies/a36.ts +81 -0
  59. package/src/assertions/bodies/a37.ts +77 -0
  60. package/src/assertions/bodies/a38.ts +73 -0
  61. package/src/assertions/bodies/a39.ts +303 -0
  62. package/src/assertions/bodies/a40.ts +128 -0
  63. package/src/assertions/bodies/a42.ts +97 -0
  64. package/src/assertions/bodies/a43.ts +181 -0
  65. package/src/assertions/bodies/a44.ts +23 -0
  66. package/src/assertions/bodies/a45.ts +172 -0
  67. package/src/assertions/bodies/a46.ts +224 -0
  68. package/src/assertions/bodies/a47.ts +126 -0
  69. package/src/assertions/bodies/a48.ts +83 -0
  70. package/src/assertions/bodies/a49.ts +81 -0
  71. package/src/assertions/bodies/a50.ts +97 -0
  72. package/src/assertions/constraint_words.ts +169 -0
  73. package/src/assertions/emitted/index.ts +148 -0
  74. package/src/assertions/facts/animated_bones.ts +30 -0
  75. package/src/assertions/facts/animation_durations.ts +37 -0
  76. package/src/assertions/facts/atlas_pages.ts +19 -0
  77. package/src/assertions/facts/atlas_regions.ts +52 -0
  78. package/src/assertions/facts/bone_timelines.ts +37 -0
  79. package/src/assertions/facts/constraint_targets.ts +56 -0
  80. package/src/assertions/facts/constraints.ts +155 -0
  81. package/src/assertions/facts/deform_survey.ts +27 -0
  82. package/src/assertions/facts/event_keys.ts +55 -0
  83. package/src/assertions/facts/linked_meshes.ts +38 -0
  84. package/src/assertions/facts/mesh_attachments.ts +100 -0
  85. package/src/assertions/facts/region_joins.ts +34 -0
  86. package/src/assertions/facts/sequences.ts +85 -0
  87. package/src/assertions/facts/skeleton_roster.ts +45 -0
  88. package/src/assertions/facts/skin_entries.ts +37 -0
  89. package/src/assertions/facts/skin_members.ts +53 -0
  90. package/src/assertions/facts/slider_composition.ts +78 -0
  91. package/src/assertions/facts/slot_colour.ts +43 -0
  92. package/src/assertions/facts/stage.ts +27 -0
  93. package/src/assertions/facts/stage_box.ts +65 -0
  94. package/src/assertions/facts/stepped_poses.ts +74 -0
  95. package/src/assertions/facts/two_colour.ts +52 -0
  96. package/src/assertions/facts/vertex_polygons.ts +53 -0
  97. package/src/assertions/footprints.ts +367 -0
  98. package/src/assertions/harness.ts +109 -0
  99. package/src/assertions/inward_advance.ts +58 -0
  100. package/src/assertions/kinds.ts +105 -0
  101. package/src/assertions/mesh_kinds.ts +56 -0
  102. package/src/assertions/model/animated_bones.ts +38 -0
  103. package/src/assertions/model/animation_durations.ts +57 -0
  104. package/src/assertions/model/atlas_pages.ts +15 -0
  105. package/src/assertions/model/atlas_regions.ts +76 -0
  106. package/src/assertions/model/bone_timelines.ts +58 -0
  107. package/src/assertions/model/constraint_targets.ts +82 -0
  108. package/src/assertions/model/constraints.ts +233 -0
  109. package/src/assertions/model/declared.ts +125 -0
  110. package/src/assertions/model/deform_survey.ts +24 -0
  111. package/src/assertions/model/event_keys.ts +45 -0
  112. package/src/assertions/model/given.ts +45 -0
  113. package/src/assertions/model/index.ts +398 -0
  114. package/src/assertions/model/linked_meshes.ts +24 -0
  115. package/src/assertions/model/mesh_attachments.ts +119 -0
  116. package/src/assertions/model/parse.ts +146 -0
  117. package/src/assertions/model/region_joins.ts +67 -0
  118. package/src/assertions/model/runtime_timelines.ts +78 -0
  119. package/src/assertions/model/sequences.ts +157 -0
  120. package/src/assertions/model/skeleton_roster.ts +23 -0
  121. package/src/assertions/model/skin_entries.ts +69 -0
  122. package/src/assertions/model/skin_members.ts +64 -0
  123. package/src/assertions/model/slider_composition.ts +193 -0
  124. package/src/assertions/model/slot_colour.ts +81 -0
  125. package/src/assertions/model/stage.ts +28 -0
  126. package/src/assertions/model/stage_box.ts +51 -0
  127. package/src/assertions/model/stepped_poses.ts +105 -0
  128. package/src/assertions/model/two_colour.ts +61 -0
  129. package/src/assertions/model/vertex_polygons.ts +72 -0
  130. package/src/assertions/reasons.ts +129 -0
  131. package/src/assertions/region_lookups.ts +61 -0
  132. package/src/assertions/report.ts +189 -0
  133. package/src/assertions/values.ts +39 -0
  134. package/src/atlas.ts +2870 -0
  135. package/src/ballot.ts +866 -0
  136. package/src/bonedist.ts +643 -0
  137. package/src/chainfit.ts +2752 -0
  138. package/src/chains.ts +170 -0
  139. package/src/check.ts +4303 -0
  140. package/src/checkpics.ts +295 -0
  141. package/src/cli/core_commands.ts +1627 -0
  142. package/src/cli/repack.ts +414 -0
  143. package/src/cli/shared.ts +2776 -0
  144. package/src/cli/spine_commands.ts +820 -0
  145. package/src/compile.ts +9414 -0
  146. package/src/core/additive.ts +458 -0
  147. package/src/core/animation.ts +1050 -0
  148. package/src/core/clipping.ts +696 -0
  149. package/src/core/constraints.ts +1876 -0
  150. package/src/core/constraints_path.ts +964 -0
  151. package/src/core/constraints_physics.ts +881 -0
  152. package/src/core/constraints_slider.ts +635 -0
  153. package/src/core/deform.ts +613 -0
  154. package/src/core/draw_order.ts +125 -0
  155. package/src/core/events.ts +135 -0
  156. package/src/core/hooks.ts +249 -0
  157. package/src/core/index.ts +1400 -0
  158. package/src/core/raw.ts +739 -0
  159. package/src/core/skins.ts +129 -0
  160. package/src/core/uvs.ts +469 -0
  161. package/src/core/vertices.ts +490 -0
  162. package/src/core/walk.ts +197 -0
  163. package/src/core/world.ts +289 -0
  164. package/src/correspondence.ts +15 -0
  165. package/src/deformbuild.ts +60 -0
  166. package/src/deformgen.ts +630 -0
  167. package/src/deformmeasure.ts +732 -0
  168. package/src/deformreport.ts +373 -0
  169. package/src/deformstructure.ts +386 -0
  170. package/src/deformsurvey.ts +2162 -0
  171. package/src/depth.ts +784 -0
  172. package/src/diff.ts +2252 -0
  173. package/src/emit.ts +134 -0
  174. package/src/emit_spine.ts +854 -0
  175. package/src/errors.ts +53 -0
  176. package/src/framing.ts +819 -0
  177. package/src/generation.ts +139 -0
  178. package/src/ingest.ts +2293 -0
  179. package/src/json-position.ts +253 -0
  180. package/src/keyorder.ts +587 -0
  181. package/src/keys.ts +486 -0
  182. package/src/ladder.ts +121 -0
  183. package/src/mesh.ts +2382 -0
  184. package/src/meshcompare.ts +1188 -0
  185. package/src/meshquality.ts +2042 -0
  186. package/src/meshrasters.ts +944 -0
  187. package/src/meshreduce.ts +1425 -0
  188. package/src/model.ts +1245 -0
  189. package/src/motion.ts +809 -0
  190. package/src/nonfinite.ts +54 -0
  191. package/src/package_meta.ts +48 -0
  192. package/src/png.ts +297 -0
  193. package/src/pose.ts +2324 -0
  194. package/src/preview.ts +434 -0
  195. package/src/region_joins.ts +54 -0
  196. package/src/render.ts +1013 -0
  197. package/src/render_core.ts +871 -0
  198. package/src/render_shared.ts +2958 -0
  199. package/src/repack.ts +495 -0
  200. package/src/rig.ts +2941 -0
  201. package/src/slots.ts +892 -0
  202. package/src/spine_side.ts +138 -0
  203. package/src/timelines.ts +837 -0
  204. package/src/trackgen.ts +364 -0
  205. package/src/transform.ts +310 -0
  206. package/src/types.ts +1797 -0
  207. package/src/validate.ts +3875 -0
  208. package/tools/contact.ts +126 -0
  209. package/tools/editor_roundtrip.ts +1641 -0
  210. package/tools/font5x7.ts +101 -0
  211. package/tools/measure_contact_depth.ts +105 -0
  212. package/tools/plate.ts +508 -0
  213. package/tools/png_probe.mjs +72 -0
package/docs/MOTION.md ADDED
@@ -0,0 +1,1241 @@
1
+ # Authoring a motion from key poses
2
+
3
+ **Read this when the request is a movement rather than a skeleton.** It is written
4
+ for an agent that has been handed loose part PNGs, a sentence of intent, and
5
+ between zero and N pictures of what the movement passes through, and that has to
6
+ come back with a Spine animation somebody would choose.
7
+
8
+ [AUTHORING.md](AUTHORING.md) is the file formats, the failure map and the CLI —
9
+ read it first and keep it open; this page never restates a field it documents. This
10
+ page is the part AUTHORING.md deliberately does not have: **what to put between two
11
+ poses**, when nothing anywhere has told you.
12
+
13
+ 🚨 **Nothing in this document grades your output, and no instrument named here
14
+ can.** `build` says a file is valid. `render` and `preview` let you look. `pose`
15
+ reads a picture you were given. The one thing that judges a movement is a person's
16
+ eye, through `rigc vote` — which is why this recipe ends by producing candidates
17
+ rather than by producing a number. There is no pass bar for a movement in this
18
+ toolchain and this page does not invent one.
19
+
20
+ - The two spec files, field by field: **AUTHORING §1–§4**
21
+ - Named failures, and the file each one points at: **AUTHORING §5–§6**
22
+ - What the Spine editor does when nobody tells it otherwise: **AUTHORING §10**
23
+ - Reading a pose out of a picture — the instrument this recipe consumes:
24
+ **AUTHORING §11**
25
+ - The parts of that picture `pose` refuses because something is drawn over them,
26
+ once a first candidate exists: **AUTHORING §12** (`rigc chainfit`). It reports the
27
+ `rotate` key value each answer implies, which is the form this recipe wants them in
28
+ - If the **skeleton** is what you have to decide rather than the movement — how many
29
+ bones, where each pivot goes, what hangs off what, and which of those the frames
30
+ can check: [RIGGING.md](RIGGING.md). §3.9's pivot solve is the one piece of that
31
+ page this one already carries, and RIGGING §2 is what a wrong answer to it looks
32
+ like from inside a fit
33
+ - If the movement is a **face** — a blink, a gaze, or a head turning off axis —
34
+ everything on this page still applies, and [FACE.md](FACE.md) is the geometry it
35
+ does not have: a turn is a projection rather than a pose, so its values are
36
+ evaluated rather than chosen
37
+ - If what you were handed is a **compiled skeleton** rather than loose parts — reading
38
+ it, transcribing it into specs, re-pivoting it, extending it with an animation:
39
+ [INGEST.md](INGEST.md)
40
+ - If you are the *person operating* an agent rather than the agent:
41
+ [PROMPTING.md](PROMPTING.md)
42
+
43
+ ---
44
+
45
+ ## 0. The normal form
46
+
47
+ **Every motion request normalises to a key-pose sequence plus in-betweens.** That
48
+ is the whole internal shape, and it does not vary with how much the user gave you.
49
+ What varies is only **where the key poses come from**:
50
+
51
+ | Level | What arrived | Where the key poses come from |
52
+ | --- | --- | --- |
53
+ | **L0** | parts + words (*"a breathing idle"*) | you invent them. A loop is the special case where the first and last are the **same** pose |
54
+ | **L1** | parts + two pictures (*"from this to this"*) | the two pictures, read into spec coordinates by `rigc pose`. They are **given conditions** |
55
+ | **L2** | parts + N ordered pictures | the same, N times. This is the general form and L1 is the N=2 case |
56
+
57
+ ⭐ **The recipe below is one recipe.** L0 spends its effort inventing poses and then
58
+ in-betweening them; L1 and L2 skip the inventing. Nothing else differs — not the
59
+ key plan, not the easing table, not the candidate axes, not the loop. If you find
60
+ yourself writing a second procedure for a second input level, you have split
61
+ something that is not two things.
62
+
63
+ 🚨 **At L1 and L2 the end poses are inputs, not targets.** Once the spec carries the
64
+ numbers `pose` read out of the picture, the animation **states** those poses by
65
+ construction; there is nothing left for it to be close to, and nothing in this
66
+ toolchain measures how near it got. This is not modesty about a weak instrument, it
67
+ decides what you do with your loops: you do not iterate toward the ends, you iterate
68
+ on the movement between them. AUTHORING §11.1 argues the same point from the
69
+ instrument's side.
70
+
71
+ The loop this page is inside:
72
+
73
+ ```
74
+ key poses → in-betweens → rigc build → rigc render / rigc preview → rigc vote
75
+ ↑ │
76
+ └──────────── a `both-unacceptable` verdict comes back here ─────────────────┘
77
+ ```
78
+
79
+ and the last hop is the one that matters: a `both-unacceptable` tie means **propose
80
+ again from a different axis** (§4), not *nudge the same candidate*. §5 has the
81
+ detail.
82
+
83
+ ### 0.1 When the axis is not time — an animation a `slider` applies
84
+
85
+ ⛔ **One shape of request does not normalise to the table above, and carrying it
86
+ through §3 anyway produces a defect nothing on this page can measure.** A `slider`
87
+ constraint (AUTHORING §3.5.2) applies an animation as a function of a **value**: it
88
+ reads a driving bone's transform property, maps it with
89
+ `time = to + (value − from) × scale`, and applies the animation at that time on
90
+ every frame. What it applies is an ordinary animation in the motion spec — same
91
+ tracks, same keys, the same `duration` — but **nothing plays it**. It is a lookup
92
+ table, and `t` in it is a coordinate on the axis rather than a moment.
93
+
94
+ ⇒ **So the constructs in §3 that are functions of time are not available to it,
95
+ and each fails in its own way rather than merely reading oddly:**
96
+
97
+ | §3 construct | On an animation a slider applies |
98
+ | --- | --- |
99
+ | §3.4 slow in and slow out | an easing curve makes the pose a **non-linear** function of the dial. The consumer moves the value at one rate and the face moves at another, and at a value held still the pose is still whatever the curve says there |
100
+ | §3.6 anticipation | places a counter-pose at a dial *position*, so the pose runs backwards while the value runs forwards. Nothing anticipates a number |
101
+ | §3.8 overshoot and settle | there is no settle: at a held value the pose is what the table says at that value, indefinitely. An overshoot keyed past the extreme is just a wrong pose at the top of the dial |
102
+ | §3.7 follow-through and the offset table | wants parts to arrive at different **times**. Here they differ by **amount** at every value, which is §3.7.1's construct and not this one |
103
+ | §3.3 timing | the key times are the axis's own coordinates — one per angle, position or level the table states — so spacing them is choosing where to sample, not choosing a rhythm |
104
+
105
+ ⭐ **What is still this page's job is whatever moves the dial**, and that is an
106
+ ordinary animation with §3 applying to it unchanged. The split is visible in the
107
+ worked example: in
108
+ [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) the two
109
+ lookup tables `turn` and `tilt` carry **no easing at all**, and `sweep` — the one
110
+ animation there meant to be played — carries every `ease` in the file.
111
+
112
+ ```bash
113
+ bun -e 'const m = JSON.parse(await Bun.file("gallery/look/motion.json").text());
114
+ for (const [name, a] of Object.entries(m.animations))
115
+ console.log(name, (JSON.stringify(a).match(/"ease"/g) ?? []).length);'
116
+ ```
117
+
118
+ ⚠️ **And two of §4's candidate axes stop being axes.** *Anticipation* and
119
+ *Termination* are both readings of how a movement is placed in time, so a ballot
120
+ spread on either of them over a slider-applied animation is asking a person to
121
+ choose between two wrong answers. The axes that survive are the ones about
122
+ **amount** — *Part amount*, *Path*, *Key density* — because those are still
123
+ readings of the value.
124
+
125
+ 📘 The face case is worked end to end in [FACE.md](FACE.md): its §8 derives the
126
+ slider's range from the turn ceiling `build` reports, which is the one number on
127
+ that axis an author cannot guess.
128
+
129
+ ---
130
+
131
+ ## 1. Prompt grammar — what a request is made of
132
+
133
+ A user writes prose. The recipe reads a fixed set of elements out of it. Both halves
134
+ of that are deliberate: the user gets natural language, you get something you can
135
+ act on without asking a questionnaire.
136
+
137
+ 📌 **Every absent element has a default, and every default you take gets one line of
138
+ output saying you took it.** A silently defaulted duration is the same defect as a
139
+ silently defaulted pivot: the user cannot correct a decision they were not told
140
+ about.
141
+
142
+ | Element | How it arrives | Default when it is absent |
143
+ | --- | --- | --- |
144
+ | **parts directory** | a path, *"the PNGs in `art/`"*, a folder dropped in | ⛔ **no default.** Nothing can start without the art, because rigc measures PNGs rather than trusting a size you typed (AUTHORING R5). Ask for it |
145
+ | **pose frames, ordered 0..N** | file paths, pictures, *"first this one, then this one"* | **none = L0.** Invent the key poses, and say in one line which poses you invented and why those |
146
+ | **target duration** | *"half a second"*, *"quick"*, *"over about two beats"* | **propose one, write it into `duration`, and say so.** A movement has to have a length; the user not naming one is not permission to leave it undecided |
147
+ | **loop or not** | *"idle"*, *"cycle"*, *"loops"* vs *"and then it stops"* | **loop if the first and last key poses are the same pose, otherwise not** — and say which reading you took. At L1 with two different pictures that reading is *not a loop*; at L0 an idle is the A=B case |
148
+ | **intent adjectives** | *"heavy"*, *"snap"*, *"weary"*, *"mechanical"* | **none = no adjectives, not a neutral adjective.** With nothing said, take the defaults in §3 as written and do not invent a character for the movement. An adjective the user did not say is the thing they will react to first |
149
+ | **animation name** | *"call it `walk`"* | the intent's own verb, lower-case, one word (`raise`, `idle`, `strike`). It is a key in `animations` and the user will type it |
150
+ | **frame rate** | *"at 12 fps"* | ⛔ **do not adopt one.** Spine's times are seconds and frames exist only for convenience (AUTHORING §10.3), so a rate belongs to `render --fps` and to nothing in either spec file |
151
+ | **pose-frame scale** | almost never stated | search the default window once, read the `search` block back, and narrow it if the answer sits at a window edge — §2.2 |
152
+
153
+ 📖 **A request in an animator's words — *"heavier"*, *"follow through"*, *"keep
154
+ the volume"* — maps to a construct in §3.1.1's table.** Two of its rows are
155
+ corrections rather than translations, so read it before assuming a word means
156
+ here what it means elsewhere.
157
+
158
+ ⚠️ **Read the intent adjectives before you read the pictures, and write down what
159
+ you think they mean, in movement terms, before any measurement.** *"Snap"* means the
160
+ extreme arrives early and the value settles late; *"heavy"* means the parts separate
161
+ in time more than they otherwise would; *"mechanical"* means constant speed, which is
162
+ the one case AUTHORING §10.4 says to argue for rather than default to. Doing this
163
+ first is what stops the pictures — which are precise, and about the ends only — from
164
+ crowding out the sentence, which is imprecise and about everything in between.
165
+
166
+ ---
167
+
168
+ ## 2. Getting the key poses
169
+
170
+ ### 2.1 L0 — you invent them
171
+
172
+ With no pictures, the key poses are yours, and the honest procedure is short:
173
+
174
+ 1. **Name the extremes.** A movement is a list of positions it visibly passes
175
+ through. Write them as sentences first (*"weight on the back foot, chest turned
176
+ away"* → *"weight forward, chest square"*), because a sentence is a thing you can
177
+ change cheaply and a set of bone angles is not.
178
+ 2. **Two is the floor and three is usually right.** A move between two extremes needs
179
+ both of them; a *cycle* needs the same pose twice with something different in the
180
+ middle, or it does not read as a cycle.
181
+ 3. **A loop is the A=B case, and its seam is a real defect.** The last key must carry
182
+ the **same value** as the first, not a value near it — AUTHORING §0's note on
183
+ `check`'s per-frame column is about exactly this class of defect, and nothing in an
184
+ aggregate can see it. Write the value twice rather than trusting yourself to have
185
+ ended where you began.
186
+ 4. **Then look.** `rigc render` writes a contact sheet of every frame in one image,
187
+ and spacing is a comparison **across** frames, so that grid is the picture to open
188
+ first (AUTHORING §0). A pose you invented and never looked at is a guess with a
189
+ number attached.
190
+
191
+ ### 2.2 L1 and L2 — the pictures, through `rigc pose`
192
+
193
+ ```bash
194
+ rigc pose --images parts/ --frame poseA.png --out poseA.json
195
+ rigc pose --images parts/ --frame poseB.png --out poseB.json
196
+ ```
197
+
198
+ One frame per call, by design. The fields are AUTHORING §11.3; what follows is how
199
+ to **consume** them, and every item is a property of the instrument rather than
200
+ advice.
201
+
202
+ **⚠️ Read `refusal` before `placement`.** Under a `no-match` refusal the placement is
203
+ **still filled in, on purpose** — a refusal says *do not trust this number*, it does
204
+ not hide it. Code that reads `placement` first and treats a non-null value as an
205
+ answer will silently adopt a refused one. `empty-part` and `larger-than-canvas` leave
206
+ `placement` null because nothing was searched; those two are the only nulls.
207
+
208
+ **📐 The coordinates are frame pixels, y down, origin top-left, and `(x, y)` is where
209
+ the part image's own centre lands** — not a corner, not a pivot. To reach Spine's
210
+ y-up, counter-clockwise world use the two conversions that already exist and
211
+ open-code neither: `screenToSpineDegrees(rotationDeg)` and
212
+ `cropToSpineY(y, frameHeight)`, both in
213
+ [`src/transform.ts`](../src/transform.ts). The `space` field of every report repeats
214
+ the contract in the file, so a consumer never has to remember which way the flip
215
+ goes. (Canonical: AUTHORING §11.2, which states the same contract as a field
216
+ reference; it is restated here because §2 is where you convert one.)
217
+
218
+ ⚠️ A **bone offset** in a spec is expressed in its parent's local axes, so the y flip
219
+ applies there too and it applies **once**. Converting a world point and then also
220
+ negating the local offset you derived from it is the commonest way to build a rig
221
+ that is a mirror of the picture in one joint and correct in the others.
222
+
223
+ **📊 Residuals are a trust signal and they are not comparable — not across parts, not
224
+ across pictures.** The residual is an alpha-weighted mean over **one** part's own
225
+ footprint against **one** frame's pixels, so it answers *how well does this placement
226
+ explain this frame here*. It does not say that a part with 0.03 was placed better
227
+ than a part with 0.06, and it does not say that pose A was read better than pose B.
228
+ ⇒ Use it to decide **which numbers to lean on** — where two placements of the same
229
+ part in the same frame differ, and whether to look at a part again — and never to
230
+ rank parts or frames.
231
+
232
+ **🔀 A symmetric part comes back as an unordered set, and ordering it across two
233
+ frames is your job.** `alternates` non-empty means the answer was not unique;
234
+ `ambiguous` means at least one alternate is inside the margin. Two identical limbs
235
+ look exactly like that, and so does a shape whose silhouette fits itself at more than
236
+ one angle. The instrument has run out — it sees one frame and has no notion of which
237
+ limb is which. A method that works:
238
+
239
+ 1. **Enumerate the assignments, not the placements.** With k interchangeable
240
+ placements of one part in frame A and k in frame B, there are k! ways to pair them.
241
+ For two, that is two options; do not treat it as a search.
242
+ 2. **Pick the assignment that minimises total movement** — the sum, over the part's
243
+ instances, of the distance its centre travels from A to B, with rotation counted in
244
+ at the part's own radius so the two terms are commensurate. Adjacent poses are
245
+ adjacent, so the pairing that makes the parts travel least is the pairing that does
246
+ not swap them.
247
+ 3. **Then pin it for the whole animation and let nothing reopen it.** Re-deciding per
248
+ frame is what produces a limb that jumps back and forth between two answers, cheap
249
+ in every frame and wrong in the relation between two — AUTHORING §8.1 documents that
250
+ failure from the fitting side.
251
+ 4. ⚠️ **Ask the user instead when continuity does not separate them.** Two cases: the
252
+ two candidate assignments come out **within a few percent of each other** (a
253
+ near-symmetric pose, or two poses far enough apart that both pairings travel about
254
+ as far), or the assignment **changes the meaning** rather than the geometry — which
255
+ arm is in front, which leg leads. Those are not measurements you are missing, they
256
+ are decisions nobody has made. One question with the two readings named is cheaper
257
+ than a rig that is confidently mirrored.
258
+
259
+ **🕶️ A middling residual next to a high `unexplained` usually means *right place,
260
+ seen through something*.** Occlusion is documented rather than solved: a part drawn
261
+ behind another has the occluder's pixels where its own should be, so its residual
262
+ rises **at the correct placement**. `unexplained` is the share of the part's material
263
+ that actually disagrees, and it is what separates the two readings — high with a
264
+ plausible placement is occlusion, high with an implausible placement is a wrong
265
+ answer. ⭐ **And it is evidence you want:** a part whose `unexplained` goes **up** in
266
+ the frame where another part crosses it is telling you the crossing part is in
267
+ **front**, which is the only place a slot order can come from at this input level
268
+ (AUTHORING R4 — the slots array *is* the draw order). §6 derives one that way.
269
+
270
+ **🔍 Surface the `search` window whenever you narrow it, and read it back before you
271
+ trust a surprise.** A window that does not contain the truth **does not reliably
272
+ refuse**: a part shrunk inside the region it came from still explains those pixels, so
273
+ the report's answer is the best placement available *inside* the window and its
274
+ residual can look perfectly reasonable. The tell is a placement sitting **at a window
275
+ edge**, or a part whose scale disagrees with its neighbours' by more than a few
276
+ percent when the picture cannot have been drawn that way. ⇒ Run the default window
277
+ once, read `search` and the scales together, narrow, run again, and **say in your log
278
+ what window produced the numbers you kept**. §6 shows the before and after on a real
279
+ part.
280
+
281
+ **🎨 Branch on `background.kind: "unknown"`.** With no dominant colour on the border
282
+ ring, every pixel counts as material, the silhouette signal is gone and the residual
283
+ is colour agreement alone. It is reported rather than being quietly weaker, so treat
284
+ it as a different input regime: lean harder on the parts whose interiors carry detail,
285
+ expect more `ambiguous` verdicts, and prefer a crop of the picture with a clean border
286
+ if the user can give one. Do not narrow `--max-residual` to make an `unknown` frame
287
+ look tidier; that suppresses the refusals, which are the only thing telling you the
288
+ frame is hard.
289
+
290
+ ### 2.3 What the poses do and do not fix
291
+
292
+ Two placements per part fix a great deal: the setup pose, every part's attachment
293
+ offset, the slot order (via `unexplained`, above), and both end poses of every
294
+ timeline. They do **not** fix the pivots — see §3.9, which is where pivots belong,
295
+ because a pivot is not visible in either end pose and only shows up in the movement
296
+ between them.
297
+
298
+ ---
299
+
300
+ ## 3. The in-betweening recipe
301
+
302
+ This is the part with no reference and no possible reference. The pictures are of the
303
+ **ends**; the frames between them are not given anywhere, cannot be measured, and
304
+ would not exist even if the user had more pictures of the same two poses. Everything
305
+ below is therefore authored knowledge, and it is sourced the way AUTHORING §10 sourced
306
+ the editor's conventions.
307
+
308
+ ⚠️ **All of it assumes the axis is time.** If what you are authoring is an
309
+ animation a `slider` applies — a face angle, a dial, a suspension that compresses
310
+ as the wheel rises — §0.1 is the exception, and it is not a small one: the easing,
311
+ the anticipation and the overshoot below each produce a specific defect there
312
+ rather than merely reading oddly.
313
+
314
+ ### 3.1 Where these come from, and how each line is marked
315
+
316
+ Two public bodies of material, and nothing else: **the twelve basic principles of
317
+ animation** as publicly catalogued, and **Spine's own documentation**. No sentence
318
+ below is copied from either — the 📗 lines are paraphrases and each carries the page
319
+ it paraphrases.
320
+
321
+ - 📗 **stated** — named and defined on the page linked in the line.
322
+ - 🧩 **inferred** — this guide's reading of that material, applied to a rigc spec.
323
+ The source does not say it, and the numbers in these lines are **defaults to start
324
+ from, not answers**.
325
+
326
+ 🚨 **Nothing here is the answer to any request.** Each item is a default to adopt
327
+ *unless the intent says otherwise*, exactly as AUTHORING §10 puts it, and the
328
+ adjectives in §1 are what overrides them.
329
+
330
+ ### 3.1.1 The animator's words, in this vocabulary
331
+
332
+ A request arrives in an animator's words — *"make it feel heavier"*, *"keep the
333
+ volume when it lands"*, *"the cape should follow through"* — and this table is
334
+ the map from those to the construct that carries them. §1's intent-adjective
335
+ paragraph does this for three words; this is its full form.
336
+
337
+ ⚠️ **Two rows are corrections rather than translations**, and they are the
338
+ reason the table earns its place: Spine's `Ease in`/`Ease out` mean the
339
+ **opposite** of the web's, and a *moving hold* is not §3.3's *hold*. Both are
340
+ words an agent already thinks it knows.
341
+
342
+ Marks are §3.1's: 📗 stated on the linked page, 🧩 this guide's reading. Per
343
+ cell, because most rows are a sourced word mapped to an unsourced construct.
344
+
345
+ | The animator's word | What it means | rigc's construct |
346
+ | --- | --- | --- |
347
+ | **weight** — *"make it feel heavier"* | 📗 how an object answers a push: a light one reacts sooner than a heavy one ([twelve principles]) | 🧩 not one field. A longer `duration`, a slower attack on the leading `ease`, and **larger** offsets down §3.7's table — heavy is *parts separating in time*, which is what §1 already says the adjective means |
348
+ | **timing** | 📗 how many frames an action takes, which is its speed ([twelve principles]) | 📗 `duration` and the key times, **in seconds** — frames exist for convenience only ([Keys]) · §3.3 |
349
+ | **spacing** | 🧩 how far the value moves between one sampled frame and the next. Timing is *when the keys are*; spacing is *what happens between them* | 🧩 the `ease` on the key, not the key times. It has no field of its own — §2.1 sends you to the contact sheet because spacing is only visible across frames · §3.4 |
350
+ | **slow in / slow out** | 📗 a body needs time to accelerate and to stop, so more drawings fall near each end ([twelve principles]) | 📗 a named entry in `easings` · §3.4. 🚨 **Spine's preset names invert the web's**: its *Ease out* changes more slowly near **the key**, its *Ease in* more slowly near **the next key** ([Graph]). An agent carrying the CSS meaning picks the opposite shape |
351
+ | **arcs** | 📗 natural movement follows a curved path; straight lines read as mechanical ([twelve principles]) | 🧩 the **channel**, not a setting: `rotate` gives the arc free, `translate` draws the straight line. The question is never *arc or line*, it is *which channel carries this move* · §3.5 |
352
+ | **anticipation** — *"wind up first"* | 📗 a small preparation that makes the action read ([twelve principles]) | 🧩 §3.6's interior key, the other way. 🚨 It goes **after** `t: 0` at L1/L2 — `t: 0` is a given condition, not a pose to move |
353
+ | **follow-through** | 📗 loosely attached parts keep going after the body stops, then are pulled back ([twelve principles]) | 🧩 the trailing part's extreme lands later than its driver's **and overshoots once** — §3.7's last rows are that pull-back · §3.8 |
354
+ | **overlapping action** — *"don't let it move as one piece"* | 📗 parts of a body move on different timings from one another ([twelve principles]) | 📗 in the editor it is literally *moving keys in time*, which is what the offset button is for ([Keys]). 🧩 here: a later extreme per bone, or `groups` + `stagger` where members share a value · §3.7 |
355
+ | **drag** — *"it should lag"* | 📗 parts take a few frames to catch up when a body starts moving ([twelve principles]) | 🧩 the same offset applied at the **start** rather than at the stop. ⚠️ Convert the source's frames to a **fraction** of the duration — §3.7's offsets are fractions, so a 0.15 s snap and a 2 s idle share one table · §3.7 |
356
+ | **the wave principle** | 📗 Esoteric's own framing: the thing to understand for tails, hair, cloth and flags — anything that follows through (video 4) | 🧩 the offset **compounding down a chain**, so the lag travels: `groups` + `stagger` where the members are a chain. ⚠️ **`wave` already names something else here** — a deform transform kind (AUTHORING §4.11.1) that ripples an attachment's vertices, not a chain's timing |
357
+ | **squash and stretch** | 📗 what gives a drawing weight and flexibility ([twelve principles]); Spine notes the shear tool is used in small amounts for organic squash and stretch ([Tools]) | 🧩 two spellings. **Cheap:** a `scale` key whose two axes differ. **Full:** a `deform` timeline, which is what `gallery/squash` uses. §7 is rigid-first — land the movement, then deform · §4 |
358
+ | **keeping the volume** | 📗 in *realistic* animation a squashed object keeps its volume: stretch it one way and it must narrow the other ([twelve principles]) | 🧩 `x·y ≈ 1` on a `scale` key, which `explain` prints beside it, and the `area` figure a `deform` key already carried. **A reading, never a rule** — a shadow, a zoom and a cartoon squash all change area on purpose. ⚠️ Say *scale product* in a message: `volume` already means an event's audio and 4.3's `ScaleYMode.Volume` |
359
+ | **overshoot and settle** | 📗 exaggeration — a motion that imitates reality exactly reads as dull ([twelve principles]) | 🧩 §3.8's interior key past the final value, then back. 🚨 The last key still carries the given end pose exactly |
360
+ | **a moving hold** — *"it should never be fully still"* | 📗 a character that is barely moving still breathes; two nearly-identical poses keep it from going lifeless ([twelve principles]) | 🧩 **not** §3.3's *hold*, which is two EQUAL keys meaning *nothing moves here*. A moving hold is the opposite: §3.3's 1.5–3 s idle band with a small excursion — §0's A=B case |
361
+ | **secondary action** | 📗 a supporting movement that emphasises the main one rather than competing with it ([twelve principles]) | 🧩 a whole extra timeline, and the **first thing to leave out of a first candidate**: a ballot spreading on both primary timing and a secondary action has asked two questions · §3.10 |
362
+ | **pose to pose** vs **straight ahead** | 📗 the two ways animation is made — extremes first and fill in, or forward frame by frame ([twelve principles]) | 🧩 a keyframed skeleton can only do the first. Nothing in the format means *"and then draw the next frame"* — §3.2 |
363
+
364
+ [twelve principles]: https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation
365
+ [Keys]: http://esotericsoftware.com/spine-keys
366
+ [Graph]: http://esotericsoftware.com/spine-graph
367
+ [Tools]: http://esotericsoftware.com/spine-tools
368
+
369
+ ⚠️ **None of this table comes from the narration of the eight *Animating with
370
+ Spine* videos**, so nothing here is presented as something said in one. Two rows
371
+ cite a video's published *description*, which is Esoteric's own prose and marked
372
+ as such; everything else is the twelve principles and Spine's written
373
+ documentation.
374
+
375
+ ### 3.2 📗 Pose to pose is the normal form, and it is one of two
376
+
377
+ Animation is publicly catalogued as being made either **straight ahead** — drawn
378
+ forward, frame after frame — or **pose to pose**, where the extremes are laid down
379
+ first and the rest is filled in between them —
380
+ [Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation).
381
+
382
+ 🧩 **⇒ A keyframed skeleton can only do the second one, so §0's normal form is not a
383
+ convention this page picked.** A Spine animation *is* sparse keys plus interpolation;
384
+ there is no channel in the format that means "and then draw the next frame". This is
385
+ why a fitter's output — one pose per frame — is the wrong shape for a motion spec even
386
+ when every pose in it is right: AUTHORING §10.3 and PROMPTING clause 4 both land on
387
+ that from the measured side.
388
+
389
+ ### 3.3 📗 Timing is the number of frames, and 🧩 in rigc it is seconds
390
+
391
+ Timing — how long a movement takes — is catalogued as what gives a movement its weight
392
+ and its meaning; the same two poses with different spacing between them read as
393
+ different actions
394
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
395
+ Spine's own guide states that times are seconds and *frames exist only for
396
+ convenience* — [Keys](http://esotericsoftware.com/spine-keys).
397
+
398
+ 🧩 **⇒ Author in seconds and never pin a key to a frame grid.** A key at `t: 0.07` is
399
+ ordinary; a key plan whose times are all multiples of 1/12 has quietly adopted a frame
400
+ rate that nothing in the spec asked for, and will re-time itself the first time
401
+ somebody renders at another rate.
402
+
403
+ 🧩 **⇒ Defaults for a single move, when the user named no duration.** A movement a
404
+ figure *does* (a reach, a raise, a step) lands between **0.3 s and 0.8 s**; a movement
405
+ that happens *to* it (a hit, a snap, a recoil) between **0.1 s and 0.3 s**; an idle
406
+ cycle between **1.5 s and 3 s**. Propose the middle of the band the intent picks out,
407
+ write it in `duration`, and say in one line that you proposed it. These are starting
408
+ points chosen so a first candidate is watchable, not measurements of anything.
409
+
410
+ 🧩 **⇒ How many interior keys, and where.** Key count is a timing decision, so it lives
411
+ here, and it is decided by **naming what each key is for** rather than by picking a
412
+ number:
413
+
414
+ | Interior keys | When that is the right count |
415
+ | --- | --- |
416
+ | **none** — the two ends plus a curve | the intent names no shape. This is a legitimate candidate rather than a stub, and it is exactly what candidate B is in §6 |
417
+ | **one**, at the extreme the intent names | one named effect: an anticipation (§3.6), an overshoot (§3.8), or the point a straight path would bow off its line (§3.5). One key per effect, at that effect's own time |
418
+ | **two or three** | the effects stack — anticipate, overshoot, settle — or the intent names a shape the ends cannot carry (*"hesitates"*, *"in two stages"*) |
419
+ | **four or more** | ⛔ ask what the extra ones are for. A key that is not an end, a named extreme, or a hold boundary is a **sample**, and §7 prices samples |
420
+
421
+ ⭐ **The three kinds of key that are forced are AUTHORING §10.3's**, and they are the
422
+ whole of what a key plan owes: the series' own ends, every change of direction, and
423
+ **both ends of any run of equal values** — a hold is authored, not omitted, and two equal
424
+ keys are the only way to say *nothing moves here* on an interpolated timeline.
425
+
426
+ ### 3.4 📗 Slow in and slow out, and 📗 Spine says constant speed reads badly
427
+
428
+ Movements are catalogued as accelerating out of an extreme and decelerating into the
429
+ next, with more drawings near the extremes than in the middle
430
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
431
+ Spine's guide is explicit about the consequence: when all the parts of a skeleton move
432
+ at constant speed *the movement tends to be robotic and lifeless* —
433
+ [Animating](http://esotericsoftware.com/spine-animating). Its curve editor offers
434
+ automatic handles first and named presets after, and its handles are normalised to
435
+ 0..1 on both axes — [Graph](http://esotericsoftware.com/spine-graph).
436
+
437
+ 🧩 **⇒ Bezier is the default and linear is the exception you argue for** — AUTHORING
438
+ §10.4 states this and §4.1's `easings` block is where it lives. For a single authored
439
+ move, **three named shapes carry it**: one that leaves an extreme slowly and gathers
440
+ speed, one that leaves fast and arrives slowly, one symmetric shape for everything
441
+ else. A fourth is worth adding when a part has to *stop dead*; a table of eight for a
442
+ half-second move is a table nobody chose from.
443
+
444
+ 🚫 **Do not fit free handles and then substitute the nearest named shape.** AUTHORING
445
+ §10.4 measures what that costs on a fitted shot, and the same trap exists here in a
446
+ smaller form: pick the table first, then write every key against the table you will
447
+ actually emit. Nothing in the loop can see the difference — the key count, the curve
448
+ kinds and the duration are all unmoved — and the rendered result changes.
449
+
450
+ ### 3.5 📗 Arcs, and 🧩 the channel decides whether you get one
451
+
452
+ Natural movement is catalogued as following arced trajectories rather than straight
453
+ lines, because limbs are hinged
454
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
455
+
456
+ 🧩 **⇒ In a skeleton the arc is free, and losing it takes effort.** A `rotate` track on
457
+ a parent bone carries every descendant along a circular path about that bone's pivot —
458
+ that *is* an arc, and it costs one timeline. A `translate` track between two positions
459
+ draws the **straight line** between them, and two `translate` tracks with the same
460
+ times draw the straight line in both axes. ⇒ **The real question is never "arc or
461
+ line", it is "which channel carries this move".** Reach for `translate` only where the
462
+ thing genuinely slides — a lift, a slide, a prop on a rail — and for a hinge use
463
+ `rotate` and take the arc.
464
+
465
+ 🧩 **⇒ Where a move must be a straight line through a hinge, it needs an interior
466
+ key.** A hand held level while the shoulder rotates is a straight path built out of two
467
+ arcs, and two keys cannot express it: the mid-point of the arc bulges away from the
468
+ line. One key at the middle of the span, placed on the line, removes most of the bulge;
469
+ two removes the rest. This is the one case where key count is doing geometric work
470
+ rather than shaping timing.
471
+
472
+ ### 3.6 📗 Anticipation, and 🧩 where it is allowed to live
473
+
474
+ A movement is catalogued as being prepared for by a smaller counter-movement — a
475
+ crouch before a jump, a wind-up before a throw — which readies the audience for what
476
+ is about to happen
477
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
478
+
479
+ 🚨 **⇒ At L1 and L2 the anticipation goes *after* `t: 0`, never before it, and this is
480
+ not a style point.** The first key pose is a **given condition**: the spec states it at
481
+ `t: 0` by construction. An anticipation authored by moving the first key earlier, or by
482
+ setting `t: 0` to the counter-pose, has overwritten an input with an invention. ⇒ Keep
483
+ `t: 0` exactly as `pose` read it, and put the counter-pose at a small positive time.
484
+
485
+ 🧩 **⇒ Defaults: the counter-move is 5–10 % of the main excursion, and its key sits at
486
+ 10–15 % of the duration.** Below 5 % it does not read; past about 15 % it stops being a
487
+ preparation and becomes a first move of its own, which is a different animation. A
488
+ movement that happens *to* the figure gets **none** — nothing anticipates being hit.
489
+
490
+ ### 3.7 📗 Follow-through and overlapping action, and 🧩 the offset table
491
+
492
+ Two related catalogued principles: parts of a body continue moving after the body has
493
+ stopped (**follow-through**), and parts do not all start and stop at the same time
494
+ (**overlapping action**) — the second being what stops a figure reading as one rigid
495
+ piece
496
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
497
+
498
+ 🧩 **⇒ In a keyed skeleton, overlap is a *timing offset per bone*, and it is the single
499
+ cheapest thing on this page.** Every bone's extreme key is at some fraction of the
500
+ duration; move a trailing bone's extreme later than its parent's and the chain reads as
501
+ connected. Nothing else changes — same poses, same easings, same key count.
502
+
503
+ 🧩 Defaults, as a fraction of the whole movement, for a chain hanging off a driver:
504
+
505
+ | Part, relative to its driver | Extreme lands | Settles |
506
+ | --- | --- | --- |
507
+ | the driver itself (the bone the intent is about) | at its own extreme | at the end |
508
+ | the next link out (forearm, neck, upper prop) | **+8–15 %** later | after the driver |
509
+ | the link after that (hand, head, prop tip) | **+15–25 %** later | last of all |
510
+ | something loose and light (cloth, hair, a pennant) | **+20–35 %** later, and it **overshoots** | last, with one crossing |
511
+ | a planted part (a base, a foot in contact) | ⛔ no timeline at all | — |
512
+
513
+ ⚠️ **The offsets compound down a chain and they are fractions, not seconds** — a
514
+ 0.15 s snap and a 2 s idle both get the same table. Past about 35 % the trailing part
515
+ is no longer following the driver, it is doing a separate action, and the movement
516
+ reads as two events rather than one.
517
+
518
+ ⛔ **A part the pictures show unchanged gets no timeline.** Keys that repeat the setup
519
+ value are exactly what the editor's own Clean Up deletes —
520
+ [Keys](http://esotericsoftware.com/spine-keys) — and a track that holds one value for a
521
+ whole animation is a reader's false lead about what the movement is about.
522
+
523
+ ### 3.7.1 🧩 The value analogue of the offset table — one track, a number per part
524
+
525
+ The table above is a per-part **timing** offset, and in rigc it has a field:
526
+ `groups` names the parts, `stagger` adds the delay in member order, and `gallery/ride`
527
+ keys four wheels and two ears that way (AUTHORING §4.3).
528
+
529
+ 🧩 **The other half of "each part gets its own number" is the value.** `groups`
530
+ keys its members **identically**, which is right for a wheel pair and wrong for a face:
531
+ there, the whole content of the movement is that every part moves a *different* amount.
532
+
533
+ 🧩 **Two spellings, and the choice is whether the numbers are decisions or
534
+ arithmetic** (AUTHORING §4.5.1 is the field reference):
535
+
536
+ - a key's `v` may be a **map keyed by member name**, which is the right form when each
537
+ number is a judgement — six hanging locks given six swings. The emitted file is byte
538
+ for byte the one six separate tracks would produce, so this is a pure relocation;
539
+ - a key may state a **`derive` model** instead — `yaw` or `pitch`, an angle, and a
540
+ **depth per member** — and the compiler evaluates each member's value from it. That
541
+ is the right form when the numbers were never judgements: `x·(cos t − 1) − z·sin t` at
542
+ six different columns is arithmetic, and the depths are the only decisions in it.
543
+
544
+ ⭐ **The reason to prefer the model where it fits is not the line count. It is that the
545
+ depths get written down.** FACE §2's sharp edge is that a part's `x` is in the file and
546
+ its `z` is not — and a `z` nobody wrote down is a number nobody can check, on a page
547
+ where a wrong depth reads as a slide rather than a turn and **nothing complains**.
548
+
549
+ ⚠️ **The two axes stay separate, deliberately.** `derive` takes no phase, index or
550
+ delay: `stagger` already does timing and two mechanisms for one lag would mean two
551
+ places to look for it. A track can carry both, and on a face usually should — the
552
+ features' parallax is per-member value, and their settle can still trail per §3.7.
553
+
554
+ ### 3.8 📗 Exaggeration, and 🧩 overshoot as its keyed form
555
+
556
+ Exaggeration is catalogued as pushing a movement past its literal reading so the
557
+ intent survives
558
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
559
+
560
+ 🧩 **⇒ For a movement that ends fast, the keyed form is an overshoot: pass the final
561
+ value, then come back to it.** Default **8–12 %** of the excursion past the end value,
562
+ with the overshoot key at **55–70 %** of the duration and the final value at the end.
563
+ 🚨 The overshoot is an **interior** key — the last key still carries the given end pose
564
+ exactly, for the same reason §3.6 keeps `t: 0` intact. A movement that ends slowly gets
565
+ no overshoot; there is nothing to absorb.
566
+
567
+ ### 3.9 🧩 The pivot — the in-between's own geometry, and the one thing two poses may not fix
568
+
569
+ The two end poses need no pivot: they are stated as placements. **The in-betweens need
570
+ one**, because interpolating a `rotate` track means turning about the bone's position,
571
+ so the pivot decides the entire path between the ends. It is an in-betweening input,
572
+ and this is where it is decided.
573
+
574
+ 📏 **That claim is measured on the other side.** [INGEST.md](INGEST.md) §4.1 moves one
575
+ pivot inside an existing rig and reports what `check` sees: the setup pose unchanged to
576
+ 2.8e-14 units, and the difference climbing monotonically from the first frame the bone
577
+ rotates. Read it if you want the numbers behind *"a pivot is invisible in either end
578
+ pose."*
579
+
580
+ **Two placements of the same part fix its rotation's fixed point, when the rotation is
581
+ large.** With the part's centre at `cA`, `cB` and its screen rotation at `θA`, `θB`, the
582
+ point that is fixed in both is the solution of
583
+
584
+ ```
585
+ ( R(θA) − R(θB) ) · d = cB − cA d = the pivot, as an offset from the part
586
+ image's centre in the part's own axes
587
+ ```
588
+
589
+ a 2×2 solve, where `R(θ)` is the frame's clockwise rotation. Reconstruct the world
590
+ pivot from either pose — they agree by construction — and take it into the parent's
591
+ local space to write it as a bone offset.
592
+
593
+ 🚨 **And here is the honesty this needs, in the same shape AUTHORING §8.1 states it
594
+ for a fitted joint: the solve is well-conditioned only when the relative rotation
595
+ across the joint actually *changes* between the two poses.** The determinant of that
596
+ 2×2 is exactly
597
+
598
+ ```
599
+ |det| = 4 · sin²(Δ/2) Δ = the CHANGE in relative angle across the joint
600
+ ```
601
+
602
+ so a reading error in the placements is amplified into the pivot by about
603
+ **1 / (2 · sin(Δ/2))**. That factor **attenuates** at Δ = 80° (≈ 0.8×) and **multiplies
604
+ by five** at Δ = 11°. ⚠️ **Nothing reports it.** Both solves return an exact answer, both
605
+ reconstruct to a fixed point that agrees between the two poses to the last decimal, and
606
+ the residuals in the pose report never move — the ill-conditioned one is simply wrong,
607
+ quietly, and every in-between hung off it swings about the wrong centre.
608
+
609
+ ⇒ **The rule, and it is arithmetic you already have:**
610
+
611
+ - **Δ ≥ 45°** — solve it. The answer is better than the placements it came from.
612
+ - **20° ≤ Δ < 45°** — solve it, then **check the conditioning** by re-solving from
613
+ placements perturbed by a pixel and seeing how far the pivot moves. If it moves
614
+ further than you would accept as a bone position, treat it as the next case.
615
+ - **Δ < 20°** — ⛔ **do not use the solve.** Take the default below and **say in your
616
+ log that the pivot was defaulted and why** — the number, not the word: *"flag hinge
617
+ defaulted; relative rotation changed 10.5° between the two poses, amplification 5.5×"*.
618
+ - **Δ = 0** (a part that only translates, or a rigid pair) — the pivot is not a
619
+ quantity the pictures contain at all. Default it.
620
+
621
+ 🧩 **The default, in order of preference.** Each is a reading of something you can
622
+ actually see, which is why they beat an ill-conditioned solve:
623
+
624
+ 1. **The joint the art draws.** Part PNGs cut for rigging usually carry the hinge —
625
+ a collar, a hoist edge, a socket, a darker cap. Take that feature's centre as the
626
+ pivot in the part's own pixels, then use the **placements** to say where it lands.
627
+ Averaged over the poses this is a *measurement of one point*, not a solve, so it
628
+ does not amplify anything.
629
+ 2. **The overlap of the two parts' footprints.** Where the child's `bbox` and the
630
+ parent's intersect, the centroid of the intersection is the visible joint.
631
+ 3. **The parent's far end.** The last resort, and often a few pixels out — which is
632
+ exactly why it gets said out loud.
633
+
634
+ 📌 **Then check the default the cheap way: it should agree with itself across the
635
+ poses.** Take your chosen pivot point into the parent **bone's** frame once per pose —
636
+ origin at the parent's own pivot, not at the centre of its image. A real hinge is
637
+ *fixed* there, so the readings should differ by about your placement noise; if they
638
+ differ by several pixels, the point you picked is not the hinge. §6 runs this check on
639
+ a real pair and gets 0.25 px. ⚠️ The check cannot see the origin: measured from the
640
+ wrong one, the readings agree exactly as well, because a constant offset agrees with
641
+ itself. §6 shows both, and what the wrong one draws.
642
+
643
+ ### 3.10 📗 Secondary action, and 🧩 what it costs here
644
+
645
+ A supporting movement that reinforces the main one — catalogued as secondary action —
646
+ is what makes a movement specific rather than generic
647
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
648
+
649
+ 🧩 **⇒ It is a whole extra timeline and it is the first thing to leave out of a first
650
+ candidate.** A secondary action is a decision about character, and a ballot that asks a
651
+ person to compare two candidates differing in *both* the primary timing and a secondary
652
+ action has asked two questions and will get one answer (§4). Land the primary movement,
653
+ then propose the secondary action as its own spread.
654
+
655
+ ### 3.11 What this section does not claim
656
+
657
+ Deliberately absent, because no public page states them and asserting them would be
658
+ handing you an answer nobody measured:
659
+
660
+ - any figure for keys per second, or for how key density should scale with duration;
661
+ - what any particular studio, project or shipped rig actually used for any of the
662
+ numbers above;
663
+ - that the offsets in §3.7 are right for a specific figure, weight or scale — they are
664
+ starting points chosen to be watchable;
665
+ - that a movement built entirely from these defaults is good. They are what a first
666
+ candidate is made of, and §4 is what happens next.
667
+
668
+ If one of these turns out to matter for a request, it belongs in that run's own notes
669
+ as something the user had to teach you — not here.
670
+
671
+ ---
672
+
673
+ ## 4. Candidate-spreading axes
674
+
675
+ `rigc vote` takes 2–4 compiled candidates and gives a person one page of looping
676
+ pixels, no paths and no prose (AUTHORING §0). What comes back is worth having only if
677
+ the candidates on it **differ in interpretation**.
678
+
679
+ ⛔ **The same easing at three strengths is a wasted ballot.** A person asked to choose
680
+ between *a bit of ease*, *more ease* and *a lot of ease* will pick one, the ledger will
681
+ record it, and you will have learned a preference about a knob rather than about the
682
+ movement. ⇒ Spread on the axes below: each one is a **different reading of the same
683
+ request**, so whichever wins tells you something the next candidate can use.
684
+
685
+ | Axis | Candidate A | Candidate B | Worth a slot when |
686
+ | --- | --- | --- | --- |
687
+ | **Path** | the move rides the hinge (`rotate`) | the move is a line (`translate`, or `rotate` with interior keys on the line) | a part travels further than its own length, so the path is visible at all |
688
+ | **Part timing** | every part reaches its extreme together | the chain staggers, per §3.7's table | the figure is more than one bone deep. This is the highest-yield axis on the page |
689
+ | **Part amount** | every part moves the same distance | each part moves by its own depth, per §3.7.1 | the movement is a turn or a lean rather than a slide, so parallax is what says which. 🧩 One `derive` key per spread, not a table per part |
690
+ | **Anticipation** | none — the movement starts at the first pose | a counter-move at 10–15 % | the intent leaves it open whether the figure *does* this or *has it done to it* |
691
+ | **Termination** | arrives and stops | overshoots and settles, per §3.8 | the movement ends fast |
692
+ | **Segmentation** | one continuous movement | two beats with a hold between them | the prompt has two verbs in it, or a comma doing the work of one |
693
+ | **Key density** | ends plus one interior key | ends plus three or four | the intent names a shape (*"hesitates"*, *"in stages"*) that the ends cannot carry |
694
+ | **Pivot, where it was defaulted** | the art's own joint feature | the parent's far end | §3.9 defaulted it and the two readings are several pixels apart. The ballot is then answering a question the pictures did not |
695
+ | **Deform** (advanced) | rigid throughout | squash/stretch on the extremes via a `deform` timeline (AUTHORING §4.11) | the rigid candidates have already been chosen between. ⚠️ **The base recipe is rigid-first** — see §7. 🧩 A deform key can state its transform rather than a table of offsets (AUTHORING §4.11.1), so this axis is two numbers to spread on rather than two tables to transcribe |
696
+
697
+ 📌 **One axis per ballot.** Two candidates differing on two axes cannot be read: the
698
+ winner tells you the pair was better, not which half of it was. If two axes both look
699
+ live, that is two ballots, and the first one's answer usually settles the second.
700
+
701
+ 🧩 **Two is the useful width, three is the ceiling.** `vote` accepts four panes, and a
702
+ person watching four loops at once is comparing the two they happened to look at
703
+ together. Reach for three only when the axis genuinely has three readings (a path that
704
+ can go over, under or straight through).
705
+
706
+ ---
707
+
708
+ ## 5. The loop, and what comes back
709
+
710
+ ```bash
711
+ rigc build --rig m.rig.json --motion m.motion.json --images parts --out spine-a
712
+ rigc build --rig m.rig.json --motion b.motion.json --images parts --out spine-b
713
+ rigc render --candidate spine-a # look at it yourself first
714
+ rigc vote --candidate spine-a --candidate spine-b # -> ballot.html
715
+ rigc vote --record vote-<id>.json # -> votes.jsonl
716
+ ```
717
+
718
+ **Compile first, vote last.** A candidate reaches a ballot only because it already
719
+ built green, so the person is never asked to read a spec, a diff or JSON (AUTHORING
720
+ §0). And look at your own candidates with `render` before you ask anybody else to:
721
+ green says the file is valid and nothing more, and a head sitting off its torso passes
722
+ every assertion.
723
+
724
+ What the ledger can say, and what each one means for the next step:
725
+
726
+ | Verdict | What it means here |
727
+ | --- | --- |
728
+ | a **winner**, `preferred` | that reading of the request is the one. Build the next spread **inside** it — take the winner and spread it on a second axis |
729
+ | a **winner**, `defect-in-others` | the others had something wrong, which is not the same as this one being right. Look for the defect, fix it, and consider re-asking on the same axis |
730
+ | **tie**, `indistinguishable` | the axis you spread on does not matter for this request. ⇒ Stop spending ballots on it and pick either |
731
+ | **tie**, `both-acceptable` | the axis matters and both readings work. Pick one, say which, move on |
732
+ | **tie**, `both-unacceptable` | 🚨 **propose again from a DIFFERENT axis.** Not a nudge of either candidate — both readings were rejected, so the thing to change is what the candidates disagree about. Going back with the same axis at new strengths is the wasted ballot from §4, arriving by a second route |
733
+ | **tie**, `unsure` | the page did not show the difference. Check that the difference is actually visible at the rendered size and rate before re-asking |
734
+
735
+ ⚠️ **A tie is a recorded answer, not a missing one**, and `both-unacceptable` is only
736
+ reachable because ties are recordable — check for it before treating a ballot as
737
+ settled. Every line carries the winner as a content **digest** rather than a label
738
+ (`B` means nothing outside one ballot) and a `coverage` set, so what is still
739
+ unreviewed is computable.
740
+
741
+ ---
742
+
743
+ ## 6. A worked example, end to end (L1)
744
+
745
+ 🚫 **Every value in this section is invented.** The parts, the pictures, the numbers,
746
+ the easing table, the times — a signal post that exists nowhere else in this
747
+ repository, chosen so that the whole recipe runs on something small enough to read.
748
+ Nothing here is an answer to anything.
749
+
750
+ What *is* real: every command line below was run, and every figure printed in an
751
+ output block is what the command actually printed.
752
+
753
+ 🖼️ **For the same recipe on art that ships, the
754
+ [`gallery/`](https://github.com/firejune/rigc/tree/main/gallery) examples are worked
755
+ in-betweening material** — `walk` is §3.5's arcs and §3.7's phase offsets on two leg
756
+ chains, `ride` puts the same offsets in `groups` + `stagger`, and `squash` is §3.9's
757
+ pivot written as a `deform` about a contact point. Each README says what every key is
758
+ *for* rather than only what it is, and what looking at the render changed.
759
+
760
+ ### The request
761
+
762
+ > *"Here are the parts and two pictures of the signal arm — hanging down in the
763
+ > first, raised in the second. Make it snap up and settle."*
764
+
765
+ Normalised against §1: parts directory **given**; two pose frames, **ordered**;
766
+ duration **absent** → §3.3 says a movement the figure *does*, so propose **0.55 s** and
767
+ say so; loop **absent** and the two pictures are different poses → **not a loop**;
768
+ intent adjectives **"snap ... settle"** → the extreme arrives early, the value settles
769
+ late, §3.8's overshoot is live; animation name → **`raise`**.
770
+
771
+ ### 1. The art, and the two pictures
772
+
773
+ ```bash setup
774
+ mkdir -p semaphore/parts && cd semaphore
775
+ bun -e '
776
+ const files = {
777
+ "parts/post.png": "iVBORw0KGgoAAAANSUhEUgAAAA4AAABgCAYAAAAttkP7AAAAVklEQVR42mPYsOvMf3Iww6jG4aExr2bKf2Ts4BVDFB7VOKpxVCNOjXIqBv/JwUNJ42gCGNU4qnFU42hJPlqSj2oc1TiqcVTjqMbR+nG0fhxNOaMawRgAyYT+Nyka/GsAAAAASUVORK5CYII=",
778
+ "parts/arm.png": "iVBORw0KGgoAAAANSUhEUgAAADwAAAAOCAYAAABzTn/UAAAAP0lEQVR42mPI8FP4Twp+c6ZpUGNC7mcY9fCoh0e4h49Nc8CLSVVPa/2jHh718KiHRz086uFRD496eNTDA+ZhAPte2VL+X1bRAAAAAElFTkSuQmCC",
779
+ "parts/flag.png": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAUCAYAAACeXl35AAAAMElEQVR42mOo09H4j46PeLjQDDOMWjhqIdUtfDZvCkV41MJRC4ehhaMlzaiFI89CANaNM2RJry/OAAAAAElFTkSuQmCC"
780
+ };
781
+ for (const [p, b] of Object.entries(files)) await Bun.write(p, Buffer.from(b, "base64"));
782
+ '
783
+ ```
784
+
785
+ Three plates: a **post** 14×96 with a light cap, a lit left edge and three unevenly
786
+ spaced bands; an **arm** 60×14 with a lit top edge, a hub at one end, a collar at the
787
+ other and two ties between; a **flag** 28×20 with a dark hoist edge down one side and a
788
+ pale blaze across the middle. The interior detail is not decoration — a part that is one
789
+ flat colour is self-similar under scaling, and §2.2's window caveat is exactly what that
790
+ produces.
791
+
792
+ The two pictures stand in for what a user would hand over. Both are 160×200 on a flat
793
+ `rgb(238, 238, 234)` ground, with the post upright, the arm turned about the top of the
794
+ post, and the flag hanging off the arm's collar — **arm drawn over post, flag over arm**.
795
+ `poseA` has the arm down and to the right and the flag drooping past it; `poseB` has the
796
+ arm raised and the flag close to level. Their bytes are in
797
+ [the appendix](#appendix--the-two-pose-frames) so the section runs end to end.
798
+
799
+ ### 2. Read the pictures — `rigc pose`
800
+
801
+ First call, default windows:
802
+
803
+ ```bash
804
+ rigc pose --images parts --frame poseA.png
805
+ ```
806
+
807
+ ```
808
+ rigc pose
809
+ .. frame …/semaphore/poseA.png (160x200)
810
+ .. ground rgb(238, 238, 234) over 100% of the border ring
811
+ .. parts …/semaphore/parts (3 png)
812
+ .. search scale 0.5–2 in 7 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
813
+ PLACE arm.png x= 92.0 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
814
+ found on a 20x25 anchor grid, step 4 at 2x reduction
815
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
816
+ found on a 40x50 anchor grid, step 2 at 2x reduction
817
+ AMBIG post.png x= 79.9 y= 149.0 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
818
+ found on a 14x17 anchor grid, step 6 at 2x reduction
819
+ alt 2: x= 79.0 y= 141.7 rot= -0.5° scale=0.691 residual=0.0687 unexplained= 25%
820
+ ambiguous: part is 14x96 px (span 93.2 frame px, opaque 1) with texture 0.0146, detail 1.406; 43 candidate(s), best 0.0593, next 0.0687, spread 0.0095 — above the measured floor (AUTHORING §11.5: detail 0.5, measured from 24 px up), so size and texture do not explain this; either the frame holds more than one place this part fits, or every candidate missed the true one — a narrower --scale or --rotation window around what you know of the part tells the two apart
821
+ ```
822
+
823
+ ⚠️ **The post came back `AMBIG`: a best at scale 0.971 and an alternate at 0.691, 7.3 px
824
+ higher, trailing it by a spread of 0.0095.** That is §2.2's caveat in the open. The post
825
+ is a long part with most of its area in one colour, so a shrunken copy sitting inside the
826
+ real post explains those pixels nearly as well, and the report's own `ambiguous:` line
827
+ says size and texture do not account for how close the two come. The report cannot say
828
+ which one is true, and it says so: it names both and asks for a narrower window. The
829
+ picture can. The arm and the flag read 0.970 and 0.955, so the picture is at the art's
830
+ own resolution, and a post at 0.69 would be drawn at about 0.7 of the size of everything
831
+ bolted to it. ⇒ Narrow, and say so:
832
+
833
+ ```bash
834
+ rigc pose --images parts --frame poseA.png --scale 0.85,1.2 --out poseA.json
835
+ rigc pose --images parts --frame poseB.png --scale 0.85,1.2 --out poseB.json
836
+ ```
837
+
838
+ ```
839
+ rigc pose
840
+ .. frame …/semaphore/poseA.png (160x200)
841
+ .. ground rgb(238, 238, 234) over 100% of the border ring
842
+ .. parts …/semaphore/parts (3 png)
843
+ .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
844
+ PLACE arm.png x= 91.9 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
845
+ found on a 20x25 anchor grid, step 4 at 2x reduction
846
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.1° scale=0.956 residual=0.0135 unexplained= 0%
847
+ found on a 40x50 anchor grid, step 2 at 2x reduction
848
+ PLACE post.png x= 79.9 y= 148.5 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
849
+ found on a 14x17 anchor grid, step 6 at 2x reduction
850
+ ```
851
+
852
+ ```
853
+ rigc pose
854
+ .. frame …/semaphore/poseB.png (160x200)
855
+ .. ground rgb(238, 238, 234) over 100% of the border ring
856
+ .. parts …/semaphore/parts (3 png)
857
+ .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
858
+ PLACE arm.png x= 102.5 y= 94.8 rot= -18.3° scale=0.974 residual=0.0305 unexplained= 4%
859
+ found on a 20x25 anchor grid, step 4 at 2x reduction
860
+ PLACE flag.png x= 137.4 y= 86.0 rot= -6.6° scale=0.956 residual=0.0134 unexplained= 0%
861
+ found on a 40x50 anchor grid, step 2 at 2x reduction
862
+ PLACE post.png x= 80.0 y= 148.0 rot= 0.0° scale=0.995 residual=0.0394 unexplained= 9%
863
+ found on a 14x17 anchor grid, step 6 at 2x reduction
864
+ ```
865
+
866
+ Three things to read out of that pair, none of which is a score:
867
+
868
+ - **Scale.** All six readings sit in 0.956–0.995, a spread of about 4 %. The one part
869
+ read twice at one place, the post, reads 0.971 and 0.995 — 2.5 % apart on a part that
870
+ does not move — so a 4 % spread is the size of the method's own repeatability rather
871
+ than six different scales. ⇒ Take the pictures as being at the art's own resolution
872
+ and author the rig in **part pixels**, so no scaling appears in the spec at all. Say
873
+ that this is what the spread was read as.
874
+ - **Draw order, from `unexplained`.** The post reads **16 %** unexplained in pose A and
875
+ **9 %** in pose B, at placements that barely move — §2.2's occlusion signature. The arm
876
+ crosses more of the post in pose A, so the arm is **in front of** the post. The flag
877
+ reads 0 % in both: nothing covers it, so it is **in front of** the arm. ⇒ Slots in
878
+ the order `post`, `arm`, `flag` (AUTHORING R4 — the slots array *is* the draw order,
879
+ and there is nowhere else in the file to say it).
880
+ - **The post does not move**, so its two readings are two measurements of one number:
881
+ x 79.9/80.0 and y 148.5/148.0. ⇒ Use the mean, **(79.95, 148.25)**, and treat the 0.5 px
882
+ disagreement as the noise floor for every other number on the page.
883
+
884
+ ### 3. Convert, and derive the rig
885
+
886
+ `cropToSpineY(y, 200) = 200 − y` and `screenToSpineDegrees(d) = −d`, both from
887
+ [`src/transform.ts`](../src/transform.ts) (§2.2 — do not open-code either):
888
+
889
+ | | pose A, Spine world | pose B, Spine world |
890
+ | --- | --- | --- |
891
+ | `post` | x 79.9 · y 51.5 · rot 0.1° | x 80.0 · y 52.0 · rot 0.0° |
892
+ | `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.5 · y 105.2 · rot 18.3° |
893
+ | `flag` | x 104.7 · y 43.9 · rot −84.1° | x 137.4 · y 114.0 · rot 6.6° |
894
+
895
+ **The shoulder, by §3.9's solve.** The arm's screen rotation changes from 61.9° to
896
+ −18.3°, so **Δ = 80.2°** — well inside the *solve it* band, `|det| = 4·sin²(40.1°) =
897
+ 1.660`, amplification 0.78×. Solving the 2×2 puts the fixed point at frame **(80.57,
898
+ 102.51)**, reconstructing identically from both poses, and the offset lands at arm-image
899
+ pixel **(6.76, 7.43)** — inside the arm's own hub, which is where a hub is for. In Spine
900
+ world that is (80.57, 97.49); in the post bone's local space, **(0.62, 45.74)**. That
901
+ point is the `arm` bone's origin, and it is the origin of everything measured in the
902
+ arm's frame below.
903
+
904
+ **The flag hinge, by §3.9's default — and this is the interesting one.** The relative
905
+ angle across that joint is 22.2° in pose A and 11.7° in pose B, so **Δ = 10.5°**:
906
+ `|det| = 0.0335`, amplification **5.5×**, comfortably inside the *do not use the solve*
907
+ band. Run it anyway, to see what it would have cost — it puts the hinge at **(49.15,
908
+ −0.38)** in the arm bone's frame, exactly as confidently as the shoulder did. The default
909
+ instead: the flag's art draws its hinge as a dark hoist strip down one edge, whose centre
910
+ is flag-image **(2.5, 10)**. Carry that point through each pose's placement into the
911
+ **arm bone's frame** — origin at the shoulder pivot above, x along the arm, y up: take
912
+ the point's frame position, subtract the pivot's, turn it back through the arm's screen
913
+ rotation and flip y once. Carried unrounded, printed rounded:
914
+
915
+ | | pose A | pose B |
916
+ | --- | --- | --- |
917
+ | the hinge in the frame, through the flag's placement | (103.52, 144.66) | (125.98, 87.32) |
918
+ | minus the pivot, (80.57, 102.51) | (22.94, 42.15) | (45.40, −15.18) |
919
+ | turned by −61.9° and +18.3°, y flipped | **(47.99, 0.38)** | **(47.87, 0.16)** |
920
+
921
+ That is §3.9's self-agreement check, and the two poses agree to **0.25 px**. ⇒ Take the
922
+ mean, **(47.93, 0.27)**, write it as **(47.93, 0)** — the y is inside the 0.5 px noise
923
+ floor — and write in the log that the pivot was **defaulted**, with the number:
924
+ *relative rotation changed 10.5°, amplification 5.5×, ill-conditioned solve declined*.
925
+
926
+ ⚠️ **Which origin the arm's frame has is the whole of this step, and the check above
927
+ cannot tell you.** A placement reports where the part image's **centre** lands, so the
928
+ nearest frame to hand is the one centred on the arm image — and measured from there the
929
+ same hinge reads (24.76, −0.05) and (24.64, −0.27). Those agree to 0.25 px just as well,
930
+ because a constant offset agrees with itself. They are short by 23.24, the arm
931
+ attachment's own offset below, and a flag hung at x 24.70 draws over the middle of the
932
+ arm: built and rendered on the pictures' grid, its flag sits 23.5 px from where
933
+ `poseA.png` draws it in frame 0 and 23.2 px from `poseB.png` at the end. With x 47.93 it
934
+ sits 0.4 px and 0.2 px off, inside the noise floor. A bone sits at its **pivot**, and its
935
+ children's offsets are measured from there.
936
+
937
+ ⭐ Worth pausing on, because it is what §3.9 is for: **the two solves are
938
+ indistinguishable from the inside.** Both are exact, both reconstruct to a point that
939
+ agrees between the poses, and no residual anywhere in either pose report moves. Here
940
+ the declined one lands 1.4 px from the drawn hinge — the 5.5× amplification applied to
941
+ about a quarter-pixel of placement noise — and nothing in either answer says which of the
942
+ two is the one to trust. The only thing separating them is Δ, which is arithmetic you
943
+ can do before you trust either.
944
+
945
+ `semaphore.rig.json` — complete, nothing trimmed:
946
+
947
+ ```json
948
+ {
949
+ "spec": "rigc-rig/1",
950
+ "name": "semaphore",
951
+ "images": "parts",
952
+ "skeleton": { "width": 160, "height": 200 },
953
+ "bones": [
954
+ { "name": "root" },
955
+ { "name": "post", "parent": "root", "x": 79.95, "y": 51.75 },
956
+ { "name": "arm", "parent": "post", "x": 0.62, "y": 45.74 },
957
+ { "name": "flag", "parent": "arm", "x": 47.93, "y": 0 }
958
+ ],
959
+ "slots": [
960
+ { "name": "post", "bone": "post", "attachment": "post" },
961
+ { "name": "arm", "bone": "arm", "attachment": "arm" },
962
+ { "name": "flag", "bone": "flag", "attachment": "flag" }
963
+ ],
964
+ "skins": {
965
+ "default": {
966
+ "post": { "post": { "image": "post.png" } },
967
+ "arm": { "arm": { "image": "arm.png", "x": 23.24 } },
968
+ "flag": { "flag": { "image": "flag.png", "x": 11.5 } }
969
+ }
970
+ }
971
+ }
972
+ ```
973
+
974
+ The two attachment offsets are the last of the arithmetic. A bone sits at its pivot and
975
+ the placement told you where the image's **centre** goes, so the offset is the gap
976
+ between them, in the bone's own axes with y flipped once: the arm's pivot is at image
977
+ (6.76, 7.43) and its centre at (30, 7), giving **x 23.24** (the y term is 0.43, inside
978
+ the 0.5 px noise floor, so it is not written); the flag's hinge is at image (2.5, 10) and
979
+ its centre at (14, 10), giving **x 11.5** exactly. Bone rotations are left off, which
980
+ means *as drawn* — the arm plate is drawn horizontal and the post vertical, so the poses
981
+ are entirely the motion spec's business.
982
+
983
+ ### 4. In-between it
984
+
985
+ Duration 0.55 s, proposed (§3.3). Three easings (§3.4). The arm is the driver. The flag
986
+ is the last thing on it and it is cloth, which is the row §3.7's table gives to *a
987
+ pennant* — not the *next link out* row, because nothing hangs off the flag — so its
988
+ extreme lands **+20–35 %** after the arm's, it overshoots, and it settles last with one
989
+ crossing. *"Snap"* buys an anticipation (§3.6) and an
990
+ overshoot (§3.8). The post is planted: ⛔ **no timeline**.
991
+
992
+ `semaphore.motion.json` — complete, nothing trimmed:
993
+
994
+ ```json
995
+ {
996
+ "spec": "rigc-motion/1",
997
+ "archetype": "semaphore",
998
+ "cut": "semaphore",
999
+ "easings": {
1000
+ "gather": [0.42, 0, 0.8, 0.36],
1001
+ "charge": [0.1, 0.72, 0.34, 1],
1002
+ "settle": [0.28, 0, 0.36, 1]
1003
+ },
1004
+ "animations": {
1005
+ "raise": {
1006
+ "duration": 0.55,
1007
+ "loop": false,
1008
+ "tracks": [
1009
+ {
1010
+ "bone": "arm",
1011
+ "property": "rotate",
1012
+ "keys": [
1013
+ { "t": 0, "v": [-61.9], "ease": "gather" },
1014
+ { "t": 0.07, "v": [-66.4], "ease": "charge" },
1015
+ { "t": 0.32, "v": [24.7], "ease": "settle" },
1016
+ { "t": 0.55, "v": [18.3] }
1017
+ ]
1018
+ },
1019
+ {
1020
+ "bone": "flag",
1021
+ "property": "rotate",
1022
+ "keys": [
1023
+ { "t": 0, "v": [-22.2], "ease": "gather" },
1024
+ { "t": 0.09, "v": [-30.1], "ease": "charge" },
1025
+ { "t": 0.47, "v": [-3.8], "ease": "settle" },
1026
+ { "t": 0.51, "v": [-15.4], "ease": "settle" },
1027
+ { "t": 0.55, "v": [-11.7] }
1028
+ ]
1029
+ }
1030
+ ]
1031
+ }
1032
+ }
1033
+ }
1034
+ ```
1035
+
1036
+ Every number in there is one of the two given conditions or one of §3's defaults, and
1037
+ which is which is worth being able to point at:
1038
+
1039
+ | Key | Where it came from |
1040
+ | --- | --- |
1041
+ | arm `t: 0` = −61.9, flag `t: 0` = −22.2 | **given** — pose A, converted. Untouched, per §3.6 |
1042
+ | arm `t: 0.55` = 18.3, flag `t: 0.55` = −11.7 | **given** — pose B. The flag's is `6.6 − 18.3`: both world rotations converted first, then differenced, because a child's track carries a **local** rotation under a rotated parent |
1043
+ | arm `t: 0.07` = −66.4 | §3.6 — 4.5° against an 80° excursion (5.6 %), at 13 % of the duration |
1044
+ | arm `t: 0.32` = 24.7 | §3.8 — 6.4° past the end value (8 %), at 58 % of the duration |
1045
+ | flag `t: 0.09`, `t: 0.47` | §3.7 — the flag's extreme lands at 85 % of the duration against the arm's 58 %, an offset of **+27 %**, the middle of its row's band, and it drags the other way first. Not the top of the band: at 35 % (`t: 0.51`) the crossing and the settle below would share 0.04 s, and the 24 fps render would still show the flag at its extreme one frame before the last |
1046
+ | flag `t: 0.51` = −15.4 | §3.7's *one crossing* for a loose part: it comes back past its own end value before settling, keyed halfway between its extreme and the end |
1047
+ | the three easings | §3.4 — one that gathers, one that arrives slowly, one symmetric. The **last key of each track carries no easing**, because there is nothing after it to ease towards (AUTHORING §4.5) |
1048
+ | the post's absent track | §3.7 — a planted part gets no timeline |
1049
+
1050
+ ### 5. Build, then look
1051
+
1052
+ ```bash
1053
+ rigc build --rig semaphore.rig.json --motion semaphore.motion.json --images parts --out spine
1054
+ ```
1055
+
1056
+ ```
1057
+ .. pages=3 regions=3 bones=4 slots=3 animations=1 version=4.3.13 regionAttachments=3 meshAttachments=0 physicsConstraints=0 rig=semaphore profile=spine
1058
+ rigc: wrote …/semaphore/spine/skeleton.json
1059
+ rigc: wrote …/semaphore/spine/skeleton.atlas
1060
+ rigc: wrote …/semaphore/spine/skeleton.model.json
1061
+ rigc: look at it: rigc preview --candidate …/semaphore/spine
1062
+ ```
1063
+
1064
+ ```bash
1065
+ rigc render --candidate spine --fps 24 --max 200
1066
+ ```
1067
+
1068
+ ```
1069
+ .. 134x200px at 24 fps, 1 set(s) -> …/semaphore/render
1070
+ .. raise 14 frame(s), 0.542s + contact.png -> …/semaphore/render/raise@24fps
1071
+ ```
1072
+
1073
+ Open `render/raise@24fps/contact.png` **before anything else** — fourteen frames as one
1074
+ grid, and spacing is a comparison across frames rather than a property of any one of
1075
+ them. What to check on it, and it is not a score: frame 0 is pose A — the flag hanging off
1076
+ the arm's collar, not over its middle — the last frame is pose B, the anticipation dips
1077
+ *after* frame 0, and the flag's extreme is visibly later than the arm's: the arm peaks at
1078
+ frame 8 while the flag is still closing on the arm's line, it keeps closing through
1079
+ frame 11 as the arm eases back, and at frame 12 it drops past its resting angle.
1080
+
1081
+ 🚫 **Do not run `rigc check` against `poseA.png` and `poseB.png`.** Two pictures are not
1082
+ a frame set: `check` reads the second as the frame 1/12 s in, at its default rate. And
1083
+ the ends' **rotations** are given conditions the spec states by construction, so a
1084
+ number for how near the movement got to them is a number about the pose estimator. What
1085
+ the pictures can still catch is §3's arithmetic, because every **offset** in the rig was
1086
+ derived rather than given, and a wrong one moves an end pose off its picture while every
1087
+ key stays exact. That is what the frame-0 check above is for, and it is a look, not a
1088
+ score. §7.
1089
+
1090
+ ### 6. Spread, and ask
1091
+
1092
+ One axis (§4), and **Part timing** is the one this request leaves genuinely open: does a
1093
+ signal flag lag its arm, or is the whole assembly stiff? Candidate B keeps both given end
1094
+ poses, keeps the duration, and drops every §3 default — one easing, two keys per track,
1095
+ no anticipation, no overshoot, no stagger. `semaphore-b.motion.json` is candidate A's file
1096
+ with **these two fields replaced** and `spec`, `archetype` and `cut` unchanged:
1097
+
1098
+ ```json
1099
+ "easings": { "drive": [0.2, 0, 0.4, 1] },
1100
+ "animations": {
1101
+ "raise": {
1102
+ "duration": 0.55,
1103
+ "loop": false,
1104
+ "tracks": [
1105
+ { "bone": "arm", "property": "rotate", "keys": [
1106
+ { "t": 0, "v": [-61.9], "ease": "drive" },
1107
+ { "t": 0.55, "v": [18.3] } ] },
1108
+ { "bone": "flag", "property": "rotate", "keys": [
1109
+ { "t": 0, "v": [-22.2], "ease": "drive" },
1110
+ { "t": 0.55, "v": [-11.7] } ] }
1111
+ ]
1112
+ }
1113
+ }
1114
+ ```
1115
+
1116
+ ```bash
1117
+ rigc build --rig semaphore.rig.json --motion semaphore-b.motion.json --images parts --out spine-b
1118
+ rigc vote --candidate spine --candidate spine-b
1119
+ ```
1120
+
1121
+ ```
1122
+ rigc vote
1123
+ .. ballot 862f444c58d10e3e
1124
+ .. animation raise
1125
+ .. A sha256:c49e458df44e… 3 page(s), 0.4 KiB <- …/semaphore/spine/skeleton.json
1126
+ .. B sha256:d1bda24d7c36… 3 page(s), 0.4 KiB <- …/semaphore/spine-b/skeleton.json
1127
+ .. the page shows A/B and nothing else — the paths above are in its manifest, never on the screen
1128
+ .. embedded every candidate's skeleton, atlas and page(s) as data URIs; the player itself loads from unpkg (@4.3.*), so the first open needs a network
1129
+ rigc: wrote …/semaphore/ballot.html (22.3 KiB — open it in a browser)
1130
+ rigc: then record the saved vote with rigc vote --record vote-862f444c58d10e3e.json --ballot …/semaphore/ballot.html
1131
+ ```
1132
+
1133
+ A person opens that page, watches two loops, picks one, and saves the small JSON it hands
1134
+ them. Then:
1135
+
1136
+ ```bash
1137
+ rigc vote --record vote-862f444c58d10e3e.json --ballot ballot.html
1138
+ ```
1139
+
1140
+ ```
1141
+ PASS V00_RESULT_IS_A_RIGC_VOTE
1142
+ PASS V01_RESULT_NAMES_THIS_BALLOT
1143
+ PASS V02_CANDIDATE_DIGESTS_ARE_THE_BALLOTS
1144
+ PASS V03_BALLOT_ID_DERIVES_FROM_ITS_CANDIDATES
1145
+ PASS V04_CHOICE_IS_ON_THE_BALLOT
1146
+ PASS V05_REASON_CODE_FITS_THE_CHOICE
1147
+ PASS V06_NOT_ALREADY_RECORDED
1148
+ .. winner A = sha256:c49e458df44efd277aa6e50ff8fa1962c1211c7617079d63fbc4cf8008eaf0c5, reason code preferred
1149
+ .. coverage 2 candidate(s): A=sha256:c49e458df44e… B=sha256:d1bda24d7c36…
1150
+ rigc: appended line 1 to …/semaphore/votes.jsonl
1151
+ ```
1152
+
1153
+ 🚫 **That answer is invented like every other value in this section — nobody looked at
1154
+ this ballot.** It is here to show the shape of what comes back: a winner identified by
1155
+ **digest** rather than by the label `A`, a reason code from the closed enumeration, and
1156
+ a `coverage` set naming what this vote actually compared. The `PASS` lines are the
1157
+ ledger checking the answer against the ballot, not anything checking the movement.
1158
+
1159
+ Had `both-unacceptable` come back instead, §5's table says what to do: not a nudge of
1160
+ either candidate, but a new spread on a **different** axis — **Termination**, say, or
1161
+ **Segmentation** — because a rejection of both readings is a statement about the axis.
1162
+
1163
+ ---
1164
+
1165
+ ## 7. Non-goals — stated, so nobody proposes them as gaps
1166
+
1167
+ 🚫 **No `rigc tween`, and no command that generates in-betweens.** Every other command
1168
+ in this toolchain either compiles what you wrote, measures it, or shows it. §3 is a page
1169
+ of authored judgement — timing offsets, arcs, anticipation, the pivot defaults — and a
1170
+ command that applied it would be making those decisions on the user's behalf with no
1171
+ place to say it had. **Authoring stays with the agent.** What the toolchain owes you is
1172
+ that the ends are stateable by construction (`pose`), that the file is checkable
1173
+ (`build`), that you can look (`render`, `preview`), and that a person can choose
1174
+ (`vote`).
1175
+
1176
+ 🚫 **No scoring of end-pose reach, and nothing here to add one to.** At L1 and L2 the
1177
+ end poses are given conditions, and what a picture gives is a **pose**: the rotations are
1178
+ `pose`'s readings, which the spec states by construction, so a number for how near the
1179
+ animation got to *them* is a number about the pose estimator. The **offsets** are the
1180
+ other half — derived from the pictures rather than given — and a rig can miss its
1181
+ picture by a wrong one with every key exact. Catching that is a look, not a score (§6.5's
1182
+ frame-0 check). This is why §6 does not run `check` on the two pictures and why no
1183
+ threshold, tolerance or pass bar appears anywhere in this document. The
1184
+ residuals in a `pose` report are trust signals about *placements*, and AUTHORING §11.1
1185
+ says the same from the instrument's side.
1186
+
1187
+ ⚠️ **Deform in-betweens are an advanced axis, and the base recipe is rigid-first.**
1188
+ Squash and stretch is expressible — a `deform` timeline moves an attachment's vertices
1189
+ over time (AUTHORING §4.11) — and it is a real axis in §4's table. It is last in that
1190
+ table on purpose: it needs a mesh rather than a region attachment, it multiplies the
1191
+ things a candidate differs by, and a movement that does not read when rigid will not be
1192
+ rescued by deforming it. ⇒ Land the rigid movement, choose between rigid candidates,
1193
+ then propose deform as its own spread.
1194
+
1195
+ 🚫 **No per-member easings and no per-member key times, and §3.7.1's construct is
1196
+ bounded by exactly that.** A group's shared times and shared curves are what make it a
1197
+ group; a per-member `v` map or a `derive` model varies the **value** and nothing else.
1198
+ A member that needs its own timing has `stagger` (one number, in member order) or its
1199
+ own track — and a third mechanism would mean the answer to *"when does this bone move"*
1200
+ lived in three places. 🚫 Nor is `derive` an expression language: the kinds are named,
1201
+ and a kind the compiler does not know is refused by name rather than evaluated.
1202
+
1203
+ 🧩 **A deform key stating a transform is not a tween, and the distinction is the
1204
+ first non-goal above rather than a nuance of it.** AUTHORING §4.11.1 lets a key name a
1205
+ model — a yaw, a scale about a point, a wave, a bend — and the compiler evaluates it
1206
+ over the attachment's own vertices. What that removes is **transcription of one key's
1207
+ arithmetic**, which is never judgement: `gallery/portrait`'s held yaw is 160 floats of
1208
+ one closed form. What it does not touch is **anything between two keys** — the times,
1209
+ the easings, the anticipation, the offset table — because a deform timeline still has
1210
+ one 0..1 blend channel and §3 still owns every value on it. ⇒ Sweeping a deform is
1211
+ editing one number **per key**, and a command that chose those numbers or the keys
1212
+ between them would be the `rigc tween` this section refuses.
1213
+
1214
+ ⚠️ **The one case where the deform is not an axis but the movement itself is a face
1215
+ turning off axis**, because a rigid candidate of it does not exist — a yaw *is* the
1216
+ redistribution. [FACE.md](FACE.md) is that case, and it also states what nothing here
1217
+ measures: a `deform` key that folds a mesh inside out gates green on every assertion,
1218
+ so a face's arithmetic has to be checked before the build rather than after the render.
1219
+
1220
+ 🚫 **No frame rate anywhere in either spec file.** `render --fps` is a sampling rate for
1221
+ looking; times in a motion spec are seconds (§3.3).
1222
+
1223
+ 🚫 **No key per frame.** A fitted pose per frame is a pixel transcription wearing a
1224
+ skeleton — AUTHORING §10.3 and PROMPTING clause 4 both price it. Keys are structure.
1225
+
1226
+ ---
1227
+
1228
+ ## Appendix — the two pose frames
1229
+
1230
+ The bytes of `poseA.png` and `poseB.png`, so §6 runs end to end. Both are 160×200 on a
1231
+ flat ground; both are invented.
1232
+
1233
+ ```bash setup
1234
+ bun -e '
1235
+ const frames = {
1236
+ "poseA.png": "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",
1237
+ "poseB.png": "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"
1238
+ };
1239
+ for (const [p, b] of Object.entries(frames)) await Bun.write(p, Buffer.from(b, "base64"));
1240
+ '
1241
+ ```