spine-rigc 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -13
- package/cli.ts +313 -10
- package/docs/AUTHORING.md +174 -9
- package/docs/INGEST.md +1117 -0
- package/docs/MOTION.md +9 -0
- package/docs/SPEC_COVERAGE.md +9 -2
- package/package.json +2 -1
- package/src/atlas.ts +871 -0
- package/src/bonedist.ts +632 -0
- package/src/check.ts +5 -2
- package/src/compile.ts +359 -52
- package/src/diff.ts +269 -1
- package/src/render.ts +87 -2
- package/src/types.ts +42 -1
- package/src/validate.ts +45 -22
package/docs/INGEST.md
ADDED
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
# Working with a skeleton you did not author
|
|
2
|
+
|
|
3
|
+
**Read this when what you were handed is already a skeleton.** It is written for an
|
|
4
|
+
agent holding a `skeleton.json` — plus its `.atlas` and page images — that came out
|
|
5
|
+
of the Spine editor or another tool, and that has been asked to work *with* it:
|
|
6
|
+
understand it, answer a complaint rigc raised about it, re-express it as rigc specs,
|
|
7
|
+
normalise or re-pivot it, or extend it with motion it does not have.
|
|
8
|
+
|
|
9
|
+
[AUTHORING.md](AUTHORING.md) is the file formats, the failure map and the CLI — read
|
|
10
|
+
it first and keep it open; this page never restates a field it documents.
|
|
11
|
+
[MOTION.md](MOTION.md) is what goes *between* two poses. This page is the third
|
|
12
|
+
question neither of them answers: **what the toolchain will and will not do with
|
|
13
|
+
somebody else's file, and what the honest routes through it are.**
|
|
14
|
+
|
|
15
|
+
🔓 **Nothing in this repository's benchmark protocol applies to you.** No reading is
|
|
16
|
+
forbidden, no reference is sealed, no attempt is scored, and no rung is being
|
|
17
|
+
attempted. Those rules exist to keep one *measured experiment* honest; you are
|
|
18
|
+
working on somebody's own data. Where this page points at a stored transcription it
|
|
19
|
+
is pointing at **worked precedent you are meant to read**, not at a candidate you are
|
|
20
|
+
not allowed to see. AUTHORING's own exemption line says the same thing from the
|
|
21
|
+
authoring side.
|
|
22
|
+
|
|
23
|
+
🚨 **The two numbers this page produces are not grades, and they measure different
|
|
24
|
+
things.** `validate`'s red says *this file breaks a stated rule* — a fact about the
|
|
25
|
+
file, not about your work, and sometimes (§3.2) a fact about the rule. `diff`'s
|
|
26
|
+
ratios say *how much of a particular reference's structure your candidate
|
|
27
|
+
reproduces* — so deliberately extending or renaming a foreign skeleton **lowers
|
|
28
|
+
them**, by design (§4.2, §4.3). Neither one has a pass bar, and this page does not
|
|
29
|
+
invent one.
|
|
30
|
+
|
|
31
|
+
- Formats and the CLI reference: [README.md](../README.md)
|
|
32
|
+
- The rig spec, field by field: **AUTHORING §3**, and [`src/rig.ts`](../src/rig.ts)
|
|
33
|
+
- The motion spec, field by field: **AUTHORING §4**
|
|
34
|
+
- Named failures, and the file each one points at: **AUTHORING §5–§6**
|
|
35
|
+
- The coordinate contract, in one place: **AUTHORING §11.2** and
|
|
36
|
+
[`src/transform.ts`](../src/transform.ts)
|
|
37
|
+
- What the editor does when nobody tells it otherwise: **AUTHORING §10**
|
|
38
|
+
- What the format holds and what rigc covers, skeleton by skeleton:
|
|
39
|
+
[SPEC_COVERAGE.md](SPEC_COVERAGE.md)
|
|
40
|
+
- If you are the *person operating* an agent rather than the agent:
|
|
41
|
+
[PROMPTING.md](PROMPTING.md)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 0. What the toolchain can do with a file you were handed
|
|
46
|
+
|
|
47
|
+
Two facts decide everything below, and they pull in opposite directions:
|
|
48
|
+
|
|
49
|
+
1. **rigc reads compiled skeleton JSON in more places than you would guess.**
|
|
50
|
+
`validate`, `render`, `preview`, `vote`, `check` and `diff` all take a
|
|
51
|
+
`skeleton.json` path directly, and none of them needs a rig spec to do it.
|
|
52
|
+
2. **rigc cannot write one back.** There is no command that edits a skeleton, no
|
|
53
|
+
importer, and no route from skeleton JSON to specs. The only thing that produces a
|
|
54
|
+
skeleton is `build`, and `build`'s input is a rig spec plus a motion spec.
|
|
55
|
+
⇒ **Every route that ends in a changed file goes through transcription** (§2).
|
|
56
|
+
|
|
57
|
+
### 0.1 The table
|
|
58
|
+
|
|
59
|
+
Every row was executed against the fetched example corpus before it was written down.
|
|
60
|
+
`3-timing-and-spacing` and `spineboy` are the two skeletons this page uses; both carry
|
|
61
|
+
an upstream `license.txt` (Appendix, and [NOTICE.md](../NOTICE.md)).
|
|
62
|
+
|
|
63
|
+
| Command | Takes a foreign `skeleton.json`? | What it needs, and what it gives back |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| **`validate <skeleton.json>`** | ✅ **yes — this is its foreign-data form** | the `.json`, plus one `.atlas` beside it or named with `--atlas`. Runs the assertions and prints `PASS`/`FAIL`/`SKIP`/`PROF` per rule, naming the profile that judged it. §1.1 |
|
|
66
|
+
| **`render --candidate <skeleton.json>`** | ✅ **yes** | PNG frames plus a contact sheet, per animation. ⭐ **It does not gate** — it drew all seven frames of `spineboy-pro`'s `hoverboard` while `validate` was failing that same file, which is how the rule rather than the file was found to be wrong (§3.2) |
|
|
67
|
+
| **`preview --candidate <skeleton.json>`** | ✅ **yes** | one self-contained `.html` that plays it in the official Spine Web Player. Needs a network the first time it is opened ([NOTICE.md](../NOTICE.md)) |
|
|
68
|
+
| **`vote --candidate <a> --candidate <b>`** | ✅ **yes, on either side** | a ballot page. Pairing a foreign export against your own transcription is a legitimate ballot, and the panes carry no paths |
|
|
69
|
+
| **`check --candidate <skeleton.json> --frames <dir>`** | ✅ **yes** | ⭐ it reads **frames and never a reference skeleton**, so a foreign export enters this one *twice over*: as the candidate, or — via `render` — as the source of the frames. §1.4 |
|
|
70
|
+
| **`diff <candidate.json> <reference.json>`** | ✅ **yes, both sides** | 49 structural measures over bones, slots, attachments, constraints, animations and events. ⛔ **Blind to every coordinate** — §1.3 |
|
|
71
|
+
| **`pose --images <dir> --frame <png>`** | ⛔ **not the skeleton** | loose part PNGs and one picture. A packed atlas page is not loose parts, and pointing it at one produces a confident answer about nothing — §5 |
|
|
72
|
+
| **`explain --rig … --motion … --out …`** | ⛔ **no** | rig spec + motion spec. It explains **what you wrote**, which makes it a transcription instrument rather than a reading one — §1.5 |
|
|
73
|
+
| **`build --rig … --motion … --images …`** | ⛔ **no** | specs in, skeleton out. The only writer in the toolchain, and the reason §2 exists |
|
|
74
|
+
| **`build … --atlas-in <file.atlas>`** | ⛔ not the skeleton — **but yes, the foreign *atlas*** | resolves your parts against the regions of a pack somebody else made, joining on region name. The one place a foreign *file* becomes an input to `build`. It applies the page's `scale:`, so an imported size is the drawing's rather than the pack's — §2.3 |
|
|
75
|
+
| **`build … --pack`** | ⛔ **no** | puts your own loose parts onto shared pages instead of one page per part, losslessly. Not foreign input, but it is what makes a transcription's atlas comparable in shape to an export's — §2.3 |
|
|
76
|
+
| **`bench <rung> --candidate …`** | ✅ mechanically | it is the *benchmark's* instrument: it knows which corpus example is which rung and measures against that one. Not an ingest instrument, and named here only so nobody reaches for it. Use `diff` and `check` directly |
|
|
77
|
+
|
|
78
|
+
### 0.2 Two path shapes, and why the directory form is not yours
|
|
79
|
+
|
|
80
|
+
`validate`, `render`, `preview`, `check`, `vote` and `bench` all accept either a
|
|
81
|
+
**directory** or a **`.json` file**. The directory form resolves to `skeleton.json` +
|
|
82
|
+
`skeleton.atlas` — **rigc's own output names**. A foreign export directory is named
|
|
83
|
+
after whatever the editor called the project, so the directory form finds nothing:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
rigc validate examples/3-timing-and-spacing/export
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
rigc validate …/examples/3-timing-and-spacing/export/skeleton.json
|
|
91
|
+
.. atlas …/examples/3-timing-and-spacing/export/skeleton.atlas
|
|
92
|
+
…
|
|
93
|
+
ENOENT: no such file or directory, open '…/examples/3-timing-and-spacing/export/skeleton.json'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
⚠️ **That one comes back as a stack trace, exit 1, rather than a named refusal.** It
|
|
97
|
+
is the only complaint on this page that does — everything in §3.1 is a sentence. Do
|
|
98
|
+
not read the trace as *rigc cannot open this export*; read the two `..` lines above
|
|
99
|
+
it, which name the two files it went looking for. ⇒ **Point every command at the
|
|
100
|
+
`.json` itself.** Every command line on this page does.
|
|
101
|
+
|
|
102
|
+
The atlas then resolves by looking beside the skeleton, and that lookup refuses
|
|
103
|
+
rather than guesses:
|
|
104
|
+
|
|
105
|
+
| What is beside the `.json` | What happens |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| exactly one `.atlas` | it is used, and the report names it |
|
|
108
|
+
| no `.atlas` | ⛔ `no .atlas beside …; name one with --atlas <path>` |
|
|
109
|
+
| two or more | ⛔ refuses and lists them — see below |
|
|
110
|
+
| any of the above, with `--atlas <path>` | `--atlas` wins outright |
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rigc validate examples/spineboy/export/spineboy-pro.json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
rigc: 2 atlases beside …/examples/spineboy/export/spineboy-pro.json (spineboy-run.atlas,
|
|
118
|
+
spineboy.atlas); name the right one with --atlas <path> — guessing by filename is how
|
|
119
|
+
an attachment quietly resolves against the wrong page
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
⭐ **This refusal is worth understanding rather than working around, because the
|
|
123
|
+
obvious heuristic is wrong on this very file.** `spineboy-ess` shares a longer
|
|
124
|
+
filename prefix with `spineboy-run.atlas` than with the `spineboy.atlas` it actually
|
|
125
|
+
uses. Pick by prefix and every attachment resolves against a page that does not
|
|
126
|
+
contain it — and §3.2 shows what that looks like when you get it wrong, which is a
|
|
127
|
+
failure two rules downstream of the choice.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 1. Reading it
|
|
132
|
+
|
|
133
|
+
### 1.1 `validate` — what the file is, and what rigc objects to
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
rigc validate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
rigc validate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
141
|
+
.. atlas …/examples/3-timing-and-spacing/export/3-timing-and-spacing.atlas
|
|
142
|
+
.. profile spine — 7 renderer-policy and 7 archetype assertion(s) do not apply
|
|
143
|
+
PASS A07_ATLAS_TEXT_SHAPE
|
|
144
|
+
PASS A00_ROUNDTRIP_PARSE
|
|
145
|
+
PASS A16_SKELETON_VERSION_4_3
|
|
146
|
+
…
|
|
147
|
+
SKIP A31_DRAW_ORDER_OFFSETS_RESOLVE: no animation carries a drawOrder timeline
|
|
148
|
+
SKIP A32_EVENT_KEYS_RESOLVE: no animation carries an event timeline
|
|
149
|
+
SKIP A34_CONSTRAINT_TIMELINE_TARGETS: no animation carries a constraint timeline
|
|
150
|
+
SKIP A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: no animation carries a deform timeline
|
|
151
|
+
SKIP A33_VERTEX_ATTACHMENT_GEOMETRY: the skeleton carries no bounding box, clipping attachment or path
|
|
152
|
+
…
|
|
153
|
+
PROF A12_NO_DARK_COLOR: renderer rule, not in profile "spine"
|
|
154
|
+
PROF A21_MESH_RIM_PINNED: archetype rule, not in profile "spine"
|
|
155
|
+
…
|
|
156
|
+
rigc: green
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Read it as three separate statements, because they answer three different questions:
|
|
160
|
+
|
|
161
|
+
- **`PASS` / `FAIL`** — the rule ran, and this is its verdict.
|
|
162
|
+
- **`SKIP`** — the rule ran and had nothing to measure, and the line says what was
|
|
163
|
+
absent. A `SKIP` is never a pass, and on foreign data the `SKIP` list is also a
|
|
164
|
+
**free inventory of what the skeleton does not contain**. The five lines above tell
|
|
165
|
+
you, without your having opened the JSON, that this export has no draw-order
|
|
166
|
+
timeline, no event timeline, no constraint timeline, no deform timeline, and no
|
|
167
|
+
bounding box, clipping attachment or path.
|
|
168
|
+
- **`PROF`** — the rule was excluded by the profile before its body ran. §3.3.
|
|
169
|
+
|
|
170
|
+
⚠️ **A green here is a statement about validity and nothing else.** It does not say
|
|
171
|
+
the skeleton is the one you were meant to be given, that its animations are the ones
|
|
172
|
+
their names suggest, or that anything in it is where the art wants it. The gate cannot
|
|
173
|
+
see a wrong animation (AUTHORING §0), and it certainly cannot see a wrong *file*.
|
|
174
|
+
|
|
175
|
+
### 1.2 `render` and `preview` — look at it
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
rigc render --candidate examples/spineboy/export/spineboy-pro.json \
|
|
179
|
+
--atlas examples/spineboy/export/spineboy.atlas \
|
|
180
|
+
--animation walk --fps 8 --max 200 --out render/sb
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
rigc render
|
|
185
|
+
.. skeleton …/examples/spineboy/export/spineboy-pro.json
|
|
186
|
+
.. atlas …/examples/spineboy/export/spineboy.atlas
|
|
187
|
+
.. 200x186px at 8 fps, 1 set(s) -> …/render/sb
|
|
188
|
+
.. walk 9 frame(s), 1.000s + contact.png -> …/render/sb/walk@8fps
|
|
189
|
+
rigc: wrote …/render/sb/frames.json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Three properties of this command matter more for foreign data than for your own:
|
|
193
|
+
|
|
194
|
+
- ⭐ **It does not gate.** That same `spineboy-pro.json` fails two assertions under the
|
|
195
|
+
default profile (§3.2) and renders all nine `walk` frames anyway. ⇒ **A red
|
|
196
|
+
`validate` is not a reason to stop looking**, and looking is often what tells you
|
|
197
|
+
whether the red matters.
|
|
198
|
+
- **Omitting `--animation` renders every animation**, each into its own directory with
|
|
199
|
+
its own contact sheet. On a file you were handed, that is the cheapest complete
|
|
200
|
+
inventory there is — one image per animation, with spacing visible across the grid.
|
|
201
|
+
- **The frame size is fitted to the skeleton's own extent**, so `--max` is a cap on the
|
|
202
|
+
longest side and not a canvas. A skeleton whose world box is 24 units wide renders
|
|
203
|
+
24 px wide however large you set it; read the size on the `..` line before concluding
|
|
204
|
+
a render came out empty.
|
|
205
|
+
|
|
206
|
+
`preview` writes one HTML file that plays the same data in the official Spine Web
|
|
207
|
+
Player. On foreign data it is also the **interop proof**: if that page plays it, a
|
|
208
|
+
Spine runtime plays it, whatever rigc's own rasteriser or validator thinks.
|
|
209
|
+
|
|
210
|
+
### 1.3 `diff` — and the two things it cannot see
|
|
211
|
+
|
|
212
|
+
`diff` takes two compiled skeletons and reports 49 measures in eight groups. Both
|
|
213
|
+
sides may be foreign; the interesting pairing during ingest is **your transcription
|
|
214
|
+
against the export it came from**:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
rigc diff work/t3/skeleton.json \
|
|
218
|
+
examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
rigc diff
|
|
223
|
+
candidate …/work/t3/skeleton.json
|
|
224
|
+
reference …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
225
|
+
.. bones=3/3 slots=2/2 skins=1/1 attachments=2/2 constraints=0/0 animations=2/2 events=0/0 (candidate/reference)
|
|
226
|
+
|
|
227
|
+
bones mean 1.000 over 8 measures
|
|
228
|
+
1.000 count 3/3 how many bones
|
|
229
|
+
1.000 names 3/3 the bone names themselves
|
|
230
|
+
1.000 parent_by_name 3/3 each bone hangs off the same parent
|
|
231
|
+
1.000 order 3/3 the bones are declared in the same order
|
|
232
|
+
1.000 length_present 3/3 a setup `length` is present or absent alike
|
|
233
|
+
1.000 inherit_present 3/3 a setup `inherit` is present or absent alike
|
|
234
|
+
1.000 depth_histogram 3/3 NAME-AGNOSTIC: as many bones at each depth
|
|
235
|
+
1.000 degree_sequence 3/3 NAME-AGNOSTIC: as many bones with each child count
|
|
236
|
+
|
|
237
|
+
bones (name-agnostic) mean 1.000 over 5 measures — the same two skeletons compared with names thrown away
|
|
238
|
+
…
|
|
239
|
+
animations mean 1.000 over 9 measures
|
|
240
|
+
1.000 count 2/2 how many animations
|
|
241
|
+
1.000 names 2/2 the animation names
|
|
242
|
+
1.000 duration 2/2 each animation runs as long (last key time, within one frame)
|
|
243
|
+
1.000 timeline_kinds 8/8 the same timelines exist
|
|
244
|
+
1.000 key_counts 69/69 those timelines carry as many keys
|
|
245
|
+
1.000 curve_kinds 69/69 as many linear / stepped / bezier keys
|
|
246
|
+
…
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Each pair is **matched / total**, where the total is the larger of the two sides. So a
|
|
250
|
+
count of `2/3` means one side has three of something and only two were matched — and
|
|
251
|
+
it does not say *which* side has three. The `..` line above is where you read that.
|
|
252
|
+
|
|
253
|
+
⛔ **`diff` is blind to every coordinate.** No measure reads a bone's
|
|
254
|
+
`x`/`y`/`rotation`, an attachment's offset, or a key's value — only *presence*,
|
|
255
|
+
*names*, *counts*, *order* and *kinds*. §4.1 moves a pivot 236.5 units and every one
|
|
256
|
+
of the 49 measures still reads **1.000**. ⇒ Never take a green `diff` as evidence that
|
|
257
|
+
a geometric edit did not land, and never take it as evidence that one did.
|
|
258
|
+
|
|
259
|
+
⛔ **And its ratios are not a score.** [`src/diff.ts`](../src/diff.ts) says so in the
|
|
260
|
+
type itself (*"Unweighted mean of the measures below. NOT a quality score"*), and the
|
|
261
|
+
report repeats it at the foot. It measures *agreement with a particular reference*,
|
|
262
|
+
which is the right question during transcription and the wrong question the moment the
|
|
263
|
+
task is to change something (§4.2, §4.3).
|
|
264
|
+
|
|
265
|
+
⭐ **The name-agnostic groups are the ones to reach for when names are what differs.**
|
|
266
|
+
`bones` and `slots` each get a second pass with names thrown away — depth histogram,
|
|
267
|
+
degree sequence, shape histogram, declaration order of shapes, and for slots the
|
|
268
|
+
attachment types and bone-binding shapes by draw-order position. That is how you tell
|
|
269
|
+
*"the same rig with a different vocabulary"* from *"a different rig"*, and §4.2 is the
|
|
270
|
+
recipe built on it.
|
|
271
|
+
|
|
272
|
+
### 1.4 `check` — the instrument that does see coordinates
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
rigc check --candidate <a compiled skeleton> --frames <a rendered frame set>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`check` never opens a reference skeleton. It renders the candidate onto the frames'
|
|
279
|
+
own pixel grid, fits it there by its own drawn pixels, and compares. For ingest that
|
|
280
|
+
gives you a loop nothing else in the toolchain provides, in two steps:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# 1. turn the foreign export into a reference frame set
|
|
284
|
+
rigc render --candidate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json \
|
|
285
|
+
--fps 12 --max 256 --out ref3
|
|
286
|
+
|
|
287
|
+
# 2. measure anything at all against it
|
|
288
|
+
rigc check --candidate work/t3 --frames ref3 \
|
|
289
|
+
--texture-from examples/3-timing-and-spacing/export/3-timing-and-spacing.atlas
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
📐 **Establish the positive control first.** Point `check` at the same export the
|
|
293
|
+
frames came from, and it has to read zero. It does:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
rigc check --candidate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json \
|
|
297
|
+
--frames ref3/light
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
rigc check
|
|
302
|
+
candidate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
303
|
+
framed to 256x116px 0.117628 px/unit world x[-573.3 .. 1603.0] y[-81.2 .. 908.9] (frames.json's own box — the candidate measured into it)
|
|
304
|
+
⤷ fit x1.000000 offset +0.00, +0.00 px rms 0.00 px over 84 edge(s) union residual +0.00 x +0.00 px aspect +0.00% (declared, 1 pass(es), settled)
|
|
305
|
+
declared frames.json's own box: TAKEN, coincident — a fit there asks for 0.00 px, under the 1 px that separates a candidate in the frames' coordinates from one in its own, over 21 frame(s).
|
|
306
|
+
|
|
307
|
+
── light — candidate animation "light", 12 fps ──
|
|
308
|
+
MAE mean 0.00 worst 0.00 at f00-1 (0..255 over the union alpha; over the whole frame, mean 0.00)
|
|
309
|
+
slot drift worst 0.5 px "pendulum" at f0003
|
|
310
|
+
per-frame all 20 adjacent pair(s) change by as much as the reference's own frames do
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Two things to take from the control, both of which you need before reading any real
|
|
314
|
+
number: the **framing** resolved to the frames' own box at 0.00 px, so the MAE is a
|
|
315
|
+
comparison of pictures rather than of framings; and **slot drift still reads 0.5 px at
|
|
316
|
+
MAE 0.00**, which is that instrument's own floor rather than a difference.
|
|
317
|
+
|
|
318
|
+
⚠️ **`--texture-from` is not optional on ingest work, and the reason is structural.**
|
|
319
|
+
rigc's default atlas is **one region per page at the art's own resolution**; the editor
|
|
320
|
+
packs many regions onto one page, often at a reduced `scale:`. Two builds of
|
|
321
|
+
geometrically identical data therefore sample differently-scaled texels in every frame,
|
|
322
|
+
and that difference is a constant no key can move. `check` says so unprompted, and
|
|
323
|
+
`--texture-from <the atlas the frames were rendered through>` measures it: same
|
|
324
|
+
geometry, swapped texels. §2.3 is what the resulting number means, and which atlas your
|
|
325
|
+
build should be using in the first place.
|
|
326
|
+
|
|
327
|
+
### 1.5 `explain` — for the specs you write, not the file you were given
|
|
328
|
+
|
|
329
|
+
📌 **`explain` does not read a compiled skeleton, and it is worth knowing that up
|
|
330
|
+
front** so you do not go looking for a reading tool that is not there:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
rigc explain examples/spineboy/export/spineboy-pro.json
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
rigc: give either --cut <name> --cuts <cuts.json>, or --rig/--motion/--out
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
It takes the same arguments as `build` minus `--profile`, prints the resolved account
|
|
341
|
+
of **your** two spec files, and never gates. Which makes it a §2 instrument rather
|
|
342
|
+
than a §1 one — the thing you run to compare what you transcribed against the export
|
|
343
|
+
you transcribed it from, by eye:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
rigc explain --rig bench/transcriptions/3-timing-and-spacing/3-timing-and-spacing-ess.rig.json \
|
|
347
|
+
--motion bench/transcriptions/3-timing-and-spacing/3-timing-and-spacing-ess.motion.json \
|
|
348
|
+
--out work/x3
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
stage 945.1005 x 815.4317 (spine 4.3.13)
|
|
353
|
+
|
|
354
|
+
bones (spine world: y up)
|
|
355
|
+
root parent=- x=0 y=0
|
|
356
|
+
square parent=root x=380.7311 y=78.8596
|
|
357
|
+
bone parent=root x=204.753 y=708.0989 rotation=180
|
|
358
|
+
|
|
359
|
+
slots (array order IS the draw order)
|
|
360
|
+
pendulum bone=bone setup=pendulum color=ffffffff attachments=[pendulum]
|
|
361
|
+
square bone=square setup=square color=ffffffff attachments=[square]
|
|
362
|
+
|
|
363
|
+
animations
|
|
364
|
+
heavy declared=5.333333s loop=true
|
|
365
|
+
bone.rotate 20 key(s)
|
|
366
|
+
t=0 value=0 bezier[4]
|
|
367
|
+
t=0.233333 value=3.31738 bezier[4]
|
|
368
|
+
…
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
⚠️ **`--out` is required and nothing is written to it.** The flag is shared with
|
|
372
|
+
`build`'s parser; `explain` prints and exits. Passing a directory that does not exist
|
|
373
|
+
is fine — it is not created.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 2. Transcription — the route that makes a foreign skeleton yours
|
|
378
|
+
|
|
379
|
+
Everything in §1 reads. To **change** anything you need specs, and getting specs out
|
|
380
|
+
of a skeleton is a job rigc does not do for you: the numbers come out of the JSON by
|
|
381
|
+
hand, into a rig spec and a motion spec, and `build` emits a new skeleton from those.
|
|
382
|
+
|
|
383
|
+
⚠️ **Say this to the user before starting, because it is the part that surprises.**
|
|
384
|
+
Transcription is not a conversion step you run; it *is* the work. What you get for it
|
|
385
|
+
is that the file becomes editable by declaration — after transcription a pivot move is
|
|
386
|
+
two numbers in a spec (§4.1) and a new animation is an added block (§4.3), where
|
|
387
|
+
before it was a hand-edit of emitted JSON with nothing checking it.
|
|
388
|
+
|
|
389
|
+
### 2.1 The workflow
|
|
390
|
+
|
|
391
|
+
1. **Read the skeleton first, with `validate` and `render`.** The `SKIP` list is your
|
|
392
|
+
feature inventory (§1.1); the contact sheets are what the animations actually do.
|
|
393
|
+
Knowing there is no deform timeline before you start is worth more than discovering
|
|
394
|
+
it in the eleventh hour of transcribing one.
|
|
395
|
+
2. **Get the loose art, at the size the export declares.** rigc measures PNGs rather
|
|
396
|
+
than trusting a size you typed (AUTHORING R5), so the art has to *be* the right
|
|
397
|
+
size. Take the target from the export's own attachments —
|
|
398
|
+
`3-timing-and-spacing` declares `"width": 745, "height": 212` for `pendulum`, and
|
|
399
|
+
the loose `pendulum.png` beside it is exactly 745×212. ⚠️ Do **not** take it from
|
|
400
|
+
the atlas region bounds: that page carries `scale: 0.5`, so `pendulum`'s bounds read
|
|
401
|
+
`373, 106`. Two numbers for one part, and the attachment's is the one in world
|
|
402
|
+
units. `--atlas-in` now does that division for you (§2.3), but it can only land
|
|
403
|
+
within the pack's own rounding — by hand, off the attachment, it is exact.
|
|
404
|
+
3. **Transcribe the rig spec: header, bones, slots, skins.** Bones parents-first; the
|
|
405
|
+
`slots` array *is* the draw order (AUTHORING R4), so its order is data you are
|
|
406
|
+
copying and not a detail. Leave `invariants` out entirely — it describes rigc's own
|
|
407
|
+
formations, and an absent field makes an archetype assertion `SKIP`, never pass
|
|
408
|
+
(AUTHORING §3.7).
|
|
409
|
+
4. **`explain`, then `build`.** `explain` first, because it prints what you wrote in a
|
|
410
|
+
shape you can compare against the export by eye (§1.5) and it never gates. Then
|
|
411
|
+
`build` under `--profile spine`.
|
|
412
|
+
5. **Transcribe the motion spec, one animation at a time**, and `build` after each.
|
|
413
|
+
6. **Close it with `diff` and `check`.** `diff` for structure; `check` against frames
|
|
414
|
+
rendered from the export for geometry; `--texture-from` to attribute the floor.
|
|
415
|
+
|
|
416
|
+
### 2.2 One feature family at a time
|
|
417
|
+
|
|
418
|
+
📌 **Transcribe by *kind*, not by animation.** All the bones, then all the slots, then
|
|
419
|
+
all the attachments, then one timeline kind across every animation. Two reasons, and
|
|
420
|
+
the second is the one that costs a day:
|
|
421
|
+
|
|
422
|
+
- A whole animation touches every feature the format has, so *"animation 1 of 6 done"*
|
|
423
|
+
means you have hit every unsolved problem at once and solved none of them cleanly.
|
|
424
|
+
- **`build` is all-or-nothing and emits only after green** (AUTHORING §0). A partial
|
|
425
|
+
transcription of one kind still builds; a half-transcribed animation may not build at
|
|
426
|
+
all, and then you are debugging your own incomplete work rather than the format.
|
|
427
|
+
|
|
428
|
+
⚠️ **When a kind turns out not to be expressible, stop and say so — that is a finding,
|
|
429
|
+
not a blocker to route around.** [SPEC_COVERAGE.md](SPEC_COVERAGE.md) is the
|
|
430
|
+
per-skeleton survey of exactly this, and it records both directions honestly: rung 6's
|
|
431
|
+
row for weighted meshes reads *"⚠️ **This was wrong.**"* over a struck-out prediction
|
|
432
|
+
that they were inexpressible. ⇒ Check the survey for your feature before concluding
|
|
433
|
+
either way, and if it is genuinely absent, the shape of the answer is *"this export
|
|
434
|
+
uses X, which the motion spec cannot say"* with a pointer — not a silent
|
|
435
|
+
approximation.
|
|
436
|
+
|
|
437
|
+
📎 **An editor export's curves usually need the raw `curve` escape hatch.** A named
|
|
438
|
+
easing is one curve reused; an export carries a different bezier per key per channel,
|
|
439
|
+
which no name can say. The motion spec's raw form takes absolute `(time, value)`
|
|
440
|
+
control points verbatim (AUTHORING §4.5). Named easings stay the right default for
|
|
441
|
+
motion you are *authoring* — this is the one case the escape hatch exists for, and the
|
|
442
|
+
3-timing transcription's own `note` says so.
|
|
443
|
+
|
|
444
|
+
### 2.3 What "byte-identical" can and cannot mean
|
|
445
|
+
|
|
446
|
+
State the ambition in the right units, because three different things get called
|
|
447
|
+
"identical" and only two are reachable.
|
|
448
|
+
|
|
449
|
+
| Ambition | Reachable? | What it costs, and what it proves |
|
|
450
|
+
| --- | --- | --- |
|
|
451
|
+
| **Structural agreement** — same bones, slots, attachments, timelines, key counts, curve kinds | ✅ yes, and `diff` measures it | the 3-timing transcription reads **1.000 on all 49 measures**. Aim here first |
|
|
452
|
+
| **Geometric agreement** — the same drawn pixels, allowing for the atlas | ✅ yes, and `check` measures it | see below |
|
|
453
|
+
| **Byte-identical JSON** | ⛔ **no, and not because of the geometry** | rigc writes defaults explicitly where the editor omits them, and the editor writes bookkeeping rigc has no field for. SPEC_COVERAGE records the count on rung 6: a field-by-field comparison against the reference export leaves **49 differences, every one benign** — 39 explicit defaults, 3 editor bookkeeping keys, 1 runtime version string, and 6 bone `icon` values, which was the only thing the rig spec could not say at all |
|
|
454
|
+
|
|
455
|
+
The geometric row needs a real number, because a naive reading of `check` makes an
|
|
456
|
+
exact transcription look wrong. Here is the 3-timing transcription against frames
|
|
457
|
+
rendered from the export it was transcribed from:
|
|
458
|
+
|
|
459
|
+
```
|
|
460
|
+
── heavy — candidate animation "heavy", 12 fps ──
|
|
461
|
+
declared frames.json's own box: TAKEN, coincident — a fit there asks for 0.04 px, under the 1 px that separates a candidate in the frames' coordinates from one in its own, over 65 frame(s).
|
|
462
|
+
MAE mean 6.42 worst 7.13 at f0064 (0..255 over the union alpha; over the whole frame, mean 0.27)
|
|
463
|
+
⭐ texture floor 6.42 above it 0.00 (over the reference's own pixels, 6.42 and 0.00)
|
|
464
|
+
slot drift worst 0.7 px "pendulum" at f0017
|
|
465
|
+
per-frame all 64 adjacent pair(s) change by as much as the reference's own frames do
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
⭐ **`above it 0.00` is the whole result.** The MAE is 6.42 and **100 % of it is
|
|
469
|
+
texture**: the same geometry sampled through the reference's own atlas reads zero. The
|
|
470
|
+
transcription is geometrically exact, and the 6.42 is one-region-per-page at full
|
|
471
|
+
resolution meeting a 512×128 page declared at `scale: 0.5`. ⇒ **On ingest work, read
|
|
472
|
+
`above it` before you read the MAE.** Without `--texture-from` there is no way to tell
|
|
473
|
+
6.42-that-is-all-texture from 6.42-that-is-all-rig, and the report warns you of exactly
|
|
474
|
+
that rather than leaving you to find out.
|
|
475
|
+
|
|
476
|
+
**Which atlas your build should use, then.** `build` has three atlas routes, and the
|
|
477
|
+
choice is an ingest decision rather than a detail. All three rows below are the same
|
|
478
|
+
specs, `--frames ref3/light`, against frames rendered from the export — one set, so
|
|
479
|
+
the figures compare:
|
|
480
|
+
|
|
481
|
+
| Route | What the emitted attachments say | `check --frames ref3/light` |
|
|
482
|
+
| --- | --- | --- |
|
|
483
|
+
| **default** — one region per page, pointing at the loose PNGs | `pendulum 745x212`, `square 159x159` | `in units … x1.0001`; MAE **6.36**, texture floor 6.36, **above it 0.00** |
|
|
484
|
+
| **`--pack`** — the same loose parts onto shared pages | `pendulum 745x212`, `square 159x159` | `in units … x1.0001`; MAE **6.36**, texture floor 6.36, **above it 0.00** |
|
|
485
|
+
| **`--atlas-in <the export's own atlas>`** | `pendulum 746x212`, `square 160x160` | `in units candidate 1053.5 x 808.2 reference 1053.5 x 808.7 x0.9997`; MAE **2.03**, texture floor 0.00, **above it 2.03** |
|
|
486
|
+
|
|
487
|
+
📌 **The first two are exact and indistinguishable**, and `--pack` is the one to reach
|
|
488
|
+
for when the deliverable is meant to look like an export: MaxRects onto shared pages,
|
|
489
|
+
byte-for-byte region copies, nothing resampled or rotated (AUTHORING §0.1). It does not
|
|
490
|
+
close the 6.36 — nothing that samples full-resolution texels can, against frames drawn
|
|
491
|
+
from a half-resolution pack — so the floor stays, `--texture-from` stays the way to
|
|
492
|
+
attribute it, and *packing does not change what the figure means.*
|
|
493
|
+
|
|
494
|
+
⭐ **The third row is the mirror image of the first two, and reading it wrong is the
|
|
495
|
+
easy mistake.** Its MAE is the *lowest* of the three because it draws through the
|
|
496
|
+
reference's own texels — floor 0.00 — so what is left is geometry, and 2.03 of geometry
|
|
497
|
+
is the ONE PIXEL the pack cannot give back. `--atlas-in` divides a region's size by the
|
|
498
|
+
page's `scale:` (nine of the ten corpus atlases declare one — eight at 0.5, one at 0.4,
|
|
499
|
+
every file but `spineboy-run.atlas`), and the packer wrote `round(drawing × scale)`, so
|
|
500
|
+
a 373-texel region at `scale: 0.5` is consistent with both a 745- and a 746-pixel
|
|
501
|
+
drawing. rigc states 746, the export says 745, and putting the pixel back by hand takes
|
|
502
|
+
the same build to `x1.0000` / MAE **0.00** — which is how the residual is known to be
|
|
503
|
+
the rounding and nothing else.
|
|
504
|
+
|
|
505
|
+
⇒ **The routes now differ in what their MAE is MADE OF rather than in whether they are
|
|
506
|
+
right.** Loose art or `--pack` gives exact geometry through coarser texels; `--atlas-in`
|
|
507
|
+
gives the reference's texels through geometry good to half a texel. Read `above it`
|
|
508
|
+
before the MAE either way, and read the `in units` line first — it is the line that
|
|
509
|
+
catches a whole-figure scale error, and it is the only one that does.
|
|
510
|
+
|
|
511
|
+
> 🕰️ **This row used to read `pendulum 373x106`, `square 80x80`, `x0.8092`, MAE
|
|
512
|
+
> 124.97 — the pack's texel counts taken as world sizes, so every attachment came out
|
|
513
|
+
> at half size, green, with nothing in the report saying so.** Found while writing this
|
|
514
|
+
> page and fixed as [issue #267](https://github.com/firejune/rigc/issues/267). The
|
|
515
|
+
> control that isolated it is now a selftest: import a pack with **no** `scale:` line
|
|
516
|
+
> (rigc's own `--pack` output writes none) and the skeleton is byte-identical to the
|
|
517
|
+
> loose build.
|
|
518
|
+
|
|
519
|
+
### 2.4 The worked precedent, and what to take from it
|
|
520
|
+
|
|
521
|
+
Three transcriptions of official Spine exports live in this repository under
|
|
522
|
+
[`bench/transcriptions/`](https://github.com/firejune/rigc/tree/main/bench/transcriptions/)
|
|
523
|
+
— `3-timing-and-spacing` (3 bones, 2 slots, 2 animations, 69 keys, every curve raw),
|
|
524
|
+
`6-arcs` (weighted meshes, mesh `edges`, four 4.3 transform constraints) and
|
|
525
|
+
`spineboy` in both `ess` and `pro`. They are **worked examples you are meant to read**,
|
|
526
|
+
and the rig spec's own `note` field is the thing to read first:
|
|
527
|
+
|
|
528
|
+
> *"Mechanical transcription of Spine's official 3-timing-and-spacing `ess` export …
|
|
529
|
+
> written to prove that a rig spec can express a foreign skeleton at all. It is NOT an
|
|
530
|
+
> authored rig: the numbers were copied out of the reference, so it says nothing about
|
|
531
|
+
> whether an agent could produce them."*
|
|
532
|
+
|
|
533
|
+
⭐ **Copy that habit, not just the technique.** A transcription's `note` should say what
|
|
534
|
+
it is, what it is not, and where its numbers came from — because the file otherwise
|
|
535
|
+
looks exactly like an authored rig and will be read as one by whoever opens it next.
|
|
536
|
+
The `images` line in those specs points out of the repository into the gitignored
|
|
537
|
+
`examples/` directory for the same reason: the art is fetched, not redistributed
|
|
538
|
+
([NOTICE.md](../NOTICE.md)).
|
|
539
|
+
|
|
540
|
+
⚠️ `bench/` does not ship in the npm package, so from an installed copy those files are
|
|
541
|
+
the link above rather than something on disk. Nor does `scripts/fetch-examples.sh` —
|
|
542
|
+
the example corpus is a repository-checkout facility, and every command line on this
|
|
543
|
+
page was run from one.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## 3. Complaints, and what each one means
|
|
548
|
+
|
|
549
|
+
Foreign data meets rigc's refusals in two waves: argument handling, before any rule
|
|
550
|
+
runs, and then the assertions.
|
|
551
|
+
|
|
552
|
+
### 3.1 Before the assertions
|
|
553
|
+
|
|
554
|
+
These three exit 2 with one sentence and no report. Together with §0.2's stack trace
|
|
555
|
+
they are the whole set an ingest task realistically meets.
|
|
556
|
+
|
|
557
|
+
| Complaint | What it means | What to do |
|
|
558
|
+
| --- | --- | --- |
|
|
559
|
+
| `N atlases beside <file> (…); name the right one with --atlas <path>` | the export directory holds more than one atlas and rigc will not guess | look at which regions each atlas declares, and name the one that covers the skeleton's attachments. §0.2 says why the filename heuristic is wrong here |
|
|
560
|
+
| `no .atlas beside <file>; name one with --atlas <path>` | you were handed the skeleton without its atlas, or copied one file out of a directory | get the atlas. Nothing downstream works without it — the attachments resolve through it |
|
|
561
|
+
| `<file> is neither a directory nor a .json skeleton` | the path is a `.skel`, a `.spine`, or anything else | §5 — rigc reads JSON only |
|
|
562
|
+
| *(exit 1, a stack trace ending in `ENOENT: … /skeleton.json`)* | you passed a directory and it is not a rigc output directory | point at the `.json` itself. §0.2 |
|
|
563
|
+
|
|
564
|
+
### 3.2 Assertions a real export fails
|
|
565
|
+
|
|
566
|
+
📊 **All twelve skeletons in the fetched corpus come back green** under the default
|
|
567
|
+
profile with the right atlas named. That is the baseline, and it is the honest headline:
|
|
568
|
+
**a correct editor export passes.** Getting there took one rule fixed, and the two
|
|
569
|
+
sections below are worth reading in full because the failures they describe mean
|
|
570
|
+
opposite things — the first is still reachable, and the second was the rule's fault.
|
|
571
|
+
|
|
572
|
+
**`A00_ROUNDTRIP_PARSE` — the atlas does not cover the skeleton.**
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
rigc validate examples/spineboy/export/spineboy-ess.json \
|
|
576
|
+
--atlas examples/spineboy/export/spineboy-run.atlas
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
```
|
|
580
|
+
FAIL A00_ROUNDTRIP_PARSE: threw: Region not found in atlas: eye-indifferent (attachment: eye-indifferent)
|
|
581
|
+
rigc: 1 assertion(s) failed
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**What it means:** the atlas you named is a real atlas and a valid one — it is just not
|
|
585
|
+
this skeleton's. `spineboy-run.atlas` packs only what the `run` animation needs. ⇒
|
|
586
|
+
**Read this as an atlas-choice failure, not as a broken skeleton.** It is the
|
|
587
|
+
downstream shape of guessing at §0.2's refusal, and it is precise about the cost: one
|
|
588
|
+
named attachment, so you can tell "wrong atlas" from "the export is missing a region"
|
|
589
|
+
by whether the missing names are a *coherent subset*. Fix by naming the right atlas —
|
|
590
|
+
`spineboy-ess.json` is green against `spineboy.atlas`.
|
|
591
|
+
|
|
592
|
+
**`A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` — and this one was the rule's fault.**
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
rigc validate examples/spineboy/export/spineboy-pro.json \
|
|
596
|
+
--atlas examples/spineboy/export/spineboy.atlas
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
PASS A00_ROUNDTRIP_PARSE
|
|
601
|
+
PASS A10_NO_NAN_AFTER_STEPPING
|
|
602
|
+
…
|
|
603
|
+
PASS A35_DEFORM_KEYS_FIT_THE_ATTACHMENT
|
|
604
|
+
rigc: green
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Why it is worth a section anyway.** That line used to be two `FAIL`s:
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
FAIL A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: … key 1: offset 1 is odd, so the run's x values land on y slots and back again
|
|
611
|
+
FAIL A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: … key 1: the run holds 147 numbers and the deform array is x, y pairs
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
and nothing was wrong with the data. `hoverboard-board` is an unweighted mesh with 148
|
|
615
|
+
floats; the key carries `offset: 1` and 147 values, covering `1..148` — the whole array
|
|
616
|
+
minus a leading zero the editor trimmed. A trim can land on a y component, so an odd
|
|
617
|
+
offset is what a trimmed run looks like, and Spine's own parser copies the run in at the
|
|
618
|
+
raw index with no alignment requirement anywhere. The proof was in the same report:
|
|
619
|
+
**`A00` and `A10` both PASSed on that file**, and `render` drew the `hoverboard`
|
|
620
|
+
animation.
|
|
621
|
+
|
|
622
|
+
⭐ **The lesson survives the fix, and it is the reason to read this.** A validity rule
|
|
623
|
+
stricter than the runtime does not look like a bug — it looks like a finding about
|
|
624
|
+
somebody's file, and the honest reading of that message (*"your x values land on y
|
|
625
|
+
slots"*) sends an agent to change correct data. Fixed as
|
|
626
|
+
[issue #262](https://github.com/firejune/rigc/issues/262): the two parity clauses are
|
|
627
|
+
gone, and the remaining A35 clauses — the run fitting inside the deform array, finite
|
|
628
|
+
values, a non-empty key array, the attachment existing in the skin — are correct and
|
|
629
|
+
catch real breakage. The over-long run in particular is still refused, and still the
|
|
630
|
+
quietest defect the format has.
|
|
631
|
+
|
|
632
|
+
🚨 **The general lesson matters more than the specific bug.** A `FAIL` on foreign data
|
|
633
|
+
has three possible meanings and the message alone does not separate them:
|
|
634
|
+
|
|
635
|
+
1. **the data is broken** — fix the data;
|
|
636
|
+
2. **the input was wrong** — wrong atlas, missing page, truncated file (§3.1, and
|
|
637
|
+
`A00` above);
|
|
638
|
+
3. **the rule is stricter than the runtime** — fix the rule, or file it.
|
|
639
|
+
|
|
640
|
+
⇒ Before changing anybody's export because rigc objected, check case 3: does the file
|
|
641
|
+
**parse** (`A00`), **step without NaN** (`A10`), and **render**? If all three, the
|
|
642
|
+
runtime is content and the burden is on the rule. Reporting that is a better answer
|
|
643
|
+
than a quietly edited export.
|
|
644
|
+
|
|
645
|
+
### 3.3 Profile choice, and the fourteen rules that will not fire
|
|
646
|
+
|
|
647
|
+
`--profile spine` is the default and answers *"is this valid Spine 4.3 that any
|
|
648
|
+
runtime plays correctly?"* — and it is the right profile for foreign data, because the
|
|
649
|
+
other one is this project's own renderer and archetype policy.
|
|
650
|
+
|
|
651
|
+
**Fourteen assertions do not run under `spine`, and they come back `PROF`, not
|
|
652
|
+
`SKIP`:**
|
|
653
|
+
|
|
654
|
+
| Excluded as | Rules |
|
|
655
|
+
| --- | --- |
|
|
656
|
+
| **renderer policy** (7) | `A11_NO_CLIPPING_ATTACHMENTS`, `A12_NO_DARK_COLOR`, `A13_MESH_BUDGET`, `A14_NO_FULL_FRAME_MESH`, `A15_IDLE_NO_MESH_BONE_KEYS`, `A19_OVERLAY_PNGS_HAVE_ALPHA`, `A27_REGION_NAME_MATCHES_PAGE_FILENAME` |
|
|
657
|
+
| **archetype policy** (7) | `A21_MESH_RIM_PINNED`, `A24_AXIS_SPACE_STROKE`, `A25_DETACHED_BONE_PARENTAGE`, `A26_SLOT_DRAW_ORDER`, `A28_RIBBON_ROWS_SHARE_WEIGHTS`, `A29_STROKE_WITHIN_CONTACT_DEPTH`, `A30_STROKE_WITHIN_CAP_CONTAINMENT` |
|
|
658
|
+
|
|
659
|
+
Three further rules — **`A06`**, **`A08`** and **`A20`** — are *mixed*: their validity
|
|
660
|
+
clauses run in both profiles and their policy clauses only under `spine-html`. `A06`'s
|
|
661
|
+
size-vs-PNG check is validity; one-part-per-page coverage, rotation and premultiplied
|
|
662
|
+
alpha are policy. `A08`'s attachment→region join is validity; requiring the two names
|
|
663
|
+
to be *identical* is policy. `A20`'s weight coherence is validity; requiring a mesh to
|
|
664
|
+
be weighted at all is policy.
|
|
665
|
+
|
|
666
|
+
⚠️ **`--profile spine-html` on foreign data produces a wall of failures that mean
|
|
667
|
+
nothing about the file.** Same `spineboy-pro.json`, same atlas, one flag changed — the
|
|
668
|
+
run ends `rigc: 53 assertion(s) failed`, and this is the tally with one real message
|
|
669
|
+
per rule:
|
|
670
|
+
|
|
671
|
+
| Count | Rule | One of its messages |
|
|
672
|
+
| --- | --- | --- |
|
|
673
|
+
| **40** | `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | `region "crosshair" has UVs (0.181640625,0.06640625)-(0.2255859375,0.2421875); one part per page must cover the page exactly` |
|
|
674
|
+
| **10** | `A15_IDLE_NO_MESH_BONE_KEYS` | `idle keys bone "front-shoulder", which drives a mesh — meshes never idle-skip` |
|
|
675
|
+
| **2** | `A20_MESH_WEIGHTS_COHERENT` | `mesh "hoverboard-board" is unweighted; the ring tier drives meshes by bones` |
|
|
676
|
+
| **1** | `A11_NO_CLIPPING_ATTACHMENTS` | `1 clipping attachment(s); the renderer skips them silently` |
|
|
677
|
+
|
|
678
|
+
Every one of those is a correct statement about a correct file: the atlas *is* packed,
|
|
679
|
+
a bone *does* key a mesh, a mesh *is* unweighted, a clipping attachment *is* present.
|
|
680
|
+
(The tally was 55 before §3.2's A35 was fixed, and that is the one entry that was *not*
|
|
681
|
+
a correct statement — which is why it belonged in a different section from these.)
|
|
682
|
+
And it is not a big-skeleton problem — `3-timing-and-spacing`, with two regions on one
|
|
683
|
+
page, fails `A06` twice for the same reason. ⇒ **Do not run `spine-html` against
|
|
684
|
+
somebody's export unless they asked whether it satisfies this project's renderer
|
|
685
|
+
policy**, which is a different question from whether their file is valid.
|
|
686
|
+
|
|
687
|
+
⚠️ **And do not read the absence of `SKIP` lines as thoroughness.** Under `spine` a
|
|
688
|
+
foreign skeleton typically produces *no* archetype `SKIP` at all — the profile excludes
|
|
689
|
+
those rules before their bodies could notice the missing `invariants` block, so they
|
|
690
|
+
arrive as `PROF`. The `PROF` list is where *"was this held to that rule at all"* gets
|
|
691
|
+
answered (AUTHORING §7).
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 4. Recipes
|
|
696
|
+
|
|
697
|
+
Each of these starts from a transcription (§2), and none is an edit to emitted JSON —
|
|
698
|
+
an edit to emitted JSON is a change nothing in the toolchain checked. All three were
|
|
699
|
+
run from copies of the stored 3-timing transcription:
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
T=bench/transcriptions/3-timing-and-spacing
|
|
703
|
+
mkdir -p work/repivot
|
|
704
|
+
cp $T/3-timing-and-spacing-ess.rig.json work/repivot/repivot.rig.json
|
|
705
|
+
cp $T/3-timing-and-spacing-ess.motion.json work/repivot/repivot.motion.json
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
⚠️ A copied spec's own `images` path is relative to where the spec was, so it breaks
|
|
709
|
+
on the copy. `--images` overrides it, relative to your working directory, and every
|
|
710
|
+
`build` below passes it.
|
|
711
|
+
|
|
712
|
+
### 4.1 Moving a pivot without moving the art
|
|
713
|
+
|
|
714
|
+
The ask: *"the arm should swing from its middle, not its end — don't change the
|
|
715
|
+
drawing."* This is the recipe the rest of the section is measured against, because its
|
|
716
|
+
correctness criterion is exact and checkable without rendering anything.
|
|
717
|
+
|
|
718
|
+
**What changes in the file:**
|
|
719
|
+
|
|
720
|
+
| Object | Change | Why |
|
|
721
|
+
| --- | --- | --- |
|
|
722
|
+
| **the bone** | its `x`/`y` move to the new pivot, expressed in its **parent's** local axes | the bone's origin *is* the pivot |
|
|
723
|
+
| **its attachments** | offsets move by the same vector expressed in the **bone's own** axes, with the opposite sign | an attachment offset is the art's centre relative to the bone origin; the bone origin just moved, so this cancels it |
|
|
724
|
+
| **its child bones** | every child's `x`/`y` needs the same opposite correction | a child's offset is in this bone's local space, so moving the origin moved every child with it |
|
|
725
|
+
| **the timelines** | ⛔ **nothing** | `rotate` keys are angles about the origin, and the origin is what you changed. This is the entire point of the edit |
|
|
726
|
+
|
|
727
|
+
⚠️ **The child-bone row is the one that gets forgotten**, and it fails quietly: the
|
|
728
|
+
re-pivoted bone's own art lands correctly and everything hanging off it is displaced by
|
|
729
|
+
exactly the vector you moved. If the bone has children, correct them in the same edit,
|
|
730
|
+
or the fix looks half-right in a way no assertion will mention.
|
|
731
|
+
|
|
732
|
+
**Worked.** 3-timing's `bone` sits at `x: 204.753, y: 708.0989` with `rotation: 180`
|
|
733
|
+
and `length: 473`; the `pendulum` attachment is at bone-local
|
|
734
|
+
`x: 316.79, y: 0.4815389`. Move the pivot half the bone's length along the bone's own
|
|
735
|
+
+x axis, `d = 236.5`: the bone's rotation is 180° in its parent's frame, so
|
|
736
|
+
`R(180)·(d, 0) = (-d, 0)`, and this bone has no children.
|
|
737
|
+
|
|
738
|
+
```diff
|
|
739
|
+
{ "name": "bone", "parent": "root", "length": 473,
|
|
740
|
+
- "x": 204.753, "y": 708.0989, "rotation": 180 }
|
|
741
|
+
+ "x": -31.747, "y": 708.0989, "rotation": 180 }
|
|
742
|
+
|
|
743
|
+
"pendulum": { "pendulum": { "image": "pendulum.png",
|
|
744
|
+
- "x": 316.79, "y": 0.4815389, "rotation": -179.81934 } }
|
|
745
|
+
+ "x": 80.29, "y": 0.4815389, "rotation": -179.81934 } }
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
rigc build --rig work/repivot/repivot.rig.json \
|
|
750
|
+
--motion work/repivot/repivot.motion.json \
|
|
751
|
+
--images examples/3-timing-and-spacing/images \
|
|
752
|
+
--out work/t3b
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Verify the invariant first, arithmetically.** The art's centre in world coordinates
|
|
756
|
+
is `bone(x, y) + R(bone.rotation) · att(x, y)`. Computed from the two built skeletons,
|
|
757
|
+
before and after:
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
original bone [204.753, 708.0989] att [316.79, 0.4816] -> art centre [-112.0370, 707.6174]
|
|
761
|
+
re-pivot bone [-31.747, 708.0989] att [ 80.29, 0.4816] -> art centre [-112.0370, 707.6174]
|
|
762
|
+
art centre moved by 2.842e-14 units
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
⭐ **That is the criterion.** If the art's world position at the setup pose moves by
|
|
766
|
+
more than floating-point noise, the compensation is wrong, and no amount of looking at
|
|
767
|
+
frames will tell you which of the two numbers to blame.
|
|
768
|
+
|
|
769
|
+
**Then confirm the movement did change**, which is the half a pose cannot show. The
|
|
770
|
+
same displacement evaluated across the bone's own rotation:
|
|
771
|
+
|
|
772
|
+
| `rotate` | original art centre | re-pivot art centre | apart |
|
|
773
|
+
| --- | --- | --- | --- |
|
|
774
|
+
| 0° | `[-112.04, 707.62]` | `[-112.04, 707.62]` | **0.00 units** |
|
|
775
|
+
| 15° | `[-101.12, 625.64]` | `[-109.18, 686.85]` | 61.74 |
|
|
776
|
+
| 45° | `[ -18.91, 483.75]` | `[ -88.18, 650.98]` | 181.01 |
|
|
777
|
+
| 90° | `[ 205.23, 391.31]` | `[ -31.27, 627.81]` | 334.46 |
|
|
778
|
+
| 180° | `[ 521.54, 708.57]` | `[ 48.54, 708.58]` | 473.00 |
|
|
779
|
+
|
|
780
|
+
**What the instruments say about it** — and this pair is why §1.3 carries its warning:
|
|
781
|
+
|
|
782
|
+
- **`diff` sees nothing.** `rigc diff work/t3b/skeleton.json <the export>` reads
|
|
783
|
+
**1.000 on all 49 measures**: same bones, names, parents, order, slots, draw order,
|
|
784
|
+
attachments, animations, timelines, key counts, curve kinds. All true, and all silent
|
|
785
|
+
about a 236.5-unit move.
|
|
786
|
+
- **`check` sees it loudly**, with the framing pinned so the comparison is of pictures
|
|
787
|
+
and not of framings:
|
|
788
|
+
|
|
789
|
+
```bash
|
|
790
|
+
rigc check --candidate work/t3b --frames ref3/heavy \
|
|
791
|
+
--viewport -573.3,-81.2,2176.3,990.1 \
|
|
792
|
+
--texture-from examples/3-timing-and-spacing/export/3-timing-and-spacing.atlas \
|
|
793
|
+
--all-frames
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
```
|
|
797
|
+
MAE mean 106.91 worst 116.67 at f0012 (0..255 over the union alpha; over the whole frame, mean 7.15)
|
|
798
|
+
⭐ texture floor 3.62 above it 105.79 (over the reference's own pixels, 1.95 and 92.51)
|
|
799
|
+
slot drift worst 42.4 px "pendulum" at f0028
|
|
800
|
+
chain slots worst slot drift mean MAE in it share
|
|
801
|
+
square 1/1 0.4 px "square" f0000 0.3 px 10.20 3.2%
|
|
802
|
+
bone 1/1 42.4 px "pendulum" f0028 31.3 px 125.70 77.3%
|
|
803
|
+
(unattributed) — — — — 19.5%
|
|
804
|
+
|
|
805
|
+
frame MAE union px Δpx ref Δ worst slot drift how slots note
|
|
806
|
+
f0000 5.98 1246 — — square 0.4 component 2/2
|
|
807
|
+
f0001 6.18 1245 19 40 square 0.4 component 2/2
|
|
808
|
+
f0002 19.58 1280 456 520 pendulum 0.7 component 2/2
|
|
809
|
+
f0003 46.11 1412 702 815 pendulum 2.1 component 2/2
|
|
810
|
+
f0004 75.88 1608 808 939 pendulum 4.3 component 2/2
|
|
811
|
+
f0005 93.32 1776 996 1125 pendulum 8.0 component 2/2
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
⭐ **Read the per-frame column before the mean.** `f0000` is **5.98** — the
|
|
815
|
+
transcription's own texture floor, i.e. *no difference at all* — and it climbs
|
|
816
|
+
monotonically from `f0002` as the bone rotates. That is the pivot's whole signature:
|
|
817
|
+
**invisible in the pose, and everything in the movement**, which is what MOTION §3.9
|
|
818
|
+
argues from the authoring side and what this measures from the ingest side. The
|
|
819
|
+
`chains` table puts 77.3 % of the difference on the `bone` chain and 3.2 % on
|
|
820
|
+
`square`, which is the edit's own blast radius.
|
|
821
|
+
|
|
822
|
+
⚠️ **Pin `--viewport` for a re-pivot check.** Drop the flag and the same run reads
|
|
823
|
+
`MAE mean 120.65` with `slot drift worst 30.3 px "pendulum" at f0000` — and that drift
|
|
824
|
+
at frame 0 is an artefact, because the re-pivot changed the skeleton's **world extent**,
|
|
825
|
+
so `frames.json`'s box came back `REFUSED, coordinates` and the framing was fitted
|
|
826
|
+
instead:
|
|
827
|
+
|
|
828
|
+
```
|
|
829
|
+
framed to 256x116px 0.100498 px/unit world x[-908.6 .. 1638.7] y[-74.6 .. 1079.7] (fitted to the candidate's own drawn pixels)
|
|
830
|
+
declared frames.json's own box: REFUSED, coordinates — a fit there asks for 31.48 px, past the 11.74 px the extent-spread tolerance reaches — a different origin or a different unit, over 65 frame(s).
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
⇒ On any edit that changes the extent, take the viewport from the reference render's
|
|
834
|
+
own `world x[…] y[…]` line, or read `check`'s framing lines before its figures.
|
|
835
|
+
|
|
836
|
+
### 4.2 Renaming, and the name-agnostic mindset
|
|
837
|
+
|
|
838
|
+
The ask: *"give everything our project's names."* Mechanically a rename pass over the
|
|
839
|
+
rig spec; the discipline is in what you check afterwards.
|
|
840
|
+
|
|
841
|
+
**Everything in a rig spec resolves by name, and a miss is refused by name.** A bone's
|
|
842
|
+
`parent`, a slot's `bone`, a constraint's `bones` and `target`, a draw-order key's
|
|
843
|
+
`slot`, an authored mesh's vertex `weights` — and, across the two files, the motion
|
|
844
|
+
spec's `archetype` against the rig spec's `name`. That last one is the first refusal a
|
|
845
|
+
rename produces, before anything else has a chance to go wrong:
|
|
846
|
+
|
|
847
|
+
```
|
|
848
|
+
rigc compile error: …/work/renamed/pendulum-rig.motion.json: motion spec names
|
|
849
|
+
archetype "3-timing-and-spacing-ess" but the rig spec at
|
|
850
|
+
…/work/renamed/pendulum-rig.rig.json is called "pendulum-rig"
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
⭐ **A rename is therefore mostly safe by construction, and its failures arrive as
|
|
854
|
+
sentences naming both sides.** That is the reason to do it in the specs rather than in
|
|
855
|
+
emitted JSON, where the same mistake is a silently unresolved reference.
|
|
856
|
+
|
|
857
|
+
**Then check it with the name-agnostic groups**, because after a rename the name-keyed
|
|
858
|
+
measures are *supposed* to disagree. Renaming `bone`→`arm`, `square`→`block`, the two
|
|
859
|
+
slots to `arm-art`/`block-art` and their attachments to match:
|
|
860
|
+
|
|
861
|
+
```
|
|
862
|
+
bones mean 0.567 over 8 measures
|
|
863
|
+
1.000 count 3/3 how many bones
|
|
864
|
+
0.200 names 1/5 the bone names themselves
|
|
865
|
+
0.333 parent_by_name 1/3 each bone hangs off the same parent
|
|
866
|
+
0.333 order 1/3 the bones are declared in the same order
|
|
867
|
+
0.333 length_present 1/3 a setup `length` is present or absent alike
|
|
868
|
+
0.333 inherit_present 1/3 a setup `inherit` is present or absent alike
|
|
869
|
+
1.000 depth_histogram 3/3 NAME-AGNOSTIC: as many bones at each depth
|
|
870
|
+
1.000 degree_sequence 3/3 NAME-AGNOSTIC: as many bones with each child count
|
|
871
|
+
|
|
872
|
+
bones (name-agnostic) mean 1.000 over 5 measures — the same two skeletons compared with names thrown away
|
|
873
|
+
1.000 count 3/3 how many bones
|
|
874
|
+
1.000 depth_histogram 3/3 as many bones at each depth
|
|
875
|
+
1.000 degree_sequence 3/3 as many bones with each child count
|
|
876
|
+
1.000 shape_histogram 3/3 as many bones of each depth-and-child-count shape (`d1c3` = one hop down, three children)
|
|
877
|
+
1.000 order_shape 3/3 the bones are declared in the same order of shapes
|
|
878
|
+
|
|
879
|
+
slots mean 0.143 over 7 measures
|
|
880
|
+
1.000 count 2/2 how many slots
|
|
881
|
+
0.000 names 0/4 the slot names themselves
|
|
882
|
+
0.000 order 0/2 the slots array IS the draw order, so its order is data
|
|
883
|
+
0.000 bone 0/2 each slot is bound to the same bone
|
|
884
|
+
0.000 attachment 0/2 each slot shows the same setup attachment
|
|
885
|
+
0.000 blend 0/2 each slot uses the same blend mode
|
|
886
|
+
0.000 color_present 0/2 a tint is present or absent alike
|
|
887
|
+
|
|
888
|
+
slots (name-agnostic) mean 1.000 over 4 measures — the same two skeletons compared with names thrown away
|
|
889
|
+
1.000 count 2/2 how many slots
|
|
890
|
+
1.000 attachment_types_by_position 2/2 the same kind of attachment sits at each position in the draw order
|
|
891
|
+
1.000 bone_binding_shape 2/2 as many slots hang off a bone of each shape (`?` = no such bone is declared)
|
|
892
|
+
1.000 order_shape 2/2 the draw order is the same order of `<attachment type>@<bone shape>`
|
|
893
|
+
|
|
894
|
+
attachments mean 0.889 over 9 measures
|
|
895
|
+
1.000 skins 1/1 the skin names
|
|
896
|
+
1.000 count 2/2 how many attachments
|
|
897
|
+
0.000 names 0/4 skin/slot/attachment keys
|
|
898
|
+
1.000 type_counts 2/2 as many of each attachment type
|
|
899
|
+
…
|
|
900
|
+
animations mean 1.000 over 9 measures
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
Three things to read out of that, in order:
|
|
904
|
+
|
|
905
|
+
- **The name-keyed collapse is the task, not a defect.** `bones` 0.567, `slots` 0.143,
|
|
906
|
+
`attachments` 0.889. Note that several *non*-name measures fall with them —
|
|
907
|
+
`slots.bone`, `slots.blend`, `bones.parent_by_name` — because they are keyed **by**
|
|
908
|
+
the name that changed. They are not saying the binding changed.
|
|
909
|
+
- 🚨 **`bones (name-agnostic)` and `slots (name-agnostic)` must stay 1.000.** They
|
|
910
|
+
measure the rig with the vocabulary thrown away, so a rename that changed only names
|
|
911
|
+
leaves them untouched. **A drop there is a structural mistake wearing a rename's
|
|
912
|
+
clothes**, and it is the only assertion this recipe really has.
|
|
913
|
+
- **`animations` stays 1.000** because animation *names* were not part of the ask.
|
|
914
|
+
Under the same edit `bones.names` reads `1/5` — `root` survived, and the total is the
|
|
915
|
+
union of both vocabularies.
|
|
916
|
+
|
|
917
|
+
⛔ And remember §1.3: `diff` reads no coordinates either way. A rename that also moved
|
|
918
|
+
something is invisible to every measure in that report. Pair it with a `check` against
|
|
919
|
+
frames rendered from the original.
|
|
920
|
+
|
|
921
|
+
⚠️ **Do not rename toward what a rule seems to want.** `A08`'s name-identity clause and
|
|
922
|
+
`A27`'s region-name-matches-page-filename are both `spine-html` policy (§3.3): under
|
|
923
|
+
the default profile they do not fire, and renaming somebody's attachments to satisfy a
|
|
924
|
+
policy they never opted into is a change with no benefit to them.
|
|
925
|
+
|
|
926
|
+
### 4.3 Extending a foreign skeleton with a new animation
|
|
927
|
+
|
|
928
|
+
The ask: *"add a `nudge` to this."* There is no append — `build` re-emits the whole
|
|
929
|
+
skeleton — so the extension is an added block in the motion spec of a transcription
|
|
930
|
+
that already round-trips.
|
|
931
|
+
|
|
932
|
+
1. **Get the transcription to structural agreement first** (§2.3), and record the
|
|
933
|
+
figure. Extending an unfinished transcription mixes two kinds of difference into
|
|
934
|
+
every measurement after it.
|
|
935
|
+
2. **Add the animation to the motion spec.** Named easings here, not raw curves —
|
|
936
|
+
§2.2's escape hatch is for reproducing an export's own beziers, and this movement
|
|
937
|
+
has no export behind it. What goes *between* the poses is [MOTION.md](MOTION.md);
|
|
938
|
+
this page stops at the mechanics.
|
|
939
|
+
3. **`build`, and read the count line.**
|
|
940
|
+
|
|
941
|
+
```bash
|
|
942
|
+
rigc build --rig work/extend/extend.rig.json \
|
|
943
|
+
--motion work/extend/extend.motion.json \
|
|
944
|
+
--images examples/3-timing-and-spacing/images \
|
|
945
|
+
--out work/t3c
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
```
|
|
949
|
+
.. pages=2 regions=2 bones=3 slots=2 animations=3 version=4.3.13 regionAttachments=2 meshAttachments=0 physicsConstraints=0 rig=3-timing-and-spacing-ess profile=spine
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
`animations=3` where the export had 2. That line is the cheapest confirmation the
|
|
953
|
+
block landed at all.
|
|
954
|
+
4. **Read `diff` knowing what it is about to say.**
|
|
955
|
+
|
|
956
|
+
```
|
|
957
|
+
animations mean 0.793 over 9 measures
|
|
958
|
+
0.667 count 2/3 how many animations
|
|
959
|
+
0.667 names 2/3 the animation names
|
|
960
|
+
0.667 duration 2/3 each animation runs as long (last key time, within one frame)
|
|
961
|
+
0.889 timeline_kinds 8/9 the same timelines exist
|
|
962
|
+
0.958 key_counts 69/72 those timelines carry as many keys
|
|
963
|
+
0.958 curve_kinds 69/72 as many linear / stepped / bezier keys
|
|
964
|
+
1.000 event_keys 0/0 as many event firings — neither side has any
|
|
965
|
+
0.667 draw_order 2/3 a draw-order timeline is present or absent alike
|
|
966
|
+
0.667 deform 2/3 a deform timeline is present or absent alike
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
🚨 **Every one of those got worse, and that is the correct result.** The
|
|
970
|
+
`animations` section went 1.000 → 0.793 because the candidate now has something the
|
|
971
|
+
reference does not. `diff` measures agreement with a reference; you were asked to
|
|
972
|
+
*disagree* with it, in one specific way. ⇒ **Check that the drop is confined to the
|
|
973
|
+
`animations` section and is the size the addition explains** — one animation of
|
|
974
|
+
three, three keys of seventy-two — and that `bones`, `slots`, `attachments` and
|
|
975
|
+
`constraints` are all still 1.000. That last part is the real assertion here: *the
|
|
976
|
+
extension changed nothing it was not supposed to change.*
|
|
977
|
+
5. **Look at it, then ask.** `render --animation nudge` and open the contact sheet;
|
|
978
|
+
`vote` the foreign original against your extended build when the question is whether
|
|
979
|
+
the new movement belongs beside the old ones. Nothing in this toolchain can answer
|
|
980
|
+
that, and MOTION §4–§5 is how to shape the ballot so the answer informs.
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
## 5. Non-goals — stated, so nobody proposes them as gaps
|
|
985
|
+
|
|
986
|
+
🚫 **rigc does not read editor project files.** A `.spine` is the editor's own project
|
|
987
|
+
format, not skeleton data, and no rigc command opens one. The path form refuses it by
|
|
988
|
+
name:
|
|
989
|
+
|
|
990
|
+
```
|
|
991
|
+
rigc: …/hero.spine is neither a directory nor a .json skeleton
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
The route from a project file to rigc is the one the editor already provides: export
|
|
995
|
+
it, and start from the export. (The official examples' project files are public
|
|
996
|
+
domain — [NOTICE.md](../NOTICE.md) — and `scripts/fetch-examples.sh` does not download
|
|
997
|
+
them, because nothing here could use one.)
|
|
998
|
+
|
|
999
|
+
🚫 **Binary `.skel` is not read, and the honest statement has two halves.** rigc's
|
|
1000
|
+
dependency *can* read it and rigc *does not*:
|
|
1001
|
+
|
|
1002
|
+
- `@esotericsoftware/spine-core@4.3.13` exports `SkeletonBinary`, whose
|
|
1003
|
+
`readSkeletonData` is the binary reader.
|
|
1004
|
+
- **rigc's own source references it zero times.** Every read path goes through
|
|
1005
|
+
`JSON.parse` and `SkeletonJson`, and the path resolver requires a `.json` extension —
|
|
1006
|
+
so a `.skel` is refused by the same sentence a `.spine` is, one layer before any
|
|
1007
|
+
format question arises.
|
|
1008
|
+
|
|
1009
|
+
⇒ Binary support is *reachable* rather than *present*: a plumbing job on a dependency
|
|
1010
|
+
that already has the reader, not a parser to write. But it is not there, and nothing on
|
|
1011
|
+
this page works on a `.skel` today. Re-export as JSON.
|
|
1012
|
+
|
|
1013
|
+
🚫 **No skeleton-to-spec decompiler.** Nothing turns skeleton JSON back into a rig spec
|
|
1014
|
+
and a motion spec. §2 is hand work, and that is the current state rather than a
|
|
1015
|
+
temporary one: a decompiler would have to invent the things the spec format exists to
|
|
1016
|
+
make explicit — which pivot, which generator, which invariant — and the compiler's own
|
|
1017
|
+
rule is that it never invents a value that is not in the spec. ⚠️ Not to be confused
|
|
1018
|
+
with the *atlas* importer below, which is a different direction and does exist.
|
|
1019
|
+
|
|
1020
|
+
✅ **A packer and an importer both exist now, so do not report them as gaps.** This
|
|
1021
|
+
non-goal used to read *"rigc emits one region per page and cannot do otherwise"*, and
|
|
1022
|
+
[issue #4](https://github.com/firejune/rigc/issues/4) closed it: `build --pack` writes
|
|
1023
|
+
shared pages losslessly and `build --atlas-in` resolves against a pack somebody else
|
|
1024
|
+
made (AUTHORING §0.1–§0.2). One-region-per-page is now the **default**, not the only
|
|
1025
|
+
shape. `--atlas-in` applies the page's `scale:`, so an imported pack states the
|
|
1026
|
+
drawing's size rather than the pack's — to within the pack's own rounding, which is
|
|
1027
|
+
§2.3's row and the whole of [issue #267](https://github.com/firejune/rigc/issues/267).
|
|
1028
|
+
|
|
1029
|
+
🚫 **No CLI unpacker, so `pose` needs loose art.** `pose` reads *loose part PNGs*
|
|
1030
|
+
against one picture. A foreign export hands you a packed page instead, and pointing
|
|
1031
|
+
`pose` at one is worse than useless — it treats the whole page as a single part and
|
|
1032
|
+
answers confidently:
|
|
1033
|
+
|
|
1034
|
+
```bash
|
|
1035
|
+
mkdir -p packed-only
|
|
1036
|
+
cp examples/3-timing-and-spacing/export/3-timing-and-spacing.png packed-only/
|
|
1037
|
+
rigc pose --images packed-only --frame 'ref-big/light@4fps/f0002.png'
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
```
|
|
1041
|
+
rigc pose
|
|
1042
|
+
.. frame …/ref-big/light@4fps/f0002.png (900x409)
|
|
1043
|
+
.. ground rgb(232, 232, 232) over 100% of the border ring
|
|
1044
|
+
.. parts …/packed-only (1 png)
|
|
1045
|
+
.. search scale 0.5–2 in 7 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
|
|
1046
|
+
PLACE 3-timing-and-spacing.png x= 324.1 y= 188.0 rot= -91.4° scale=0.632 residual=0.2078 unexplained= 29%
|
|
1047
|
+
found on a 7x3 anchor grid, step 4 at 32x reduction
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
⚠️ **`residual=0.2078` is *under* the default 0.25 refusal bar**, so nothing refused
|
|
1051
|
+
it, and `PLACE` rather than `AMBIG` means nothing flagged it either. With the same frame
|
|
1052
|
+
and the two real loose parts, the answer is what it should be:
|
|
1053
|
+
|
|
1054
|
+
```bash
|
|
1055
|
+
rigc pose --images examples/3-timing-and-spacing/images \
|
|
1056
|
+
--frame 'ref-big/light@4fps/f0002.png' --scale 0.3,0.6
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
```
|
|
1060
|
+
.. parts …/examples/3-timing-and-spacing/images (2 png)
|
|
1061
|
+
.. search scale 0.3–0.6 in 4 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
|
|
1062
|
+
PLACE pendulum.png x= 319.3 y= 214.5 rot= -88.9° scale=0.412 residual=0.0410 unexplained= 7%
|
|
1063
|
+
found on a 12x5 anchor grid, step 5 at 16x reduction
|
|
1064
|
+
PLACE square.png x= 437.2 y= 343.1 rot= -0.1° scale=0.410 residual=0.0330 unexplained= 4%
|
|
1065
|
+
found on a 57x26 anchor grid, step 4 at 4x reduction
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
⇒ **Check what is in `--images` before trusting a `pose` report on ingest work.** One
|
|
1069
|
+
PNG where you expected several is the tell, and the `.. parts` line prints the
|
|
1070
|
+
count. AUTHORING §11.4 is the rest of what that command cannot see.
|
|
1071
|
+
|
|
1072
|
+
📎 To be exact about what is missing: rigc *can* lift a region's drawing back off a
|
|
1073
|
+
page — `extractRegion` does it, and the contour mesh generator uses it under
|
|
1074
|
+
`--atlas-in` — so what is absent is a **command**, not the capability. It refuses by
|
|
1075
|
+
name on a region packed `rotate: 90` (AUTHORING §0.2), which a foreign pack can be and
|
|
1076
|
+
rigc's own never is.
|
|
1077
|
+
|
|
1078
|
+
🚫 **No `validate --fix`, and no normalisation pass.** Every recipe in §4 is a change
|
|
1079
|
+
you state in a spec and rebuild. A tool that rewrote somebody's export in place would
|
|
1080
|
+
be making decisions on their behalf with nowhere to say it had — and, per §3.2, some of
|
|
1081
|
+
those decisions would be wrong about the rule rather than about the file.
|
|
1082
|
+
|
|
1083
|
+
🚫 **`diff` will not be given coordinate measures to make it a geometry check.**
|
|
1084
|
+
`check` is the geometry instrument, and it works by rendering, which is the only way to
|
|
1085
|
+
compare two rigs that may be authored in different coordinate systems at different
|
|
1086
|
+
scales. A coordinate diff between two skeletons would be arithmetic on numbers that do
|
|
1087
|
+
not compare — `check`'s own report says the two world boxes *"are different coordinate
|
|
1088
|
+
systems and do not compare; the pixel grid does."*
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## Appendix — the corpus this page was verified against
|
|
1093
|
+
|
|
1094
|
+
Every command line above was run from a checkout with `bun run fetch-examples`
|
|
1095
|
+
completed. Two skeletons carry all of it:
|
|
1096
|
+
|
|
1097
|
+
| Example | Files used | Licence |
|
|
1098
|
+
| --- | --- | --- |
|
|
1099
|
+
| **`3-timing-and-spacing`** | `export/3-timing-and-spacing-ess.json`; `export/3-timing-and-spacing.atlas` (one 512×128 page, `scale: 0.5`, 2 regions); `images/pendulum.png` (745×212); `images/square.png` (159×159) | `license.txt` present, © 2021-2025 Esoteric Software |
|
|
1100
|
+
| **`spineboy`** | `export/spineboy-pro.json`, `export/spineboy-ess.json`, `export/spineboy.atlas`, `export/spineboy-run.atlas` | `license.txt` present, © 2013 Esoteric Software LLC |
|
|
1101
|
+
|
|
1102
|
+
The working directories the commands write into — `ref3/`, `ref-big/`, `render/`,
|
|
1103
|
+
`work/`, `packed-only/` — are throwaway and none is committed.
|
|
1104
|
+
|
|
1105
|
+
⚠️ **`7-anticipation` ships no `license.txt` upstream**, so the redistribution grant its
|
|
1106
|
+
siblings carry does not exist for it. No excerpt, figure or image on this page comes
|
|
1107
|
+
from it; the only places it appears at all are the two corpus-wide tallies — §3.2's
|
|
1108
|
+
*twelve of twelve skeletons* and §2.3's *nine of ten atlases* — both of which count
|
|
1109
|
+
every directory the fetch produced.
|
|
1110
|
+
`scripts/fetch-examples.sh` prints a warning naming it; [NOTICE.md](../NOTICE.md) has
|
|
1111
|
+
the per-directory terms.
|
|
1112
|
+
|
|
1113
|
+
The transcription these recipes start from is
|
|
1114
|
+
[`bench/transcriptions/3-timing-and-spacing/`](https://github.com/firejune/rigc/tree/main/bench/transcriptions/3-timing-and-spacing/),
|
|
1115
|
+
unmodified. The re-pivoted, renamed and extended variants in §4 were built from copies
|
|
1116
|
+
of it; none is committed, because each is an illustration of an edit rather than a
|
|
1117
|
+
transcription of anything.
|