spine-rigc 0.7.0 โ†’ 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/MOTION.md ADDED
@@ -0,0 +1,990 @@
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
+ - If you are the *person operating* an agent rather than the agent:
26
+ [PROMPTING.md](PROMPTING.md)
27
+
28
+ ---
29
+
30
+ ## 0. The normal form
31
+
32
+ **Every motion request normalises to a key-pose sequence plus in-betweens.** That
33
+ is the whole internal shape, and it does not vary with how much the user gave you.
34
+ What varies is only **where the key poses come from**:
35
+
36
+ | Level | What arrived | Where the key poses come from |
37
+ | --- | --- | --- |
38
+ | **L0** | parts + words (*"a breathing idle"*) | you invent them. A loop is the special case where the first and last are the **same** pose |
39
+ | **L1** | parts + two pictures (*"from this to this"*) | the two pictures, read into spec coordinates by `rigc pose`. They are **given conditions** |
40
+ | **L2** | parts + N ordered pictures | the same, N times. This is the general form and L1 is the N=2 case |
41
+
42
+ โญ **The recipe below is one recipe.** L0 spends its effort inventing poses and then
43
+ in-betweening them; L1 and L2 skip the inventing. Nothing else differs โ€” not the
44
+ key plan, not the easing table, not the candidate axes, not the loop. If you find
45
+ yourself writing a second procedure for a second input level, you have split
46
+ something that is not two things.
47
+
48
+ ๐Ÿšจ **At L1 and L2 the end poses are inputs, not targets.** Once the spec carries the
49
+ numbers `pose` read out of the picture, the animation **states** those poses by
50
+ construction; there is nothing left for it to be close to, and nothing in this
51
+ toolchain measures how near it got. This is not modesty about a weak instrument, it
52
+ decides what you do with your loops: you do not iterate toward the ends, you iterate
53
+ on the movement between them. AUTHORING ยง11.1 argues the same point from the
54
+ instrument's side.
55
+
56
+ The loop this page is inside:
57
+
58
+ ```
59
+ key poses โ†’ in-betweens โ†’ rigc build โ†’ rigc render / rigc preview โ†’ rigc vote
60
+ โ†‘ โ”‚
61
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ a `both-unacceptable` verdict comes back here โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
62
+ ```
63
+
64
+ and the last hop is the one that matters: a `both-unacceptable` tie means **propose
65
+ again from a different axis** (ยง4), not *nudge the same candidate*. ยง5 has the
66
+ detail.
67
+
68
+ ---
69
+
70
+ ## 1. Prompt grammar โ€” what a request is made of
71
+
72
+ A user writes prose. The recipe reads a fixed set of elements out of it. Both halves
73
+ of that are deliberate: the user gets natural language, you get something you can
74
+ act on without asking a questionnaire.
75
+
76
+ ๐Ÿ“Œ **Every absent element has a default, and every default you take gets one line of
77
+ output saying you took it.** A silently defaulted duration is the same defect as a
78
+ silently defaulted pivot: the user cannot correct a decision they were not told
79
+ about.
80
+
81
+ | Element | How it arrives | Default when it is absent |
82
+ | --- | --- | --- |
83
+ | **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 |
84
+ | **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 |
85
+ | **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 |
86
+ | **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 |
87
+ | **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 |
88
+ | **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 |
89
+ | **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 |
90
+ | **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 |
91
+
92
+ โš ๏ธ **Read the intent adjectives before you read the pictures, and write down what
93
+ you think they mean, in movement terms, before any measurement.** *"Snap"* means the
94
+ extreme arrives early and the value settles late; *"heavy"* means the parts separate
95
+ in time more than they otherwise would; *"mechanical"* means constant speed, which is
96
+ the one case AUTHORING ยง10.4 says to argue for rather than default to. Doing this
97
+ first is what stops the pictures โ€” which are precise, and about the ends only โ€” from
98
+ crowding out the sentence, which is imprecise and about everything in between.
99
+
100
+ ---
101
+
102
+ ## 2. Getting the key poses
103
+
104
+ ### 2.1 L0 โ€” you invent them
105
+
106
+ With no pictures, the key poses are yours, and the honest procedure is short:
107
+
108
+ 1. **Name the extremes.** A movement is a list of positions it visibly passes
109
+ through. Write them as sentences first (*"weight on the back foot, chest turned
110
+ away"* โ†’ *"weight forward, chest square"*), because a sentence is a thing you can
111
+ change cheaply and a set of bone angles is not.
112
+ 2. **Two is the floor and three is usually right.** A move between two extremes needs
113
+ both of them; a *cycle* needs the same pose twice with something different in the
114
+ middle, or it does not read as a cycle.
115
+ 3. **A loop is the A=B case, and its seam is a real defect.** The last key must carry
116
+ the **same value** as the first, not a value near it โ€” AUTHORING ยง0's note on
117
+ `check`'s per-frame column is about exactly this class of defect, and nothing in an
118
+ aggregate can see it. Write the value twice rather than trusting yourself to have
119
+ ended where you began.
120
+ 4. **Then look.** `rigc render` writes a contact sheet of every frame in one image,
121
+ and spacing is a comparison **across** frames, so that grid is the picture to open
122
+ first (AUTHORING ยง0). A pose you invented and never looked at is a guess with a
123
+ number attached.
124
+
125
+ ### 2.2 L1 and L2 โ€” the pictures, through `rigc pose`
126
+
127
+ ```bash
128
+ rigc pose --images parts/ --frame poseA.png --out poseA.json
129
+ rigc pose --images parts/ --frame poseB.png --out poseB.json
130
+ ```
131
+
132
+ One frame per call, by design. The fields are AUTHORING ยง11.3; what follows is how
133
+ to **consume** them, and every item is a property of the instrument rather than
134
+ advice.
135
+
136
+ **โš ๏ธ Read `refusal` before `placement`.** Under a `no-match` refusal the placement is
137
+ **still filled in, on purpose** โ€” a refusal says *do not trust this number*, it does
138
+ not hide it. Code that reads `placement` first and treats a non-null value as an
139
+ answer will silently adopt a refused one. `empty-part` and `larger-than-canvas` leave
140
+ `placement` null because nothing was searched; those two are the only nulls.
141
+
142
+ **๐Ÿ“ The coordinates are frame pixels, y down, origin top-left, and `(x, y)` is where
143
+ the part image's own centre lands** โ€” not a corner, not a pivot. To reach Spine's
144
+ y-up, counter-clockwise world use the two conversions that already exist and
145
+ open-code neither: `screenToSpineDegrees(rotationDeg)` and
146
+ `cropToSpineY(y, frameHeight)`, both in
147
+ [`src/transform.ts`](../src/transform.ts). The `space` field of every report repeats
148
+ the contract in the file, so a consumer never has to remember which way the flip
149
+ goes.
150
+
151
+ โš ๏ธ A **bone offset** in a spec is expressed in its parent's local axes, so the y flip
152
+ applies there too and it applies **once**. Converting a world point and then also
153
+ negating the local offset you derived from it is the commonest way to build a rig
154
+ that is a mirror of the picture in one joint and correct in the others.
155
+
156
+ **๐Ÿ“Š Residuals are a trust signal and they are not comparable โ€” not across parts, not
157
+ across pictures.** The residual is an alpha-weighted mean over **one** part's own
158
+ footprint against **one** frame's pixels, so it answers *how well does this placement
159
+ explain this frame here*. It does not say that a part with 0.03 was placed better
160
+ than a part with 0.06, and it does not say that pose A was read better than pose B.
161
+ โ‡’ Use it to decide **which numbers to lean on** โ€” where two placements of the same
162
+ part in the same frame differ, and whether to look at a part again โ€” and never to
163
+ rank parts or frames.
164
+
165
+ **๐Ÿ”€ A symmetric part comes back as an unordered set, and ordering it across two
166
+ frames is your job.** `alternates` non-empty means the answer was not unique;
167
+ `ambiguous` means at least one alternate is inside the margin. Two identical limbs
168
+ look exactly like that, and so does a shape whose silhouette fits itself at more than
169
+ one angle. The instrument has run out โ€” it sees one frame and has no notion of which
170
+ limb is which. A method that works:
171
+
172
+ 1. **Enumerate the assignments, not the placements.** With k interchangeable
173
+ placements of one part in frame A and k in frame B, there are k! ways to pair them.
174
+ For two, that is two options; do not treat it as a search.
175
+ 2. **Pick the assignment that minimises total movement** โ€” the sum, over the part's
176
+ instances, of the distance its centre travels from A to B, with rotation counted in
177
+ at the part's own radius so the two terms are commensurate. Adjacent poses are
178
+ adjacent, so the pairing that makes the parts travel least is the pairing that does
179
+ not swap them.
180
+ 3. **Then pin it for the whole animation and let nothing reopen it.** Re-deciding per
181
+ frame is what produces a limb that jumps back and forth between two answers, cheap
182
+ in every frame and wrong in the relation between two โ€” AUTHORING ยง8.1 documents that
183
+ failure from the fitting side.
184
+ 4. โš ๏ธ **Ask the user instead when continuity does not separate them.** Two cases: the
185
+ two candidate assignments come out **within a few percent of each other** (a
186
+ near-symmetric pose, or two poses far enough apart that both pairings travel about
187
+ as far), or the assignment **changes the meaning** rather than the geometry โ€” which
188
+ arm is in front, which leg leads. Those are not measurements you are missing, they
189
+ are decisions nobody has made. One question with the two readings named is cheaper
190
+ than a rig that is confidently mirrored.
191
+
192
+ **๐Ÿ•ถ๏ธ A middling residual next to a high `unexplained` usually means *right place,
193
+ seen through something*.** Occlusion is documented rather than solved: a part drawn
194
+ behind another has the occluder's pixels where its own should be, so its residual
195
+ rises **at the correct placement**. `unexplained` is the share of the part's material
196
+ that actually disagrees, and it is what separates the two readings โ€” high with a
197
+ plausible placement is occlusion, high with an implausible placement is a wrong
198
+ answer. โญ **And it is evidence you want:** a part whose `unexplained` goes **up** in
199
+ the frame where another part crosses it is telling you the crossing part is in
200
+ **front**, which is the only place a slot order can come from at this input level
201
+ (AUTHORING R4 โ€” the slots array *is* the draw order). ยง6 derives one that way.
202
+
203
+ **๐Ÿ” Surface the `search` window whenever you narrow it, and read it back before you
204
+ trust a surprise.** A window that does not contain the truth **does not reliably
205
+ refuse**: a part shrunk inside the region it came from still explains those pixels, so
206
+ the report's answer is the best placement available *inside* the window and its
207
+ residual can look perfectly reasonable. The tell is a placement sitting **at a window
208
+ edge**, or a part whose scale disagrees with its neighbours' by more than a few
209
+ percent when the picture cannot have been drawn that way. โ‡’ Run the default window
210
+ once, read `search` and the scales together, narrow, run again, and **say in your log
211
+ what window produced the numbers you kept**. ยง6 shows the before and after on a real
212
+ part.
213
+
214
+ **๐ŸŽจ Branch on `background.kind: "unknown"`.** With no dominant colour on the border
215
+ ring, every pixel counts as material, the silhouette signal is gone and the residual
216
+ is colour agreement alone. It is reported rather than being quietly weaker, so treat
217
+ it as a different input regime: lean harder on the parts whose interiors carry detail,
218
+ expect more `ambiguous` verdicts, and prefer a crop of the picture with a clean border
219
+ if the user can give one. Do not narrow `--max-residual` to make an `unknown` frame
220
+ look tidier; that suppresses the refusals, which are the only thing telling you the
221
+ frame is hard.
222
+
223
+ ### 2.3 What the poses do and do not fix
224
+
225
+ Two placements per part fix a great deal: the setup pose, every part's attachment
226
+ offset, the slot order (via `unexplained`, above), and both end poses of every
227
+ timeline. They do **not** fix the pivots โ€” see ยง3.9, which is where pivots belong,
228
+ because a pivot is not visible in either end pose and only shows up in the movement
229
+ between them.
230
+
231
+ ---
232
+
233
+ ## 3. The in-betweening recipe
234
+
235
+ This is the part with no reference and no possible reference. The pictures are of the
236
+ **ends**; the frames between them are not given anywhere, cannot be measured, and
237
+ would not exist even if the user had more pictures of the same two poses. Everything
238
+ below is therefore authored knowledge, and it is sourced the way AUTHORING ยง10 sourced
239
+ the editor's conventions.
240
+
241
+ ### 3.1 Where these come from, and how each line is marked
242
+
243
+ Two public bodies of material, and nothing else: **the twelve basic principles of
244
+ animation** as publicly catalogued, and **Spine's own documentation**. No sentence
245
+ below is copied from either โ€” the ๐Ÿ“— lines are paraphrases and each carries the page
246
+ it paraphrases.
247
+
248
+ - ๐Ÿ“— **stated** โ€” named and defined on the page linked in the line.
249
+ - ๐Ÿงฉ **inferred** โ€” this guide's reading of that material, applied to a rigc spec.
250
+ The source does not say it, and the numbers in these lines are **defaults to start
251
+ from, not answers**.
252
+
253
+ ๐Ÿšจ **Nothing here is the answer to any request.** Each item is a default to adopt
254
+ *unless the intent says otherwise*, exactly as AUTHORING ยง10 puts it, and the
255
+ adjectives in ยง1 are what overrides them.
256
+
257
+ ### 3.2 ๐Ÿ“— Pose to pose is the normal form, and it is one of two
258
+
259
+ Animation is publicly catalogued as being made either **straight ahead** โ€” drawn
260
+ forward, frame after frame โ€” or **pose to pose**, where the extremes are laid down
261
+ first and the rest is filled in between them โ€”
262
+ [Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation).
263
+
264
+ ๐Ÿงฉ **โ‡’ A keyframed skeleton can only do the second one, so ยง0's normal form is not a
265
+ convention this page picked.** A Spine animation *is* sparse keys plus interpolation;
266
+ there is no channel in the format that means "and then draw the next frame". This is
267
+ why a fitter's output โ€” one pose per frame โ€” is the wrong shape for a motion spec even
268
+ when every pose in it is right: AUTHORING ยง10.3 and PROMPTING clause 4 both land on
269
+ that from the measured side.
270
+
271
+ ### 3.3 ๐Ÿ“— Timing is the number of frames, and ๐Ÿงฉ in rigc it is seconds
272
+
273
+ Timing โ€” how long a movement takes โ€” is catalogued as what gives a movement its weight
274
+ and its meaning; the same two poses with different spacing between them read as
275
+ different actions
276
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
277
+ Spine's own guide states that times are seconds and *frames exist only for
278
+ convenience* โ€” [Keys](http://esotericsoftware.com/spine-keys).
279
+
280
+ ๐Ÿงฉ **โ‡’ Author in seconds and never pin a key to a frame grid.** A key at `t: 0.07` is
281
+ ordinary; a key plan whose times are all multiples of 1/12 has quietly adopted a frame
282
+ rate that nothing in the spec asked for, and will re-time itself the first time
283
+ somebody renders at another rate.
284
+
285
+ ๐Ÿงฉ **โ‡’ Defaults for a single move, when the user named no duration.** A movement a
286
+ figure *does* (a reach, a raise, a step) lands between **0.3 s and 0.8 s**; a movement
287
+ that happens *to* it (a hit, a snap, a recoil) between **0.1 s and 0.3 s**; an idle
288
+ cycle between **1.5 s and 3 s**. Propose the middle of the band the intent picks out,
289
+ write it in `duration`, and say in one line that you proposed it. These are starting
290
+ points chosen so a first candidate is watchable, not measurements of anything.
291
+
292
+ ๐Ÿงฉ **โ‡’ How many interior keys, and where.** Key count is a timing decision, so it lives
293
+ here, and it is decided by **naming what each key is for** rather than by picking a
294
+ number:
295
+
296
+ | Interior keys | When that is the right count |
297
+ | --- | --- |
298
+ | **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 |
299
+ | **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 |
300
+ | **two or three** | the effects stack โ€” anticipate, overshoot, settle โ€” or the intent names a shape the ends cannot carry (*"hesitates"*, *"in two stages"*) |
301
+ | **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 |
302
+
303
+ โญ **The three kinds of key that are forced are AUTHORING ยง10.3's**, and they are the
304
+ whole of what a key plan owes: the series' own ends, every change of direction, and
305
+ **both ends of any run of equal values** โ€” a hold is authored, not omitted, and two equal
306
+ keys are the only way to say *nothing moves here* on an interpolated timeline.
307
+
308
+ ### 3.4 ๐Ÿ“— Slow in and slow out, and ๐Ÿ“— Spine says constant speed reads badly
309
+
310
+ Movements are catalogued as accelerating out of an extreme and decelerating into the
311
+ next, with more drawings near the extremes than in the middle
312
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
313
+ Spine's guide is explicit about the consequence: when all the parts of a skeleton move
314
+ at constant speed *the movement tends to be robotic and lifeless* โ€”
315
+ [Animating](http://esotericsoftware.com/spine-animating). Its curve editor offers
316
+ automatic handles first and named presets after, and its handles are normalised to
317
+ 0..1 on both axes โ€” [Graph](http://esotericsoftware.com/spine-graph).
318
+
319
+ ๐Ÿงฉ **โ‡’ Bezier is the default and linear is the exception you argue for** โ€” AUTHORING
320
+ ยง10.4 states this and ยง4.1's `easings` block is where it lives. For a single authored
321
+ move, **three named shapes carry it**: one that leaves an extreme slowly and gathers
322
+ speed, one that leaves fast and arrives slowly, one symmetric shape for everything
323
+ else. A fourth is worth adding when a part has to *stop dead*; a table of eight for a
324
+ half-second move is a table nobody chose from.
325
+
326
+ ๐Ÿšซ **Do not fit free handles and then substitute the nearest named shape.** AUTHORING
327
+ ยง10.4 measures what that costs on a fitted shot, and the same trap exists here in a
328
+ smaller form: pick the table first, then write every key against the table you will
329
+ actually emit. Nothing in the loop can see the difference โ€” the key count, the curve
330
+ kinds and the duration are all unmoved โ€” and the rendered result changes.
331
+
332
+ ### 3.5 ๐Ÿ“— Arcs, and ๐Ÿงฉ the channel decides whether you get one
333
+
334
+ Natural movement is catalogued as following arced trajectories rather than straight
335
+ lines, because limbs are hinged
336
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
337
+
338
+ ๐Ÿงฉ **โ‡’ In a skeleton the arc is free, and losing it takes effort.** A `rotate` track on
339
+ a parent bone carries every descendant along a circular path about that bone's pivot โ€”
340
+ that *is* an arc, and it costs one timeline. A `translate` track between two positions
341
+ draws the **straight line** between them, and two `translate` tracks with the same
342
+ times draw the straight line in both axes. โ‡’ **The real question is never "arc or
343
+ line", it is "which channel carries this move".** Reach for `translate` only where the
344
+ thing genuinely slides โ€” a lift, a slide, a prop on a rail โ€” and for a hinge use
345
+ `rotate` and take the arc.
346
+
347
+ ๐Ÿงฉ **โ‡’ Where a move must be a straight line through a hinge, it needs an interior
348
+ key.** A hand held level while the shoulder rotates is a straight path built out of two
349
+ arcs, and two keys cannot express it: the mid-point of the arc bulges away from the
350
+ line. One key at the middle of the span, placed on the line, removes most of the bulge;
351
+ two removes the rest. This is the one case where key count is doing geometric work
352
+ rather than shaping timing.
353
+
354
+ ### 3.6 ๐Ÿ“— Anticipation, and ๐Ÿงฉ where it is allowed to live
355
+
356
+ A movement is catalogued as being prepared for by a smaller counter-movement โ€” a
357
+ crouch before a jump, a wind-up before a throw โ€” which readies the audience for what
358
+ is about to happen
359
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
360
+
361
+ ๐Ÿšจ **โ‡’ At L1 and L2 the anticipation goes *after* `t: 0`, never before it, and this is
362
+ not a style point.** The first key pose is a **given condition**: the spec states it at
363
+ `t: 0` by construction. An anticipation authored by moving the first key earlier, or by
364
+ setting `t: 0` to the counter-pose, has overwritten an input with an invention. โ‡’ Keep
365
+ `t: 0` exactly as `pose` read it, and put the counter-pose at a small positive time.
366
+
367
+ ๐Ÿงฉ **โ‡’ Defaults: the counter-move is 5โ€“10 % of the main excursion, and its key sits at
368
+ 10โ€“15 % of the duration.** Below 5 % it does not read; past about 15 % it stops being a
369
+ preparation and becomes a first move of its own, which is a different animation. A
370
+ movement that happens *to* the figure gets **none** โ€” nothing anticipates being hit.
371
+
372
+ ### 3.7 ๐Ÿ“— Follow-through and overlapping action, and ๐Ÿงฉ the offset table
373
+
374
+ Two related catalogued principles: parts of a body continue moving after the body has
375
+ stopped (**follow-through**), and parts do not all start and stop at the same time
376
+ (**overlapping action**) โ€” the second being what stops a figure reading as one rigid
377
+ piece
378
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
379
+
380
+ ๐Ÿงฉ **โ‡’ In a keyed skeleton, overlap is a *timing offset per bone*, and it is the single
381
+ cheapest thing on this page.** Every bone's extreme key is at some fraction of the
382
+ duration; move a trailing bone's extreme later than its parent's and the chain reads as
383
+ connected. Nothing else changes โ€” same poses, same easings, same key count.
384
+
385
+ ๐Ÿงฉ Defaults, as a fraction of the whole movement, for a chain hanging off a driver:
386
+
387
+ | Part, relative to its driver | Extreme lands | Settles |
388
+ | --- | --- | --- |
389
+ | the driver itself (the bone the intent is about) | at its own extreme | at the end |
390
+ | the next link out (forearm, neck, upper prop) | **+8โ€“15 %** later | after the driver |
391
+ | the link after that (hand, head, prop tip) | **+15โ€“25 %** later | last of all |
392
+ | something loose and light (cloth, hair, a pennant) | **+20โ€“35 %** later, and it **overshoots** | last, with one crossing |
393
+ | a planted part (a base, a foot in contact) | โ›” no timeline at all | โ€” |
394
+
395
+ โš ๏ธ **The offsets compound down a chain and they are fractions, not seconds** โ€” a
396
+ 0.15 s snap and a 2 s idle both get the same table. Past about 35 % the trailing part
397
+ is no longer following the driver, it is doing a separate action, and the movement
398
+ reads as two events rather than one.
399
+
400
+ โ›” **A part the pictures show unchanged gets no timeline.** Keys that repeat the setup
401
+ value are exactly what the editor's own Clean Up deletes โ€”
402
+ [Keys](http://esotericsoftware.com/spine-keys) โ€” and a track that holds one value for a
403
+ whole animation is a reader's false lead about what the movement is about.
404
+
405
+ ### 3.8 ๐Ÿ“— Exaggeration, and ๐Ÿงฉ overshoot as its keyed form
406
+
407
+ Exaggeration is catalogued as pushing a movement past its literal reading so the
408
+ intent survives
409
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
410
+
411
+ ๐Ÿงฉ **โ‡’ For a movement that ends fast, the keyed form is an overshoot: pass the final
412
+ value, then come back to it.** Default **8โ€“12 %** of the excursion past the end value,
413
+ with the overshoot key at **55โ€“70 %** of the duration and the final value at the end.
414
+ ๐Ÿšจ The overshoot is an **interior** key โ€” the last key still carries the given end pose
415
+ exactly, for the same reason ยง3.6 keeps `t: 0` intact. A movement that ends slowly gets
416
+ no overshoot; there is nothing to absorb.
417
+
418
+ ### 3.9 ๐Ÿงฉ The pivot โ€” the in-between's own geometry, and the one thing two poses may not fix
419
+
420
+ The two end poses need no pivot: they are stated as placements. **The in-betweens need
421
+ one**, because interpolating a `rotate` track means turning about the bone's position,
422
+ so the pivot decides the entire path between the ends. It is an in-betweening input,
423
+ and this is where it is decided.
424
+
425
+ **Two placements of the same part fix its rotation's fixed point, when the rotation is
426
+ large.** With the part's centre at `cA`, `cB` and its screen rotation at `ฮธA`, `ฮธB`, the
427
+ point that is fixed in both is the solution of
428
+
429
+ ```
430
+ ( R(ฮธA) โˆ’ R(ฮธB) ) ยท d = cB โˆ’ cA d = the pivot, as an offset from the part
431
+ image's centre in the part's own axes
432
+ ```
433
+
434
+ a 2ร—2 solve, where `R(ฮธ)` is the frame's clockwise rotation. Reconstruct the world
435
+ pivot from either pose โ€” they agree by construction โ€” and take it into the parent's
436
+ local space to write it as a bone offset.
437
+
438
+ ๐Ÿšจ **And here is the honesty this needs, in the same shape AUTHORING ยง8.1 states it
439
+ for a fitted joint: the solve is well-conditioned only when the relative rotation
440
+ across the joint actually *changes* between the two poses.** The determinant of that
441
+ 2ร—2 is exactly
442
+
443
+ ```
444
+ |det| = 4 ยท sinยฒ(ฮ”/2) ฮ” = the CHANGE in relative angle across the joint
445
+ ```
446
+
447
+ so a reading error in the placements is amplified into the pivot by about
448
+ **1 / (2 ยท sin(ฮ”/2))**. That factor **attenuates** at ฮ” = 80ยฐ (โ‰ˆ 0.8ร—) and **multiplies
449
+ by five** at ฮ” = 11ยฐ. โš ๏ธ **Nothing reports it.** Both solves return an exact answer, both
450
+ reconstruct to a fixed point that agrees between the two poses to the last decimal, and
451
+ the residuals in the pose report never move โ€” the ill-conditioned one is simply wrong,
452
+ quietly, and every in-between hung off it swings about the wrong centre.
453
+
454
+ โ‡’ **The rule, and it is arithmetic you already have:**
455
+
456
+ - **ฮ” โ‰ฅ 45ยฐ** โ€” solve it. The answer is better than the placements it came from.
457
+ - **20ยฐ โ‰ค ฮ” < 45ยฐ** โ€” solve it, then **check the conditioning** by re-solving from
458
+ placements perturbed by a pixel and seeing how far the pivot moves. If it moves
459
+ further than you would accept as a bone position, treat it as the next case.
460
+ - **ฮ” < 20ยฐ** โ€” โ›” **do not use the solve.** Take the default below and **say in your
461
+ log that the pivot was defaulted and why** โ€” the number, not the word: *"flag hinge
462
+ defaulted; relative rotation changed 10.8ยฐ between the two poses, amplification 5.3ร—"*.
463
+ - **ฮ” = 0** (a part that only translates, or a rigid pair) โ€” the pivot is not a
464
+ quantity the pictures contain at all. Default it.
465
+
466
+ ๐Ÿงฉ **The default, in order of preference.** Each is a reading of something you can
467
+ actually see, which is why they beat an ill-conditioned solve:
468
+
469
+ 1. **The joint the art draws.** Part PNGs cut for rigging usually carry the hinge โ€”
470
+ a collar, a hoist edge, a socket, a darker cap. Take that feature's centre as the
471
+ pivot in the part's own pixels, then use the **placements** to say where it lands.
472
+ Averaged over the poses this is a *measurement of one point*, not a solve, so it
473
+ does not amplify anything.
474
+ 2. **The overlap of the two parts' footprints.** Where the child's `bbox` and the
475
+ parent's intersect, the centroid of the intersection is the visible joint.
476
+ 3. **The parent's far end.** The last resort, and often a few pixels out โ€” which is
477
+ exactly why it gets said out loud.
478
+
479
+ ๐Ÿ“Œ **Then check the default the cheap way: it should agree with itself across the
480
+ poses.** Take your chosen pivot point into the parent's local frame once per pose. A
481
+ real hinge is *fixed* there, so the readings should differ by about your placement
482
+ noise; if they differ by several pixels, the point you picked is not the hinge. ยง6
483
+ runs this check on a real pair and gets 0.23 px.
484
+
485
+ ### 3.10 ๐Ÿ“— Secondary action, and ๐Ÿงฉ what it costs here
486
+
487
+ A supporting movement that reinforces the main one โ€” catalogued as secondary action โ€”
488
+ is what makes a movement specific rather than generic
489
+ ([Twelve basic principles](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation)).
490
+
491
+ ๐Ÿงฉ **โ‡’ It is a whole extra timeline and it is the first thing to leave out of a first
492
+ candidate.** A secondary action is a decision about character, and a ballot that asks a
493
+ person to compare two candidates differing in *both* the primary timing and a secondary
494
+ action has asked two questions and will get one answer (ยง4). Land the primary movement,
495
+ then propose the secondary action as its own spread.
496
+
497
+ ### 3.11 What this section does not claim
498
+
499
+ Deliberately absent, because no public page states them and asserting them would be
500
+ handing you an answer nobody measured:
501
+
502
+ - any figure for keys per second, or for how key density should scale with duration;
503
+ - what any particular studio, project or shipped rig actually used for any of the
504
+ numbers above;
505
+ - that the offsets in ยง3.7 are right for a specific figure, weight or scale โ€” they are
506
+ starting points chosen to be watchable;
507
+ - that a movement built entirely from these defaults is good. They are what a first
508
+ candidate is made of, and ยง4 is what happens next.
509
+
510
+ If one of these turns out to matter for a request, it belongs in that run's own notes
511
+ as something the user had to teach you โ€” not here.
512
+
513
+ ---
514
+
515
+ ## 4. Candidate-spreading axes
516
+
517
+ `rigc vote` takes 2โ€“4 compiled candidates and gives a person one page of looping
518
+ pixels, no paths and no prose (AUTHORING ยง0). What comes back is worth having only if
519
+ the candidates on it **differ in interpretation**.
520
+
521
+ โ›” **The same easing at three strengths is a wasted ballot.** A person asked to choose
522
+ between *a bit of ease*, *more ease* and *a lot of ease* will pick one, the ledger will
523
+ record it, and you will have learned a preference about a knob rather than about the
524
+ movement. โ‡’ Spread on the axes below: each one is a **different reading of the same
525
+ request**, so whichever wins tells you something the next candidate can use.
526
+
527
+ | Axis | Candidate A | Candidate B | Worth a slot when |
528
+ | --- | --- | --- | --- |
529
+ | **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 |
530
+ | **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 |
531
+ | **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* |
532
+ | **Termination** | arrives and stops | overshoots and settles, per ยง3.8 | the movement ends fast |
533
+ | **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 |
534
+ | **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 |
535
+ | **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 |
536
+ | **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 |
537
+
538
+ ๐Ÿ“Œ **One axis per ballot.** Two candidates differing on two axes cannot be read: the
539
+ winner tells you the pair was better, not which half of it was. If two axes both look
540
+ live, that is two ballots, and the first one's answer usually settles the second.
541
+
542
+ ๐Ÿงฉ **Two is the useful width, three is the ceiling.** `vote` accepts four panes, and a
543
+ person watching four loops at once is comparing the two they happened to look at
544
+ together. Reach for three only when the axis genuinely has three readings (a path that
545
+ can go over, under or straight through).
546
+
547
+ ---
548
+
549
+ ## 5. The loop, and what comes back
550
+
551
+ ```bash
552
+ rigc build --rig m.rig.json --motion m.motion.json --images parts --out spine-a
553
+ rigc build --rig m.rig.json --motion b.motion.json --images parts --out spine-b
554
+ rigc render --candidate spine-a # look at it yourself first
555
+ rigc vote --candidate spine-a --candidate spine-b # -> ballot.html
556
+ rigc vote --record vote-<id>.json # -> votes.jsonl
557
+ ```
558
+
559
+ **Compile first, vote last.** A candidate reaches a ballot only because it already
560
+ built green, so the person is never asked to read a spec, a diff or JSON (AUTHORING
561
+ ยง0). And look at your own candidates with `render` before you ask anybody else to:
562
+ green says the file is valid and nothing more, and a head sitting off its torso passes
563
+ every assertion.
564
+
565
+ What the ledger can say, and what each one means for the next step:
566
+
567
+ | Verdict | What it means here |
568
+ | --- | --- |
569
+ | 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 |
570
+ | 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 |
571
+ | **tie**, `indistinguishable` | the axis you spread on does not matter for this request. โ‡’ Stop spending ballots on it and pick either |
572
+ | **tie**, `both-acceptable` | the axis matters and both readings work. Pick one, say which, move on |
573
+ | **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 |
574
+ | **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 |
575
+
576
+ โš ๏ธ **A tie is a recorded answer, not a missing one**, and `both-unacceptable` is only
577
+ reachable because ties are recordable โ€” check for it before treating a ballot as
578
+ settled. Every line carries the winner as a content **digest** rather than a label
579
+ (`B` means nothing outside one ballot) and a `coverage` set, so what is still
580
+ unreviewed is computable.
581
+
582
+ ---
583
+
584
+ ## 6. A worked example, end to end (L1)
585
+
586
+ ๐Ÿšซ **Every value in this section is invented.** The parts, the pictures, the numbers,
587
+ the easing table, the times โ€” a signal post that exists nowhere else in this
588
+ repository, chosen so that the whole recipe runs on something small enough to read.
589
+ Nothing here is an answer to anything.
590
+
591
+ What *is* real: every command line below was run, and every figure printed in an
592
+ output block is what the command actually printed.
593
+
594
+ ### The request
595
+
596
+ > *"Here are the parts and two pictures of the signal arm โ€” hanging down in the
597
+ > first, raised in the second. Make it snap up and settle."*
598
+
599
+ Normalised against ยง1: parts directory **given**; two pose frames, **ordered**;
600
+ duration **absent** โ†’ ยง3.3 says a movement the figure *does*, so propose **0.55 s** and
601
+ say so; loop **absent** and the two pictures are different poses โ†’ **not a loop**;
602
+ intent adjectives **"snap ... settle"** โ†’ the extreme arrives early, the value settles
603
+ late, ยง3.8's overshoot is live; animation name โ†’ **`raise`**.
604
+
605
+ ### 1. The art, and the two pictures
606
+
607
+ ```bash
608
+ mkdir -p semaphore/parts && cd semaphore
609
+ bun -e '
610
+ const files = {
611
+ "parts/post.png": "iVBORw0KGgoAAAANSUhEUgAAAA4AAABgCAYAAAAttkP7AAAAVklEQVR42mPYsOvMf3Iww6jG4aExr2bKf2Ts4BVDFB7VOKpxVCNOjXIqBv/JwUNJ42gCGNU4qnFU42hJPlqSj2oc1TiqcVTjqMbR+nG0fhxNOaMawRgAyYT+Nyka/GsAAAAASUVORK5CYII=",
612
+ "parts/arm.png": "iVBORw0KGgoAAAANSUhEUgAAADwAAAAOCAYAAABzTn/UAAAAP0lEQVR42mPI8FP4Twp+c6ZpUGNC7mcY9fCoh0e4h49Nc8CLSVVPa/2jHh718KiHRz086uFRD496eNTDA+ZhAPte2VL+X1bRAAAAAElFTkSuQmCC",
613
+ "parts/flag.png": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAUCAYAAACeXl35AAAAMElEQVR42mOo09H4j46PeLjQDDOMWjhqIdUtfDZvCkV41MJRC4ehhaMlzaiFI89CANaNM2RJry/OAAAAAElFTkSuQmCC"
614
+ };
615
+ for (const [p, b] of Object.entries(files)) await Bun.write(p, Buffer.from(b, "base64"));
616
+ '
617
+ ```
618
+
619
+ Three plates: a **post** 14ร—96 with a light cap, a lit left edge and three unevenly
620
+ spaced bands; an **arm** 60ร—14 with a lit top edge, a hub at one end, a collar at the
621
+ other and two ties between; a **flag** 28ร—20 with a dark hoist edge down one side and a
622
+ pale blaze across the middle. The interior detail is not decoration โ€” a part that is one
623
+ flat colour is self-similar under scaling, and ยง2.2's window caveat is exactly what that
624
+ produces.
625
+
626
+ The two pictures stand in for what a user would hand over. Both are 160ร—200 on a flat
627
+ `rgb(238, 238, 234)` ground, with the post upright, the arm turned about the top of the
628
+ post, and the flag hanging off the arm's collar โ€” **arm drawn over post, flag over arm**.
629
+ `poseA` has the arm down and to the right and the flag drooping past it; `poseB` has the
630
+ arm raised and the flag close to level. Their bytes are in
631
+ [the appendix](#appendix--the-two-pose-frames) so the section runs end to end.
632
+
633
+ ### 2. Read the pictures โ€” `rigc pose`
634
+
635
+ First call, default windows:
636
+
637
+ ```bash
638
+ rigc pose --images parts --frame poseA.png
639
+ ```
640
+
641
+ ```
642
+ rigc pose
643
+ .. frame โ€ฆ/semaphore/poseA.png (160x200)
644
+ .. ground rgb(238, 238, 234) over 100% of the border ring
645
+ .. parts โ€ฆ/semaphore/parts (3 png)
646
+ .. search scale 0.5โ€“2 in 7 step(s) ยท rotation -180ยฐโ€“180ยฐ step 15ยฐ ยท refuse above residual 0.25
647
+ PLACE arm.png x= 91.9 y= 122.8 rot= 61.9ยฐ scale=0.968 residual=0.0320 unexplained= 4%
648
+ found on a 10x13 anchor grid, step 4 at 4x reduction
649
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.3ยฐ scale=0.955 residual=0.0135 unexplained= 0%
650
+ found on a 20x25 anchor grid, step 4 at 2x reduction
651
+ AMBIG post.png x= 79.0 y= 141.7 rot= -0.4ยฐ scale=0.690 residual=0.0687 unexplained= 25%
652
+ found on a 7x9 anchor grid, step 3 at 8x reduction
653
+ alt 2: x= 78.9 y= 140.7 rot= -0.4ยฐ scale=0.650 residual=0.0692 unexplained= 26%
654
+ alt 3: x= 78.4 y= 135.5 rot= -0.5ยฐ scale=0.500 residual=0.0702 unexplained= 23%
655
+ ```
656
+
657
+ โš ๏ธ **The post came back at scale 0.690 with two alternates trailing it down to 0.500 โ€”
658
+ the window's own floor.** That is ยง2.2's caveat in the open: the post is a long part with
659
+ most of its area in one colour, so a shrunken copy sitting inside the real post explains
660
+ those pixels nearly as well, and three near-equal optima marching toward the edge of the
661
+ window is what that looks like. The arm and the flag agree on โ‰ˆ0.96, which says the
662
+ picture is at the art's own resolution. โ‡’ Narrow, and say so:
663
+
664
+ ```bash
665
+ rigc pose --images parts --frame poseA.png --scale 0.85,1.2 --out poseA.json
666
+ rigc pose --images parts --frame poseB.png --scale 0.85,1.2 --out poseB.json
667
+ ```
668
+
669
+ ```
670
+ .. search scale 0.85โ€“1.2 in 2 step(s) ยท rotation -180ยฐโ€“180ยฐ step 15ยฐ ยท refuse above residual 0.25
671
+ PLACE arm.png x= 91.9 y= 122.8 rot= 61.9ยฐ scale=0.967 residual=0.0320 unexplained= 5%
672
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.3ยฐ scale=0.955 residual=0.0135 unexplained= 0%
673
+ PLACE post.png x= 79.9 y= 148.6 rot= -0.1ยฐ scale=0.975 residual=0.0589 unexplained= 16%
674
+ ```
675
+
676
+ ```
677
+ .. search scale 0.85โ€“1.2 in 2 step(s) ยท rotation -180ยฐโ€“180ยฐ step 15ยฐ ยท refuse above residual 0.25
678
+ PLACE arm.png x= 102.6 y= 94.8 rot= -18.2ยฐ scale=0.973 residual=0.0305 unexplained= 4%
679
+ PLACE flag.png x= 137.4 y= 86.0 rot= -6.6ยฐ scale=0.956 residual=0.0134 unexplained= 1%
680
+ PLACE post.png x= 80.0 y= 148.0 rot= 0.0ยฐ scale=0.995 residual=0.0394 unexplained= 9%
681
+ ```
682
+
683
+ Three things to read out of that pair, none of which is a score:
684
+
685
+ - **Scale.** All six readings sit in 0.955โ€“0.995 โ€” a spread of about 4 %, which is the
686
+ method's own floor rather than six different scales. โ‡’ Take the pictures as being at
687
+ the art's own resolution and author the rig in **part pixels**, so no scaling appears
688
+ in the spec at all. Say that this is what the spread was read as.
689
+ - **Draw order, from `unexplained`.** The post reads **16 %** unexplained in pose A and
690
+ **9 %** in pose B, at placements that barely move โ€” ยง2.2's occlusion signature. The arm
691
+ crosses more of the post in pose A, so the arm is **in front of** the post. The flag
692
+ reads 0 % and 1 %: nothing covers it, so it is **in front of** the arm. โ‡’ Slots in
693
+ the order `post`, `arm`, `flag` (AUTHORING R4 โ€” the slots array *is* the draw order,
694
+ and there is nowhere else in the file to say it).
695
+ - **The post does not move**, so its two readings are two measurements of one number:
696
+ x 79.9/80.0 and y 148.6/148.0. โ‡’ Use the mean, **(79.95, 148.3)**, and treat the 0.6 px
697
+ disagreement as the noise floor for every other number on the page.
698
+
699
+ ### 3. Convert, and derive the rig
700
+
701
+ `cropToSpineY(y, 200) = 200 โˆ’ y` and `screenToSpineDegrees(d) = โˆ’d`, both from
702
+ [`src/transform.ts`](../src/transform.ts) (ยง2.2 โ€” do not open-code either):
703
+
704
+ | | pose A, Spine world | pose B, Spine world |
705
+ | --- | --- | --- |
706
+ | `post` | x 79.9 ยท y 51.4 ยท rot 0.1ยฐ | x 80.0 ยท y 52.0 ยท rot 0.0ยฐ |
707
+ | `arm` | x 91.9 ยท y 77.2 ยท rot โˆ’61.9ยฐ | x 102.6 ยท y 105.2 ยท rot 18.2ยฐ |
708
+ | `flag` | x 104.7 ยท y 43.9 ยท rot โˆ’84.3ยฐ | x 137.4 ยท y 114.0 ยท rot 6.6ยฐ |
709
+
710
+ **The shoulder, by ยง3.9's solve.** The arm's screen rotation changes from 61.9ยฐ to
711
+ โˆ’18.2ยฐ, so **ฮ” = 80.1ยฐ** โ€” well inside the *solve it* band, `|det| = 4ยทsinยฒ(40.05ยฐ) =
712
+ 1.656`, amplification 0.78ร—. Solving the 2ร—2 puts the fixed point at frame **(80.59,
713
+ 102.44)**, reconstructing identically from both poses, and the offset lands at arm-image
714
+ pixel **(6.71, 7.38)** โ€” inside the arm's own hub, which is where a hub is for. In Spine
715
+ world that is (80.59, 97.56); in the post bone's local space, **(0.64, 45.86)**.
716
+
717
+ **The flag hinge, by ยง3.9's default โ€” and this is the interesting one.** The relative
718
+ angle across that joint is 22.4ยฐ in pose A and 11.6ยฐ in pose B, so **ฮ” = 10.8ยฐ**:
719
+ `|det| = 0.0354`, amplification **5.3ร—**, comfortably inside the *do not use the solve*
720
+ band. Run it anyway, to see what it would have cost โ€” it returns arm-local **(25.64,
721
+ 1.32)**, exactly as confidently as the shoulder did. The default instead: the flag's art
722
+ draws its hinge as a dark hoist strip down one edge, whose centre is flag-image
723
+ **(2.5, 10)**; carrying that point through each pose's placement into arm-local gives
724
+ **(24.77, 0.01)** and **(24.54, 0.20)** โ€” ยง3.9's self-agreement check, and the two poses
725
+ agree to **0.23 px**. โ‡’ Take the mean, **(24.66, 0)**, and write in the log that the
726
+ pivot was **defaulted**, with the number: *relative rotation changed 10.8ยฐ,
727
+ amplification 5.3ร—, ill-conditioned solve declined*.
728
+
729
+ โญ Worth pausing on, because it is what ยง3.9 is for: **the two solves are
730
+ indistinguishable from the inside.** Both are exact, both reconstruct to a point that
731
+ agrees between the poses, and no residual anywhere in either pose report moves. The only
732
+ thing separating them is ฮ”, which is arithmetic you can do before you trust either.
733
+
734
+ `semaphore.rig.json` โ€” complete, nothing trimmed:
735
+
736
+ ```json
737
+ {
738
+ "spec": "rigc-rig/1",
739
+ "name": "semaphore",
740
+ "images": "parts",
741
+ "skeleton": { "width": 160, "height": 200 },
742
+ "bones": [
743
+ { "name": "root" },
744
+ { "name": "post", "parent": "root", "x": 79.95, "y": 51.7 },
745
+ { "name": "arm", "parent": "post", "x": 0.64, "y": 45.86 },
746
+ { "name": "flag", "parent": "arm", "x": 24.66, "y": 0 }
747
+ ],
748
+ "slots": [
749
+ { "name": "post", "bone": "post", "attachment": "post" },
750
+ { "name": "arm", "bone": "arm", "attachment": "arm" },
751
+ { "name": "flag", "bone": "flag", "attachment": "flag" }
752
+ ],
753
+ "skins": {
754
+ "default": {
755
+ "post": { "post": { "image": "post.png" } },
756
+ "arm": { "arm": { "image": "arm.png", "x": 23.29 } },
757
+ "flag": { "flag": { "image": "flag.png", "x": 11.5 } }
758
+ }
759
+ }
760
+ }
761
+ ```
762
+
763
+ The two attachment offsets are the last of the arithmetic. A bone sits at its pivot and
764
+ the placement told you where the image's **centre** goes, so the offset is the gap
765
+ between them, in the bone's own axes with y flipped once: the arm's pivot is at image
766
+ (6.71, 7.38) and its centre at (30, 7), giving **x 23.29** (the y term is 0.38, inside
767
+ the 0.6 px noise floor, so it is not written); the flag's hinge is at image (2.5, 10) and
768
+ its centre at (14, 10), giving **x 11.5** exactly. Bone rotations are left off, which
769
+ means *as drawn* โ€” the arm plate is drawn horizontal and the post vertical, so the poses
770
+ are entirely the motion spec's business.
771
+
772
+ ### 4. In-between it
773
+
774
+ Duration 0.55 s, proposed (ยง3.3). Three easings (ยง3.4). The arm is the driver; the flag
775
+ is the next link out and it is light, so ยง3.7's table puts its extreme **+20โ€“35 %** after
776
+ the arm's and gives it an overshoot. *"Snap"* buys an anticipation (ยง3.6) and an
777
+ overshoot (ยง3.8). The post is planted: โ›” **no timeline**.
778
+
779
+ `semaphore.motion.json` โ€” complete, nothing trimmed:
780
+
781
+ ```json
782
+ {
783
+ "spec": "rigc-motion/1",
784
+ "archetype": "semaphore",
785
+ "cut": "semaphore",
786
+ "easings": {
787
+ "gather": [0.42, 0, 0.8, 0.36],
788
+ "charge": [0.1, 0.72, 0.34, 1],
789
+ "settle": [0.28, 0, 0.36, 1]
790
+ },
791
+ "animations": {
792
+ "raise": {
793
+ "duration": 0.55,
794
+ "loop": false,
795
+ "tracks": [
796
+ {
797
+ "bone": "arm",
798
+ "property": "rotate",
799
+ "keys": [
800
+ { "t": 0, "v": [-61.9], "ease": "gather" },
801
+ { "t": 0.07, "v": [-66.4], "ease": "charge" },
802
+ { "t": 0.32, "v": [24.6], "ease": "settle" },
803
+ { "t": 0.55, "v": [18.2] }
804
+ ]
805
+ },
806
+ {
807
+ "bone": "flag",
808
+ "property": "rotate",
809
+ "keys": [
810
+ { "t": 0, "v": [-22.4], "ease": "gather" },
811
+ { "t": 0.09, "v": [-30.1], "ease": "charge" },
812
+ { "t": 0.38, "v": [-3.8], "ease": "settle" },
813
+ { "t": 0.48, "v": [-15.4], "ease": "settle" },
814
+ { "t": 0.55, "v": [-11.6] }
815
+ ]
816
+ }
817
+ ]
818
+ }
819
+ }
820
+ }
821
+ ```
822
+
823
+ Every number in there is one of the two given conditions or one of ยง3's defaults, and
824
+ which is which is worth being able to point at:
825
+
826
+ | Key | Where it came from |
827
+ | --- | --- |
828
+ | arm `t: 0` = โˆ’61.9, flag `t: 0` = โˆ’22.4 | **given** โ€” pose A, converted. Untouched, per ยง3.6 |
829
+ | arm `t: 0.55` = 18.2, flag `t: 0.55` = โˆ’11.6 | **given** โ€” pose B. The flag's is `6.6 โˆ’ 18.2`: both world rotations converted first, then differenced, because a child's track carries a **local** rotation under a rotated parent |
830
+ | arm `t: 0.07` = โˆ’66.4 | ยง3.6 โ€” 4.5ยฐ against an 80ยฐ excursion (5.6 %), at 13 % of the duration |
831
+ | arm `t: 0.32` = 24.6 | ยง3.8 โ€” 6.4ยฐ past the end value (8 %), at 58 % of the duration |
832
+ | flag `t: 0.09`, `t: 0.38` | ยง3.7 โ€” the flag's extreme lands at 69 % against the arm's 58 %, an offset of **+11 %**, and it drags the other way first |
833
+ | flag `t: 0.48` = โˆ’15.4 | ยง3.7's *one crossing* for a loose part: it comes back past its own end value before settling |
834
+ | 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) |
835
+ | the post's absent track | ยง3.7 โ€” a planted part gets no timeline |
836
+
837
+ ### 5. Build, then look
838
+
839
+ ```bash
840
+ rigc build --rig semaphore.rig.json --motion semaphore.motion.json --images parts --out spine
841
+ ```
842
+
843
+ ```
844
+ .. pages=3 regions=3 bones=4 slots=3 animations=1 version=4.3.13 regionAttachments=3 meshAttachments=0 physicsConstraints=0 rig=semaphore profile=spine
845
+ rigc: wrote โ€ฆ/semaphore/spine/skeleton.json
846
+ rigc: wrote โ€ฆ/semaphore/spine/skeleton.atlas
847
+ ```
848
+
849
+ ```bash
850
+ rigc render --candidate spine --fps 24 --max 200
851
+ ```
852
+
853
+ ```
854
+ .. 111x200px at 24 fps, 1 set(s) -> โ€ฆ/semaphore/render
855
+ .. raise 14 frame(s), 0.542s + contact.png -> โ€ฆ/semaphore/render/raise@24fps
856
+ ```
857
+
858
+ Open `render/raise@24fps/contact.png` **before anything else** โ€” fourteen frames as one
859
+ grid, and spacing is a comparison across frames rather than a property of any one of
860
+ them. What to check on it, and it is not a score: frame 0 is pose A, the last frame is
861
+ pose B, the anticipation dips *after* frame 0, and the flag's extreme is visibly later
862
+ than the arm's.
863
+
864
+ ๐Ÿšซ **Do not run `rigc check` against `poseA.png` and `poseB.png`.** Two pictures are not
865
+ a frame set, and more to the point the ends are **given conditions** the spec states by
866
+ construction โ€” measuring how near it got to them measures the pose estimator, not the
867
+ movement. ยง7.
868
+
869
+ ### 6. Spread, and ask
870
+
871
+ One axis (ยง4), and **Part timing** is the one this request leaves genuinely open: does a
872
+ signal flag lag its arm, or is the whole assembly stiff? Candidate B keeps both given end
873
+ poses, keeps the duration, and drops every ยง3 default โ€” one easing, two keys per track,
874
+ no anticipation, no overshoot, no stagger. `semaphore-b.motion.json` is candidate A's file
875
+ with **these two fields replaced** and `spec`, `archetype` and `cut` unchanged:
876
+
877
+ ```json
878
+ "easings": { "drive": [0.2, 0, 0.4, 1] },
879
+ "animations": {
880
+ "raise": {
881
+ "duration": 0.55,
882
+ "loop": false,
883
+ "tracks": [
884
+ { "bone": "arm", "property": "rotate", "keys": [
885
+ { "t": 0, "v": [-61.9], "ease": "drive" },
886
+ { "t": 0.55, "v": [18.2] } ] },
887
+ { "bone": "flag", "property": "rotate", "keys": [
888
+ { "t": 0, "v": [-22.4], "ease": "drive" },
889
+ { "t": 0.55, "v": [-11.6] } ] }
890
+ ]
891
+ }
892
+ }
893
+ ```
894
+
895
+ ```bash
896
+ rigc build --rig semaphore.rig.json --motion semaphore-b.motion.json --images parts --out spine-b
897
+ rigc vote --candidate spine --candidate spine-b
898
+ ```
899
+
900
+ ```
901
+ rigc vote
902
+ .. ballot 15b3f32bbbce77be
903
+ .. animation raise
904
+ .. A sha256:2bc29990faf6โ€ฆ 3 page(s), 0.4 KiB <- โ€ฆ/semaphore/spine/skeleton.json
905
+ .. B sha256:1bec4ce801adโ€ฆ 3 page(s), 0.4 KiB <- โ€ฆ/semaphore/spine-b/skeleton.json
906
+ .. the page shows A/B and nothing else โ€” the paths above are in its manifest, never on the screen
907
+ rigc: wrote โ€ฆ/semaphore/ballot.html (22.5 KiB โ€” open it in a browser)
908
+ rigc: then record the saved vote with rigc vote --record vote-15b3f32bbbce77be.json --ballot โ€ฆ/semaphore/ballot.html
909
+ ```
910
+
911
+ A person opens that page, watches two loops, picks one, and saves the small JSON it hands
912
+ them. Then:
913
+
914
+ ```bash
915
+ rigc vote --record vote-15b3f32bbbce77be.json --ballot ballot.html
916
+ ```
917
+
918
+ ```
919
+ PASS V00_RESULT_IS_A_RIGC_VOTE
920
+ PASS V01_RESULT_NAMES_THIS_BALLOT
921
+ PASS V02_CANDIDATE_DIGESTS_ARE_THE_BALLOTS
922
+ PASS V03_BALLOT_ID_DERIVES_FROM_ITS_CANDIDATES
923
+ PASS V04_CHOICE_IS_ON_THE_BALLOT
924
+ PASS V05_REASON_CODE_FITS_THE_CHOICE
925
+ PASS V06_NOT_ALREADY_RECORDED
926
+ .. winner A = sha256:2bc29990faf6e24c953cabd09f87cdfc2edd3885a1486f3d3fdb4a88a81439c8, reason code preferred
927
+ .. coverage 2 candidate(s): A=sha256:2bc29990faf6โ€ฆ B=sha256:1bec4ce801adโ€ฆ
928
+ rigc: appended line 1 to โ€ฆ/semaphore/votes.jsonl
929
+ ```
930
+
931
+ ๐Ÿšซ **That answer is invented like every other value in this section โ€” nobody looked at
932
+ this ballot.** It is here to show the shape of what comes back: a winner identified by
933
+ **digest** rather than by the label `A`, a reason code from the closed enumeration, and
934
+ a `coverage` set naming what this vote actually compared. The `PASS` lines are the
935
+ ledger checking the answer against the ballot, not anything checking the movement.
936
+
937
+ Had `both-unacceptable` come back instead, ยง5's table says what to do: not a nudge of
938
+ either candidate, but a new spread on a **different** axis โ€” **Termination**, say, or
939
+ **Segmentation** โ€” because a rejection of both readings is a statement about the axis.
940
+
941
+ ---
942
+
943
+ ## 7. Non-goals โ€” stated, so nobody proposes them as gaps
944
+
945
+ ๐Ÿšซ **No `rigc tween`, and no command that generates in-betweens.** Every other command
946
+ in this toolchain either compiles what you wrote, measures it, or shows it. ยง3 is a page
947
+ of authored judgement โ€” timing offsets, arcs, anticipation, the pivot defaults โ€” and a
948
+ command that applied it would be making those decisions on the user's behalf with no
949
+ place to say it had. **Authoring stays with the agent.** What the toolchain owes you is
950
+ that the ends are stateable by construction (`pose`), that the file is checkable
951
+ (`build`), that you can look (`render`, `preview`), and that a person can choose
952
+ (`vote`).
953
+
954
+ ๐Ÿšซ **No scoring of end-pose reach, and nothing here to add one to.** At L1 and L2 the
955
+ end poses are given conditions; a number saying how near the animation got to them is a
956
+ number about the pose estimator. This is why ยง6 does not run `check` on the two pictures
957
+ and why no threshold, tolerance or pass bar appears anywhere in this document. The
958
+ residuals in a `pose` report are trust signals about *placements*, and AUTHORING ยง11.1
959
+ says the same from the instrument's side.
960
+
961
+ โš ๏ธ **Deform in-betweens are an advanced axis, and the base recipe is rigid-first.**
962
+ Squash and stretch is expressible โ€” a `deform` timeline moves an attachment's vertices
963
+ over time (AUTHORING ยง4.11) โ€” and it is a real axis in ยง4's table. It is last in that
964
+ table on purpose: it needs a mesh rather than a region attachment, it multiplies the
965
+ things a candidate differs by, and a movement that does not read when rigid will not be
966
+ rescued by deforming it. โ‡’ Land the rigid movement, choose between rigid candidates,
967
+ then propose deform as its own spread.
968
+
969
+ ๐Ÿšซ **No frame rate anywhere in either spec file.** `render --fps` is a sampling rate for
970
+ looking; times in a motion spec are seconds (ยง3.3).
971
+
972
+ ๐Ÿšซ **No key per frame.** A fitted pose per frame is a pixel transcription wearing a
973
+ skeleton โ€” AUTHORING ยง10.3 and PROMPTING clause 4 both price it. Keys are structure.
974
+
975
+ ---
976
+
977
+ ## Appendix โ€” the two pose frames
978
+
979
+ The bytes of `poseA.png` and `poseB.png`, so ยง6 runs end to end. Both are 160ร—200 on a
980
+ flat ground; both are invented.
981
+
982
+ ```bash
983
+ bun -e '
984
+ const frames = {
985
+ "poseA.png": "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",
986
+ "poseB.png": "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"
987
+ };
988
+ for (const [p, b] of Object.entries(frames)) await Bun.write(p, Buffer.from(b, "base64"));
989
+ '
990
+ ```