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