spine-rigc 0.2.1
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/LICENSE +21 -0
- package/NOTICE.md +76 -0
- package/README.md +558 -0
- package/cli.ts +739 -0
- package/docs/AUTHORING.md +1303 -0
- package/docs/SPEC_COVERAGE.md +1109 -0
- package/package.json +65 -0
- package/src/check.ts +1714 -0
- package/src/compile.ts +1861 -0
- package/src/diff.ts +847 -0
- package/src/errors.ts +22 -0
- package/src/framing.ts +539 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +433 -0
- package/src/png.ts +50 -0
- package/src/render.ts +974 -0
- package/src/rig.ts +731 -0
- package/src/slots.ts +603 -0
- package/src/timelines.ts +253 -0
- package/src/transform.ts +130 -0
- package/src/types.ts +586 -0
- package/src/validate.ts +1586 -0
- package/tools/font5x7.ts +101 -0
- package/tools/plate.ts +286 -0
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
# Authoring a rig with rigc
|
|
2
|
+
|
|
3
|
+
**Read this before you write a spec.** It is written for an agent that has never
|
|
4
|
+
seen this repository and cannot see what it is authoring. Together with the
|
|
5
|
+
validator's messages it *is* the interface: there is no editor viewport here, so
|
|
6
|
+
the only way to know a rig is right is to compile it and read what comes back.
|
|
7
|
+
|
|
8
|
+
Two input files, one CLI loop, and a list of named failures. Everything below is
|
|
9
|
+
checked against the code that implements it.
|
|
10
|
+
|
|
11
|
+
๐จ **The gate cannot see a wrong animation, and it will not tell you so.** `build`
|
|
12
|
+
is green when the file is *valid* โ parseable, steppable, nothing degenerate in it.
|
|
13
|
+
Whether the animation is the one you were asked for is a question it does not ask
|
|
14
|
+
and has no way to answer. This is not a caveat: rung 1's first honest run shipped a
|
|
15
|
+
build with **every easing in the file reversed** and the gate passed it green. If you were given pictures, `check` (**ยง9**) is the half of the loop
|
|
16
|
+
that can see that, and a run that skips it has verified nothing about the motion.
|
|
17
|
+
|
|
18
|
+
- Formats and CLI reference: [README.md](../README.md)
|
|
19
|
+
- The rig spec's own source-level documentation: [`src/rig.ts`](../src/rig.ts)
|
|
20
|
+
- The motion spec and emitted shapes: [`src/types.ts`](../src/types.ts)
|
|
21
|
+
- What the format holds and rigc covers: [SPEC_COVERAGE.md](SPEC_COVERAGE.md)
|
|
22
|
+
- Reproducing a shot you were given as pictures: **ยง8**, and read it *before* you
|
|
23
|
+
start measuring rather than after; then **ยง9** for the loop that closes it
|
|
24
|
+
- The conventions an editor user follows without being told โ one image per
|
|
25
|
+
attachment, keying practice, curve kind, draw order โ sourced from Spine's own
|
|
26
|
+
public documentation: **ยง10**
|
|
27
|
+
|
|
28
|
+
## The vocabulary is Spine's
|
|
29
|
+
|
|
30
|
+
Wherever rigc has no better abstraction it uses **Spine 4.3's own concept, its own
|
|
31
|
+
field name and its own default**, so that what you know about the Spine User Guide
|
|
32
|
+
transfers: *bones*, *slots*, *draw order*, *skins*, *setup pose*, *region
|
|
33
|
+
attachments*, *meshes*, *animations* and *timelines*, and the constraint families
|
|
34
|
+
(*IK*, *transform*, *path*, *physics*). The emitted file is
|
|
35
|
+
[Spine's JSON format](http://esotericsoftware.com/spine-json-format) โ nothing else.
|
|
36
|
+
|
|
37
|
+
rigc's own additions sit on top and are few: `from` on a bone, `image` on an
|
|
38
|
+
attachment, `generator` on a mesh, and an `invariants` block. They are named so you
|
|
39
|
+
can see where Spine stops.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 0. The loop
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bun install # once
|
|
47
|
+
|
|
48
|
+
bun cli.ts build \
|
|
49
|
+
--rig path/to/my.rig.json \
|
|
50
|
+
--motion path/to/my.motion.json \
|
|
51
|
+
--images path/to/images \
|
|
52
|
+
--out path/to/spine \
|
|
53
|
+
--profile spine
|
|
54
|
+
|
|
55
|
+
# read the report โ fix the spec โ run it again
|
|
56
|
+
|
|
57
|
+
bun cli.ts check \
|
|
58
|
+
--candidate path/to/spine \
|
|
59
|
+
--frames path/to/reference/frames
|
|
60
|
+
|
|
61
|
+
# read the table โ fix the spec โ build again โ check again
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`build` compiles, round-trips the result through `@esotericsoftware/spine-core`,
|
|
65
|
+
runs the named assertions, and **writes only if every one of them is green.** A red
|
|
66
|
+
run leaves nothing on disk, so there is no half-written artifact to mistake for a
|
|
67
|
+
result. There is no `--no-validate`, and there will not be one.
|
|
68
|
+
|
|
69
|
+
`check` is the second half, and it is only skippable if nobody gave you pictures.
|
|
70
|
+
Green from `build` means the file is valid; it says nothing at all about whether
|
|
71
|
+
the animation is the one in the frames, and there is no assertion that could โ see
|
|
72
|
+
ยง9. The two run in that order because `check` needs artifacts on disk and `build`
|
|
73
|
+
only writes them when the gate is green.
|
|
74
|
+
|
|
75
|
+
What the flags mean:
|
|
76
|
+
|
|
77
|
+
| Flag | Meaning |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `--rig` | the rig spec โ skeleton structure |
|
|
80
|
+
| `--motion` | the motion spec โ time |
|
|
81
|
+
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths are written relative to it |
|
|
82
|
+
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory) |
|
|
83
|
+
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
84
|
+
| `--profile` | `spine` = the 18 validity rules ยท `spine-html` = all 32 (**the default**) |
|
|
85
|
+
|
|
86
|
+
Pick the profile deliberately. `spine-html` adds one renderer's policy and one
|
|
87
|
+
project's canvas budget, and those rules fire on perfectly correct Spine data
|
|
88
|
+
(clipping attachments, unweighted meshes, packed atlases). If what you are
|
|
89
|
+
authoring is "valid Spine 4.3 that any runtime plays correctly", use
|
|
90
|
+
`--profile spine`. A report always prints which profile ran and lists what that
|
|
91
|
+
profile left out, on `PROF` lines.
|
|
92
|
+
|
|
93
|
+
The other commands:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bun cli.ts explain --rig โฆ --motion โฆ --out โฆ # the compiled rig as a table
|
|
97
|
+
bun cli.ts validate path/to/spine # re-gate artifacts already on disk
|
|
98
|
+
bun cli.ts diff candidate.json reference.json
|
|
99
|
+
bun cli.ts check --candidate path/to/spine --frames path/to/frames
|
|
100
|
+
bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **`explain`** is the one to reach for when a rig compiles but looks wrong. It
|
|
104
|
+
prints the stage, every bone with its resolved parent and position, the slots in
|
|
105
|
+
draw order with their setup attachment, and every animation's timelines key by
|
|
106
|
+
key with the curve kind. It does not write anything.
|
|
107
|
+
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
108
|
+
sections (bones, slots, attachments, constraints, animations, events). It
|
|
109
|
+
deliberately does not combine them into a score: a rig with the right skeleton
|
|
110
|
+
and the wrong timing and a rig with the right timing and the wrong skeleton call
|
|
111
|
+
for opposite fixes. A measure with nothing to compare says `0/0` and says so.
|
|
112
|
+
- **`check`** renders your candidate into the reference frames' own pixel grid and
|
|
113
|
+
compares pixels โ the only thing here that can see a wrong animation. **ยง9.**
|
|
114
|
+
- **`bench <rung>`** runs one rung of [the benchmark ladder](LADDER.md): validate
|
|
115
|
+
under `--profile spine`, then diff against that rung's reference export, and with
|
|
116
|
+
`--frames` the `check` table as well. Unlike the three above it is a **finish
|
|
117
|
+
line, not a loop**: it opens the reference export, so a run that consults it and
|
|
118
|
+
then edits is no longer an authoring run. `check` carries no such restriction โ
|
|
119
|
+
see ยง9.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 1. The two files
|
|
124
|
+
|
|
125
|
+
### 1.1 A complete minimal rig spec
|
|
126
|
+
|
|
127
|
+
Every field below is required for this to compile; nothing has been trimmed for
|
|
128
|
+
brevity. `images/box.png` is a real PNG beside the spec.
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"spec": "rigc-rig/1",
|
|
133
|
+
"name": "hello",
|
|
134
|
+
"images": "images",
|
|
135
|
+
"skeleton": { "width": 400, "height": 300 },
|
|
136
|
+
"bones": [
|
|
137
|
+
{ "name": "root" },
|
|
138
|
+
{ "name": "box", "parent": "root", "x": 0, "y": 120 }
|
|
139
|
+
],
|
|
140
|
+
"slots": [
|
|
141
|
+
{ "name": "box", "bone": "box", "attachment": "box" }
|
|
142
|
+
],
|
|
143
|
+
"skins": {
|
|
144
|
+
"default": {
|
|
145
|
+
"box": { "box": { "image": "box.png" } }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
That compiles to a two-bone skeleton with one slot showing a region attachment
|
|
152
|
+
whose `width`/`height` were **measured from the PNG**, plus a one-page atlas.
|
|
153
|
+
|
|
154
|
+
### 1.2 A complete minimal motion spec
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"spec": "rigc-motion/1",
|
|
159
|
+
"archetype": "hello",
|
|
160
|
+
"cut": "hello",
|
|
161
|
+
"easings": { "smooth": [0.25, 0, 0.75, 1] },
|
|
162
|
+
"animations": {
|
|
163
|
+
"bob": {
|
|
164
|
+
"duration": 1,
|
|
165
|
+
"loop": true,
|
|
166
|
+
"tracks": [
|
|
167
|
+
{
|
|
168
|
+
"bone": "box",
|
|
169
|
+
"property": "translate",
|
|
170
|
+
"keys": [
|
|
171
|
+
{ "t": 0, "v": [0, 0], "ease": "smooth" },
|
|
172
|
+
{ "t": 0.5, "v": [0, 40], "ease": "smooth" },
|
|
173
|
+
{ "t": 1, "v": [0, 0] }
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`archetype` must equal the rig spec's `name` โ a motion spec was authored against
|
|
183
|
+
one skeleton, and pairing it with another aims its keys at bones whose names happen
|
|
184
|
+
to match and whose meaning does not.
|
|
185
|
+
|
|
186
|
+
A motion spec with `"animations": {}` is legal and emits a skeleton with no
|
|
187
|
+
animations at all โ a **static rig**, a skeleton that exists to be posed. That is
|
|
188
|
+
a real deliverable and not a stepping stone: the ladder's first rung ships one.
|
|
189
|
+
`A09_ANIMATION_DURATION_MATCHES_SPEC` then reports **SKIP**, because there is no
|
|
190
|
+
duration on either side to compare; it is not a pass, and the report says so.
|
|
191
|
+
|
|
192
|
+
An animation that declares `"tracks": []` is a different thing โ a *named* empty
|
|
193
|
+
animation, `duration: 0`, which is what the editor writes for a placeholder. That
|
|
194
|
+
one A09 does compare.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 2. The rules that decide what lands in the file
|
|
199
|
+
|
|
200
|
+
**R1 โ A field is emitted exactly when you declare it.** Not "when it differs from
|
|
201
|
+
the default". Spine's own exporter omits anything equal to a default; rigc cannot,
|
|
202
|
+
because a rig may need to say `x: 0` out loud and because deciding emission from
|
|
203
|
+
the *value* would make the file depend on arithmetic rather than on what you wrote.
|
|
204
|
+
Omit a field and Spine's default stands; write it and it is in the file.
|
|
205
|
+
|
|
206
|
+
**R2 โ The compiler never invents a value.** No defaults guessed from the art, no
|
|
207
|
+
re-measured plates, no reasonable fallbacks. A missing number is a `CompileError`
|
|
208
|
+
naming the field.
|
|
209
|
+
|
|
210
|
+
**R3 โ One fact, one author.** A setup pose comes from the rig slot's `attachment`
|
|
211
|
+
**or** from the motion spec's `setup` block, never both. A slot's attachments come
|
|
212
|
+
from a manifest part **or** from a rig skin, never both. A constraint is declared in
|
|
213
|
+
the rig **or** in the motion spec's `physics` table, never both. Each of those is a
|
|
214
|
+
compile error rather than a silent precedence rule.
|
|
215
|
+
|
|
216
|
+
**R4 โ The slots array *is* the setup draw order.** There is no separate
|
|
217
|
+
draw-order field in the skeleton's structure. Index 0 is drawn first (furthest
|
|
218
|
+
back). One animation *can* reorder them over time โ that is the `drawOrder`
|
|
219
|
+
timeline of ยง4.7, and its offsets are counted against this array.
|
|
220
|
+
|
|
221
|
+
**R5 โ `image` means "measure this PNG".** `width`/`height` have **no parser
|
|
222
|
+
default** in Spine: omit them in raw JSON and they load as `NaN`, every UV
|
|
223
|
+
collapses, and nothing reports an error. Name an `image` instead and rigc reads the
|
|
224
|
+
PNG header, fills both in, and emits that same file as the atlas page โ so the size
|
|
225
|
+
in the skeleton and the size in the atlas cannot drift apart. The **region name is the
|
|
226
|
+
PNG's basename**; when your placeholder name differs from it, rigc writes a `path`
|
|
227
|
+
so the attachment still joins to the region.
|
|
228
|
+
|
|
229
|
+
**R6 โ A key carries `ease` or `curve`, never both.** A named easing says "this
|
|
230
|
+
shape, wherever it is used" and is the recommended path. `curve` is the escape
|
|
231
|
+
hatch: the absolute `(time, value)` control points, verbatim, for when every key
|
|
232
|
+
needs a different shape.
|
|
233
|
+
|
|
234
|
+
**R7 โ `duration` is declared, and checked, twice over.** Skeleton JSON carries no
|
|
235
|
+
duration field โ the loader takes the largest key time. So you state the duration
|
|
236
|
+
you meant and rigc compares it against the compiled result; a mismatch larger than
|
|
237
|
+
one frame (1/60 s) is a compile error, and assertion `A09` re-checks it against the
|
|
238
|
+
*loaded* skeleton afterwards.
|
|
239
|
+
|
|
240
|
+
That frame of slack is for a duration declared *longer* than the motion โ an
|
|
241
|
+
animation may hold its final pose. In the other direction there is no slack to give:
|
|
242
|
+
**no key may land past the declared duration**, and this is checked per timeline
|
|
243
|
+
rather than per animation, within 1e-6 s. Both halves matter, and the second is not
|
|
244
|
+
the first with a smaller number โ see ยง4.5.
|
|
245
|
+
|
|
246
|
+
**R8 โ `from` needs a cut manifest.** `from.anchor` / `from.slotWindow` /
|
|
247
|
+
`from.meshCenter` / `from.rotation` read measured art out of a manifest. Without
|
|
248
|
+
`--manifest` they are a compile error naming the bone. A rig with no measured art
|
|
249
|
+
behind it writes literal `x`/`y` instead.
|
|
250
|
+
|
|
251
|
+
**R9 โ Nothing is written until every assertion is green.**
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 3. The rig spec, field by field
|
|
256
|
+
|
|
257
|
+
`spec` must be exactly `"rigc-rig/1"`. `name` must be a non-empty string. `bones`
|
|
258
|
+
must be non-empty. `slots` must be present (it may be empty).
|
|
259
|
+
|
|
260
|
+
### 3.1 `skeleton` โ the header
|
|
261
|
+
|
|
262
|
+
| Field | Spine meaning | Default |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| `x`, `y` | setup-pose bounding box origin | `0` |
|
|
265
|
+
| `width`, `height` | setup-pose bounding box size | falls back to the manifest's crop; **with neither, the compile fails** |
|
|
266
|
+
| `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
|
|
267
|
+
| `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
|
|
268
|
+
| `images` | nonessential path hint the editor writes | carried through verbatim |
|
|
269
|
+
|
|
270
|
+
`spine` and `hash` are not yours to write: rigc emits its own version label
|
|
271
|
+
(`A16` re-checks it is on the 4.3 line) and inventing a hash would claim an export
|
|
272
|
+
this file did not come from.
|
|
273
|
+
|
|
274
|
+
`width`/`height` are what `A14` and `A19` measure against, so a guessed stage is a
|
|
275
|
+
gate measuring against a number nobody wrote down.
|
|
276
|
+
|
|
277
|
+
### 3.2 `bones` โ Spine's bone list
|
|
278
|
+
|
|
279
|
+
`parent` is resolved **by name against bones already declared**, exactly as the
|
|
280
|
+
parser does. A forward reference is not a rigc restriction: in the loaded skeleton
|
|
281
|
+
it would simply be a second root.
|
|
282
|
+
|
|
283
|
+
| Field | Spine meaning | Default |
|
|
284
|
+
| --- | --- | --- |
|
|
285
|
+
| `name` | required, unique โ the join key for slots, meshes and timelines | โ |
|
|
286
|
+
| `parent` | omitted only by the root bone | none |
|
|
287
|
+
| `length` | bone length; cosmetic in a renderer, part of a faithful reproduction | `0` |
|
|
288
|
+
| `x`, `y` | position **local to the parent** | `0` |
|
|
289
|
+
| `rotation` | degrees, counter-clockwise, y **up** | `0` |
|
|
290
|
+
| `scaleX`, `scaleY` | | `1` |
|
|
291
|
+
| `shearX`, `shearY` | | `0` |
|
|
292
|
+
| `inherit` | `normal` ยท `onlyTranslation` ยท `noRotationOrReflection` ยท `noScale` ยท `noScaleOrReflection` | `normal` |
|
|
293
|
+
| `skin` | `BoneData.skinRequired` | `false` |
|
|
294
|
+
| `color` | `rrggbbaa`, editor affordance | โ |
|
|
295
|
+
| `icon` | the editor's icon for this bone, e.g. `arrowsB`; editor affordance, no rendering effect. Copied through verbatim โ no assertion checks the name, because the icon vocabulary is the editor's and an unknown one is not an error | โ |
|
|
296
|
+
| `from` | **rigc extension** โ take `x`/`y` (and optionally `rotation`) from a cut manifest | โ |
|
|
297
|
+
|
|
298
|
+
โ ๏ธ Spine 4.0/4.1 called `inherit` **`transform`**. That old key still *loads* in 4.3
|
|
299
|
+
and the inheritance silently falls back to Normal โ assertion `A02` refuses it.
|
|
300
|
+
|
|
301
|
+
### 3.3 `slots` โ Spine's slot list, in draw order
|
|
302
|
+
|
|
303
|
+
| Field | Spine meaning | Default |
|
|
304
|
+
| --- | --- | --- |
|
|
305
|
+
| `name` | required, unique | โ |
|
|
306
|
+
| `bone` | required; must be a bone this rig declares | โ |
|
|
307
|
+
| `attachment` | the **setup pose** attachment name, or `null` for "show nothing" | must come from here or from `motion.setup` (R3) |
|
|
308
|
+
| `color` | `rrggbbaa` tint | opaque white |
|
|
309
|
+
| `dark` | two-colour tint, `rrggbb` | โ (๐ซ `A12` under `spine-html`) |
|
|
310
|
+
| `blend` | `normal` ยท `additive` ยท `multiply` ยท `screen` | `normal` |
|
|
311
|
+
|
|
312
|
+
โ ๏ธ **A slot with no attachments is not emitted.** If nothing fills it โ no skin
|
|
313
|
+
entry, no manifest part โ it is dropped from the skeleton without an error, and the
|
|
314
|
+
emitted slots array is a *subsequence* of the rig's. That is deliberate: the rig's
|
|
315
|
+
slot list is the canonical table and declaring a slot no cut fills is legitimate,
|
|
316
|
+
because it fixes where that slot will sit when one does. It also means a typo in a
|
|
317
|
+
skin's slot key can cost you a slot quietly, so check `explain`'s slot table.
|
|
318
|
+
|
|
319
|
+
### 3.4 `skins` โ placeholder โ attachment maps
|
|
320
|
+
|
|
321
|
+
`skins` is `skinName โ slotName โ placeholderName โ attachment`. Give at least
|
|
322
|
+
`default`; it becomes the skeleton's default skin. (No rung of the benchmark ladder
|
|
323
|
+
uses a named skin โ all twelve official example skeletons have exactly one skin,
|
|
324
|
+
called `default`.)
|
|
325
|
+
|
|
326
|
+
**Region attachment** ([Spine: region attachments](http://esotericsoftware.com/spine-regions)),
|
|
327
|
+
the default `type`:
|
|
328
|
+
|
|
329
|
+
| Field | Meaning |
|
|
330
|
+
| --- | --- |
|
|
331
|
+
| `type` | `"region"`, or omit |
|
|
332
|
+
| `image` | **rigc extension.** A PNG relative to the rig's `images` directory; rigc measures it (R5) |
|
|
333
|
+
| `width`, `height` | required by the format โ give them, or give an `image` |
|
|
334
|
+
| `path` | the atlas region to resolve; defaults to the attachment's own name. rigc sets it for you when the PNG basename differs from the placeholder |
|
|
335
|
+
| `x`, `y` | offset from the bone, in the bone's local space |
|
|
336
|
+
| `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
|
|
337
|
+
| `scaleX`, `scaleY`, `color` | as Spine |
|
|
338
|
+
|
|
339
|
+
**Mesh attachment** ([Spine: meshes](http://esotericsoftware.com/spine-meshes)) โ
|
|
340
|
+
either authored geometry (`uvs` + `triangles` + geometry, plus `hull`, `edges`,
|
|
341
|
+
`width`, `height`) **or** a `generator`, never both.
|
|
342
|
+
|
|
343
|
+
Geometry comes in one of two fields:
|
|
344
|
+
|
|
345
|
+
| Field | Meaning |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| `vertices` | **unweighted**: one `x, y` per uv pair, so `vertices.length === uvs.length`. Nothing here names a bone |
|
|
348
|
+
| `weights` | **weighted, by name**: one entry per vertex, each a list of `{ "bone": โฆ, "x": โฆ, "y": โฆ, "weight": โฆ }`. This is the form to use |
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
"weights": [
|
|
352
|
+
[{ "bone": "tail3", "x": 184.91, "y": -2.83, "weight": 0.006 },
|
|
353
|
+
{ "bone": "tail4", "x": 92.72, "y": -2.83, "weight": 0.994 }],
|
|
354
|
+
[{ "bone": "tail4", "x": 84.66, "y": -8.47, "weight": 1 }]
|
|
355
|
+
]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
โญ **Weights bind bones by NAME, like everything else in a rig spec.** A bone's
|
|
359
|
+
`parent`, a slot's `bone`, an ik constraint's `bones` and `target` and a draw-order
|
|
360
|
+
key's `slot` all resolve by name and refuse a miss by name, and mesh weights now do
|
|
361
|
+
too: an unknown name is a `CompileError` that says which vertex and which name, and
|
|
362
|
+
the compiler resolves the names to indices on emit. So inserting a bone renumbers
|
|
363
|
+
the emitted array and rebinds nothing.
|
|
364
|
+
|
|
365
|
+
๐จ **The index form is still reachable and it still costs silence.** Spine's own
|
|
366
|
+
encoding is a flat run โ `boneCount, (boneIndex, bindX, bindY, weight) ร n, โฆ` โ
|
|
367
|
+
where `boneIndex` is a position in the **emitted** bone array, a list the rig spec
|
|
368
|
+
never writes and cannot see. Put one bone ahead of the meshes and every vertex
|
|
369
|
+
rebinds: the file still loads, every index is still in range, every vertex's weights
|
|
370
|
+
still sum to 1, and `A04`, `A20` and `diff` are all quiet, because an index has no
|
|
371
|
+
name to be wrong. (Measured, on the rung 6 transcription: union MAE 3.30 โ 15.09,
|
|
372
|
+
worst mesh-slot drift 0.09 px โ 9.8 px, with a green gate throughout. Issue #45.)
|
|
373
|
+
rigc therefore refuses a weighted `vertices` run unless the attachment says
|
|
374
|
+
`"boneIndexing": "raw"` out loud โ an opt-in, because what is being opted into is
|
|
375
|
+
the silence.
|
|
376
|
+
|
|
377
|
+
โ ๏ธ `vertices` carries **no encoding flag** of its own, which is why the length rule
|
|
378
|
+
above is load-bearing: if `vertices.length` equals `uvs.length` the parser reads
|
|
379
|
+
unweighted x/y pairs, otherwise it reads the weighted run. A coincidental length
|
|
380
|
+
match reads weight data as coordinates, silently โ that is `A04`.
|
|
381
|
+
|
|
382
|
+
โ ๏ธ **Authored geometry is not a rigc generator, and the gate says so.** rigc built
|
|
383
|
+
neither its rim nor its rows, so it gets to assume nothing about its topology:
|
|
384
|
+
`A21_MESH_RIM_PINNED` and `A28_RIBBON_ROWS_SHARE_WEIGHTS` **SKIP** on an authored
|
|
385
|
+
mesh with that as the reason, and `A20`'s two generator-policy branches (a mesh here
|
|
386
|
+
is weighted; a generated mesh binds only bones that move it) do not apply to one.
|
|
387
|
+
`A20`'s coherence rules โ weights present, in range, summing to 1 โ still do. Issue
|
|
388
|
+
#44; before it was fixed, `A21` reported 40 failures on a correct 40-vertex editor
|
|
389
|
+
mesh because an absent `meshKinds` entry read as `ring`.
|
|
390
|
+
|
|
391
|
+
The two generators are `ring` and `ribbon` (see [`src/mesh.ts`](../src/mesh.ts));
|
|
392
|
+
they encode a deformation model rather than a table of numbers, which is why they
|
|
393
|
+
are code invoked by data. A generator is for a skeleton with **no** manifest; a cut
|
|
394
|
+
that has one invokes the same builders through the manifest's `mesh` block.
|
|
395
|
+
|
|
396
|
+
### 3.5 `constraints` โ 4.3's single typed array
|
|
397
|
+
|
|
398
|
+
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
399
|
+
discriminator. The 4.1/4.2 shape (top-level `ik`/`transform`/`path`/`physics`
|
|
400
|
+
arrays) still loads clean and **the constraints simply vanish** โ that is `A01`.
|
|
401
|
+
|
|
402
|
+
rigc emits `ik` ([IK constraints](http://esotericsoftware.com/spine-ik-constraints)),
|
|
403
|
+
`transform` ([transform constraints](http://esotericsoftware.com/spine-transform-constraints))
|
|
404
|
+
and `physics` ([physics constraints](http://esotericsoftware.com/spine-physics-constraints)).
|
|
405
|
+
Field lists are in [`src/rig.ts`](../src/rig.ts); three traps worth carrying here:
|
|
406
|
+
|
|
407
|
+
- A transform constraint's `properties` names come from a fixed six โ `rotate`,
|
|
408
|
+
`x`, `y`, `scaleX`, `scaleY`, `shearY`. rigc refuses anything else by name; in
|
|
409
|
+
raw JSON the parser throws.
|
|
410
|
+
- Each transform mix is read **only if the matching `to` property was declared**, so
|
|
411
|
+
a `mixRotate` without a `rotate` entry is dead data.
|
|
412
|
+
- A physics constraint's five components all default to 0, so one that names none of
|
|
413
|
+
them parses cleanly and does nothing at all. rigc refuses it up front, and `A23`
|
|
414
|
+
catches it from the other side.
|
|
415
|
+
|
|
416
|
+
### 3.6 `invariants` โ what the artifact cannot say about itself
|
|
417
|
+
|
|
418
|
+
Optional, and only meaningful for rigc's own formations: `meshSlots` and
|
|
419
|
+
`meshTriangles` (the two halves of the mesh budget `A13` measures against),
|
|
420
|
+
`axisBone`, `massBone`, `detached`. Nothing in skeleton JSON records that a
|
|
421
|
+
bone carries a cut's axis or that a parentage is forbidden, so the rig spec says it
|
|
422
|
+
and the validator's archetype assertions read it. **An assertion whose field is
|
|
423
|
+
absent reports SKIP, never a pass.** If you are reproducing a foreign skeleton,
|
|
424
|
+
leave this out entirely and run `--profile spine` โ and expect `PROF` rather than
|
|
425
|
+
that SKIP, because the profile excludes an archetype assertion before its body
|
|
426
|
+
could notice the missing field (ยง5.2).
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## 4. The motion spec, field by field
|
|
431
|
+
|
|
432
|
+
`spec` must be `"rigc-motion/1"`; `archetype` must equal the rig's `name`; `cut` is
|
|
433
|
+
a label for the shot. Always include an `easings` object โ an empty one is fine.
|
|
434
|
+
|
|
435
|
+
### 4.1 `easings` โ named handles
|
|
436
|
+
|
|
437
|
+
`name โ [hx1, hy1, hx2, hy2]`, the **normalised graph-view handles** an editor
|
|
438
|
+
shows. rigc converts them per key into the absolute `(time, value)` control points
|
|
439
|
+
the JSON actually holds. Writing normalised handles into a raw `curve` instead loads
|
|
440
|
+
without error and plays a different curve.
|
|
441
|
+
|
|
442
|
+
### 4.2 `setup` โ the setup pose, per slot
|
|
443
|
+
|
|
444
|
+
`slotName โ { attachment?: string | null, color?: [r, g, b, a] }`, with the colour
|
|
445
|
+
channels in 0..1. Declaring a slot's setup pose here **and** on the rig slot is a
|
|
446
|
+
compile error (R3). Use whichever file owns the decision: a rig that is purely
|
|
447
|
+
structure puts it on the slot; a cut whose overlay mechanism is a decision about
|
|
448
|
+
time puts it here.
|
|
449
|
+
|
|
450
|
+
### 4.3 `animations` โ name โ animation
|
|
451
|
+
|
|
452
|
+
| Field | Meaning |
|
|
453
|
+
| --- | --- |
|
|
454
|
+
| `duration` | seconds, declared and checked (R7) |
|
|
455
|
+
| `loop` | a **player hint only** โ skeleton JSON has no loop field, so this is not emitted and no assertion or diff measure reads it |
|
|
456
|
+
| `note` | free text |
|
|
457
|
+
| `tracks` | the timelines |
|
|
458
|
+
| `drawOrder` | the draw-order timeline โ ยง4.7. Not a track: it names no target |
|
|
459
|
+
|
|
460
|
+
`groups` (`name โ [member, โฆ]`) lets one track target several bones or slots at
|
|
461
|
+
once; `lag` shifts every key of a track, and `stagger` adds a per-member delay in
|
|
462
|
+
member order.
|
|
463
|
+
|
|
464
|
+
### 4.4 `tracks` โ one target, one property
|
|
465
|
+
|
|
466
|
+
A track names **exactly one** of `bone`, `slot`, `group`, `physics`. Two tracks on
|
|
467
|
+
the same `target.property` is a compile error: merge them.
|
|
468
|
+
|
|
469
|
+
| Target | `property` | Key `v` |
|
|
470
|
+
| --- | --- | --- |
|
|
471
|
+
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
472
|
+
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
473
|
+
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
474
|
+
| `slot` | `attachment` | the attachment name, or `null` for "show nothing" |
|
|
475
|
+
| `physics` | `mix` | `[mix]`, 0..1 โ the constraint's authority |
|
|
476
|
+
| `physics` | `reset` | `null` โ the key *is* the event |
|
|
477
|
+
|
|
478
|
+
Translate values are **relative to the bone's setup position**; scale values are
|
|
479
|
+
multipliers where `1` is setup; rotation is in degrees.
|
|
480
|
+
|
|
481
|
+
**Single-axis timelines are not sugar.** Spine keys `translatex` and `translatey` as
|
|
482
|
+
separate timelines, so an animation that moves along one axis only is *not*
|
|
483
|
+
reproduced by a `translate` whose other channel happens to be flat: the timeline
|
|
484
|
+
count differs, the key count differs, and so does what a runtime blends against.
|
|
485
|
+
Use the paired form when a bone moves on both axes together; use the single-axis
|
|
486
|
+
form when only one axis is keyed, or when the two axes need different key times or
|
|
487
|
+
different curves. The same applies to `scale`/`scalex`/`scaley` and
|
|
488
|
+
`shear`/`shearx`/`sheary`.
|
|
489
|
+
|
|
490
|
+
An `attachment` key carries no easing โ attachment timelines are inherently
|
|
491
|
+
stepped.
|
|
492
|
+
|
|
493
|
+
### 4.5 `keys` โ times, values, curves
|
|
494
|
+
|
|
495
|
+
- `t` is in seconds and **must strictly increase** after `lag`/`stagger` are added.
|
|
496
|
+
Seconds, not frames: nothing requires a key to land on any frame grid, and a
|
|
497
|
+
reference rendered at some rate says nothing about where its keys are. Put keys
|
|
498
|
+
where the motion changes.
|
|
499
|
+
- **No key may land past the animation's `duration`.** Nothing that plays the
|
|
500
|
+
animation for the duration it declares ever reaches such a key, so it is a
|
|
501
|
+
compile error โ checked on **every timeline**, not just on the latest key in the
|
|
502
|
+
animation. The tolerance is 1e-6 s, which is one step of the grid rigc rounds key
|
|
503
|
+
times onto, so a key you put exactly *on* a duration that is not a round number
|
|
504
|
+
of microseconds is fine. R7's frame of slack does not apply in this direction and
|
|
505
|
+
would not see this: rung 6 rounded its key times to 4 dp somewhere upstream, its
|
|
506
|
+
one-frame reveal landed 0.000034 s past a 68/12 s duration, another track was
|
|
507
|
+
already sitting on the declared duration so the animation's *longest* key time
|
|
508
|
+
looked right โ and the reveal never appeared. If you want a key on the last
|
|
509
|
+
sample, write the duration's own value; if you want the animation to run longer,
|
|
510
|
+
say so in `duration`.
|
|
511
|
+
- `ease` names an entry of `easings`, or the literal `"stepped"`. Absent = linear โ
|
|
512
|
+
and that is **rigc's** default, not the editor's habit. A new key in the editor is
|
|
513
|
+
linear, but one placed between Bezier keys is not, and ยง10.4's rule from Spine's own
|
|
514
|
+
pages is that Bezier is the shape to adopt and linear the one you argue for. So
|
|
515
|
+
leaving `ease` off is a positive claim of constant speed, not a way of declining to
|
|
516
|
+
decide; ยง8 has what that bet cost on the ladder.
|
|
517
|
+
- `curve` is the raw form: **four numbers per value channel**, concatenated in field
|
|
518
|
+
order, as absolute `(time, value)` control points. A short array multiplies
|
|
519
|
+
`undefined` into the cubic and yields `NaN` with no error, so rigc length- and
|
|
520
|
+
finiteness-checks it on the way in (`A05` checks it again in the emitted file).
|
|
521
|
+
- A key may carry `ease` **or** `curve`, never both (R6).
|
|
522
|
+
- The **last** key of a track can carry neither: there is nothing to ease to, and
|
|
523
|
+
saying otherwise is a compile error.
|
|
524
|
+
|
|
525
|
+
### 4.6 `physics` โ the tuning table
|
|
526
|
+
|
|
527
|
+
`name โ { bone, x?, y?, rotate?, scaleX?, shearX?, inertia?, strength?, damping?,
|
|
528
|
+
mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
529
|
+
`constraints` array. `mass: 0` becomes an infinite inverse mass and `damping โฅ 1`
|
|
530
|
+
never settles โ both are `A23`.
|
|
531
|
+
|
|
532
|
+
`mix` is a player-side `AnimationStateData` config and is **not** emitted into
|
|
533
|
+
skeleton JSON.
|
|
534
|
+
|
|
535
|
+
### 4.7 `drawOrder` โ reordering the slots over time
|
|
536
|
+
|
|
537
|
+
The one timeline that names no target, so it sits on the animation rather than in
|
|
538
|
+
`tracks` โ which is exactly where 4.3 writes it (`animations.<a>.drawOrder`,
|
|
539
|
+
beside `bones` and `slots`).
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
"animations": {
|
|
543
|
+
"duck": {
|
|
544
|
+
"duration": 2,
|
|
545
|
+
"loop": false,
|
|
546
|
+
"drawOrder": [
|
|
547
|
+
{ "t": 0.5, "offsets": [{ "slot": "arm", "offset": 2 }] },
|
|
548
|
+
{ "t": 1.5 }
|
|
549
|
+
],
|
|
550
|
+
"tracks": []
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
- `offset` is **how many places later** that slot is drawn; negative moves it
|
|
556
|
+
earlier. Counted against the **setup** order (ยง3.3's array), never against
|
|
557
|
+
wherever the previous key left it โ each key is a complete statement of the
|
|
558
|
+
change, because the parser rebuilds the whole permutation from setup every time.
|
|
559
|
+
- A key with **no `offsets`** restores the setup order. That is the format's own
|
|
560
|
+
encoding for it, and it is how you put a swap back.
|
|
561
|
+
- Only slots that move need an entry.
|
|
562
|
+
- Draw-order keys are **stepped by nature** and carry no `ease` or `curve`.
|
|
563
|
+
- Its last key counts towards the declared duration like any other (R7).
|
|
564
|
+
|
|
565
|
+
rigc refuses four things here, all of which the parser would take:
|
|
566
|
+
|
|
567
|
+
| You wrote | You get |
|
|
568
|
+
| --- | --- |
|
|
569
|
+
| a slot this rig does not emit | `slot "X" is not one this rig emits` |
|
|
570
|
+
| the same slot twice in one key | `slot "X" is offset twice in one key` |
|
|
571
|
+
| an offset that lands outside the slots array | `slot "X" is at index 0 and offset 4 puts it at 4, outside the 2 emitted slots` |
|
|
572
|
+
| offsets in any order | nothing โ rigc **sorts** them into slot order for you |
|
|
573
|
+
|
|
574
|
+
The last one is not a courtesy. `readDrawOrder` walks a forward-only cursor over
|
|
575
|
+
the setup array, so a file whose offsets descend does not load *wrong* โ it does
|
|
576
|
+
not load at all, and the loader spins until the process dies. The array order in
|
|
577
|
+
the emitted file is the parser's requirement rather than a decision of yours, so
|
|
578
|
+
you state the set of moves and rigc writes them in the order the parser needs.
|
|
579
|
+
`A31_DRAW_ORDER_OFFSETS_RESOLVE` checks all four from the other side.
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## 5. Reading a failure
|
|
584
|
+
|
|
585
|
+
Failures arrive in two layers, and they read differently.
|
|
586
|
+
|
|
587
|
+
### 5.1 Compile errors โ before the gate
|
|
588
|
+
|
|
589
|
+
A `CompileError` names the object and the field, and nothing is written. These are
|
|
590
|
+
the frequent ones, verbatim:
|
|
591
|
+
|
|
592
|
+
| Message | What to change |
|
|
593
|
+
| --- | --- |
|
|
594
|
+
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
595
|
+
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
596
|
+
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
597
|
+
| `no setup pose for slot "X": give the motion spec a \`setup\` entry or the rig slot an \`attachment\`` | R3 โ pick one file and declare it there |
|
|
598
|
+
| `a region needs width and height โ give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
|
|
599
|
+
| `image "X.png" is not on disk at โฆ` | fix the name, or point `--images` at the right directory |
|
|
600
|
+
| `duplicate region name "X"` | two PNGs share a basename; one part, one page, one name |
|
|
601
|
+
| `motion spec names archetype "A" but the rig spec at โฆ is called "B"` | make `archetype` equal the rig's `name` |
|
|
602
|
+
| `animation "A" declares duration Ns but its last key is at Ms` | R7 โ fix whichever of the two you meant |
|
|
603
|
+
| `animation "A" slot "X" attachment: key at Ns is Ms past the declared duration Ds` | ยง4.5 โ the key is past the end of the animation and nothing will sample it. Move the key onto `duration`, or raise `duration` |
|
|
604
|
+
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
605
|
+
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (ยง4.4) |
|
|
606
|
+
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
607
|
+
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
608
|
+
| `key times must strictly increase (at t=โฆ)` | including after `lag` and `stagger` |
|
|
609
|
+
| `animation "A" has two tracks on X.property; merge them into one track` | one timeline per target property |
|
|
610
|
+
| `no stage size: give the rig spec a \`skeleton.width\`/\`skeleton.height\`` | ยง3.1 |
|
|
611
|
+
| `drawOrder at t=โฆ: slot "X" is not one this rig emits` / `is offset twice in one key` / `puts it at N, outside the โฆ emitted slots` | ยง4.7 |
|
|
612
|
+
| `bone "X" takes its position from โฆ, which needs a cut manifest` | R8 โ pass `--manifest`, or write literal `x`/`y` |
|
|
613
|
+
|
|
614
|
+
### 5.2 Assertions โ the gate
|
|
615
|
+
|
|
616
|
+
The report prints one line per assertion:
|
|
617
|
+
|
|
618
|
+
```
|
|
619
|
+
PASS A08_REGION_NAMES_MATCH_ATTACHMENTS
|
|
620
|
+
SKIP A21_MESH_RIM_PINNED: the skeleton has no weighted mesh attachment, โฆ
|
|
621
|
+
PROF A11_NO_CLIPPING_ATTACHMENTS: renderer rule, not in profile "spine"
|
|
622
|
+
FAIL A20_MESH_WEIGHTS_COHERENT: mesh "x" vertex 12 weights sum to 0.9000
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
- **PASS** โ it ran and held.
|
|
626
|
+
- **SKIP** โ it had *nothing to look at*, and the reason says what was missing. A
|
|
627
|
+
skip is never folded into the pass count.
|
|
628
|
+
- **PROF** โ the profile you chose does not carry that kind of rule. Two kinds sit
|
|
629
|
+
outside `spine`, not one: the renderer rules **and** the archetype rules. The
|
|
630
|
+
exclusion is checked before the assertion's body runs, so an archetype rule with
|
|
631
|
+
no `invariants` field to measure reports `PROF` here, never its own SKIP. A
|
|
632
|
+
`--profile spine` green means *valid Spine*, never *passes the renderer policy*
|
|
633
|
+
and never *holds to the archetype rules*.
|
|
634
|
+
- **FAIL** โ the detail names the object, the value found and the value required.
|
|
635
|
+
That detail is the instruction; the table below says which file to change.
|
|
636
|
+
|
|
637
|
+
| Assertion | Profile | What tripped it, and where to fix it |
|
|
638
|
+
| --- | --- | --- |
|
|
639
|
+
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas. Everything else in the report is downstream of this one โ fix it first |
|
|
640
|
+
| `A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS` | both | a 4.1/4.2-shaped `ik`/`transform`/`path`/`physics`/`slider` array. rigc emits the 4.3 `constraints` array, so this normally means hand-edited JSON |
|
|
641
|
+
| `A02_NO_BONE_TRANSFORM_KEY` | both | a bone uses 4.2's `transform`; rename it `inherit` in the rig spec |
|
|
642
|
+
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size โ the attachment has no `image` and no `width`/`height` |
|
|
643
|
+
| `A04_MESH_TRIANGLES_AND_ENCODING` | both | authored mesh geometry: triangle count not a multiple of 3, an index out of range, or a `vertices` length that disagrees with `uvs` (the weighted/unweighted trap) |
|
|
644
|
+
| `A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel** |
|
|
645
|
+
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both โ | the atlas `size:` disagrees with the PNG on disk. Under `spine-html` also: `pma`, rotation, and a region that does not cover its page |
|
|
646
|
+
| `A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file |
|
|
647
|
+
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both โ | an attachment resolves to a region the atlas does not have โ usually a `path`/`image` basename mismatch. Under `spine-html` the placeholder and the region name must also be *identical* |
|
|
648
|
+
| `A09_ANIMATION_DURATION_MATCHES_SPEC` | both | the loaded duration โ the declared one, or the two sides disagree about which animations exist (R7). Asymmetric by design: a frame of slack for an animation that ends early, and none worth the name for a key *past* the declared end, which is the same rule ยง4.5 states at compile time โ held here against a skeleton the compiler never saw. **SKIP** when neither side has an animation at all โ a static rig has no duration |
|
|
649
|
+
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale |
|
|
650
|
+
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
651
|
+
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
652
|
+
| `A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither |
|
|
653
|
+
| `A14_NO_FULL_FRAME_MESH` | renderer | a mesh spans the whole stage โ a full-frame canvas that can never dirty-skip |
|
|
654
|
+
| `A15_IDLE_NO_MESH_BONE_KEYS` | renderer | the `idle` animation keys a bone that drives a mesh, directly or as a control bone |
|
|
655
|
+
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` label is not on the 4.3 line (`4.3`, `4.3.N`, `4.3.N-suffix`) |
|
|
656
|
+
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out` |
|
|
657
|
+
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug โ report it |
|
|
658
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay page has a colour type with no alpha channel. Only the full-stage base plate may be opaque |
|
|
659
|
+
| `A20_MESH_WEIGHTS_COHERENT` | both โ | a weighted vertex with no bone, a negative weight, a bone index out of range, or weights that do not sum to 1. Under `spine-html` also: an unweighted mesh, or a binding at weight 0 |
|
|
660
|
+
| `A21_MESH_RIM_PINNED` | archetype | a generated ring's rim, or a ribbon's entry row, is not pinned to its anchor bone at weight 1 |
|
|
661
|
+
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count |
|
|
662
|
+
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, is muted by `mix: 0`, has `mass: 0`, has `strength: 0`, or has `damping` outside `(0, 1)` so it never settles |
|
|
663
|
+
| `A24_AXIS_SPACE_STROKE` | archetype | a bone under the rig's `axisBone` was keyed with a screen-space Y component, or the axis bone itself was keyed |
|
|
664
|
+
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
665
|
+
| `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not a subsequence of the rig's slot table โ a slot is out of order, or is not in the table at all |
|
|
666
|
+
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | renderer | a single-region page whose region name is not the PNG's basename |
|
|
667
|
+
| `A28_RIBBON_ROWS_SHARE_WEIGHTS` | archetype | the two vertices of a ribbon row carry different weights, so the strip would change width |
|
|
668
|
+
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
669
|
+
| `A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | the animation drives past the measured containment ceiling, or scales a bone in the axis subtree |
|
|
670
|
+
| `A31_DRAW_ORDER_OFFSETS_RESOLVE` | both | a draw-order key names a slot the skeleton does not have, offsets one slot twice, puts a slot outside the slots array, or lists its offsets out of slot order (ยง4.7). The only assertion that runs **before** `A00` โ the last of those shapes makes the loader spin rather than return, so the round trip is refused instead of attempted |
|
|
671
|
+
|
|
672
|
+
`both โ` marks a mixed assertion: its validity half always runs and its policy
|
|
673
|
+
clauses are gated by profile.
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## 6. What rigc will refuse โ do not spend a loop on these
|
|
678
|
+
|
|
679
|
+
These are in the Spine 4.3 format, and the emitter does not write them. Each one is
|
|
680
|
+
a **`NotImplementedError` naming the field**, because the parser's own behaviour is
|
|
681
|
+
worse: an unknown attachment `type` returns `null` and the attachment disappears,
|
|
682
|
+
and a constraint entry with an unrecognised `type` matches no case and vanishes.
|
|
683
|
+
|
|
684
|
+
| You wrote | You get |
|
|
685
|
+
| --- | --- |
|
|
686
|
+
| attachment `type` of `boundingbox`, `point`, `clipping`, `path`, `linkedmesh` | `attachment type "X" is in the Spine 4.3 format and rigc does not emit it yet. Implemented: region, mesh.` |
|
|
687
|
+
| constraint `type` of `path` or `slider` | `constraint type "X" โฆ Implemented: ik, transform, physics.` |
|
|
688
|
+
| mesh `generator.kind` of `contour` | `the "contour" generator would triangulate a part's own alpha mask, and src/mesh.ts has no triangulator` |
|
|
689
|
+
|
|
690
|
+
Two more limits that are not errors but will shape what you can attempt:
|
|
691
|
+
|
|
692
|
+
- **No atlas packer and no atlas importer.** rigc emits **one part per page**: every
|
|
693
|
+
region covers its whole page, `pma: false`. To reproduce a skeleton whose art
|
|
694
|
+
ships as a packed atlas you either supply loose PNGs and let rigc build its own
|
|
695
|
+
atlas, or hand the packed atlas to `validate`/`bench` alongside the candidate.
|
|
696
|
+
- **Sequences, `drawOrderFolder`, event timelines and deform timelines** are walked
|
|
697
|
+
by the validator but are not motion-spec properties: the track table in ยง4.4 is
|
|
698
|
+
the complete list of what a *track* can key, and ยง4.7's `drawOrder` is the only
|
|
699
|
+
timeline outside it.
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## 7. Before you call it done
|
|
704
|
+
|
|
705
|
+
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
706
|
+
2. Read the `SKIP` lines. Each one is a check that did *not* run โ make sure none of
|
|
707
|
+
them is a check you were relying on.
|
|
708
|
+
โ ๏ธ Under `--profile spine` a foreign skeleton usually produces **no SKIP lines
|
|
709
|
+
at all**, and that is not a clean bill of health. The archetype assertions are
|
|
710
|
+
excluded by the profile before the missing `invariants` block could make them
|
|
711
|
+
skip, so they come back `PROF` instead. Do not go looking for a SKIP that the
|
|
712
|
+
profile already accounted for; read step 3 instead.
|
|
713
|
+
3. Read the `PROF` lines. They are where "was this rig held to that rule at all"
|
|
714
|
+
gets answered for everything the profile left out โ the renderer policy *and*
|
|
715
|
+
the archetype rules. A green under `spine` has been held to neither; a green
|
|
716
|
+
under `spine-html` has been held to both.
|
|
717
|
+
4. Run `explain` and read the slots table: every slot you declared should be there
|
|
718
|
+
(ยง3.3), in the order you meant, showing the setup attachment you meant.
|
|
719
|
+
5. If you were given **frames**, run `check` and read the table (ยง9). Steps 1โ4 are
|
|
720
|
+
all about validity and structure; none of them can tell you the animation is
|
|
721
|
+
wrong, and this is the step that can. Do it before step 6, not after โ `bench`
|
|
722
|
+
is a finish line.
|
|
723
|
+
6. If you are reproducing a reference, run `diff` or `bench` and read **every**
|
|
724
|
+
measure. There is no single score, and a `0/0` measure compared nothing.
|
|
725
|
+
|
|
726
|
+
Then read **ยง10** against what you wrote. Steps 1โ6 ask whether the rig is valid and
|
|
727
|
+
whether it looks right; ยง10 asks whether it is built the way the editor builds one,
|
|
728
|
+
which is a question none of them can reach and which the measures in `bench` do see.
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## 8. Reading reference frames
|
|
733
|
+
|
|
734
|
+
Only if you are reproducing a shot you were given as **rendered frames** โ the
|
|
735
|
+
benchmark ladder works that way, and so does any brief that hands you pictures
|
|
736
|
+
instead of numbers. Skip this section if you are authoring from a manifest.
|
|
737
|
+
|
|
738
|
+
The frames are the whole of what you know, so every number you author comes out of
|
|
739
|
+
measuring them, and **a measurement artefact is indistinguishable from a fact about
|
|
740
|
+
the animation** until something contradicts it. The three below are not
|
|
741
|
+
hypothetical: all three were made, believed, and only then caught, on the first
|
|
742
|
+
run of ladder rung 3. Each one had a tidy story attached, which is what made it
|
|
743
|
+
survive.
|
|
744
|
+
|
|
745
|
+
**Two things that touch become one thing.** An estimator that fits a shape to the
|
|
746
|
+
whole silhouette โ a PCA, a bounding box, a centroid โ silently changes meaning on
|
|
747
|
+
the frames where two parts overlap, because their pixels label as one blob and the
|
|
748
|
+
mass of the second drags the fit. On rung 3 that put the swinging bar 11ยฐ off on
|
|
749
|
+
exactly the frame where it struck the block, which read as a sharp deceleration *at
|
|
750
|
+
contact*: an obvious energy transfer, and a key worth authoring. It was not there.
|
|
751
|
+
Measured on a region that excludes the other part, the deceleration is smooth and
|
|
752
|
+
has **no corner at contact at all**. โ Measure each part on pixels that can only be
|
|
753
|
+
that part โ a connected component, an annulus, a colour key โ and be most
|
|
754
|
+
suspicious of your result on precisely the frames where the interesting event
|
|
755
|
+
happens, because those are the frames where the parts are touching.
|
|
756
|
+
|
|
757
|
+
**A symmetric shape hides a sign error.** The same run masked the block out of the
|
|
758
|
+
bar's estimator by subtracting its rotated footprint, and rotated the test box the
|
|
759
|
+
wrong way. A square is symmetric under 90ยฐ, so the error is `2ฮธ mod 90`: invisible
|
|
760
|
+
while the block is upright, and only leaking pixels once it has turned ~25ยฐ. One of
|
|
761
|
+
the two shots stayed correct and the other quietly produced a **negative** render
|
|
762
|
+
scale from the same script. โ Run the same estimator over two shots and cross-check
|
|
763
|
+
a quantity that must agree between them โ the pixels-per-unit scale, a fixed
|
|
764
|
+
pivot's position, the size of something that never changes. A single shot cannot
|
|
765
|
+
tell you your estimator is wrong.
|
|
766
|
+
|
|
767
|
+
**Draw order is read from what stays visible, not from what looks cut.** Where two
|
|
768
|
+
parts overlap, the one in front usually looks like it is *clipping* the one behind โ
|
|
769
|
+
and a light seam along the edge makes that reading stronger. That seam is often a
|
|
770
|
+
rendering artefact: a PNG with a fully transparent border whose RGB is white bleeds
|
|
771
|
+
a halo under bilinear filtering, so the join is background-coloured rather than
|
|
772
|
+
either part's colour, which reads as a hole. โ Decide draw order by finding a frame
|
|
773
|
+
where one part's **interior detail** โ a marking, a highlight, anything not on its
|
|
774
|
+
outline โ lies inside the other part's area, and see which survives. Then write the
|
|
775
|
+
slots in that order (R4), because there is no other place in the file to say it.
|
|
776
|
+
|
|
777
|
+
And the general form of all three: **when a reading implies a key, look for a second
|
|
778
|
+
way to get the same number before you author it.** A wrong measurement costs one
|
|
779
|
+
spurious key; a wrong measurement you believed costs the shape of the whole shot.
|
|
780
|
+
|
|
781
|
+
**A value is easier to get right than a curve.** The three traps above are all
|
|
782
|
+
about measuring a *value*, and both ladder runs so far found that the values came
|
|
783
|
+
out right early: rung 1's key values were exact at every keyframe on the second
|
|
784
|
+
build. What was wrong was the *shape between* them โ every accelerating segment
|
|
785
|
+
had the decelerating curve and vice versa, from one inverted comparison. Nothing
|
|
786
|
+
in a static reading of the file can find that, because both candidates for the
|
|
787
|
+
shape are legal and the file reads fine either way, and a symmetric pair of easings
|
|
788
|
+
looks the same in every listing. Curves are where the error lives; ยง9 is how you
|
|
789
|
+
find it.
|
|
790
|
+
|
|
791
|
+
**But linear is not the neutral option.** The frames genuinely underdetermine a
|
|
792
|
+
curve: at rung 6's 12 fps, with keys landing every one to three frames, there is
|
|
793
|
+
almost nothing left for a handle shape to be constrained by, and **there is no
|
|
794
|
+
principled way to estimate one from frame spacing alone โ rigc does not offer one and
|
|
795
|
+
this guide does not invent one.** `diff`'s `curve_kinds` counts how many keys are
|
|
796
|
+
linear, stepped or bezier and never compares two handle shapes; `check` measures the
|
|
797
|
+
rendered result, so it can tell you a curve is *wrong* without telling you what it
|
|
798
|
+
should have been. What does not follow is that omitting `ease` abstains. It authors
|
|
799
|
+
constant speed on every span โ the one shape a hand-animated reference almost never
|
|
800
|
+
has (ยง10.4) โ and the ladder has measured both sides of that bet. Rung 6 keyed
|
|
801
|
+
everything linear for exactly the reasoning above and scored `curve_kinds` **34/539**,
|
|
802
|
+
its single largest structural gap. Rung 3's second attempt โ same brief, same frames โ
|
|
803
|
+
applied ยง10.4's rule instead and went 41/69 โ 49/69, with `key_counts` rising beside
|
|
804
|
+
it and every other section figure unchanged. โ Take the curve *kind* from what the motion does โ starts, stops,
|
|
805
|
+
accelerates, settles, falls โ rather than from how far apart the keys are; take its
|
|
806
|
+
shape from ยง10.4's automatic-handle advice and a small reused `easings` table; and
|
|
807
|
+
leave `check` to catch the one thing no static reading can, an easing applied the
|
|
808
|
+
wrong way round.
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
## 9. Checking against the frames โ `rigc check`
|
|
813
|
+
|
|
814
|
+
```bash
|
|
815
|
+
bun cli.ts check --candidate path/to/spine --frames path/to/reference/frames
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
`--frames` takes either a **skeleton root** (the directory holding `frames.json`,
|
|
819
|
+
which checks every animation of that shot) or **one animation directory** inside
|
|
820
|
+
it. Everything else is optional: `--atlas` when the candidate's atlas is not beside
|
|
821
|
+
its skeleton, `--as <name>` when your animation is called something the frame
|
|
822
|
+
directory is not, `--all-frames` to list every frame instead of the worst by MAE,
|
|
823
|
+
`--json <out>` for the whole per-frame, per-slot report.
|
|
824
|
+
|
|
825
|
+
โ ๏ธ **A frame set may be contact-sheets-only.** `check` only reads `fNNNN.png`
|
|
826
|
+
files โ a committed reference set that ships a contact sheet plus a couple of
|
|
827
|
+
stills (rung 2's does: `f0000.png` and `f0310.png` per animation, the rest folded
|
|
828
|
+
into `contact.png` so a 311-frame shot does not commit 311 near-duplicate PNGs)
|
|
829
|
+
reports `frames 2 on disk, candidate samples 311, 2 compared` and means it: `check`
|
|
830
|
+
compared exactly the committed stills, not the shot. That is not a defect to author
|
|
831
|
+
around โ the frame count line says so rather than pretending a fuller comparison
|
|
832
|
+
happened โ but it does mean a clean `check` table on a contact-sheet-only set says
|
|
833
|
+
nothing about the frames between the stills. Whole-shot fidelity against a contact
|
|
834
|
+
sheet needs a tile-wise comparison against the sheet's own grid, which `check` does
|
|
835
|
+
not do yet (issue #36);
|
|
836
|
+
[`bench/runs/2026-08-23-rung2-2/sheetcheck.ts`](../bench/runs/2026-08-23-rung2-2/sheetcheck.ts)
|
|
837
|
+
is a working prototype, built in-run for exactly this gap.
|
|
838
|
+
|
|
839
|
+
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
840
|
+
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
841
|
+
sampled at, and without it the 12 fps protocol rate is assumed and the report says
|
|
842
|
+
so rather than letting the assumption look like a measurement. Passing `--fps` with
|
|
843
|
+
a value the sidecar contradicts is an error, not an override.
|
|
844
|
+
|
|
845
|
+
`--viewport <x>,<y>,<width>,<height>` pins your candidate's world box instead of
|
|
846
|
+
fitting it. Two uses:
|
|
847
|
+
|
|
848
|
+
- the derivation cannot work โ a candidate deliberately missing a part has a
|
|
849
|
+
different content box by construction, and pinning lets the rest of the shot
|
|
850
|
+
still be measured;
|
|
851
|
+
- you want the framing **held still** between builds. The framing line is still
|
|
852
|
+
measured and printed when the box is pinned, so a pinned run separates "my keys
|
|
853
|
+
moved" from "my framing moved" without either hiding the other. On a shot whose
|
|
854
|
+
MAE moves by more than a point for a fraction of a pixel, that separation is
|
|
855
|
+
worth more than the absolute number.
|
|
856
|
+
|
|
857
|
+
There used to be a third โ *"you already know your candidate's world coordinates
|
|
858
|
+
match the reference's own, declared in `frames.json`"* โ and **`check` now does
|
|
859
|
+
that one for you** (issue #52). Before fitting anything it renders your candidate
|
|
860
|
+
into the box `frames.json` records and measures where your pixels land. If they
|
|
861
|
+
land on the reference's to within a pixel, that box is yours too, and it is used:
|
|
862
|
+
it is not an *estimate* of where the frames were drawn, it is where they were
|
|
863
|
+
drawn, and the framing line says `frames.json's own box โ the candidate measured
|
|
864
|
+
into it`. If they do not, the box is refused and your candidate is framed by its
|
|
865
|
+
own pixels exactly as before โ which is the ordinary case, because the reference's
|
|
866
|
+
origin is in a file you are not allowed to open.
|
|
867
|
+
|
|
868
|
+
That is worth a paragraph rather than a line because of what it costs when it is
|
|
869
|
+
missing. The fit is registered on **extent**, and extent is not alignment (see the
|
|
870
|
+
โ ๏ธ in ยง9.2), so on a shot whose silhouette differs anywhere it lands a fraction of
|
|
871
|
+
a percent away from the framing the frames were drawn at โ and a fraction of a
|
|
872
|
+
percent of scale is worth several MAE. Measured on rung 6: **8.73 fitted against
|
|
873
|
+
3.50 in the frames' own box**, with every content box, residual and rms already
|
|
874
|
+
under the method's noise. Rung 5 measured the same gap, 12.49 against 4.35. Neither
|
|
875
|
+
author could tell that from a wrong animation without running the pin by hand.
|
|
876
|
+
|
|
877
|
+
Pinning to paper over a **real** framing difference โ rather than one of the two
|
|
878
|
+
cases above โ is the dishonest use: it makes a genuine mismatch between your
|
|
879
|
+
candidate and the reference disappear from the report instead of showing up as
|
|
880
|
+
`content`/`rms`/`union residual`. That used to be easy to do by accident, because
|
|
881
|
+
the old quad-corner framing could be wrong by more than a pixel for reasons that had
|
|
882
|
+
nothing to do with either side's motion โ two honest ladder runs measured it costing
|
|
883
|
+
30+ points of MAE with no key changed, which is why framing is now fitted to drawn
|
|
884
|
+
pixels rather than quad corners (issue #34, closed by #39; see ยง9.2). Pin to a box
|
|
885
|
+
you can name a reason for, and read the unpinned framing line first when you are not
|
|
886
|
+
sure whether you have one.
|
|
887
|
+
|
|
888
|
+
โ ๏ธ **The framing is over the frames you compare.** `--frames <root>` fits one
|
|
889
|
+
framing across every set under it; `--frames <root>/<one-set>` fits one to that set
|
|
890
|
+
alone. Both are right and they are not the same number, so compare like with like
|
|
891
|
+
across builds.
|
|
892
|
+
|
|
893
|
+
### 9.1 Why this exists
|
|
894
|
+
|
|
895
|
+
The validator has no way to know whether an animation is the right animation. It
|
|
896
|
+
parses the skeleton, steps every timeline, and refuses what is degenerate โ a
|
|
897
|
+
`NaN` pose, a curve of the wrong length, a duration that disagrees with the last
|
|
898
|
+
key. A rig whose motion is backwards is none of those things. **`diff` cannot see
|
|
899
|
+
it either**: reversing every easing leaves the timeline count, the key count, the
|
|
900
|
+
curve kinds and the duration exactly where they were, so every measure it reports
|
|
901
|
+
is unmoved. The two tools together can tell you a file is valid Spine that closely
|
|
902
|
+
matches a reference's structure, while it plays a different shot.
|
|
903
|
+
|
|
904
|
+
So `check` compares pictures. It renders your candidate with the same rasteriser
|
|
905
|
+
that drew the reference frames, onto the same pixel grid, and reports what differs.
|
|
906
|
+
|
|
907
|
+
๐ **It never opens the reference skeleton โ only the frames.** That matters for
|
|
908
|
+
you specifically: it means **you may run `check` as often as you like** without
|
|
909
|
+
your run ceasing to be an honest authoring run. It is a loop, in the way `build` is
|
|
910
|
+
a loop. `bench` and `diff` against a rung's export are not โ they read the answer,
|
|
911
|
+
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
912
|
+
|
|
913
|
+
### 9.2 Reading the table
|
|
914
|
+
|
|
915
|
+
```
|
|
916
|
+
framed to 256x116px 0.116677 px/unit world x[-782.1 .. 1412.0] y[-794.7 .. 199.5] (fitted to the candidate's own drawn pixels)
|
|
917
|
+
reference 256x116px 0.117628 px/unit world x[-573.3 .. 1603.0] y[-81.2 .. 908.9] (frames.json)
|
|
918
|
+
content candidate 234.6x95.5px at (11.3, 11.5) reference 234.7x95.3px at (11.2, 11.7) (union over 86 frame(s))
|
|
919
|
+
โคท fit x0.999256 offset +0.05, -0.02 px rms 0.42 px over 344 edge(s) union residual -0.27 x +0.17 px aspect -0.30% (derived, 4 pass(es), settled)
|
|
920
|
+
in units candidate 1995.3 x 809.7 reference 1995.3 x 809.9 x0.9999
|
|
921
|
+
|
|
922
|
+
โโ heavy โ candidate animation "heavy", 12 fps โโ
|
|
923
|
+
frames 65 on disk, candidate samples 65, 65 compared
|
|
924
|
+
MAE mean 23.10 worst 43.36 at f0029 (0..255 over the union alpha; โฆ)
|
|
925
|
+
slot drift worst 2.1 px "pendulum" at f0029
|
|
926
|
+
per-frame 1 of 64 adjacent pair(s) change by a different amount than the reference does; worst
|
|
927
|
+
f0018, yours moved 0 px where the reference moved 374
|
|
928
|
+
|
|
929
|
+
the 9 frames worth reading โ worst by MAE, plus every frame whose own change disagrees, in index order
|
|
930
|
+
frame MAE union px ฮpx ref ฮ worst slot drift how slots note
|
|
931
|
+
f0018 9.12 1402 0 374 pendulum 0.4 component 2/2 the reference moves here and yours holds still
|
|
932
|
+
f0029 43.36 1409 288 301 pendulum 2.1 component 2/2
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
**Read the framing block first.** Everything below it is computed on the grid it
|
|
936
|
+
chose, so an error there arrives disguised as motion โ which is exactly what
|
|
937
|
+
happened to two honest ladder runs before this was fixed (issue #34).
|
|
938
|
+
|
|
939
|
+
Unless the frames' own box already fits you (above), your candidate is framed **by
|
|
940
|
+
its own drawn pixels**. `check` renders it at the frames' own rate and grid, takes
|
|
941
|
+
the content box of what it actually draws, takes the reference's content box off
|
|
942
|
+
the PNGs with the same rule, and fits the similarity transform โ one uniform scale
|
|
943
|
+
plus a translation, least squares over **every edge of every frame** โ that carries
|
|
944
|
+
one onto the other. Then it renders through that transform and measures again,
|
|
945
|
+
until the correction is the identity.
|
|
946
|
+
|
|
947
|
+
The parenthesis at the end of the `โคท fit` line says which of those happened and how
|
|
948
|
+
it ended: `derived` for the fit and `declared` for the frames' own box, then the
|
|
949
|
+
pass count, then one of
|
|
950
|
+
|
|
951
|
+
- `settled` โ the correction converged to the identity. Nothing further to read.
|
|
952
|
+
- `coincident` โ the frames' own box was kept because your pixels landed in it.
|
|
953
|
+
The fit beside it is what a fit would still ask for, and on a shot whose
|
|
954
|
+
silhouette differs anywhere that is not zero; it is the fit's floor, not your
|
|
955
|
+
keys.
|
|
956
|
+
- `cycling` โ the correction fell into a repeating orbit instead of converging.
|
|
957
|
+
**More passes cannot help**: the fit has no fixed point here. Read the `โ ๏ธ` line
|
|
958
|
+
under it, which says whether the two content boxes agree anyway (the fit's own
|
|
959
|
+
floor, and the numbers below are usable) or do not (a real shape difference, and
|
|
960
|
+
that is the finding).
|
|
961
|
+
- `unsettled` โ it ran out of passes without either. Same two readings as
|
|
962
|
+
`cycling`, and the `โ ๏ธ` line tells you which.
|
|
963
|
+
|
|
964
|
+
That procedure is blind to the two things it must be blind to. **An invisible
|
|
965
|
+
margin cannot move it**: a region's quad runs past its own artwork wherever the art
|
|
966
|
+
is transparent, and that used to set the scale; now nothing outside the drawing is
|
|
967
|
+
looked at (the selftest proves art padded by 20 px on two sides reports numbers
|
|
968
|
+
identical to the last decimal). **A choice of units cannot move it either**: a rig
|
|
969
|
+
scaled by 2 % renders to the same pixels and reads the same MAE.
|
|
970
|
+
|
|
971
|
+
The lines, in order:
|
|
972
|
+
|
|
973
|
+
- `framed to` / `reference` โ the two world boxes and their scales. They are
|
|
974
|
+
**different coordinate systems and do not compare term by term**; the reference's
|
|
975
|
+
is printed for orientation and for turning a pixel measurement into units. Unless
|
|
976
|
+
`framed to` says `frames.json's own box`, in which case they are one box and one
|
|
977
|
+
coordinate system, because your candidate was measured into it.
|
|
978
|
+
- `content` โ the two boxes in **frame pixels**, which do compare, and the fit that
|
|
979
|
+
put one on the other. `fit x1.000000` with a small `offset` means the two shots
|
|
980
|
+
are the same size in the same place.
|
|
981
|
+
- `rms` โ what the fit could not explain, across every edge of every frame. Under a
|
|
982
|
+
pixel is the method's own noise. Over a pixel means no single scale and offset
|
|
983
|
+
puts these two shots on each other: they are different shapes, not the same shape
|
|
984
|
+
misframed.
|
|
985
|
+
- `union residual` and `aspect` โ the extent your shot covers that the reference's
|
|
986
|
+
does not, after the fit. This is the number that says *"something reaches
|
|
987
|
+
somewhere nothing in the frames does, or is a different size"*, and a warning
|
|
988
|
+
spells it out past a pixel.
|
|
989
|
+
- `in units` โ the same two boxes in world units. The framing absorbs a pure scale
|
|
990
|
+
on purpose, so this is the only place one shows; it compares only if you measured
|
|
991
|
+
the shot in the frames' own units.
|
|
992
|
+
|
|
993
|
+
โ ๏ธ **The framing is fitted to extent, and extent is not the same as alignment.**
|
|
994
|
+
When your silhouette genuinely differs somewhere โ a limb that overreaches, a part
|
|
995
|
+
that is a little large โ the best fit of the two extents is not quite the best
|
|
996
|
+
alignment of the two pictures, and the fit spends a fraction of a pixel absorbing
|
|
997
|
+
a difference that would have been cheaper to leave alone. Measured floor: about a
|
|
998
|
+
third of a pixel on the ladder's shots. On most that is invisible; on a small
|
|
999
|
+
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five. This
|
|
1000
|
+
is the floor the frames' own box has no share in, which is why `check` prefers that
|
|
1001
|
+
box whenever your pixels are measured to land in it; `--viewport` is how you stop
|
|
1002
|
+
it in the cases that box does not cover.
|
|
1003
|
+
|
|
1004
|
+
**MAE** is the mean absolute RGB difference, 0..255, over the pixels either side
|
|
1005
|
+
covers โ the *union alpha*. It is not scored against a threshold, any more than a
|
|
1006
|
+
`diff` measure is. What it is good for is comparison: between two builds of your
|
|
1007
|
+
own rig, and between frames of one build. A shot whose MAE is flat across the set
|
|
1008
|
+
and a shot with two spikes are different diagnoses โ the first is usually framing
|
|
1009
|
+
or art, the second is timing at those moments. The whole-frame figure printed
|
|
1010
|
+
beside it is the same difference averaged over the background as well; it is there
|
|
1011
|
+
because an ad-hoc re-render check naturally computes that one, and on every set
|
|
1012
|
+
measured so far it comes out ten to twenty-five times smaller and correspondingly
|
|
1013
|
+
blunter.
|
|
1014
|
+
|
|
1015
|
+
**`ฮpx` and `ref ฮ`** are the two columns that do **not** compare you against the
|
|
1016
|
+
reference. They compare each side against **itself one frame earlier**: how many
|
|
1017
|
+
pixels of your own frame moved since your own previous frame, and the same for the
|
|
1018
|
+
reference. Then the `per-frame` summary compares those two numbers.
|
|
1019
|
+
|
|
1020
|
+
That is a different question from everything else in the report, and it catches a
|
|
1021
|
+
class of defect nothing else here can โ because the defect is cheap in every single
|
|
1022
|
+
frame and wrong only in the relation between two:
|
|
1023
|
+
|
|
1024
|
+
- **A held pose that is not held.** Rung 6's reference is pixel-identical across
|
|
1025
|
+
f64โf67. A greedy key reduction had sloped a line through that plateau, legal
|
|
1026
|
+
under its own per-key tolerance, and the candidate moved **91 px across f67โf68
|
|
1027
|
+
where the reference moves 3**. The gate was green, `diff` was unmoved, and the
|
|
1028
|
+
aggregate MAE did not shift by a tenth of a point. The column says `the reference
|
|
1029
|
+
holds still here and yours does not`.
|
|
1030
|
+
- **A one-frame event that never fires.** The same run's tracker reveal landed a
|
|
1031
|
+
fraction of a millisecond past the animation's last sample. `diff` read the
|
|
1032
|
+
structure as matching. The column says `the reference moves here and yours holds
|
|
1033
|
+
still`.
|
|
1034
|
+
|
|
1035
|
+
Both of those were found by that run writing its own render-diff by hand. Read this
|
|
1036
|
+
line whenever the MAE is flat and something still looks wrong: a flat MAE says the
|
|
1037
|
+
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
1038
|
+
and blinks where the reference does.
|
|
1039
|
+
|
|
1040
|
+
โ ๏ธ Only between **adjacent** frames. A set that ships stills rather than every frame
|
|
1041
|
+
โ rung 2's contact-sheet sets โ reports `no two compared frames are adjacent`, and
|
|
1042
|
+
means it: the difference between two frames 310 apart is not a frame-to-frame delta.
|
|
1043
|
+
A disagreement needs one side to hold *exactly* still while the other moves, or one
|
|
1044
|
+
side to move four times the other and at least two dozen pixels more; below that the
|
|
1045
|
+
two rasterisations differ by their own last bit and the column says nothing.
|
|
1046
|
+
|
|
1047
|
+
**Slot drift** is what you act on. For each of your slots, `check` measures where
|
|
1048
|
+
it landed and how far that is from where the reference put it. That names the part,
|
|
1049
|
+
the frame and the distance, so "the beach ball is 4.7 px low at f0005" is a
|
|
1050
|
+
sentence you can take straight back to a key.
|
|
1051
|
+
|
|
1052
|
+
There are two matchers and the `how` column says which one answered:
|
|
1053
|
+
|
|
1054
|
+
- `component` โ your slot sits on a connected component of the reference frame that
|
|
1055
|
+
is its own size. The drift is the distance between the two centroids, and it is
|
|
1056
|
+
the strongest answer available.
|
|
1057
|
+
- `tmpl 0.62` โ the reference merged your slot into a neighbour (they touch, or one
|
|
1058
|
+
is drawn over the other), so the fallback rendered **your slot on its own** and
|
|
1059
|
+
correlated it against the reference around where you drew it. The number is the
|
|
1060
|
+
confidence: how much better the winning position was than the best rival inside
|
|
1061
|
+
the search window. This is what gives a shot like a chain of touching links any
|
|
1062
|
+
drift at all โ under connected components alone, every frame of it is ambiguous.
|
|
1063
|
+
|
|
1064
|
+
โ ๏ธ **Both matchers are capped, and a blank is a real answer.** A part can be
|
|
1065
|
+
displaced by about its own size and still be that part in the picture; past that,
|
|
1066
|
+
a match is another object, not this one moved. So `check` searches that far and no
|
|
1067
|
+
further, and reports **no match** rather than a number โ a 4 px ball cannot report
|
|
1068
|
+
the 47 px course as its drift. The bar rises with the distance being claimed: a
|
|
1069
|
+
peak sitting where you already drew the slot only has to confirm it, a peak
|
|
1070
|
+
claiming the part moved most of a radius has to be distinctive to be believed.
|
|
1071
|
+
|
|
1072
|
+
The `slots` column is how many of the slots you drew got an answer at all, and the
|
|
1073
|
+
summary line carries the same denominator. `N reference component(s) no slot
|
|
1074
|
+
reaches` means the reference frame contains something none of your slots overlaps:
|
|
1075
|
+
a part you have not authored, or one you have put somewhere else entirely.
|
|
1076
|
+
|
|
1077
|
+
### 9.3 What it still cannot see
|
|
1078
|
+
|
|
1079
|
+
- **Anything a frame does not contain.** Bone `length`, the setup `inherit` mode,
|
|
1080
|
+
a slot's name, a bone's parentage. Frames carry appearance, and these are not it.
|
|
1081
|
+
- **A difference smaller than the render scale.** At rung 3's 0.117 px per unit, a
|
|
1082
|
+
key 4 units out moves nothing. Author to the frames' precision and record that
|
|
1083
|
+
the rest was not checkable.
|
|
1084
|
+
- **Whether a mesh is deformed or merely posed.** Since #27 the rasteriser draws
|
|
1085
|
+
meshes โ weighted, deformed, both โ so a rung with meshes is measurable. What it
|
|
1086
|
+
still cannot tell you is *how* a silhouette got its shape: a hull moved by a
|
|
1087
|
+
bone chain and the same hull moved by deform keys render to the same pixels, and
|
|
1088
|
+
the frames cannot separate them. Choose on what the rig has to do next, not on
|
|
1089
|
+
what the frames appear to say.
|
|
1090
|
+
- **Which of two explanations is right.** A slot 3 px low every frame and a slot
|
|
1091
|
+
3 px low at one frame have the same drift and opposite causes. The table gives
|
|
1092
|
+
you the frame index; ยง8's rule still applies โ look for a second way to get the
|
|
1093
|
+
number before you author the key.
|
|
1094
|
+
- **What happens between two committed frames.** `ฮpx` compares adjacent frames and
|
|
1095
|
+
a set that ships stills has none, so a shot that is right at every committed frame
|
|
1096
|
+
and wrong between them reads clean. That is the same gap `--frames` on a
|
|
1097
|
+
contact-sheet set already has, and it is why the frame-count line is printed.
|
|
1098
|
+
|
|
1099
|
+
---
|
|
1100
|
+
|
|
1101
|
+
## 10. What the editor does by default
|
|
1102
|
+
|
|
1103
|
+
Every reference in this repository was made in the Spine editor by a person, and a
|
|
1104
|
+
rig authored here is measured against one. rigc's own defaults are deliberately
|
|
1105
|
+
*absent* rather than opinionated (R1: a field is emitted exactly when you declare
|
|
1106
|
+
it), so nothing in the compiler will push you toward the shape an editor rig has.
|
|
1107
|
+
This section is that push, and it comes from **Spine's public documentation only** โ
|
|
1108
|
+
what the editor does when nobody tells it otherwise, and what its user guide
|
|
1109
|
+
recommends.
|
|
1110
|
+
|
|
1111
|
+
**Nothing here is the answer to any shot.** These are defaults to adopt *unless the
|
|
1112
|
+
shot says otherwise*, and each is overridable by something you can see in the
|
|
1113
|
+
frames. Every line is marked with where it comes from:
|
|
1114
|
+
|
|
1115
|
+
- ๐ **stated** โ quoted or paraphrased from the page linked in the line.
|
|
1116
|
+
- ๐งฉ **inferred** โ this guide's reading of those pages. Spine does not say it.
|
|
1117
|
+
|
|
1118
|
+
### 10.1 Structure
|
|
1119
|
+
|
|
1120
|
+
๐ **One image, one attachment, one slot.** Dragging an image into the viewport makes
|
|
1121
|
+
the editor *"create a slot and a region attachment under the root bone for the
|
|
1122
|
+
image"*, and *"each part of the skeleton that will move independently needs to be a
|
|
1123
|
+
separate image file"* โ [Images](http://esotericsoftware.com/spine-images). A shared
|
|
1124
|
+
slot is opt-in on the PSD path too: the `[slot]` tag is what places layers into one
|
|
1125
|
+
โ [Import PSD](http://esotericsoftware.com/spine-import-psd). โ in rigc: one entry
|
|
1126
|
+
in `slots` per image, one placeholder per slot in the `default` skin (ยง3.3, ยง3.4).
|
|
1127
|
+
|
|
1128
|
+
๐ **A shared slot is for alternatives, not for economy.** *"Slots group attachments
|
|
1129
|
+
of the same type. For example, a weapon slot may have a knife, sword, axe, etc."*,
|
|
1130
|
+
and *"only one attachment (or none) can be visible at any given time"* โ
|
|
1131
|
+
[Slots](http://esotericsoftware.com/spine-slots). So two parts that are ever on
|
|
1132
|
+
screen together **cannot** share a slot; two that never coexist may.
|
|
1133
|
+
|
|
1134
|
+
๐งฉ **โ If nothing in the shot swaps between them, give them a slot each.** Folding
|
|
1135
|
+
parts into one slot with attachment keys to keep the slot list short is a decision
|
|
1136
|
+
the shot has to earn. It changes the emitted slots array, and ยง4.7's offsets are
|
|
1137
|
+
counted against that array.
|
|
1138
|
+
|
|
1139
|
+
๐ **The attachment name is the image name.** Spine finds an image by *"taking the
|
|
1140
|
+
path specified under the Images node and appending the attachment name"*, and *"if
|
|
1141
|
+
an attachment has a Path set, the path is used to find the image file instead of the
|
|
1142
|
+
attachment name"* โ [Images](http://esotericsoftware.com/spine-images). โ in rigc:
|
|
1143
|
+
keep the placeholder equal to the PNG's basename and no `path` is written (R5). A
|
|
1144
|
+
`path` in the emitted file means the two disagreed.
|
|
1145
|
+
|
|
1146
|
+
๐ **Housekeeping the format fixes for you.** The default skin *"always has the name
|
|
1147
|
+
`default`"* and *"bones are ordered so that the parent always comes before a child
|
|
1148
|
+
bone"* โ [JSON format](http://esotericsoftware.com/spine-json-format). ยง3.4.
|
|
1149
|
+
|
|
1150
|
+
### 10.2 Draw order
|
|
1151
|
+
|
|
1152
|
+
๐ **An overlap change is a draw-order key.** The draw order *"can be keyed"*, and
|
|
1153
|
+
slots *"decouple bones from the draw order, allowing attachments on the same bone to
|
|
1154
|
+
be drawn above and below an attachment on a different bone"* โ
|
|
1155
|
+
[Slots](http://esotericsoftware.com/spine-slots); its key button sits on the Draw
|
|
1156
|
+
Order node โ [Keys](http://esotericsoftware.com/spine-keys).
|
|
1157
|
+
|
|
1158
|
+
๐งฉ **โ There is no other way to say it.** The Keys page's list of keyable properties
|
|
1159
|
+
runs bone transforms, transform inheritance, slot attachment, slot colour, draw
|
|
1160
|
+
order, events, sequence, deform and the constraint families โ **a bone's parent and
|
|
1161
|
+
a slot's bone are not on it.** An editor rig therefore cannot express *"this passes
|
|
1162
|
+
in front now"* by re-parenting or by reassigning a slot's bone; it has exactly one
|
|
1163
|
+
expression, and it is the timeline of ยง4.7. If you are reaching for a structural
|
|
1164
|
+
change to fix an overlap, you have left the editor's vocabulary.
|
|
1165
|
+
|
|
1166
|
+
๐ **Offsets count from setup, and an empty key restores it.** Offsets are *"the
|
|
1167
|
+
number of draw order entries to shift the specified slot relative to its setup pose
|
|
1168
|
+
draw order index"*, and *"if `offsets` is omitted, the keyframe will set the draw
|
|
1169
|
+
order to the setup pose draw order"* โ
|
|
1170
|
+
[JSON format](http://esotericsoftware.com/spine-json-format). That is ยง4.7 exactly.
|
|
1171
|
+
|
|
1172
|
+
๐ **Hide with an attachment key, not with alpha.** *"Setting the alpha to zero to
|
|
1173
|
+
make an attachment invisible is not an efficient way to hide the attachment โฆ It is
|
|
1174
|
+
better to hide an attachment by setting a slot attachment key. To avoid an abrupt
|
|
1175
|
+
disappearance, the slot color can be used to fade to transparent before hiding"* โ
|
|
1176
|
+
[Slots](http://esotericsoftware.com/spine-slots). โ in rigc: an `attachment` track
|
|
1177
|
+
keyed to `null`, with an `rgba` track ahead of it when the part has to fade first.
|
|
1178
|
+
|
|
1179
|
+
### 10.3 Keys
|
|
1180
|
+
|
|
1181
|
+
๐ **Both axes on one key.** *"By default, each translate, scale, and shear key for a
|
|
1182
|
+
bone sets both X and Y. This is sufficient for many animations and reduces the
|
|
1183
|
+
number of timelines โฆ For animations that need it, X and Y can be keyed separately
|
|
1184
|
+
by checking the Separate checkbox"*; slot colour is the same โ *"each color key for
|
|
1185
|
+
a slot sets both color (RGB) and alpha (A)"* โ
|
|
1186
|
+
[Keys](http://esotericsoftware.com/spine-keys). โ in rigc: `translate` / `scale` /
|
|
1187
|
+
`shear` / `rgba` are the default forms. ยง4.4's single-axis timelines **are** that
|
|
1188
|
+
Separate checkbox โ for a bone whose axes need different times or different curves,
|
|
1189
|
+
not for one that merely happens to move on one axis.
|
|
1190
|
+
|
|
1191
|
+
๐ **Times are seconds; frames are a convenience.** *"Frames exist only for
|
|
1192
|
+
convenience"*, the timeline defaults to *"30 frames per second"*, and keys may sit
|
|
1193
|
+
between them โ *"a bone could have a translate key on frame 15, then another key on
|
|
1194
|
+
frame 15.01"* โ [Keys](http://esotericsoftware.com/spine-keys). This is why ยง4.5
|
|
1195
|
+
takes `t` in seconds and pins nothing to a grid.
|
|
1196
|
+
|
|
1197
|
+
๐ **The editor's habit is to key liberally, then delete the redundant ones.** Auto
|
|
1198
|
+
Key sets a key *"any time a change is made โฆ it is common to have auto key enabled
|
|
1199
|
+
all the time"*, and Clean Up *"deletes all unnecessary keys โฆ keying the same value
|
|
1200
|
+
multiple times in a row, keying the same values as the setup pose"*, because *"often
|
|
1201
|
+
it is convenient to set keys liberally when designing an animation, then use Clean
|
|
1202
|
+
Up afterward"* โ [Keys](http://esotericsoftware.com/spine-keys). โ a shipped export
|
|
1203
|
+
is dense, but it does not repeat a value.
|
|
1204
|
+
|
|
1205
|
+
๐ **Add a key when a curve cannot carry the shape.** *"If a curve is not smooth
|
|
1206
|
+
enough, it is easily remedied by adding another key"*, and the **Bounce** handle
|
|
1207
|
+
preset exists for *"changing directions abruptly, such as when a ball bounces"* โ
|
|
1208
|
+
[Graph](http://esotericsoftware.com/spine-graph).
|
|
1209
|
+
|
|
1210
|
+
๐งฉ **โ Key every change of direction, not only the extremes.** Two keys and a curve
|
|
1211
|
+
describe a transition; they cannot describe a path that turns. So an editor rig
|
|
1212
|
+
carries a key wherever the motion changes direction, wherever a hold begins and
|
|
1213
|
+
ends, and wherever one Bezier span could not hold the shape โ more keys than a
|
|
1214
|
+
minimal one-key-per-pose spec produces.
|
|
1215
|
+
|
|
1216
|
+
๐ซ **No public page gives a keys-per-second figure, and this guide does not invent
|
|
1217
|
+
one.** Reaching for a target density is guessing. The frames are the only thing that
|
|
1218
|
+
can say where the motion turns; ยง8 is how to read them.
|
|
1219
|
+
|
|
1220
|
+
### 10.4 Curves
|
|
1221
|
+
|
|
1222
|
+
๐ **Linear is what a *new* key gets, and it does not survive contact with a curve.**
|
|
1223
|
+
*"Normally new keys are assigned a linear curve type. However, if a key is placed
|
|
1224
|
+
between keys that are using Bezier or stepped, then the new key is assigned a Bezier
|
|
1225
|
+
or stepped curve type instead"* โ [Graph](http://esotericsoftware.com/spine-graph).
|
|
1226
|
+
The editor has also carried a **default curve type** setting since 4.1.13-beta, with
|
|
1227
|
+
a *Last chosen* mode added in 4.3 โ
|
|
1228
|
+
[Changelog](http://esotericsoftware.com/spine-changelog). โ *"the editor defaults to
|
|
1229
|
+
linear"* is true only of the first key of an untouched curve.
|
|
1230
|
+
|
|
1231
|
+
๐ **The guide's own advice is against constant speed.** *"Curves allow the animator
|
|
1232
|
+
to adjust the speed of a transition between keys. When all the parts of a skeleton
|
|
1233
|
+
are moving at a constant speed, the movement tends to be robotic and lifeless"* โ
|
|
1234
|
+
[Animating](http://esotericsoftware.com/spine-animating).
|
|
1235
|
+
|
|
1236
|
+
๐งฉ **โ Bezier is the default to adopt; linear is the exception you argue for.** Use
|
|
1237
|
+
linear where constant speed is the intent โ a machine, a slide, a continuous drift โ
|
|
1238
|
+
and stepped where a value must not tween at all. Anything that starts, stops,
|
|
1239
|
+
accelerates, settles or falls gets a curve.
|
|
1240
|
+
|
|
1241
|
+
๐ **Automatic handles first, adjust after.** *"The angle of the handles is adjusted
|
|
1242
|
+
automatically based on the values of the keys before and after the key โฆ Automatic
|
|
1243
|
+
handles often provide good results. It can be useful to first apply automatic
|
|
1244
|
+
handles, then adjust them manually only if necessary."* The named presets are
|
|
1245
|
+
**Flat**, **Bounce**, **Ease out** (*"the value changes more slowly near the key"*)
|
|
1246
|
+
and **Ease in** (*"the value changes more slowly near the next key"*) โ
|
|
1247
|
+
[Graph](http://esotericsoftware.com/spine-graph).
|
|
1248
|
+
|
|
1249
|
+
๐งฉ **โ That is what `easings` is for.** A handful of named shapes, reused by name
|
|
1250
|
+
across the file, is how an editor rig reads. Raw `curve` is R6's escape hatch โ one
|
|
1251
|
+
key needing a shape no other key has โ not the normal way to write a curve.
|
|
1252
|
+
|
|
1253
|
+
๐ **Handles are normalised, and that is the shape an `easings` entry takes.** For a
|
|
1254
|
+
Bezier key, *"the X axis is from 0 to 1 and represents the percent of time between
|
|
1255
|
+
the two keyframes. The Y axis is from 0 to 1 and represents the percent of the
|
|
1256
|
+
difference between the keyframe's values"* โ
|
|
1257
|
+
[JSON format](http://esotericsoftware.com/spine-json-format). Those four numbers are
|
|
1258
|
+
exactly an `easings` entry (ยง4.1), and rigc converts them per key into the absolute
|
|
1259
|
+
control points the emitted file holds. Writing them into a raw `curve` instead is
|
|
1260
|
+
the silent failure ยง4.1 warns about.
|
|
1261
|
+
|
|
1262
|
+
๐ **Some keys have no curve at all.** No line is drawn between keys when *"the type
|
|
1263
|
+
of key does not have a transition, such as slot attachment or event keys"* โ
|
|
1264
|
+
[Dopesheet](http://esotericsoftware.com/spine-dopesheet). This is why rigc refuses
|
|
1265
|
+
`ease` and `curve` on attachment keys (ยง4.4) and on draw-order keys (ยง4.7).
|
|
1266
|
+
|
|
1267
|
+
### 10.5 What the export leaves out
|
|
1268
|
+
|
|
1269
|
+
๐ **Nonessential data is off unless someone checked the box.** *"Data marked
|
|
1270
|
+
'nonessential' is only output when the Nonessential data export setting is checked"*
|
|
1271
|
+
โ [JSON format](http://esotericsoftware.com/spine-json-format); the setting adds
|
|
1272
|
+
*"additional data โฆ that is not usually needed at runtime"* โ
|
|
1273
|
+
[Export](http://esotericsoftware.com/spine-export). What that page marks
|
|
1274
|
+
nonessential: the skeleton's `fps`, `images` and `audio`; a mesh's and a linked
|
|
1275
|
+
mesh's `width` and `height`; a mesh's `edges`; and the editor colours of bounding
|
|
1276
|
+
box, path, point and clipping attachments. โ a mesh in an export made without that
|
|
1277
|
+
box carries **no** `width`/`height`. rigc's `image` supplies them from the PNG (R5),
|
|
1278
|
+
so you never write them by hand.
|
|
1279
|
+
|
|
1280
|
+
โ ๏ธ **A region's `width`/`height` are not on that list.** They are documented with no
|
|
1281
|
+
*"assume โฆ if omitted"* default โ the same fact R5 states from the parser's side:
|
|
1282
|
+
omit them in raw JSON and every UV collapses, in silence. Name an `image`.
|
|
1283
|
+
|
|
1284
|
+
โ ๏ธ **Do not imitate the exporter's omissions.** Spine's exporter drops fields equal
|
|
1285
|
+
to their default, which is why the format page is a long list of *"assume 0 if
|
|
1286
|
+
omitted"* โ and **rigc deliberately does the opposite** (R1, ยง2). Writing `x: 0` is
|
|
1287
|
+
legitimate here. The habit worth carrying over is not *omit defaults*, it is
|
|
1288
|
+
*declare only what the shot needs*.
|
|
1289
|
+
|
|
1290
|
+
### 10.6 What this section does not claim
|
|
1291
|
+
|
|
1292
|
+
Conventions that are visible in reference exports but that **no public Spine page
|
|
1293
|
+
states** are deliberately absent. A guide that asserted them would be handing you an
|
|
1294
|
+
answer read off the exports:
|
|
1295
|
+
|
|
1296
|
+
- any figure for keys per second, or for how key density scales with frame rate;
|
|
1297
|
+
- which curve type any particular example project or studio actually shipped;
|
|
1298
|
+
- whether a given export was made with Nonessential data checked;
|
|
1299
|
+
- how many bones, slots or timelines a rig of a given size ought to have;
|
|
1300
|
+
- whether a shipped rig prefers automatic Bezier handles or hand-placed ones.
|
|
1301
|
+
|
|
1302
|
+
If one of those turns out to matter, it belongs in the run's `log.md` as something
|
|
1303
|
+
the frames had to teach you โ not here.
|