spine-rigc 0.25.5 → 0.26.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 +10 -7
- package/cli.ts +149 -16
- package/docs/AUTHORING.md +363 -61
- package/docs/INGEST.md +13 -4
- package/docs/SPEC_COVERAGE.md +8 -6
- package/package.json +1 -1
- package/src/atlas.ts +11 -2
- package/src/check.ts +59 -1
- package/src/compile.ts +513 -107
- package/src/diff.ts +14 -1
- package/src/emit.ts +76 -41
- package/src/ingest.ts +126 -14
- package/src/mesh.ts +40 -0
- package/src/render.ts +69 -7
- package/src/rig.ts +164 -32
- package/src/slots.ts +169 -27
- package/src/types.ts +41 -0
- package/src/validate.ts +439 -46
package/docs/AUTHORING.md
CHANGED
|
@@ -160,16 +160,16 @@ What the flags mean:
|
|
|
160
160
|
| `--rig` | the rig spec — skeleton structure |
|
|
161
161
|
| `--motion` | the motion spec — time |
|
|
162
162
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths and `skeleton.images` are written relative to it |
|
|
163
|
-
| `--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) |
|
|
164
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** |
|
|
165
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** |
|
|
166
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** |
|
|
167
|
-
| `--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) |
|
|
168
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) |
|
|
169
169
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
170
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
171
|
| `--cuts` | the `cuts.json` `--cut` names. Required beside it — `--cut` alone is refused, with no guess at where the table lives |
|
|
172
|
-
| `--profile` | `spine` = the
|
|
172
|
+
| `--profile` | `spine` = the 29 validity rules (**the default**) · `spine-html` = all 44, opt-in |
|
|
173
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 |
|
|
174
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 |
|
|
175
175
|
| `--record` | `vote` only: a saved vote to check against its ballot and append to the ledger, instead of writing a ballot. This is the command's second mode; it takes no `--candidate` |
|
|
@@ -208,10 +208,15 @@ part per page" flat and rigc's own pack could not satisfy it. Since
|
|
|
208
208
|
[#266](https://github.com/firejune/rigc/issues/266) that clause is **one part per
|
|
209
209
|
page OR a tiling page**, so the combination is an ordinary build — and it is the
|
|
210
210
|
only one that puts the renderer's own rulebook over shared-page sampling. What a
|
|
211
|
-
*tiling* page has to satisfy is stated where the clause is,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
*tiling* page has to satisfy under that profile is stated where the clause is,
|
|
212
|
+
§5.2's `A06` row: no two regions on one page overlapping. Rotation is still
|
|
213
|
+
refused, and that is a separate clause about rigc's packer never turning a region.
|
|
214
|
+
|
|
215
|
+
⚠️ The other half of that sentence — every region wholly inside the page it names
|
|
216
|
+
— left this profile in [#694](https://github.com/firejune/rigc/issues/694). It is
|
|
217
|
+
**validity**, so no profile switches it off: a rectangle outside its page is
|
|
218
|
+
broken for every consumer, while two regions over the same texels is something
|
|
219
|
+
correct, editor-exported data does.
|
|
215
220
|
|
|
216
221
|
### 0.1 Packing the parts onto shared pages — `--pack`
|
|
217
222
|
|
|
@@ -319,6 +324,17 @@ texel count beside it so both numbers are visible:
|
|
|
319
324
|
measures the PNG. Reach for `--atlas-in` when the pack is what you were handed, or
|
|
320
325
|
when drawing through the pack's own texels is the point.
|
|
321
326
|
|
|
327
|
+
**What `--out` holds afterwards:** `skeleton.json` and a `skeleton.atlas` that is
|
|
328
|
+
the pack, page paths pointing back at the pack's own PNGs — so `rigc validate
|
|
329
|
+
<that directory>` reads it green with no flags, exactly as it reads a loose
|
|
330
|
+
build's. Add `--copy-images` and the pack's page PNGs are copied in beside the
|
|
331
|
+
skeleton and the page names become their basenames, which is the same directory
|
|
332
|
+
with nothing outside it left to resolve. ⚠️ Until
|
|
333
|
+
[#693](https://github.com/firejune/rigc/issues/693) that flag rebuilt the atlas
|
|
334
|
+
from the parts the rig declared instead of from the pack, and a rebuild through
|
|
335
|
+
`ingest --art none` declares none: the file written was **zero bytes**, on a build
|
|
336
|
+
that printed `PASS` for all four atlas assertions.
|
|
337
|
+
|
|
322
338
|
The emitted `skeleton.atlas` **is** the imported one, verbatim except for its page
|
|
323
339
|
name lines, which are paths and have to be re-anchored to `--out`. Fields rigc
|
|
324
340
|
does not re-serialise (`format:`, `repeat:`, and `scale:` itself) survive the trip
|
|
@@ -334,7 +350,7 @@ Four things are refused rather than warned about, because each of them otherwise
|
|
|
334
350
|
| a region name the atlas does not have | `AtlasAttachmentLoader` returns null and the part silently does not draw. The refusal lists the near misses — the usual cause is one character |
|
|
335
351
|
| a size the spec disagrees with | the same silence `A06` exists for, one link earlier: a quad sized against a region of another size collapses |
|
|
336
352
|
| a page the atlas names and the disk lacks | nothing to sample; caught on the way in, so the message names the atlas rather than the artifact rigc wrote from it |
|
|
337
|
-
| a rectangle that runs off its page | `x + width` past the page width makes `u2 > 1`, which samples whatever the wrap mode does |
|
|
353
|
+
| a rectangle that runs off its page | `x + width` past the page width makes `u2 > 1`, which samples whatever the wrap mode does. The gate names the same rectangle, under every profile, for a pack that reaches it without passing through here — `A06`, §5.2 ([#694](https://github.com/firejune/rigc/issues/694)) |
|
|
338
354
|
|
|
339
355
|
One limit, stated rather than discovered:
|
|
340
356
|
|
|
@@ -480,7 +496,7 @@ the first:
|
|
|
480
496
|
|
|
481
497
|
| gutter | meaning |
|
|
482
498
|
| --- | --- |
|
|
483
|
-
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `
|
|
499
|
+
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `point`, an attachment `sequence`, an unknown field on a bone, slot or constraint, a timeline family the motion spec has no track for. The command exits non-zero **and still writes both specs**, because a spec plus a list of what is missing from it beats no spec |
|
|
484
500
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
485
501
|
| `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how. A path attachment's `lengths` is the one that matters — it is `PathConstraint`'s own four-sample measurement rather than an arc length (#560), so a transcribed one would freeze whatever produced the source. The header ones are cheaper: `HEADER_BOOKKEEPING` for a field the spec has no home for, `HEADER_REDERIVED` for the version string, `HEADER_ORIGIN` for an origin the source left to the format and the rebuild writes out (#622) |
|
|
486
502
|
|
|
@@ -518,6 +534,17 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
518
534
|
extremes, how far each vertex moved and whether the winding survived
|
|
519
535
|
(**§4.11.2**). It takes no `--profile`, it never gates, and it does not write
|
|
520
536
|
anything — so the figures are readable on a build the gate is refusing.
|
|
537
|
+
|
|
538
|
+
⭐ **It reads what `ingest` writes, given the pack** — `--atlas-in <pack.atlas>`,
|
|
539
|
+
with `build`'s meaning (§0.2). That matters more here than it looks: printing the
|
|
540
|
+
`DEFORM` block means **posing** the rig, a pose resolves *every* attachment
|
|
541
|
+
against the atlas whether or not anything deforms it, and a spec written by
|
|
542
|
+
`ingest --art none` states sizes and names no image. So the size-only pair
|
|
543
|
+
`build --atlas-in` gates green is readable here through the same flag, and
|
|
544
|
+
without it the pair is refused by name at exit 2 rather than posed
|
|
545
|
+
([#697](https://github.com/firejune/rigc/issues/697), §5.1). ⚠️ `--profile`,
|
|
546
|
+
`--pack`, `--page-size`, `--padding` and `--copy-images` are `build`'s and are
|
|
547
|
+
not here: four of them decide what is *written*, and this command writes nothing.
|
|
521
548
|
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
522
549
|
sections (bones, slots, attachments, constraints, animations, events). It
|
|
523
550
|
deliberately does not combine them into a score: a rig with the right skeleton
|
|
@@ -922,7 +949,7 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
922
949
|
| `bone` | required; must be a bone this rig declares | — |
|
|
923
950
|
| `attachment` | the **setup pose** attachment name, or `null` for "show nothing" | must come from here or from `motion.setup` (R3) — **except** on a slot nothing fills, where it can only be `null` and may be left out |
|
|
924
951
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
925
|
-
| `dark` | two-colour tint, `rrggbb` | — (🚫 `A12` under `spine-html`) |
|
|
952
|
+
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2` track keys it over time and requires it | — (🚫 `A12` under `spine-html`) |
|
|
926
953
|
| `blend` | `normal` · `additive` · `multiply` · `screen` | `normal` |
|
|
927
954
|
|
|
928
955
|
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
@@ -964,6 +991,12 @@ A skin can also say which bones and constraints it **switches on**, and that nee
|
|
|
964
991
|
one more level, so a skin entry has a second spelling — see §3.4.1. The short one
|
|
965
992
|
above is unchanged and is what almost every rig wants.
|
|
966
993
|
|
|
994
|
+
🔸 **`default` is a name, not a requirement.** A rig may put every attachment in
|
|
995
|
+
named skins and declare no `default` at all, which is what an editor export of a
|
|
996
|
+
multi-skin character gives back; rigc still emits a `default` skin, empty, because
|
|
997
|
+
it always does. An animation keying such a slot resolves its attachment names
|
|
998
|
+
against every skin there is — §4.4 states that rule and §5.1 the one refusal left.
|
|
999
|
+
|
|
967
1000
|
🔸 **A skin may fill no slot with anything that needs art, and that build is
|
|
968
1001
|
green.** The atlas is built out of what the skins reference, so a rig whose skins
|
|
969
1002
|
name no `image` — an empty `default`, or one carrying only a `boundingbox`, a
|
|
@@ -1158,6 +1191,60 @@ rather than the figure it was filed over:
|
|
|
1158
1191
|
README carries the inradius arithmetic, both coverage readings, and the rim move
|
|
1159
1192
|
that settled it.
|
|
1160
1193
|
|
|
1194
|
+
**Linked mesh** ([Spine: linked meshes](http://esotericsoftware.com/spine-meshes)) —
|
|
1195
|
+
a mesh that draws **another mesh's geometry** with **its own art**. It is the type
|
|
1196
|
+
a skin variant uses: one triangulation and one set of weights, several outfits over
|
|
1197
|
+
it. Say `type: "linkedmesh"`, or put `source` on a `type: "mesh"` — the format has
|
|
1198
|
+
both spellings, they share one parser branch, and `source` is what decides between
|
|
1199
|
+
them (`SkeletonJson.ts:568-569`, `:582`), so rigc reads them the same way.
|
|
1200
|
+
|
|
1201
|
+
| Field | Meaning |
|
|
1202
|
+
| --- | --- |
|
|
1203
|
+
| `source` | **required.** The **placeholder** of the mesh whose geometry this one draws — the key it is filed under in its skin, not its `name`. A miss is refused naming the skin, the slot and what that slot holds |
|
|
1204
|
+
| `slot` | the slot the source lives in. Default: **this attachment's own slot**. Resolved by name against the rig's slots |
|
|
1205
|
+
| `skin` | the skin the source lives in. Default: **`default`** — the default skin, *not* the skin this link is written in. Resolved by name |
|
|
1206
|
+
| `timelines` | default **`true`**: the link plays the source's `deform` keys. `false` makes it its own timeline target, so only keys written against the link move it |
|
|
1207
|
+
| `image`, `path`, `width`, `height`, `color` | exactly as on a mesh — the link resolves **its own** region, which is the point of the type |
|
|
1208
|
+
|
|
1209
|
+
```json
|
|
1210
|
+
"skins": {
|
|
1211
|
+
"base": { "cloak": { "cloak": { "type": "mesh", "image": "cloak_red.png", "uvs": [], "triangles": [], "weights": [] } } },
|
|
1212
|
+
"winter": { "cloak": { "cloak": { "type": "linkedmesh", "image": "cloak_blue.png", "source": "cloak", "skin": "base" } } }
|
|
1213
|
+
}
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
🚨 **A linked mesh states no geometry of its own, and every geometry key on one is
|
|
1217
|
+
refused by name.** `uvs`, `triangles`, `vertices`, `weights`, `boneIndexing`,
|
|
1218
|
+
`hull`, `edges` and `generator` are read by **nothing**: the parser returns from
|
|
1219
|
+
the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`). Measured on
|
|
1220
|
+
a forged skeleton — a link declaring 5 uvs, 3 triangles, `hull: 5` and
|
|
1221
|
+
`edges: [0, 2]` beside a 4-vertex source loaded with the **source's** 8-long
|
|
1222
|
+
`worldVerticesLength`, 6 triangles, `hullLength` 8 and 10 edges. Nothing the author
|
|
1223
|
+
wrote reached anything and nothing said so.
|
|
1224
|
+
|
|
1225
|
+
🚫 **A chain is refused, and so is a link to itself.** A `source` that names
|
|
1226
|
+
another linked mesh resolves in the order the file was read: measured through
|
|
1227
|
+
spine-core, the chained link loaded the full geometry with the source declared
|
|
1228
|
+
first, and `worldVerticesLength` **0**, 0 triangles and a 0x0 size with the two
|
|
1229
|
+
keys swapped in the same file — silently, both ways. A construct whose meaning
|
|
1230
|
+
depends on JSON key order is one rigc will not write. Point `source` at the mesh.
|
|
1231
|
+
|
|
1232
|
+
⚠️ **`width`/`height` are emitted and the gate cannot see them.** The runtime
|
|
1233
|
+
overwrites both with the source's when it resolves the link
|
|
1234
|
+
(`MeshAttachment.setSourceMesh`; measured: a link stating `99x77` beside a 32x32
|
|
1235
|
+
source loads as 32x32). They are written because the editor reads them off the
|
|
1236
|
+
file and because the spec stated them — R1 — and no assertion can check them.
|
|
1237
|
+
|
|
1238
|
+
🔸 **`A21_MESH_RIM_PINNED` and `A28_RIBBON_ROWS_SHARE_WEIGHTS` leave a link out**,
|
|
1239
|
+
for the same reason they leave authored geometry out: the rim and the rows it draws
|
|
1240
|
+
are its source's and are measured there. `A21` drops it from the set it measures and
|
|
1241
|
+
**SKIPs by name** — naming the link and its source — when that leaves nothing;
|
|
1242
|
+
`A28` passes over it. `A04`, `A20` and `A22` read a link exactly as they read any
|
|
1243
|
+
other mesh, because after the round trip it **is** the source's triangles, weights
|
|
1244
|
+
and uvs. `A13_MESH_BUDGET` counts it as a mesh of its own: the runtime draws it as
|
|
1245
|
+
one, so a link in a second slot is a second mesh slot against
|
|
1246
|
+
`invariants.meshSlots`.
|
|
1247
|
+
|
|
1161
1248
|
The generators are `ring`, `ribbon`, `contour` and `grid` (see
|
|
1162
1249
|
[`src/mesh.ts`](../src/mesh.ts)); the first two encode a deformation model rather
|
|
1163
1250
|
than a table of numbers, which is why they are code invoked by data. The last two
|
|
@@ -1213,7 +1300,7 @@ ends, so the deformation dies into the pinned rim instead of creasing against it
|
|
|
1213
1300
|
| `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 |
|
|
1214
1301
|
| `size` | **required.** The part window, `[w, h]` in pixels, which the UVs and the emitted `width`/`height` are taken from |
|
|
1215
1302
|
| `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 |
|
|
1216
|
-
| `controls` | **required.** The control bones, by name, at least one. Each must be a bone the rig declares |
|
|
1303
|
+
| `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 |
|
|
1217
1304
|
|
|
1218
1305
|
⚠️ **`size` is stated here, not measured.** A `contour` and a `grid` take the
|
|
1219
1306
|
window off the attachment's own `image`; a `ring` and a `ribbon` are built from
|
|
@@ -1223,17 +1310,24 @@ drawing — measured: a 240x240 part declared `"size": [64, 64]` builds green un
|
|
|
1223
1310
|
`--profile spine-html`. That is R1 rather than a gap: the compiler emits the
|
|
1224
1311
|
number the spec states and does not re-measure a plate to overrule it.
|
|
1225
1312
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1313
|
+
⭐ **Several grips split the ring by where the rig put them, on this route as on
|
|
1314
|
+
the manifest one.** Each control bone's angle about `center` is measured from its
|
|
1315
|
+
own rest position — the window is centred on the slot bone (the ⭐ above), so a
|
|
1316
|
+
bone sitting below the aperture owns the arc below it — and authority is
|
|
1317
|
+
smoothstepped between the two bones either side of a vertex, so no grip creases
|
|
1318
|
+
against the next. Measured on a two-control ring whose grips sit 12px above and
|
|
1319
|
+
below the centre: 25 vertices, both grips bound, and of the 8 shared vertices off
|
|
1320
|
+
the centre line all 8 take more weight from the grip on their own side. Splitting
|
|
1321
|
+
**moves** authority rather than adding it: every vertex gives its controls the
|
|
1322
|
+
same total with one grip, two or three.
|
|
1323
|
+
|
|
1324
|
+
⚠️ **This route used to bind only the first name**
|
|
1325
|
+
([#684](https://github.com/firejune/rigc/issues/684)). It passed no angles at all,
|
|
1326
|
+
so the split collapsed onto `controls[0]` and the rest of the names reached the
|
|
1327
|
+
`MESH` line, the bone list and nothing else — and the gate was green, because a
|
|
1328
|
+
bone no vertex binds is in no weight, no sum and no index. `A20_MESH_WEIGHTS_COHERENT`
|
|
1329
|
+
now names it, so a ring that declares a grip and does not use it is a failure
|
|
1330
|
+
rather than a quiet stiffness.
|
|
1237
1331
|
|
|
1238
1332
|
**Stated limits, each a named refusal rather than a mesh that loads wrong:**
|
|
1239
1333
|
|
|
@@ -1245,6 +1339,8 @@ several grips.
|
|
|
1245
1339
|
| 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 |
|
|
1246
1340
|
| a `bias` ramp that does not increase | `bias ramp must increase, got [16, 4]` |
|
|
1247
1341
|
| a control bone the rig does not declare | `mesh bone "nobody" is not in the rig's bone list` |
|
|
1342
|
+
| 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 |
|
|
1343
|
+
| 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 |
|
|
1248
1344
|
|
|
1249
1345
|
#### `ribbon` — a strip of cross rows riding a bone chain
|
|
1250
1346
|
|
|
@@ -1931,6 +2027,20 @@ rigc emits all five: `ik`
|
|
|
1931
2027
|
and `slider`. Field lists are in [`src/rig.ts`](../src/rig.ts); the traps worth
|
|
1932
2028
|
carrying here:
|
|
1933
2029
|
|
|
2030
|
+
- 🔑 **A constraint name is unique per KIND, not across the array.** Spine
|
|
2031
|
+
resolves one with `SkeletonData.findConstraint(name, type)`, which tests
|
|
2032
|
+
`constraint instanceof type` **before** it compares the name, and every
|
|
2033
|
+
resolution in the format goes through it: a timeline group, a skin's member
|
|
2034
|
+
list (§3.4.1), a slider's animation. So an `ik` constraint and a `transform`
|
|
2035
|
+
constraint may both be called `leg` — the editor exports both, the runtime
|
|
2036
|
+
finds each from its own group, and a motion spec's `ik` block, `transform`
|
|
2037
|
+
block and `path`/`physics`/`slider` tracks each name the kind they mean and
|
|
2038
|
+
resolve the same way (§4.9, §4.12). Two constraints **of one kind** sharing a
|
|
2039
|
+
name are refused (§5.1), because no timeline could say which was meant. Until
|
|
2040
|
+
[#692](https://github.com/firejune/rigc/issues/692) the rig spec kept one
|
|
2041
|
+
namespace over the whole array, so a rig the editor exports and the runtime
|
|
2042
|
+
plays — an IK chain and the transform constraint that follows it, both carrying
|
|
2043
|
+
the chain's name — could not be written down at all.
|
|
1934
2044
|
- A transform constraint's `properties` names come from a fixed six — `rotate`,
|
|
1935
2045
|
`x`, `y`, `scaleX`, `scaleY`, `shearY`. rigc refuses anything else by name; in
|
|
1936
2046
|
raw JSON the parser throws.
|
|
@@ -2722,6 +2832,7 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2722
2832
|
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
2723
2833
|
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
2724
2834
|
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
2835
|
+
| `slot` | `rgba2` | `[lr, lg, lb, la, dr, dg, db]` in 0..1 — the two-colour tint, light then dark, **seven** channels. The slot must declare a setup `dark` (§3.3) |
|
|
2725
2836
|
| `slot` | `attachment` | the attachment name, or `null` for "show nothing" |
|
|
2726
2837
|
| `physics` | `inertia`, `strength`, `damping`, `mass`, `wind`, `gravity` | `[value]` — the constraint's own tuning, keyed over time |
|
|
2727
2838
|
| `physics` | `mix` | `[mix]`, **0 or more** — the constraint's authority |
|
|
@@ -2765,9 +2876,9 @@ accepts is what it accepts.
|
|
|
2765
2876
|
it actually poses, both ways, which is what makes the list checkable at all: it
|
|
2766
2877
|
was stated there too until the refusal had something to state.
|
|
2767
2878
|
|
|
2768
|
-
⚠️ **A `slot` track's `property` is one of the
|
|
2879
|
+
⚠️ **A `slot` track's `property` is one of the three above, and anything else is a
|
|
2769
2880
|
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
2770
|
-
attachment, rgba)`, §5.1's row. Until
|
|
2881
|
+
attachment, rgba, rgba2)`, §5.1's row. Until
|
|
2771
2882
|
[#650](https://github.com/firejune/rigc/issues/650) it was not: the emitter had a
|
|
2772
2883
|
branch for `attachment` and wrote **everything else** as an rgba timeline under
|
|
2773
2884
|
the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
@@ -2776,21 +2887,33 @@ the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
|
2776
2887
|
one-channel spelling of the same mistake was refused at compile as `rgba value
|
|
2777
2888
|
needs 4 channels, got 1`, a message about a key you had not written.
|
|
2778
2889
|
|
|
2779
|
-
- The
|
|
2890
|
+
- The three are the emitter's own dispatch table (`SLOT_TRACKS` in
|
|
2780
2891
|
`src/compile.ts`): `compileTrack` reads it to pick its branch, and the refusal
|
|
2781
2892
|
prints `Object.keys` of the same object, so what you are told a slot accepts
|
|
2782
2893
|
is what it accepts.
|
|
2783
|
-
- **Nothing derives this page's copy of that
|
|
2784
|
-
|
|
2894
|
+
- **Nothing derives this page's copy of that list from the table**, and it is
|
|
2895
|
+
three names long: no `DQ*`/`RD*`/`CUR*` control reads §4.4 (the only gated
|
|
2785
2896
|
table on this page is §3.5.2.1's, held by `RD01`–`RD06`). What keeps the two
|
|
2786
2897
|
in step is the control that quotes the message — `RF23` in `selftest.ts` —
|
|
2787
|
-
which goes red if the accepted
|
|
2788
|
-
it.
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
`
|
|
2793
|
-
|
|
2898
|
+
which goes red if the accepted list ever widens without this page moving with
|
|
2899
|
+
it. It did, on the day `rgba2` was added
|
|
2900
|
+
([#690](https://github.com/firejune/rigc/issues/690)), which is the mechanism
|
|
2901
|
+
working rather than a hole in it.
|
|
2902
|
+
- 🎨 **`rgba2` keys the two-colour tint, and the slot has to own one first.** A
|
|
2903
|
+
track `{ "slot": "X", "property": "rgba2" }` on a slot whose rig spec declares
|
|
2904
|
+
no `dark` (§3.3) is a compile error with the slot named, and it is not a
|
|
2905
|
+
formality: the runtime allocates a slot's dark colour only when its setup pose
|
|
2906
|
+
has one, so a file keying it without one parses cleanly and then throws in the
|
|
2907
|
+
player the first time the animation is applied. `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN`
|
|
2908
|
+
(§5.2) holds the same pairing on a skeleton rigc did not write. 🚫 Both halves
|
|
2909
|
+
of the two-colour tint are refused under `--profile spine-html`, whose renderer
|
|
2910
|
+
ignores them; the default `spine` profile that `build` runs reports `A12` as
|
|
2911
|
+
`PROF` and never applies it.
|
|
2912
|
+
- The format has three more slot timelines (`rgb`, `alpha`, `rgb2`) and rigc
|
|
2913
|
+
emits none of them, so their names are refused here too; `A12_NO_DARK_COLOR`
|
|
2914
|
+
refuses `rgb2` — and `rgba2`, and the slot field — in a file under that
|
|
2915
|
+
renderer's profile (SPEC_COVERAGE §2.1). `sequence` is a timeline on an
|
|
2916
|
+
**attachment**, not on a slot, and rigc does not emit that either.
|
|
2794
2917
|
|
|
2795
2918
|
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
2796
2919
|
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
@@ -2808,7 +2931,7 @@ resolved against the rig.
|
|
|
2808
2931
|
with no table claiming the property the track fell through to the slot branch,
|
|
2809
2932
|
and what you were told was that the first member is not a slot — on a file that
|
|
2810
2933
|
named neither a slot nor that member. A group of **slots** got §4.4's slot row
|
|
2811
|
-
instead (`slot "M" has no timeline "P" (it has: attachment, rgba)`), which is
|
|
2934
|
+
instead (`slot "M" has no timeline "P" (it has: attachment, rgba, rgba2)`), which is
|
|
2812
2935
|
true of the member and names one family out of three on a track whose family
|
|
2813
2936
|
nothing had determined.
|
|
2814
2937
|
- **A constraint property never reaches it.** `position`, `spacing` and `time` are
|
|
@@ -2871,6 +2994,36 @@ different curves. The same applies to `scale`/`scalex`/`scaley` and
|
|
|
2871
2994
|
An `attachment` key carries no easing — attachment timelines are inherently
|
|
2872
2995
|
stepped.
|
|
2873
2996
|
|
|
2997
|
+
🔑 **An attachment key resolves against every skin, not against `default` alone.**
|
|
2998
|
+
A slot's `attachment` timeline carries a name and **no skin** — the format has no
|
|
2999
|
+
field for one — so the names a key may use are the union of every skin's
|
|
3000
|
+
placeholders for that slot, the default skin's included.
|
|
3001
|
+
`Skeleton.getAttachment` resolves the keyed name at run time through the skin the
|
|
3002
|
+
skeleton is **wearing** and, failing that, through `defaultSkin` (spine-core
|
|
3003
|
+
4.3.13 `Skeleton.js:335-346`), so which skin's art a key lands on is the
|
|
3004
|
+
consumer's, decided by dressing the skeleton rather than by the animation.
|
|
3005
|
+
|
|
3006
|
+
- **A name some skins fill and others do not is accepted, and that is the
|
|
3007
|
+
format's own semantics** rather than a hole in the check: under a skin that
|
|
3008
|
+
lacks it the slot shows nothing, which is exactly what a `null` key says and a
|
|
3009
|
+
thing a rig may well mean. What is refused is a name **no** skin holds, and the
|
|
3010
|
+
refusal says which skins were searched and what the slot does have (§5.1).
|
|
3011
|
+
- A rig with **no `default` skin at all** — every attachment in named skins,
|
|
3012
|
+
which is the shape an editor export of a multi-skin character gives back — is
|
|
3013
|
+
therefore a rig whose attachment keys work. Until
|
|
3014
|
+
[#695](https://github.com/firejune/rigc/issues/695) it was not: keys were
|
|
3015
|
+
checked against the default skin alone, so a named-skin name was refused as
|
|
3016
|
+
unknown, and a rig with no default skin had **every** attachment key refused,
|
|
3017
|
+
including ones whose art is in the first named skin. The setup pose resolved
|
|
3018
|
+
across skins the whole time (§4.2), so the two halves of one slot disagreed —
|
|
3019
|
+
`slots[].attachment: "plain"` was accepted and a key naming `plain` on that
|
|
3020
|
+
same slot was not.
|
|
3021
|
+
- ⚠️ **A `deform` track is the other way round and names its skin outright**
|
|
3022
|
+
(§4.11.5), because the format keys a deform timeline on a `skin/slot/attachment`
|
|
3023
|
+
triple and a deform run is geometry for one attachment object. An attachment
|
|
3024
|
+
timeline has no such field, which is why this one is a union and that one is a
|
|
3025
|
+
lookup.
|
|
3026
|
+
|
|
2874
3027
|
### 4.5 `keys` — times, values, curves
|
|
2875
3028
|
|
|
2876
3029
|
- `t` is in seconds and **must strictly increase** after `lag`/`stagger` are added.
|
|
@@ -3441,6 +3594,35 @@ Per key:
|
|
|
3441
3594
|
| `offset` | the same start as a raw index into the deform array. Never with `fromVertex` |
|
|
3442
3595
|
| `ease` / `curve` | one channel, and it eases the **blend**, not a coordinate |
|
|
3443
3596
|
|
|
3597
|
+
**The target is any attachment that has a vertex array** — a mesh, a bounding
|
|
3598
|
+
box, a clipping polygon, or a **path**. The array a key edits is that
|
|
3599
|
+
attachment's own, so everything below about runs, start indices and the two
|
|
3600
|
+
encodings reads the same whichever it is. A region attachment has no vertex array
|
|
3601
|
+
and is refused by name.
|
|
3602
|
+
|
|
3603
|
+
⭐ **A path's vertices are its control points** — knots and their Bezier handles
|
|
3604
|
+
alike, in the order §3.5.1 lists them — so a run covers them in that order and a
|
|
3605
|
+
`fromVertex` counts them the same way. Its deform array is `vertexCount * 2` long
|
|
3606
|
+
unweighted, and one pair per influence weighted, exactly as a mesh's is. What is
|
|
3607
|
+
and is not measured on one:
|
|
3608
|
+
|
|
3609
|
+
- `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` measures the run against that length, as
|
|
3610
|
+
it does for every other target.
|
|
3611
|
+
- `A39_DEFORM_KEEPS_TRIANGLE_WINDING` reports **SKIP**, naming the slot and
|
|
3612
|
+
saying the attachment has no triangles. A path is knots and handles; there is
|
|
3613
|
+
no winding to keep, and reporting a pass for a measurement that did not happen
|
|
3614
|
+
is the one thing an assertion here may not do.
|
|
3615
|
+
- **`lengths` is not re-measured, and cannot be.** It is a field of the
|
|
3616
|
+
attachment (§3.5.1) and the format has nowhere to put a per-key one, so the
|
|
3617
|
+
array every exporter writes — rigc's included — is the **setup** measurement.
|
|
3618
|
+
`PathConstraint.computeWorldPositions` reads it only when `constantSpeed` is
|
|
3619
|
+
`false`; under the parser's default, `true`, it re-measures the curve from the
|
|
3620
|
+
posed vertices every frame. So a path constraint follows the deformed curve as
|
|
3621
|
+
written, and under `constantSpeed: false` it traverses the deformed curve at
|
|
3622
|
+
the **setup** spacing. That is the format's behaviour rather than rigc's
|
|
3623
|
+
choice, which is why it is stated here instead of refused: an editor export of
|
|
3624
|
+
the same rig does the same thing.
|
|
3625
|
+
|
|
3444
3626
|
Four things are worth having straight before you write one.
|
|
3445
3627
|
|
|
3446
3628
|
**A key is a sparse edit, and a key with no `vertices` is the setup pose.** The
|
|
@@ -3494,6 +3676,7 @@ half of the format:
|
|
|
3494
3676
|
| `fromVertex` on a multi-bone vertex | `"fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` |
|
|
3495
3677
|
| an attachment that is not there | `slot "flat" in skin "default" has no attachment "flatt" (it has: flat)` |
|
|
3496
3678
|
| a deform on a region attachment | `a deform timeline keys the vertices of an attachment, and this one is a "region"` |
|
|
3679
|
+
| a run past a path's control points | `this attachment's deform array is 18 long (9 vertices)` — the same bound as any other target, counted in the control points §3.5.1 declares |
|
|
3497
3680
|
| `transform` beside a `vertices` run | `the key carries both a "transform" and a "vertices" run, and they are two answers to one question` |
|
|
3498
3681
|
| `transform` with `fromVertex` or `offset` | `A transform is a model of the whole attachment and is evaluated over all 25 of its vertices, so it always starts at deform index 0` |
|
|
3499
3682
|
| a `transform` on an attachment whose weights do not close at 1 | `this one has a vertex the arithmetic cannot place — vertex 1's 2 weights sum to 0.9000 rather than 1` |
|
|
@@ -4371,6 +4554,9 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4371
4554
|
| `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 |
|
|
4372
4555
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
4373
4556
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
4557
|
+
| `two ik constraints are called "X" — a constraint resolves by name AND type (\`SkeletonData.findConstraint\`), so names are unique PER KIND: an ik and a transform constraint may share one, two of a kind may not` | §3.5 — rename one of the two. The kind in the sentence is the pair's own, so `two transform constraints are called "X"` is the same refusal on another kind; a name shared **across** kinds is not this error and never was one to fix |
|
|
4558
|
+
| `physics constraint "X" is declared in both the rig spec and the motion spec's physics table` | §4.6 — the rig spec declares a physics constraint's structure and the motion spec's `physics` table declares one outright; pick the file it belongs in. Per kind, like every other constraint name: an `ik` "X" in the rig spec beside a `physics` "X" here is two constraints and is not this error |
|
|
4559
|
+
| `skin "S" activates ik constraint "X", which skin "T" already activates; a constraint belongs to one skin` | §3.4.1 — a constraint runs under one skin or under all of them. The kind is in the sentence because `ik` "X" and `transform` "X" are two constraints, and each may belong to a different skin |
|
|
4374
4560
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
4375
4561
|
| `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 |
|
|
4376
4562
|
| `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` |
|
|
@@ -4379,7 +4565,14 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4379
4565
|
| `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 |
|
|
4380
4566
|
| `"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 |
|
|
4381
4567
|
| `attachment type "X" is not one of the 7 the Spine 4.3 format defines (…)` | §6 — a name the format does not have. Not a deferral, and not something rigc will grow: fix the spelling (`sequence` is a key on a region or a mesh, not a type) |
|
|
4382
|
-
| `this attachment is a "
|
|
4568
|
+
| `this attachment is a "point" … rigc does not emit it yet` | §6 — a construct the format has and rigc does not write. The message says what it would carry; SPEC_COVERAGE part 1-6 is the row it reads from |
|
|
4569
|
+
| `a linked mesh needs "source" — the PLACEHOLDER of the mesh whose geometry it draws …` | §3.4 — `source` is what MAKES a mesh linked, and the parser falsy-tests it, so an absent or empty one is read as an ordinary mesh and throws on the `uvs` a link has not got |
|
|
4570
|
+
| `a linked mesh states "uvs", "triangles", …, and a linked mesh has no geometry of its own` | §3.4 — remove them, or remove `source` and author this as a mesh. The parser returns before `readVertices`, so those keys are read by nothing at all |
|
|
4571
|
+
| `"source" is "X", and skin "S" … slot "L" … holds 2: "a", "b"` | §3.4 — `source` is the PLACEHOLDER the source is filed under, not its `name`. A clause after the skin and after the slot says whether each was stated or taken from the parser's default — **the default skin** and **this attachment's own slot**, which is the pair that surprises |
|
|
4572
|
+
| `"slot" is "X", which the rig does not declare as a slot` / `"skin" is "X", … the rig declares no such skin` | §3.4 — a link resolves both by name. Left to the round trip these are the runtime's `Source mesh slot not found` and `Skin not found`, which name neither the attachment nor where it looked |
|
|
4573
|
+
| `"source" is "X", which is itself a linked mesh, and a chain of them is refused` | §3.4 — point `source` at the mesh. A chain resolves in file order and loads nothing at all in one of the two orders, silently |
|
|
4574
|
+
| `"source" is "X", which is a "region" attachment and not a mesh` | §3.4 — a link takes another MESH's geometry; off any other type the runtime reads `undefined` and says nothing |
|
|
4575
|
+
| `a linked mesh needs width and height — give them, or give an "image" and rigc will measure the PNG` | §3.4 — the mesh rule, on a link. Its art is its own |
|
|
4383
4576
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
4384
4577
|
| `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 |
|
|
4385
4578
|
| `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 |
|
|
@@ -4395,6 +4588,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4395
4588
|
| `motion spec names archetype "A" but the rig spec at … is called "B"` | make `archetype` equal the rig's `name` |
|
|
4396
4589
|
| `animation "A" declares duration Ns but its last key is at Ms` | R7 — fix whichever of the two you meant |
|
|
4397
4590
|
| `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` |
|
|
4591
|
+
| `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 |
|
|
4398
4592
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
4399
4593
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
4400
4594
|
| `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)) |
|
|
@@ -4430,7 +4624,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4430
4624
|
| `animation "A" keys "X" as an ik constraint, but the rig declares it as a "transform" constraint` | §4.9 — a timeline's target resolves by name AND type; put the entry under the right group |
|
|
4431
4625
|
| `ik constraint "X": key 0 names "softness" and key 1 (t=…) does not` | §4.9 — every key is read with its own default, so state the field on every key or on none |
|
|
4432
4626
|
| `ik constraint "X" (t=…): mix is 1.5, outside 0..1` | §4.9 — an IK mix is a percentage; a transform mix is unbounded |
|
|
4433
|
-
| `deform …: the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long` | §4.11 — shorten the run or move its start; the parser would drop the tail in silence |
|
|
4627
|
+
| `deform …: the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long (4 vertices)` | §4.11 — shorten the run or move its start; the parser would drop the tail in silence. The count in brackets is the **target's own**: a mesh's or a path's vertices, or a weighted attachment's bone influences |
|
|
4434
4628
|
| `deform …: "fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` | §4.11 — key the control bone, or write bind-space pairs and start with `offset` |
|
|
4435
4629
|
| `deform …: slot "X" in skin "default" has no attachment "Y" (it has: …)` | §4.11 — fix the placeholder name |
|
|
4436
4630
|
| `deform … (t=…): the key carries both a "transform" and a "vertices" run` | §4.11.1 — a model and a table are two answers to one question; drop one |
|
|
@@ -4457,14 +4651,45 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4457
4651
|
| `animation "A" keys "X" as a path constraint, but the rig declares it as a "slider"` | §4.12 — a timeline group resolves by name AND type; use the field named after the constraint's own type |
|
|
4458
4652
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
4459
4653
|
| `rgba value needs 4 channels, got 3` | §4.4 — an `rgba` key is `[r, g, b, a]`. It names no animation, slot or key time, and the only input that reaches it is a slot `rgba` key: the setup pose's `color` is refused earlier, by its own row, with the slot named |
|
|
4654
|
+
| `rgba2 value needs 7 channels, got 6` | §4.4 — an `rgba2` key is `[lr, lg, lb, la, dr, dg, db]`: the light colour with its alpha, then the dark colour **without** one. Six is the commonest way to get it wrong, because the dark half looks like it should take an alpha too — the format has no channel for it, and neither does the runtime's `setFrame`. Like the row above it names no animation or key time; the only input that reaches it is a slot `rgba2` key |
|
|
4460
4655
|
| `animation "A" bone "B" has no timeline "P" (it has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate)` | §4.4 — a bone has exactly ten timelines and `P` is none of them. Fix the spelling — the single-axis ones are lower-case (`translatex`, not `translateX`). A **constraint** property is refused first, by its own row, naming the field its constraint's name goes in. When `P` is a slot timeline the message says so and where to put the name: `. "rgba" is a slot timeline — put the name in "slot"`. Before [#656](https://github.com/firejune/rigc/issues/656) all of them read `bone "B" cannot take slot property "P"`, which named the slot family whatever you had written and listed nothing |
|
|
4461
|
-
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate; a slot group has: attachment, rgba; a physics constraint group has: inertia, strength, damping, mass, wind, gravity, mix, reset)` | §4.3, §4.4 — a group's family is decided by the property, and `P` is in none of the three tables, so there is no family to resolve the members as. Fix the spelling and the group becomes whichever family the property names. The group is refused before its members are looked up, so a member the rig does not declare is a **later** message; an unknown group NAME is an earlier one. Before [#661](https://github.com/firejune/rigc/issues/661) a group of bones read `animation "A" targets unknown slot "M"` and a group of slots got the slot row below, naming one family out of three |
|
|
4462
|
-
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba)` | §4.4 — a slot has exactly
|
|
4656
|
+
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate; a slot group has: attachment, rgba, rgba2; a physics constraint group has: inertia, strength, damping, mass, wind, gravity, mix, reset)` | §4.3, §4.4 — a group's family is decided by the property, and `P` is in none of the three tables, so there is no family to resolve the members as. Fix the spelling and the group becomes whichever family the property names. The group is refused before its members are looked up, so a member the rig does not declare is a **later** message; an unknown group NAME is an earlier one. Before [#661](https://github.com/firejune/rigc/issues/661) a group of bones read `animation "A" targets unknown slot "M"` and a group of slots got the slot row below, naming one family out of three |
|
|
4657
|
+
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba, rgba2)` | §4.4 — a slot has exactly three timelines and `P` is none of them. Fix the spelling; a bone or constraint property written on a slot track is refused by its own row instead. Before [#650](https://github.com/firejune/rigc/issues/650) every other name compiled as an **rgba** timeline called `P`, and what you saw was `A00_ROUNDTRIP_PARSE` on the emitted file — or, for the one-channel spelling, `rgba value needs 4 channels, got 1` |
|
|
4658
|
+
| `animation "A" slot "X" rgba2: slot "X" declares no setup "dark", and an "rgba2" timeline poses a slot's dark colour …` | §3.3, §4.4 — the two-colour tint has a setup half and a keyed half, and the keyed half cannot exist without the other. `Slot`'s constructor allocates a dark colour only for a slot whose setup pose declares one, and `RGBA2Timeline` writes it unconditionally — so without the `dark` the file loads, and the first `state.apply` throws `TypeError: null is not an object` in the consumer's process. Give the slot the `dark` it holds at rest, or key `rgba` if only the light colour moves. Raised before the keys are read, with the slot named, for the same reason the row above is |
|
|
4463
4659
|
| `N pair(s) of animation names have no one order: … "turn" / "Turn" (case) — they are one name in two cases, and which of them the editor puts first is not measured; rename one of them so they differ by more than letter case` | **R10** — rename until no pair is left. The kind in brackets says which of the editor comparator's four UNMEASURED choices decides the pair: `case` (a pure case tie), `number` (one number written two ways, or a run of digits against a word) or `separator` (make the first character that differs a letter or a digit). rigc keys `animations` in the editor's own comparator — natural and case-insensitive ([#539](https://github.com/firejune/rigc/issues/539), [#543](https://github.com/firejune/rigc/issues/543)) — so a pair that comparator settles is emitted rather than refused, and only the four choices nobody has measured are a compile error; on those, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
|
|
4464
4660
|
| `N pair(s) of skin names have no one order: … "Zulu" / "mike" (case) — folded to one case "Zulu" and "mike" order the other way round, so whether the editor folds SKIN names decides this pair` | **R11** — rename until no pair is left. The same shape as the row above with a **wider** family: #539 measured the editor's comparator for animation names and thereby ruled codepoint out, and nothing has ruled anything out for skin names, so a pair the candidates could disagree about is refused even where the animation rule would emit it. `Zulu`/`mike` and `mike10`/`mike2` build as animation names and are refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) |
|
|
4465
4661
|
| `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 |
|
|
4466
4662
|
| `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 |
|
|
4467
4663
|
|
|
4664
|
+
⚠️ **One refusal in this section is not a `CompileError`, and it is `explain`'s.**
|
|
4665
|
+
`explain` prints the `DEFORM` block by **posing** the rig, and a pose resolves every
|
|
4666
|
+
attachment against the atlas — so it needs the art `build` needs, reached the same
|
|
4667
|
+
two ways (§0.2). A pair whose art it cannot resolve is refused **before a line of
|
|
4668
|
+
the report**, at **exit 2**, in rigc's own sentence:
|
|
4669
|
+
|
|
4670
|
+
```
|
|
4671
|
+
rigc explain: skin "default" slot "lamp" placeholder "shade": attachment "shade"
|
|
4672
|
+
wants region "shade", which this build's atlas does not have (it declares no region
|
|
4673
|
+
at all). `explain` poses the rig to measure its deform keys and a pose resolves
|
|
4674
|
+
every attachment against the atlas, so there is nothing to pose it against. Art
|
|
4675
|
+
reaches a compile two ways and this run took neither: `--atlas-in <pack.atlas>`
|
|
4676
|
+
resolves the parts against a pack somebody already made, and an "image" per
|
|
4677
|
+
attachment resolves them as loose PNGs under `--images <dir>` — a spec that states
|
|
4678
|
+
a size and names no image is what `ingest --art none` writes, and `--atlas-in` is
|
|
4679
|
+
what reads it. 1 of 1 attachment lookup(s) here resolve to no region.
|
|
4680
|
+
```
|
|
4681
|
+
|
|
4682
|
+
Both halves of it are measured rather than fixed text: the count is this pair's, and
|
|
4683
|
+
where the atlas has a **near miss** the sentence prints that too, in `A08`'s own
|
|
4684
|
+
clause and `A08`'s own words. When `--atlas-in` **was** given and the region is still
|
|
4685
|
+
absent, the second half names the pack instead and asks you to fix the spec's region
|
|
4686
|
+
name or point the flag at the pack that has it. Until
|
|
4687
|
+
[#697](https://github.com/firejune/rigc/issues/697) there was no rigc sentence at
|
|
4688
|
+
all: the report printed in full and the run then died inside `AtlasAttachmentLoader`
|
|
4689
|
+
with `Region not found in atlas: shade (attachment: shade)` and a spine-core stack
|
|
4690
|
+
trace, at exit 1 — the runtime's message about rigc's internals standing in for
|
|
4691
|
+
rigc's message about your two files.
|
|
4692
|
+
|
|
4468
4693
|
### 5.2 Assertions — the gate
|
|
4469
4694
|
|
|
4470
4695
|
The report prints one line per assertion:
|
|
@@ -4531,7 +4756,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4531
4756
|
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size — the attachment has no `image` and no `width`/`height`. **SKIP** when the skeleton carries no region attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4532
4757
|
| `A04_MESH_TRIANGLES_AND_ENCODING` | both | authored mesh geometry: triangle count not a multiple of 3, an index out of range, or a `vertices` length that disagrees with `uvs` (the weighted/unweighted trap) **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4533
4758
|
| `A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel**. **SKIP** when no animation carries a timeline at all ([#580](https://github.com/firejune/rigc/issues/580)). Timelines with no `curve` on any key still PASS: every timeline name is checked against the channel table whether or not a curve sits on one |
|
|
4534
|
-
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk
|
|
4759
|
+
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk, **or** a region's rectangle is not inside the page it names — rotation honoured, so a region at `rotate: 90` or `270` occupies `height x width` of the page and a region that fits only because it is turned is inside it. The message names the region, the rectangle it occupies, the page and the page's size. That clause is **validity** and runs under both profiles ([#694](https://github.com/firejune/rigc/issues/694)): a rectangle outside its page makes `u2 > 1` and samples whatever the wrap mode returns, and `--atlas-in` already refuses the same rectangle at compile time (§0.2). Under `spine-html` also: `pma`, rotation, and two regions on one page over the same texels — a packed page must be **one part covering it exactly** (the unpacked convention) or a **tiling** ([#266](https://github.com/firejune/rigc/issues/266)), and what that message names is the pair that shares texels. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4535
4760
|
| `A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file. ⚠️ An atlas with **no page block at all** — no non-blank line — is not one of those: its subject is absent, so this reports **SKIP** naming the byte count it read, and so do the four rules below whose subject is a page ([#608](https://github.com/firejune/rigc/issues/608)). A rig whose skins need no art writes exactly that file (§3.4), and before #608 this row refused it with two findings naming a page block that was not there. What an empty atlas does **not** excuse is an attachment that wants a region out of it — that is `A08` |
|
|
4536
4761
|
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. Until [#589](https://github.com/firejune/rigc/issues/589) they were unreachable: the loader threw first and the miss arrived as `A00_ROUNDTRIP_PARSE`. There is no `spine-html` clause here any more — a placeholder is free to differ from the region its `path` names ([#574](https://github.com/firejune/rigc/issues/574)) **SKIP** when no attachment names a region *and* the atlas declares none — both of its subjects at once ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4537
4762
|
| `A09_ANIMATION_DURATION_MATCHES_SPEC` | both | the loaded duration ≠ the declared one, or the two sides disagree about which animations exist (R7). Asymmetric by design: a frame of slack for an animation that ends early, and none worth the name for a key *past* the declared end, which is the same rule §4.5 states at compile time — held here against a skeleton the compiler never saw. **SKIP** when neither side has an animation at all — a static rig has no duration |
|
|
@@ -4545,7 +4770,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4545
4770
|
| `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)) |
|
|
4546
4771
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
4547
4772
|
| `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)) |
|
|
4548
|
-
| `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,
|
|
4773
|
+
| `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)) |
|
|
4549
4774
|
| `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 |
|
|
4550
4775
|
| `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)) |
|
|
4551
4776
|
| `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 |
|
|
@@ -4568,6 +4793,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4568
4793
|
| `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` | both | two or more sliders whose animations key the same timeline, where a later one is not `additive` — it writes that property outright at `mix: 1` and every earlier slider on it is dead (§3.5.2). Also fires when the shared timeline **cannot** be applied additively (a slot colour, an attachment swap, a draw order, an ik mix, a path's `spacing`, most physics properties), where `"additive": true` is not the fix and one of the two has to go. ⭐ Which of the two it is, is **posed rather than read off `Timeline.additive`**: the shared timeline is applied twice with `add` set and the detail says what it did ([#655](https://github.com/firejune/rigc/issues/655) — two classes declare that flag falsely about themselves, so a path constraint's `mix` and a slider's `time` were refused although they compose). The detail names the bone or slot and the property, every slider keying it in `constraints` order with its flag, which one wins today, and the class that was posed. Four shapes are deliberately not findings: a slider below `mix: 1` or with its `mix` keyed (the apply is then a lerp from the current pose, not an overwrite), two `skinRequired` sliders no skin activates together, two sliders on different properties, and a shared timeline that writes **nothing a pose holds** — an `events` timeline fires no event under a slider (`firedEvents` is null), so neither slider has anything there for the other to erase. **SKIP** when fewer than two sliders are at full authority; a PASS means two were compared |
|
|
4569
4794
|
| `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` | both | a physics constraint driving a component the **Spine editor** cannot hold, on a rig that declared `invariants.editorRoundTrip` (§3.7). The editor's physics model holds `x` and `y` only, with no cap on how many at once, so a constraint driving `rotate`, `scaleX` or `shearX` is imported, exported and handed back driving **nothing** — measured over three rigs and twelve constraints with the predictions written first ([#540](https://github.com/firejune/rigc/issues/540)). The detail names the constraint and each component. ⚠️ rigc's own output is correct — every runtime plays a rotation jiggle — so this is opt-in and the default is *not* silence: on a rig that declares nothing it **SKIPs**, and the SKIP names the constraint and the component anyway, so an author learns without having asked. Fix by driving the constraint in `x`/`y`, or by dropping the declaration if the rig never goes near the editor. Disjoint from `A23_PHYSICS_CONSTRAINT_EFFECTIVE` by construction: A23 refuses an **empty** driven set, which is what comes back from the editor, and this refuses a non-empty one that will not survive going in. **SKIP** also when the rig declares the editor and carries no physics constraint at all |
|
|
4570
4795
|
| `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` | both | a slider whose animation keys a property of a constraint **at or before it** in `constraints` (§3.5.2) — a slider's `mix` or `time`, an ik or transform mix, a path `position`, `spacing` or `mix`, any physics value. That array is the update order for every kind, and each constraint reads its own applied pose when its turn comes — `Slider.update` takes `mix` as the alpha it applies with and `time` as the time it applies at, `PhysicsConstraint.update` returns on `mix` 0 before reading the rest — so the key lands after the only read of it and `Posed.resetConstrained` discards it before the next frame: what the driven constraint drives is dead at every position of the driving dial, although its pose still holds the number ([#658](https://github.com/firejune/rigc/issues/658), [#665](https://github.com/firejune/rigc/issues/665)). The detail names the slider, the driven constraint with its kind, both array indices, the property, the runtime class whose `update` reads it, and the animation the key sits in. Fix by moving the driver earlier, or by keying that property from a slider that already is. **The two indices equal is the same failure**: a slider cannot key its own `mix` or `time`, and one muted at setup that keys its own `mix` up never applies anything at all — `A37` is silent there, because it asks whether *an* animation keys the mix and not which one. **Two shapes it deliberately leaves out**, both measured: a `physics` `reset` key, which fires on a crossed frame time and so never fires from a slider at all, in either order — the reorder would repair nothing; and a physics timeline naming no constraint, which is every physics constraint declaring that property global and IS refused for the ones already run. Disjoint from `A40` by construction: `A40` asks who writes a shared property last and excludes every slider whose `mix` is keyed, this asks whether anything reads what was written. **SKIP** when the skeleton declares no slider, and when no slider's animation keys a constraint property — that SKIP names any `reset` keys it found — a pass means a driver and a driven were compared |
|
|
4796
|
+
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | both | a slot's `dark` (§3.3) or an `rgba2` timeline (§4.4) that the runtime does not hold as the file states it. Three shapes, all of which parse in silence: a `dark` the slot reader **drops** — it takes the field through a truthiness test, so `""` is discarded without a word and the slot renders with one colour; a `dark` that is **not six hex digits** — `Color.setFromString` slices fixed offsets and stores whatever `parseInt` gives back, so `"4020"` loads a channel of `NaN`; and an `rgba2` timeline on a slot with **no `dark` at all**, where the runtime allocates no dark colour and the first `state.apply` throws in the consumer's process. The keyed half is read by posing: the animation is stepped to each key's own time and the posed `color` and `darkColor` are compared against the hex the key states, to half a quantisation step (`1/510`). The detail names the slot, the value found and the value required. ⚠️ The required value is parsed **here** and not through `Color.fromString`, because a check that read it out of the parser it is checking would agree with that parser whatever it did. `compile.ts` refuses the third shape outright in a rig rigc builds; this is the same fact held against a skeleton it did not write. **SKIP** when no slot declares a `dark` and no animation keys an `rgba2` — there is then no two-colour tint to read back |
|
|
4571
4797
|
|
|
4572
4798
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
4573
4799
|
clauses are gated by profile.
|
|
@@ -4582,10 +4808,12 @@ own behaviour is worse: an unknown attachment `type` returns `null` and the
|
|
|
4582
4808
|
attachment disappears, and a constraint entry with an unrecognised `type` matches no
|
|
4583
4809
|
case and vanishes.
|
|
4584
4810
|
|
|
4585
|
-
A deferral carries its reason, and
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4811
|
+
A deferral carries its reason, and there is one deferred attachment type left:
|
|
4812
|
+
**`point` appears nowhere in the benchmark corpus** (SPEC_COVERAGE parts 3-1 and
|
|
4813
|
+
4-2), so it is not on the ladder's critical path. The message says so, because a
|
|
4814
|
+
deferral without its reason is a wall rather than a work item. `linkedmesh` stood
|
|
4815
|
+
beside it until [#691](https://github.com/firejune/rigc/issues/691) and is now
|
|
4816
|
+
emitted — §3.4 has its fields.
|
|
4589
4817
|
|
|
4590
4818
|
⚠️ **A spelling the format does not have is a different refusal and says so.**
|
|
4591
4819
|
`sequence` is not an attachment type, and a `"type"` that is `null` is not an absent
|
|
@@ -4596,13 +4824,12 @@ are `CompileError`s, and they name what the format actually defines
|
|
|
4596
4824
|
|
|
4597
4825
|
| You wrote | You get |
|
|
4598
4826
|
| --- | --- |
|
|
4599
|
-
| attachment `type` of `point`
|
|
4600
|
-
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) |
|
|
4827
|
+
| attachment `type` of `point` | `this attachment is a "point" — a position and an angle with no geometry at all — "x", "y", "rotation" and "color" …. rigc does not emit it yet, deliberately: it emits region, mesh, linkedmesh, boundingbox, clipping, path, and a point appears nowhere in the benchmark corpus …` — the message names the **construct**, not just its type string, and part 1-6 is where the sentence comes from |
|
|
4828
|
+
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) | **not a refusal any more** — both spellings compile to a linked mesh (§3.4, [#691](https://github.com/firejune/rigc/issues/691)). They share one parser branch and `source` is what decides between them (SPEC_COVERAGE part 1-6), so `source` on a mesh is a linked mesh whatever `type` says. It was once refused as *2 keys this compiler does not read: "source", "skin" … fix the spelling or remove it*, whose remedy destroys the construct ([#577](https://github.com/firejune/rigc/issues/577)) |
|
|
4601
4829
|
| attachment `type` of anything else — `sequence`, a typo | `attachment type "X" is not one of the 7 the Spine 4.3 format defines (region, mesh, linkedmesh, boundingbox, path, point, clipping). … the attachment is dropped from the skeleton without a word` — a **`CompileError`**, not a deferral: rigc is not going to implement a name the format does not have. (`sequence` is a key on a region or a mesh, not a type of its own.) |
|
|
4602
4830
|
| `"type": null` | `"type" is null, which is not a name. … PRESENT-and-null is not absent: getValue(map, "type", "region") takes the default only when the key is missing, so this map matches no case, readAttachment returns null, and the attachment is dropped from the skeleton without a word. Remove the key, or name a type.` Leaving the key **out** is legal and reads as `region`; writing it as `null` is not the same thing ([#577](https://github.com/firejune/rigc/issues/577)) |
|
|
4603
4831
|
| constraint `type` of anything else | `constraint type "X" is not one Spine 4.3 knows. The five are: ik, transform, path, physics, slider.` — all five are emitted, so this is a typo, and a typo is what the parser drops in silence |
|
|
4604
4832
|
| a path attachment's `lengths` | `"lengths" is not authored — rigc measures the setup arc length of each curve off the geometry` (§3.4). Not a deferral: a second copy of a number the vertices already fix |
|
|
4605
|
-
| a `deform` timeline on a path attachment | `a path attachment does have a vertex array, and rigc does not key it yet` — the format allows it and an animated track is a real idiom, but a deformed path invalidates the `lengths` a `constantSpeed: false` traversal reads. Move the curve by posing the bones its vertices are bound to |
|
|
4606
4833
|
| any key neither format has, anywhere in either file | `<object> has a key this compiler does not read: "x" (did you mean "y"?) … Known here: …` (§5.1). Not a deferral either: a key nothing reads is a value you wrote and the emitted skeleton does not contain |
|
|
4607
4834
|
|
|
4608
4835
|
Two more limits that are not errors but will shape what you can attempt:
|
|
@@ -5937,6 +6164,13 @@ There are two matchers and the `how` column says which one answered:
|
|
|
5937
6164
|
confidence: how much better the winning position was than the best rival inside
|
|
5938
6165
|
the search window. This is what gives a shot like a chain of touching links any
|
|
5939
6166
|
drift at all — under connected components alone, every frame of it is ambiguous.
|
|
6167
|
+
⭐ **Only the pixels of it your own composite lets show are correlated**: a
|
|
6168
|
+
template pixel you draw something over cannot match the reference wherever the
|
|
6169
|
+
slot really is, so it adds the same residual at every offset — and, because
|
|
6170
|
+
sliding the template moves those samples onto other pixels, their gradient
|
|
6171
|
+
decides the winner wherever the visible basin is shallow. That is not a
|
|
6172
|
+
hypothetical: it is what made four of the seven examples in this repository
|
|
6173
|
+
report 0.8–2.2 px against frames rendered from themselves (issue #698).
|
|
5940
6174
|
|
|
5941
6175
|
⚠️ **Both matchers are capped, and a blank is a real answer.** A part can be
|
|
5942
6176
|
displaced by about its own size and still be that part in the picture; past that,
|
|
@@ -5946,6 +6180,23 @@ the 47 px course as its drift. The bar rises with the distance being claimed: a
|
|
|
5946
6180
|
peak sitting where you already drew the slot only has to confirm it, a peak
|
|
5947
6181
|
claiming the part moved most of a radius has to be distinctive to be believed.
|
|
5948
6182
|
|
|
6183
|
+
⚠️ **And a slot you cover completely has no drift to report at all.** If every
|
|
6184
|
+
pixel a slot draws is painted over by something later in your own draw order,
|
|
6185
|
+
none of its ink reaches the picture, so there is nothing to correlate — `check`
|
|
6186
|
+
says so by name and counts it out, rather than correlating hidden pixels against
|
|
6187
|
+
whatever is on top of them:
|
|
6188
|
+
|
|
6189
|
+
```
|
|
6190
|
+
its drift is not measurable — every one of the 2116 px this 48x54 px slot draws is
|
|
6191
|
+
covered by something the candidate draws over it, so none of its own ink reaches the
|
|
6192
|
+
picture to be correlated against
|
|
6193
|
+
```
|
|
6194
|
+
|
|
6195
|
+
That is a fact about the shot and not an error: a part behind another part is
|
|
6196
|
+
still where the rig put it, and the answer to *where did it land* is that this
|
|
6197
|
+
run cannot say. Read it beside the `slots` column, which counts it as
|
|
6198
|
+
unattributed.
|
|
6199
|
+
|
|
5949
6200
|
The `slots` column is how many of the slots you drew got an answer at all, and the
|
|
5950
6201
|
summary line carries the same denominator. `N reference component(s) no slot
|
|
5951
6202
|
reaches` means the reference frame contains something none of your slots overlaps:
|
|
@@ -5969,25 +6220,67 @@ bun cli.ts check --candidate <build> --frames <frames>
|
|
|
5969
6220
|
frames 11 on disk, candidate samples 11, 11 compared
|
|
5970
6221
|
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)
|
|
5971
6222
|
⤷ 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.
|
|
5972
|
-
slot drift worst 0.
|
|
6223
|
+
slot drift worst 0.5 px "plate" at f0004
|
|
6224
|
+
⤷ 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
|
|
5973
6225
|
per-frame all 10 adjacent pair(s) change by as much as the reference's own frames do
|
|
5974
6226
|
```
|
|
5975
6227
|
|
|
5976
6228
|
⚠️ **The MAE floor is zero and the slot-drift floor is not**, and the second half of
|
|
5977
6229
|
that is the instrument's own arithmetic rather than anything about your rig. Both
|
|
5978
6230
|
sides are the same pixels, so every frame's MAE is exactly 0 — which is why the line
|
|
5979
|
-
says `(exact)` instead of naming a frame. The drift is a **correlation
|
|
5980
|
-
|
|
5981
|
-
clamped to half a pixel on each axis
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
6231
|
+
says `(exact)` instead of naming a frame. The drift is a **correlation**: it finds
|
|
6232
|
+
the whole-pixel offset that matches best and then fits a parabola through three
|
|
6233
|
+
whole-pixel residuals for the fraction, clamped to half a pixel on each axis.
|
|
6234
|
+
|
|
6235
|
+
🔑 **So the bound has two halves, and the run states both of them for you.** The
|
|
6236
|
+
`⤷ bounded by` line is not a constant off this page: it is `hypot(|dx| + 0.5,
|
|
6237
|
+
|dy| + 0.5)` for that match's own whole-pixel winner `(dx, dy)`. A winner at the
|
|
6238
|
+
offset you drew — which is the answer on every frame of a correct rig — bounds the
|
|
6239
|
+
whole figure at `hypot(0.5, 0.5) = 0.71 px`, and the line says the figure is the
|
|
6240
|
+
sub-pixel step and nothing else. A winner one pixel out bounds it at 1.58 px and
|
|
6241
|
+
says the correlation moved the part. ⇒ **Read a drift against the line under it,
|
|
6242
|
+
never against a number from a page about another rig.** A component match prints
|
|
6243
|
+
the other sentence — two centroids have no such bound, only the search radius.
|
|
6244
|
+
|
|
6245
|
+
🚨 **The clamp bounds the figure only while the whole-pixel winner is the
|
|
6246
|
+
identity, and for a while nothing checked that second half.** Issue #698: four of
|
|
6247
|
+
the seven examples in this repository read **0.81, 1.11, 2.13 and 2.21 px** against
|
|
6248
|
+
frames rendered from themselves, because the template carried the pixels the
|
|
6249
|
+
candidate draws *over itself*. Those match nothing wherever the slot really is, so
|
|
6250
|
+
they add a residual at every offset — and sliding the template moves them onto
|
|
6251
|
+
other pixels, so their gradient walks the winner off the origin wherever the
|
|
6252
|
+
visible basin is shallow. The exhaustive whole-pixel field for `flex`'s backdrop
|
|
6253
|
+
had its minimum at `(2, 0)` scoring 7.75 against the identity offset's 8.76.
|
|
6254
|
+
Correlating only what shows put all seven back on `(0, 0)`, and `C25`–`C30` of
|
|
6255
|
+
the repository's own selftest gate that over every example it ships — so the next
|
|
6256
|
+
one is gated by arriving.
|
|
6257
|
+
|
|
6258
|
+
🔸 The same page said `3.7 px` before issue #678, for an unrelated reason worth
|
|
6259
|
+
keeping: the coarse sweep started at `−radius` and stepped by its stride, so the
|
|
6260
|
+
identity offset was on the lattice only when the stride divided the radius, and
|
|
6261
|
+
the `±1` refinement around a winner two pixels out could not reach back to it.
|
|
6262
|
+
|
|
6263
|
+
**A second example, and the one the repair was measured on.** `gallery/flex` draws
|
|
6264
|
+
a banner and a leaf over a full-stage backdrop, so almost every slot of it reaches
|
|
6265
|
+
the template matcher:
|
|
6266
|
+
|
|
6267
|
+
```bash
|
|
6268
|
+
bun cli.ts build --rig gallery/flex/rig.json --motion gallery/flex/motion.json --out <build>
|
|
6269
|
+
bun cli.ts render --candidate <build> --animation wave --fps 12 --out <frames>
|
|
6270
|
+
bun cli.ts check --candidate <build> --frames <frames>
|
|
6271
|
+
```
|
|
6272
|
+
|
|
6273
|
+
```
|
|
6274
|
+
── wave — candidate animation "wave", 12 fps ──
|
|
6275
|
+
frames 30 on disk, candidate samples 30, 30 compared
|
|
6276
|
+
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)
|
|
6277
|
+
⤷ 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.
|
|
6278
|
+
slot drift worst 0.2 px "plate" at f0009
|
|
6279
|
+
⤷ 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
|
|
6280
|
+
per-frame all 29 adjacent pair(s) change by as much as the reference's own frames do
|
|
6281
|
+
```
|
|
6282
|
+
|
|
6283
|
+
That figure was **2.21 px** on the same command before #698, on the same slot.
|
|
5991
6284
|
|
|
5992
6285
|
⭐ **And the same frames plus two deliberately wrong builds are what say which column
|
|
5993
6286
|
answers which question.** Each differs from the build above in exactly one way —
|
|
@@ -6029,6 +6322,15 @@ samples, so the column is correctly silent on it. ⇒ Read the pair together: a
|
|
|
6029
6322
|
MAE with `per-frame` silent is a pose in the wrong place or at the wrong moment; a
|
|
6030
6323
|
loud MAE with `per-frame` firing is a **speed**, which is a curve.
|
|
6031
6324
|
|
|
6325
|
+
🔸 Neither block above reprints its own `⤷ bounded by` line, for the same reason
|
|
6326
|
+
both carry the declaration: the spec edits are not in the tree, so those two lines
|
|
6327
|
+
would be hand-written rather than taken. What the gate measures on them is stated
|
|
6328
|
+
instead — the worst match of each sits at whole-pixel `(0, −31)` and `(0, −32)`,
|
|
6329
|
+
which bounds them at **31.50 px** and **32.50 px**. That is the contrast worth
|
|
6330
|
+
carrying away: a correct rig's drift is bounded by the clamp because its winner is
|
|
6331
|
+
the identity, and these two are bounded by nothing of the kind because a part
|
|
6332
|
+
really moved.
|
|
6333
|
+
|
|
6032
6334
|
**The `chains` block is the same two measures on the unit you actually repair.**
|
|
6033
6335
|
|
|
6034
6336
|
```
|