spine-rigc 0.30.0 → 0.32.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 +39 -1
- package/docs/AUTHORING.md +309 -48
- package/docs/INGEST.md +12 -10
- package/docs/SPEC_COVERAGE.md +6 -5
- package/package.json +1 -1
- package/src/atlas.ts +180 -0
- package/src/compile.ts +486 -21
- package/src/deformmeasure.ts +7 -2
- package/src/ingest.ts +254 -29
- package/src/motion.ts +120 -2
- package/src/rig.ts +179 -5
- package/src/timelines.ts +94 -6
- package/src/types.ts +106 -3
- package/src/validate.ts +618 -221
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 32 validity rules (**the default**) · `spine-html` = all 47, 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` |
|
|
@@ -360,6 +360,39 @@ it hits instead of leaving it to be discovered:
|
|
|
360
360
|
says so rather than offering one, and the repair is to re-export the page at the
|
|
361
361
|
size the atlas declares, or repack.
|
|
362
362
|
|
|
363
|
+
📐 **`explain` does not refuse such a page — it withholds what it would have
|
|
364
|
+
measured off it** ([#750](https://github.com/firejune/rigc/issues/750)). It never
|
|
365
|
+
gates, a runtime draws the page, and most of its report — bones, slots, timelines
|
|
366
|
+
— reads no texel at all, so the pack still compiles and the report still prints.
|
|
367
|
+
What it no longer prints is a figure taken at the coordinates the atlas states,
|
|
368
|
+
because on such a file those are another part of the picture: measured on a pack
|
|
369
|
+
at half resolution, an authored mesh that covers 100.00% of its art and reaches
|
|
370
|
+
16.00px past it printed **68.49%** and **76.24px**, and a `grid` whose depth sheet
|
|
371
|
+
reads 10 of its 12 vertices on undrawn texels printed **12 of 12**. The page is
|
|
372
|
+
named once where the report starts, with the ratio `A06` refuses it by, and every
|
|
373
|
+
withheld figure says so where it would have stood:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# .. page "hero.png" declares 4096x4096 and its PNG is 2048x2048 — 0.5000 of the declared width and 0.5000
|
|
377
|
+
# of the declared height: every figure below taken off this page's texels is withheld, and says so where
|
|
378
|
+
# it would have stood. …
|
|
379
|
+
# fan authored 9 vertices / 8 triangles (budget 64) bones=[fan] fit not measured: page "hero.png" …
|
|
380
|
+
# the count of vertices on undrawn texels is not measured: page "hero.png" …
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`build`'s `MESH` lines withhold the same figures in the same words and print no
|
|
384
|
+
page line of their own, because `A06` is `build`'s statement of that fact and a
|
|
385
|
+
second one would be two refusals of one page. A **`contour`** is the one reading
|
|
386
|
+
that cannot be withheld, because its outline *is* its geometry: it is refused as a
|
|
387
|
+
compile error carrying `A06`'s whole sentence — ratio and repair — on `explain`
|
|
388
|
+
and on `build` alike, where on `build` it arrives before the gate would have said
|
|
389
|
+
it. Carry out the repair and every figure comes back. ⚠️ They come back measured
|
|
390
|
+
on the **coarser** texels the page really has, so they need not equal the figures
|
|
391
|
+
of the pack the page was halved from: on the same fixture the `scale: 0.5`
|
|
392
|
+
restatement reads the authored mesh at 100.00% coverage reaching **8.00px** past
|
|
393
|
+
it (the overshoot is counted in the page's own texels), and traces the contour as
|
|
394
|
+
11 vertices where the full-resolution page traced 15.
|
|
395
|
+
|
|
363
396
|
🚨 **A page that is not a PNG is refused by name, before anything is compiled
|
|
364
397
|
against it** ([#732](https://github.com/firejune/rigc/issues/732)). rigc reads PNG
|
|
365
398
|
and nothing else — the size `A06` judges, the alpha `A19` judges, the renderer and
|
|
@@ -594,7 +627,7 @@ the first:
|
|
|
594
627
|
|
|
595
628
|
| gutter | meaning |
|
|
596
629
|
| --- | --- |
|
|
597
|
-
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `point`,
|
|
630
|
+
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `point`, a `sequence` block the parser would read as some other series, 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 |
|
|
598
631
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
599
632
|
| `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) |
|
|
600
633
|
|
|
@@ -660,6 +693,16 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
660
693
|
([#697](https://github.com/firejune/rigc/issues/697), §5.1). ⚠️ `--profile`,
|
|
661
694
|
`--pack`, `--page-size`, `--padding` and `--copy-images` are `build`'s and are
|
|
662
695
|
not here: four of them decide what is *written*, and this command writes nothing.
|
|
696
|
+
|
|
697
|
+
📐 **What it will not measure: the texels of a page that is not its declared
|
|
698
|
+
size** ([#750](https://github.com/firejune/rigc/issues/750)). Under
|
|
699
|
+
`--atlas-in`, a page whose PNG is not the `size:` its atlas states is named once
|
|
700
|
+
at the top of the report, and the figures that would have been read off it — an
|
|
701
|
+
authored mesh's `covers …% of the art, reaching …px past it`, a depth sheet's
|
|
702
|
+
count of vertices on undrawn texels — are replaced by `fit not measured: …` and
|
|
703
|
+
`… is not measured: …` naming the page and the ratio. A `contour` on such a page
|
|
704
|
+
is refused, since its outline is read off those texels. Why, the quoted lines,
|
|
705
|
+
and the repair: §0.2.
|
|
663
706
|
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
664
707
|
sections (bones, slots, attachments, constraints, animations, events). It
|
|
665
708
|
deliberately does not combine them into a score: a rig with the right skeleton
|
|
@@ -1071,7 +1114,7 @@ it would simply be a second root.
|
|
|
1071
1114
|
| `rotation` | degrees, counter-clockwise, y **up** | `0` |
|
|
1072
1115
|
| `scaleX`, `scaleY` | | `1` |
|
|
1073
1116
|
| `shearX`, `shearY` | | `0` |
|
|
1074
|
-
| `inherit` | `normal` · `onlyTranslation` · `noRotationOrReflection` · `noScale` · `noScaleOrReflection` | `normal` |
|
|
1117
|
+
| `inherit` | `normal` · `onlyTranslation` · `noRotationOrReflection` · `noScale` · `noScaleOrReflection`. The first letter's case is free (`NoScale` loads) and the rest is exact: the runtime folds that one letter and nothing else, so `NOSCALE` is refused — it used to compile and load as **no mode at all** ([#733](https://github.com/firejune/rigc/issues/733)). An animation can change it over time: the `inherit` track (§4.4) | `normal` |
|
|
1075
1118
|
| `skin` | `BoneData.skinRequired` | `false` |
|
|
1076
1119
|
| `color` | `rrggbbaa`, editor affordance | — |
|
|
1077
1120
|
| `icon` | the editor's icon for this bone, e.g. `arrowsB`; editor affordance, no rendering effect. Copied through verbatim — no assertion checks the name, because the icon vocabulary is the editor's and an unknown one is not an error | — |
|
|
@@ -1180,13 +1223,15 @@ the default `type`:
|
|
|
1180
1223
|
| `x`, `y` | offset from the bone, in the bone's local space |
|
|
1181
1224
|
| `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
|
|
1182
1225
|
| `scaleX`, `scaleY`, `color` | as Spine |
|
|
1226
|
+
| `sequence` | a **numbered image series** instead of one region: `{ "count", "start"?, "digits"?, "setup"? }`, and `path` (or the placeholder) is the series' stem. No `image` beside it — the frames are the images. §3.4.3 |
|
|
1183
1227
|
|
|
1184
1228
|
**Mesh attachment** ([Spine: meshes](http://esotericsoftware.com/spine-meshes)) —
|
|
1185
1229
|
either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
|
|
1186
1230
|
never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
|
|
1187
1231
|
omitted, rigc derives — `hull` and `edges` from the triangles, the size from the
|
|
1188
|
-
PNG — and the rules are a few paragraphs down. `type`, `image`, `path
|
|
1189
|
-
mean exactly what they mean on a region
|
|
1232
|
+
PNG — and the rules are a few paragraphs down. `type`, `image`, `path`, `color` and
|
|
1233
|
+
`sequence` mean exactly what they mean on a region (a sequence mesh takes authored
|
|
1234
|
+
geometry, never a `generator` — §3.4.3).
|
|
1190
1235
|
|
|
1191
1236
|
🔑 **`path` is one rule for both kinds.** A mesh derives it from `image` the way a
|
|
1192
1237
|
region does: stated wins, otherwise the PNG's basename when that differs from the
|
|
@@ -2288,6 +2333,69 @@ nothing at all. `check` then compares blank against blank and reports a perfect
|
|
|
2288
2333
|
`--skin <name>` to both, once per skin (**§9**); `tools/editor_roundtrip.ts`
|
|
2289
2334
|
loops over every skin the build declares for the same reason.
|
|
2290
2335
|
|
|
2336
|
+
#### 3.4.3 `sequence` — a numbered image series on one attachment
|
|
2337
|
+
|
|
2338
|
+
A region, a mesh or a linked mesh can draw one of a **series** of atlas regions
|
|
2339
|
+
instead of one: a flame, a blink, a spinning coin. The attachment says how many
|
|
2340
|
+
frames there are and how they are named, and a `sequence` timeline (§4.13) says
|
|
2341
|
+
which one shows. The format's own block, spelled as the file spells it
|
|
2342
|
+
(`readSequence`, `SkeletonJson.js:641-649`):
|
|
2343
|
+
|
|
2344
|
+
| Field | Meaning |
|
|
2345
|
+
| --- | --- |
|
|
2346
|
+
| `count` | how many frames. **Required** — the parser's default is 0, and a series of no frames loads holding no region and draws nothing, without an error |
|
|
2347
|
+
| `start` | the number the first frame's name carries. Parser default 1 |
|
|
2348
|
+
| `digits` | zero-pad the frame number to at least this many digits. Parser default 0 (no padding) |
|
|
2349
|
+
| `setup` | the frame the setup pose shows, 0-based. Parser default 0. ⚠️ Spelled `setup`, as the file spells it — `setupIndex` is the runtime's field name and is refused as a key this compiler does not read |
|
|
2350
|
+
|
|
2351
|
+
```json
|
|
2352
|
+
"glint": { "glint": { "path": "glint_", "sequence": { "count": 4, "start": 1, "digits": 4 } } }
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
Frame `i` is the atlas region **`<stem><start + i>`**, the number left-padded with
|
|
2356
|
+
zeros to `digits` (`Sequence.getPath`) — so the four frames above are `glint_0001`
|
|
2357
|
+
to `glint_0004`. The stem is `path`, or the placeholder when no `path` is stated
|
|
2358
|
+
(`path` defaults to the attachment's name, exactly as for one region). Each frame is
|
|
2359
|
+
resolved **by name**: on the loose route it is the PNG `<images>/<frame>.png`, and
|
|
2360
|
+
under `--atlas-in` it is the pack's region of that name. A region name may carry a
|
|
2361
|
+
folder — an editor's `fx/flame_0001` is `images/fx/flame_0001.png` — and it is the
|
|
2362
|
+
full name that is looked up.
|
|
2363
|
+
|
|
2364
|
+
🚫 **A missing frame is refused by name, with its number and the name looked for** —
|
|
2365
|
+
the compiler never draws one frame in place of another:
|
|
2366
|
+
|
|
2367
|
+
```
|
|
2368
|
+
skin "default" slot "glint" attachment "glint": sequence frame 4 of 5 (number 5) is the region "glint_0005", and there is no PNG for it at …/glint_0005.png
|
|
2369
|
+
```
|
|
2370
|
+
|
|
2371
|
+
The loader's own miss would be `Region not found in atlas: glint_0005 (attachment:
|
|
2372
|
+
glint)`, which says neither that the region was a frame nor of which series.
|
|
2373
|
+
|
|
2374
|
+
⚠️ **The rest of what is refused**, each a series the parser would load as something
|
|
2375
|
+
other than what was written (measured on spine-core 4.3.13,
|
|
2376
|
+
[#729](https://github.com/firejune/rigc/issues/729)):
|
|
2377
|
+
|
|
2378
|
+
- a `setup` at or past `count` — `Sequence.resolveIndex` clamps it to the last frame
|
|
2379
|
+
(`setup: 7` on four frames showed frame 4);
|
|
2380
|
+
- a fractional `count`, `start`, `digits` or `setup` — `start: 1.5` would ask the
|
|
2381
|
+
atlas for `glint_1.5`;
|
|
2382
|
+
- an `image` beside `sequence` — one file names one region, and the series names
|
|
2383
|
+
`count` of them;
|
|
2384
|
+
- a `generator` beside it on a mesh — a generator traces one plate, and which frame
|
|
2385
|
+
it should trace is not something the spec says. Author the geometry; every frame
|
|
2386
|
+
shares it;
|
|
2387
|
+
- a `sequence` on a `boundingbox`, `clipping` or `path` — the parser reads the key
|
|
2388
|
+
only on the three kinds that draw a region, so it would be dropped in silence. The
|
|
2389
|
+
refusal names region, mesh and linkedmesh.
|
|
2390
|
+
|
|
2391
|
+
`width` and `height` are the attachment's one size, every frame drawn into it. Omit
|
|
2392
|
+
them and rigc takes the frames' size — **only when every frame measures the same**;
|
|
2393
|
+
frames of different sizes are refused until you state the size, because picking one
|
|
2394
|
+
of them would be the compiler choosing a value. Under `--atlas-in` a stated size that
|
|
2395
|
+
disagrees with a packed frame is refused, as it is for one region.
|
|
2396
|
+
`A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` (§5.2) holds the block and
|
|
2397
|
+
every frame the timelines show against the file.
|
|
2398
|
+
|
|
2291
2399
|
### 3.5 `constraints` — 4.3's single typed array
|
|
2292
2400
|
|
|
2293
2401
|
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
@@ -2338,14 +2446,20 @@ carrying here:
|
|
|
2338
2446
|
`true` and leaves the rest alone (`Animation.js:2066-2072`). So the flag is one
|
|
2339
2447
|
constraint's **opt-in to being driven in bulk**, per tuning value, and it does
|
|
2340
2448
|
nothing on its own. The parser's default for all seven is `false`.
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
through**, `false` included: measured, a
|
|
2347
|
-
|
|
2348
|
-
dropping it the way the motion spec's
|
|
2449
|
+
The motion spec keys that timeline as **`"physics": "*"`** (§4.4,
|
|
2450
|
+
[#726](https://github.com/firejune/rigc/issues/726)), and rigc writes it under
|
|
2451
|
+
the empty name. Until then rigc emitted the flags and could not emit the
|
|
2452
|
+
timeline that reads them: every physics track named its constraint and the
|
|
2453
|
+
empty name was refused as `keys unknown physics constraint ""`. What rigc does
|
|
2454
|
+
with the flags is **pass them through**, `false` included: measured, a
|
|
2455
|
+
constraint stating none emits none, and one stating `"windGlobal": false` emits
|
|
2456
|
+
`"windGlobal": false` rather than dropping it the way the motion spec's
|
|
2457
|
+
`physics` table drops a default (§4.6). ⚠️ That table has **no** `…Global`
|
|
2458
|
+
field, so a constraint a `"*"` track is meant to reach is declared here, in the
|
|
2459
|
+
rig spec's `constraints`.
|
|
2460
|
+
- `"*"` is **reserved** as a physics constraint's name, in the rig spec and in
|
|
2461
|
+
§4.6's table alike: a track naming it could mean either. `compile` refuses one
|
|
2462
|
+
by name.
|
|
2349
2463
|
- ⚠️ `src/rig.ts` called the physics `ScaleYMode` key **`scaleYMode`** until
|
|
2350
2464
|
issue #545 — the runtime's field name rather than the format's key — and nothing
|
|
2351
2465
|
read it, so a spec that wrote `scaleYMode` set no mode and said nothing. A rig
|
|
@@ -2601,8 +2715,8 @@ driven constraint and its kind, the property, both `constraints` indices, the
|
|
|
2601
2715
|
runtime class whose `update` does the reading and the animation the key sits in,
|
|
2602
2716
|
and its repair is the reorder. It is a rule of its own rather than a clause of
|
|
2603
2717
|
`A40` because `A40`'s population is the sliders whose `mix` nothing keys — the
|
|
2604
|
-
exclusion *is* the shape of the hole — and not a clause of `A37`,
|
|
2605
|
-
|
|
2718
|
+
exclusion *is* the shape of the hole — and not a clause of `A37`, which asks
|
|
2719
|
+
whether some animation keys the `mix` above 0 and never which one.
|
|
2606
2720
|
⭐ **The runtime repairs this for bones and not for constraints**, which is why an
|
|
2607
2721
|
author cannot reason it out from the bone case: `Slider.sort` clears `sorted` on
|
|
2608
2722
|
every bone its animation keys so those bones re-sort *after* the slider, while
|
|
@@ -3057,6 +3171,7 @@ time puts it here.
|
|
|
3057
3171
|
| `ik` | IK constraint timelines — §4.9. Not a track: its keys carry named fields, not one `v` |
|
|
3058
3172
|
| `transform` | transform constraint timelines — §4.10. Same reason |
|
|
3059
3173
|
| `deform` | deform timelines — §4.11. Same reason |
|
|
3174
|
+
| `sequence` | which frame of an attachment's numbered series shows — §4.13. Same reason |
|
|
3060
3175
|
|
|
3061
3176
|
`groups` (`name → [member, …]`) lets one track target several bones, slots or
|
|
3062
3177
|
physics constraints at once; `lag` shifts every key of a track, and `stagger`
|
|
@@ -3067,7 +3182,7 @@ group that names a member twice is a compile error, and so is one that names non
|
|
|
3067
3182
|
⭐ **Which of the three a group's members are is decided by the `property`, not
|
|
3068
3183
|
by the group.** A group declares names and nothing else; the compiler reads the
|
|
3069
3184
|
property first — a physics timeline makes the members physics constraints, one of
|
|
3070
|
-
a bone's
|
|
3185
|
+
a bone's eleven makes them bones, and `attachment`/`rgba` makes them slots — and then
|
|
3071
3186
|
resolves every member against the rig as that. So a `group` is the one target
|
|
3072
3187
|
where the property picks the family rather than the other way round (§4.4's ⭐ is
|
|
3073
3188
|
about the three **constraint** families, which are picked by the field), and a
|
|
@@ -3109,6 +3224,7 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
3109
3224
|
| --- | --- | --- |
|
|
3110
3225
|
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
3111
3226
|
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
3227
|
+
| `bone` | `inherit` | a mode name, not an array: `normal` · `onlyTranslation` · `noRotationOrReflection` · `noScale` · `noScaleOrReflection` — the five a bone's setup `inherit` takes (§3.2), read by the same rule. **Stepped by the format**: the mode changes AT the key and holds until the next one, and before the first key the bone is in its setup mode. So a key carries no `ease` and no `curve`. What it changes is how much of the parent's world transform the bone takes: `normal` all of it, `onlyTranslation` the position alone, `noRotationOrReflection` everything but the rotation, `noScale` / `noScaleOrReflection` everything but the scale |
|
|
3112
3228
|
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
3113
3229
|
| `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
3230
|
| `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 |
|
|
@@ -3118,6 +3234,7 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
3118
3234
|
| `physics` | `inertia`, `strength`, `damping`, `mass`, `wind`, `gravity` | `[value]` — the constraint's own tuning, keyed over time |
|
|
3119
3235
|
| `physics` | `mix` | `[mix]`, **0 or more** — the constraint's authority |
|
|
3120
3236
|
| `physics` | `reset` | `null` — the key *is* the event |
|
|
3237
|
+
| `physics: "*"` | any of the eight above | as above — the timeline that names **no** constraint. The value timelines write every physics constraint whose rig-spec entry declares that property global (`"strengthGlobal": true` for `strength`; §3.5) and leave every other one alone; `reset` resets every physics constraint and asks no flag |
|
|
3121
3238
|
| `path` | `position`, `spacing` | `[value]` — see §4.12 |
|
|
3122
3239
|
| `path` | `mix` | `[mixRotate, mixX, mixY]` — one timeline, three channels |
|
|
3123
3240
|
| `slider` | `time` | `[seconds]` — where in its animation the slider sits |
|
|
@@ -3126,10 +3243,10 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
3126
3243
|
Translate values are **relative to the bone's setup position**; scale values are
|
|
3127
3244
|
multipliers where `1` is setup; rotation is in degrees.
|
|
3128
3245
|
|
|
3129
|
-
⚠️ **A `bone` track's `property` is one of the
|
|
3246
|
+
⚠️ **A `bone` track's `property` is one of the eleven above, and anything else is a
|
|
3130
3247
|
compile error** — `animation "A" bone "B" has no timeline "P" (it has: translate,
|
|
3131
|
-
translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate
|
|
3132
|
-
§5.1's row. The
|
|
3248
|
+
translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate,
|
|
3249
|
+
inherit)`, §5.1's row. The eleven are the three rows above read as one list, in the order the
|
|
3133
3250
|
message prints them, and they are the emitter's own dispatch table (`BONE_TRACKS`
|
|
3134
3251
|
in `src/compile.ts`): `resolveTargets` asks that table which family a track
|
|
3135
3252
|
belongs to, `compileValueTrack` writes a key out of the shape it finds there, and
|
|
@@ -3149,13 +3266,15 @@ accepts is what it accepts.
|
|
|
3149
3266
|
**constraint** property written on a bone track (`mix`, `inertia`, `position`,
|
|
3150
3267
|
…) never reaches this refusal at all — it is refused first, by the row that
|
|
3151
3268
|
names the field its constraint's name belongs in (§4.12).
|
|
3152
|
-
- **Nothing derives this page's copy of the
|
|
3269
|
+
- **Nothing derives this page's copy of the eleven from the table.** What keeps the
|
|
3153
3270
|
two in step is the control that quotes the message — `RF26` in `selftest.ts` —
|
|
3154
3271
|
which goes red if the list ever widens without this page moving with it, and
|
|
3155
3272
|
`RF27` holds the slot clause the same way. The selftest's spelling census
|
|
3156
|
-
(`PS144`) reads the
|
|
3157
|
-
it actually poses, both ways, which is what makes the list checkable at
|
|
3158
|
-
was stated there too until the refusal had something to state.
|
|
3273
|
+
(`PS144`) reads the eleven off the same message and compares them against the
|
|
3274
|
+
eleven it actually poses, both ways, which is what makes the list checkable at
|
|
3275
|
+
all: it was stated there too until the refusal had something to state. It was
|
|
3276
|
+
ten until [#733](https://github.com/firejune/rigc/issues/733) added `inherit`,
|
|
3277
|
+
and that landing is what moved `RF26`, this page and the census together.
|
|
3159
3278
|
|
|
3160
3279
|
⚠️ **A `slot` track's `property` is one of the six above, and anything else is a
|
|
3161
3280
|
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
@@ -3228,8 +3347,8 @@ needs 4 channels, got 1`, a message about a key you had not written.
|
|
|
3228
3347
|
here, and would be refused by the runtime's own reader too (`Invalid timeline
|
|
3229
3348
|
type for a slot`). `A12_NO_DARK_COLOR` refuses `rgb2` — and `rgba2`, and the
|
|
3230
3349
|
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
|
|
3232
|
-
|
|
3350
|
+
`sequence` is a timeline on an **attachment**, not on a slot — it is the
|
|
3351
|
+
family beside `deform`, §4.13.
|
|
3233
3352
|
|
|
3234
3353
|
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
3235
3354
|
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
@@ -3283,6 +3402,17 @@ a delta from the constraint's own setting.
|
|
|
3283
3402
|
a file rigc did not write, naming the animation, the constraint, the key time
|
|
3284
3403
|
and the value — so the compiler is where a spec you wrote is refused, and the
|
|
3285
3404
|
assertion is where an import is.
|
|
3405
|
+
- ⚠️ **`damping`'s bound depends on the constraint's `fps`, and a rig played at
|
|
3406
|
+
60 fps hides it** ([#748](https://github.com/firejune/rigc/issues/748)). The
|
|
3407
|
+
decay is `damping ** (60 * step)` with `step` = `1 / fps`, so the exponent is
|
|
3408
|
+
exactly 1 at 60 fps, where a negative damping only flips the velocity's sign
|
|
3409
|
+
each step and can look like a jiggle settling. Wherever `60 / fps` is not a
|
|
3410
|
+
whole number the same negative is raised to a fractional power, which is NaN.
|
|
3411
|
+
[measured] one planted `damping` key of −0.5 on the generated physics fixture
|
|
3412
|
+
stays finite at 60 fps and at 30 (exponent 2), and is NaN within three steps of
|
|
3413
|
+
the key at 45 and at 120. `1` never decays and above `1` diverges at every
|
|
3414
|
+
rate. The refusal names the exponent and the rate for this reason, and `T101`
|
|
3415
|
+
holds it to the two-rate measurement.
|
|
3286
3416
|
- 🚫 **`inertia`, `wind` and `gravity` are bounded nowhere, and neither is the top
|
|
3287
3417
|
of `mix`.** The runtime documents no range for the first three, and
|
|
3288
3418
|
`PhysicsConstraintPose` documents `mix` as "a percentage (0+)" — so a negative
|
|
@@ -3299,7 +3429,15 @@ a delta from the constraint's own setting.
|
|
|
3299
3429
|
back ([#727](https://github.com/firejune/rigc/issues/727)). At rest the two
|
|
3300
3430
|
part ways: a `strength` of 0 is a constraint nothing pulls back, which `A23`
|
|
3301
3431
|
refuses, and a `mix` of 0 is a constraint muted until an animation keys it
|
|
3302
|
-
above 0 — the next bullet.
|
|
3432
|
+
above 0 — the next bullet. A **negative** setup `strength` is refused too, with
|
|
3433
|
+
a different sentence, because it is a different rig: the restoring term is
|
|
3434
|
+
added to the offset instead of taken out, so the offset is **pushed away** and
|
|
3435
|
+
grows — [measured] resting at −100 on the generated physics fixture it grew
|
|
3436
|
+
28.35× over 0.5 s with no sign change, where resting at 100 it swung back
|
|
3437
|
+
through 0. `A23` reads which sentence to print off the `strength` row of
|
|
3438
|
+
`PHYSICS_POSE_RULES` (its `outside` arms), and the key's refusal quotes the
|
|
3439
|
+
negative arm from the same row, so the two cannot say different things about
|
|
3440
|
+
one number ([#748](https://github.com/firejune/rigc/issues/748)).
|
|
3303
3441
|
- ⚠️ **A setup `mix` of `0` is legal when some animation keys it above 0** ([#743](https://github.com/firejune/rigc/issues/743)).
|
|
3304
3442
|
A constraint muted *at rest* is a rig whose physics is off until an animation
|
|
3305
3443
|
switches it on, which is a design rather than the silence `A23` was built for.
|
|
@@ -3309,6 +3447,11 @@ a delta from the constraint's own setting.
|
|
|
3309
3447
|
at 1 does, and keyed-to-0 poses it exactly where one with no timeline at all
|
|
3310
3448
|
does. The unnamed global timeline counts as a key for every constraint whose
|
|
3311
3449
|
own `mixGlobal` is set (§3.5), and so does an animation only a slider applies.
|
|
3450
|
+
"Keyed above 0" means **any value the timeline poses**, which includes a
|
|
3451
|
+
Bezier between two keys of 0 whose handles lie above 0: the runtime
|
|
3452
|
+
interpolates through the curve's samples, not between the keys, and such a
|
|
3453
|
+
pair moves the bone ([#752](https://github.com/firejune/rigc/issues/752)). The
|
|
3454
|
+
same reading is `A36`'s and `A37`'s (§4.12).
|
|
3312
3455
|
- 📏 **What a `strength` key of `0` costs, measured through spine-core** on the
|
|
3313
3456
|
generated overlay fixture, stepping at 60 fps from `Physics.reset` (#727). With
|
|
3314
3457
|
no wind or gravity the offset coasts to a limit rather than running away — a
|
|
@@ -3322,6 +3465,28 @@ a delta from the constraint's own setting.
|
|
|
3322
3465
|
the first sub-step and **still NaN after the restoring key**, and a keyed
|
|
3323
3466
|
`damping` of `2` was still 6.9e4 two seconds later.
|
|
3324
3467
|
|
|
3468
|
+
**`"physics": "*"` is the physics timeline that names no constraint**
|
|
3469
|
+
([#726](https://github.com/firejune/rigc/issues/726)). The skeleton file writes it
|
|
3470
|
+
under the empty name — `animations.<a>.physics[""]`, which `SkeletonJson` loads as
|
|
3471
|
+
constraint index `-1` without looking anything up — and the runtime then applies it
|
|
3472
|
+
to every active physics constraint whose own data declares the keyed property
|
|
3473
|
+
global (`Animation.js:2067-2075`). So what it drives is decided in the **rig spec**,
|
|
3474
|
+
by the seven `…Global` flags of §3.5, one per value timeline; `reset` is the
|
|
3475
|
+
exception and resets every physics constraint. [measured] over three constraints of
|
|
3476
|
+
which two declare `strengthGlobal`, a `"*"` `strength` track keyed to 35 poses those
|
|
3477
|
+
two at 35 and leaves the third at its setup 100.
|
|
3478
|
+
|
|
3479
|
+
- ⚠️ **A `"*"` track that reaches no constraint is a compile error**, naming the flag
|
|
3480
|
+
and the constraints that do not declare it: the runtime would walk every
|
|
3481
|
+
constraint, skip each, and the file would parse and do nothing. Set the flag on
|
|
3482
|
+
the constraints the track is for, or key one by name.
|
|
3483
|
+
- 🚫 **`"physics": ""` is refused**, and deliberately: the empty string is the
|
|
3484
|
+
likeliest shape of a target somebody forgot to fill in, and a forgotten target
|
|
3485
|
+
that quietly meant "every global constraint" is the silence this format exists to
|
|
3486
|
+
name. The refusal points at `"*"`.
|
|
3487
|
+
- ⚠️ `"*"` is a track's `physics` field and nothing else — a `group` that lists it
|
|
3488
|
+
is refused, and so is a constraint called `"*"` (§3.5).
|
|
3489
|
+
|
|
3325
3490
|
On a track that names a `group`, one key's `v` may instead be a **map keyed by
|
|
3326
3491
|
member name**, whose entries are each exactly the `v` above — or a `derive`
|
|
3327
3492
|
model the compiler evaluates per member. §4.5.1.
|
|
@@ -3641,10 +3806,14 @@ mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
|
3641
3806
|
be **keyed over time** as `tracks` entries naming this constraint (§4.4); this
|
|
3642
3807
|
table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and `damping ≥ 1`
|
|
3643
3808
|
never settles — both are `A23`, here and on every timeline key that states them
|
|
3644
|
-
([#610](https://github.com/firejune/rigc/issues/610))
|
|
3809
|
+
([#610](https://github.com/firejune/rigc/issues/610)); a `damping` at or below 0
|
|
3810
|
+
is refused as well, and below 0 whether it is NaN depends on `fps` — §4.4
|
|
3811
|
+
([#748](https://github.com/firejune/rigc/issues/748)). ⚠️ `strength: 0` is `A23` **here and not on a key**:
|
|
3645
3812
|
at rest it is a constraint nothing pulls back, and on a key it is a release somebody
|
|
3646
3813
|
asked for, which §4.4 states with the measurement behind it
|
|
3647
|
-
([#727](https://github.com/firejune/rigc/issues/727)).
|
|
3814
|
+
([#727](https://github.com/firejune/rigc/issues/727)). A negative `strength` is
|
|
3815
|
+
`A23` here and on a key, and here its sentence is its own: the offset is pushed
|
|
3816
|
+
away and grows ([#748](https://github.com/firejune/rigc/issues/748)). `mix: 0` is the one value
|
|
3648
3817
|
here an animation can answer for: it rests the constraint **muted**, which is
|
|
3649
3818
|
legal, and `A23` names it only when no timeline in any animation keys that `mix`
|
|
3650
3819
|
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
|
|
@@ -4758,12 +4927,80 @@ so `v` is three numbers and not one.
|
|
|
4758
4927
|
|
|
4759
4928
|
⚠️ **A muted constraint with no timeline is a finding, not an idiom.** Turning a
|
|
4760
4929
|
constraint on from an animation is the idiom (§4.10), so `A36`/`A37` only object to
|
|
4761
|
-
all-zero mixes when **no** animation keys that constraint's `mix
|
|
4762
|
-
at setup, key it somewhere. `A23` asks the same question of a
|
|
4763
|
-
([#743](https://github.com/firejune/rigc/issues/743)) and
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4930
|
+
all-zero mixes when **no** animation keys that constraint's `mix` **above 0**. If
|
|
4931
|
+
you mute one at setup, key it up somewhere. `A23` asks the same question of a
|
|
4932
|
+
physics constraint ([#743](https://github.com/firejune/rigc/issues/743)), and the
|
|
4933
|
+
three share one reading ([#752](https://github.com/firejune/rigc/issues/752)): the
|
|
4934
|
+
key **values** the loaded timeline poses, so a `mix` timeline keying 0 only is not a
|
|
4935
|
+
rescue, and a Bezier between two keys of 0 whose handles lie above 0 is. A path
|
|
4936
|
+
constraint is switched on by a key posing **any one** of its three mixes above 0,
|
|
4937
|
+
because `PathConstraint.update` returns only when all three are 0. [measured] on
|
|
4938
|
+
generated fixtures, a path constraint and a slider muted at rest and keyed to 0
|
|
4939
|
+
only pose every bone exactly where the same rig with no mix timeline does, and until
|
|
4940
|
+
#752 both passed. `A23` alone also counts the unnamed global timeline for every
|
|
4941
|
+
constraint declaring `mixGlobal`, since only the physics family has one. The refusal
|
|
4942
|
+
says both halves — `path constraint "P" has mixRotate 0, mixX 0 and mixY 0 at setup
|
|
4943
|
+
and none of the 2 animations keys its mix above 0; …` — and names both repairs.
|
|
4944
|
+
|
|
4945
|
+
### 4.13 `sequence` — which frame of a numbered series shows
|
|
4946
|
+
|
|
4947
|
+
The other attachment timeline, beside `deform` and for the same reason: its key is
|
|
4948
|
+
three named fields rather than one `v`, and it is aimed at an attachment rather than
|
|
4949
|
+
a slot, so it is a family of its own
|
|
4950
|
+
(`animations.<a>.attachments.<skin>.<slot>.<attachment>.sequence` in the file):
|
|
4951
|
+
|
|
4952
|
+
```json
|
|
4953
|
+
"sequence": [
|
|
4954
|
+
{ "slot": "glint", "attachment": "glint", "keys": [
|
|
4955
|
+
{ "t": 0, "mode": "loop", "delay": 0.1 },
|
|
4956
|
+
{ "t": 0.5, "mode": "pingpong", "index": 1, "delay": 0.1 } ] }
|
|
4957
|
+
]
|
|
4958
|
+
```
|
|
4959
|
+
|
|
4960
|
+
| Field | Meaning |
|
|
4961
|
+
| --- | --- |
|
|
4962
|
+
| `skin` | the skin the attachment lives in; absent means `"default"` |
|
|
4963
|
+
| `slot`, `attachment` | the slot and the attachment's placeholder — the attachment must carry a `sequence` block (§3.4.3) |
|
|
4964
|
+
| `keys[].t` | seconds |
|
|
4965
|
+
| `keys[].mode` | one of `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse`. Parser default `hold` — **per key**, not carried |
|
|
4966
|
+
| `keys[].index` | the frame the key starts on, 0-based. Parser default 0 |
|
|
4967
|
+
| `keys[].delay` | seconds per frame. Parser default: **the previous key's** `delay`, 0 on the first |
|
|
4968
|
+
|
|
4969
|
+
From the key's time on, the frame shown is `index` advanced by one every `delay`
|
|
4970
|
+
seconds and folded back into the series by `mode` — `SequenceTimeline.applyToSlot`,
|
|
4971
|
+
with `i = index + floor((time - keyTime) / delay + 0.00001)` and `n = 2 * count - 2`:
|
|
4972
|
+
|
|
4973
|
+
| `mode` | frame shown |
|
|
4974
|
+
| --- | --- |
|
|
4975
|
+
| `hold` | `index`, never advancing |
|
|
4976
|
+
| `once` | `min(count - 1, i)` — stops on the last frame |
|
|
4977
|
+
| `loop` | `i % count` |
|
|
4978
|
+
| `pingpong` | `i % n`, then `n - that` once it reaches `count` — bounces off both ends |
|
|
4979
|
+
| `onceReverse` | `max(count - 1 - i, 0)` |
|
|
4980
|
+
| `loopReverse` | `count - 1 - (i % count)` |
|
|
4981
|
+
| `pingpongReverse` | `(i + count - 1) % n`, then folded as `pingpong` |
|
|
4982
|
+
|
|
4983
|
+
Measured on four frames at `delay: 0.1` from t = 0 (frames numbered 0–3):
|
|
4984
|
+
`loop` shows 0, 1, 2, 3, 0, 1 at t = 0.05, 0.15 … 0.55; `pingpong` 0, 1, 2, 3, 2, 1, 0;
|
|
4985
|
+
`onceReverse` 3, 2, 1, 0, 0. Before the first key the frame is the block's `setup`.
|
|
4986
|
+
|
|
4987
|
+
⚠️ **A sequence timeline does not lengthen an animation.** A loop keyed once at t=0
|
|
4988
|
+
in an animation nothing else extends has a duration of 0, and a player that does
|
|
4989
|
+
not loop the animation shows frame `index` forever (measured). The animation's
|
|
4990
|
+
`duration` is its last key, as everywhere (R7), so key something to the length you
|
|
4991
|
+
mean.
|
|
4992
|
+
|
|
4993
|
+
🚫 Refused by name, each measured to load without a word: a `mode` outside the seven
|
|
4994
|
+
(it loads as `hold`); an `index` that is fractional, negative, or at or past the
|
|
4995
|
+
series' `count` (truncated, clamped); an advancing mode at an **effective** delay of
|
|
4996
|
+
0 — stated, or carried from the key before — where `(time - keyTime) / 0` is
|
|
4997
|
+
Infinity, `Infinity | 0` is 0, and the key shows its first frame throughout; a track
|
|
4998
|
+
on an attachment with no `sequence` block (the parser gives every region a series of
|
|
4999
|
+
one, so every mode shows it); and a track on a **linked mesh that plays its source's
|
|
5000
|
+
timelines** (`timelines` absent or true), whose `timelineAttachment` is the source,
|
|
5001
|
+
so a key aimed at the link is applied to nothing — key the source instead, and the
|
|
5002
|
+
link steps its own series by it, or set `"timelines": false`. Only the fields the
|
|
5003
|
+
key states are emitted: the compiler writes no `mode`, `index` or `delay` for you.
|
|
4767
5004
|
|
|
4768
5005
|
---
|
|
4769
5006
|
|
|
@@ -4899,6 +5136,11 @@ same hole issue #307 closed for the motion spec.)
|
|
|
4899
5136
|
| `…ik[i]`, `…transform[i]`, `…deform[i]` | an object | `null` in one of these lists crashed with a raw `TypeError` on `track.constraint` / `track.skin` |
|
|
4900
5137
|
| `…ik[i].constraint`, `…transform[i].constraint` | a non-empty string | 4.3 writes the group as `ik.<constraint>`, so the name is the only target there is |
|
|
4901
5138
|
| `…deform[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | — |
|
|
5139
|
+
| `…sequence` | an array | §4.13 — one entry per skin/slot/attachment triple |
|
|
5140
|
+
| `…sequence[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | — |
|
|
5141
|
+
| `…sequence[i].keys[j].mode` | one of the seven — `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse` | `SequenceMode[mode]` is `undefined` for anything else and the mode bits store 0: the key loads without a word and plays as `hold` |
|
|
5142
|
+
| `…sequence[i].keys[j].index` | a whole number ≥ 0 | stored as `index << 4`, which truncates a fraction (1.5 showed frame 1). Whether it is inside the series' `count` is the compile-time row below |
|
|
5143
|
+
| `…sequence[i].keys[j].delay` | a finite number ≥ 0 — and **above 0 wherever the mode advances**, counting a delay carried from the key before | `(time - keyTime) / 0` is Infinity and `Infinity \| 0` is 0, so a `loop` at delay 0 shows its first frame throughout. The message reads `` `…keys[j]` plays "loop" at a delay of 0 … which is "hold" spelt as "loop" `` |
|
|
4902
5144
|
|
|
4903
5145
|
The second wave is everything that needed the **other** file, the property table
|
|
4904
5146
|
or the key's position in its own track. These are the frequent ones, verbatim:
|
|
@@ -4928,6 +5170,14 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4928
5170
|
| `"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 |
|
|
4929
5171
|
| `"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 |
|
|
4930
5172
|
| `"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 |
|
|
5173
|
+
| `… is a boundingbox and states a "sequence". A sequence is a numbered series of atlas regions, and only the 3 kinds that draw a region carry one — region, mesh, linkedmesh …` | §3.4.3 — put the series on a region or a mesh, or remove it |
|
|
5174
|
+
| `… "sequence" states no "count" …` / `… "sequence".setup is N, and a C-frame series has frames 0 to C-1 …` / `… "sequence".F is V; it is a whole number …` | §3.4.3 — the parser reads an omitted count as 0 frames and clamps a setup past the end; state the count, and a 0-based `setup` inside it |
|
|
5175
|
+
| `… states "image" beside "sequence" …` / `… states "generator" beside "sequence" …` | §3.4.3 — the frames are the images; remove `image`. A sequence mesh takes authored geometry |
|
|
5176
|
+
| `… sequence frame I of C (number N) is the region "R", and there is no PNG for it at P …` (or `… which the atlas at A does not have …` under `--atlas-in`) | §3.4.3 — add the frame, or state the `count` the series really has. The compiler draws no frame in place of another |
|
|
5177
|
+
| `… the N frames of this sequence measure W1, W2 in width … State "width"` | §3.4.3 — frames of different sizes; state the attachment's size |
|
|
5178
|
+
| `animation "A" sequence S/X/P: attachment "P" carries no "sequence" block, so there is no series to step …` | §4.13 — give the attachment a `sequence`, or remove the track |
|
|
5179
|
+
| `animation "A" sequence S/X/P (t=T): index I is past the end of a C-frame series …` | §4.13 — `index` is 0-based |
|
|
5180
|
+
| `animation "A" sequence S/X/P: attachment "P" is a linked mesh that plays its source's timelines …` | §4.13 — key the source (the link steps its own series by it), or set `"timelines": false` on the link |
|
|
4931
5181
|
| `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 |
|
|
4932
5182
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
4933
5183
|
| `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 |
|
|
@@ -4936,6 +5186,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4936
5186
|
| `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 |
|
|
4937
5187
|
| `image "X.png" is not on disk at …` | fix the name, or point `--images` at the right directory |
|
|
4938
5188
|
| `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)) |
|
|
5189
|
+
| `a "contour" generator traces the part's own alpha, and "X.png" is lifted off a packed page at the coordinates the atlas states, which on this file are not where its texels are — so there is no silhouette here to trace, only another part of the page. page "p.png" declares …` | the page's PNG is not the size its atlas declares, and the rest of the message is `A06`'s sentence for it: re-declare the page with the `scale:` header it names, or re-export the page at its declared size (§0.2, [#750](https://github.com/firejune/rigc/issues/750)) |
|
|
4939
5190
|
| `--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) |
|
|
4940
5191
|
| `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 |
|
|
4941
5192
|
| `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 |
|
|
@@ -4948,8 +5199,12 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4948
5199
|
| `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` |
|
|
4949
5200
|
| `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 |
|
|
4950
5201
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
5202
|
+
| `animation "A" keys physics "*" P, the timeline that names no constraint and drives every physics constraint declaring "PGlobal": true, and none of "C", … does — it would parse and move nothing` | §4.4 — set `"PGlobal": true` on the constraints the track is for (§3.5), or key one by name. For `reset` it reads *every physics constraint the rig has*, and fires only on a rig with none |
|
|
5203
|
+
| `` `animations."A".tracks[i].physics` is the string ""; the empty name is how a skeleton file spells a physics timeline that names no constraint, and a motion spec spells that "*" … `` | §4.4 — name one constraint, or write `"*"` |
|
|
5204
|
+
| `physics constraint "*": the name is reserved — …` / `` `physics."*"` names a physics constraint "*", and that name is reserved … `` | §3.5 — `"*"` is the target of the timeline that names no constraint; rename the constraint |
|
|
5205
|
+
| `animation "A": group "G" lists "*", which is not a constraint but the target that names none …` | §4.4 — write `"*"` as the track's `physics` field |
|
|
4951
5206
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
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)) |
|
|
5207
|
+
| `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)); a setup `strength` below 0 says the offset is pushed away and grows, the same arm this message quotes for a negative key ([#748](https://github.com/firejune/rigc/issues/748)). A `damping` key's sentence names the exponent `60 * step` and why a negative is NaN at any `fps` where `60 / fps` is not whole (§4.4) |
|
|
4953
5208
|
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
4954
5209
|
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
4955
5210
|
| `key times must strictly increase (at t=…)` | including after `lag` and `stagger` |
|
|
@@ -5014,8 +5269,11 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5014
5269
|
| `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
5270
|
| `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
5271
|
| `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` |
|
|
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
|
|
5018
|
-
| `animation "A"
|
|
5272
|
+
| `animation "A" bone "B" has no timeline "P" (it has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate, inherit)` | §4.4 — a bone has exactly eleven 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 |
|
|
5273
|
+
| `animation "A" bone "B" inherit key at t=T carries an easing ("E"), and this timeline is stepped by the format — its reader builds no curve, so the mode changes AT the key and holds until the next one. Remove it` (and `… carries a curve, …` for a raw `curve`) | §4.4 — an `inherit` key is a mode, not a value between two others: the parser's `inherit` branch reads `time` and `inherit` and nothing else, so an easing would be written into a key nobody reads (`A05_CURVE_ARRAY_LENGTH` refuses the same `curve` on a file rigc did not write). `"stepped"` is refused too, for the same reason — the timeline already is. Delete the `ease` or `curve` ([#733](https://github.com/firejune/rigc/issues/733)) |
|
|
5274
|
+
| `animation "A" bone "B" inherit key at t=T names mode "M"; known: normal, onlyTranslation, noRotationOrReflection, noScale, noScaleOrReflection — the runtime folds the case of the first letter and of nothing else` | §4.4 — `M` is not one of the five. The list and the rule are the setup field's (§3.2) — one resolver reads both — so `NoScale` compiles (and is written `noScale`, the editor's spelling) while `NOSCALE` and `noscale` do not: the runtime would load either as **no mode**, and the bone would keep whatever world transform it had ([#733](https://github.com/firejune/rigc/issues/733)) |
|
|
5275
|
+
| `bone "B" has inherit "M"; known: normal, onlyTranslation, noRotationOrReflection, noScale, noScaleOrReflection — the runtime folds the case of the first letter and of nothing else` | §3.2 — the setup half of the row above, and the same list. Before [#733](https://github.com/firejune/rigc/issues/733) this check was case-insensitive, which is wider than the runtime: `"NOSCALE"` compiled, gated green, and loaded as no mode, so the bone's world rotation, scale and shear were never computed — every attachment on it collapsed to a point. Fix the spelling |
|
|
5276
|
+
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate, inherit; 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
5277
|
| `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
5278
|
| `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
5279
|
| `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)) |
|
|
@@ -5122,7 +5380,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5122
5380
|
| `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` |
|
|
5123
5381
|
| `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)) |
|
|
5124
5382
|
| `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 |
|
|
5125
|
-
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale. **SKIP** when the skeleton carries no animation ([#580](https://github.com/firejune/rigc/issues/580)): the NaN is produced by stepping, and a static rig is never stepped — the same subject `A09` skips on |
|
|
5383
|
+
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale. 🦴 **It also poses every bone `inherit` key at its own time** ([#733](https://github.com/firejune/rigc/issues/733)): the runtime resolves a mode by folding the case of its first letter and nothing else, and a spelling that misses is stored as **NaN** — the world position stays finite, `updateWorldTransform` matches no mode, and the bone keeps the rotation, scale and shear it had. The detail names the animation, the bone, the key's time and its spelling, beside the five. A bone whose *setup* spelling misses poses no mode at all, and is named the same way from the stepping loop. Neither is reachable from a rig spec — `build` refuses both spellings by name (§5.1) — so on a green build this clause is about files rigc did not write. Which mode a correct spelling poses is not judged here, because the lookup that resolved it is the one that would be checked **SKIP** when the skeleton carries no animation ([#580](https://github.com/firejune/rigc/issues/580)): the NaN is produced by stepping, and a static rig is never stepped — the same subject `A09` skips on |
|
|
5126
5384
|
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
5127
5385
|
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
5128
5386
|
| `A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither — which means *unmeasured*, not that the budget is inert: the same `meshSlots` is a **compile-time** refusal for rigc's own generators, before the gate (§3.7, issue #274) **SKIP** also when the rig budgets **only** triangles and the skeleton carries no mesh ([#580](https://github.com/firejune/rigc/issues/580)). A declared slot budget still PASSes there, because zero mesh slots is a count measured against a ceiling |
|
|
@@ -5135,7 +5393,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5135
5393
|
| `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)) |
|
|
5136
5394
|
| `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 |
|
|
5137
5395
|
| `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)) |
|
|
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
|
|
5396
|
+
| `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` at or below 0 — `0` says `nothing pulls it back`, below 0 says the offset `is pushed away and grows with every step`, both read off the row's `outside` arms, which the key's refusal quotes too ([#748](https://github.com/firejune/rigc/issues/748)) — 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, counts every sample of a Bezier between two keys as a value the timeline poses — so two keys of 0 joined by a curve lifted above 0 are a rescue, measured to move the bone — and takes an animation a slider applies like any other. It is the one reading `A36` and `A37` use as well ([#752](https://github.com/firejune/rigc/issues/752)). `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 |
|
|
5139
5397
|
| `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)) |
|
|
5140
5398
|
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
5141
5399
|
| `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 |
|
|
@@ -5146,10 +5404,10 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5146
5404
|
| `A31_DRAW_ORDER_OFFSETS_RESOLVE` | both | a draw-order key names a slot the skeleton does not have, offsets one slot twice, puts a slot outside the slots array, or lists its offsets out of slot order (§4.7). The only assertion that runs **before** `A00` — the last of those shapes makes the loader spin rather than return, so the round trip is refused instead of attempted |
|
|
5147
5405
|
| `A32_EVENT_KEYS_RESOLVE` | both | an event key fires a name the skeleton's `events` block does not declare, sits earlier in time than the key before it, or sets `volume`/`balance` on an event with no `audio` (§4.8). **SKIP** when no animation carries an event timeline |
|
|
5148
5406
|
| `A33_VERTEX_ATTACHMENT_GEOMETRY` | both | a bounding box, clipping polygon or path whose `vertexCount` is missing or disagrees with its vertex array, a weighted run that decodes to the wrong number of vertices or an out-of-range bone index, a clipping `end` naming a slot the skeleton does not have, a path whose vertex count is not a multiple of 3, or a path `lengths` array that does not strictly increase (§3.4). **SKIP** when the skeleton carries none of the three |
|
|
5149
|
-
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik`, `transform`, `path`, `physics` or `slider` timeline names a constraint the skeleton does not declare, names one of another type, or carries no keys at all (§4.4, §4.9, §4.10, §4.12). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. **SKIP** when no animation carries one |
|
|
5407
|
+
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik`, `transform`, `path`, `physics` or `slider` timeline names a constraint the skeleton does not declare, names one of another type, or carries no keys at all (§4.4, §4.9, §4.10, §4.12). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. The **empty** name under `physics` is not a miss — it is the timeline that names no constraint (§4.4's `"*"`), and it is refused only when it reaches none: `animation "A" physics constraint "" timeline "P": a physics group that names no constraint writes every physics constraint declaring "PGlobal": true, and none of "C", … does — the parser loads it, the runtime walks every constraint, and no constraint takes the key` ([#726](https://github.com/firejune/rigc/issues/726)). Who it reaches is asked of the runtime's own `PhysicsConstraintTimeline.global` on the file's constraints — the reading `A23` and `A42` share. **SKIP** when no animation carries one |
|
|
5150
5408
|
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | both | a deform key's run runs past the end of the attachment's deform array, holds a non-finite number, has an empty key array, or names a skin/slot/attachment triple that does not resolve (§4.11). The overrun is the quiet one — the parser copies into a `Float32Array` and drops the tail. ⛔ It does **not** require pair alignment: the runtime has no such rule and a trimmed editor run legitimately starts and ends mid-pair (§4.11, issue #262). **SKIP** when no animation carries a deform timeline |
|
|
5151
|
-
| `A36_PATH_CONSTRAINT_EFFECTIVE` | both | a path constraint whose slot has no path attachment in any skin, one that constrains no bone, or one whose three mixes are all 0 at setup with no animation keying
|
|
5152
|
-
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` (§3.5.2). **SKIP** when the skeleton declares no slider |
|
|
5409
|
+
| `A36_PATH_CONSTRAINT_EFFECTIVE` | both | a path constraint whose slot has no path attachment in any skin, one that constrains no bone, or one whose three mixes are all 0 at setup with no animation keying any of them **above 0** (§3.5.1, §4.12). The first is the quiet one: `update()` returns on its first line and the constraint reports mixes it never applies. The third reads the values the loaded timeline poses — keys and Bezier samples alike — the way `A23` reads a physics `mix`, so a `mix` timeline keying 0 only is not a rescue: [measured] it poses every bone exactly where no timeline does, and before [#752](https://github.com/firejune/rigc/issues/752) it passed. The detail says both halves: `path constraint "P" has mixRotate 0, mixX 0 and mixY 0 at setup and none of the 2 animations keys its mix above 0; update() returns on all-zero mixes, so nothing ever puts a bone on the path — rest one of the three above 0, or key its mix above 0 in an animation`. **SKIP** when the skeleton declares no path constraint |
|
|
5410
|
+
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` **above 0** (§3.5.2, §4.12) — the same reading as `A36`'s, so a `mix` timeline keying 0 only is no rescue ([#752](https://github.com/firejune/rigc/issues/752)): `slider "S" has mix 0 at setup and none of the 2 animations keys its mix above 0; update() returns on mix 0 — rest it above 0, or key its mix above 0 in an animation`. **SKIP** when the skeleton declares no slider |
|
|
5153
5411
|
| `A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED` | both | a bone or constraint a skin activates that is not `skinRequired` (the list changes nothing), or one that is `skinRequired` and no skin activates (it is never active). Two keys in two places, and only together do they mean "this belongs to that skin" (§3.4.1). **SKIP** when no skin activates anything and nothing is `skinRequired` |
|
|
5154
5412
|
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. ⚠️ And the **skin** it poses in is the one the timeline is keyed on (§4.11.5, issue #583), since a deform's address is a `skin / slot / attachment` triple: the pose wears that skin, which also switches on any `skin: true` bone or constraint it activates, and the "nothing is drawn" sentence names the skin it was read under. **SKIP** when no animation carries a deform timeline, when nothing keyed has triangles, when every mesh keyed is exempt, when every key measured draws no pixels or is unreachable *and no span between them folds where anything is drawn*, or when there is no rig info at all |
|
|
5155
5413
|
| `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 |
|
|
@@ -5158,6 +5416,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5158
5416
|
| `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 |
|
|
5159
5417
|
| `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
5418
|
| `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 |
|
|
5419
|
+
| `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` | both | a **numbered series** (§3.4.3, §4.13) that the runtime does not show as the file states it. Every shape below loads without a word, measured on spine-core 4.3.13 ([#729](https://github.com/firejune/rigc/issues/729)). **The block**: a `sequence` with no `count` (`readSequence` reads 0, and the attachment holds no region) or a `setup` at or past `count` (`Sequence.resolveIndex` clamps it to the last frame). **The keys**: a `mode` outside the seven — `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse` — loads as `hold`; an `index` that is fractional (`index << 4` truncates it) or past the end (clamped); an advancing mode at an effective delay of 0 (the parser carries a key's `delay` from the key before; `(time - keyTime) / 0` is Infinity and `Infinity \| 0` is 0, so it never advances); a timeline on an attachment that carries no block (the parser gives every region a one-region series, so every mode shows it). **The pose**: every key is stepped to mid-frame sample times — enough to wrap every mode — and the region the slot shows is held to the frame the file's own statement gives, the arithmetic of `SequenceTimeline.applyToSlot` and the names of `Sequence.getPath` transcribed rather than read off the loaded timeline, so the check is not the runtime agreeing with itself. Before the first key the frame is `setup`. ⚠️ A sample where the slot shows another attachment is not compared, because the runtime writes nothing there; a timeline with no comparable sample is counted in `stats.sequenceSamplesUnshown`. `compile.ts` refuses every one of these shapes in a spec (§5.1); this is them held against a skeleton it did not write. **SKIP** when no attachment carries a `sequence` block and no animation keys a `sequence` timeline |
|
|
5161
5420
|
|
|
5162
5421
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
5163
5422
|
clauses are gated by profile.
|
|
@@ -5205,12 +5464,14 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
5205
5464
|
`--profile spine-html` over a packed atlas. The default is still **one part per
|
|
5206
5465
|
page** with `pma: false` and every region covering its whole page, and nothing
|
|
5207
5466
|
about a build changes unless one of those flags is given.
|
|
5208
|
-
- **`
|
|
5209
|
-
`
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
(§4.
|
|
5467
|
+
- **`drawOrderFolder`** is walked by the validator (so `A05` checks its curves and
|
|
5468
|
+
`diff` counts its keys) and cannot be *written*: there is no motion-spec property
|
|
5469
|
+
for it. Everything a motion spec **can** key is §4.4's track table — which now
|
|
5470
|
+
includes the `path` and `slider` groups (§4.12) — plus the six families that sit
|
|
5471
|
+
beside `tracks`: `drawOrder` (§4.7), `events` (§4.8), `ik` (§4.9), `transform`
|
|
5472
|
+
(§4.10), `deform` (§4.11) and `sequence` (§4.13). `sequence` stood beside
|
|
5473
|
+
`drawOrderFolder` in this sentence until
|
|
5474
|
+
[#729](https://github.com/firejune/rigc/issues/729) made it writable.
|
|
5214
5475
|
|
|
5215
5476
|
---
|
|
5216
5477
|
|