spine-rigc 0.25.4 → 0.25.6
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 +32 -13
- package/cli.ts +192 -20
- package/docs/AUTHORING.md +477 -11
- package/docs/FACE.md +153 -37
- package/docs/INGEST.md +64 -5
- package/docs/MOTION.md +1 -1
- package/docs/PROMPTING.md +2 -2
- package/docs/RIGGING.md +3 -2
- package/docs/SPEC_COVERAGE.md +19 -0
- package/package.json +1 -1
- package/skills/face/SKILL.md +13 -4
- package/skills/ingest/SKILL.md +6 -3
- package/skills/rigc/SKILL.md +11 -4
- package/src/atlas.ts +11 -2
- package/src/check.ts +82 -3
- package/src/compile.ts +193 -40
- package/src/emit.ts +76 -41
- package/src/errors.ts +32 -1
- package/src/ingest.ts +41 -3
- package/src/mesh.ts +40 -0
- package/src/rig.ts +28 -6
- package/src/slots.ts +216 -25
- package/src/types.ts +41 -14
- package/src/validate.ts +32 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -56,14 +56,18 @@ provenance for a reader of record — the loop that hit a trap, the issue that c
|
|
|
56
56
|
and following one can arrive at a stored candidate's own spec, at the corpus inventory,
|
|
57
57
|
or at the **derivation** of the gate a verdict is read against, none of which a run may
|
|
58
58
|
open. ⭐ The gate's **clause statements** are a different matter and a run may read them:
|
|
59
|
-
they are in
|
|
59
|
+
they are in
|
|
60
|
+
[GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md), which is
|
|
61
|
+
repository material and not in the published package — item 11 of the allowed list (owner ruling 2026-08-29) — the
|
|
60
62
|
measure, the comparator, the number and the SKIP semantics, with no recorded figure in it.
|
|
61
63
|
So: read the
|
|
62
64
|
document, take its numbered sections as the input, and leave its footprints to whoever
|
|
63
65
|
is maintaining it. The rule this states is that an **allowed-reading surface has to be
|
|
64
66
|
closed under reading**; the criterion behind it is under *The honesty rule* in
|
|
65
67
|
[LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md), and the enumerated allowed and forbidden lists are in
|
|
66
|
-
|
|
68
|
+
[bench/runs/README.md](https://github.com/firejune/rigc/blob/main/bench/runs/README.md),
|
|
69
|
+
which is also repository material and not in the published package,
|
|
70
|
+
*What a run may read* — the prompt that starts a run quotes them
|
|
67
71
|
outright, which is the copy that binds.
|
|
68
72
|
|
|
69
73
|
## The vocabulary is Spine's
|
|
@@ -156,13 +160,15 @@ What the flags mean:
|
|
|
156
160
|
| `--rig` | the rig spec — skeleton structure |
|
|
157
161
|
| `--motion` | the motion spec — time |
|
|
158
162
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths and `skeleton.images` are written relative to it |
|
|
159
|
-
| `--copy-images` | `build` only: also copies every
|
|
163
|
+
| `--copy-images` | `build` only: also copies every page **the emitted atlas names** into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own, and points `skeleton.images` at `--out` itself so the editor's import finds the parts beside the skeleton (issue #370; §3.1 says why it is spelled `../<out>/` and not `./`). Under `--atlas-in` those pages are the pack's, not one per part (issue #693 — **§0.2**). Default is unchanged — page paths still point at the source art (issue #217) |
|
|
160
164
|
| `--pack` | `build` only: arrange every part onto **shared** atlas page(s), written into `--out` as real PNGs, instead of one page per part. Lossless — nothing is resampled, trimmed or rotated. Default is unchanged (issue #4) — **§0.1** |
|
|
161
165
|
| `--page-size` | `build --pack` only: the largest page edge (default `2048`). A ceiling, not the size: page edges are powers of two and the one written is the smallest that holds the pack — **§0.1** |
|
|
162
166
|
| `--padding` | `build --pack` only: the gutter each region reserves on every side (default `2`), filled by extending the region's own edge pixels outwards. `0` is not a legal-but-tight choice, it is bleed — **§0.1** |
|
|
163
|
-
| `--atlas-in` | `build`
|
|
167
|
+
| `--atlas-in` | `build` and `explain`: resolve every part against the **regions of a pre-packed `.atlas`** instead of against loose PNGs. Region geometry is read from the file and sizes are descaled by the page's `scale:`; `build` re-emits the atlas into `--out`, re-anchored, and `explain` writes nothing and poses through it — **§0.2**. On `explain` it is the flag that makes a **size-only** spec readable at all (`ingest --art none`), because posing resolves every attachment against an atlas; without it that pair is refused by name rather than thrown through ([#697](https://github.com/firejune/rigc/issues/697), §5.1) |
|
|
164
168
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory). For `pose` it is the directory of **loose part PNGs to place** — every `.png` in it is a part, in name order. For `chainfit` it is only where each attachment's image name **resolves**: the candidate decides what the parts are, so extra PNGs are unused and a missing name is refused by name (§12.3) |
|
|
165
169
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
170
|
+
| `--cut` | `build`, `explain` and `validate`: look up a named cut in `--cuts <cuts.json>`, **instead of** `--rig`/`--motion`/`--out` — the two spellings are one build stated two ways and are refused together. A `cuts.json` is `{ "<name>": { "rig": …, "motion": …, "out": …, "manifest"?: … } }`, every path in it relative to the table's own file, so the table lives with the project that owns the art |
|
|
171
|
+
| `--cuts` | the `cuts.json` `--cut` names. Required beside it — `--cut` alone is refused, with no guess at where the table lives |
|
|
166
172
|
| `--profile` | `spine` = the 28 validity rules (**the default**) · `spine-html` = all 43, opt-in |
|
|
167
173
|
| `--candidate` | `check`, `bench`, `render`, `preview`, `chainfit` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton. **`vote` is the one command that takes it more than once** — repeat it 2–4 times, one per pane, labelled A, B, C, D in the order given; everywhere else a repeat is a typo and is refused |
|
|
168
174
|
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview`, and for `vote` the first of candidate A. A name the skeleton does not have is refused, with the ones it does have listed — and for `vote`, so is a name that only *some* candidates have |
|
|
@@ -231,7 +237,8 @@ a gap:
|
|
|
231
237
|
|
|
232
238
|
- **no resampling, no scaling.** A region's pixels are the loose PNG's pixels.
|
|
233
239
|
rigc writes no `scale:` line, so a pack cannot be coarser than its drawings the
|
|
234
|
-
way the shipped examples' packs are
|
|
240
|
+
way the shipped examples' packs are — what a `scale:` costs on the way back in
|
|
241
|
+
is §0.2.
|
|
235
242
|
- **no trimming.** `offsets:` always states a zero inset and the drawing's full
|
|
236
243
|
size. Stripping transparent border would shrink pages, and it would also make
|
|
237
244
|
every region attachment's quad depend on the packer — measure it yourself if you
|
|
@@ -312,6 +319,17 @@ texel count beside it so both numbers are visible:
|
|
|
312
319
|
measures the PNG. Reach for `--atlas-in` when the pack is what you were handed, or
|
|
313
320
|
when drawing through the pack's own texels is the point.
|
|
314
321
|
|
|
322
|
+
**What `--out` holds afterwards:** `skeleton.json` and a `skeleton.atlas` that is
|
|
323
|
+
the pack, page paths pointing back at the pack's own PNGs — so `rigc validate
|
|
324
|
+
<that directory>` reads it green with no flags, exactly as it reads a loose
|
|
325
|
+
build's. Add `--copy-images` and the pack's page PNGs are copied in beside the
|
|
326
|
+
skeleton and the page names become their basenames, which is the same directory
|
|
327
|
+
with nothing outside it left to resolve. ⚠️ Until
|
|
328
|
+
[#693](https://github.com/firejune/rigc/issues/693) that flag rebuilt the atlas
|
|
329
|
+
from the parts the rig declared instead of from the pack, and a rebuild through
|
|
330
|
+
`ingest --art none` declares none: the file written was **zero bytes**, on a build
|
|
331
|
+
that printed `PASS` for all four atlas assertions.
|
|
332
|
+
|
|
315
333
|
The emitted `skeleton.atlas` **is** the imported one, verbatim except for its page
|
|
316
334
|
name lines, which are paths and have to be re-anchored to `--out`. Fields rigc
|
|
317
335
|
does not re-serialise (`format:`, `repeat:`, and `scale:` itself) survive the trip
|
|
@@ -335,6 +353,28 @@ One limit, stated rather than discovered:
|
|
|
335
353
|
pack is a `DROP`, not a refusal — the same documented absence a missing PNG has
|
|
336
354
|
always been, and the line names the atlas rather than a file nobody opened.
|
|
337
355
|
|
|
356
|
+
⭐ **The missing PNG it is the same rule as, spelled out**, because it was left
|
|
357
|
+
implicit here and the two halves are read together. A `states:` entry whose PNG is
|
|
358
|
+
not on disk is a `DROP` too, and the line names the file rather than the pack:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# --atlas-in: DROP lens_l/shut: no region "lens_l_shut" in export/atlas.atlas (state not emitted)
|
|
362
|
+
# loose PNGs: DROP lens_l/shut: no PNG at parts/lens_l_shut.png (state not emitted)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Either way the slot is emitted **empty** and the build is green — which is right
|
|
366
|
+
while nothing poses that state, and wrong the moment something does. So when the
|
|
367
|
+
setup pose names it, the build is refused, and the refusal names the state and
|
|
368
|
+
what was consulted for it rather than the slot alone (§5.1). ⚠️ Its remedy is
|
|
369
|
+
**not** the `null` the neighbouring message offers: posing `null` builds a rig
|
|
370
|
+
with that part missing, at exit 0 and a green gate. Restore the art, or fix the
|
|
371
|
+
path the manifest states.
|
|
372
|
+
|
|
373
|
+
🔑 **A refused build prints its `DROP` lines too**, under the refusal on stderr,
|
|
374
|
+
and did not before [#671](https://github.com/firejune/rigc/issues/671): they were
|
|
375
|
+
reported from the compile result and a refusal returns none, so the one output
|
|
376
|
+
naming the file was missing from exactly the run that failed over it.
|
|
377
|
+
|
|
338
378
|
A **turned** region is not one of them, and used to be (issue #570). A pack made
|
|
339
379
|
by somebody else routinely rotates a region to save space — `rotate: 90`,
|
|
340
380
|
`rotate: 180`, `rotate: 270`, or the format's older `rotate: true` — and rigc
|
|
@@ -489,6 +529,17 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
489
529
|
extremes, how far each vertex moved and whether the winding survived
|
|
490
530
|
(**§4.11.2**). It takes no `--profile`, it never gates, and it does not write
|
|
491
531
|
anything — so the figures are readable on a build the gate is refusing.
|
|
532
|
+
|
|
533
|
+
⭐ **It reads what `ingest` writes, given the pack** — `--atlas-in <pack.atlas>`,
|
|
534
|
+
with `build`'s meaning (§0.2). That matters more here than it looks: printing the
|
|
535
|
+
`DEFORM` block means **posing** the rig, a pose resolves *every* attachment
|
|
536
|
+
against the atlas whether or not anything deforms it, and a spec written by
|
|
537
|
+
`ingest --art none` states sizes and names no image. So the size-only pair
|
|
538
|
+
`build --atlas-in` gates green is readable here through the same flag, and
|
|
539
|
+
without it the pair is refused by name at exit 2 rather than posed
|
|
540
|
+
([#697](https://github.com/firejune/rigc/issues/697), §5.1). ⚠️ `--profile`,
|
|
541
|
+
`--pack`, `--page-size`, `--padding` and `--copy-images` are `build`'s and are
|
|
542
|
+
not here: four of them decide what is *written*, and this command writes nothing.
|
|
492
543
|
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
493
544
|
sections (bones, slots, attachments, constraints, animations, events). It
|
|
494
545
|
deliberately does not combine them into a score: a rig with the right skeleton
|
|
@@ -935,6 +986,12 @@ A skin can also say which bones and constraints it **switches on**, and that nee
|
|
|
935
986
|
one more level, so a skin entry has a second spelling — see §3.4.1. The short one
|
|
936
987
|
above is unchanged and is what almost every rig wants.
|
|
937
988
|
|
|
989
|
+
🔸 **`default` is a name, not a requirement.** A rig may put every attachment in
|
|
990
|
+
named skins and declare no `default` at all, which is what an editor export of a
|
|
991
|
+
multi-skin character gives back; rigc still emits a `default` skin, empty, because
|
|
992
|
+
it always does. An animation keying such a slot resolves its attachment names
|
|
993
|
+
against every skin there is — §4.4 states that rule and §5.1 the one refusal left.
|
|
994
|
+
|
|
938
995
|
🔸 **A skin may fill no slot with anything that needs art, and that build is
|
|
939
996
|
green.** The atlas is built out of what the skins reference, so a rig whose skins
|
|
940
997
|
name no `image` — an empty `default`, or one carrying only a `boundingbox`, a
|
|
@@ -1153,6 +1210,110 @@ placed with its centre on the bone the slot names — which is also exactly wher
|
|
|
1153
1210
|
plain region attachment with no `x`/`y` would have drawn it. Measured on a ring, a
|
|
1154
1211
|
ribbon and a moved bone by `CT05` in the selftest (issue #1).
|
|
1155
1212
|
|
|
1213
|
+
#### `ring` — an aperture that opens inside a pinned seam
|
|
1214
|
+
|
|
1215
|
+
**When you need one:** a part with a hole in it that a bone opens and closes — an
|
|
1216
|
+
eye, a mouth, an iris. The silhouette stays exactly where the art put it and only
|
|
1217
|
+
the inside moves, which is the one thing a `contour` cannot do and authored
|
|
1218
|
+
`weights` can only do a vertex at a time.
|
|
1219
|
+
|
|
1220
|
+
Three rings and a hub, and every one of them is geometry rigc builds: the part
|
|
1221
|
+
window edge and the seam contour are pinned to the slot bone at weight 1, and the
|
|
1222
|
+
inner ring is shared with the control bone by a smoothstep falloff — flat at both
|
|
1223
|
+
ends, so the deformation dies into the pinned rim instead of creasing against it
|
|
1224
|
+
([`src/mesh.ts`](../src/mesh.ts)).
|
|
1225
|
+
|
|
1226
|
+
```json
|
|
1227
|
+
"generator": {
|
|
1228
|
+
"kind": "ring",
|
|
1229
|
+
"hull": [[24, 8], [40, 8], [56, 24], [56, 40], [40, 56], [24, 56], [8, 40], [8, 24]],
|
|
1230
|
+
"center": [32, 32],
|
|
1231
|
+
"inner": 0.45,
|
|
1232
|
+
"size": [64, 64],
|
|
1233
|
+
"controls": ["iris_aperture"]
|
|
1234
|
+
}
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
| Field | Meaning |
|
|
1238
|
+
| --- | --- |
|
|
1239
|
+
| `hull` | **required.** The seam contour, in part-local pixels, y down — the closed outline the aperture sits inside. At least 6 points, every one inside the window, and star-shaped about `center` |
|
|
1240
|
+
| `center` | **required.** The aperture centre, part-local pixels, y down |
|
|
1241
|
+
| `inner` | **required.** Where the moving ring sits between the centre (`0`) and the hull (`1`), strictly inside that interval. No default: a ring with no number here is refused, not centred |
|
|
1242
|
+
| `size` | **required.** The part window, `[w, h]` in pixels, which the UVs and the emitted `width`/`height` are taken from |
|
|
1243
|
+
| `bias` | optional; absent means authority is radial only. `{ "axis_deg": <screen degrees, y down>, "ramp": [d0, d1] }` — a line through `center` at that angle, with control authority 0 on its negative side and 1 on its positive side, smoothstepped across the signed distances `d0 < d1`. It is what lets a mouth open downward with the upper lip left pinned |
|
|
1244
|
+
| `controls` | **required.** The control bones, by name, at least one. Each must be a bone the rig declares. **More than one splits the ring by angle**, and the angle of each is measured from where the rig put that bone relative to `center` — so the split is a consequence of the skeleton and never a number you write here |
|
|
1245
|
+
|
|
1246
|
+
⚠️ **`size` is stated here, not measured.** A `contour` and a `grid` take the
|
|
1247
|
+
window off the attachment's own `image`; a `ring` and a `ribbon` are built from
|
|
1248
|
+
the numbers in this block and rigc compares neither to the PNG. The emitted
|
|
1249
|
+
`width`/`height` are this `size`, so a `size` that is not the art's own scales the
|
|
1250
|
+
drawing — measured: a 240x240 part declared `"size": [64, 64]` builds green under
|
|
1251
|
+
`--profile spine-html`. That is R1 rather than a gap: the compiler emits the
|
|
1252
|
+
number the spec states and does not re-measure a plate to overrule it.
|
|
1253
|
+
|
|
1254
|
+
⭐ **Several grips split the ring by where the rig put them, on this route as on
|
|
1255
|
+
the manifest one.** Each control bone's angle about `center` is measured from its
|
|
1256
|
+
own rest position — the window is centred on the slot bone (the ⭐ above), so a
|
|
1257
|
+
bone sitting below the aperture owns the arc below it — and authority is
|
|
1258
|
+
smoothstepped between the two bones either side of a vertex, so no grip creases
|
|
1259
|
+
against the next. Measured on a two-control ring whose grips sit 12px above and
|
|
1260
|
+
below the centre: 25 vertices, both grips bound, and of the 8 shared vertices off
|
|
1261
|
+
the centre line all 8 take more weight from the grip on their own side. Splitting
|
|
1262
|
+
**moves** authority rather than adding it: every vertex gives its controls the
|
|
1263
|
+
same total with one grip, two or three.
|
|
1264
|
+
|
|
1265
|
+
⚠️ **This route used to bind only the first name**
|
|
1266
|
+
([#684](https://github.com/firejune/rigc/issues/684)). It passed no angles at all,
|
|
1267
|
+
so the split collapsed onto `controls[0]` and the rest of the names reached the
|
|
1268
|
+
`MESH` line, the bone list and nothing else — and the gate was green, because a
|
|
1269
|
+
bone no vertex binds is in no weight, no sum and no index. `A20_MESH_WEIGHTS_COHERENT`
|
|
1270
|
+
now names it, so a ring that declares a grip and does not use it is a failure
|
|
1271
|
+
rather than a quiet stiffness.
|
|
1272
|
+
|
|
1273
|
+
**Stated limits, each a named refusal rather than a mesh that loads wrong:**
|
|
1274
|
+
|
|
1275
|
+
| The input | What you get |
|
|
1276
|
+
| --- | --- |
|
|
1277
|
+
| fewer than 6 hull points | `hull needs at least 6 points, got 4` |
|
|
1278
|
+
| `inner` at either end | `inner must be in (0,1), got 1` — at the hull it is the seam, at the centre it is a point |
|
|
1279
|
+
| a hull point outside the window | `hull point (99,8) is outside the 64x64 part window` |
|
|
1280
|
+
| a hull the centre cannot see all of | `hull is not star-shaped about the aperture centre; the inner ring would fold` — the inner ring is the hull scaled toward `center`, so an edge hidden from it crosses the rim and renders as folded meat |
|
|
1281
|
+
| a `bias` ramp that does not increase | `bias ramp must increase, got [16, 4]` |
|
|
1282
|
+
| a control bone the rig does not declare | `mesh bone "nobody" is not in the rig's bone list` |
|
|
1283
|
+
| two or more `controls`, one of them ON `center` | `control bone "iris_aperture" sits on the aperture centre, so it has no radial direction` — the position a lone control is supposed to occupy is the one position a split cannot use. With a single control it is never asked, so this refusal cannot fire on one |
|
|
1284
|
+
| two `controls` at the same angle about `center` | `two control bones share the angle 90 degrees about the aperture centre` — further out is not elsewhere: the arc between them is empty and one of them would bind nothing |
|
|
1285
|
+
|
|
1286
|
+
#### `ribbon` — a strip of cross rows riding a bone chain
|
|
1287
|
+
|
|
1288
|
+
**When you need one:** a part that hangs and swings off a chain — a tail, a
|
|
1289
|
+
strap, a lock of hair, a drip. The strip spans the part window and each cross row
|
|
1290
|
+
blends linearly between the two chain knots it falls between, so the whole part
|
|
1291
|
+
follows the chain with no weight written by hand.
|
|
1292
|
+
|
|
1293
|
+
The entry row is pinned to the slot bone at weight 1, which is what keeps the
|
|
1294
|
+
strip's origin where it was authored while the rest of it falls; that is the
|
|
1295
|
+
branch `A21_MESH_RIM_PINNED` measures on a ribbon, and
|
|
1296
|
+
`A28_RIBBON_ROWS_SHARE_WEIGHTS` is what holds both vertices of a row to the same
|
|
1297
|
+
blend.
|
|
1298
|
+
|
|
1299
|
+
```json
|
|
1300
|
+
"generator": { "kind": "ribbon", "size": [24, 180], "rows": 7, "chain": ["strap_a", "strap_b", "strap_c"] }
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
| Field | Meaning |
|
|
1304
|
+
| --- | --- |
|
|
1305
|
+
| `size` | **required.** The part window, `[w, h]` in pixels; the strip spans it, entry row at the top. Stated rather than measured, exactly as a `ring`'s is |
|
|
1306
|
+
| `rows` | **required.** How many cross rows, entry row first. A whole number, at least 3; the strip is `2 * (rows - 1)` triangles and `2 * rows` vertices, all of them on the outline |
|
|
1307
|
+
| `chain` | **required.** The bone chain the strip rides, **root first**, at least one name. The knots sit evenly along the strip, so the chain's order is the order the part falls in |
|
|
1308
|
+
|
|
1309
|
+
**Stated limits:**
|
|
1310
|
+
|
|
1311
|
+
| The input | What you get |
|
|
1312
|
+
| --- | --- |
|
|
1313
|
+
| fewer than 3 rows | `ribbon needs at least 3 rows, got 2` — two rows is one quad and has nothing to bend |
|
|
1314
|
+
| an empty `chain` | `ribbon needs at least one chain bone, got 0` |
|
|
1315
|
+
| a chain bone the rig does not declare | `mesh bone "nobody" is not in the rig's bone list` |
|
|
1316
|
+
|
|
1156
1317
|
#### `contour` — the mesh is the art's own silhouette
|
|
1157
1318
|
|
|
1158
1319
|
**When you need one:** the part's outline is the interesting thing and a
|
|
@@ -1804,7 +1965,7 @@ rigc emits all five: `ik`
|
|
|
1804
1965
|
([transform constraints](http://esotericsoftware.com/spine-transform-constraints)),
|
|
1805
1966
|
`path` ([path constraints](http://esotericsoftware.com/spine-path-constraints)),
|
|
1806
1967
|
`physics` ([physics constraints](http://esotericsoftware.com/spine-physics-constraints))
|
|
1807
|
-
and `slider`. Field lists are in [`src/rig.ts`](../src/rig.ts);
|
|
1968
|
+
and `slider`. Field lists are in [`src/rig.ts`](../src/rig.ts); the traps worth
|
|
1808
1969
|
carrying here:
|
|
1809
1970
|
|
|
1810
1971
|
- A transform constraint's `properties` names come from a fixed six — `rotate`,
|
|
@@ -1819,7 +1980,25 @@ carrying here:
|
|
|
1819
1980
|
`scaleY`, spelled `"none"`, `"uniform"` or `"volume"`. It is an enum resolved by
|
|
1820
1981
|
`Utils.enumValue`, so only the first letter's case is free and an unrecognised
|
|
1821
1982
|
name is assigned as `undefined` with no error; rigc checks it, like the three
|
|
1822
|
-
path modes below.
|
|
1983
|
+
path modes below.
|
|
1984
|
+
- A physics constraint carries seven **`*Global`** booleans —
|
|
1985
|
+
`inertiaGlobal`, `strengthGlobal`, `dampingGlobal`, `massGlobal`, `windGlobal`,
|
|
1986
|
+
`gravityGlobal` and `mixGlobal` — and "global" is not a scale or a space. Spine
|
|
1987
|
+
4.3 lets a physics timeline name **no** constraint (`animations.<a>.physics[""]`,
|
|
1988
|
+
which the parser reads as constraint index `-1`, `SkeletonJson.js:1048-1053`);
|
|
1989
|
+
such a timeline drives every active physics constraint whose matching flag is
|
|
1990
|
+
`true` and leaves the rest alone (`Animation.js:2066-2072`). So the flag is one
|
|
1991
|
+
constraint's **opt-in to being driven in bulk**, per tuning value, and it does
|
|
1992
|
+
nothing on its own. The parser's default for all seven is `false`.
|
|
1993
|
+
⚠️ **rigc emits them and cannot emit the timeline that reads them.** Every
|
|
1994
|
+
physics track in a motion spec names its constraint and that name is resolved
|
|
1995
|
+
against the rig — the empty name is refused, `animation "A" keys unknown physics
|
|
1996
|
+
constraint "" (the rig declares: …)` — so the flags are for a player, or a
|
|
1997
|
+
later hand-edit, that supplies one. What rigc does with them is **pass them
|
|
1998
|
+
through**, `false` included: measured, a constraint stating none emits none, and
|
|
1999
|
+
one stating `"windGlobal": false` emits `"windGlobal": false` rather than
|
|
2000
|
+
dropping it the way the motion spec's `physics` table drops a default (§4.6).
|
|
2001
|
+
- ⚠️ `src/rig.ts` called the physics `ScaleYMode` key **`scaleYMode`** until
|
|
1823
2002
|
issue #545 — the runtime's field name rather than the format's key — and nothing
|
|
1824
2003
|
read it, so a spec that wrote `scaleYMode` set no mode and said nothing. A rig
|
|
1825
2004
|
that still writes it is now refused by name, with `scaleY` beside it.
|
|
@@ -2383,7 +2562,7 @@ that SKIP, because the profile excludes an archetype assertion before its body
|
|
|
2383
2562
|
could notice the missing field (§5.2).
|
|
2384
2563
|
|
|
2385
2564
|
🚨 **The exception: `meshSlots` is required by a rig that invokes a mesh
|
|
2386
|
-
generator** (`ring`, `ribbon`, `contour` — §3.4), and it is a **compile-time**
|
|
2565
|
+
generator** (`ring`, `ribbon`, `contour`, `grid` — §3.4), and it is a **compile-time**
|
|
2387
2566
|
refusal rather than an assertion. Undeclared means a budget of zero, so the build
|
|
2388
2567
|
stops before the gate with `N mesh slot(s) emitted but the rig "X" allows 0`.
|
|
2389
2568
|
Geometry rigc built is geometry rigc will not ship unmeasured; geometry the author
|
|
@@ -2391,6 +2570,20 @@ drew is exempt, because rigc did not draw it. So `A13`'s **SKIP** means *this ri
|
|
|
2391
2570
|
is unmeasured*, not *this budget is inert* — those are two code paths with one
|
|
2392
2571
|
name, and reading the SKIP as the whole story is what issue #274 was.
|
|
2393
2572
|
|
|
2573
|
+
🔗 **`detached` is a list of forbidden parentages**, and its shape is
|
|
2574
|
+
`[{ "bone": …, "notUnder": …, "why"?: … }]`. `A25_DETACHED_BONE_PARENTAGE` walks
|
|
2575
|
+
`bone`'s parent chain and fails if `notUnder` is anywhere on it — an **ancestor**
|
|
2576
|
+
rule, not a direct-parent one, because a bone dragged by its grandparent is
|
|
2577
|
+
dragged just the same. A `bone` the rig does not declare is its own failure
|
|
2578
|
+
(`the rig declares "X" detached from "Y" but has no such bone`), so a rename that
|
|
2579
|
+
leaves this block behind is loud rather than quietly vacuous. `why` is optional
|
|
2580
|
+
here — unlike `deformMayFold`'s, which is required, and the difference is what
|
|
2581
|
+
each field does: this one turns a check ON for a pair, so an empty reason costs
|
|
2582
|
+
nothing but a reader's time. What it is for is a bone whose wrong parentage still
|
|
2583
|
+
loads and still animates and merely lies — something released into the world that
|
|
2584
|
+
must not ride the part that released it. [RIGGING.md](RIGGING.md) §10.3 has a
|
|
2585
|
+
worked one.
|
|
2586
|
+
|
|
2394
2587
|
🚨 **`deformMayFold` is the one field here that turns a check OFF**, so it is the
|
|
2395
2588
|
one field whose own shape is refused rather than skipped. It is
|
|
2396
2589
|
`[{ "slot": …, "why": … }]`, it exempts that slot from
|
|
@@ -2715,6 +2908,36 @@ different curves. The same applies to `scale`/`scalex`/`scaley` and
|
|
|
2715
2908
|
An `attachment` key carries no easing — attachment timelines are inherently
|
|
2716
2909
|
stepped.
|
|
2717
2910
|
|
|
2911
|
+
🔑 **An attachment key resolves against every skin, not against `default` alone.**
|
|
2912
|
+
A slot's `attachment` timeline carries a name and **no skin** — the format has no
|
|
2913
|
+
field for one — so the names a key may use are the union of every skin's
|
|
2914
|
+
placeholders for that slot, the default skin's included.
|
|
2915
|
+
`Skeleton.getAttachment` resolves the keyed name at run time through the skin the
|
|
2916
|
+
skeleton is **wearing** and, failing that, through `defaultSkin` (spine-core
|
|
2917
|
+
4.3.13 `Skeleton.js:335-346`), so which skin's art a key lands on is the
|
|
2918
|
+
consumer's, decided by dressing the skeleton rather than by the animation.
|
|
2919
|
+
|
|
2920
|
+
- **A name some skins fill and others do not is accepted, and that is the
|
|
2921
|
+
format's own semantics** rather than a hole in the check: under a skin that
|
|
2922
|
+
lacks it the slot shows nothing, which is exactly what a `null` key says and a
|
|
2923
|
+
thing a rig may well mean. What is refused is a name **no** skin holds, and the
|
|
2924
|
+
refusal says which skins were searched and what the slot does have (§5.1).
|
|
2925
|
+
- A rig with **no `default` skin at all** — every attachment in named skins,
|
|
2926
|
+
which is the shape an editor export of a multi-skin character gives back — is
|
|
2927
|
+
therefore a rig whose attachment keys work. Until
|
|
2928
|
+
[#695](https://github.com/firejune/rigc/issues/695) it was not: keys were
|
|
2929
|
+
checked against the default skin alone, so a named-skin name was refused as
|
|
2930
|
+
unknown, and a rig with no default skin had **every** attachment key refused,
|
|
2931
|
+
including ones whose art is in the first named skin. The setup pose resolved
|
|
2932
|
+
across skins the whole time (§4.2), so the two halves of one slot disagreed —
|
|
2933
|
+
`slots[].attachment: "plain"` was accepted and a key naming `plain` on that
|
|
2934
|
+
same slot was not.
|
|
2935
|
+
- ⚠️ **A `deform` track is the other way round and names its skin outright**
|
|
2936
|
+
(§4.11.5), because the format keys a deform timeline on a `skin/slot/attachment`
|
|
2937
|
+
triple and a deform run is geometry for one attachment object. An attachment
|
|
2938
|
+
timeline has no such field, which is why this one is a union and that one is a
|
|
2939
|
+
lookup.
|
|
2940
|
+
|
|
2718
2941
|
### 4.5 `keys` — times, values, curves
|
|
2719
2942
|
|
|
2720
2943
|
- `t` is in seconds and **must strictly increase** after `lag`/`stagger` are added.
|
|
@@ -4076,6 +4299,19 @@ at setup, key it somewhere.
|
|
|
4076
4299
|
|
|
4077
4300
|
Failures arrive in two layers, and they read differently.
|
|
4078
4301
|
|
|
4302
|
+
📥 **A third surface is `ingest`'s, and it is not a failure at all.** What a skeleton
|
|
4303
|
+
holds and the spec format cannot say is **recorded** rather than refused — one coded
|
|
4304
|
+
line per finding, and a non-zero exit only when one of them is a blocker (§0.3's gutter
|
|
4305
|
+
table is the three kinds); both specs are written either way. So there is no file to
|
|
4306
|
+
change for one of those, there is a decision to make. (`ingest` does refuse one thing,
|
|
4307
|
+
and it is not about the file's contents: an *option* that contradicts the file, such as
|
|
4308
|
+
`--stage` beside a box the skeleton declares.)
|
|
4309
|
+
Every code it can print, with its gutter, whether it changes the exit code, what it
|
|
4310
|
+
means and what to do, is the finding-code table in `docs/INGEST.md` §2.0. (Spelled
|
|
4311
|
+
rather than linked, like the references at the top of this page: that page cites the
|
|
4312
|
+
corpus inventory, and a ladder run reads this guide without following its references
|
|
4313
|
+
out of it.)
|
|
4314
|
+
|
|
4079
4315
|
### 5.1 Compile errors — before the gate
|
|
4080
4316
|
|
|
4081
4317
|
A `CompileError` names the object and the field, and nothing is written.
|
|
@@ -4112,6 +4348,40 @@ a real key in the wrong case leads the list), and the shape's whole key set is
|
|
|
4112
4348
|
printed after. What it will *not* do is guess — a key four edits from anything gets
|
|
4113
4349
|
no suggestion, only the set.
|
|
4114
4350
|
|
|
4351
|
+
⭐ **The scan runs before the required keys are asked for, in both files**, and
|
|
4352
|
+
that is what decides which message a *misspelled* required key gets. Misspelling
|
|
4353
|
+
one is the commonest way to lose it, so it is refused as the typo it is rather than
|
|
4354
|
+
as the thing it lost:
|
|
4355
|
+
|
|
4356
|
+
```
|
|
4357
|
+
rigc compile error: rig.json: this rig spec has a key this compiler does not read:
|
|
4358
|
+
"slot" (did you mean "slots"?). Nothing reads such a key, so it would be dropped
|
|
4359
|
+
from the emitted skeleton in silence — fix the spelling or remove it. Known here:
|
|
4360
|
+
bones, constraints, events, images, invariants, name, note, skeleton, skins, slots,
|
|
4361
|
+
spec.
|
|
4362
|
+
```
|
|
4363
|
+
|
|
4364
|
+
A key that is **genuinely** absent has no near miss to name, so the scan finds
|
|
4365
|
+
nothing and the required-key message below it is what arrives — `a rig spec needs a
|
|
4366
|
+
"slots" array (it may be empty; its ORDER is the draw order)` here, and
|
|
4367
|
+
`` `animations` is absent; … `` in a motion spec. Read the two apart by that:
|
|
4368
|
+
**"does not read"** means the key *is* in your file, spelt some other way, and
|
|
4369
|
+
**"needs a …"** / **"is absent"** means it is not in the file at all. Where both
|
|
4370
|
+
are true at once — a typo beside a key that really is missing — the typo is named
|
|
4371
|
+
first, because it is the one you can fix without re-reading the file. Until
|
|
4372
|
+
[#672](https://github.com/firejune/rigc/issues/672) the rig spec's required-key
|
|
4373
|
+
checks ran *above* its scan, so `"slot"` for `"slots"` printed the sentence asking
|
|
4374
|
+
for an array the file already had, one character off, and never named the stray
|
|
4375
|
+
key; the motion spec has always had this order.
|
|
4376
|
+
|
|
4377
|
+
⚠️ One check stays above the scan in both parsers: the `spec` version tag. The
|
|
4378
|
+
version is what decides *which* key set applies, so a file declaring a version this
|
|
4379
|
+
compiler does not know is refused as that — `unknown rig spec version "rigc-rig/2",
|
|
4380
|
+
expected "rigc-rig/1"` — rather than key by key against a set it never claimed. The
|
|
4381
|
+
price is paid by one spelling: a misspelt `spec` key itself reads as `unknown rig
|
|
4382
|
+
spec version undefined, expected "rigc-rig/1"`, which is true and one line short of
|
|
4383
|
+
naming the typo.
|
|
4384
|
+
|
|
4115
4385
|
⚠️ There is **no forward-compatibility escape**, and no `note` field except where
|
|
4116
4386
|
one is listed: a rig spec's root, a motion spec's root, an animation, and a
|
|
4117
4387
|
`physics` tuning entry. Prose anywhere else has to go in a document, because a key
|
|
@@ -4163,11 +4433,15 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4163
4433
|
|
|
4164
4434
|
| Message | What to change |
|
|
4165
4435
|
| --- | --- |
|
|
4436
|
+
| `a rig spec needs a "name" — a motion spec names it to pick this rig` | §3 — name the rig, and make the motion spec's `archetype` equal it |
|
|
4437
|
+
| `a rig spec needs a non-empty "bones" array` | §3.2 — a rig with no bones has nothing to hang a slot on; every one has at least a root |
|
|
4438
|
+
| `a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)` | §3.3 — write `[]` for a rig that draws nothing. ⚠️ These three arrive only when the key is really absent: **misspelt**, it is the unknown-key refusal above, naming what you wrote |
|
|
4166
4439
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
4167
4440
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
4168
4441
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
4169
4442
|
| `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. A slot **nothing** fills is exempt: its setup pose can only be "show nothing" and is not asked for |
|
|
4170
|
-
| `the setup pose shows attachment "A" on slot "X", which no skin and no manifest part fills` | §3.3 — the slot is emitted empty, so `A` resolves to nothing. Give the slot an attachment (a skin entry or a manifest part), or state the setup pose as `null` |
|
|
4443
|
+
| `the setup pose shows attachment "A" on slot "X", which no skin and no manifest part fills` | §3.3 — the slot is emitted empty and nothing was ever going to fill it, so `A` resolves to nothing. Give the slot an attachment (a skin entry or a manifest part), or state the setup pose as `null` |
|
|
4444
|
+
| `the setup pose shows attachment "A" on slot "X", and the slot is emitted empty because the one manifest state that fills it has no art: "s" (no PNG at p)` | §0.2 — a **different** fault with a similar shape: a manifest part does fill this slot and its art was not where the manifest pointed, so every state of it was dropped. **Restore the file or fix the path** — posing `null` compiles, but it ships the rig with that part missing and the gate green. The path printed is the one rigc tried, which is what tells a deleted file from a misspelt `states:` entry; `all N manifest states that fill it have no art` is the same message where the part has more than one. Under `--atlas-in` the same sentence reads `no region "R" in <atlas>` and asks you to add the region to the pack — no file was opened ([#671](https://github.com/firejune/rigc/issues/671)) |
|
|
4171
4445
|
| `a region needs width and height — give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
|
|
4172
4446
|
| `a mesh needs width and height — give them, or give an "image" and rigc will measure the PNG` | §3.4 — the same rule for a mesh |
|
|
4173
4447
|
| `"type" is null, which is not a name. An attachment's type is one of region, mesh, linkedmesh, … or the key is absent and reads as "region"` | §6 — **remove the key**. Absent is the format's own default; present-and-null matches no parser case and the attachment is dropped in silence |
|
|
@@ -4177,13 +4451,18 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4177
4451
|
| `hull vertices must come first; vertex i is on the boundary and vertex j is not. The triangles' outline runs …: list those K vertices first, in that order, then the M interior vertices` | §3.4 — renumber the vertices: the printed walk first, then the interior |
|
|
4178
4452
|
| `hull vertices must trace the outline in order; the triangles' outline runs …, so vertex a has to follow vertex b in the list, and vertex c does` | §3.4 — renumber along the printed walk |
|
|
4179
4453
|
| `the triangles do not tile the outline: …` / `the triangles' outline is not one closed loop: …` | §3.4 — a doubled triangle, an unused vertex, a pinch or a hole in `triangles` |
|
|
4454
|
+
| `vertex N binds bone "X", which the rig does not declare as a bone` | §3.4 — an authored mesh's `weights` bind by NAME, like everything else in a rig spec. Fix the spelling, or declare the bone. The message names the skin, the slot, the placeholder and the vertex, because an index would name none of them |
|
|
4180
4455
|
| `image "X.png" is not on disk at …` | fix the name, or point `--images` at the right directory |
|
|
4456
|
+
| `parts/iris_open.png is 96x64 but slot "iris" declares 96x60` | R5 — a manifest `states:` entry whose art is not the window the part declares. Re-export the PNG, or fix the part's `size`; a quad sized against art of another size is the silence `A06` exists for, and the window is what the quad is built from |
|
|
4457
|
+
| `plates/00_stage.png is 256x256 but the manifest window for "stage" is 250x256` | R5 — the same check on the part's unconditional `image`, against the window the crop gives it |
|
|
4458
|
+
| `region "00_stage" of <pack>.atlas (declared 256x256 by its offsets) is 256x256 but the manifest window for "stage" is 250x256` | R5 — the row above under `--atlas-in`, and the prefix is the whole point: it says which of the two rigc **measured**, because the remedy differs. A bare path is a loose PNG it opened and you re-export; a `region … of <pack>` was read out of the pack, and you repack or aim the part at another region. This is the size row of §0.2's four, with the message it actually prints |
|
|
4181
4459
|
| `duplicate region name "X"` | two PNGs share a basename; one part, one page, one name |
|
|
4182
4460
|
| `"b/X.png" and the art already atlased as region "X" are two different files — … Rename one of the PNGs.` | R5 — the region name is the basename, so only one of the two can hold it. Rename a file (not a placeholder: the placeholder is free to repeat) |
|
|
4183
|
-
| `the image "X.png" was never added to the atlas, so there is no region "X" …` | nothing in the spec — every image an attachment names is measured, so this says rigc skipped one. Report it
|
|
4461
|
+
| `the image "X.png" was never added to the atlas, so there is no region "X" …` | nothing in the spec — every image an attachment names is measured and atlased, one per file whichever skin names it, so this says rigc skipped one. Report it on [the tracker](https://github.com/firejune/rigc/issues). The `#555` the message itself cites is the change that made one-per-file an invariant, not a place to file against |
|
|
4184
4462
|
| `motion spec names archetype "A" but the rig spec at … is called "B"` | make `archetype` equal the rig's `name` |
|
|
4185
4463
|
| `animation "A" declares duration Ns but its last key is at Ms` | R7 — fix whichever of the two you meant |
|
|
4186
4464
|
| `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` |
|
|
4465
|
+
| `animation "A" slot "X" attachment: attachment "N" is not in slot "X" under any skin (searched: default, alt) — the slot has: plain, trim` | §4.4 — the keyed name is in **no** skin, and the two clauses say where the compiler looked and what it would have taken. Fix the spelling, or give some skin a placeholder called `N`. A name only a NAMED skin fills is not this error and never was one to fix — it compiles, and the slot shows nothing under the skins that lack it. Before [#695](https://github.com/firejune/rigc/issues/695) the message read `attachment "N" is not in slot "X"` and was raised against the **default skin alone**, so it fired on correct rigs: any key into named-skin art, and every key in a rig with no default skin. `the slot has no attachments at all` is the same message where nothing fills the slot |
|
|
4187
4466
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
4188
4467
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
4189
4468
|
| `animation "A" physics constraint "C" mass key at t=… is 0 (massInverse Infinity); must be > 0 — …` | §4.4 — a keyed physics value the runtime cannot use. The message names the bound and the `PhysicsConstraint.js` lines that make it one: `mass` and `strength` are `> 0`, `damping` is inside `(0, 1)`, `mix` is `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)) |
|
|
@@ -4254,6 +4533,35 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4254
4533
|
| `slot "patch": placeholder "patch" is filled by the "default" skin AND by skins "zulu", "mike", and the Spine editor has no way to hold that … Move the default skin's entry for this slot into a named skin — call it "base"` | **R12** — do what it says: move that entry out of `default` into a named skin. The editor has no representation for a placeholder the default skin shares with a named one, in either spelling, and §3.4.2 has both measurements. Renaming the placeholder does not help; the shape is what is refused |
|
|
4255
4534
|
| `N attachment name collision(s): a placeholder that more than one skin fills is emitted with the name "<skin>/<placeholder>" … slot "patch": skin "base" placeholder "zulu/patch" and skin "zulu" placeholder "patch" would both be named "zulu/patch"` | **R12** — rename the placeholder or the skin. rigc composes an attachment name for every placeholder more than one skin fills (§3.4.2), and this fires when a composed name is one another entry in the same slot already answers to — including a plain name in the default skin, which composed nothing. Both sites are named; either rename ends it |
|
|
4256
4535
|
|
|
4536
|
+
⚠️ **One refusal in this section is not a `CompileError`, and it is `explain`'s.**
|
|
4537
|
+
`explain` prints the `DEFORM` block by **posing** the rig, and a pose resolves every
|
|
4538
|
+
attachment against the atlas — so it needs the art `build` needs, reached the same
|
|
4539
|
+
two ways (§0.2). A pair whose art it cannot resolve is refused **before a line of
|
|
4540
|
+
the report**, at **exit 2**, in rigc's own sentence:
|
|
4541
|
+
|
|
4542
|
+
```
|
|
4543
|
+
rigc explain: skin "default" slot "lamp" placeholder "shade": attachment "shade"
|
|
4544
|
+
wants region "shade", which this build's atlas does not have (it declares no region
|
|
4545
|
+
at all). `explain` poses the rig to measure its deform keys and a pose resolves
|
|
4546
|
+
every attachment against the atlas, so there is nothing to pose it against. Art
|
|
4547
|
+
reaches a compile two ways and this run took neither: `--atlas-in <pack.atlas>`
|
|
4548
|
+
resolves the parts against a pack somebody already made, and an "image" per
|
|
4549
|
+
attachment resolves them as loose PNGs under `--images <dir>` — a spec that states
|
|
4550
|
+
a size and names no image is what `ingest --art none` writes, and `--atlas-in` is
|
|
4551
|
+
what reads it. 1 of 1 attachment lookup(s) here resolve to no region.
|
|
4552
|
+
```
|
|
4553
|
+
|
|
4554
|
+
Both halves of it are measured rather than fixed text: the count is this pair's, and
|
|
4555
|
+
where the atlas has a **near miss** the sentence prints that too, in `A08`'s own
|
|
4556
|
+
clause and `A08`'s own words. When `--atlas-in` **was** given and the region is still
|
|
4557
|
+
absent, the second half names the pack instead and asks you to fix the spec's region
|
|
4558
|
+
name or point the flag at the pack that has it. Until
|
|
4559
|
+
[#697](https://github.com/firejune/rigc/issues/697) there was no rigc sentence at
|
|
4560
|
+
all: the report printed in full and the run then died inside `AtlasAttachmentLoader`
|
|
4561
|
+
with `Region not found in atlas: shade (attachment: shade)` and a spine-core stack
|
|
4562
|
+
trace, at exit 1 — the runtime's message about rigc's internals standing in for
|
|
4563
|
+
rigc's message about your two files.
|
|
4564
|
+
|
|
4257
4565
|
### 5.2 Assertions — the gate
|
|
4258
4566
|
|
|
4259
4567
|
The report prints one line per assertion:
|
|
@@ -4334,7 +4642,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4334
4642
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) — as it is for `A06`, `A19` and `A27`; see `A07` ([#608](https://github.com/firejune/rigc/issues/608)) |
|
|
4335
4643
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
4336
4644
|
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)) **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4337
|
-
| `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,
|
|
4645
|
+
| `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, a binding at weight 0, or **a bone the mesh declares that no vertex binds** — `mesh "x" declares bone "grip_b" and none of its 25 vertices binds it; the weights reference "box", "grip_a"`. Those three are one sentence about rigc's own generators: the bone set a generated mesh declares is the bone set its weights reference, so a `controls` or `chain` name that moves nothing is a defect where a foreign mesh's is not ([#684](https://github.com/firejune/rigc/issues/684)). Fix the rig spec's `controls`/`chain`, or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4338
4646
|
| `A21_MESH_RIM_PINNED` | archetype | a generated ring's rim, a ribbon's entry row, or a contour's outline (which is all of it) is not pinned to its anchor bone at weight 1 |
|
|
4339
4647
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4340
4648
|
| `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 — **at rest, and on every physics timeline key** ([#610](https://github.com/firejune/rigc/issues/610)). The timeline arm reads each key through the runtime's own `PhysicsConstraint*Timeline.set`, so a keyed `mass` is judged as the `massInverse` it becomes, and the detail names the animation, the constraint, the key time, the value and the bound. One difference between the two arms, and the runtime is the reason for it: a **key** of `mix: 0` is accepted, because `update` opens with `if (mix === 0) return;` and muting a constraint for a stretch is what a mix timeline is for — the editor's own `sack-pro` example keys it there on 24 of its 36 mix keys. `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. **SKIP** when the skeleton declares no physics constraint ([#580](https://github.com/firejune/rigc/issues/580)) — the same sentence `A36` and `A37` have always printed for their own constraint types |
|
|
@@ -5657,6 +5965,11 @@ line whenever the MAE is flat and something still looks wrong: a flat MAE says t
|
|
|
5657
5965
|
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
5658
5966
|
and blinks where the reference does.
|
|
5659
5967
|
|
|
5968
|
+
⭐ **And read it when the MAE is loud, because it is what says *which kind* of loud.**
|
|
5969
|
+
It is the only column that separates a wrong curve from a key moved in time — the two
|
|
5970
|
+
builds under *The floor every figure here is read against* are that pair, and the
|
|
5971
|
+
drift column reports 31.1 px and 32.1 px for them.
|
|
5972
|
+
|
|
5660
5973
|
⚠️ **Each set is compared against ITSELF, so on a shot committed at two rates a hold
|
|
5661
5974
|
can exist in one set and not the other.** The coarse set samples every other frame of
|
|
5662
5975
|
the fine one, so a pair the coarse set holds across is a constraint between samples
|
|
@@ -5721,6 +6034,13 @@ There are two matchers and the `how` column says which one answered:
|
|
|
5721
6034
|
confidence: how much better the winning position was than the best rival inside
|
|
5722
6035
|
the search window. This is what gives a shot like a chain of touching links any
|
|
5723
6036
|
drift at all — under connected components alone, every frame of it is ambiguous.
|
|
6037
|
+
⭐ **Only the pixels of it your own composite lets show are correlated**: a
|
|
6038
|
+
template pixel you draw something over cannot match the reference wherever the
|
|
6039
|
+
slot really is, so it adds the same residual at every offset — and, because
|
|
6040
|
+
sliding the template moves those samples onto other pixels, their gradient
|
|
6041
|
+
decides the winner wherever the visible basin is shallow. That is not a
|
|
6042
|
+
hypothetical: it is what made four of the seven examples in this repository
|
|
6043
|
+
report 0.8–2.2 px against frames rendered from themselves (issue #698).
|
|
5724
6044
|
|
|
5725
6045
|
⚠️ **Both matchers are capped, and a blank is a real answer.** A part can be
|
|
5726
6046
|
displaced by about its own size and still be that part in the picture; past that,
|
|
@@ -5730,11 +6050,157 @@ the 47 px course as its drift. The bar rises with the distance being claimed: a
|
|
|
5730
6050
|
peak sitting where you already drew the slot only has to confirm it, a peak
|
|
5731
6051
|
claiming the part moved most of a radius has to be distinctive to be believed.
|
|
5732
6052
|
|
|
6053
|
+
⚠️ **And a slot you cover completely has no drift to report at all.** If every
|
|
6054
|
+
pixel a slot draws is painted over by something later in your own draw order,
|
|
6055
|
+
none of its ink reaches the picture, so there is nothing to correlate — `check`
|
|
6056
|
+
says so by name and counts it out, rather than correlating hidden pixels against
|
|
6057
|
+
whatever is on top of them:
|
|
6058
|
+
|
|
6059
|
+
```
|
|
6060
|
+
its drift is not measurable — every one of the 2116 px this 48x54 px slot draws is
|
|
6061
|
+
covered by something the candidate draws over it, so none of its own ink reaches the
|
|
6062
|
+
picture to be correlated against
|
|
6063
|
+
```
|
|
6064
|
+
|
|
6065
|
+
That is a fact about the shot and not an error: a part behind another part is
|
|
6066
|
+
still where the rig put it, and the answer to *where did it land* is that this
|
|
6067
|
+
run cannot say. Read it beside the `slots` column, which counts it as
|
|
6068
|
+
unattributed.
|
|
6069
|
+
|
|
5733
6070
|
The `slots` column is how many of the slots you drew got an answer at all, and the
|
|
5734
6071
|
summary line carries the same denominator. `N reference component(s) no slot
|
|
5735
6072
|
reaches` means the reference frame contains something none of your slots overlaps:
|
|
5736
6073
|
a part you have not authored, or one you have put somewhere else entirely.
|
|
5737
6074
|
|
|
6075
|
+
🚩 **The floor every figure here is read against, and how to measure it.** `check`
|
|
6076
|
+
grades nothing — `rigc check --help` says so, and the exit code is 0 on any run that
|
|
6077
|
+
could be made — so a figure means something only beside one you took yourself. The
|
|
6078
|
+
cheapest one to take is the **identity run**: a build compared against frames
|
|
6079
|
+
rendered from that same build. It is what this instrument says when the true answer
|
|
6080
|
+
is *nothing moved*.
|
|
6081
|
+
|
|
6082
|
+
```bash
|
|
6083
|
+
bun cli.ts build --rig gallery/squash/rig.json --motion gallery/squash/motion.json --out <build>
|
|
6084
|
+
bun cli.ts render --candidate <build> --animation bounce --fps 12 --out <frames>
|
|
6085
|
+
bun cli.ts check --candidate <build> --frames <frames>
|
|
6086
|
+
```
|
|
6087
|
+
|
|
6088
|
+
```
|
|
6089
|
+
── bounce — candidate animation "bounce", 12 fps ──
|
|
6090
|
+
frames 11 on disk, candidate samples 11, 11 compared
|
|
6091
|
+
MAE mean 0.00 worst 0.00 (exact: none of the 11 compared frame(s) differs from the reference) (0..255 over the union alpha; over the whole frame, mean 0.00)
|
|
6092
|
+
⤷ over the REFERENCE's own drawn pixels, mean 0.00 — the union figure compares two builds of the same rig; this one is the one to optimise against, because the union is yours to grow.
|
|
6093
|
+
slot drift worst 0.5 px "plate" at f0004
|
|
6094
|
+
⤷ bounded by 0.71 px — the correlation put this slot where the candidate drew it, so the whole figure is the sub-pixel step, clamped to 0.5 px on each axis
|
|
6095
|
+
per-frame all 10 adjacent pair(s) change by as much as the reference's own frames do
|
|
6096
|
+
```
|
|
6097
|
+
|
|
6098
|
+
⚠️ **The MAE floor is zero and the slot-drift floor is not**, and the second half of
|
|
6099
|
+
that is the instrument's own arithmetic rather than anything about your rig. Both
|
|
6100
|
+
sides are the same pixels, so every frame's MAE is exactly 0 — which is why the line
|
|
6101
|
+
says `(exact)` instead of naming a frame. The drift is a **correlation**: it finds
|
|
6102
|
+
the whole-pixel offset that matches best and then fits a parabola through three
|
|
6103
|
+
whole-pixel residuals for the fraction, clamped to half a pixel on each axis.
|
|
6104
|
+
|
|
6105
|
+
🔑 **So the bound has two halves, and the run states both of them for you.** The
|
|
6106
|
+
`⤷ bounded by` line is not a constant off this page: it is `hypot(|dx| + 0.5,
|
|
6107
|
+
|dy| + 0.5)` for that match's own whole-pixel winner `(dx, dy)`. A winner at the
|
|
6108
|
+
offset you drew — which is the answer on every frame of a correct rig — bounds the
|
|
6109
|
+
whole figure at `hypot(0.5, 0.5) = 0.71 px`, and the line says the figure is the
|
|
6110
|
+
sub-pixel step and nothing else. A winner one pixel out bounds it at 1.58 px and
|
|
6111
|
+
says the correlation moved the part. ⇒ **Read a drift against the line under it,
|
|
6112
|
+
never against a number from a page about another rig.** A component match prints
|
|
6113
|
+
the other sentence — two centroids have no such bound, only the search radius.
|
|
6114
|
+
|
|
6115
|
+
🚨 **The clamp bounds the figure only while the whole-pixel winner is the
|
|
6116
|
+
identity, and for a while nothing checked that second half.** Issue #698: four of
|
|
6117
|
+
the seven examples in this repository read **0.81, 1.11, 2.13 and 2.21 px** against
|
|
6118
|
+
frames rendered from themselves, because the template carried the pixels the
|
|
6119
|
+
candidate draws *over itself*. Those match nothing wherever the slot really is, so
|
|
6120
|
+
they add a residual at every offset — and sliding the template moves them onto
|
|
6121
|
+
other pixels, so their gradient walks the winner off the origin wherever the
|
|
6122
|
+
visible basin is shallow. The exhaustive whole-pixel field for `flex`'s backdrop
|
|
6123
|
+
had its minimum at `(2, 0)` scoring 7.75 against the identity offset's 8.76.
|
|
6124
|
+
Correlating only what shows put all seven back on `(0, 0)`, and `C25`–`C30` of
|
|
6125
|
+
the repository's own selftest gate that over every example it ships — so the next
|
|
6126
|
+
one is gated by arriving.
|
|
6127
|
+
|
|
6128
|
+
🔸 The same page said `3.7 px` before issue #678, for an unrelated reason worth
|
|
6129
|
+
keeping: the coarse sweep started at `−radius` and stepped by its stride, so the
|
|
6130
|
+
identity offset was on the lattice only when the stride divided the radius, and
|
|
6131
|
+
the `±1` refinement around a winner two pixels out could not reach back to it.
|
|
6132
|
+
|
|
6133
|
+
**A second example, and the one the repair was measured on.** `gallery/flex` draws
|
|
6134
|
+
a banner and a leaf over a full-stage backdrop, so almost every slot of it reaches
|
|
6135
|
+
the template matcher:
|
|
6136
|
+
|
|
6137
|
+
```bash
|
|
6138
|
+
bun cli.ts build --rig gallery/flex/rig.json --motion gallery/flex/motion.json --out <build>
|
|
6139
|
+
bun cli.ts render --candidate <build> --animation wave --fps 12 --out <frames>
|
|
6140
|
+
bun cli.ts check --candidate <build> --frames <frames>
|
|
6141
|
+
```
|
|
6142
|
+
|
|
6143
|
+
```
|
|
6144
|
+
── wave — candidate animation "wave", 12 fps ──
|
|
6145
|
+
frames 30 on disk, candidate samples 30, 30 compared
|
|
6146
|
+
MAE mean 0.00 worst 0.00 (exact: none of the 30 compared frame(s) differs from the reference) (0..255 over the union alpha; over the whole frame, mean 0.00)
|
|
6147
|
+
⤷ over the REFERENCE's own drawn pixels, mean 0.00 — the union figure compares two builds of the same rig; this one is the one to optimise against, because the union is yours to grow.
|
|
6148
|
+
slot drift worst 0.2 px "plate" at f0009
|
|
6149
|
+
⤷ bounded by 0.71 px — the correlation put this slot where the candidate drew it, so the whole figure is the sub-pixel step, clamped to 0.5 px on each axis
|
|
6150
|
+
per-frame all 29 adjacent pair(s) change by as much as the reference's own frames do
|
|
6151
|
+
```
|
|
6152
|
+
|
|
6153
|
+
That figure was **2.21 px** on the same command before #698, on the same slot.
|
|
6154
|
+
|
|
6155
|
+
⭐ **And the same frames plus two deliberately wrong builds are what say which column
|
|
6156
|
+
answers which question.** Each differs from the build above in exactly one way —
|
|
6157
|
+
every easing reversed `(x1,y1,x2,y2) → (1−x2,1−y2,1−x1,1−y1)`, and the ball's bottom
|
|
6158
|
+
key moved from 0.4 s to 0.62 s — and each is checked against the same identity
|
|
6159
|
+
frames. Both gate green and both exit 0.
|
|
6160
|
+
|
|
6161
|
+
**No run reproduces this:** the candidate is `gallery/squash` with the three curves in its `easings` block reflected through the diagonal, which is an edit to a spec this repository does not carry a second copy of
|
|
6162
|
+
|
|
6163
|
+
```
|
|
6164
|
+
── bounce — candidate animation "bounce", 12 fps ──
|
|
6165
|
+
frames 11 on disk, candidate samples 11, 11 compared
|
|
6166
|
+
MAE mean 6.69 worst 11.71 at f0007 (0..255 over the union alpha; over the whole frame, mean 5.74)
|
|
6167
|
+
⤷ over the REFERENCE's own drawn pixels, mean 6.69 — the union figure compares two builds of the same rig; this one is the one to optimise against, because the union is yours to grow.
|
|
6168
|
+
slot drift worst 31.1 px "ball" at f0006
|
|
6169
|
+
per-frame 1 of 10 adjacent pair(s) change by a different amount than the reference does; worst f0010, yours moved 4152 px where the reference moved 912
|
|
6170
|
+
```
|
|
6171
|
+
|
|
6172
|
+
**No run reproduces this:** the candidate is `gallery/squash` with the ball's `translatey` key at 0.4 s moved to 0.62 s, which is an edit to a spec this repository does not carry a second copy of
|
|
6173
|
+
|
|
6174
|
+
```
|
|
6175
|
+
── bounce — candidate animation "bounce", 12 fps ──
|
|
6176
|
+
frames 11 on disk, candidate samples 11, 11 compared
|
|
6177
|
+
MAE mean 2.15 worst 6.38 at f0007 (0..255 over the union alpha; over the whole frame, mean 1.84)
|
|
6178
|
+
⤷ over the REFERENCE's own drawn pixels, mean 2.15 — the union figure compares two builds of the same rig; this one is the one to optimise against, because the union is yours to grow.
|
|
6179
|
+
slot drift worst 32.1 px "ball" at f0007
|
|
6180
|
+
per-frame all 10 adjacent pair(s) change by as much as the reference's own frames do
|
|
6181
|
+
```
|
|
6182
|
+
|
|
6183
|
+
⇒ **`per-frame` is the column that separates a wrong curve from a moved key, and it
|
|
6184
|
+
is the only one of the three that does.** The `MAE` says both are wrong and says it
|
|
6185
|
+
threefold louder for the curve (6.69 against 2.15). `slot drift` cannot tell them
|
|
6186
|
+
apart at all — 31.1 px against 32.1 px, the same part, a frame either side — because
|
|
6187
|
+
both put the ball somewhere it is not at the moment being compared. `per-frame` fires
|
|
6188
|
+
on the reversed easings alone, and it is measuring the one thing the two defects do
|
|
6189
|
+
not share: how far each side moved **since its own previous frame**. A key moved in
|
|
6190
|
+
time changes *when* the poses arrive and not how fast the shot travels between two
|
|
6191
|
+
samples, so the column is correctly silent on it. ⇒ Read the pair together: a loud
|
|
6192
|
+
MAE with `per-frame` silent is a pose in the wrong place or at the wrong moment; a
|
|
6193
|
+
loud MAE with `per-frame` firing is a **speed**, which is a curve.
|
|
6194
|
+
|
|
6195
|
+
🔸 Neither block above reprints its own `⤷ bounded by` line, for the same reason
|
|
6196
|
+
both carry the declaration: the spec edits are not in the tree, so those two lines
|
|
6197
|
+
would be hand-written rather than taken. What the gate measures on them is stated
|
|
6198
|
+
instead — the worst match of each sits at whole-pixel `(0, −31)` and `(0, −32)`,
|
|
6199
|
+
which bounds them at **31.50 px** and **32.50 px**. That is the contrast worth
|
|
6200
|
+
carrying away: a correct rig's drift is bounded by the clamp because its winner is
|
|
6201
|
+
the identity, and these two are bounded by nothing of the kind because a part
|
|
6202
|
+
really moved.
|
|
6203
|
+
|
|
5738
6204
|
**The `chains` block is the same two measures on the unit you actually repair.**
|
|
5739
6205
|
|
|
5740
6206
|
```
|