spine-rigc 0.28.0 → 0.30.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 +4 -4
- package/cli.ts +10 -0
- package/docs/AUTHORING.md +290 -105
- package/docs/INGEST.md +15 -2
- package/docs/SPEC_COVERAGE.md +5 -4
- package/package.json +1 -1
- package/src/compile.ts +480 -308
- package/src/ingest.ts +248 -22
- package/src/png.ts +162 -7
- package/src/rig.ts +32 -21
- package/src/timelines.ts +101 -13
- package/src/types.ts +13 -7
- package/src/validate.ts +541 -28
- package/tools/editor_roundtrip.ts +1 -1
- package/tools/plate.ts +6 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -169,7 +169,7 @@ What the flags mean:
|
|
|
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 31 validity rules (**the default**) · `spine-html` = all 46, 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` |
|
|
@@ -324,6 +324,72 @@ texel count beside it so both numbers are visible:
|
|
|
324
324
|
measures the PNG. Reach for `--atlas-in` when the pack is what you were handed, or
|
|
325
325
|
when drawing through the pack's own texels is the point.
|
|
326
326
|
|
|
327
|
+
🚨 **A page that declares a size it does not have is a different thing, and it is
|
|
328
|
+
refused** ([#715](https://github.com/firejune/rigc/issues/715)). The common shape
|
|
329
|
+
is a pack whose `4096x4096` pages ship as `2048x2048` PNGs with the atlas
|
|
330
|
+
untouched — every region still stated in 4096-space — and that is **not** what a
|
|
331
|
+
`scale:` line says. A runtime draws it anyway: `TextureAtlas` computes every
|
|
332
|
+
region's UVs as a fraction of the **declared** size (`region.u = region.x /
|
|
333
|
+
page.width`) and nothing in the region mapping opens the file, so the same
|
|
334
|
+
fraction of the picture is addressed whatever size the PNG is. What a page grid
|
|
335
|
+
that disagrees with its file breaks is every reader that addresses the page in
|
|
336
|
+
**texels** — `A19`'s alpha scan, the region lift a mesh generator traces, and a
|
|
337
|
+
canvas renderer that cuts each part out by source rectangle. So `A06` refuses it
|
|
338
|
+
under **both** profiles, names the ratio it measured on each axis, and prints the
|
|
339
|
+
header that states the same art truthfully:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# FAIL A06_ATLAS_PAGE_SIZE_MATCHES_PNG: page "hero.png" declares 4096x4096 and its PNG is 2048x2048 —
|
|
343
|
+
# 0.5000 of the declared width and 0.5000 of the declared height. … The format states coarser texels
|
|
344
|
+
# with the `scale:` header and rigc builds that: declare `size: 2048, 2048` with `scale: 0.5000` and
|
|
345
|
+
# multiply every `bounds`/`offsets` on this page by 0.5000, and every part keeps the size it has now.
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
⇒ **Carry that out and the build is green with the attachment sizes it already
|
|
349
|
+
had**, because the division cancels (`originalWidth / scale`, above) — which is
|
|
350
|
+
the measurement that settles where this clause belongs: the format already has a
|
|
351
|
+
line for coarser texels and rigc already reads it, so the refusal hands over a
|
|
352
|
+
repair rather than a preference. Two limits, and the message names whichever one
|
|
353
|
+
it hits instead of leaving it to be discovered:
|
|
354
|
+
|
|
355
|
+
- a region whose `bounds`/`offsets` do not land on a whole texel of the file
|
|
356
|
+
cannot be re-declared at all, and the refusal names the first number that does
|
|
357
|
+
not divide;
|
|
358
|
+
- a page whose two axes differ — `4096x4096` declared over a `2048x3072` PNG —
|
|
359
|
+
has no honest header either, because `scale:` carries **one** ratio. The message
|
|
360
|
+
says so rather than offering one, and the repair is to re-export the page at the
|
|
361
|
+
size the atlas declares, or repack.
|
|
362
|
+
|
|
363
|
+
🚨 **A page that is not a PNG is refused by name, before anything is compiled
|
|
364
|
+
against it** ([#732](https://github.com/firejune/rigc/issues/732)). rigc reads PNG
|
|
365
|
+
and nothing else — the size `A06` judges, the alpha `A19` judges, the renderer and
|
|
366
|
+
the region lift all decode PNG, and rigc links no decoder for any other format —
|
|
367
|
+
and a file's name is not evidence: a production pack shipped WebP pages called
|
|
368
|
+
`*.png`. What a page is comes off its first bytes. The refusal says what it found
|
|
369
|
+
there — a WebP, JPEG, GIF, KTX or KTX2 file by its signature, or no format rigc
|
|
370
|
+
recognises, with the first bytes in hex either way — and a file that begins as a
|
|
371
|
+
PNG and runs out before its `IEND` chunk is called **truncated**, wherever it was
|
|
372
|
+
cut. `build --atlas-in` and `explain --atlas-in` say so where they open the pack,
|
|
373
|
+
every such page in one sentence and before the compile; a loose part that is not a
|
|
374
|
+
PNG is refused as the image it is; and `rigc validate` on a directory holding such
|
|
375
|
+
a page fails `A06` with the same sentence:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# FAIL A06_ATLAS_PAGE_SIZE_MATCHES_PNG: page "hero.png" declares 2048x2048 and its file cannot be read as PNG, so
|
|
379
|
+
# the size was not measured: /abs/art/hero.png is a WebP image (a RIFF/WEBP container whose first chunk is
|
|
380
|
+
# "VP8L"), not a PNG: … rigc reads PNG and nothing else — its page-size and alpha readers, its renderer and
|
|
381
|
+
# its region lift all decode PNG, and it links no decoder for any other format — so nothing in this file was
|
|
382
|
+
# measured. Re-export it as PNG
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
⇒ **Re-export the page as PNG under the name the atlas gives it**, and the next run
|
|
386
|
+
measures it like any other. ⚠️ `A06` deliberately does not read the width and
|
|
387
|
+
height a WebP header carries in a fixed field. Doing so would print a size that no
|
|
388
|
+
reader in this tree can check — the parse would agree only with the forgery it was
|
|
389
|
+
written against — about a page that is refused either way; and the variant that
|
|
390
|
+
let a size-matching WebP page pass was measured building green under the default
|
|
391
|
+
profile and writing a directory whose next `render` refused the page.
|
|
392
|
+
|
|
327
393
|
**What `--out` holds afterwards:** `skeleton.json` and a `skeleton.atlas` that is
|
|
328
394
|
the pack, page paths pointing back at the pack's own PNGs — so `rigc validate
|
|
329
395
|
<that directory>` reads it green with no flags, exactly as it reads a loose
|
|
@@ -342,8 +408,8 @@ because the text passes through by line; regions the rig does not use stay in th
|
|
|
342
408
|
file, because a real pack is shared between cuts and an importer that quietly
|
|
343
409
|
dropped half of one would make `--out` disagree with the pack it was built from.
|
|
344
410
|
|
|
345
|
-
|
|
346
|
-
**loads clean and draws wrong
|
|
411
|
+
Five things are refused rather than warned about, because each of them otherwise
|
|
412
|
+
**loads clean and draws wrong** — or, the last, cannot be read back at all:
|
|
347
413
|
|
|
348
414
|
| What | Why it cannot be a warning |
|
|
349
415
|
| --- | --- |
|
|
@@ -351,6 +417,7 @@ Four things are refused rather than warned about, because each of them otherwise
|
|
|
351
417
|
| 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 |
|
|
352
418
|
| 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 |
|
|
353
419
|
| 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)) |
|
|
420
|
+
| a page file that is not a PNG | nothing in rigc can read it — not the gate, not `render`, not a mesh generator — so a green build over it would certify pixels nobody opened. Named by what its first bytes are, every such page of the pack in one sentence; the gate says the same for a directory that reaches it another way — `A06`, §5.2 ([#732](https://github.com/firejune/rigc/issues/732)) |
|
|
354
421
|
|
|
355
422
|
One limit, stated rather than discovered:
|
|
356
423
|
|
|
@@ -819,8 +886,7 @@ behind it writes literal `x`/`y` instead.
|
|
|
819
886
|
|
|
820
887
|
**R10 — The `animations` object is keyed in the editor's order, not in yours, and
|
|
821
888
|
names that have no one order are refused.** Declare animations in whatever order
|
|
822
|
-
reads best; the emit keys them the way the Spine editor does
|
|
823
|
-
case-insensitive**. This is the one place rigc
|
|
889
|
+
reads best; the emit keys them the way the Spine editor does. This is the one place rigc
|
|
824
890
|
reorders anything you wrote, and it is not cosmetic: a `slider`'s animation is a
|
|
825
891
|
**name** in JSON and an **ordinal** in the format's binary half, so an editor that
|
|
826
892
|
re-sorts the object repoints every slider whose animation moved index — silently,
|
|
@@ -829,61 +895,73 @@ in a file that still parses and still gates green (§3.5.2,
|
|
|
829
895
|
animation's own body is byte-identical either way, and every other collection is
|
|
830
896
|
emitted in the order you gave it.
|
|
831
897
|
|
|
832
|
-
⚠️ **What
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
|
898
|
+
⚠️ **What that comparator is, measured rather than inferred.** Five name lists
|
|
899
|
+
went through a licensed 4.3.26 editor as JSON and came back as JSON, and the ten
|
|
900
|
+
answers are in the repository as
|
|
901
|
+
`fixtures/editor-order/probe{1..5}.{in,out}.json`
|
|
902
|
+
([#728](https://github.com/firejune/rigc/issues/728)). Every row below is a clause
|
|
903
|
+
of the rule with the pair from those files that shows it — nothing here is a
|
|
904
|
+
guess, and the selftest re-derives each row from the files rather than from this
|
|
905
|
+
page:
|
|
906
|
+
|
|
907
|
+
| The editor | Shown by |
|
|
908
|
+
| --- | --- |
|
|
909
|
+
| folds case **upward**, per character, and leaves a character with no single upper case where it is | `z` before `_x` (`_` is 0x5F, above `Z` and below `a`); `z` before `ß`, which a whole-string upper case would write `SS` and sort among the `S`s |
|
|
910
|
+
| reads a **run of digits as a number** — at any position, after any script | `x2` before `x10`; `中2` before `中10` |
|
|
911
|
+
| **defers** two runs that are one number written twice, and settles them at the end with the leading zeros later | `a01` before `a1b`, so the walk did not stop at the equal runs; `a1` before `a01`, which is how it ends |
|
|
912
|
+
| **skips a space**, and gives that tie to the name with fewer of them — after the leading-zero tie-break, not before it | `bc` before `b c`; `a 1` before `a01` |
|
|
913
|
+
| otherwise orders by **code point after folding**, and does not treat punctuation as ignorable | `a-1` before `a1`; `a1b` before `a_1` |
|
|
914
|
+
| leaves a remaining **tie in the order your file declares it** | `turn` before `Turn`; `Mango` before `mango` — each given in that order and returned in it |
|
|
915
|
+
| writes **leaves then folders at the root**, and **sub-folders then leaves inside a folder** | `h` before `f/sub1/deep/q`; `f/sub1/x` before `f/leafA` |
|
|
916
|
+
|
|
917
|
+
⭐ **A tie is not an ambiguity, and that is what retired most of this rule's
|
|
918
|
+
refusals.** Two names the comparator cannot separate come back in the order the
|
|
919
|
+
file gave them, so the order rigc emits for such a pair is **your own declaration
|
|
920
|
+
order** and the editor keeps it. Nothing moves index, so there is nothing to
|
|
921
|
+
refuse: `Turn` beside `turn`, `turn01` beside `turn1`, `1turn` beside `turn`,
|
|
922
|
+
`wave_x` beside `wavea` all build, and each is keyed the way the round trips say.
|
|
923
|
+
|
|
924
|
+
So what is left refused is short, and each row is a pair two readings of the
|
|
925
|
+
**same** measurement order differently:
|
|
926
|
+
|
|
927
|
+
| Refused | Because | Instead |
|
|
843
928
|
| --- | --- | --- |
|
|
844
|
-
|
|
|
845
|
-
|
|
|
846
|
-
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
as R10, in the collection that was believed exempt from it. A skin is a **name** in
|
|
871
|
-
the JSON half of the format and an **ordinal** in the binary half —
|
|
872
|
-
`skins[readInt()]` for an attachment timeline, `skins[skinIndex]` for a linked mesh
|
|
873
|
-
— so an editor that writes the array in another order repoints every such
|
|
874
|
-
reference, silently, in a file that still parses. Measured: a rig built
|
|
875
|
-
`default, zulu, mike, alpha` exported `default, alpha, mike, zulu`
|
|
929
|
+
| `number` — two or more digit runs that are each one number written twice, pointing opposite ways (`x01y1` against `x1y01`) | the round trips measured one deferred run; keeping the first such difference and keeping the last are both consistent with that | write each number one way, with leading zeros or without |
|
|
930
|
+
| `separator` — a whitespace character that is not a space (a tab, a no-break space) | the space is measured skipped and nothing else is, so a rule that skips all whitespace and one that skips only the space disagree here | rename so the only whitespace in either name is a space |
|
|
931
|
+
| `folder` — two sibling **folders** the comparator cannot separate (`Fx/a` against `fx/b`) | a tie between two leaves is file order, but the editor holds a folder as an object and keeps its entries together, and no round trip carried two folders one fold apart | rename one of the folders so they differ by more than letter case, spacing or a leading zero |
|
|
932
|
+
|
|
933
|
+
⭐ **Capitals and numbered series are not what is refused.**
|
|
934
|
+
`Sweep, Turn, Wave, Zoom02, Zoom10` builds, and so does `shot1 … shot12`: a
|
|
935
|
+
numbered series that crosses 9 → 10 is keyed **1, 2, … 9, 10, 11, 12**, which is
|
|
936
|
+
what the editor does with it — and is not what a codepoint sort does.
|
|
937
|
+
|
|
938
|
+
✅ **This rule was a quantifier over comparators until #728, and that is what
|
|
939
|
+
changed.** rigc keyed `animations` codepoint-ascending until
|
|
940
|
+
[#543](https://github.com/firejune/rigc/issues/543) and then emitted a member of
|
|
941
|
+
the "natural, case-insensitive" family, refusing every pair the family could
|
|
942
|
+
disagree about. Both refusals were sound and both over-refused by construction,
|
|
943
|
+
because a quantifier stands in for a measurement: a name with a capital, an
|
|
944
|
+
accent, a space or a folder in it was refused rather than emitted in the order the
|
|
945
|
+
editor returns. Measuring the comparator moves no byte on any set the old rule
|
|
946
|
+
accepted; it stops refusing the ones it did.
|
|
947
|
+
|
|
948
|
+
**R11 — The `skins` array is written with `default` first and the rest in exactly
|
|
949
|
+
the order R10 describes.** A skin is a **name** in the JSON half of the format and
|
|
950
|
+
an **ordinal** in the binary half — `skins[readInt()]` for an attachment timeline,
|
|
951
|
+
`skins[skinIndex]` for a linked mesh — so an editor that writes the array in
|
|
952
|
+
another order repoints every such reference, silently, in a file that still
|
|
953
|
+
parses. Measured: a rig built `default, zulu, mike, alpha` exported
|
|
954
|
+
`default, alpha, mike, zulu`
|
|
876
955
|
([#541](https://github.com/firejune/rigc/issues/541)).
|
|
877
956
|
|
|
878
|
-
⚠️ **
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
names, and the two refusals say which is which.
|
|
957
|
+
⚠️ **It is one comparator, and that is measured too.** Two of the five round
|
|
958
|
+
trips carried one name list as **both** collections and both came back in one
|
|
959
|
+
order, which is what retired the wider skin refusal #541 shipped: until #728,
|
|
960
|
+
`Zulu` beside `mike` and `mike10` beside `mike2` built as animation names and were
|
|
961
|
+
refused as skin names. Both build now. `default` is pinned rather than sorted, on
|
|
962
|
+
names the same comparator puts ahead of it — `default` before `2`, `default`
|
|
963
|
+
before `A` — so renaming a skin away from `default` makes it an ordinary name that
|
|
964
|
+
sorts like one.
|
|
887
965
|
|
|
888
966
|
**R12 — A placeholder that more than one skin fills gets a per-skin attachment
|
|
889
967
|
`name`, and the `default` skin may not be one of those skins.** rigc writes
|
|
@@ -1010,7 +1088,7 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
1010
1088
|
| `bone` | required; must be a bone this rig declares | — |
|
|
1011
1089
|
| `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 |
|
|
1012
1090
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
1013
|
-
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2`
|
|
1091
|
+
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2` and `rgb2` tracks key it over time and require it | — (🚫 `A12` under `spine-html`) |
|
|
1014
1092
|
| `blend` | `normal` · `additive` · `multiply` · `screen` | `normal` |
|
|
1015
1093
|
|
|
1016
1094
|
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
@@ -2045,8 +2123,24 @@ is dead data in silence:
|
|
|
2045
2123
|
runs, under any skin there is.
|
|
2046
2124
|
|
|
2047
2125
|
rigc refuses both halves by name, and `A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED` checks
|
|
2048
|
-
the artifact for them.
|
|
2049
|
-
|
|
2126
|
+
the artifact for them.
|
|
2127
|
+
|
|
2128
|
+
⭐ **A bone or a constraint may be listed by more than one skin**, and each list
|
|
2129
|
+
is emitted as written: two mutually exclusive variants of one body region both
|
|
2130
|
+
activating the bone they switch on is the ordinary case. At runtime it is active
|
|
2131
|
+
while **the skin being worn** lists it — `Skeleton.updateCache` walks that skin's
|
|
2132
|
+
`bones` and turns each one on, along with its whole ancestor chain — so a bone two
|
|
2133
|
+
skins name poses under either of them and under neither of the rest. A consumer
|
|
2134
|
+
that combines the two with `Skin.addSkin` gets it once; the runtime deduplicates
|
|
2135
|
+
by object identity.
|
|
2136
|
+
|
|
2137
|
+
⚠️ **The worn skin, and only the worn skin — including `default`.** The
|
|
2138
|
+
default-skin fallback is about *art*: `getAttachment` looks in the current skin
|
|
2139
|
+
and then in `SkeletonData.defaultSkin`, and `updateCache` does no such thing. So a
|
|
2140
|
+
`skin: true` bone that only the `default` skin lists is **inactive under every
|
|
2141
|
+
other skin** and inactive with no skin set. Listing it there to mean "always on"
|
|
2142
|
+
gets the opposite; leave the flag off instead, which is what "active under every
|
|
2143
|
+
skin" is spelled as.
|
|
2050
2144
|
|
|
2051
2145
|
⚠️ **The two spellings are told apart by these seven keys** — `attachments`,
|
|
2052
2146
|
`bones`, `ik`, `transform`, `path`, `physics`, `slider` — so a skin that uses any of
|
|
@@ -2377,9 +2471,11 @@ re-rendered mean absolute error from 10.4655 / 8.4961 / 8.7140 down to
|
|
|
2377
2471
|
identically, which is what rigc emitted when that trip was measured.)
|
|
2378
2472
|
|
|
2379
2473
|
⚠️ The editor's comparator is natural and case-insensitive
|
|
2380
|
-
([#539](https://github.com/firejune/rigc/issues/539))
|
|
2381
|
-
|
|
2382
|
-
|
|
2474
|
+
([#539](https://github.com/firejune/rigc/issues/539)) and is now measured in full
|
|
2475
|
+
off five stored round trips
|
|
2476
|
+
([#728](https://github.com/firejune/rigc/issues/728)) — so the emit is the
|
|
2477
|
+
editor's own order, and only what those files leave open is a compile error.
|
|
2478
|
+
**R10** has the rule and the three shapes to avoid.
|
|
2383
2479
|
|
|
2384
2480
|
✅ **What that repair does not reach is a compile error now, not a hazard.** This
|
|
2385
2481
|
paragraph used to say that names a codepoint sort and a friendlier one disagree
|
|
@@ -2391,7 +2487,8 @@ that to naming discipline — and since
|
|
|
2391
2487
|
[#543](https://github.com/firejune/rigc/issues/543) it does better than refusing
|
|
2392
2488
|
those two, because they are the two sets the editor's answer is **known** for:
|
|
2393
2489
|
both are emitted in the order it returned. What is still a compile error is the
|
|
2394
|
-
set whose order turns on one of the
|
|
2490
|
+
set whose order turns on one of the three things the round trips of
|
|
2491
|
+
[#728](https://github.com/firejune/rigc/issues/728) leave open, printed with both
|
|
2395
2492
|
names, which of them decides it, and the rename that settles it. What changed is
|
|
2396
2493
|
the price of forgetting: a build that stops, rather than a slider that silently
|
|
2397
2494
|
applies the wrong animation.
|
|
@@ -3013,7 +3110,10 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
3013
3110
|
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
3014
3111
|
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
3015
3112
|
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
3113
|
+
| `slot` | `rgb` | `[r, g, b]` in 0..1 — the light colour **without** its alpha, which stays wherever the setup pose or an `alpha` track puts it. Emitted as `{ time, color: "rrggbb" }` |
|
|
3114
|
+
| `slot` | `alpha` | `[a]`, **0 to 1** — the light colour's alpha alone; the rgb is left where it is. Emitted as `{ time, value }`, a number rather than a byte |
|
|
3016
3115
|
| `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) |
|
|
3116
|
+
| `slot` | `rgb2` | `[lr, lg, lb, dr, dg, db]` in 0..1 — the two-colour tint **without** the light alpha, six channels. The slot must declare a setup `dark` (§3.3) |
|
|
3017
3117
|
| `slot` | `attachment` | the attachment name, or `null` for "show nothing" |
|
|
3018
3118
|
| `physics` | `inertia`, `strength`, `damping`, `mass`, `wind`, `gravity` | `[value]` — the constraint's own tuning, keyed over time |
|
|
3019
3119
|
| `physics` | `mix` | `[mix]`, **0 or more** — the constraint's authority |
|
|
@@ -3057,9 +3157,9 @@ accepts is what it accepts.
|
|
|
3057
3157
|
it actually poses, both ways, which is what makes the list checkable at all: it
|
|
3058
3158
|
was stated there too until the refusal had something to state.
|
|
3059
3159
|
|
|
3060
|
-
⚠️ **A `slot` track's `property` is one of the
|
|
3160
|
+
⚠️ **A `slot` track's `property` is one of the six above, and anything else is a
|
|
3061
3161
|
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
3062
|
-
attachment, rgba, rgba2)`, §5.1's row. Until
|
|
3162
|
+
attachment, rgba, rgb, alpha, rgba2, rgb2)`, §5.1's row. Until
|
|
3063
3163
|
[#650](https://github.com/firejune/rigc/issues/650) it was not: the emitter had a
|
|
3064
3164
|
branch for `attachment` and wrote **everything else** as an rgba timeline under
|
|
3065
3165
|
the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
@@ -3068,18 +3168,52 @@ the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
|
3068
3168
|
one-channel spelling of the same mistake was refused at compile as `rgba value
|
|
3069
3169
|
needs 4 channels, got 1`, a message about a key you had not written.
|
|
3070
3170
|
|
|
3071
|
-
- The
|
|
3072
|
-
`src/compile.ts`)
|
|
3073
|
-
|
|
3074
|
-
|
|
3171
|
+
- The six are the emitter's own dispatch table (`SLOT_TRACKS` in
|
|
3172
|
+
`src/compile.ts`), in the order `SkeletonJson.readAnimation` switches on
|
|
3173
|
+
them — which makes it the format's whole slot switch: `compileTrack` reads it
|
|
3174
|
+
to pick its branch, and the refusal prints `Object.keys` of the same object,
|
|
3175
|
+
so what you are told a slot accepts is what it accepts.
|
|
3075
3176
|
- **Nothing derives this page's copy of that list from the table**, and it is
|
|
3076
|
-
|
|
3177
|
+
six names long: no `DQ*`/`RD*`/`CUR*` control reads §4.4 (the only gated
|
|
3077
3178
|
table on this page is §3.5.2.1's, held by `RD01`–`RD06`). What keeps the two
|
|
3078
3179
|
in step is the control that quotes the message — `RF23` in `selftest.ts` —
|
|
3079
3180
|
which goes red if the accepted list ever widens without this page moving with
|
|
3080
3181
|
it. It did, on the day `rgba2` was added
|
|
3081
|
-
([#690](https://github.com/firejune/rigc/issues/690))
|
|
3082
|
-
|
|
3182
|
+
([#690](https://github.com/firejune/rigc/issues/690)) and again when `rgb`,
|
|
3183
|
+
`alpha` and `rgb2` were ([#730](https://github.com/firejune/rigc/issues/730)),
|
|
3184
|
+
which is the mechanism working rather than a hole in it.
|
|
3185
|
+
- 🎨 **`rgb`, `alpha` and `rgb2` are the SEPARABLE colour timelines, and they
|
|
3186
|
+
are not spellings of `rgba`.** Each poses part of the slot's colour and leaves
|
|
3187
|
+
the rest exactly where it was: `rgb` writes the light colour's r g b and never
|
|
3188
|
+
its alpha, `alpha` writes the alpha and never the r g b, and `rgb2` writes the
|
|
3189
|
+
light r g b and the dark colour and never the light alpha. So an `rgb` track
|
|
3190
|
+
and an `alpha` track on one slot, each on its own key times, is a fade and a
|
|
3191
|
+
tint that move independently — the shape an editor export carries when the
|
|
3192
|
+
two were keyed apart. Written as one `rgba` instead, every key would have to
|
|
3193
|
+
state the other channel at a time nobody keyed it, and a lone fade has no
|
|
3194
|
+
`rgba` spelling at all: an `rgba` key poses the r g b too, pinning whatever
|
|
3195
|
+
else tints the slot.
|
|
3196
|
+
- An `alpha` key is `[a]` like every other one-channel track, and it is the
|
|
3197
|
+
one colour key stored as a **number** rather than a byte (`{ time, value }`),
|
|
3198
|
+
so it is not rounded onto 1/255 the way the hex shapes are. It has to be
|
|
3199
|
+
**from 0 to 1**: the runtime clamps the posed alpha to that range only after
|
|
3200
|
+
interpolating the stored value, so a key of 1.5 would reach full opacity at a
|
|
3201
|
+
different time from a key of 1 — a curve nobody keyed. The compiler refuses
|
|
3202
|
+
it rather than clamping it (§5.1).
|
|
3203
|
+
- `rgb2`, like `rgba2`, needs the slot to declare a setup `dark` (§3.3) — for
|
|
3204
|
+
the same reason, measured the same way: without one the file loads and the
|
|
3205
|
+
first `state.apply` throws `TypeError: null is not an object` inside
|
|
3206
|
+
`RGB2Timeline.apply1`. The refusal names `rgb` as the way out.
|
|
3207
|
+
- ⚠️ **Two tracks of one slot that pose the same channel are a compile
|
|
3208
|
+
error**, even though they are two properties and the one-track-per-target
|
|
3209
|
+
rule above cannot see them: `rgba` beside `alpha` (both pose the alpha),
|
|
3210
|
+
`rgba` beside `rgb`, `rgba2` beside `rgb2`, and so on. A colour timeline
|
|
3211
|
+
poses its channels at **every** time — the setup value before its first key
|
|
3212
|
+
included — so the one the file states later overwrites the other everywhere
|
|
3213
|
+
and the first one's keys are read by nothing. `rgb` beside `alpha`, and
|
|
3214
|
+
`rgb2` beside `alpha`, share no channel and are the pairs to write.
|
|
3215
|
+
`A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN`
|
|
3216
|
+
(§5.2) names the same pair in a file rigc did not write.
|
|
3083
3217
|
- 🎨 **`rgba2` keys the two-colour tint, and the slot has to own one first.** A
|
|
3084
3218
|
track `{ "slot": "X", "property": "rgba2" }` on a slot whose rig spec declares
|
|
3085
3219
|
no `dark` (§3.3) is a compile error with the slot named, and it is not a
|
|
@@ -3090,11 +3224,12 @@ needs 4 channels, got 1`, a message about a key you had not written.
|
|
|
3090
3224
|
of the two-colour tint are refused under `--profile spine-html`, whose renderer
|
|
3091
3225
|
ignores them; the default `spine` profile that `build` runs reports `A12` as
|
|
3092
3226
|
`PROF` and never applies it.
|
|
3093
|
-
- The
|
|
3094
|
-
|
|
3095
|
-
refuses `rgb2` — and `rgba2`, and the
|
|
3096
|
-
|
|
3097
|
-
**attachment**, not on a slot, and rigc does
|
|
3227
|
+
- The six are every slot timeline the format has; anything else is refused
|
|
3228
|
+
here, and would be refused by the runtime's own reader too (`Invalid timeline
|
|
3229
|
+
type for a slot`). `A12_NO_DARK_COLOR` refuses `rgb2` — and `rgba2`, and the
|
|
3230
|
+
slot field — in a file under the `spine-html` profile (SPEC_COVERAGE §2.1).
|
|
3231
|
+
`sequence` is a timeline on an **attachment**, not on a slot, and rigc does
|
|
3232
|
+
not emit that one.
|
|
3098
3233
|
|
|
3099
3234
|
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
3100
3235
|
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
@@ -3142,8 +3277,8 @@ a delta from the constraint's own setting.
|
|
|
3142
3277
|
key states a mass and the pose holds `1 / mass`, so a `mass` key of `0` is an
|
|
3143
3278
|
infinite inverse mass — the constraint stops moving.
|
|
3144
3279
|
- **Four of the seven are bounded, and a key outside its bound is a compile
|
|
3145
|
-
error** ([#610](https://github.com/firejune/rigc/issues/610)). `mass`
|
|
3146
|
-
|
|
3280
|
+
error** ([#610](https://github.com/firejune/rigc/issues/610)). `mass` must be
|
|
3281
|
+
`> 0`, `damping` must be strictly inside `(0, 1)`, and `mix` and `strength`
|
|
3147
3282
|
must be `0` or more. `A23_PHYSICS_CONSTRAINT_EFFECTIVE` applies the same four to
|
|
3148
3283
|
a file rigc did not write, naming the animation, the constraint, the key time
|
|
3149
3284
|
and the value — so the compiler is where a spec you wrote is refused, and the
|
|
@@ -3153,11 +3288,39 @@ a delta from the constraint's own setting.
|
|
|
3153
3288
|
`PhysicsConstraintPose` documents `mix` as "a percentage (0+)" — so a negative
|
|
3154
3289
|
wind is the other direction and a `mix` of `1.5` is an over-mix. Both are real
|
|
3155
3290
|
and both compile.
|
|
3156
|
-
- ⚠️ **A `mix` key of exactly `0` is legal where a setup
|
|
3157
|
-
the difference is not an inconsistency
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3291
|
+
- ⚠️ **A `mix` or `strength` key of exactly `0` is legal where a setup value of
|
|
3292
|
+
`0` is not**, and the difference is not an inconsistency: a setup pose says what
|
|
3293
|
+
the constraint IS and a key says what it is doing for a stretch. For `mix`,
|
|
3294
|
+
`PhysicsConstraint.update` opens with `if (mix === 0) return;`, so muting a
|
|
3295
|
+
constraint for part of an animation is what a mix timeline is for. For
|
|
3296
|
+
`strength`, 0 takes the restoring term out of the velocity update and leaves
|
|
3297
|
+
`damping` and `inertia` applied — the offset is not pulled back *while the key
|
|
3298
|
+
holds*, which is "physics released" for that span, and the next key pulls it
|
|
3299
|
+
back ([#727](https://github.com/firejune/rigc/issues/727)). At rest the two
|
|
3300
|
+
part ways: a `strength` of 0 is a constraint nothing pulls back, which `A23`
|
|
3301
|
+
refuses, and a `mix` of 0 is a constraint muted until an animation keys it
|
|
3302
|
+
above 0 — the next bullet.
|
|
3303
|
+
- ⚠️ **A setup `mix` of `0` is legal when some animation keys it above 0** ([#743](https://github.com/firejune/rigc/issues/743)).
|
|
3304
|
+
A constraint muted *at rest* is a rig whose physics is off until an animation
|
|
3305
|
+
switches it on, which is a design rather than the silence `A23` was built for.
|
|
3306
|
+
What `A23` refuses is the constraint nothing rescues: muted at setup and keyed
|
|
3307
|
+
**nowhere**, or keyed **to 0 only**. [measured] the three are not a matter of
|
|
3308
|
+
taste — muted-and-keyed-to-1 poses its bone exactly where the same rig resting
|
|
3309
|
+
at 1 does, and keyed-to-0 poses it exactly where one with no timeline at all
|
|
3310
|
+
does. The unnamed global timeline counts as a key for every constraint whose
|
|
3311
|
+
own `mixGlobal` is set (§3.5), and so does an animation only a slider applies.
|
|
3312
|
+
- 📏 **What a `strength` key of `0` costs, measured through spine-core** on the
|
|
3313
|
+
generated overlay fixture, stepping at 60 fps from `Physics.reset` (#727). With
|
|
3314
|
+
no wind or gravity the offset coasts to a limit rather than running away — a
|
|
3315
|
+
0.5 s release and a 2.0 s release end **0.95 %** apart — and the restoring key
|
|
3316
|
+
takes it from 5.5063 back under 0.01 in **54 steps**. With `gravity -40` pulling,
|
|
3317
|
+
the offset travels at terminal velocity for as long as the key holds (178 units
|
|
3318
|
+
over 0.5 s, 843 over 2.0 s) and the restoring key still returns it to the
|
|
3319
|
+
never-released run's own equilibrium, **39.999969 against 40.000000**, in 13
|
|
3320
|
+
steps. No NaN in any of it. ⚠️ The neighbour that does NOT come back is the
|
|
3321
|
+
reason `mass` keeps the narrow bound on a key: a keyed `mass` of `0` is NaN from
|
|
3322
|
+
the first sub-step and **still NaN after the restoring key**, and a keyed
|
|
3323
|
+
`damping` of `2` was still 6.9e4 two seconds later.
|
|
3161
3324
|
|
|
3162
3325
|
On a track that names a `group`, one key's `v` may instead be a **map keyed by
|
|
3163
3326
|
member name**, whose entries are each exactly the `v` above — or a `derive`
|
|
@@ -3478,7 +3641,15 @@ mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
|
3478
3641
|
be **keyed over time** as `tracks` entries naming this constraint (§4.4); this
|
|
3479
3642
|
table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and `damping ≥ 1`
|
|
3480
3643
|
never settles — both are `A23`, here and on every timeline key that states them
|
|
3481
|
-
([#610](https://github.com/firejune/rigc/issues/610)).
|
|
3644
|
+
([#610](https://github.com/firejune/rigc/issues/610)). ⚠️ `strength: 0` is `A23` **here and not on a key**:
|
|
3645
|
+
at rest it is a constraint nothing pulls back, and on a key it is a release somebody
|
|
3646
|
+
asked for, which §4.4 states with the measurement behind it
|
|
3647
|
+
([#727](https://github.com/firejune/rigc/issues/727)). `mix: 0` is the one value
|
|
3648
|
+
here an animation can answer for: it rests the constraint **muted**, which is
|
|
3649
|
+
legal, and `A23` names it only when no timeline in any animation keys that `mix`
|
|
3650
|
+
above 0 — on a key it is the mute for a span (§4.4, [#743](https://github.com/firejune/rigc/issues/743)). None of the
|
|
3651
|
+
three is a compile error — a setup value this table states reaches the gate, where the whole
|
|
3652
|
+
file can be read; it is a **key** outside its bound that `build` refuses (§4.4). Every field but `bone` and `note` must be a finite
|
|
3482
3653
|
number: a non-number is rounded to `NaN` and emitted as `null`, which the runtime
|
|
3483
3654
|
reads as **zero**, so `"mass": "heavy"` used to ship a constraint that never
|
|
3484
3655
|
settles with no word from anybody (#307).
|
|
@@ -4588,7 +4759,11 @@ so `v` is three numbers and not one.
|
|
|
4588
4759
|
⚠️ **A muted constraint with no timeline is a finding, not an idiom.** Turning a
|
|
4589
4760
|
constraint on from an animation is the idiom (§4.10), so `A36`/`A37` only object to
|
|
4590
4761
|
all-zero mixes when **no** animation keys that constraint's `mix`. If you mute one
|
|
4591
|
-
at setup, key it somewhere.
|
|
4762
|
+
at setup, key it somewhere. `A23` asks the same question of a physics constraint
|
|
4763
|
+
([#743](https://github.com/firejune/rigc/issues/743)) and asks it more sharply:
|
|
4764
|
+
it reads the key **values**, so a `mix` timeline keying 0 only is not a rescue,
|
|
4765
|
+
and it counts the unnamed global timeline for every constraint declaring
|
|
4766
|
+
`mixGlobal`. `A36`/`A37` take any non-empty key array.
|
|
4592
4767
|
|
|
4593
4768
|
---
|
|
4594
4769
|
|
|
@@ -4737,7 +4912,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4737
4912
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
4738
4913
|
| `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 |
|
|
4739
4914
|
| `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 |
|
|
4740
|
-
| `skin "S"
|
|
4915
|
+
| `skin "S" lists "X" under "transform", but the rig declares it as a "ik" constraint — a skin looks its constraints up by name AND type, so this one is a miss and the loader throws` | §3.4.1 — move the name to the list named after the constraint's own kind. A name the rig declares under **no** kind is the other miss and says so (`skin "S" activates ik constraint "X", which this rig does not declare`). ⚠️ A name listed by **several skins** is not an error and no longer was one as of [#725](https://github.com/firejune/rigc/issues/725): the lists are per-skin sets and a bone or constraint in two of them is active under either |
|
|
4741
4916
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
4742
4917
|
| `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 |
|
|
4743
4918
|
| `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` |
|
|
@@ -4760,6 +4935,8 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4760
4935
|
| `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` |
|
|
4761
4936
|
| `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 |
|
|
4762
4937
|
| `image "X.png" is not on disk at …` | fix the name, or point `--images` at the right directory |
|
|
4938
|
+
| `image "X.png": /…/X.png is a WebP image (…), not a PNG: its first 12 byte(s) are …` | the file is there and is not a PNG — the name ends in `.png` and the bytes decide. Re-export it as PNG; the same sentence says **truncated** for a PNG that runs out before its `IEND`, and then the repair is a whole copy (§0.2, [#732](https://github.com/firejune/rigc/issues/732)) |
|
|
4939
|
+
| `--atlas-in <pack>.atlas: N of its M page(s) cannot be read as PNG, and nothing was compiled against the pack — page "p.png": …` | the same, for every page of the pack at once, before the compile: re-export each named page as PNG under the name the atlas gives it (§0.2) |
|
|
4763
4940
|
| `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 |
|
|
4764
4941
|
| `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 |
|
|
4765
4942
|
| `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 |
|
|
@@ -4772,7 +4949,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4772
4949
|
| `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 |
|
|
4773
4950
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
4774
4951
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
4775
|
-
| `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`
|
|
4952
|
+
| `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` is `> 0`, `damping` is inside `(0, 1)`, `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)). ⚠️ Those are the bounds a **key** is held to. A setup `strength` of `0` is refused too, but by `A23` rather than here, and with its own sentence — `physics "C" has strength 0; nothing pulls it back` ([#727](https://github.com/firejune/rigc/issues/727)) |
|
|
4776
4953
|
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
4777
4954
|
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
4778
4955
|
| `key times must strictly increase (at t=…)` | including after `lag` and `stagger` |
|
|
@@ -4833,12 +5010,16 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4833
5010
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
4834
5011
|
| `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 |
|
|
4835
5012
|
| `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 |
|
|
5013
|
+
| `rgb value needs 3 channels, got 4` · `alpha value needs 1 channel, got 2` · `rgb2 value needs 6 channels, got 7` | §4.4 — the separable shapes, refused in the words the two above use: an `rgb` key is `[r, g, b]`, an `alpha` key is `[a]`, an `rgb2` key is `[lr, lg, lb, dr, dg, db]`. Four channels on an `rgb` track is the likeliest way to get it wrong — the `rgba` spelling on the timeline that exists to leave the alpha alone — and seven on `rgb2` is the `rgba2` spelling. [#730](https://github.com/firejune/rigc/issues/730) |
|
|
5014
|
+
| `animation "A" slot "X" alpha: alpha key value must be [a]` | §4.4 — an alpha key's `v` is a one-element array like every other one-channel track, even though the file writes it bare (`{ time, value }`). The same row exists for each colour shape with its own spelling (`rgb key value must be [r,g,b]`, …) |
|
|
5015
|
+
| `animation "A" slot "X" alpha: key at t=T is V; an alpha is a number from 0 to 1 …` | §4.4 — the one colour key stored as a number, so the one nothing clamps on the way out. The runtime clamps the posed alpha only after interpolating, so a key outside 0..1 bends the curve toward a value no pose holds; state the value you mean. 0 and 1 themselves are taken |
|
|
5016
|
+
| `animation "A" slot "X": tracks "P" and "Q" both key the slot's alpha — a colour timeline poses its channels at every time …` | §4.4 — two colour tracks of one slot that pose a shared channel (`rgba` + `alpha`, `rgba` + `rgb`, `rgba2` + `rgb2`, …). Each poses its channels at every time, its setup value before its first key included, so the later in the file overwrites the other everywhere and one of them is read by nothing. Key each channel once: `rgb` and `alpha` for two halves on their own key times, `rgba` for both together. The channel named is whichever the two share — `light rgb`, `alpha`, `dark colour` |
|
|
4836
5017
|
| `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 |
|
|
4837
|
-
| `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 |
|
|
4838
|
-
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba, rgba2)` | §4.4 — a slot has exactly
|
|
4839
|
-
| `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 |
|
|
4840
|
-
| `N pair(s) of animation names have no one order: … "
|
|
4841
|
-
| `N pair(s) of skin names have no one order: … "
|
|
5018
|
+
| `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, rgb, alpha, rgba2, rgb2; 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 |
|
|
5019
|
+
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba, rgb, alpha, rgba2, rgb2)` | §4.4 — a slot has exactly six timelines — every one the format has — 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` |
|
|
5020
|
+
| `animation "A" slot "X" rgba2: slot "X" declares no setup "dark", and an "rgba2" timeline poses a slot's dark colour …` — and the same with `rgb2` | §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` (for `rgb2`, `rgb`) if only the light colour moves. `RGB2Timeline` was measured to throw the same way before `rgb2` joined the refusal ([#730](https://github.com/firejune/rigc/issues/730)). Raised before the keys are read, with the slot named, for the same reason the row above is |
|
|
5021
|
+
| `N pair(s) of animation names have no one order: … "Fx/a" / "fx/b" (folder) — "Fx/a" and "fx/b" sit in the sibling folders "Fx" and "fx", which the comparator leaves in one place …; rename one of the two folders so they differ by more than letter case, spacing or a leading zero` | **R10** — rename until no pair is left. The kind in brackets says which of the three things the five stored round trips leave open decides the pair: `number` (two digit runs that are each one number written twice, pointing opposite ways), `separator` (a whitespace character that is not a space) or `folder` (two sibling folders the comparator cannot separate). rigc keys `animations` in the editor's own comparator, read off `fixtures/editor-order/probe{1..5}.{in,out}.json` ([#728](https://github.com/firejune/rigc/issues/728)) — so a pair those files settle is emitted rather than refused, **including a pair that differs only in case**, whose order is then the one your spec declared. On the three that are left, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
|
|
5022
|
+
| `N pair(s) of skin names have no one order: … "Fx/a" / "fx/b" (folder) — …` | **R11** — rename until no pair is left. The same shape and the same three kinds as the row above, because it is the same comparator: two of the five round trips carried one name list as both collections and both came back in one order ([#728](https://github.com/firejune/rigc/issues/728)). ⚠️ This row was **wider** than R10's until then — `Zulu`/`mike` and `mike10`/`mike2` built as animation names and were refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) — and both build now |
|
|
4842
5023
|
| `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 |
|
|
4843
5024
|
| `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 |
|
|
4844
5025
|
|
|
@@ -4937,7 +5118,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4937
5118
|
| `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)) |
|
|
4938
5119
|
| `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)) |
|
|
4939
5120
|
| `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 |
|
|
4940
|
-
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk, **
|
|
5121
|
+
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk — the message names the ratio it measured on **each** axis and the header that states the same art truthfully, and §0.2 has the whole of it ([#715](https://github.com/firejune/rigc/issues/715)): a runtime maps a region as a fraction of the DECLARED size and never reads the texture's own, so such a page draws, and what it breaks is every reader that addresses the page in texels. A uniform ratio is a `size:`/`scale:` pair away from honest and the message prints it; two ratios are not, because `scale:` carries one number, and the message says so rather than offering a header that would not work. **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. **Or** the page file is not a PNG at all, and then nothing about its size is measured: the message carries the page's path and the size the atlas declares, and names what the file is by its first bytes — one of WebP, JPEG, GIF, KTX, KTX2 by its signature, or no image format rigc recognises, the bytes in hex either way — or calls it **truncated** when it begins as a PNG and runs out before its `IEND` (§0.2, [#732](https://github.com/firejune/rigc/issues/732)). That clause is validity too: nothing in rigc can read such a page back. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4941
5122
|
| `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` |
|
|
4942
5123
|
| `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)) |
|
|
4943
5124
|
| `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 |
|
|
@@ -4950,11 +5131,11 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4950
5131
|
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` label is not on the 4.3 line (`4.3`, `4.3.N`, `4.3.N-suffix`) |
|
|
4951
5132
|
| `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)) |
|
|
4952
5133
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
4953
|
-
| `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)). ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page** ([#705](https://github.com/firejune/rigc/issues/705)): a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. Before #705 the walk was silent about its own reach, so a part nobody could read printed *opaque in every one of its 12x8 texels* over zero of them, which is a refusal pointing at the wrong file: the art it names may be transparent, and the repair is the rectangle in `A06`'s row above **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5134
|
+
| `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)). ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page** ([#705](https://github.com/firejune/rigc/issues/705)): a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. ⚠️ **A page whose IMAGE is not the size the atlas declares for it is the same non-measurement for every region on it** ([#715](https://github.com/firejune/rigc/issues/715)), and #705's clause does not cover that case: a page rescaled after packing leaves most rectangles partly on it, at coordinates that address a different part of the picture, so the scan came back with a confident verdict over texels nobody had located — on a two-region pack at a uniform 0.5 an opaque part's failure **disappeared**, the scan having found a transparent texel 32 texels away from it. The row names the page's two sizes and points at `A06`, which judges the page grid and prints the header that repairs it (§0.2). It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. Before #705 the walk was silent about its own reach, so a part nobody could read printed *opaque in every one of its 12x8 texels* over zero of them, which is a refusal pointing at the wrong file: the art it names may be transparent, and the repair is the rectangle in `A06`'s row above. ⚠️ **A page file that cannot be read as PNG at all is the same non-measurement for every part on it** ([#732](https://github.com/firejune/rigc/issues/732)): one row per page naming its parts and pointing at `A06`, which names what the file is — where it used to print `threw: cannot decode PNG …: unexpected end of file`, an inflate error about a file that was never a PNG. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4954
5135
|
| `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)) |
|
|
4955
5136
|
| `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 |
|
|
4956
5137
|
| `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)) |
|
|
4957
|
-
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component,
|
|
5138
|
+
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, rests at `mix: 0` with **no timeline in any animation keying that `mix` above 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), [#743](https://github.com/firejune/rigc/issues/743)). 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. Two differences between the two arms, and the runtime is the reason for both: 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 — and a **key** of `strength: 0` is accepted, because it releases the constraint for the span with `damping` and `inertia` still applied and the next key pulls the offset back, measured through spine-core at no NaN, a coast to a limit and a return in 54 steps ([#727](https://github.com/firejune/rigc/issues/727)). As a **setup** value `strength: 0` is still refused by the arm above, and `mix: 0` is refused only when nothing keys it above 0. The `mix` branch above is why `mix` is the one setup value a key can answer for: at rest the constraint is **inert** rather than broken, so a rig that rests muted and is keyed above 0 is refused by nothing, while a rig resting at `mass: 0` is `massInverse` Infinity before anything plays and no key reaches back into that. The detail of the refusal says both halves and how many animations were searched: `physics "C" has mix 0 and none of the 3 animations keys its mix above 0; it is muted — rest it above 0, or key its mix above 0 in an animation`. The search counts the unnamed global timeline for every constraint whose own `mixGlobal` is set, reads each key through the runtime's accessor, and takes an animation a slider applies like any other. `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. `ingest` does not carry a constraint that drives no component into the spec it writes: it omits it with its timelines and reports `PHYSICS_DRIVES_NOTHING` ([INGEST §2.0](INGEST.md), [#731](https://github.com/firejune/rigc/issues/731)), so this sentence is met on a file, never on a decompiled rebuild. **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 |
|
|
4958
5139
|
| `A24_AXIS_SPACE_STROKE` | archetype | a bone under the rig's `axisBone` was keyed with a screen-space Y component, or the axis bone itself was keyed. **SKIP** when the rig declares no axis bone, and also when no animation keys that bone or anything under it ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4959
5140
|
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
4960
5141
|
| `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not the rig's slot table — a slot is out of order, is not in the table at all, or is in the table and missing from the skeleton (§3.3). **SKIP** when the rig declares no canonical slot order. ⚠️ A skeleton with **no** slot beside a rig that declares some is **not** a skip, and it is the one rule in this family where an empty loop is not a vacuous pass ([#580](https://github.com/firejune/rigc/issues/580)): the completeness clause reads it as every declared slot lost and names them, which is the maximal case of what [#575](https://github.com/firejune/rigc/issues/575) filed |
|
|
@@ -4974,8 +5155,9 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4974
5155
|
| `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 |
|
|
4975
5156
|
| `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 |
|
|
4976
5157
|
| `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 |
|
|
4977
|
-
| `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 |
|
|
5158
|
+
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | both | a slot's `dark` (§3.3) or an `rgba2` / `rgb2` 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` or `rgb2` 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. An `rgb2` key's light colour is compared over its three channels only: the light alpha is not its to state, and that it is left where it was is measured in the selftest (`S85`). **SKIP** when no slot declares a `dark` and no animation keys an `rgba2` or `rgb2` — there is then no two-colour tint to read back |
|
|
4978
5159
|
| `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` | both | a **linked mesh** (§3.4) — `type: "linkedmesh"`, or a `type: "mesh"` carrying `source` — that also states `uvs`, `triangles`, `vertices`, `hull` or `edges`. The parser returns from the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so those keys are read by **nothing at all** and `setSourceMesh` fills the attachment with the source's arrays instead: the file says one mesh and every runtime draws another, in silence. The detail names the attachment by skin, slot and placeholder, every key it states, the `source` and where the parser looks for it — the two defaults spelled out, because an omitted `skin` is the **default** skin rather than the one the link is written in — and the shape the keys describe beside the shape the attachment loaded. ⚠️ **`width`/`height` are not part of this.** `setSourceMesh` overwrites both with the source's, so they are as dead at runtime — but the parser reads them (`:569-570`), the format carries them on a link and rigc emits them, so refusing them would refuse every link rigc writes (§3.4). `compile.ts` refuses the same shape outright in a rig rigc builds (§5.1); this is that fact held against a skeleton it did not write, and `ingest` reports it as `ATTACHMENT_LINK_GEOMETRY` ([INGEST §2.0](INGEST.md)). **SKIP** when no attachment in the skeleton takes its geometry from another — which is almost every skeleton, so a pass here means a link was read ([#710](https://github.com/firejune/rigc/issues/710)) |
|
|
5160
|
+
| `A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN` | both | an `rgb` or `alpha` timeline (§4.4) the runtime does not hold as the file states it, in one of two shapes that both parse in silence. **A channel keyed twice**: another colour timeline of the same slot in the same animation poses a channel this one poses — `rgba` beside `alpha` is the shape a converter leaves when it writes a separable `rgb` back as `rgba` next to the `alpha` it kept. Every colour timeline poses its channels at every time, the setup value before its first key included, so the one the file states later overwrites the other everywhere; the detail names both timelines, the channel, and which one survives. **A key not posed as written**: the animation is stepped to each key's own time and the posed r g b (for `rgb`, against the hex, to half a quantisation step) or alpha (for `alpha`, against `value`, whose absence the parser reads as 0) is compared — a colour that is not six hex digits loads as NaN, and a key whose time another key repeats is read by nothing. ⚠️ An `rgb` alone written as an `rgba` holding the setup alpha is **not** caught and cannot be from the file: it is a correct `rgba`, and the difference shows only under another track that moves the alpha. The loaded timeline class and the channels a separable timeline leaves alone are measured in the selftest (`S83`–`S85`) rather than here, because against the linked parser neither can come out wrong. The channel table is `SLOT_COLOR_CHANNELS` in `src/timelines.ts`, shared with the compiler's refusal and held to the runtime's own property ids (`S89`). **SKIP** when no animation keys an `rgb` or `alpha` — there is then no separable slot colour to read back |
|
|
4979
5161
|
|
|
4980
5162
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
4981
5163
|
clauses are gated by profile.
|
|
@@ -6722,9 +6904,9 @@ one key, deform blocks counted at each of their three levels;
|
|
|
6722
6904
|
⇒ in rigc: only `animations` is emitted sorted (R10), because it is the one
|
|
6723
6905
|
object measured here whose ORDER is also an index space — every reference into
|
|
6724
6906
|
the re-sorted *other* objects is by name on both sides, so nothing moves when
|
|
6725
|
-
they are re-keyed. rigc emits **that comparator's own order
|
|
6726
|
-
|
|
6727
|
-
|
|
6907
|
+
they are re-keyed. rigc emits **that comparator's own order**, which
|
|
6908
|
+
[#728](https://github.com/firejune/rigc/issues/728) then measured in full off five
|
|
6909
|
+
stored round trips (R10) rather than quantifying over a family. Sorting the
|
|
6728
6910
|
105 collections that way reproduces **105 of 105**, the three codepoint cannot
|
|
6729
6911
|
included, and refuses none of them; the codepoint rule that stood until
|
|
6730
6912
|
[#543](https://github.com/firejune/rigc/issues/543) reproduced 102 and refused
|
|
@@ -6747,10 +6929,13 @@ does — `skins` carries ordinals in the binary half, `skins[readInt()]` for an
|
|
|
6747
6929
|
attachment timeline and `skins[skinIndex]` for a linked mesh — so this is the
|
|
6748
6930
|
`animations` defect (#535) in the collection nobody had checked. ⇒ in rigc: R11.
|
|
6749
6931
|
|
|
6750
|
-
|
|
6751
|
-
zulu` is the order codepoint, case-folding and natural order all
|
|
6752
|
-
|
|
6753
|
-
|
|
6932
|
+
✅ **Which comparator it is was the open half of that, and #728 closed it.**
|
|
6933
|
+
`alpha, mike, zulu` is the order codepoint, case-folding and natural order all
|
|
6934
|
+
produce, so #541's rig refuted nothing and rigc refused any skin-name pair the
|
|
6935
|
+
candidates could disagree about. Two of the five round trips carried one name list
|
|
6936
|
+
as **both** collections and both came back in one order, so `skins` and
|
|
6937
|
+
`animations` share one comparator — measured, not inferred — and the wider skin
|
|
6938
|
+
refusal is gone (R11).
|
|
6754
6939
|
|
|
6755
6940
|
✅ **The two readings this replaces.** A pull request once called `skins` *measured
|
|
6756
6941
|
preserved*, on the strength of a one-skin rig where a one-element array comes back
|