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/README.md +57 -2
- package/cli.ts +116 -0
- package/docs/AUTHORING.md +136 -1
- package/docs/MOTION.md +990 -0
- package/package.json +3 -1
- package/src/pose.ts +1386 -0
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": "iVBORw0KGgoAAAANSUhEUgAAAKAAAADICAYAAABvaOoaAAAJdklEQVR42u3c+1dP6R7A8fk/zprLMmbGuAyOSyQ0DE1yaSKJiOiiQS4hSRdEiqTojEvu3a8qRWWU0oWYTsh9yCXLObPmX/ic9TQr2vPdm/3dX2dazPuH90/W9sNnvdaz7ed5fD/6/ff/CFFf9RFDIAASAIkASAAkAiABkAiABEAiABIAiQBIACQCIAGQCIAEQCIAEgCJAEgAJAIgAZAIgARAIgASAIkASAAkAiABkAiABEACIIMgABIAiQBIACQCIAGQCIAEQCIAEgCJAEgAJAIgAZAIgARAIgASAIkASAAkAiABkAiABEAiABIAiQBIACQCIAGQCIAEQAIgEQAJgEQAJAASAZAASARAAiARAAmARAAkABIBkABIBEACIBEACYBEACQAEgGQAEgEQAIgEQAJgB926fvi5UJNBbMA4F/b0UMpstJ3tITM/Ew2rZjNTAD415R9+ois8Xfthte7kqJs5gPA/19ninMkPMjdBl5P6wPdmRMA333VVeUSudLbEF7vsk4dYmYAfDc1NtZJTLi/KXg9rfGfyOwA6FiPHz+Q+KhQu+D17ujBvcwRgI7V83VrpZW+I5khAB3fYrEKUJWesp05AtCxAuc6WwYYOnuQdHY+ZI4AtF7Mtl0OrYKpSZuZIwCtV1LVKkHzJ9kNL8z7CylMmS0vW+Lkdkc7swSgdYDxiWl24Tu9Y5o8bYiS31q3dleWk8wsAWgdoCpk0fS3wtv74xCp3uMiHYV+r/D1dK21iXkC0DrA3akZhvB2BQ+Ss7ucpTHd9VVd9Rs1ACtyE5knAK0DVIUGaI/iti8dKCXxYzXwerqZN89mFbzccIGZAtC+VkXtf9WikMhueDH+AyQ/zkkXXu+e1a7TAKzK38lMAWgdoMrbY8xb4fXUnj3HZhX8uaaMuQLQOsBxk38wDVDVeWG1BmBtEacjAHQAoOs0P/GZOd40wNYTs2xWwfNn85gtAK0DdJkyVyp3T3grvkPhw2WVVz+pPblEA7CpLI7ZAtA6QNWsaRMN4R2LGCHrfPq/+lreGjzCZhU8W3IKgACzDlCVFad9FWdGjZKIBV/q7hWeO6zdnG47xyoIQAcBuk/9A15e7GiJ9v/K+G6gVz/JjHWxWQXLCg4DkKwDVAV7DX7j8dz+1UOlNvWPfy/eLw/UALx3MU5evHgKQLIO0GnsBP1z4RVDpDrZRfOKbjk8Vf57JVaDsDTvAADJOkDVTNfXr9/EkEFSkehs+IFy94z2i/h5U5zcv38bgGQd4Ohxk2T7soFSsmPsW7dmmn6aJC+borT/FsxNASBZB6iKXDbO9Ob07aKFNh8kbW1XAUjWAY7/1t2uI7quhgjtvmDubgCSdYCqMD/zq+CtfF+bVbC5qRaApO2T/sNM99nnX9u1Cj6rW68BeC5vJwDJOkCVj9tA0wBv5My1WQXrLlYCkKwD/KTfQLtWwV+rtde1LhRuByA5ALD/MPGY+PZXsdqkVpvVu1ePs1kFqysLAUj2f4T05DJljlw6MFkXnjqWU8dzvU9N6jOXagA2lGwDIFkHqMo5sl33fqC6mPDnY7v45aNsVsGK0kwAknWAXV1PpDrV/fX9wLn9DS8sRMz/UtqLtRcVWiviAEjWAapnMzN2S8T8LwzhKZQKZ/fV/WMeNqtgedFRAALQOsDu5+ePsYGnrumr1/GfX9EPzgZpAHbUxAIQgI4BPJGh/V2ZA6uHSl2q/v8nuXLETX67Gqe9rpX/EwABaB2gau2SSbr3A/W6VxqgAdjZECePHt0HIACtAywtPGl6Y7r54GR52RytXQVzUwEIQOsAVfkp/qYR3in2t/kguXXzFwAC0DpAdbphzxHdi8uR2i/i3D0ABKB1gKqcfUHmr2sVLLBZBa9eaQAgAK0DrK89b9cq+PzShtc/anRisSTGhAAQgNYBqrLSwsxf18r1kcacIElY8XovsabqLAABaB2geo2awad++FL9AGYPPH+3j2We6z9ksedEOZGWIvujN0tiaLC0NNYDEIDmAXavgukbX0GrSR4vebFOcnjtP2Vv4DcS5TVAljl/Kn5DPpZlgz+VsEH9ZcvgARJtUP7JYwAEoHmAavWK8pwhm5y+kqihxrDMlrFnFwABaB5gysZwh9H1bl/EegAC0DzAI0kJ7xRgQmAAAAFoHmDe8QxL0LZ9M1CShg+R1JHD5KDTCDnhPFpyxo+VowvnAxCA5gHWVJZrYMUO+Vp2DhssySOGSvqo4ZIxZqScHuckBROcpezb8VL1natccpssDd9/p1uZtxcAAWgeYNv1K1Ls6iKVkyfKz1MnGcIyW62HGwABaB6guqpvBVrjDHdpcJ+i+2cPH9wBIP8tU9ub/s7zntMNof17eYB0bAiTezEb5WFCjHTuS5Bnh1Kk6+h+afH21H2m5XIdAAFoHmDxgnmGAB8lbevGpte1Rb66z5wvLgAgAM0DzFkeaAjw/tZIQ4BtwUt0nyk+chCAADQPMGvjekOAtzetNQR4c/Vy3Wdydu0AIB8h5s+CcxLiDQEqZEYAFU69Z7Le09MQAPYRwKJD6YYA1WvWCKB6PeuugKHBAASgeYDnCvMMAaoPDSOAvyZt1X2myM8XgAA0D7C5vtYQoNpqMQL4ZH+S/lew53QAAtA8wHt3bxlvOE//3hBgV0aa4XMABKBpgKo696mGmJ4d3GuIsMlgE/tG2zUAAtA8wDLvHwwBPk7ZaQjwqu8c3WfqqioBCEDzAPOXLDIE+HBntCHA6wELdZ8pzzwJQACaB5gVtsIQ4N3oDYYA23/UP0XJT0sBIADtABgTZQiwY/0qQ4Ad4St1n8mOiwYgAM0DzNuXbAiwfUWgIcC7Wzbon4asWQVAAJoHWHrquCHAX5Yu1MX39F/JcmfzOv1X8NLFAASgeYAXz5UbAmz2miE3wkKkLWixtC70kZY5s+Syh9sbL6yWzp0NQACaB6iu5jt6Hf99v5oPwD4E+Px55zsF+D5ezQdgHwJUVc3ysAtYzQx3OePrLblBAZIVvkay47dKQXqaVORlS8PFGlZAANoHsHiBTzesevcpUjHbUwr8/SR7ZahkbYmU3D2JcuZ4hlwoPyPXrjTKk86HH9xsAdjHAK9fbZbbHe1/29kCsI8B/t0DIAABCEAAEgABCEAAEgABCEAAEgABCEAAEgAB+CH+OBEBEIAABCAAGQIAAchHCAAJgAAEIAAJgAAEIAAJgAAEIAAJgAAEIAABCEAAEgCJAEgAJHoH/Q+vLpEQQGI1ugAAAABJRU5ErkJggg==",
|
|
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
|
+
```
|