spine-rigc 1.0.0 โ 1.0.2
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 +35 -71
- package/docs/AUTHORING.md +727 -1160
- package/docs/FACE.md +205 -371
- package/docs/INGEST.md +155 -305
- package/docs/MOTION.md +11 -27
- package/docs/PROMPTING.md +1 -1
- package/docs/RIGGING.md +10 -26
- package/docs/SPEC_COVERAGE.md +27 -871
- package/package.json +1 -1
- package/src/atlas.ts +9 -8
- package/src/compile.ts +1 -1
- package/src/generation.ts +2 -1
- package/src/ladder.ts +1 -1
- package/src/rig.ts +4 -4
- package/src/validate.ts +20 -14
package/docs/AUTHORING.md
CHANGED
|
@@ -11,8 +11,8 @@ checked against the code that implements it.
|
|
|
11
11
|
๐จ **The gate cannot see a wrong animation, and it will not tell you so.** `build`
|
|
12
12
|
is green when the file is *valid* โ parseable, steppable, nothing degenerate in it.
|
|
13
13
|
Whether the animation is the one you were asked for is a question it does not ask
|
|
14
|
-
and has no way to answer. This is not a caveat:
|
|
15
|
-
|
|
14
|
+
and has no way to answer. This is not a caveat: a build with **every easing in the
|
|
15
|
+
file reversed** gates green. If you were given pictures, `check` (**ยง9**) is the half of the loop
|
|
16
16
|
that can see that, and a run that skips it has verified nothing about the motion.
|
|
17
17
|
|
|
18
18
|
- Formats and CLI reference: [README.md](../README.md)
|
|
@@ -20,10 +20,11 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
20
20
|
- The motion spec and emitted shapes: [`src/types.ts`](../src/types.ts)
|
|
21
21
|
- What the motion spec's own parser proves, and which refusals it deliberately
|
|
22
22
|
leaves to compile time: [`src/motion.ts`](../src/motion.ts)
|
|
23
|
-
- What the format holds
|
|
24
|
-
authoring input, and deliberately unlinked**: it
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
- What the Spine 4.3 format holds, field by field: `docs/SPEC_COVERAGE.md` โ ๐ซ **not
|
|
24
|
+
an authoring input, and deliberately unlinked**: it and the dated survey it was split
|
|
25
|
+
from, `docs/SURVEY_2026-08-22.md`, which inventories the benchmark corpus skeleton by
|
|
26
|
+
skeleton, are both on the ladder run's forbidden list. Named here for a maintainer,
|
|
27
|
+
not offered to a run
|
|
27
28
|
- Working with a skeleton **somebody else authored** โ what the CLI will and will not
|
|
28
29
|
do with a foreign `skeleton.json`, transcribing one into specs, re-pivoting and
|
|
29
30
|
renaming it: `docs/INGEST.md`. ๐ซ **Not an authoring input, and deliberately
|
|
@@ -52,13 +53,12 @@ property of the tool.
|
|
|
52
53
|
|
|
53
54
|
๐ **A ladder run reads this guide in full and does not follow its references out of
|
|
54
55
|
it.** The guide is allowed reading; not everything it cites is. Citations here are
|
|
55
|
-
provenance for a reader of record
|
|
56
|
-
and following one can arrive at a stored candidate's own spec, at the corpus inventory,
|
|
56
|
+
provenance for a reader of record, and following one can arrive at a stored candidate's own spec, at the corpus inventory,
|
|
57
57
|
or at the **derivation** of the gate a verdict is read against, none of which a run may
|
|
58
58
|
open. โญ The gate's **clause statements** are a different matter and a run may read them:
|
|
59
59
|
they are in
|
|
60
60
|
[GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md), which is
|
|
61
|
-
repository material and not in the published package โ item 11 of the allowed list
|
|
61
|
+
repository material and not in the published package โ item 11 of the allowed list โ the
|
|
62
62
|
measure, the comparator, the number and the SKIP semantics, with no recorded figure in it.
|
|
63
63
|
So: read the
|
|
64
64
|
document, take its numbered sections as the input, and leave its footprints to whoever
|
|
@@ -160,11 +160,11 @@ What the flags mean:
|
|
|
160
160
|
| `--rig` | the rig spec โ skeleton structure |
|
|
161
161
|
| `--motion` | the motion spec โ time |
|
|
162
162
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths and `skeleton.images` are written relative to it |
|
|
163
|
-
| `--copy-images` | `build` only: also copies every page **the emitted atlas names** into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own, and points `skeleton.images` at `--out` itself so the editor's import finds the parts beside the skeleton (
|
|
164
|
-
| `--pack` | `build` only: arrange every part onto **shared** atlas page(s), written into `--out` as real PNGs, instead of one page per part. Lossless โ nothing is resampled, trimmed or rotated.
|
|
163
|
+
| `--copy-images` | `build` only: also copies every page **the emitted atlas names** into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own, and points `skeleton.images` at `--out` itself so the editor's import finds the parts beside the skeleton (ยง3.1 says why it is spelled `../<out>/` and not `./`). Under `--atlas-in` those pages are the pack's, not one per part (**ยง0.2**). Without it, page paths point at the source art |
|
|
164
|
+
| `--pack` | `build` only: arrange every part onto **shared** atlas page(s), written into `--out` as real PNGs, instead of one page per part. Lossless โ nothing is resampled, trimmed or rotated. The default is one page per part โ **ยง0.1** |
|
|
165
165
|
| `--page-size` | `build --pack` only: the largest page edge (default `2048`). A ceiling, not the size: page edges are powers of two and the one written is the smallest that holds the pack โ **ยง0.1** |
|
|
166
166
|
| `--padding` | `build --pack` only: the gutter each region reserves on every side (default `2`), filled by extending the region's own edge pixels outwards. `0` is not a legal-but-tight choice, it is bleed โ **ยง0.1** |
|
|
167
|
-
| `--atlas-in` | `build` and `explain`: resolve every part against the **regions of a pre-packed `.atlas`** instead of against loose PNGs. Region geometry is read from the file and sizes are descaled by the page's `scale:`; `build` re-emits the atlas into `--out`, re-anchored, and `explain` writes nothing and poses through it โ **ยง0.2**. On `explain` it is the flag that makes a **size-only** spec readable at all (`ingest --art none`), because posing resolves every attachment against an atlas; without it that pair is refused by name rather than thrown through (
|
|
167
|
+
| `--atlas-in` | `build` and `explain`: resolve every part against the **regions of a pre-packed `.atlas`** instead of against loose PNGs. Region geometry is read from the file and sizes are descaled by the page's `scale:`; `build` re-emits the atlas into `--out`, re-anchored, and `explain` writes nothing and poses through it โ **ยง0.2**. On `explain` it is the flag that makes a **size-only** spec readable at all (`ingest --art none`), because posing resolves every attachment against an atlas; without it that pair is refused by name rather than thrown through (ยง5.1) |
|
|
168
168
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory). For `pose` it is the directory of **loose part PNGs to place** โ every `.png` in it is a part, in name order. For `chainfit` it is only where each attachment's image name **resolves**: the candidate decides what the parts are, so extra PNGs are unused and a missing name is refused by name (ยง12.3) |
|
|
169
169
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
170
170
|
| `--cut` | `build`, `explain` and `validate`: look up a named cut in `--cuts <cuts.json>`, **instead of** `--rig`/`--motion`/`--out` โ the two spellings are one build stated two ways and are refused together. A `cuts.json` is `{ "<name>": { "rig": โฆ, "motion": โฆ, "out": โฆ, "manifest"?: โฆ } }`, every path in it relative to the table's own file, so the table lives with the project that owns the art |
|
|
@@ -202,19 +202,16 @@ unweighted meshes, packed atlases) โ reach for it when you are shipping into
|
|
|
202
202
|
*that* project, not to be thorough. A report always prints which profile ran and
|
|
203
203
|
lists what that profile left out, on `PROF` lines.
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
spine-html
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
page OR a tiling page**, so the combination is an ordinary build โ and it is the
|
|
210
|
-
only one that puts the renderer's own rulebook over shared-page sampling. What a
|
|
205
|
+
*Packed atlases* are in that list for what a page may hold, not for being packed:
|
|
206
|
+
under `spine-html`, `A06`'s coverage clause is **one part per page OR a tiling
|
|
207
|
+
page**, so `--pack --profile spine-html` is an ordinary build โ and it is the only
|
|
208
|
+
one that puts the renderer's own rulebook over shared-page sampling. What a
|
|
211
209
|
*tiling* page has to satisfy under that profile is stated where the clause is,
|
|
212
|
-
ยง5.2's `A06` row: no two regions on one page overlapping. Rotation is
|
|
213
|
-
|
|
210
|
+
ยง5.2's `A06` row: no two regions on one page overlapping. Rotation is refused, and
|
|
211
|
+
that is a separate clause about rigc's packer never turning a region.
|
|
214
212
|
|
|
215
|
-
โ ๏ธ The other half of that
|
|
216
|
-
|
|
217
|
-
**validity**, so no profile switches it off: a rectangle outside its page is
|
|
213
|
+
โ ๏ธ The other half of that clause โ every region wholly inside the page it names โ
|
|
214
|
+
is **validity**, so no profile switches it off: a rectangle outside its page is
|
|
218
215
|
broken for every consumer, while two regions over the same texels is something
|
|
219
216
|
correct, editor-exported data does.
|
|
220
217
|
|
|
@@ -251,7 +248,7 @@ a gap:
|
|
|
251
248
|
- **no rotation.** `rotate: 0` on every region, and it is a fact rather than a
|
|
252
249
|
field: the runtime transposes `u2/v2` at 90 and not at 270, and
|
|
253
250
|
`RegionAttachment.computeUVs` assigns a different corner order at 90, so a
|
|
254
|
-
rotated pack is one rigc's own `--atlas` substitution cannot read
|
|
251
|
+
rotated pack is one rigc's own `--atlas` substitution cannot read.
|
|
255
252
|
Rotation buys page area; a page that runs out of room spills to a second page
|
|
256
253
|
instead.
|
|
257
254
|
- **no re-ordering of anything the skeleton says.** `skeleton.json` from a packed
|
|
@@ -310,7 +307,7 @@ drawing at its own size, which is what an attachment's size means.
|
|
|
310
307
|
`scale: 0.5` holds texels half the size of the drawings it was packed from โ the
|
|
311
308
|
line says so, and an attachment's `width` is in world units, which the runtime
|
|
312
309
|
reads straight out of the skeleton JSON with the atlas nowhere in the expression.
|
|
313
|
-
So the scale has to be undone here or never
|
|
310
|
+
So the scale has to be undone here or never. But the packer wrote
|
|
314
311
|
`round(drawing ร scale)`, so a 373-texel region at `scale: 0.5` is consistent with
|
|
315
312
|
a 745- and a 746-pixel drawing and the file does not say which: an imported size
|
|
316
313
|
is right to within `0.5 / scale` source pixels, and the build report prints the
|
|
@@ -324,19 +321,17 @@ texel count beside it so both numbers are visible:
|
|
|
324
321
|
measures the PNG. Reach for `--atlas-in` when the pack is what you were handed, or
|
|
325
322
|
when drawing through the pack's own texels is the point.
|
|
326
323
|
|
|
327
|
-
๐ **What you make beside the art stays at the art's size
|
|
328
|
-
([#762](https://github.com/firejune/rigc/issues/762)). A depth sheet or a soft
|
|
324
|
+
๐ **What you make beside the art stays at the art's size.** A depth sheet or a soft
|
|
329
325
|
mask is read in the drawing's pixels on a `scale:` page as on loose parts โ each
|
|
330
326
|
vertex's texel position over the stated scale โ and a mesh fit's overshoot is
|
|
331
327
|
printed in them, with the texel it was measured on named beside it. So are a
|
|
332
|
-
`contour`'s `margin` and `tolerance
|
|
333
|
-
([#779](https://github.com/firejune/rigc/issues/779)): the trace runs on the
|
|
328
|
+
`contour`'s `margin` and `tolerance`: the trace runs on the
|
|
334
329
|
texels there are, and the two distances are applied on them as `value ร scale`
|
|
335
330
|
texels โ the scale the header states, never a measured one (below). What stays
|
|
336
331
|
in texels is only what is counted off them: a contour's hole count.
|
|
337
332
|
|
|
338
333
|
๐จ **A page that declares a size it does not have is a different thing, and it is
|
|
339
|
-
refused
|
|
334
|
+
refused.** The common shape
|
|
340
335
|
is a pack whose `4096x4096` pages ship as `2048x2048` PNGs with the atlas
|
|
341
336
|
untouched โ every region still stated in 4096-space โ and that is **not** what a
|
|
342
337
|
`scale:` line says. A runtime draws it anyway: `TextureAtlas` computes every
|
|
@@ -372,14 +367,14 @@ it hits instead of leaving it to be discovered:
|
|
|
372
367
|
size the atlas declares, or repack.
|
|
373
368
|
|
|
374
369
|
๐ **`explain` does not refuse such a page โ it withholds what it would have
|
|
375
|
-
measured off it
|
|
370
|
+
measured off it.** It never
|
|
376
371
|
gates, a runtime draws the page, and most of its report โ bones, slots, timelines
|
|
377
372
|
โ reads no texel at all, so the pack still compiles and the report still prints.
|
|
378
|
-
What it
|
|
373
|
+
What it does not print is a figure taken at the coordinates the atlas states,
|
|
379
374
|
because on such a file those are another part of the picture: measured on a pack
|
|
380
375
|
at half resolution, an authored mesh that covers 100.00% of its art and reaches
|
|
381
|
-
16.00px past it
|
|
382
|
-
reads 10 of its 12 vertices on undrawn texels
|
|
376
|
+
16.00px past it would print **68.49%** and **76.24px**, and a `grid` whose depth sheet
|
|
377
|
+
reads 10 of its 12 vertices on undrawn texels would print **12 of 12**. The page is
|
|
383
378
|
named once where the report starts, with the ratio `A06` refuses it by, and every
|
|
384
379
|
withheld figure says so where it would have stood:
|
|
385
380
|
|
|
@@ -402,12 +397,11 @@ on the **coarser** texels the page really has, so a figure that depends on the
|
|
|
402
397
|
grid need not equal the one the pack the page was halved from reads โ and a
|
|
403
398
|
contour's outline is one of them, below.
|
|
404
399
|
|
|
405
|
-
๐ **A fit's overshoot is stated in the drawing's pixels on every page
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
the
|
|
410
|
-
on a `scale: 2` one. It is now the texel distance over the scale the atlas states,
|
|
400
|
+
๐ **A fit's overshoot is stated in the drawing's pixels on every page.** It is a
|
|
401
|
+
distance, and on a page that declares a `scale:` it is taken on the page's texels โ
|
|
402
|
+
counted in them, one mesh over one drawing reads 16.00px on the declared-size page,
|
|
403
|
+
**8.00px** on its `scale: 0.5` restatement and **32.00px** on a `scale: 2` one. So
|
|
404
|
+
it is printed as the texel distance over the scale the atlas states,
|
|
411
405
|
which is the unit an attachment's `width` is in and the one you drew in, and the
|
|
412
406
|
line says which grid it was taken on โ a texel of a `scale: 0.5` page is 2.00px of
|
|
413
407
|
the drawing, so that is the step the figure moves in:
|
|
@@ -426,14 +420,14 @@ texels are the drawing, and the line is the one it always was. A `contour`'s hol
|
|
|
426
420
|
count is a count of those cells and says `texel(s)` on such a page.
|
|
427
421
|
|
|
428
422
|
๐ **A `contour`'s `margin` and `tolerance` are the drawing's pixels on every
|
|
429
|
-
page
|
|
423
|
+
page.** They are your
|
|
430
424
|
statement, in the unit every other size in the spec is in, so on a `scale:` page
|
|
431
425
|
the trace applies them as `margin ร scale` and `tolerance ร scale` texels, and
|
|
432
|
-
`maxVertices` is judged on the outline that asks for.
|
|
433
|
-
|
|
434
|
-
|
|
426
|
+
`maxVertices` is judged on the outline that asks for. Applied in texels
|
|
427
|
+
unconverted, they would ask each page a different question. Measured on one pack
|
|
428
|
+
at three scales, one spec โ `tolerance: 1.5`, `margin: 2`, `maxVertices: 48`:
|
|
435
429
|
|
|
436
|
-
| page | applied in texels
|
|
430
|
+
| page | applied in texels | applied as the drawing's pixels (what rigc does) |
|
|
437
431
|
| --- | --- | --- |
|
|
438
432
|
| declared size | 15 vertices | **15** vertices, the same mesh |
|
|
439
433
|
| `scale: 2` | refused: `simplified to 66 vertices at tolerance 1.5, past the 48` | **15** vertices, the declared outline to 0.00px |
|
|
@@ -458,7 +452,7 @@ names the figure the spec states, the texels it was applied as and the scale:
|
|
|
458
452
|
```
|
|
459
453
|
|
|
460
454
|
๐จ **A page that is not a PNG is refused by name, before anything is compiled
|
|
461
|
-
against it
|
|
455
|
+
against it.** rigc reads PNG
|
|
462
456
|
and nothing else โ the size `A06` judges, the alpha `A19` judges, the renderer and
|
|
463
457
|
the region lift all decode PNG, and rigc links no decoder for any other format โ
|
|
464
458
|
and a file's name is not evidence: a production pack shipped WebP pages called
|
|
@@ -483,20 +477,16 @@ a page fails `A06` with the same sentence:
|
|
|
483
477
|
measures it like any other. โ ๏ธ `A06` deliberately does not read the width and
|
|
484
478
|
height a WebP header carries in a fixed field. Doing so would print a size that no
|
|
485
479
|
reader in this tree can check โ the parse would agree only with the forgery it was
|
|
486
|
-
written against โ about a page that is refused either way; and
|
|
487
|
-
|
|
488
|
-
|
|
480
|
+
written against โ about a page that is refused either way; and a size-matching
|
|
481
|
+
WebP page let through that way builds green under the default profile and writes
|
|
482
|
+
a directory whose next `render` refuses the page.
|
|
489
483
|
|
|
490
484
|
**What `--out` holds afterwards:** `skeleton.json` and a `skeleton.atlas` that is
|
|
491
485
|
the pack, page paths pointing back at the pack's own PNGs โ so `rigc validate
|
|
492
486
|
<that directory>` reads it green with no flags, exactly as it reads a loose
|
|
493
487
|
build's. Add `--copy-images` and the pack's page PNGs are copied in beside the
|
|
494
488
|
skeleton and the page names become their basenames, which is the same directory
|
|
495
|
-
with nothing outside it left to resolve.
|
|
496
|
-
[#693](https://github.com/firejune/rigc/issues/693) that flag rebuilt the atlas
|
|
497
|
-
from the parts the rig declared instead of from the pack, and a rebuild through
|
|
498
|
-
`ingest --art none` declares none: the file written was **zero bytes**, on a build
|
|
499
|
-
that printed `PASS` for all four atlas assertions.
|
|
489
|
+
with nothing outside it left to resolve.
|
|
500
490
|
|
|
501
491
|
The emitted `skeleton.atlas` **is** the imported one, verbatim except for its page
|
|
502
492
|
name lines, which are paths and have to be re-anchored to `--out`. Fields rigc
|
|
@@ -505,17 +495,15 @@ because the text passes through by line; regions the rig does not use stay in th
|
|
|
505
495
|
file, because a real pack is shared between cuts and an importer that quietly
|
|
506
496
|
dropped half of one would make `--out` disagree with the pack it was built from.
|
|
507
497
|
|
|
508
|
-
๐ธ **The one thing not passed through is the pack's blank lines
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
`A07_ATLAS_TEXT_SHAPE: line 1: consecutive blank lines`. A run of blank lines
|
|
518
|
-
between two page blocks reads as one to the runtime, and is written as one.
|
|
498
|
+
๐ธ **The one thing not passed through is the pack's blank lines.** They are
|
|
499
|
+
written in the shape rigc writes its own packs in: none before the first page,
|
|
500
|
+
exactly one between two page blocks, one trailing newline. Every non-blank line
|
|
501
|
+
keeps its bytes and its order, so a pack already in that shape โ every editor
|
|
502
|
+
export in the example corpus, and every pack `--pack` writes โ is emitted byte for
|
|
503
|
+
byte. A pack from a 3.8-era packer begins with a blank line: the runtime reads a
|
|
504
|
+
run of blank lines before the first page as nothing, so the pack loads, and
|
|
505
|
+
`build` writes it without the blank. A run of blank lines between two page blocks
|
|
506
|
+
reads as one to the runtime, and is written as one.
|
|
519
507
|
|
|
520
508
|
Five things are refused rather than warned about, because each of them otherwise
|
|
521
509
|
**loads clean and draws wrong** โ or, the last, cannot be read back at all:
|
|
@@ -525,17 +513,17 @@ Five things are refused rather than warned about, because each of them otherwise
|
|
|
525
513
|
| a region name the atlas does not have | `AtlasAttachmentLoader` returns null and the part silently does not draw. The refusal lists the near misses โ the usual cause is one character |
|
|
526
514
|
| 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 |
|
|
527
515
|
| 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 |
|
|
528
|
-
| 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
|
|
529
|
-
| 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
|
|
516
|
+
| 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 |
|
|
517
|
+
| 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 |
|
|
530
518
|
|
|
531
519
|
One limit, stated rather than discovered:
|
|
532
520
|
|
|
533
521
|
- an **optional state** (a manifest `states:` entry) whose region is not in the
|
|
534
|
-
pack is a `DROP`, not a refusal โ the same documented absence a missing PNG
|
|
535
|
-
|
|
522
|
+
pack is a `DROP`, not a refusal โ the same documented absence a missing PNG is,
|
|
523
|
+
and the line names the atlas rather than a file nobody opened.
|
|
536
524
|
|
|
537
|
-
โญ **The missing PNG it is the same rule as, spelled out**, because
|
|
538
|
-
|
|
525
|
+
โญ **The missing PNG it is the same rule as, spelled out**, because the two halves
|
|
526
|
+
are read together. A `states:` entry whose PNG is
|
|
539
527
|
not on disk is a `DROP` too, and the line names the file rather than the pack:
|
|
540
528
|
|
|
541
529
|
```bash
|
|
@@ -552,11 +540,9 @@ with that part missing, at exit 0 and a green gate. Restore the art, or fix the
|
|
|
552
540
|
path the manifest states.
|
|
553
541
|
|
|
554
542
|
๐ **A refused build prints its `DROP` lines too**, under the refusal on stderr,
|
|
555
|
-
|
|
556
|
-
reported from the compile result and a refusal returns none, so the one output
|
|
557
|
-
naming the file was missing from exactly the run that failed over it.
|
|
543
|
+
so the one output naming the file is there on exactly the run that failed over it.
|
|
558
544
|
|
|
559
|
-
A **turned** region is not one of them
|
|
545
|
+
A **turned** region is not one of them. A pack made
|
|
560
546
|
by somebody else routinely rotates a region to save space โ `rotate: 90`,
|
|
561
547
|
`rotate: 180`, `rotate: 270`, or the format's older `rotate: true` โ and rigc
|
|
562
548
|
reads all of them: anything that measures a part's pixels sees the same grid it
|
|
@@ -582,15 +568,13 @@ means for the numbers beside it, and the pack states none of this:
|
|
|
582
568
|
|
|
583
569
|
Each direction there is **measured** rather than reasoned about: it comes off
|
|
584
570
|
`MeshAttachment.computeUVs` in the linked runtime, the one routine there that
|
|
585
|
-
says where a region's texels are for all four values
|
|
586
|
-
the words in that list from the same routine instead of reading them. โ ๏ธ Do not
|
|
571
|
+
says where a region's texels are for all four values. โ ๏ธ Do not
|
|
587
572
|
take the turn from `TextureAtlas`'s own `u2`/`v2` โ those transpose at a quarter
|
|
588
573
|
turn one way and not at the other, so one of the two pairs describes a rectangle
|
|
589
|
-
the page does not have
|
|
574
|
+
the page does not have.
|
|
590
575
|
|
|
591
|
-
`build`
|
|
592
|
-
report carries the rectangle to cut instead of leaving it to be derived
|
|
593
|
-
([#718](https://github.com/firejune/rigc/issues/718)):
|
|
576
|
+
`build` prints the page rectangle on the line for a turned region, so the
|
|
577
|
+
report carries the rectangle to cut instead of leaving it to be derived:
|
|
594
578
|
|
|
595
579
|
```bash
|
|
596
580
|
# .. pendulum 105x139 <- ../export/atlas.png @ 710,16 rotate 90, occupies 139x105
|
|
@@ -599,7 +583,7 @@ report carries the rectangle to cut instead of leaving it to be derived
|
|
|
599
583
|
โ ๏ธ Two limits here are real and neither is about rotation:
|
|
600
584
|
|
|
601
585
|
- `--atlas-in` cannot recover what a `scale:` quantised away (above), turned or not;
|
|
602
|
-
- the **renderer** profile
|
|
586
|
+
- the **renderer** profile refuses a turned region outright
|
|
603
587
|
(`A06`, `--profile spine-html`), because that profile is about artifacts rigc
|
|
604
588
|
itself emits and it never packs one turned. Reading a foreign pack and gating
|
|
605
589
|
one under somebody else's renderer policy are different questions.
|
|
@@ -648,7 +632,7 @@ repository builds on every run.
|
|
|
648
632
|
| `--art loose` (default) | name an `image` per attachment โ `<path or placeholder>.png` โ so the rebuild resolves loose PNGs and rigc measures them |
|
|
649
633
|
| `--art none` | state `width`/`height` only, so the rebuild is `build --atlas-in <pack.atlas>` and every part resolves out of the pack |
|
|
650
634
|
| `--images <dir>` | **write** the rig spec's own `images` directory, spelled relative to `--out`, so the rebuild is a plain `build --rig โฆ --motion โฆ --out โฆ`. Without it the field is left out and every `image` resolves against `--out` itself, which holds the specs and no art โ so every rebuild has to repeat `build --images <dir>`. Refused together with `--art none`, which writes no `image` for it to be the base of |
|
|
651
|
-
| `--stage x,y,w,h` | a setup bounding box to **add** to a skeleton that declares none โ without it the absence is carried as `"width": null, "height": null
|
|
635
|
+
| `--stage x,y,w,h` | a setup bounding box to **add** to a skeleton that declares none โ without it the absence is carried as `"width": null, "height": null`. An editor export *may* be such a file; every export under `examples/` carries a box and `ingest` reads it straight through โ so passing the flag at one of them is **refused**, naming both boxes, rather than silently doing nothing |
|
|
652
636
|
| `--name <n>` | the rig spec's `name`, which the motion spec's `archetype` must equal (default: the file's basename) |
|
|
653
637
|
|
|
654
638
|
โ ๏ธ **`ingest --images` and `build --images` point opposite ways.** `build --images`
|
|
@@ -663,8 +647,8 @@ where the parts are in one convention.
|
|
|
663
647
|
one โ posing the rig gives the *animated* extent, which is a different number from the
|
|
664
648
|
editor's setup box. So a file that declares none is **carried as declaring none**: the
|
|
665
649
|
spec states `"width": null, "height": null` (ยง3.1), the rebuild emits no box, and
|
|
666
|
-
nothing is recorded, because the rebuild is the file that was read
|
|
667
|
-
|
|
650
|
+
nothing is recorded, because the rebuild is the file that was read.
|
|
651
|
+
`--stage x,y,w,h` *adds* a box โ
|
|
668
652
|
supply it from the project the file came from, or from the editor's own canvas โ and
|
|
669
653
|
is recorded as a `NO_STAGE` judgement. A header stating **half** a stage (an origin
|
|
670
654
|
with no extent) is a `NO_STAGE` blocker: the spec holds a stage as four fields or none.
|
|
@@ -672,27 +656,20 @@ with no extent) is a `NO_STAGE` blocker: the spec holds a stage as four fields o
|
|
|
672
656
|
โ **The flag is refused beside a box the file states.** Two sources for one value, and
|
|
673
657
|
the file is the one that was measured โ so `ingest` names both boxes and stops rather
|
|
674
658
|
than writing one of them and saying nothing. Drop the flag, or correct `skeleton` in the
|
|
675
|
-
source if its box is wrong
|
|
676
|
-
|
|
677
|
-
`
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
the editor preserves the absence rather than inventing a stage
|
|
690
|
-
([#616](https://github.com/firejune/rigc/issues/616)). So `--stage` is for a file that
|
|
691
|
-
really has none, and this repository's corpus holds no example of one; a production
|
|
692
|
-
corpus measured for [#714](https://github.com/firejune/rigc/issues/714) holds 48 of 48. It is still the
|
|
693
|
-
value that costs least to get wrong: `diff` reports the box as `stage_present` and
|
|
694
|
-
`stage_box` ([#578](https://github.com/firejune/rigc/issues/578)) and both are
|
|
695
|
-
`(reported)`, so nothing on the ladder consults them.
|
|
659
|
+
source if its box is wrong. What it does **not** do is refuse an *omitted origin*:
|
|
660
|
+
inside a declared extent an omitted `x`/`y` is `0` โ the reading `build` emits and
|
|
661
|
+
`diff` compares โ so the written spec states it and a `LOSS HEADER_ORIGIN` line says
|
|
662
|
+
the source omitted it and that the rebuild will spell it.
|
|
663
|
+
|
|
664
|
+
โ ๏ธ **All twelve exports under `examples/` declare `x`, `y`, `width` and `height`**,
|
|
665
|
+
`ingest` reads each box straight through, and not one needs the flag. What an
|
|
666
|
+
editor export *may* do is carry none: a rigc build that declares no stage came back
|
|
667
|
+
from a Spine 4.3.26 round trip with a header of `hash`, `spine`, `images`, `audio`
|
|
668
|
+
and **no box at all** โ the editor preserves the absence rather than inventing a
|
|
669
|
+
stage. So `--stage` is for a file that really has none; the example corpus holds no
|
|
670
|
+
such file, and production exports do โ 48 of 48 measured. It is the value that
|
|
671
|
+
costs least to get wrong: `diff` reports the box as `stage_present` and
|
|
672
|
+
`stage_box` and both are `(reported)`, so nothing on the ladder consults them.
|
|
696
673
|
|
|
697
674
|
โ ๏ธ **The duration is a convention, and it is recorded as one.** Skeleton JSON has no
|
|
698
675
|
duration field. The largest key time is the only derivable answer and it is what a
|
|
@@ -709,24 +686,26 @@ the first:
|
|
|
709
686
|
| --- | --- |
|
|
710
687
|
| `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 |
|
|
711
688
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
712
|
-
| `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how: `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
|
|
689
|
+
| `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how: `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 |
|
|
713
690
|
|
|
714
691
|
โ **It reads one generation of the format, and a file from another one ends loud.**
|
|
715
692
|
Spine data is locked to the generation that exported it and a mismatch does not
|
|
716
693
|
throw: 4.3 takes constraints from the top-level `constraints` array alone, so a
|
|
717
694
|
4.0โ4.2 file's `ik`/`transform`/`path`/`physics` arrays load as nothing at all โ 1,302
|
|
718
695
|
shipped skeletons parsed on a 4.3 runtime and loaded 0 of 8,672 constraints
|
|
719
|
-
(
|
|
696
|
+
(GENERATIONS.md, row 1). So `ingest` reads
|
|
720
697
|
`skeleton.spine` before it reads a field of the file. A file from another generation is
|
|
721
698
|
a `BLOCK GENERATION_UNSUPPORTED` naming the generation, the string it was read from,
|
|
722
699
|
and what a 4.3 reader loses **on that file**: the constraints parked in those arrays
|
|
723
|
-
counted by kind, the bones carrying 4.
|
|
700
|
+
counted by kind, the bones carrying 4.0/4.1's `transform` where 4.3 spells `inherit`, and
|
|
724
701
|
the physics constraints omitting `inertia`/`damping`, whose default is not the same
|
|
725
702
|
number in the two. A label naming no generation rigc knows โ or a header stating none โ
|
|
726
703
|
is a `BLOCK GENERATION_UNKNOWN`, never rounded to the nearest: a catalog that rounded
|
|
727
704
|
handed 19 skeletons labelled `3.8.99` a 4.2 runtime and every one of them posed as NaN
|
|
728
|
-
(row 7). Reading a file with *that generation's own* defaults is
|
|
729
|
-
|
|
705
|
+
(row 7). Reading a file with *that generation's own* defaults is not in this tool โ
|
|
706
|
+
re-export as 4.3, or transcribe by hand ([INGEST.md](INGEST.md) ยง2).
|
|
707
|
+
[GENERATIONS.md](https://github.com/firejune/rigc/blob/main/docs/GENERATIONS.md) is
|
|
708
|
+
the whole policy, and the editor migration it names.
|
|
730
709
|
|
|
731
710
|
๐ **Do not delete the `note`.** Both written specs carry one saying the file is
|
|
732
711
|
decompiled and naming the skeleton it came from. A decompiled spec is
|
|
@@ -770,12 +749,12 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
770
749
|
`ingest --art none` states sizes and names no image. So the size-only pair
|
|
771
750
|
`build --atlas-in` gates green is readable here through the same flag, and
|
|
772
751
|
without it the pair is refused by name at exit 2 rather than posed
|
|
773
|
-
(
|
|
752
|
+
(ยง5.1). โ ๏ธ `--profile`,
|
|
774
753
|
`--pack`, `--page-size`, `--padding` and `--copy-images` are `build`'s and are
|
|
775
754
|
not here: four of them decide what is *written*, and this command writes nothing.
|
|
776
755
|
|
|
777
756
|
๐ **What it will not measure: the texels of a page that is not its declared
|
|
778
|
-
size
|
|
757
|
+
size.** Under
|
|
779
758
|
`--atlas-in`, a page whose PNG is not the `size:` its atlas states is named once
|
|
780
759
|
at the top of the report, and the figures that would have been read off it โ an
|
|
781
760
|
authored mesh's `covers โฆ% of the art, reaching โฆpx past it`, a depth sheet's
|
|
@@ -785,7 +764,7 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
785
764
|
and the repair: ยง0.2. On a page that **does** declare a `scale:`, the overshoot
|
|
786
765
|
in that line is the drawing's pixels and the line names the texel it was taken
|
|
787
766
|
on (`โฆ measured on the page's texels at scale: 0.5 โ a texel is 2.00px of the
|
|
788
|
-
drawing
|
|
767
|
+
drawing`).
|
|
789
768
|
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
790
769
|
sections (bones, slots, attachments, constraints, animations, events). It
|
|
791
770
|
deliberately does not combine them into a score: a rig with the right skeleton
|
|
@@ -951,24 +930,12 @@ one A09 does compare.
|
|
|
951
930
|
**R1 โ A field is emitted when you declare it, and left out where the parser would
|
|
952
931
|
read the same value without it.** Omit a field and Spine's default stands; write it
|
|
953
932
|
and it reaches the file โ unless what you wrote **is** that default, in which case
|
|
954
|
-
the emitter leaves it out, the way the editor's own exporter does
|
|
955
|
-
[#716](https://github.com/firejune/rigc/issues/716)). Writing `x: 0` is still
|
|
933
|
+
the emitter leaves it out, the way the editor's own exporter does. Writing `x: 0` is still
|
|
956
934
|
legitimate, and your spec still says it: the spec is the record of what you wrote,
|
|
957
935
|
and the file is what the runtime reads. The two cannot disagree about a value,
|
|
958
936
|
because a key is left out only where the 4.3 parser loads the same `SkeletonData`
|
|
959
937
|
without it โ exact equality with the float the file would hold, so `x: 1e-45` is
|
|
960
|
-
written
|
|
961
|
-
|
|
962
|
-
โ ๏ธ This rule said *"not 'when it differs from the default'"* until #716, on two
|
|
963
|
-
grounds: that a rig may need to say `x: 0` out loud, and that deciding emission from
|
|
964
|
-
the value would make the file depend on arithmetic. Neither survived being measured.
|
|
965
|
-
What reads the file is the 4.3 parser, which reads the absent key as the same number,
|
|
966
|
-
and the editor, whose own export leaves the same key out โ so there is nobody to say
|
|
967
|
-
it out loud *to*; and the decision is an exact comparison with the parser's fallback,
|
|
968
|
-
which is not arithmetic in any sense a reader has to redo. What the
|
|
969
|
-
old rule cost was concrete: a rebuild of an editor export restated **2,338** keys the
|
|
970
|
-
export leaves to the parser, so `ingest โ build` could never give an editor's file
|
|
971
|
-
back.
|
|
938
|
+
written (ยง10.6c).
|
|
972
939
|
|
|
973
940
|
**R2 โ The compiler never invents a value.** No defaults guessed from the art, no
|
|
974
941
|
re-measured plates, no reasonable fallbacks. A missing number is a `CompileError`
|
|
@@ -999,8 +966,7 @@ two files put two regions in it and each attachment draws its own (ยง3.4.2), whi
|
|
|
999
966
|
two attachments naming the same file share the one region that file made. What
|
|
1000
967
|
cannot be reconciled is two *different* files whose basenames collide โ only one of
|
|
1001
968
|
them can be region `patch` โ so rigc refuses the build and names both paths rather
|
|
1002
|
-
than letting one of them silently draw the other's pixels
|
|
1003
|
-
([#555](https://github.com/firejune/rigc/issues/555)).
|
|
969
|
+
than letting one of them silently draw the other's pixels.
|
|
1004
970
|
|
|
1005
971
|
**R6 โ A key carries `ease` or `curve`, never both.** A named easing says "this
|
|
1006
972
|
shape, wherever it is used" and is the recommended path. `curve` is the escape
|
|
@@ -1032,19 +998,16 @@ reads best; the emit keys them the way the Spine editor does. This is the one pl
|
|
|
1032
998
|
reorders anything you wrote, and it is not cosmetic: a `slider`'s animation is a
|
|
1033
999
|
**name** in JSON and an **ordinal** in the format's binary half, so an editor that
|
|
1034
1000
|
re-sorts the object repoints every slider whose animation moved index โ silently,
|
|
1035
|
-
in a file that still parses and still gates green (ยง3.5.2
|
|
1036
|
-
[#535](https://github.com/firejune/rigc/issues/535)). Nothing else moves: each
|
|
1001
|
+
in a file that still parses and still gates green (ยง3.5.2). Nothing else moves: each
|
|
1037
1002
|
animation's own body is byte-identical either way, and every other collection is
|
|
1038
1003
|
emitted in the order you gave it.
|
|
1039
1004
|
|
|
1040
1005
|
โ ๏ธ **What that comparator is, measured rather than inferred.** Five name lists
|
|
1041
1006
|
went through a licensed 4.3.26 editor as JSON and came back as JSON, and the ten
|
|
1042
1007
|
answers are in the repository as
|
|
1043
|
-
`fixtures/editor-order/probe{1..5}.{in,out}.json
|
|
1044
|
-
([#728](https://github.com/firejune/rigc/issues/728)). Every row below is a clause
|
|
1008
|
+
`fixtures/editor-order/probe{1..5}.{in,out}.json`. Every row below is a clause
|
|
1045
1009
|
of the rule with the pair from those files that shows it โ nothing here is a
|
|
1046
|
-
guess
|
|
1047
|
-
page:
|
|
1010
|
+
guess:
|
|
1048
1011
|
|
|
1049
1012
|
| The editor | Shown by |
|
|
1050
1013
|
| --- | --- |
|
|
@@ -1059,14 +1022,13 @@ page:
|
|
|
1059
1022
|
๐ธ **One clause of the rule is not from those files.** Before comparing, the
|
|
1060
1023
|
comparator reads U+3000 IDEOGRAPHIC SPACE as a space and the full-width digits
|
|
1061
1024
|
U+FF10โU+FF19 as `0`โ`9`. No probe carried either; the fold was measured on a
|
|
1062
|
-
production skin's slot keys (
|
|
1063
|
-
|
|
1025
|
+
production skin's slot keys (ยง10.6b says what was measured and why it is those
|
|
1026
|
+
two classes and not NFKC), and
|
|
1064
1027
|
it applies here because R10, R11 and a skin's slot keys are one comparator. So
|
|
1065
1028
|
`shot๏ผ` sorts before `shot10`, and a pair an ideographic space decides is
|
|
1066
1029
|
ordered rather than refused as `separator`.
|
|
1067
1030
|
|
|
1068
|
-
โญ **A tie is not an ambiguity
|
|
1069
|
-
refusals.** Two names the comparator cannot separate come back in the order the
|
|
1031
|
+
โญ **A tie is not an ambiguity.** Two names the comparator cannot separate come back in the order the
|
|
1070
1032
|
file gave them, so the order rigc emits for such a pair is **your own declaration
|
|
1071
1033
|
order** and the editor keeps it. Nothing moves index, so there is nothing to
|
|
1072
1034
|
refuse: `Turn` beside `turn`, `turn01` beside `turn1`, `1turn` beside `turn`,
|
|
@@ -1086,30 +1048,18 @@ So what is left refused is short, and each row is a pair two readings of the
|
|
|
1086
1048
|
numbered series that crosses 9 โ 10 is keyed **1, 2, โฆ 9, 10, 11, 12**, which is
|
|
1087
1049
|
what the editor does with it โ and is not what a codepoint sort does.
|
|
1088
1050
|
|
|
1089
|
-
โ
**This rule was a quantifier over comparators until #728, and that is what
|
|
1090
|
-
changed.** rigc keyed `animations` codepoint-ascending until
|
|
1091
|
-
[#543](https://github.com/firejune/rigc/issues/543) and then emitted a member of
|
|
1092
|
-
the "natural, case-insensitive" family, refusing every pair the family could
|
|
1093
|
-
disagree about. Both refusals were sound and both over-refused by construction,
|
|
1094
|
-
because a quantifier stands in for a measurement: a name with a capital, an
|
|
1095
|
-
accent, a space or a folder in it was refused rather than emitted in the order the
|
|
1096
|
-
editor returns. Measuring the comparator moves no byte on any set the old rule
|
|
1097
|
-
accepted; it stops refusing the ones it did.
|
|
1098
|
-
|
|
1099
1051
|
**R11 โ The `skins` array is written with `default` first and the rest in exactly
|
|
1100
1052
|
the order R10 describes.** A skin is a **name** in the JSON half of the format and
|
|
1101
1053
|
an **ordinal** in the binary half โ `skins[readInt()]` for an attachment timeline,
|
|
1102
1054
|
`skins[skinIndex]` for a linked mesh โ so an editor that writes the array in
|
|
1103
1055
|
another order repoints every such reference, silently, in a file that still
|
|
1104
1056
|
parses. Measured: a rig built `default, zulu, mike, alpha` exported
|
|
1105
|
-
`default, alpha, mike, zulu
|
|
1106
|
-
([#541](https://github.com/firejune/rigc/issues/541)).
|
|
1057
|
+
`default, alpha, mike, zulu`.
|
|
1107
1058
|
|
|
1108
1059
|
โ ๏ธ **It is one comparator, and that is measured too.** Two of the five round
|
|
1109
1060
|
trips carried one name list as **both** collections and both came back in one
|
|
1110
|
-
order,
|
|
1111
|
-
|
|
1112
|
-
refused as skin names. Both build now. `default` is pinned rather than sorted, on
|
|
1061
|
+
order, so `Zulu` beside `mike` and `mike10` beside `mike2` build as skin names
|
|
1062
|
+
exactly as they do as animation names. `default` is pinned rather than sorted, on
|
|
1113
1063
|
names the same comparator puts ahead of it โ `default` before `2`, `default`
|
|
1114
1064
|
before `A` โ so renaming a skin away from `default` makes it an ordinary name that
|
|
1115
1065
|
sorts like one.
|
|
@@ -1118,16 +1068,14 @@ sorts like one.
|
|
|
1118
1068
|
the `default` skin may not share a placeholder with a named skin.** rigc derives
|
|
1119
1069
|
no name. A placeholder several skins fill is emitted under that placeholder in
|
|
1120
1070
|
each skin, carrying the `name` each entry states and none where it states none โ
|
|
1121
|
-
which is the shape the editor itself exports (ยง3.4.2).
|
|
1122
|
-
[#796](https://github.com/firejune/rigc/issues/796) rigc composed
|
|
1123
|
-
`"<skin>/<placeholder>"` there, which renamed every such attachment at runtime.
|
|
1071
|
+
which is the shape the editor itself exports (ยง3.4.2).
|
|
1124
1072
|
What you *do* author is where the shared art lives: a
|
|
1125
1073
|
placeholder the `default` skin shares with a named skin is a **compile error**,
|
|
1126
|
-
because the Spine editor has no representation for it in either spelling (
|
|
1127
|
-
|
|
1074
|
+
because the Spine editor has no representation for it in either spelling (measured
|
|
1075
|
+
on 4.3.26 โ named, the export re-keys it and the default skin draws
|
|
1128
1076
|
nothing; unnamed, the import is refused). Put the shared entry in a named skin โ
|
|
1129
1077
|
`base` โ and every filler is a named skin. A placeholder only one skin fills is
|
|
1130
|
-
emitted
|
|
1078
|
+
emitted as written, in the default skin or anywhere else.
|
|
1131
1079
|
|
|
1132
1080
|
---
|
|
1133
1081
|
|
|
@@ -1141,8 +1089,7 @@ payloads in this guide are written to illustrate a field, never copied out of a
|
|
|
1141
1089
|
reference export โ an example lifted from one would be handing an authoring agent an
|
|
1142
1090
|
answer to the rung it is standing on, which is the rule ยง10.6 states and the honesty
|
|
1143
1091
|
rule in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) turns on. If a snippet here matches a reference file,
|
|
1144
|
-
that is a defect in this guide: report it.
|
|
1145
|
-
is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
1092
|
+
that is a defect in this guide: report it.
|
|
1146
1093
|
|
|
1147
1094
|
### 3.1 `skeleton` โ the header
|
|
1148
1095
|
|
|
@@ -1152,53 +1099,49 @@ is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
|
1152
1099
|
| `width`, `height` | setup-pose bounding box size, **or both `null` for "this skeleton declares no stage"** | falls back to the manifest's crop; **with neither the number nor the `null`, the compile fails** |
|
|
1153
1100
|
| `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
|
|
1154
1101
|
| `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
|
|
1155
|
-
| `images` | where the editor's import looks for the part PNGs, as a path from the skeleton file | **written for you**: under `--copy-images` the `--out` directory itself, spelled `../<its basename>/` (a literal `./` is dropped by the editor on import; a named directory is kept and every part is found โ measured on 4.3.23); otherwise the relative path from `--out` to the one directory the spec names every part PNG in (the rig's images directory, or the manifest's plates). A declared value is carried through verbatim โ and overridden by `--copy-images`, which moved the parts. Parts spread over several directories have no single true path, so nothing is written
|
|
1156
|
-
| `audio` | nonessential: where the editor looks for the skeleton's audio files, as a path from the skeleton file โ a string, or `null` for none | **not written unless you state it.** rigc has no audio to point at, so this is a value a spec states or does not; stated, it is carried verbatim, `null` included, because `null` is what an editor export writes when no audio folder is set (all twelve under `examples/` do) and `ingest` carries it from there
|
|
1102
|
+
| `images` | where the editor's import looks for the part PNGs, as a path from the skeleton file | **written for you**: under `--copy-images` the `--out` directory itself, spelled `../<its basename>/` (a literal `./` is dropped by the editor on import; a named directory is kept and every part is found โ measured on 4.3.23); otherwise the relative path from `--out` to the one directory the spec names every part PNG in (the rig's images directory, or the manifest's plates). A declared value is carried through verbatim โ and overridden by `--copy-images`, which moved the parts. Parts spread over several directories have no single true path, so nothing is written |
|
|
1103
|
+
| `audio` | nonessential: where the editor looks for the skeleton's audio files, as a path from the skeleton file โ a string, or `null` for none | **not written unless you state it.** rigc has no audio to point at, so this is a value a spec states or does not; stated, it is carried verbatim, `null` included, because `null` is what an editor export writes when no audio folder is set (all twelve under `examples/` do) and `ingest` carries it from there. Anything but a string or `null` is a compile error naming the value |
|
|
1157
1104
|
|
|
1158
1105
|
`spine` and `hash` are not yours to write: rigc emits its own version label
|
|
1159
1106
|
(`A16` re-checks it is on the 4.3 line) and inventing a hash would claim an export
|
|
1160
1107
|
this file did not come from. The editor's import then warns `Data version 4.3.13
|
|
1161
1108
|
does not match Spine version 4.3.23. The Spine version should match the version
|
|
1162
1109
|
that exported the data file.` and imports anyway โ the label is the runtime rigc
|
|
1163
|
-
links, not the editor that will open the file, and the warning is harmless
|
|
1164
|
-
(issue #370).
|
|
1110
|
+
links, not the editor that will open the file, and the warning is harmless.
|
|
1165
1111
|
|
|
1166
1112
|
`width`/`height` are what `A14` and `A19` measure against, so a guessed stage is a
|
|
1167
1113
|
gate measuring against a number nobody wrote down.
|
|
1168
1114
|
|
|
1169
1115
|
โญ **A skeleton may declare no stage, and saying so is not the same as saying
|
|
1170
|
-
nothing
|
|
1116
|
+
nothing.** Write the pair as `null`:
|
|
1171
1117
|
|
|
1172
1118
|
```json
|
|
1173
1119
|
"skeleton": { "width": null, "height": null }
|
|
1174
1120
|
```
|
|
1175
1121
|
|
|
1176
1122
|
and the emitted header carries **none** of `x`/`y`/`width`/`height` โ which is
|
|
1177
|
-
what an export of a skeleton whose stage was never set looks like
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
nothing"), so nothing new is introduced here but a third value of a field that
|
|
1181
|
-
already existed.
|
|
1123
|
+
what an export of a skeleton whose stage was never set looks like. `null` is this
|
|
1124
|
+
spec's spelling for a stated absence wherever it has one (`slots[].attachment` is
|
|
1125
|
+
`null` for "show nothing").
|
|
1182
1126
|
|
|
1183
1127
|
Three readings stay apart, and the middle one is the point of the other two:
|
|
1184
1128
|
|
|
1185
1129
|
| What the spec says | What happens |
|
|
1186
1130
|
| --- | --- |
|
|
1187
|
-
| a number for each | the stage
|
|
1131
|
+
| a number for each | the stage; a manifest `crop` is the fallback |
|
|
1188
1132
|
| `"width": null, "height": null` | builds, and emits a header with no stage at all โ and this **beats** a manifest's `crop`, because a rig spec is where a claim about the skeleton is made |
|
|
1189
|
-
| neither | **refused
|
|
1133
|
+
| neither | **refused**: `no stage size: โฆ` |
|
|
1190
1134
|
| one `null`, one number | refused โ a stage has both extents or neither, and which half was meant is not derivable |
|
|
1191
1135
|
| the pair `null` **and** an `x` or `y` | refused โ an origin for a box that is not there |
|
|
1192
1136
|
|
|
1193
|
-
What each tool does without one โ
|
|
1194
|
-
|
|
1195
|
-
number where the box would be:
|
|
1137
|
+
What each tool does without one โ not one of them puts a number where the box
|
|
1138
|
+
would be:
|
|
1196
1139
|
|
|
1197
1140
|
| Reader | With a stage | Without one |
|
|
1198
1141
|
| --- | --- | --- |
|
|
1199
1142
|
| `build` | emits `x`/`y`/`width`/`height` | emits none of them |
|
|
1200
1143
|
| `A14_NO_FULL_FRAME_MESH` | fails a mesh as big as the stage | **SKIP**, by name |
|
|
1201
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | exempts the base plate a cut manifest names โ the part whose window is the crop โ and, on a build that names none, the one image that covers the stage | a manifest build exempts the plate it names, exactly as with one
|
|
1144
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | exempts the base plate a cut manifest names โ the part whose window is the crop โ and, on a build that names none, the one image that covers the stage | a manifest build exempts the plate it names, exactly as with one. A rig-spec build names no base plate, so an opaque part is refused with *"this skeleton declares no stage size to measure one against, and the build names no base plate"* and the two ways to decide it: a `skeleton` stage the plate covers, or *"build from a cut manifest, whose base plate is the part whose window is the crop"* |
|
|
1202
1145
|
| `diff` | `stage_present` and `stage_box` | `stage_present` 1/1 when neither side declares one (agreement), `stage_box` 0/0 |
|
|
1203
1146
|
| `explain` | `stage W x H` | `stage none declared` |
|
|
1204
1147
|
| `render` | frames the posed extent of every animation | the same frames, plus a line saying the viewport is the posed extent and no stage |
|
|
@@ -1210,7 +1153,7 @@ number where the box would be:
|
|
|
1210
1153
|
โ ๏ธ A stage-less skeleton is **unmeasured, not certified**: `A14_NO_FULL_FRAME_MESH`
|
|
1211
1154
|
reports **SKIP** on one, because there is no full frame for a mesh to span. And
|
|
1212
1155
|
`rigc diff` reports it โ `skeleton.stage_present` and `skeleton.stage_box`, in the
|
|
1213
|
-
header block at the top of the report โ so a stage somebody invented
|
|
1156
|
+
header block at the top of the report โ so a stage somebody invented reads
|
|
1214
1157
|
below 1.000 against a source that has none.
|
|
1215
1158
|
|
|
1216
1159
|
โญ **A stage at `0,0` is not a stage-less one, and an editor export spells it by
|
|
@@ -1242,7 +1185,7 @@ it would simply be a second root.
|
|
|
1242
1185
|
| `rotation` | degrees, counter-clockwise, y **up** | `0` |
|
|
1243
1186
|
| `scaleX`, `scaleY` | | `1` |
|
|
1244
1187
|
| `shearX`, `shearY` | | `0` |
|
|
1245
|
-
| `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 โ
|
|
1188
|
+
| `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 โ the runtime would load it as **no mode at all**. An animation can change it over time: the `inherit` track (ยง4.4) | `normal` |
|
|
1246
1189
|
| `skin` | `BoneData.skinRequired` | `false` |
|
|
1247
1190
|
| `color` | `rrggbbaa`, editor affordance | โ |
|
|
1248
1191
|
| `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 | โ |
|
|
@@ -1275,18 +1218,13 @@ Declaring a slot no cut fills is therefore normal โ it fixes where that slot s
|
|
|
1275
1218
|
whether or not this cut has art for it โ and transcribing a foreign skeleton that
|
|
1276
1219
|
carries an empty slot reproduces it exactly.
|
|
1277
1220
|
|
|
1278
|
-
โ ๏ธ **
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
`drawOrder` key's offsets are counted against and what an index-keyed consumer
|
|
1282
|
-
splits on. Two production exports declaring 53 and 61 slots built green at 51 and 57
|
|
1283
|
-
and read 0.962 and 0.934 under `diff` against the file they were transcribed from.
|
|
1284
|
-
If you have a rig that leaned on the drop, the emitted array simply grows; nothing
|
|
1285
|
-
else about it moves.
|
|
1221
|
+
โ ๏ธ **Keeping the empty slot is what keeps the index.** A `drawOrder` key's offsets
|
|
1222
|
+
are counted against the slots array, and so is an index-keyed consumer, so a slot
|
|
1223
|
+
dropped from it would move every slot below it up one place.
|
|
1286
1224
|
|
|
1287
1225
|
๐ซ **Naming an attachment on a slot nothing fills is refused by name**: `the setup
|
|
1288
1226
|
pose shows attachment "x" on slot "y", which no skin and no manifest part fills`.
|
|
1289
|
-
That is
|
|
1227
|
+
That is half-finished wiring-up โ the slot is emitted empty
|
|
1290
1228
|
and the name resolves to nothing, so either give the slot an attachment or state the
|
|
1291
1229
|
setup pose as `null`.
|
|
1292
1230
|
|
|
@@ -1299,10 +1237,10 @@ called `default`.)
|
|
|
1299
1237
|
|
|
1300
1238
|
A skin can also say which bones and constraints it **switches on**, and that needs
|
|
1301
1239
|
one more level, so a skin entry has a second spelling โ see ยง3.4.1. The short one
|
|
1302
|
-
above is
|
|
1240
|
+
above is what almost every rig wants.
|
|
1303
1241
|
|
|
1304
1242
|
๐ธ **`default` is a name, not a requirement, and rigc writes one exactly when the
|
|
1305
|
-
spec has one
|
|
1243
|
+
spec has one.** A rig may put
|
|
1306
1244
|
every attachment in named skins and declare no `default` at all, which is what an
|
|
1307
1245
|
editor export of a multi-skin character gives back: the 4.3.26 editor took a build
|
|
1308
1246
|
declaring `skins: [default, alt, base]` with `default` **empty** and exported
|
|
@@ -1311,9 +1249,7 @@ key โ `"default": {}` included, which is written back empty โ or when a mani
|
|
|
1311
1249
|
part files its states under it, and **none otherwise**. spine-core loads both
|
|
1312
1250
|
spellings alike (`SkeletonData.defaultSkin` is null or an empty skin, and neither
|
|
1313
1251
|
fills a slot), and the binary format has no way to hold an empty default skin at
|
|
1314
|
-
all.
|
|
1315
|
-
had one or not, so a rebuild of such an export read `attachments.skins 1/2`
|
|
1316
|
-
against it. Two things change for a rig with no default skin, and both are
|
|
1252
|
+
all. Two things differ for a rig with no default skin, and both are
|
|
1317
1253
|
named: a linked mesh that states no `skin` looks for its source in the default
|
|
1318
1254
|
skin and is refused (the error table in ยง5.1), and `rigc explain` says the
|
|
1319
1255
|
skeleton declares no default skin above its slot list instead of printing
|
|
@@ -1329,11 +1265,7 @@ with **no pages**, and rigc writes `skeleton.atlas` as an **empty file** (zero
|
|
|
1329
1265
|
bytes). Four rules then report SKIP by name rather than a pass, because a page is
|
|
1330
1266
|
their whole subject: `A07_ATLAS_TEXT_SHAPE`, `A06_ATLAS_PAGE_SIZE_MATCHES_PNG`,
|
|
1331
1267
|
`A17_ATLAS_PAGE_FILES_EXIST` and โ under `spine-html` โ
|
|
1332
|
-
`A19_OVERLAY_PNGS_HAVE_ALPHA` and `A27_REGION_NAME_MATCHES_PAGE_FILENAME`.
|
|
1333
|
-
[#608](https://github.com/firejune/rigc/issues/608) the same compile wrote one
|
|
1334
|
-
newline instead and `A07` refused it with two findings, so a hit-box skeleton โ a
|
|
1335
|
-
correct rig, whose geometry `A33_VERTEX_ATTACHMENT_GEOMETRY` passes โ could not be
|
|
1336
|
-
built at all.
|
|
1268
|
+
`A19_OVERLAY_PNGS_HAVE_ALPHA` and `A27_REGION_NAME_MATCHES_PAGE_FILENAME`.
|
|
1337
1269
|
|
|
1338
1270
|
โ ๏ธ **This is not the case where an attachment WANTS a region.** A region or mesh
|
|
1339
1271
|
attachment that states `width`/`height` and names no `image` still resolves a
|
|
@@ -1349,8 +1281,7 @@ there is nothing to draw`. That is the honest division โ the rig is valid Spin
|
|
|
1349
1281
|
data, and there is no picture of it. `rigc check` says the same thing one step
|
|
1350
1282
|
on, since it has no frames to compare; `tools/editor_roundtrip.ts` quotes both
|
|
1351
1283
|
renderers and reports its step 5 as a **SKIP** naming that, and the round trip
|
|
1352
|
-
comes back green on the strength of `validate` and `diff
|
|
1353
|
-
([#621](https://github.com/firejune/rigc/issues/621)). A skin only **one** side
|
|
1284
|
+
comes back green on the strength of `validate` and `diff`. A skin only **one** side
|
|
1354
1285
|
can draw is the other case entirely, and stays red.
|
|
1355
1286
|
|
|
1356
1287
|
**Region attachment** ([Spine: region attachments](http://esotericsoftware.com/spine-regions)),
|
|
@@ -1381,15 +1312,10 @@ region does: stated wins, otherwise the PNG's basename when that differs from th
|
|
|
1381
1312
|
attachment's name โ its `name`, else the placeholder โ otherwise nothing. The parser reads `path` off both with the same line
|
|
1382
1313
|
(`getValue(map, "path", name)`, `SkeletonJson.ts:541` and `:570`), and `path`
|
|
1383
1314
|
defaults to the attachment's **name** rather than to the placeholder โ so a mesh
|
|
1384
|
-
with `image: hair_short.png` under a placeholder called `hair`
|
|
1385
|
-
`hair`, which no atlas has.
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
`A00_ROUNDTRIP_PARSE: threw: Region not found in atlas: hair` โ which is the
|
|
1389
|
-
loader's sentence and all the report had. Since
|
|
1390
|
-
[#589](https://github.com/firejune/rigc/issues/589) the same miss is named by
|
|
1391
|
-
`A08_REGION_NAMES_MATCH_ATTACHMENTS`, with the skin, the slot, the placeholder
|
|
1392
|
-
and the attachment's own name beside the path.
|
|
1315
|
+
with `image: hair_short.png` under a placeholder called `hair` and no derived
|
|
1316
|
+
`path` would resolve the region `hair`, which no atlas has. A region that is
|
|
1317
|
+
missing anyway is named by `A08_REGION_NAMES_MATCH_ATTACHMENTS`, with the skin,
|
|
1318
|
+
the slot, the placeholder and the attachment's own name beside the path.
|
|
1393
1319
|
|
|
1394
1320
|
Where each vertex sits on the **art**:
|
|
1395
1321
|
|
|
@@ -1404,8 +1330,7 @@ of the **top-left** quadrant's colour and not one pixel of any other, and the sa
|
|
|
1404
1330
|
quad given `0.5 โ 1` as 3,120 px of the **bottom-right**. Beside it, the runtime's own
|
|
1405
1331
|
`MeshAttachment.computeUVs`, handed the emitted atlas region, put `(0, 0)` at page
|
|
1406
1332
|
pixel `(0.00, 0.00)` and `(1, 1)` at `(64.00, 48.00)` โ the region's own two
|
|
1407
|
-
corners, in the page's y-down pixels.
|
|
1408
|
-
above against that second measurement.
|
|
1333
|
+
corners, in the page's y-down pixels.
|
|
1409
1334
|
|
|
1410
1335
|
Geometry comes in one of two fields:
|
|
1411
1336
|
|
|
@@ -1424,19 +1349,19 @@ Geometry comes in one of two fields:
|
|
|
1424
1349
|
|
|
1425
1350
|
โญ **Weights bind bones by NAME, like everything else in a rig spec.** A bone's
|
|
1426
1351
|
`parent`, a slot's `bone`, an ik constraint's `bones` and `target` and a draw-order
|
|
1427
|
-
key's `slot` all resolve by name and refuse a miss by name, and mesh weights
|
|
1352
|
+
key's `slot` all resolve by name and refuse a miss by name, and mesh weights do
|
|
1428
1353
|
too: an unknown name is a `CompileError` that says which vertex and which name, and
|
|
1429
1354
|
the compiler resolves the names to indices on emit. So inserting a bone renumbers
|
|
1430
1355
|
the emitted array and rebinds nothing.
|
|
1431
1356
|
|
|
1432
|
-
๐จ **The index form is
|
|
1357
|
+
๐จ **The index form is reachable, and it costs silence.** Spine's own
|
|
1433
1358
|
encoding is a flat run โ `boneCount, (boneIndex, bindX, bindY, weight) ร n, โฆ` โ
|
|
1434
1359
|
where `boneIndex` is a position in the **emitted** bone array, a list the rig spec
|
|
1435
1360
|
never writes and cannot see. Put one bone ahead of the meshes and every vertex
|
|
1436
1361
|
rebinds: the file still loads, every index is still in range, every vertex's weights
|
|
1437
1362
|
still sum to 1, and `A04`, `A20` and `diff` are all quiet, because an index has no
|
|
1438
1363
|
name to be wrong. (Measured, on the rung 6 transcription: union MAE 3.30 โ 15.09,
|
|
1439
|
-
worst mesh-slot drift 0.09 px โ 9.8 px, with a green gate throughout.
|
|
1364
|
+
worst mesh-slot drift 0.09 px โ 9.8 px, with a green gate throughout.)
|
|
1440
1365
|
rigc therefore refuses a weighted `vertices` run unless the attachment says
|
|
1441
1366
|
`"boneIndexing": "raw"` out loud โ an opt-in, because what is being opted into is
|
|
1442
1367
|
the silence.
|
|
@@ -1465,11 +1390,9 @@ these messages prints the outline walk, because the walk is the fix:
|
|
|
1465
1390
|
| outline vertices that are first but zigzag | `hull vertices must trace the outline in order; the triangles' outline runs 0 โ 1 โ 3 โ 5 โ โฆ โ 4 โ 2, so vertex 3 has to follow vertex 1 in the list, and vertex 2 does. Renumber the vertices along that walk` |
|
|
1466
1391
|
| triangles with no single outline | `the triangles do not tile the outline: 25 vertices with a 16-vertex outline tile as 32 triangles and there are 33` โ an unused vertex or a doubled interior triangle; or `the triangles' outline is not one closed loop: vertex 4 has 4 boundary edges` โ a pinch, a hole, or a doubled triangle on the perimeter |
|
|
1467
1392
|
|
|
1468
|
-
๐จ **A row-major grid is the case this catches
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
no `hull` can describe that list โ and `hull: 0`, which rigc used to write there,
|
|
1472
|
-
is what the editor repairs on import by making **every** vertex a hull vertex in
|
|
1393
|
+
๐จ **A row-major grid is the case this catches**: a 5ร5 grid's perimeter is 16 of
|
|
1394
|
+
its 25 vertices, interleaved with the interior, so no `hull` can describe that
|
|
1395
|
+
list โ and `hull: 0` is what the editor repairs on import by making **every** vertex a hull vertex in
|
|
1473
1396
|
list order and saying so in a WARNING. A two-column strip is the same trap with
|
|
1474
1397
|
every vertex on the outline: the list zigzags across, the outline runs down one
|
|
1475
1398
|
side and up the other, and the hull it would declare self-intersects. The fix is
|
|
@@ -1489,17 +1412,14 @@ export carries the edges somebody drew โ passes through verbatim.
|
|
|
1489
1412
|
`width`/`height` are the size of the image the mesh is drawn on. Stated wins;
|
|
1490
1413
|
omitted, they are the named PNG's measured size (R5), the same number a region
|
|
1491
1414
|
reads. With neither a size nor an `image`: `a mesh needs width and height โ give
|
|
1492
|
-
them, or give an "image" and rigc will measure the PNG`.
|
|
1493
|
-
before #368 โ a size no spec stated.
|
|
1415
|
+
them, or give an "image" and rigc will measure the PNG`.
|
|
1494
1416
|
|
|
1495
1417
|
โ ๏ธ **Authored geometry is not a rigc generator, and the gate says so.** rigc built
|
|
1496
1418
|
neither its rim nor its rows, so it gets to assume nothing about its topology:
|
|
1497
1419
|
`A21_MESH_RIM_PINNED` and `A28_RIBBON_ROWS_SHARE_WEIGHTS` **SKIP** on an authored
|
|
1498
1420
|
mesh with that as the reason, and `A20`'s two generator-policy branches (a mesh here
|
|
1499
1421
|
is weighted; a generated mesh binds only bones that move it) do not apply to one.
|
|
1500
|
-
`A20`'s coherence rules โ weights present, in range, summing to 1 โ still do.
|
|
1501
|
-
#44; before it was fixed, `A21` reported 40 failures on a correct 40-vertex editor
|
|
1502
|
-
mesh because an absent `meshKinds` entry read as `ring`.
|
|
1422
|
+
`A20`'s coherence rules โ weights present, in range, summing to 1 โ still do.
|
|
1503
1423
|
|
|
1504
1424
|
โญ **Coverage is the exception, and it is reported for an authored mesh too.** Those
|
|
1505
1425
|
rules are about a mesh's **structure** โ where its rim is, how its rows pair โ
|
|
@@ -1524,15 +1444,13 @@ meant to bend a core while its edges stretch โ so the figure informs and the
|
|
|
1524
1444
|
decision stays with the author. A mesh with no `image` reports nothing, because
|
|
1525
1445
|
there is nothing to measure it against.
|
|
1526
1446
|
|
|
1527
|
-
**
|
|
1528
|
-
|
|
1529
|
-
*on*
|
|
1530
|
-
|
|
1531
|
-
assertion passed (issue #277). That is why the command above prints 100.00%
|
|
1532
|
-
rather than the figure it was filed over:
|
|
1447
|
+
**Why the figure is worth reading.** The ball is a centre vertex plus 8 rim
|
|
1448
|
+
vertices, and an octagon's sides pass `R ยท cos(ฯ/8)` from its centre โ so rim
|
|
1449
|
+
vertices placed *on* a round silhouette leave the ink outline between the spokes
|
|
1450
|
+
undrawn, and every assertion passes.
|
|
1533
1451
|
[`gallery/squash`](https://github.com/firejune/rigc/tree/main/gallery/squash)'s
|
|
1534
1452
|
README carries the inradius arithmetic, both coverage readings, and the rim move
|
|
1535
|
-
that
|
|
1453
|
+
that fixes it.
|
|
1536
1454
|
|
|
1537
1455
|
**Linked mesh** ([Spine: linked meshes](http://esotericsoftware.com/spine-meshes)) โ
|
|
1538
1456
|
a mesh that draws **another mesh's geometry** with **its own art**. It is the type
|
|
@@ -1568,7 +1486,7 @@ skeleton rigc did **not** write, where the compiler never sees the spec:
|
|
|
1568
1486
|
`A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` (ยง5.2) names the attachment, the
|
|
1569
1487
|
keys and whose geometry is drawn instead, and `ingest` reports one as
|
|
1570
1488
|
`ATTACHMENT_LINK_GEOMETRY` before dropping it
|
|
1571
|
-
([INGEST ยง2.0](INGEST.md))
|
|
1489
|
+
([INGEST ยง2.0](INGEST.md)).
|
|
1572
1490
|
|
|
1573
1491
|
๐ซ **A chain is refused, and so is a link to itself.** A `source` that names
|
|
1574
1492
|
another linked mesh resolves in the order the file was read: measured through
|
|
@@ -1669,7 +1587,7 @@ other vertex attachment.
|
|
|
1669
1587
|
| `name`, `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | as on a bounding box โ except that these vertices are knots **and** their handles, which the count rule below is about |
|
|
1670
1588
|
| `closed` | default **false**. True joins the last knot back to the first |
|
|
1671
1589
|
| `constantSpeed` | default **true** โ note the direction. Leaving it out asks for the expensive-and-correct traversal, in which the runtime re-measures the path every frame and `lengths` is never read. `false` makes the runtime trust the emitted `lengths` instead: cheaper, exact only while the path holds its setup shape, and the reason a deformed path wants the default |
|
|
1672
|
-
| `lengths` | **stated or measured
|
|
1590
|
+
| `lengths` | **stated or measured**. The cumulative length at the end of each curve, and exactly **`vertexCount / 3`** entries on an open path and a closed one alike โ the parser's own allocation, which on an open path is one more than its curves: the last entry is the wrap-around curve's cumulative, which the editor writes and nothing reads. **Stated**, it is emitted as stated (float32, like every number in the file) โ which is what `ingest` writes from an export, and the only way to carry the editor's number, because the editor measures the pose the first update gives the path constraint, *constraints applied*, and rigc does not pose. A stated array of the wrong count is refused with both counts, `"lengths" has N entry(ies) where the parser sizes M`, and a non-finite entry or one below its predecessor by index. **Left out**, rigc measures it off the geometry: weighted vertices through their bones' setup world transforms, scale, shear and `inherit` included, on the **unconstrained** setup pose โ so a path whose bones a constraint moves at rest gets a different figure from the editor's. Only `constantSpeed: false` reads the field at all. What the numbers are โ and why *arc length* is the wrong name for them โ is ยง10.6 |
|
|
1673
1591
|
|
|
1674
1592
|
๐จ **`vertexCount` counts knots AND handles, and it has to be a multiple of 3.**
|
|
1675
1593
|
The parser hands `vertexCount << 1` to `readVertices` and then walks the result in
|
|
@@ -1710,13 +1628,12 @@ mesh counts against that budget, a rig that declares none has a budget of
|
|
|
1710
1628
|
beside it: without it `A13_MESH_BUDGET` has nothing to measure against and SKIPs,
|
|
1711
1629
|
and the `MESH` report line has no budget to print. Authored geometry is exempt in
|
|
1712
1630
|
the other direction and for the same reason โ rigc did not draw it, so leaving it
|
|
1713
|
-
unmeasured is the author's call
|
|
1631
|
+
unmeasured is the author's call.
|
|
1714
1632
|
|
|
1715
1633
|
โญ **The no-manifest path centres the part window on its own slot bone.** There is
|
|
1716
1634
|
no crop to flip against, so `size` (or, for a contour, the PNG's own size) is
|
|
1717
1635
|
placed with its centre on the bone the slot names โ which is also exactly where a
|
|
1718
|
-
plain region attachment with no `x`/`y` would have drawn it.
|
|
1719
|
-
ribbon and a moved bone by `CT05` in the selftest (issue #1).
|
|
1636
|
+
plain region attachment with no `x`/`y` would have drawn it.
|
|
1720
1637
|
|
|
1721
1638
|
#### `ring` โ an aperture that opens inside a pinned seam
|
|
1722
1639
|
|
|
@@ -1770,13 +1687,9 @@ the centre line all 8 take more weight from the grip on their own side. Splittin
|
|
|
1770
1687
|
**moves** authority rather than adding it: every vertex gives its controls the
|
|
1771
1688
|
same total with one grip, two or three.
|
|
1772
1689
|
|
|
1773
|
-
โ ๏ธ **
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
`MESH` line, the bone list and nothing else โ and the gate was green, because a
|
|
1777
|
-
bone no vertex binds is in no weight, no sum and no index. `A20_MESH_WEIGHTS_COHERENT`
|
|
1778
|
-
now names it, so a ring that declares a grip and does not use it is a failure
|
|
1779
|
-
rather than a quiet stiffness.
|
|
1690
|
+
โ ๏ธ **A grip no vertex binds is a failure, not a quiet stiffness.** A bone no
|
|
1691
|
+
vertex binds is in no weight, no sum and no index, so nothing else would notice
|
|
1692
|
+
it; `A20_MESH_WEIGHTS_COHERENT` names it.
|
|
1780
1693
|
|
|
1781
1694
|
**Stated limits, each a named refusal rather than a mesh that loads wrong:**
|
|
1782
1695
|
|
|
@@ -1853,12 +1766,12 @@ detail of the attachment.
|
|
|
1853
1766
|
โญ **Both keys are the fragment.** Drop the two of them into ยง1.1's minimal rig in
|
|
1854
1767
|
place of its `skins`, rename its `box` bone and slot to `cape`, put a
|
|
1855
1768
|
transparent-margined `cape.png` in `images/`, and it compiles โ `invariants`
|
|
1856
|
-
included, because without it the generator is refused
|
|
1769
|
+
included, because without it the generator is refused. The
|
|
1857
1770
|
`meshTriangles` figure is invented; pick the one your renderer can afford.
|
|
1858
1771
|
|
|
1859
1772
|
| Field | Meaning |
|
|
1860
1773
|
| --- | --- |
|
|
1861
|
-
| `tolerance` | **required.** Douglas-Peucker tolerance, in the drawing's pixels โ on a packed page that declares a `scale:`, applied as `tolerance ร scale` of its texels (ยง0.2
|
|
1774
|
+
| `tolerance` | **required.** Douglas-Peucker tolerance, in the drawing's pixels โ on a packed page that declares a `scale:`, applied as `tolerance ร scale` of its texels (ยง0.2). Bigger spends fewer vertices and cuts more corners |
|
|
1862
1775
|
| `margin` | how far the outline is pushed out past the traced silhouette, in the drawing's pixels (`margin ร scale` texels on a `scale:` page). Default `1` |
|
|
1863
1776
|
| `maxVertices` | refuse rather than emit more outline vertices than this โ judged on the outline the two distances above ask for, so a finer `scale:` page of the same art needs no bigger budget. Default `64` |
|
|
1864
1777
|
| `alpha` | the alpha at or above which a pixel counts as art, `1`..`255`. Default `1` โ any pixel that is not fully transparent |
|
|
@@ -1880,9 +1793,7 @@ against the mask it came from**. `build` and `explain` print what it measured:
|
|
|
1880
1793
|
```
|
|
1881
1794
|
|
|
1882
1795
|
The budget in that line is the rig's `invariants.meshTriangles` โ `(no budget
|
|
1883
|
-
declared)` when it declares none, which is the same distinction `A13` SKIPs on.
|
|
1884
|
-
used to be the literal `80` whatever the rig said, so the line an author reads and
|
|
1885
|
-
the assertion that measures could print two different numbers (issue #275). A part
|
|
1796
|
+
declared)` when it declares none, which is the same distinction `A13` SKIPs on. A part
|
|
1886
1797
|
whose outline encloses a hole says so too, because nothing else in the output
|
|
1887
1798
|
moves when one appears:
|
|
1888
1799
|
|
|
@@ -1916,8 +1827,7 @@ That last one is the guarantee: **the emitted triangles cover at least 99.5% of
|
|
|
1916
1827
|
the art**, measured by rasterising them back over the mask, and a build that would
|
|
1917
1828
|
clip the art is refused rather than shipped. It is not 100% because a
|
|
1918
1829
|
simplification that could never cut a corner would not be one โ the figure a
|
|
1919
|
-
given part actually measures is in the `build`/`explain` line above
|
|
1920
|
-
selftest's fixture measures 100.000%.
|
|
1830
|
+
given part actually measures is in the `build`/`explain` line above.
|
|
1921
1831
|
|
|
1922
1832
|
Self-intersection is refused; **holes are not cut out**; and nothing here does
|
|
1923
1833
|
interior/Steiner points, so a contour mesh bends only where its outline has
|
|
@@ -1947,11 +1857,8 @@ attachment's own `image`, and every vertex is pinned to the slot bone at weight
|
|
|
1947
1857
|
|
|
1948
1858
|
โญ **Positions, because [FACE ยง4.1](FACE.md) places columns where the drawing
|
|
1949
1859
|
needs them.** The five above are `gallery/portrait`'s own โ dense at the
|
|
1950
|
-
silhouette, sparse across the middle, and not reaching the window edge
|
|
1951
|
-
|
|
1952
|
-
it replaces, and it does replace it: the selftest builds that exact mesh from
|
|
1953
|
-
those five numbers and requires it to come out identical to the 25 vertex pairs
|
|
1954
|
-
and 32 triangles the example shipped by hand.
|
|
1860
|
+
silhouette, sparse across the middle, and not reaching the window edge โ a
|
|
1861
|
+
layout an even division cannot state.
|
|
1955
1862
|
|
|
1956
1863
|
๐จ **The reason to generate this at all is the hull.** Spine's `hull` is a
|
|
1957
1864
|
**count** โ the first `hull` entries of the vertex list are the outline โ so the
|
|
@@ -1993,9 +1900,9 @@ off a greyscale sheet in the part's own pixel grid:
|
|
|
1993
1900
|
|
|
1994
1901
|
| Field | Meaning |
|
|
1995
1902
|
| --- | --- |
|
|
1996
|
-
| `image` | **required.** The sheet, relative to the rig's `images` directory, and the **same pixel size as this attachment's `image`**. It is not packed into the atlas โ it is a measurement rigc reads at compile time, not art anything draws. Under `--atlas-in` that is still the drawing's size, on a page that declares a `scale:` too: the sheet is read at each vertex's texel position over the scale the atlas states, so one sheet serves the loose parts and every pack of them
|
|
1903
|
+
| `image` | **required.** The sheet, relative to the rig's `images` directory, and the **same pixel size as this attachment's `image`**. It is not packed into the atlas โ it is a measurement rigc reads at compile time, not art anything draws. Under `--atlas-in` that is still the drawing's size, on a page that declares a `scale:` too: the sheet is read at each vertex's texel position over the scale the atlas states, so one sheet serves the loose parts and every pack of them |
|
|
1997
1904
|
| `near` | **required.** `"white"` or `"black"` โ which end of the range is closest to the viewer. Stated rather than defaulted: both conventions are in use, and a sheet read with the wrong one turns the part inside out with every gate still green |
|
|
1998
|
-
| `zScale` | **required.** How many world units the map's full range spans, in the attachment's own units โ the number `radius`
|
|
1905
|
+
| `zScale` | **required.** How many world units the map's full range spans, in the attachment's own units โ the number `radius` carries on a cylinder. 8 bits of level say nothing about scale, so this is authored, never measured |
|
|
1999
1906
|
| `gamma`, `contrast`, `bias` | the tone curve applied to the nearness, defaults `1` / `1` / `0`. State them when a consumer's own renderer curves the same sheet, so the mesh and that renderer describe one surface |
|
|
2000
1907
|
|
|
2001
1908
|
The order is fixed and it matters, because every step is a place two
|
|
@@ -2024,9 +1931,7 @@ The loop this replaces is *pick an angle, build, read the refusal, guess again*.
|
|
|
2024
1931
|
#### Is the ceiling describing the form, or the sheet's grain?
|
|
2025
1932
|
|
|
2026
1933
|
The lines under the ceiling answer that, and they are **reports only** โ nothing
|
|
2027
|
-
in them refuses a build or moves a ceiling
|
|
2028
|
-
([#412](https://github.com/firejune/rigc/issues/412),
|
|
2029
|
-
[#448](https://github.com/firejune/rigc/issues/448)).
|
|
1934
|
+
in them refuses a build or moves a ceiling.
|
|
2030
1935
|
|
|
2031
1936
|
The ceiling is the **minimum** of the per-triangle fold angles, and a minimum
|
|
2032
1937
|
cannot say whether it is the floor of a band or one bad pixel. Measured: a clean
|
|
@@ -2110,8 +2015,7 @@ off the screen before it gets there** โ fade the slot to alpha 0 (`rgba`, ยง4.
|
|
|
2110
2015
|
swap the attachment away, as the far cheek or ear does while the head turns. That
|
|
2111
2016
|
third one is measured rather than declared: a deform key whose slot draws no
|
|
2112
2017
|
pixels at that key's own time is passed over by name, with the reason on the
|
|
2113
|
-
stats line and in the `DEFORM` block
|
|
2114
|
-
([#401](https://github.com/firejune/rigc/issues/401)). **Alpha exactly 0** โ a
|
|
2018
|
+
stats line and in the `DEFORM` block. **Alpha exactly 0** โ a
|
|
2115
2019
|
part faded halfway is still refused, and the alpha is in the message.
|
|
2116
2020
|
|
|
2117
2021
|
๐จ **Land the alpha-0 key *before* the folding key, not on it.** The gate measures
|
|
@@ -2141,9 +2045,8 @@ u** โ and it reaches zero at `tan t = Aโ/A_yaw`. Three consequences:
|
|
|
2141
2045
|
|
|
2142
2046
|
โ It is a **report and never a refusal**. `A39` owns the refusal, from the
|
|
2143
2047
|
artifact and through the runtime; a second wall here would be the compiler
|
|
2144
|
-
inventing a policy out of a measurement.
|
|
2145
|
-
|
|
2146
|
-
the triangle `A39` actually names, to 0.01ยฐ.
|
|
2048
|
+
inventing a policy out of a measurement. The two agree: this number is the angle
|
|
2049
|
+
`A39` fires at, on the triangle `A39` names, to 0.01ยฐ.
|
|
2147
2050
|
|
|
2148
2051
|
๐จ **The one that will catch you: the sheet has to cover the mesh, and usually
|
|
2149
2052
|
it does not.** A contour mesh puts every vertex *on* the silhouette and pushes it
|
|
@@ -2170,15 +2073,13 @@ in it, which is what monocular depth estimation produces โ covers its whole gr
|
|
|
2170
2073
|
by construction, so the coverage refusal has nothing to hold it to and skips.
|
|
2171
2074
|
That is right: a full-frame sheet is a legitimate statement and rigc has no
|
|
2172
2075
|
authority to guess an input away. But the defect the refusal exists to catch is
|
|
2173
|
-
still reachable in that encoding, so the report counts it instead
|
|
2174
|
-
([#449](https://github.com/firejune/rigc/issues/449)):
|
|
2076
|
+
still reachable in that encoding, so the report counts it instead:
|
|
2175
2077
|
|
|
2176
2078
|
```
|
|
2177
2079
|
80 of 189 vertices sample a texel the part image does not draw โ their z is the sheet's reading of somewhere the part is not
|
|
2178
2080
|
```
|
|
2179
2081
|
|
|
2180
|
-
โ ๏ธ **The reported range is NOT where this shows up
|
|
2181
|
-
until it was measured. A background level is a legitimate depth value, so a map
|
|
2082
|
+
โ ๏ธ **The reported range is NOT where this shows up.** A background level is a legitimate depth value, so a map
|
|
2182
2083
|
half of which is background reports exactly as full a range as one that is all
|
|
2183
2084
|
subject: on the measured build, `z=[0, 223.97]` of a stated 224 โ healthy โ with
|
|
2184
2085
|
54 % of the mesh reading background. "Covers its whole grid" is true and about
|
|
@@ -2227,21 +2128,18 @@ hanging sleeve.
|
|
|
2227
2128
|
| Field | Meaning |
|
|
2228
2129
|
| --- | --- |
|
|
2229
2130
|
| `bone` | **required.** The bone the region is carried by. It has to already exist โ a bone a physics constraint targets is part of the skeleton, not a side effect of a mesh |
|
|
2230
|
-
| `mask` | **required.** A greyscale sheet in the part's own pixel grid: the level IS the weight, black still and white fully carried, sampled at each vertex. Alpha is not read โ a transparent pixel is black. The grid is the drawing's, as for a depth map: on a `scale:` page the mask is still the art's size
|
|
2131
|
+
| `mask` | **required.** A greyscale sheet in the part's own pixel grid: the level IS the weight, black still and white fully carried, sampled at each vertex. Alpha is not read โ a transparent pixel is black. The grid is the drawing's, as for a depth map: on a `scale:` page the mask is still the art's size |
|
|
2231
2132
|
|
|
2232
2133
|
The remainder always stays on the slot bone, so every vertex closes at 1 by
|
|
2233
2134
|
construction rather than by `A20` catching it later. `build` and `explain` report
|
|
2234
2135
|
the mask's digest, how many vertices were carried outright and how many landed in
|
|
2235
2136
|
the painted falloff.
|
|
2236
2137
|
|
|
2237
|
-
๐จ **Why a painted mask and not a depth threshold.**
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
It also claimed something untrue โ "no mask painted" โ while the renderer this
|
|
2243
|
-
was modelled on had a hand-painted spring mask all along. rigc does not get to
|
|
2244
|
-
delete an input by guessing it.
|
|
2138
|
+
๐จ **Why a painted mask and not a depth threshold.** A threshold โ "the near part
|
|
2139
|
+
wobbles" โ is wrong, and instructively so: **softness and prominence are different
|
|
2140
|
+
properties of a drawing.** The most prominent thing on a face is the nose, and a
|
|
2141
|
+
nose does not wobble. A threshold produces a region that is plausible, gates green
|
|
2142
|
+
and carries the wrong pixels. rigc does not get to delete an input by guessing it.
|
|
2245
2143
|
|
|
2246
2144
|
โญ The falloff is painted for the same reason. A `feather` parameter would be
|
|
2247
2145
|
rigc guessing the shape of something you can simply draw.
|
|
@@ -2261,13 +2159,10 @@ bone, must close at 1, and at least one must actually be carried.
|
|
|
2261
2159
|
| a mask that is not on disk | `the soft mask "x.png" is not at โฆ` |
|
|
2262
2160
|
|
|
2263
2161
|
โญ **One depth pass buys both, on one part.** A carried mesh has two bones on the
|
|
2264
|
-
vertices in the mask's falloff, and a `transform` key (ยง4.12)
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
evaluated at each vertex's setup world position and pushed into every influence
|
|
2269
|
-
through that bone's own inverse. `SF03` is the case that measures the
|
|
2270
|
-
combination. โ ๏ธ Read ยง4.11.1 before you add a mask to a part that already turns:
|
|
2162
|
+
vertices in the mask's falloff, and a `transform` key (ยง4.12) works on it: the
|
|
2163
|
+
model is evaluated at each vertex's setup world position and pushed into every
|
|
2164
|
+
influence through that bone's own inverse, so the **angle** a raised surface turns
|
|
2165
|
+
through and the **impact** a soft one answers sit on one slot. โ ๏ธ Read ยง4.11.1 before you add a mask to a part that already turns:
|
|
2271
2166
|
past one bone the model's own coordinates are **world** ones, so `radius` and
|
|
2272
2167
|
`about` change units.
|
|
2273
2168
|
#### 3.4.1 A skin that switches bones and constraints on
|
|
@@ -2374,13 +2269,11 @@ defaults to it, so an image named after it needs no `path`:
|
|
|
2374
2269
|
emits `"name": "patch-base"` first and no `path`, and the runtime draws region
|
|
2375
2270
|
`patch-base` for an attachment it calls `patch-base`, filed under `patch`.
|
|
2376
2271
|
|
|
2377
|
-
๐ฌ **
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
and [#552](https://github.com/firejune/rigc/issues/552) wrote
|
|
2383
|
-
`"<skin>/<placeholder>"` on every contested entry. Both halves fail a measurement:
|
|
2272
|
+
๐ฌ **rigc composes no name here, and nothing needs one.** The editor's refusal of a
|
|
2273
|
+
four-skin rig โ `Multiple attachments have the same name: patch patch` โ reads like
|
|
2274
|
+
"one name over several attachments, because a linked mesh resolves its parent by
|
|
2275
|
+
name", and a composed `"<skin>/<placeholder>"` on every contested entry like the
|
|
2276
|
+
repair. Both halves fail a measurement:
|
|
2384
2277
|
|
|
2385
2278
|
- **A linked mesh finds its source by skin, slot and KEY.** Through spine-core
|
|
2386
2279
|
4.3.13, two skins each fill `C` with a different mesh and no `name`, both load
|
|
@@ -2393,18 +2286,17 @@ and [#552](https://github.com/firejune/rigc/issues/552) wrote
|
|
|
2393
2286
|
through Spine 4.3.26 with every composed name stripped. It is the file that editor
|
|
2394
2287
|
exported.
|
|
2395
2288
|
|
|
2396
|
-
What
|
|
2397
|
-
placeholder beside the named ones โ
|
|
2398
|
-
one named skin โ and that shape is refused on measurements of its own, below.
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2289
|
+
What the editor does refuse is a rig whose **default** skin fills the contested
|
|
2290
|
+
placeholder beside the named ones โ the smallest refusing variant is `default` plus
|
|
2291
|
+
one named skin โ and that shape is refused on measurements of its own, below. A
|
|
2292
|
+
composed name would rename every contested attachment at runtime, which
|
|
2293
|
+
`slot.attachment.name` shows a consumer; `diff` reads it as
|
|
2294
|
+
`attachments.runtime_name`.
|
|
2402
2295
|
|
|
2403
2296
|
๐จ **Every skin that shares a placeholder has to be a named one โ the default
|
|
2404
2297
|
skin may not be among them, and rigc refuses the rig if it is.** That is not a
|
|
2405
2298
|
style rule; it is the editor's model, and two round trips through Spine
|
|
2406
|
-
**4.3.26** established it by ruling out both of the only two spellings there are
|
|
2407
|
-
([#567](https://github.com/firejune/rigc/issues/567)):
|
|
2299
|
+
**4.3.26** established it by ruling out both of the only two spellings there are:
|
|
2408
2300
|
|
|
2409
2301
|
- **Give the default skin's entry a name of its own** (`"name": "default/patch"`)
|
|
2410
2302
|
and the import succeeds โ then the export comes back with that name as the
|
|
@@ -2444,28 +2336,16 @@ What to know about it, and nothing to author:
|
|
|
2444
2336
|
`const regionImage = region && this.regionImages.get(region.name)`, where
|
|
2445
2337
|
`region` came off the attachment โ which `AtlasAttachmentLoader` resolved
|
|
2446
2338
|
through `path`. Every published version of that renderer keys the same way.
|
|
2447
|
-
โ ๏ธ `A08` used to carry a `--profile spine-html` clause requiring a
|
|
2448
|
-
placeholder to be spelled exactly like the region it resolves to, which made
|
|
2449
|
-
this shape and a green `spine-html` **mutually exclusive** from
|
|
2450
|
-
[#567](https://github.com/firejune/rigc/issues/567) onwards; the first
|
|
2451
|
-
production rig with named skins hit it three times. That clause is retired โ
|
|
2452
|
-
restated as the join the renderer actually performs it was a tautology over
|
|
2453
|
-
the resolve check beside it
|
|
2454
|
-
([#574](https://github.com/firejune/rigc/issues/574)).
|
|
2455
2339
|
- **A stated `name` with no `path` resolves the region the name spells.** `path`
|
|
2456
2340
|
defaults to the attachment's **name**, not to its placeholder, so an entry naming
|
|
2457
2341
|
`patch-base` and drawing `patch_a.png` needs a `path` โ and gets one, because the
|
|
2458
2342
|
image basename differs from the name. `A08_REGION_NAMES_MATCH_ATTACHMENTS` names
|
|
2459
|
-
the skin, the slot, the placeholder and the path if the region is not there
|
|
2460
|
-
([#589](https://github.com/firejune/rigc/issues/589)).
|
|
2343
|
+
the skin, the slot, the placeholder and the path if the region is not there.
|
|
2461
2344
|
- **Each skin's art is measured and atlased on its own.** The example above points
|
|
2462
2345
|
the two skins at two PNGs, so there are two regions, each attachment's `path`
|
|
2463
2346
|
resolving to the file that attachment named and its `width`/`height` measured off
|
|
2464
|
-
that file.
|
|
2465
|
-
|
|
2466
|
-
atlas nor the measurement โ so name the two files **distinctly**, because the
|
|
2467
|
-
region name is the basename and `a/patch.png` beside `b/patch.png` is refused
|
|
2468
|
-
(R5).
|
|
2347
|
+
that file. Name the two files **distinctly**: the region name is the basename,
|
|
2348
|
+
and `a/patch.png` beside `b/patch.png` is refused (R5).
|
|
2469
2349
|
|
|
2470
2350
|
โ ๏ธ **Nothing measured says the editor wants names unique within a slot.**
|
|
2471
2351
|
`spineboy-pro.json`, which the editor wrote, gives the name `head` to a region in
|
|
@@ -2475,7 +2355,7 @@ slot across its named skins. The one refusal the editor was measured making is t
|
|
|
2475
2355
|
default-skin share, and that is the rule rigc enforces.
|
|
2476
2356
|
|
|
2477
2357
|
๐จ **Once a rig has named skins, no instrument here can see them until you say
|
|
2478
|
-
which one
|
|
2358
|
+
which one.** `render` and
|
|
2479
2359
|
`check` set no skin unless told to, so every slot resolves through the *default*
|
|
2480
2360
|
skin alone and the art you just moved into `base`, `zulu` and `mike` draws
|
|
2481
2361
|
nothing at all. `check` then compares blank against blank and reports a perfect
|
|
@@ -2483,8 +2363,7 @@ nothing at all. `check` then compares blank against blank and reports a perfect
|
|
|
2483
2363
|
`--skin <name>` to both, once per skin (**ยง9**); `tools/editor_roundtrip.ts`
|
|
2484
2364
|
loops over every skin the build and the export both declare for the same reason,
|
|
2485
2365
|
and names a skin only one of them declares as **lost** (or **added**) **by the
|
|
2486
|
-
export**, quoting `diff`'s `attachments.skins`, without rendering it on either side
|
|
2487
|
-
([#801](https://github.com/firejune/rigc/issues/801)).
|
|
2366
|
+
export**, quoting `diff`'s `attachments.skins`, without rendering it on either side.
|
|
2488
2367
|
|
|
2489
2368
|
#### 3.4.3 `sequence` โ a numbered image series on one attachment
|
|
2490
2369
|
|
|
@@ -2525,8 +2404,7 @@ The loader's own miss would be `Region not found in atlas: glint_0005 (attachmen
|
|
|
2525
2404
|
glint)`, which says neither that the region was a frame nor of which series.
|
|
2526
2405
|
|
|
2527
2406
|
โ ๏ธ **The rest of what is refused**, each a series the parser would load as something
|
|
2528
|
-
other than what was written (measured on spine-core 4.3.13
|
|
2529
|
-
[#729](https://github.com/firejune/rigc/issues/729)):
|
|
2407
|
+
other than what was written (measured on spine-core 4.3.13):
|
|
2530
2408
|
|
|
2531
2409
|
- a `setup` at or past `count` โ `Sequence.resolveIndex` clamps it to the last frame
|
|
2532
2410
|
(`setup: 7` on four frames showed frame 4);
|
|
@@ -2546,8 +2424,7 @@ frames **may differ in size**: the runtime scales each frame's region into the
|
|
|
2546
2424
|
attachment's size (`RegionAttachment.computeUVs`, `width / region.originalWidth`), so a
|
|
2547
2425
|
stated size is emitted as stated and compared with no frame, on the loose route and
|
|
2548
2426
|
under `--atlas-in` alike. That is what an editor exports for a series that mixes image
|
|
2549
|
-
sizes โ the setup frame's size, the other frames as they are
|
|
2550
|
-
([#795](https://github.com/firejune/rigc/issues/795)). Omit them and rigc takes the
|
|
2427
|
+
sizes โ the setup frame's size, the other frames as they are. Omit them and rigc takes the
|
|
2551
2428
|
frames' size โ **only when every frame measures the same**; frames of different sizes
|
|
2552
2429
|
are refused until you state the size, because picking one of them would be the
|
|
2553
2430
|
compiler choosing a value. โ ๏ธ One region is different: its `width` is its image's size
|
|
@@ -2579,11 +2456,7 @@ carrying here:
|
|
|
2579
2456
|
finds each from its own group, and a motion spec's `ik` block, `transform`
|
|
2580
2457
|
block and `path`/`physics`/`slider` tracks each name the kind they mean and
|
|
2581
2458
|
resolve the same way (ยง4.9, ยง4.12). Two constraints **of one kind** sharing a
|
|
2582
|
-
name are refused (ยง5.1), because no timeline could say which was meant.
|
|
2583
|
-
[#692](https://github.com/firejune/rigc/issues/692) the rig spec kept one
|
|
2584
|
-
namespace over the whole array, so a rig the editor exports and the runtime
|
|
2585
|
-
plays โ an IK chain and the transform constraint that follows it, both carrying
|
|
2586
|
-
the chain's name โ could not be written down at all.
|
|
2459
|
+
name are refused (ยง5.1), because no timeline could say which was meant.
|
|
2587
2460
|
- A transform constraint's `properties` names come from a fixed six โ `rotate`,
|
|
2588
2461
|
`x`, `y`, `scaleX`, `scaleY`, `shearY`. rigc refuses anything else by name; in
|
|
2589
2462
|
raw JSON the parser throws.
|
|
@@ -2606,11 +2479,8 @@ carrying here:
|
|
|
2606
2479
|
`true` and leaves the rest alone (`Animation.js:2066-2072`). So the flag is one
|
|
2607
2480
|
constraint's **opt-in to being driven in bulk**, per tuning value, and it does
|
|
2608
2481
|
nothing on its own. The parser's default for all seven is `false`.
|
|
2609
|
-
The motion spec keys that timeline as **`"physics": "*"`** (ยง4.4,
|
|
2610
|
-
|
|
2611
|
-
the empty name. Until then rigc emitted the flags and could not emit the
|
|
2612
|
-
timeline that reads them: every physics track named its constraint and the
|
|
2613
|
-
empty name was refused as `keys unknown physics constraint ""`. What rigc does
|
|
2482
|
+
The motion spec keys that timeline as **`"physics": "*"`** (ยง4.4), and rigc
|
|
2483
|
+
writes it under the empty name. What rigc does
|
|
2614
2484
|
with the flags is **pass them through**, `false` included: measured, a
|
|
2615
2485
|
constraint stating none emits none, and one stating `"windGlobal": false` emits
|
|
2616
2486
|
`"windGlobal": false` rather than dropping it the way the motion spec's
|
|
@@ -2620,10 +2490,8 @@ carrying here:
|
|
|
2620
2490
|
- `"*"` is **reserved** as a physics constraint's name, in the rig spec and in
|
|
2621
2491
|
ยง4.6's table alike: a track naming it could mean either. `compile` refuses one
|
|
2622
2492
|
by name.
|
|
2623
|
-
- โ ๏ธ
|
|
2624
|
-
|
|
2625
|
-
read it, so a spec that wrote `scaleYMode` set no mode and said nothing. A rig
|
|
2626
|
-
that still writes it is now refused by name, with `scaleY` beside it.
|
|
2493
|
+
- โ ๏ธ **`scaleYMode`** โ the runtime's field name rather than the format's key โ
|
|
2494
|
+
is refused by name, with `scaleY` beside it.
|
|
2627
2495
|
|
|
2628
2496
|
Every constraint may also carry `skin: true`, which makes it run only under the skin
|
|
2629
2497
|
that lists it โ see ยง3.4.1, and note that the flag alone does nothing.
|
|
@@ -2633,7 +2501,7 @@ that is deliberate. Every key on a constraint object is a Spine field the emitte
|
|
|
2633
2501
|
writes; a statement to the gate about who turns the dial is what the artifact cannot
|
|
2634
2502
|
say about itself, so it lives in `invariants.consumerDrivenMix` (ยง3.7), beside
|
|
2635
2503
|
`deformMayFold` โ the one other exemption from a named rule, which names its subject
|
|
2636
|
-
the same way
|
|
2504
|
+
the same way.
|
|
2637
2505
|
|
|
2638
2506
|
#### 3.5.1 `path` โ bones that travel along a curve
|
|
2639
2507
|
|
|
@@ -2714,7 +2582,7 @@ dial, the gate swings.
|
|
|
2714
2582
|
timeline left it: a negative time is not clamped to the first frame, it is a time
|
|
2715
2583
|
before the animation starts, where `Animation.apply` leaves the pose it found
|
|
2716
2584
|
untouched. That is the same picture as the first frame only when the first frame
|
|
2717
|
-
*is* the rest pose.
|
|
2585
|
+
*is* the rest pose.
|
|
2718
2586
|
|
|
2719
2587
|
| Field | Meaning |
|
|
2720
2588
|
| --- | --- |
|
|
@@ -2739,40 +2607,28 @@ where the message can name both files.
|
|
|
2739
2607
|
why R10 exists.** In JSON the reference is a name on both sides. In the format's
|
|
2740
2608
|
binary half it is an **ordinal** โ `constraint.animation = animations[readInt()]`
|
|
2741
2609
|
(`SkeletonBinary`) โ so an editor holding that ordinal writes back whichever
|
|
2742
|
-
animation now stands at the position. `gallery/look`
|
|
2743
|
-
(data version 4.3.26) as `turn, tilt, sweep` with `yaw -> "turn"`
|
|
2610
|
+
animation now stands at the position. `gallery/look` taken into a licensed editor
|
|
2611
|
+
(data version 4.3.26) as `turn, tilt, sweep` with `yaw -> "turn"` comes back
|
|
2744
2612
|
`sweep, tilt, turn` with **`yaw -> "sweep"`**: a file that parses, gates green and
|
|
2745
|
-
applies the wrong animation
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
that nobody had round-tripped such a pair. Somebody has: `Turn, sweep, wave` came
|
|
2765
|
-
back `sweep, Turn, wave` and `turn10, turn2, zoom` came back `turn2, turn10, zoom`
|
|
2766
|
-
([#539](https://github.com/firejune/rigc/issues/539)). โ rigc no longer leaves
|
|
2767
|
-
that to naming discipline โ and since
|
|
2768
|
-
[#543](https://github.com/firejune/rigc/issues/543) it does better than refusing
|
|
2769
|
-
those two, because they are the two sets the editor's answer is **known** for:
|
|
2770
|
-
both are emitted in the order it returned. What is still a compile error is the
|
|
2771
|
-
set whose order turns on one of the three things the round trips of
|
|
2772
|
-
[#728](https://github.com/firejune/rigc/issues/728) leave open, printed with both
|
|
2773
|
-
names, which of them decides it, and the rename that settles it. What changed is
|
|
2774
|
-
the price of forgetting: a build that stops, rather than a slider that silently
|
|
2775
|
-
applies the wrong animation.
|
|
2613
|
+
applies the wrong animation โ `tilt` survives only because it sits at index 1 in
|
|
2614
|
+
both orderings. rigc emits animations in the editor's own order so its re-sort
|
|
2615
|
+
moves no index; on the same rig through the same editor that keeps
|
|
2616
|
+
`yaw -> "turn"`, at a re-rendered mean absolute error of 0.3035 / 0.0769 / 0.0588
|
|
2617
|
+
against 10.4655 / 8.4961 / 8.7140 for the order above.
|
|
2618
|
+
|
|
2619
|
+
โ ๏ธ The editor's comparator is natural and case-insensitive, measured off five
|
|
2620
|
+
stored round trips โ so the emit is the editor's own order, and only what those
|
|
2621
|
+
round trips leave open is a compile error. **R10** has the rule and the three
|
|
2622
|
+
shapes to avoid.
|
|
2623
|
+
|
|
2624
|
+
โ
**What that order does not settle is a compile error, not a hazard.** Names a
|
|
2625
|
+
codepoint sort and a friendlier one disagree about โ `Turn` / `turn`, `turn2` /
|
|
2626
|
+
`turn10` โ are emitted in the order the editor returns for them: measured,
|
|
2627
|
+
`Turn, sweep, wave` comes back `sweep, Turn, wave` and `turn10, turn2, zoom` comes
|
|
2628
|
+
back `turn2, turn10, zoom`. A set whose order turns on one of the three things
|
|
2629
|
+
those round trips leave open is refused, printed with both names, which of them
|
|
2630
|
+
decides it, and the rename that settles it โ a build that stops, rather than a
|
|
2631
|
+
slider that silently applies the wrong animation.
|
|
2776
2632
|
|
|
2777
2633
|
โ ๏ธ **The fields of the model you did not choose are refused, not ignored.** The
|
|
2778
2634
|
parser reads `time` only in the bone-less branch and `property`/`from`/`to`/`scale`/
|
|
@@ -2811,8 +2667,8 @@ one wins today. โ rigc does **not** set the flag for you. The compiler never
|
|
|
2811
2667
|
invents a value that is not in the spec, and a rig whose composition was chosen by
|
|
2812
2668
|
the tool is one nobody can reason about.
|
|
2813
2669
|
|
|
2814
|
-
โ ๏ธ **And below full authority it is a weighting rather than a quieter erasure
|
|
2815
|
-
|
|
2670
|
+
โ ๏ธ **And below full authority it is a weighting rather than a quieter erasure.**
|
|
2671
|
+
An additive slider scales
|
|
2816
2672
|
its whole contribution by its own `mix`, so two of them at any pair of mixes are
|
|
2817
2673
|
still the sum โ which is why `A40` skipping below full authority is right: what
|
|
2818
2674
|
happens there is a weighting, not the erasure it refuses. A **non-additive**
|
|
@@ -2822,8 +2678,7 @@ the pose it found*, so the earlier slider is not erased โ it is attenuated by
|
|
|
2822
2678
|
part of the way down takes that share of every earlier slider on the target with
|
|
2823
2679
|
it, on every frame, with the gate green โ the second reason to write
|
|
2824
2680
|
`"additive": true` on **every** slider that shares a target and not only on the
|
|
2825
|
-
later one. `
|
|
2826
|
-
runtime, and [`docs/FACE.md`](FACE.md) ยง8 is the same rule on a face's two axes.
|
|
2681
|
+
later one. [`docs/FACE.md`](FACE.md) ยง8 is the same rule on a face's two axes.
|
|
2827
2682
|
|
|
2828
2683
|
โ ๏ธ **And `"additive": true` is not always available.** What composes is bone,
|
|
2829
2684
|
deform, transform-constraint, path `position` and path `mix`, physics
|
|
@@ -2831,36 +2686,27 @@ deform, transform-constraint, path `position` and path `mix`, physics
|
|
|
2831
2686
|
a slot colour, an attachment swap, a draw order and a sequence, and also an **ik
|
|
2832
2687
|
constraint's mix**, a path's `spacing`, and every physics timeline except those
|
|
2833
2688
|
two โ so two sliders sharing one of those overwrite each other whatever you
|
|
2834
|
-
write. โ ๏ธ
|
|
2835
|
-
|
|
2836
|
-
refuses the ik case this sentence did not name.
|
|
2689
|
+
write. โ ๏ธ Those are examples of the complement, not the whole of it: `A40` reads
|
|
2690
|
+
no list.
|
|
2837
2691
|
|
|
2838
|
-
๐จ **That list is not `Timeline.additive
|
|
2692
|
+
๐จ **That list is not `Timeline.additive`.**
|
|
2839
2693
|
The runtime's own flag says a class "supports being applied additively", and on
|
|
2840
2694
|
two classes it is not what the class does: `PathConstraintMixTimeline` and
|
|
2841
2695
|
`SliderTimeline` declare themselves non-additive and their `apply` passes the
|
|
2842
2696
|
`add` argument straight through anyway โ every other non-additive timeline either
|
|
2843
2697
|
hardcodes `false` in the call, zeroes `add` first, or never reads it. So two
|
|
2844
2698
|
additive sliders keying one path constraint's `mix`, or one slider's `time`,
|
|
2845
|
-
**do** compose, as the same sum as everything else above
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
โญ **The same measurement retired a refusal nothing could have distinguished.**
|
|
2857
|
-
Two sliders whose animations both fire **events** were refused as sharing a
|
|
2858
|
-
property โ and a slider fires no event at all: it applies its animation with
|
|
2859
|
-
`firedEvents` null, and `EventTimeline.apply` returns on that. The probe's third
|
|
2860
|
-
answer is *this timeline moved no pose at all*, so an events pair, and a physics
|
|
2861
|
-
`reset` pair with it, are simply not findings. Nothing else changed: a shared
|
|
2862
|
-
slot colour, attachment, draw order, ik mix, path `spacing` or physics property
|
|
2863
|
-
is refused exactly as before.
|
|
2699
|
+
**do** compose, as the same sum as everything else above. โ
**So `A40` does not
|
|
2700
|
+
read the flag: it poses each shared timeline twice with `add` set and reads
|
|
2701
|
+
whether the second application accumulated**, and the message names what the
|
|
2702
|
+
class was measured to do.
|
|
2703
|
+
|
|
2704
|
+
โญ **Two sliders whose animations both fire events are not a finding.** A slider
|
|
2705
|
+
fires no event at all: it applies its animation with `firedEvents` null, and
|
|
2706
|
+
`EventTimeline.apply` returns on that. `A40`'s third answer is *this timeline
|
|
2707
|
+
moved no pose at all*, so an events pair, and a physics `reset` pair with it, are
|
|
2708
|
+
passed. A shared slot colour, attachment, draw order, ik mix, path `spacing` or
|
|
2709
|
+
physics property is refused.
|
|
2864
2710
|
|
|
2865
2711
|
โ **And the `constraints` array decides twice, for two different reasons.**
|
|
2866
2712
|
*Overwriting* has a direction: the slider **later in the array** puts its own
|
|
@@ -2874,16 +2720,11 @@ earlier ik constraint, the `position` of an earlier path constraint or the `wind
|
|
|
2874
2720
|
of an earlier physics constraint writes a value nothing reads again, and what
|
|
2875
2721
|
that constraint drives is dead at every position of the dial.
|
|
2876
2722
|
|
|
2877
|
-
โ
**That second one is a refusal by name
|
|
2878
|
-
[#658](https://github.com/firejune/rigc/issues/658), and it covers every
|
|
2879
|
-
constraint kind since [#665](https://github.com/firejune/rigc/issues/665).**
|
|
2723
|
+
โ
**That second one is a refusal by name, for every constraint kind.**
|
|
2880
2724
|
`A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` names the driving slider, the
|
|
2881
2725
|
driven constraint and its kind, the property, both `constraints` indices, the
|
|
2882
2726
|
runtime class whose `update` does the reading and the animation the key sits in,
|
|
2883
|
-
and its repair is the reorder.
|
|
2884
|
-
`A40` because `A40`'s population is the sliders whose `mix` nothing keys โ the
|
|
2885
|
-
exclusion *is* the shape of the hole โ and not a clause of `A37`, which asks
|
|
2886
|
-
whether some animation keys the `mix` above 0 and never which one.
|
|
2727
|
+
and its repair is the reorder.
|
|
2887
2728
|
โญ **The runtime repairs this for bones and not for constraints**, which is why an
|
|
2888
2729
|
author cannot reason it out from the bone case: `Slider.sort` clears `sorted` on
|
|
2889
2730
|
every bone its animation keys so those bones re-sort *after* the slider, while
|
|
@@ -2904,14 +2745,10 @@ round. ๐ธ **One spelling is outside the rule and a reorder does not repair it*
|
|
|
2904
2745
|
a `physics` `reset` key. It writes no pose and fires only when a frame time is
|
|
2905
2746
|
crossed, and a slider applies its animation at a single instant โ so the key
|
|
2906
2747
|
never fires in either order, and the SKIP says so rather than advising a move
|
|
2907
|
-
that would change nothing.
|
|
2908
|
-
`PS158`, `PS159` and `PS160` in `selftest.ts`.
|
|
2748
|
+
that would change nothing.
|
|
2909
2749
|
|
|
2910
2750
|
Swap the two array entries and the answer swaps with them โ it is the
|
|
2911
|
-
array that decides, not the flags and not which animation the file names first
|
|
2912
|
-
(`PS135` in `selftest.ts` poses four such targets both ways; `PS136` poses the
|
|
2913
|
-
three that do compose, and they are the same sum ยง3.5.2 states, over each target's
|
|
2914
|
-
own setup value). A40 names this case separately, because the fix is different:
|
|
2751
|
+
array that decides, not the flags and not which animation the file names first. A40 names this case separately, because the fix is different:
|
|
2915
2752
|
key such a property from one slider only, or move both edits into the single
|
|
2916
2753
|
animation one slider applies.
|
|
2917
2754
|
|
|
@@ -2920,8 +2757,7 @@ animation one slider applies.
|
|
|
2920
2757
|
**A `property` under `local: false` is read through the world transform, and four
|
|
2921
2758
|
of those readings are bounded.** A range that names values the reader cannot
|
|
2922
2759
|
return is dead there: the dial moves, the reading does not follow, and nothing at
|
|
2923
|
-
runtime says so. [measured] against `spine-core` 4.3.13, one reader at a time
|
|
2924
|
-
`bench/studies/2026-09-06-readers`:
|
|
2760
|
+
runtime says so. [measured] against `spine-core` 4.3.13, one reader at a time:
|
|
2925
2761
|
|
|
2926
2762
|
| `property` | `"local"` | Reads | Producible floor | Producible ceiling |
|
|
2927
2763
|
| --- | --- | --- | --- | --- |
|
|
@@ -2952,10 +2788,9 @@ runtime says so. [measured] against `spine-core` 4.3.13, one reader at a time
|
|
|
2952
2788
|
๐จ One that dips below it is not *dead*, it **folds**: โ2 and +2 read the same
|
|
2953
2789
|
number, select the same frame and pose the same face, so the axis doubles back
|
|
2954
2790
|
on itself about the point it should have passed through. Such a range is
|
|
2955
|
-
**refused at compile** (ยง3.5.2.2, beside the `rotate` circle). [measured]
|
|
2956
|
-
`
|
|
2957
|
-
|
|
2958
|
-
applies the same time at โ2 and +2 to the bit, while the same mapping read
|
|
2791
|
+
**refused at compile** (ยง3.5.2.2, beside the `rotate` circle). [measured] a
|
|
2792
|
+
legal `0`..`4` window turned below 0 by a consumer applies the same time at โ2
|
|
2793
|
+
and +2 to the bit, while the same mapping read
|
|
2959
2794
|
`local: true` is on its own signed mapping at every cell.
|
|
2960
2795
|
- **`shearY` under `local: false` wraps like `rotate` does, and worse.** It is a
|
|
2961
2796
|
difference of two `atan2` calls, so at any one bone orientation the readable
|
|
@@ -2965,8 +2800,8 @@ runtime says so. [measured] against `spine-core` 4.3.13, one reader at a time
|
|
|
2965
2800
|
pointing. Prefer `local: true` for a shear axis.
|
|
2966
2801
|
โ ๏ธ **Nothing refuses a `shearY` range, and that is deliberate.** The reader
|
|
2967
2802
|
keeps its sign โ it does not fold the way the two `scale` readers do โ and what
|
|
2968
|
-
it does instead is not a fact a rig spec holds: [measured]
|
|
2969
|
-
|
|
2803
|
+
it does instead is not a fact a rig spec holds: [measured] three orientations
|
|
2804
|
+
swept across their own seams each wrap by exactly one turn at
|
|
2970
2805
|
`90ยฐ โ ฮธx` (90ยฐ, 45ยฐ and 150ยฐ for a dial bone at 0ยฐ, 45ยฐ and โ60ยฐ), which any
|
|
2971
2806
|
animation a consumer writes can move. A range rule here would have to name a
|
|
2972
2807
|
seam the compiler cannot know, and would pass the case that actually breaks.
|
|
@@ -3026,8 +2861,7 @@ examples above each sit within one turn, where a single ยฑ360 gives the same
|
|
|
3026
2861
|
answer; past that only the modulo does. [measured] through spine-core, a bone
|
|
3027
2862
|
parked at **โ500ยฐ** drives the slider to **3.600000 s**, which is exactly where a
|
|
3028
2863
|
bone parked at **220ยฐ** drives it โ so the reading is 220ยฐ, not โ140ยฐ, and a
|
|
3029
|
-
refusal naming โ140ยฐ would be naming a value that reader cannot return at all
|
|
3030
|
-
(issue [#431](https://github.com/firejune/rigc/issues/431)). The same on the other
|
|
2864
|
+
refusal naming โ140ยฐ would be naming a value that reader cannot return at all. The same on the other
|
|
3031
2865
|
side: **900ยฐ** drives it to **0.200000 s**, the time a bone at **180ยฐ** selects.
|
|
3032
2866
|
|
|
3033
2867
|
๐ **The consequence in that message is computed, not described.** Both refusals
|
|
@@ -3062,10 +2896,7 @@ lies outside `[0, 360]` โ and not the reach from the boundary to the far end.
|
|
|
3062
2896
|
two are the same number for a range that *straddles* a boundary, as `300ยฐ..500ยฐ`
|
|
3063
2897
|
does. A range lying **wholly** outside is told its own width instead: `400ยฐ..500ยฐ`
|
|
3064
2898
|
reads *"the 100.000ยฐ of the range past 360ยฐ"*, and `-500ยฐ..-300ยฐ` *"the 200.000ยฐ
|
|
3065
|
-
of the range below 0ยฐ"
|
|
3066
|
-
[#434](https://github.com/firejune/rigc/issues/434) โ both used to print the
|
|
3067
|
-
reach, which on the first of those was 140.000ยฐ, wider than the 100ยฐ-wide range
|
|
3068
|
-
it was describing).
|
|
2899
|
+
of the range below 0ยฐ"*.
|
|
3069
2900
|
|
|
3070
2901
|
โญ **A range ending exactly on 360ยฐ is legal**, and that is the whole turn: a
|
|
3071
2902
|
wheel, a turntable, a head that goes all the way round, written `from: 0` with a
|
|
@@ -3109,8 +2940,7 @@ the same posed face. The refusal names the arc that is written twice:
|
|
|
3109
2940
|
- **A range lying wholly below 0 gets its own sentence** โ *"the whole 4.000 of
|
|
3110
2941
|
this range is below 0, so it reaches none of the animation's 1s"* โ and that
|
|
3111
2942
|
figure is the width **under** the floor, not the reach to the far end, the same
|
|
3112
|
-
distinction
|
|
3113
|
-
circle.
|
|
2943
|
+
distinction the circle's message draws.
|
|
3114
2944
|
- **`loop: true` changes nothing here**, unlike the circle: a fold is not a
|
|
3115
2945
|
clamp, so there is no loop branch in the message. [measured] the same ยฑ1.500
|
|
3116
2946
|
pair applies 1.875000 s either way.
|
|
@@ -3118,14 +2948,14 @@ the same posed face. The refusal names the arc that is written twice:
|
|
|
3118
2948
|
negative half drives the *first* half of the animation ([measured] โ2 โ
|
|
3119
2949
|
0.000 s, +2 โ 1.000 s), or moving the range so it does not dip below 0.
|
|
3120
2950
|
|
|
3121
|
-
|
|
2951
|
+
`shearY` has no such rule and
|
|
3122
2952
|
ยง3.5.2.1 says why: that reader keeps its sign and wraps at a seam the bone's own
|
|
3123
2953
|
orientation places, which a rig spec does not hold.
|
|
3124
2954
|
|
|
3125
|
-
โ ๏ธ **An artifact can still carry a dead range** โ one exported from the editor
|
|
3126
|
-
hand-edited
|
|
3127
|
-
|
|
3128
|
-
|
|
2955
|
+
โ ๏ธ **An artifact can still carry a dead range** โ one exported from the editor or
|
|
2956
|
+
hand-edited. `A39` reports that from the artifact side as a key at a time no dial
|
|
2957
|
+
selects (ยง4.11.4); the compile refusal above is what stops a rig spec from
|
|
2958
|
+
producing one.
|
|
3129
2959
|
|
|
3130
2960
|
๐ **A slider's animation is measured in the slider's own frame** โ `A39` inverts
|
|
3131
2961
|
the mapping above and drives the bone to the value it names, rather than playing
|
|
@@ -3133,8 +2963,7 @@ the animation on a track while the slider sits at its neutral. ยง4.11.4 is what
|
|
|
3133
2963
|
that changes and why it matters at `mix: 1`.
|
|
3134
2964
|
|
|
3135
2965
|
๐ธ **`scale` is emitted as its float32**, like every other number rigc writes, so
|
|
3136
|
-
`1/60` ships as `0.016666668` โ 8e-8 relative
|
|
3137
|
-
six-decimal grid before issue #716). Invisible in the middle of the range; it
|
|
2966
|
+
`1/60` ships as `0.016666668` โ 8e-8 relative. Invisible in the middle of the range; it
|
|
3138
2967
|
shows at the top of it, where a 60ยฐ turn then applies at 1.00000008 s rather than
|
|
3139
2968
|
1 s. With `loop: false` that is the last frame and harmless, with
|
|
3140
2969
|
`loop: true` it wraps to the start. When the range comes from a *measured* ceiling
|
|
@@ -3205,7 +3034,7 @@ stops before the gate with `N mesh slot(s) emitted but the rig "X" allows 0`.
|
|
|
3205
3034
|
Geometry rigc built is geometry rigc will not ship unmeasured; geometry the author
|
|
3206
3035
|
drew is exempt, because rigc did not draw it. So `A13`'s **SKIP** means *this rig
|
|
3207
3036
|
is unmeasured*, not *this budget is inert* โ those are two code paths with one
|
|
3208
|
-
name
|
|
3037
|
+
name.
|
|
3209
3038
|
|
|
3210
3039
|
๐ **`detached` is a list of forbidden parentages**, and its shape is
|
|
3211
3040
|
`[{ "bone": โฆ, "notUnder": โฆ, "why"?: โฆ }]`. `A25_DETACHED_BONE_PARENTAGE` walks
|
|
@@ -3234,20 +3063,16 @@ is the whole value of it. What the field is for is art that folds on purpose: a
|
|
|
3234
3063
|
page turning over, a cloth creasing back on itself, where the reversed winding
|
|
3235
3064
|
*is* the drawing.
|
|
3236
3065
|
|
|
3237
|
-
๐ **
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
and the entry is gone. โ An exemption whose `why` reads *"known defect, see
|
|
3241
|
-
#N"* is a legitimate use of the field and an honest one, but it is a loan
|
|
3242
|
-
against a fix, not a fix โ and the thing that made it repayable was A39
|
|
3243
|
-
measuring the ceiling the art could actually take.
|
|
3066
|
+
๐ **An exemption whose `why` reads *"known defect, see #N"* is a legitimate use
|
|
3067
|
+
of the field and an honest one**, but it is a loan against a fix, not a fix โ and
|
|
3068
|
+
what makes it repayable is A39 measuring the ceiling the art can actually take.
|
|
3244
3069
|
|
|
3245
3070
|
๐ฌ **`editorRoundTrip` is the one field here that names a CONSUMER rather than a
|
|
3246
3071
|
shape.** Write `"editorRoundTrip": true` when this rig is authored to come back
|
|
3247
3072
|
out of the Spine editor โ imported, hand-edited, exported โ and
|
|
3248
3073
|
`A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` refuses a physics constraint driving a
|
|
3249
3074
|
component that editor cannot hold. `true` is the only accepted value; a `false`
|
|
3250
|
-
would be a key nothing reads
|
|
3075
|
+
would be a key nothing reads.
|
|
3251
3076
|
|
|
3252
3077
|
โ ๏ธ **Leaving it out is not a weaker gate, and this is the part worth reading.**
|
|
3253
3078
|
rigc's output is not wrong here: a physics constraint driving `rotate` is valid
|
|
@@ -3260,7 +3085,7 @@ constraint and the component:
|
|
|
3260
3085
|
SKIP A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP: the rig "look" does not declare `invariants.editorRoundTrip`, so nothing here is gated against the Spine editor. What is here: physics "whip" drives rotate, and the editor's physics model holds x and y only, so a round trip returns that constraint driving nothing at all (issue #540)
|
|
3261
3086
|
```
|
|
3262
3087
|
|
|
3263
|
-
๐ **Measured, not inferred
|
|
3088
|
+
๐ **Measured, not inferred:**
|
|
3264
3089
|
three rigs, twelve constraints, predictions written before the round trip. A lone
|
|
3265
3090
|
`y` came back and `x` + `y` together came back โ the rule is membership, not arity
|
|
3266
3091
|
โ while a lone `rotate`, a lone `scaleX` and a lone `shearX` each came back driving
|
|
@@ -3273,16 +3098,13 @@ loss seen from the far side: it is what fires on the file the editor hands **bac
|
|
|
3273
3098
|
draws no pixels at that key's own time is already passed over โ `A39` measures
|
|
3274
3099
|
that and says so (ยง4.11, and the `skipped` line in the `DEFORM` block). Declaring
|
|
3275
3100
|
the slot instead would turn the check off at every angle where the part is fully
|
|
3276
|
-
visible too, which is trading a false positive for a blind spot on the same slot
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
drawn and are gated (ยง4.11.3); this used to be a rule you had to follow and is
|
|
3280
|
-
now one the gate keeps
|
|
3281
|
-
([#403](https://github.com/firejune/rigc/issues/403)).
|
|
3101
|
+
visible too, which is trading a false positive for a blind spot on the same slot.
|
|
3102
|
+
โ ๏ธ And land that alpha-0 key **before** the folding key rather than on it โ the
|
|
3103
|
+
frames in between are drawn and are gated (ยง4.11.3).
|
|
3282
3104
|
|
|
3283
3105
|
๐๏ธ **`consumerDrivenMix` names the ik and transform constraints whose mix the
|
|
3284
|
-
CONSUMER sets** โ from code, at runtime โ rather than any animation in this file
|
|
3285
|
-
|
|
3106
|
+
CONSUMER sets** โ from code, at runtime โ rather than any animation in this file.
|
|
3107
|
+
It is
|
|
3286
3108
|
`[{ "constraint": โฆ, "type": "ik" | "transform", "why": โฆ }]`, and it exempts that
|
|
3287
3109
|
constraint from `A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT` or
|
|
3288
3110
|
`A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` (ยง4.12). A constraint resting muted
|
|
@@ -3310,8 +3132,7 @@ spelling of `gallery/look`'s rule that a face angle is a value rather than a tim
|
|
|
3310
3132
|
`spec` must be `"rigc-motion/1"`; `archetype` must equal the rig's `name`; `cut` is
|
|
3311
3133
|
a label for the shot. Always include an `easings` object โ an empty one is fine.
|
|
3312
3134
|
|
|
3313
|
-
โญ **The file is PARSED, not cast** ([`src/motion.ts`](../src/motion.ts),
|
|
3314
|
-
#307), so a field of the wrong type is refused before any of it is compiled. Every
|
|
3135
|
+
โญ **The file is PARSED, not cast** ([`src/motion.ts`](../src/motion.ts)), so a field of the wrong type is refused before any of it is compiled. Every
|
|
3315
3136
|
one of those refusals names the file, the key path, what the value actually is and
|
|
3316
3137
|
the spelling that works โ `path/to/motion.json: \`easings."soft"\` is an array of
|
|
3317
3138
|
3; a named easing is FOUR finite numbers โฆ`. The split with the compile-time
|
|
@@ -3321,7 +3142,7 @@ parser; whether a name resolves against the rig, whether a bone is in that group
|
|
|
3321
3142
|
whether a key's value has the right number of channels for its property needs
|
|
3322
3143
|
something this file does not contain, and stays where it can say so.
|
|
3323
3144
|
|
|
3324
|
-
โ ๏ธ **An unknown key is
|
|
3145
|
+
โ ๏ธ **An unknown key is ignored**, exactly as it is in a rig spec โ a
|
|
3325
3146
|
misspelled `"easing"` for `"ease"` plays linear and says nothing. The two formats
|
|
3326
3147
|
are deliberately consistent here rather than each surprising in its own way.
|
|
3327
3148
|
|
|
@@ -3332,11 +3153,9 @@ shows. rigc converts them per key into the absolute `(time, value)` control poin
|
|
|
3332
3153
|
the JSON actually holds. Writing normalised handles into a raw `curve` instead loads
|
|
3333
3154
|
without error and plays a different curve.
|
|
3334
3155
|
|
|
3335
|
-
**Four finite numbers, refused by name if they are not
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
plays, and is not the shape the spec named. A non-numeric handle was the same
|
|
3339
|
-
silence one character further in.
|
|
3156
|
+
**Four finite numbers, refused by name if they are not.** Unchecked,
|
|
3157
|
+
`[0.42, 0, 0.58]` would emit `"curve": [0.42, 0, 0.58, null]` โ a curve with a
|
|
3158
|
+
hole in it, which loads, plays, and is not the shape the spec named.
|
|
3340
3159
|
|
|
3341
3160
|
### 4.2 `setup` โ the setup pose, per slot
|
|
3342
3161
|
|
|
@@ -3344,12 +3163,9 @@ silence one character further in.
|
|
|
3344
3163
|
channels in 0..1. Both halves are refused by name: an entry that is not that object
|
|
3345
3164
|
โ `"lid_l": null`, or `"lid_l": "plate"` with the attachment name where its wrapper
|
|
3346
3165
|
belongs โ and a colour channel that is not a finite number in 0..1. The second
|
|
3347
|
-
spelling is the one worth knowing about
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
it also covers a slot the rig declares without attachments and a slot name that
|
|
3351
|
-
matches nothing at all โ the two corners where the emit-path version stayed silent
|
|
3352
|
-
(#307). Declaring a slot's setup pose here **and** on the rig slot is a
|
|
3166
|
+
spelling is the one worth knowing about: unchecked it would **hide the slot**,
|
|
3167
|
+
which is the opposite of what was asked. The check also covers a slot the rig
|
|
3168
|
+
declares without attachments and a slot name that matches nothing at all. Declaring a slot's setup pose here **and** on the rig slot is a
|
|
3353
3169
|
compile error (R3). Use whichever file owns the decision: a rig that is purely
|
|
3354
3170
|
structure puts it on the slot; a cut whose overlay mechanism is a decision about
|
|
3355
3171
|
time puts it here.
|
|
@@ -3359,7 +3175,7 @@ time puts it here.
|
|
|
3359
3175
|
| Field | Meaning |
|
|
3360
3176
|
| --- | --- |
|
|
3361
3177
|
| `duration` | seconds, declared and checked (R7) |
|
|
3362
|
-
| `loop` | a **player hint only** โ skeleton JSON has no loop field, so this is not emitted and no assertion or diff measure reads it. **Optional
|
|
3178
|
+
| `loop` | a **player hint only** โ skeleton JSON has no loop field, so this is not emitted and no assertion or diff measure reads it. **Optional** |
|
|
3363
3179
|
| `note` | free text |
|
|
3364
3180
|
| `tracks` | the timelines |
|
|
3365
3181
|
| `drawOrder` | the draw-order timeline โ ยง4.7. Not a track: it names no target |
|
|
@@ -3383,8 +3199,7 @@ resolves every member against the rig as that. So a `group` is the one target
|
|
|
3383
3199
|
where the property picks the family rather than the other way round (ยง4.4's โญ is
|
|
3384
3200
|
about the three **constraint** families, which are picked by the field), and a
|
|
3385
3201
|
property that is in none of the three tables is refused naming the group and all
|
|
3386
|
-
three vocabularies rather than being read as any of them
|
|
3387
|
-
([#661](https://github.com/firejune/rigc/issues/661); ยง4.4). A group whose members
|
|
3202
|
+
three vocabularies rather than being read as any of them (ยง4.4). A group whose members
|
|
3388
3203
|
are not all of one family is not refused as such: the first member that is not
|
|
3389
3204
|
what the property made it is the one named.
|
|
3390
3205
|
|
|
@@ -3443,60 +3258,22 @@ multipliers where `1` is setup; rotation is in degrees.
|
|
|
3443
3258
|
compile error** โ `animation "A" bone "B" has no timeline "P" (it has: translate,
|
|
3444
3259
|
translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate,
|
|
3445
3260
|
inherit)`, ยง5.1's row. The eleven are the three rows above read as one list, in the order the
|
|
3446
|
-
message prints them, and
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
- Until [#656](https://github.com/firejune/rigc/issues/656) it printed no list at
|
|
3453
|
-
all: the refusal read `bone "B" cannot take slot property "P"`, which named the
|
|
3454
|
-
**slot** family for whatever you had written โ `wobble`, `translateX`, `rgb` โ
|
|
3455
|
-
and told you nothing about what a bone does take. Nothing wrong reached disk
|
|
3456
|
-
then either, because the dispatch was already this table; what was missing was
|
|
3457
|
-
the way forward.
|
|
3458
|
-
- `rgba` and `attachment` are the only two names that sentence was ever right
|
|
3459
|
-
about, and for those the redirect survives as a clause beside the list:
|
|
3460
|
-
`. "rgba" is a slot timeline โ put the name in "slot"`. It is read off
|
|
3461
|
-
`SLOT_TRACKS`, so the two halves of the message cannot drift apart. A
|
|
3261
|
+
message prints them, and the refusal prints the same table the compiler dispatches
|
|
3262
|
+
on โ so what you are told a bone accepts is what it accepts.
|
|
3263
|
+
|
|
3264
|
+
- For `rgba` and `attachment` the refusal adds a redirect beside the list:
|
|
3265
|
+
`. "rgba" is a slot timeline โ put the name in "slot"`. A
|
|
3462
3266
|
**constraint** property written on a bone track (`mix`, `inertia`, `position`,
|
|
3463
3267
|
โฆ) never reaches this refusal at all โ it is refused first, by the row that
|
|
3464
3268
|
names the field its constraint's name belongs in (ยง4.12).
|
|
3465
|
-
- **Nothing derives this page's copy of the eleven from the table.** What keeps the
|
|
3466
|
-
two in step is the control that quotes the message โ `RF26` in `selftest.ts` โ
|
|
3467
|
-
which goes red if the list ever widens without this page moving with it, and
|
|
3468
|
-
`RF27` holds the slot clause the same way. The selftest's spelling census
|
|
3469
|
-
(`PS144`) reads the eleven off the same message and compares them against the
|
|
3470
|
-
eleven it actually poses, both ways, which is what makes the list checkable at
|
|
3471
|
-
all: it was stated there too until the refusal had something to state. It was
|
|
3472
|
-
ten until [#733](https://github.com/firejune/rigc/issues/733) added `inherit`,
|
|
3473
|
-
and that landing is what moved `RF26`, this page and the census together.
|
|
3474
3269
|
|
|
3475
3270
|
โ ๏ธ **A `slot` track's `property` is one of the six above, and anything else is a
|
|
3476
3271
|
compile error** โ `animation "A" slot "X" has no timeline "P" (it has:
|
|
3477
|
-
attachment, rgba, rgb, alpha, rgba2, rgb2)`, ยง5.1's row.
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
(`A00_ROUNDTRIP_PARSE: threw: Invalid timeline type for a slot`) โ while the
|
|
3483
|
-
one-channel spelling of the same mistake was refused at compile as `rgba value
|
|
3484
|
-
needs 4 channels, got 1`, a message about a key you had not written.
|
|
3485
|
-
|
|
3486
|
-
- The six are the emitter's own dispatch table (`SLOT_TRACKS` in
|
|
3487
|
-
`src/compile.ts`), in the order `SkeletonJson.readAnimation` switches on
|
|
3488
|
-
them โ which makes it the format's whole slot switch: `compileTrack` reads it
|
|
3489
|
-
to pick its branch, and the refusal prints `Object.keys` of the same object,
|
|
3490
|
-
so what you are told a slot accepts is what it accepts.
|
|
3491
|
-
- **Nothing derives this page's copy of that list from the table**, and it is
|
|
3492
|
-
six names long: no `DQ*`/`RD*`/`CUR*` control reads ยง4.4 (the only gated
|
|
3493
|
-
table on this page is ยง3.5.2.1's, held by `RD01`โ`RD06`). What keeps the two
|
|
3494
|
-
in step is the control that quotes the message โ `RF23` in `selftest.ts` โ
|
|
3495
|
-
which goes red if the accepted list ever widens without this page moving with
|
|
3496
|
-
it. It did, on the day `rgba2` was added
|
|
3497
|
-
([#690](https://github.com/firejune/rigc/issues/690)) and again when `rgb`,
|
|
3498
|
-
`alpha` and `rgb2` were ([#730](https://github.com/firejune/rigc/issues/730)),
|
|
3499
|
-
which is the mechanism working rather than a hole in it.
|
|
3272
|
+
attachment, rgba, rgb, alpha, rgba2, rgb2)`, ยง5.1's row.
|
|
3273
|
+
|
|
3274
|
+
- The six are in the order `SkeletonJson.readAnimation` switches on them โ the
|
|
3275
|
+
format's whole slot switch โ and the refusal prints the table the compiler
|
|
3276
|
+
dispatches on, so what you are told a slot accepts is what it accepts.
|
|
3500
3277
|
- ๐จ **`rgb`, `alpha` and `rgb2` are the SEPARABLE colour timelines, and they
|
|
3501
3278
|
are not spellings of `rgba`.** Each poses part of the slot's colour and leaves
|
|
3502
3279
|
the rest exactly where it was: `rgb` writes the light colour's r g b and never
|
|
@@ -3542,7 +3319,7 @@ needs 4 channels, got 1`, a message about a key you had not written.
|
|
|
3542
3319
|
- The six are every slot timeline the format has; anything else is refused
|
|
3543
3320
|
here, and would be refused by the runtime's own reader too (`Invalid timeline
|
|
3544
3321
|
type for a slot`). `A12_NO_DARK_COLOR` refuses `rgb2` โ and `rgba2`, and the
|
|
3545
|
-
slot field โ in a file under the `spine-html` profile
|
|
3322
|
+
slot field โ in a file under the `spine-html` profile.
|
|
3546
3323
|
`sequence` is a timeline on an **attachment**, not on a slot โ it is the
|
|
3547
3324
|
family beside `deform`, ยง4.13.
|
|
3548
3325
|
|
|
@@ -3552,26 +3329,13 @@ and anything else is a compile error** โ `animation "A" group "G" has no timel
|
|
|
3552
3329
|
โฆ)`, ยง5.1's row. It is the only refusal on this page that prints **three** lists,
|
|
3553
3330
|
and the reason is ยง4.3's: a group's family is decided by the property, so a
|
|
3554
3331
|
property no table claims leaves the compiler with no family to answer for. All
|
|
3555
|
-
three
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
resolved against the rig.
|
|
3559
|
-
|
|
3560
|
-
- Until [#661](https://github.com/firejune/rigc/issues/661) a group of **bones**
|
|
3561
|
-
with a misspelled bone property read `animation "A" targets unknown slot "M"`:
|
|
3562
|
-
with no table claiming the property the track fell through to the slot branch,
|
|
3563
|
-
and what you were told was that the first member is not a slot โ on a file that
|
|
3564
|
-
named neither a slot nor that member. A group of **slots** got ยง4.4's slot row
|
|
3565
|
-
instead (`slot "M" has no timeline "P" (it has: attachment, rgba, rgba2)`), which is
|
|
3566
|
-
true of the member and names one family out of three on a track whose family
|
|
3567
|
-
nothing had determined.
|
|
3332
|
+
three are the tables the compiler dispatches on, and the refusal comes after the
|
|
3333
|
+
group's own existence check and before any member is resolved against the rig.
|
|
3334
|
+
|
|
3568
3335
|
- **A constraint property never reaches it.** `position`, `spacing` and `time` are
|
|
3569
3336
|
refused first with the field their constraint's name goes in (ยง4.12), and a
|
|
3570
3337
|
physics timeline spelled correctly is not an error at all โ a group of physics
|
|
3571
3338
|
constraints is how several are tuned in one track.
|
|
3572
|
-
- `RF30`โ`RF34` in `selftest.ts` quote this message; the three lists are typed
|
|
3573
|
-
there rather than read off the tables, so widening any of the three without
|
|
3574
|
-
moving this page turns them red.
|
|
3575
3339
|
|
|
3576
3340
|
**A physics constraint's six tuning timelines override ยง4.6's table for the
|
|
3577
3341
|
length of an animation.** `{ "physics": "hair", "property": "wind", "keys": [โฆ] }`
|
|
@@ -3592,21 +3356,19 @@ a delta from the constraint's own setting.
|
|
|
3592
3356
|
key states a mass and the pose holds `1 / mass`, so a `mass` key of `0` is an
|
|
3593
3357
|
infinite inverse mass โ every velocity and offset is NaN from the first step.
|
|
3594
3358
|
- **Four of the seven are bounded, and a key outside its bound is a compile
|
|
3595
|
-
error
|
|
3359
|
+
error.** `mass` must be
|
|
3596
3360
|
`> 0`, `damping` must be inside the closed `[0, 1]`, and `mix` and `strength`
|
|
3597
3361
|
must be `0` or more. `A23_PHYSICS_CONSTRAINT_EFFECTIVE` applies the same four to
|
|
3598
3362
|
a file rigc did not write, naming the animation, the constraint, the key time
|
|
3599
3363
|
and the value โ so the compiler is where a spec you wrote is refused, and the
|
|
3600
3364
|
assertion is where an import is.
|
|
3601
|
-
- ๐ **Each way out of a bound has a basis, and the refusal says which
|
|
3602
|
-
|
|
3365
|
+
- ๐ **Each way out of a bound has a basis, and the refusal says which.**
|
|
3366
|
+
**arithmetic** means the
|
|
3603
3367
|
runtime cannot compute the value โ an expression in the integrator is
|
|
3604
3368
|
non-finite at it โ and the sentence names the expression and its lines;
|
|
3605
3369
|
**behavioural** means the runtime runs it, finitely, and the rig runs wrongly,
|
|
3606
3370
|
so refusing it is rigc's call and the sentence says so and says what the value
|
|
3607
|
-
does. Two of the eight ways out are arithmetic.
|
|
3608
|
-
`PHYSICS_POSE_RULES` rows' own `basis`, verbatim, and `CUR100` holds it to them;
|
|
3609
|
-
every basis is re-measured through spine-core by `T113` on every run.
|
|
3371
|
+
does. Two of the eight ways out are arithmetic.
|
|
3610
3372
|
|
|
3611
3373
|
| field | at rest | on a key | basis |
|
|
3612
3374
|
| --- | --- | --- | --- |
|
|
@@ -3622,18 +3384,15 @@ a delta from the constraint's own setting.
|
|
|
3622
3384
|
finite on every one. Every run-away overflows at some horizon, which is why
|
|
3623
3385
|
`damping` above 1 is behavioural although it diverges.
|
|
3624
3386
|
- โ๏ธ **A negative `mix` is refused on a physics constraint and accepted on a
|
|
3625
|
-
transform constraint, and neither is the runtime's arithmetic
|
|
3626
|
-
([#798](https://github.com/firejune/rigc/issues/798)). [measured] both are a
|
|
3387
|
+
transform constraint, and neither is the runtime's arithmetic.** [measured] both are a
|
|
3627
3388
|
finite, mirrored scale: a physics constraint resting at `mix` โ0.5 moves its
|
|
3628
3389
|
bone by exactly the opposite of +0.5 on every step, and a transform constraint
|
|
3629
3390
|
at `mixRotate` โ0.5 rotates its bone by the negative of +0.5's. What differs is
|
|
3630
3391
|
the range the runtime documents for each field โ `PhysicsConstraintPose.mix` is
|
|
3631
3392
|
"a percentage (0+)", `TransformConstraintPose.mixRotate` "a percentage
|
|
3632
|
-
(unbounded)" โ and each rule follows its own.
|
|
3633
|
-
documented ranges against the runtime; `T105` holds the transform side's
|
|
3634
|
-
acceptance.
|
|
3393
|
+
(unbounded)" โ and each rule follows its own.
|
|
3635
3394
|
- โ ๏ธ **`damping`'s bound depends on the constraint's `fps`, and a rig played at
|
|
3636
|
-
60 fps hides it
|
|
3395
|
+
60 fps hides it.** The
|
|
3637
3396
|
decay is `damping ** (60 * step)` with `step` = `1 / fps`, so the exponent is
|
|
3638
3397
|
exactly 1 at 60 fps, where a negative damping only flips the velocity's sign
|
|
3639
3398
|
each step and can look like a jiggle settling. Wherever `60 / fps` is not a
|
|
@@ -3642,15 +3401,13 @@ a delta from the constraint's own setting.
|
|
|
3642
3401
|
stays finite at 60 fps and at 30 (exponent 2), and is NaN within three steps of
|
|
3643
3402
|
the key at 45 and at 120. Above `1` every velocity grows on every step and
|
|
3644
3403
|
diverges at every rate. The refusal names the exponent and the rate for this
|
|
3645
|
-
reason
|
|
3646
|
-
- โ
**Both ends of `damping` are inside the bound**
|
|
3647
|
-
([#794](https://github.com/firejune/rigc/issues/794)). `1 ** x` is 1 and
|
|
3404
|
+
reason.
|
|
3405
|
+
- โ
**Both ends of `damping` are inside the bound.** `1 ** x` is 1 and
|
|
3648
3406
|
`0 ** x` is 0 for every positive exponent, so neither depends on `fps`: at `1`
|
|
3649
3407
|
the velocity never decays and the jiggle holds for as long as it runs, and at
|
|
3650
3408
|
`0` every velocity is zeroed on every step, so the offset follows the bone with
|
|
3651
3409
|
no overshoot. Both are finite, at rest and keyed โ [measured] 120 steps from
|
|
3652
|
-
`Physics.reset` at 60, 45 and 30 fps on the generated physics fixture
|
|
3653
|
-
`T109`โ`T111` hold. โ ๏ธ `1` is also what a 4.2 file means by saying nothing:
|
|
3410
|
+
`Physics.reset` at 60, 45 and 30 fps on the generated physics fixture. โ ๏ธ `1` is also what a 4.2 file means by saying nothing:
|
|
3654
3411
|
4.2's parser reads an omitted `damping` as `1` where 4.3's reads `0.85`
|
|
3655
3412
|
(ยง4.6), so a rig migrated from 4.2 that keys "the default" keys `1`.
|
|
3656
3413
|
- ๐ซ **`inertia`, `wind` and `gravity` are bounded nowhere, and neither is the top
|
|
@@ -3666,7 +3423,7 @@ a delta from the constraint's own setting.
|
|
|
3666
3423
|
`strength`, 0 takes the restoring term out of the velocity update and leaves
|
|
3667
3424
|
`damping` and `inertia` applied โ the offset is not pulled back *while the key
|
|
3668
3425
|
holds*, which is "physics released" for that span, and the next key pulls it
|
|
3669
|
-
back
|
|
3426
|
+
back. At rest the two
|
|
3670
3427
|
part ways: a `strength` of 0 is a constraint nothing pulls back, which `A23`
|
|
3671
3428
|
refuses, and a `mix` of 0 is a constraint muted until an animation keys it
|
|
3672
3429
|
above 0 โ the next bullet. A **negative** setup `strength` is refused too, with
|
|
@@ -3674,11 +3431,9 @@ a delta from the constraint's own setting.
|
|
|
3674
3431
|
added to the offset instead of taken out, so the offset is **pushed away** and
|
|
3675
3432
|
grows โ [measured] resting at โ100 on the generated physics fixture it grew
|
|
3676
3433
|
28.35ร over 0.5 s with no sign change, where resting at 100 it swung back
|
|
3677
|
-
through 0. `A23`
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
one number ([#748](https://github.com/firejune/rigc/issues/748)).
|
|
3681
|
-
- โ ๏ธ **A setup `mix` of `0` is legal when some animation keys it above 0** ([#743](https://github.com/firejune/rigc/issues/743)).
|
|
3434
|
+
through 0. `A23` and the key's refusal read that sentence from one source, so
|
|
3435
|
+
the two cannot say different things about one number.
|
|
3436
|
+
- โ ๏ธ **A setup `mix` of `0` is legal when some animation keys it above 0.**
|
|
3682
3437
|
A constraint muted *at rest* is a rig whose physics is off until an animation
|
|
3683
3438
|
switches it on, which is a design rather than the silence `A23` was built for.
|
|
3684
3439
|
What `A23` refuses is the constraint nothing rescues: muted at setup and keyed
|
|
@@ -3690,10 +3445,10 @@ a delta from the constraint's own setting.
|
|
|
3690
3445
|
"Keyed above 0" means **any value the timeline poses**, which includes a
|
|
3691
3446
|
Bezier between two keys of 0 whose handles lie above 0: the runtime
|
|
3692
3447
|
interpolates through the curve's samples, not between the keys, and such a
|
|
3693
|
-
pair moves the bone
|
|
3448
|
+
pair moves the bone. The
|
|
3694
3449
|
same reading is `A36`'s and `A37`'s (ยง4.12).
|
|
3695
3450
|
- ๐ **What a `strength` key of `0` costs, measured through spine-core** on the
|
|
3696
|
-
generated overlay fixture, stepping at 60 fps from `Physics.reset
|
|
3451
|
+
generated overlay fixture, stepping at 60 fps from `Physics.reset`. With
|
|
3697
3452
|
no wind or gravity the offset coasts to a limit rather than running away โ a
|
|
3698
3453
|
0.5 s release and a 2.0 s release end **0.95 %** apart โ and the restoring key
|
|
3699
3454
|
takes it from 5.5063 back under 0.01 in **54 steps**. With `gravity -40` pulling,
|
|
@@ -3705,8 +3460,7 @@ a delta from the constraint's own setting.
|
|
|
3705
3460
|
the first sub-step and **still NaN after the restoring key**, and a keyed
|
|
3706
3461
|
`damping` of `2` was still 6.9e4 two seconds later.
|
|
3707
3462
|
|
|
3708
|
-
**`"physics": "*"` is the physics timeline that names no constraint
|
|
3709
|
-
([#726](https://github.com/firejune/rigc/issues/726)). The skeleton file writes it
|
|
3463
|
+
**`"physics": "*"` is the physics timeline that names no constraint.** The skeleton file writes it
|
|
3710
3464
|
under the empty name โ `animations.<a>.physics[""]`, which `SkeletonJson` loads as
|
|
3711
3465
|
constraint index `-1` without looking anything up โ and the runtime then applies it
|
|
3712
3466
|
to every active physics constraint whose own data declares the keyed property
|
|
@@ -3759,14 +3513,7 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3759
3513
|
refusal says which skins were searched and what the slot does have (ยง5.1).
|
|
3760
3514
|
- A rig with **no `default` skin at all** โ every attachment in named skins,
|
|
3761
3515
|
which is the shape an editor export of a multi-skin character gives back โ is
|
|
3762
|
-
therefore a rig whose attachment keys work.
|
|
3763
|
-
[#695](https://github.com/firejune/rigc/issues/695) it was not: keys were
|
|
3764
|
-
checked against the default skin alone, so a named-skin name was refused as
|
|
3765
|
-
unknown, and a rig with no default skin had **every** attachment key refused,
|
|
3766
|
-
including ones whose art is in the first named skin. The setup pose resolved
|
|
3767
|
-
across skins the whole time (ยง4.2), so the two halves of one slot disagreed โ
|
|
3768
|
-
`slots[].attachment: "plain"` was accepted and a key naming `plain` on that
|
|
3769
|
-
same slot was not.
|
|
3516
|
+
therefore a rig whose attachment keys work.
|
|
3770
3517
|
- โ ๏ธ **A `deform` track is the other way round and names its skin outright**
|
|
3771
3518
|
(ยง4.11.5), because the format keys a deform timeline on a `skin/slot/attachment`
|
|
3772
3519
|
triple and a deform run is geometry for one attachment object. An attachment
|
|
@@ -3782,7 +3529,7 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3782
3529
|
- **Key times are emitted as float32s like every other number, and never stored
|
|
3783
3530
|
LATER than you wrote them.** Every emitted number is the shortest decimal naming
|
|
3784
3531
|
its float32 โ the text the editor writes, and the precision the runtime keeps,
|
|
3785
|
-
because `spine-core` reads a timeline's frames into a `Float32Array
|
|
3532
|
+
because `spine-core` reads a timeline's frames into a `Float32Array`.
|
|
3786
3533
|
A time that already names a float โ `0.5`, `0.2`, the editor's `1.4333333` โ is
|
|
3787
3534
|
written as you wrote it. A time that does not โ `2/12` computed in doubles, a key
|
|
3788
3535
|
moved by `lag` or `stagger` โ steps to the **largest float not above it**, never
|
|
@@ -3792,9 +3539,7 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3792
3539
|
*larger* than either, so a key stored there is applied at sample **3** of a 12 fps
|
|
3793
3540
|
playback and not sample 2 โ a whole frame late, with nothing raised. On a
|
|
3794
3541
|
**stepped** timeline (an attachment timeline always is) that is the wrong picture
|
|
3795
|
-
rather than a slightly wrong value
|
|
3796
|
-
late for exactly this, on the six-decimal grid rigc emitted until #716, until the
|
|
3797
|
-
run's own frame check caught it (issue #99). Stepping down cannot do that; the
|
|
3542
|
+
rather than a slightly wrong value. Stepping down cannot do that; the
|
|
3798
3543
|
worst it can do is store a key one float early โ 1.5e-8 s at 1/6 s โ on the sample
|
|
3799
3544
|
it was written for. โ ๏ธ What this does **not** protect you from is rounding your
|
|
3800
3545
|
own times before you write them โ write `2/12`, not `0.1667`, and let the compiler
|
|
@@ -3808,13 +3553,11 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3808
3553
|
and a stepped key there sits above the sample that was meant to see it. On an
|
|
3809
3554
|
interpolated timeline that costs a few ULPs of value and nothing else. On a
|
|
3810
3555
|
**stepped** one it is the whole frame โ and on the last sample it is the whole
|
|
3811
|
-
event, because there is no later sample to catch it. Measured on
|
|
3812
|
-
|
|
3556
|
+
event, because there is no later sample to catch it. Measured on a 6.5 s shot
|
|
3557
|
+
at 12 fps: **13 of its 78 sample times are affected** (f6, f15, f18, f21, f24,
|
|
3813
3558
|
f27, f60, f63, f66, f69, f72, f75, f78), and an attachment key written at the
|
|
3814
3559
|
declared duration `6.5` never fired at all against an accumulated
|
|
3815
|
-
`6.499999999999994
|
|
3816
|
-
already fixed, and cost that run three builds
|
|
3817
|
-
([`2026-08-26-rung5-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-26-rung5-1/LOOP.md), ยง8). โ **For a
|
|
3560
|
+
`6.499999999999994`. โ **For a
|
|
3818
3561
|
stepped timeline, write a time a little below `T` โ `T โ 1e-6` still works, at
|
|
3819
3562
|
any magnitude โ rather than `T`.** What makes it work is not the size of the step:
|
|
3820
3563
|
any time below `T` is stored on a float below `T`, because a time that names a
|
|
@@ -3831,10 +3574,10 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3831
3574
|
step at the duration โ 4.8e-7 s at 5 s, 3.8e-6 s at 32 s โ which is the most a
|
|
3832
3575
|
key you put exactly *on* a duration the float cannot hold is stored past it, so
|
|
3833
3576
|
such a key is fine. R7's frame of slack does not apply in this direction and
|
|
3834
|
-
would not see this:
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3577
|
+
would not see this: key times rounded to 4 dp upstream put a one-frame reveal
|
|
3578
|
+
0.000034 s past a 68/12 s duration while another track sat on the declared
|
|
3579
|
+
duration, so the animation's *longest* key time looked right โ and the reveal
|
|
3580
|
+
never appeared. If you want a key on the last
|
|
3838
3581
|
sample, write the duration's own value; if you want the animation to run longer,
|
|
3839
3582
|
say so in `duration`.
|
|
3840
3583
|
- `ease` names an entry of `easings`, or the literal `"stepped"`. Absent = linear โ
|
|
@@ -3847,7 +3590,7 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3847
3590
|
key equals the next key's *as emitted* โ a `translate` whose x moves and whose y
|
|
3848
3591
|
does not is **not** a hold โ the curve would run from a value to the same value and
|
|
3849
3592
|
draw nothing, and the editor writes that segment as `"curve": "stepped"`. So rigc
|
|
3850
|
-
does too (ยง10.4
|
|
3593
|
+
does too (ยง10.4): the frames are byte-identical either way, and a build
|
|
3851
3594
|
stops differing from its own editor export on `diff`'s `animations.curve_kinds`. A
|
|
3852
3595
|
raw `curve` is **not** rewritten โ it states the file's own numbers verbatim, and
|
|
3853
3596
|
the editor's own exports do carry beziers over holds, so a transcription has to be
|
|
@@ -3863,12 +3606,10 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3863
3606
|
### 4.5.1 A group track's per-member values โ a map, or a model
|
|
3864
3607
|
|
|
3865
3608
|
โญ **`groups` keys several bones identically, and on a face the whole content of
|
|
3866
|
-
the motion is that each part gets a *different* number.**
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
different values โ and exactly **one** of the twenty was a `groups` entry, the
|
|
3871
|
-
pair that happened to share `cos t`.
|
|
3609
|
+
the motion is that each part gets a *different* number.** `gallery/portrait`'s
|
|
3610
|
+
held 12ยฐ yaw, written as plain tracks, is the same two properties on six sibling
|
|
3611
|
+
bones โ identical times, identical easings, identical key counts, six different
|
|
3612
|
+
values.
|
|
3872
3613
|
|
|
3873
3614
|
Two spellings land, and **which one is right depends on whether the numbers are
|
|
3874
3615
|
decisions or arithmetic.**
|
|
@@ -3884,9 +3625,8 @@ would be for that one member, so nothing about the value shapes in ยง4.4 changes
|
|
|
3884
3625
|
{ "t": 2.2, "v": [0] } ] }
|
|
3885
3626
|
```
|
|
3886
3627
|
|
|
3887
|
-
A non-map `v`
|
|
3888
|
-
|
|
3889
|
-
separate tracks produced. Reach for this when the six numbers are six
|
|
3628
|
+
A non-map `v` means every member gets it, and the emitted file is **byte for
|
|
3629
|
+
byte** the one six separate tracks produce. Reach for this when the six numbers are six
|
|
3890
3630
|
**judgements**: hand-picked swings on a row of hanging locks, a decided offset
|
|
3891
3631
|
per part.
|
|
3892
3632
|
|
|
@@ -3905,9 +3645,8 @@ Same move as ยง4.11.1 on the bone half of the same turn:
|
|
|
3905
3645
|
|
|
3906
3646
|
โญ **The depth table is the reason this is worth more than the map, and it is not
|
|
3907
3647
|
the line count.** FACE ยง2's sharp edge is that **`x` is in the file and `z` is
|
|
3908
|
-
not** โ
|
|
3909
|
-
|
|
3910
|
-
`derive` key puts them in the file that uses them, and FACE ยง3's whole argument
|
|
3648
|
+
not** โ with plain values, every depth that produced every number lives outside
|
|
3649
|
+
the specs. A `derive` key puts them in the file that uses them, and FACE ยง3's whole argument
|
|
3911
3650
|
is that a **depth** is the decision while a **residual** is not.
|
|
3912
3651
|
|
|
3913
3652
|
**The two kinds, and the property picks the projection:**
|
|
@@ -3933,8 +3672,7 @@ a head swing the other way (FACE ยง2). That sign is the one parameter here no
|
|
|
3933
3672
|
assertion can check, so the closed form is the arbiter: a part with
|
|
3934
3673
|
`depth > carried` gets a **negative** residual, and FACE ยง3 makes exactly that
|
|
3935
3674
|
the nose diagnostic โ *if the nose's residual is not negative, the depths are
|
|
3936
|
-
wrong*.
|
|
3937
|
-
missing here and stated backwards in the field reference.)
|
|
3675
|
+
wrong*.
|
|
3938
3676
|
|
|
3939
3677
|
`carried`
|
|
3940
3678
|
(default 0) is **the depth whose shift a parent bone already applies** โ FACE
|
|
@@ -3988,8 +3726,8 @@ iris counter-scale is `1/scaleX` of the socket, and it stays two ordinary
|
|
|
3988
3726
|
`groups` entries with one shared value each โ because a counter-scale belongs to
|
|
3989
3727
|
the **socket**, not to the part: `spark_l` at local `x = โ11` takes the same
|
|
3990
3728
|
number as `iris_l` at `0`. A shared value on a group is the correct statement of
|
|
3991
|
-
that, and forcing it into a per-member model would
|
|
3992
|
-
|
|
3729
|
+
that, and forcing it into a per-member model would be a worse spec that merely
|
|
3730
|
+
used the field.
|
|
3993
3731
|
|
|
3994
3732
|
๐ญ **Deliberately not built:** a per-member `v` on a track that names no group (a
|
|
3995
3733
|
value map needs members to name); per-member **easings** or key **times** (the
|
|
@@ -4037,7 +3775,7 @@ group members (the per-member values of one track, side by side โ issue #295)
|
|
|
4037
3775
|
that face, because it is the only feature in front of the skull surface โ and
|
|
4038
3776
|
FACE ยง3 makes it the diagnostic: *if the nose's residual is not negative, the
|
|
4039
3777
|
depths are wrong.* That check is arithmetic rather than a render, and this block
|
|
4040
|
-
is where it is
|
|
3778
|
+
is where it is legible.
|
|
4041
3779
|
|
|
4042
3780
|
**It quotes; it does not re-derive.** Every value is the one the compiler
|
|
4043
3781
|
emitted, so the block and the artifact cannot disagree โ a report that evaluated
|
|
@@ -4058,34 +3796,30 @@ mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
|
4058
3796
|
`constraints` array. Seven of them โ the six tuning numbers and `mix` โ can also
|
|
4059
3797
|
be **keyed over time** as `tracks` entries naming this constraint (ยง4.4); this
|
|
4060
3798
|
table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and a `damping` above 1
|
|
4061
|
-
never settles โ both are `A23`, here and on every timeline key that states them
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
with both ends in: `1` never decays and `0` zeroes the velocity on every step,
|
|
4066
|
-
both finite at every rate ([#794](https://github.com/firejune/rigc/issues/794)).
|
|
3799
|
+
never settles โ both are `A23`, here and on every timeline key that states them;
|
|
3800
|
+
a `damping` below 0 is refused as well, and whether it is NaN depends on `fps` โ
|
|
3801
|
+
ยง4.4. `damping` is `[0, 1]` with both ends in: `1` never decays and `0` zeroes the
|
|
3802
|
+
velocity on every step, both finite at every rate.
|
|
4067
3803
|
โ ๏ธ An omitted `damping` is `0.85` to 4.3's parser (`SkeletonJson.js:308` in
|
|
4068
3804
|
4.3.13) and `1` to 4.2's (`SkeletonJson.js:242` in 4.2.120), so a constraint
|
|
4069
3805
|
migrated from 4.2 that stated nothing may arrive stating `1` โ which builds. โ ๏ธ `strength: 0` is `A23` **here and not on a key**:
|
|
4070
3806
|
at rest it is a constraint nothing pulls back, and on a key it is a release somebody
|
|
4071
|
-
asked for, which ยง4.4 states with the measurement behind it
|
|
4072
|
-
([#727](https://github.com/firejune/rigc/issues/727)). A negative `strength` is
|
|
3807
|
+
asked for, which ยง4.4 states with the measurement behind it. A negative `strength` is
|
|
4073
3808
|
`A23` here and on a key, and here its sentence is its own: the offset is pushed
|
|
4074
|
-
away and grows
|
|
3809
|
+
away and grows. `mix: 0` is the one value
|
|
4075
3810
|
here an animation can answer for: it rests the constraint **muted**, which is
|
|
4076
3811
|
legal, and `A23` names it only when no timeline in any animation keys that `mix`
|
|
4077
|
-
above 0 โ on a key it is the mute for a span (ยง4.4
|
|
3812
|
+
above 0 โ on a key it is the mute for a span (ยง4.4). None of the
|
|
4078
3813
|
three is a compile error โ a setup value this table states reaches the gate, where the whole
|
|
4079
3814
|
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
|
|
4080
|
-
number: a non-number
|
|
4081
|
-
|
|
4082
|
-
settles with no word from anybody (#307).
|
|
3815
|
+
number: a non-number would reach the file as `null`, which the runtime reads as
|
|
3816
|
+
**zero**, so `"mass": "heavy"` would ship a constraint that never settles.
|
|
4083
3817
|
|
|
4084
3818
|
`mix` is a player-side `AnimationStateData` config and is **not** emitted into
|
|
4085
|
-
skeleton JSON
|
|
3819
|
+
skeleton JSON, so the parser is the only check it gets: it is
|
|
4086
3820
|
`{ "default": <seconds>, "pairs"?: [["<from>", "<to>", <seconds>], โฆ] }`, and a
|
|
4087
|
-
`default` that is not a number
|
|
4088
|
-
|
|
3821
|
+
`default` that is not a number would pass the gate and the round trip before
|
|
3822
|
+
becoming a `NaN` mix duration in the player.
|
|
4089
3823
|
|
|
4090
3824
|
### 4.7 `drawOrder` โ reordering the slots over time
|
|
4091
3825
|
|
|
@@ -4228,11 +3962,9 @@ here was measured off a real rig. Copy the shape, not the values.
|
|
|
4228
3962
|
the constraint (`SkeletonJson:155`) and once on **every timeline key**
|
|
4229
3963
|
(`:912`) โ so a key that omits `bendPositive` does not inherit the constraint's
|
|
4230
3964
|
value, it asserts `true`. A rig declaring `bendPositive: false` under a timeline
|
|
4231
|
-
that keys only `mix` therefore
|
|
4232
|
-
with the field still in the file and inert
|
|
4233
|
-
|
|
4234
|
-
stamps the rig's value onto every emitted key, so the declaration reaches the
|
|
4235
|
-
runtime. **Stating a flag on every key still overrides the rig** โ the format
|
|
3965
|
+
that keys only `mix` would therefore bend the *other* way for the whole
|
|
3966
|
+
animation, with the field still in the file and inert. So rigc stamps the rig's
|
|
3967
|
+
value onto every emitted key, and the declaration reaches the runtime. **Stating a flag on every key still overrides the rig** โ the format
|
|
4236
3968
|
keys them per key on purpose, a bend that flips partway through is a real thing
|
|
4237
3969
|
to write, and it is what the editor's own export does.
|
|
4238
3970
|
- ๐ **A spec that came out of `ingest` (ยง0.3) states all three on every key, and
|
|
@@ -4488,8 +4220,7 @@ emitted file, measuring the array's length from the attachment rather than assum
|
|
|
4488
4220
|
an encoding.
|
|
4489
4221
|
|
|
4490
4222
|
๐ **A run is not required to be an even number of numbers, and it is not required
|
|
4491
|
-
to start on an even index.**
|
|
4492
|
-
#576. Nothing in either reader aligns a run to a pair: `SkeletonJson` does
|
|
4223
|
+
to start on an even index.** Nothing in either reader aligns a run to a pair: `SkeletonJson` does
|
|
4493
4224
|
`Utils.arrayCopy(vertices, 0, deform, offset, vertices.length)` โ a raw copy, at
|
|
4494
4225
|
the raw index the key gives โ and `SkeletonBinary` reads a count and a start and
|
|
4495
4226
|
fills `for (let v = start; v < end; v++)`. So a run may begin and end mid-pair,
|
|
@@ -4499,24 +4230,21 @@ the whole delta minus one leading zero.
|
|
|
4499
4230
|
|
|
4500
4231
|
โญ **An odd run's last number is an x with no y beside it, and that is a
|
|
4501
4232
|
statement, not an accident.** It moves that vertex in x and leaves its y at the
|
|
4502
|
-
setup value, because the parser copies your numbers and touches nothing else.
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
โ ๏ธ What you lose with those rows is a typo filter: `offset: 3` written where
|
|
4511
|
-
`fromVertex: 3` was meant now compiles. It always half-did โ `offset: 4` meant as
|
|
4512
|
-
vertex 4 lands on vertex 2 and was never refused โ so read the field name twice.
|
|
4233
|
+
setup value, because the parser copies your numbers and touches nothing else.
|
|
4234
|
+
Padding a `0` to make the run even *changes what plays* wherever that setup y is
|
|
4235
|
+
non-zero, so there is no second spelling of such a key โ which is why an odd run
|
|
4236
|
+
is accepted rather than refused.
|
|
4237
|
+
|
|
4238
|
+
โ ๏ธ What that costs is a typo filter: `offset: 3` written where `fromVertex: 3` was
|
|
4239
|
+
meant compiles, as `offset: 4` meant as vertex 4 lands on vertex 2 โ so read the
|
|
4240
|
+
field name twice.
|
|
4513
4241
|
`offset` is an index into the deform array; `fromVertex` is a vertex.
|
|
4514
4242
|
|
|
4515
4243
|
๐ผ๏ธ **Worked examples, and they use a deform for four different things** โ all
|
|
4516
4244
|
four are repository material rather than part of the published package, so the
|
|
4517
4245
|
links go to GitHub.
|
|
4518
4246
|
[`gallery/squash`](https://github.com/firejune/rigc/tree/main/gallery/squash) โ a 9-vertex ball squashed about its contact point,
|
|
4519
|
-
from the two affine transforms its keys
|
|
4247
|
+
from the two affine transforms its keys **state**.
|
|
4520
4248
|
[`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait) โ a 2.5D head turn, where the keys are the
|
|
4521
4249
|
**projection of a yaw** rather than a squash: two grid meshes whose columns are
|
|
4522
4250
|
placed to sample a cosine, and a measured account of the angle past which the
|
|
@@ -4545,12 +4273,10 @@ build.
|
|
|
4545
4273
|
half: the key names a transform, the compiler evaluates it over the attachment's
|
|
4546
4274
|
own setup geometry, and the emitted file carries the numbers.
|
|
4547
4275
|
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
second full table, which is why that example's own angle sweep needed a
|
|
4553
|
-
throwaway script that never made it into the repository. It is now four lines:
|
|
4276
|
+
`gallery/portrait`'s held 12ยฐ head yaw, written as a table, is **160 vertex
|
|
4277
|
+
offsets across 8 keys**, and not one of them is a judgement โ every one is
|
|
4278
|
+
`xยท(cos t โ 1) โ zยทsin t` at a different column (FACE ยง1), and a second angle is
|
|
4279
|
+
a second full table. As a model it is four lines:
|
|
4554
4280
|
|
|
4555
4281
|
```json
|
|
4556
4282
|
"deform": [
|
|
@@ -4591,8 +4317,7 @@ convention:
|
|
|
4591
4317
|
**It covers every vertex, always.** A transform is a model of the attachment, not
|
|
4592
4318
|
an edit of part of it, so it starts at deform index 0 and runs to the end โ which
|
|
4593
4319
|
is why `fromVertex` and `offset` are refused beside it. A model applied to part
|
|
4594
|
-
of a run leaves a **step at the run's edge
|
|
4595
|
-
[#313](https://github.com/firejune/rigc/issues/313) records. If you want a
|
|
4320
|
+
of a run leaves a **step at the run's edge**. If you want a
|
|
4596
4321
|
partial run, write it.
|
|
4597
4322
|
|
|
4598
4323
|
**There is no `parallax` kind, and the reason is worth stating.** A pure depth
|
|
@@ -4619,8 +4344,7 @@ parameters that key states, and what happens between two keys is still the
|
|
|
4619
4344
|
timeline's own single 0..1 blend channel. Sweeping an angle is editing one number
|
|
4620
4345
|
per key.
|
|
4621
4346
|
|
|
4622
|
-
**It works on a weighted attachment, and reads world coordinates there
|
|
4623
|
-
([#389](https://github.com/firejune/rigc/issues/389)). On an unweighted
|
|
4347
|
+
**It works on a weighted attachment, and reads world coordinates there.** On an unweighted
|
|
4624
4348
|
attachment the deform array is one `x, y` per vertex in the slot bone's space,
|
|
4625
4349
|
and the model is evaluated in that space. The same is true of a weighted
|
|
4626
4350
|
attachment while every vertex has exactly one bone and they all share it โ the
|
|
@@ -4660,8 +4384,7 @@ consequences. One kind is the exception and it is an exception with a proof:
|
|
|
4660
4384
|
`affine` refuses a determinant at or below zero, and above zero a positive
|
|
4661
4385
|
determinant means no triangle **can** reverse.
|
|
4662
4386
|
|
|
4663
|
-
**A model that evaluates to nothing is refused too
|
|
4664
|
-
([#350](https://github.com/firejune/rigc/issues/350)). Every parameter can be
|
|
4387
|
+
**A model that evaluates to nothing is refused too.** Every parameter can be
|
|
4665
4388
|
individually legal and the model still come out as a **run of zeros** โ a `wave`
|
|
4666
4389
|
whose wavelength puts every vertex on a zero crossing, a `bend` over a span the
|
|
4667
4390
|
part barely enters. The key then claims a deformation, emits the identity and
|
|
@@ -4714,19 +4437,17 @@ like every other number, so the same spec emits the same bytes and
|
|
|
4714
4437
|
`A18_DETERMINISTIC_EMIT` proves it on a second compile. The grid is absolute on
|
|
4715
4438
|
purpose: a model's identities โ a wave sampled on its zero crossings, a whole
|
|
4716
4439
|
revolution โ are exact zeros float64 misses by ~1e-16, and the refusal of a key
|
|
4717
|
-
that states a deformation and evaluates to nothing is decided on it
|
|
4440
|
+
that states a deformation and evaluates to nothing is decided on it;
|
|
4718
4441
|
a float32 alone is relative and has no zero to land on. The runtime then loads
|
|
4719
4442
|
the numbers into a `Float32Array`, which is equally true of a hand-written table โ
|
|
4720
|
-
the difference the generator makes is that the numbers
|
|
4443
|
+
the difference the generator makes is that the numbers agree with a stated
|
|
4721
4444
|
model instead of with a transcription.
|
|
4722
4445
|
|
|
4723
|
-
๐ญ **
|
|
4724
|
-
|
|
4725
|
-
a
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
`DEFORM` report block ([#316](https://github.com/firejune/rigc/issues/316))
|
|
4729
|
-
quotes the model above rather than re-evaluating it โ **ยง4.11.2**.
|
|
4446
|
+
๐ญ **The same construct covers the bone half of the turn.** The **bone** tracks of
|
|
4447
|
+
the same turn, where the missing number is a depth rather than a vertex, take a
|
|
4448
|
+
`derive` kind on a group track, under the same rules as this one โ **ยง4.5.1**. The
|
|
4449
|
+
per-key `DEFORM` report block quotes the model above rather than re-evaluating it
|
|
4450
|
+
โ **ยง4.11.2**.
|
|
4730
4451
|
|
|
4731
4452
|
---
|
|
4732
4453
|
|
|
@@ -4779,7 +4500,7 @@ deform (what each key does to the geometry โ figures with names, never a bar;
|
|
|
4779
4500
|
๐ **The frame is the posed one, and the denominator is 1.000 by definition.**
|
|
4780
4501
|
Both sides of every comparison are taken at the key's own time with the animation
|
|
4781
4502
|
applied โ the deformed mesh against **the same posed bones with the deform
|
|
4782
|
-
cleared**. Setup bones
|
|
4503
|
+
cleared**. Setup bones would be wrong in principle: a weighted mesh's
|
|
4783
4504
|
offsets are authored in bone space against the pose they land in. So every ratio
|
|
4784
4505
|
is *the deform's own contribution*, and a `(setup 1.000)` column beside it would
|
|
4785
4506
|
be printing the definition. It is also what makes a **mirrored** slot bone a
|
|
@@ -4863,18 +4584,14 @@ and displaces `x` over row-major quads preserves every signed area exactly.
|
|
|
4863
4584
|
### 4.11.3 The times no key lands on โ `A39` between two keys
|
|
4864
4585
|
|
|
4865
4586
|
๐จ **Your keys are not where the runtime is.** It interpolates between them, so a
|
|
4866
|
-
deform that is inside its fold angle at *every* key can be past it in between
|
|
4867
|
-
|
|
4868
|
-
[#403](https://github.com/firejune/rigc/issues/403) nothing looked there. The
|
|
4869
|
-
reachable version of that was the fade above: land the alpha-0 key **on** the
|
|
4587
|
+
deform that is inside its fold angle at *every* key can be past it in between.
|
|
4588
|
+
The reachable version of that is the fade above: land the alpha-0 key **on** the
|
|
4870
4589
|
folding key and every key is honest โ that one really does draw nothing โ while
|
|
4871
4590
|
the frames just before it are drawn, nearly folded, and land on no key at all. On
|
|
4872
|
-
the turn probe that is **8 reversed triangles at alpha 0.20
|
|
4591
|
+
the turn probe that is **8 reversed triangles at alpha 0.20**.
|
|
4873
4592
|
|
|
4874
|
-
โ `A39`
|
|
4875
|
-
refuses one with its own sentence.
|
|
4876
|
-
rig it was written from is a probe `selftest.ts` generates and nothing else can
|
|
4877
|
-
invoke โ so the sentence is described here rather than transcribed.
|
|
4593
|
+
โ `A39` scans every interval between two consecutive deform keys as well, and
|
|
4594
|
+
refuses one with its own sentence.
|
|
4878
4595
|
|
|
4879
4596
|
**What it carries**, in the order it says it: `BETWEEN key <i> (t=โฆs) and key <j>
|
|
4880
4597
|
(t=โฆs)` where a key refusal puts one index; the time the closed form solved for, and
|
|
@@ -4887,8 +4604,6 @@ that same instant, present only where it is not 1; and the ways out โ for an
|
|
|
4887
4604
|
interpolating span the four the table below gives, the fade one among them only
|
|
4888
4605
|
where the alpha is not 1, and for a stepped one the key it holds instead, with
|
|
4889
4606
|
`invariants.deformMayFold` the last resort either way.
|
|
4890
|
-
[`src/validate.ts`](../src/validate.ts) builds it, beside the key sentence ยง4.11.2
|
|
4891
|
-
quotes.
|
|
4892
4607
|
|
|
4893
4608
|
**What to change when you see it**, in the order worth trying:
|
|
4894
4609
|
|
|
@@ -4942,8 +4657,7 @@ means one thing: played on track 0. **An animation a `slider` applies (ยง3.5.2)
|
|
|
4942
4657
|
never played that way** โ spine-core says so in `SkeletonData.findSliderAnimations`
|
|
4943
4658
|
โ and posing it as though it were is a frame no playthrough contains. The dial
|
|
4944
4659
|
picks the time, so **the key's time and the applied time are the same number by
|
|
4945
|
-
construction
|
|
4946
|
-
[#407](https://github.com/firejune/rigc/issues/407), and it refused a correct rig.
|
|
4660
|
+
construction**, and measuring them independently would refuse a correct rig.
|
|
4947
4661
|
|
|
4948
4662
|
โ For every deform key of an animation some slider applies, `A39` **inverts that
|
|
4949
4663
|
slider's own mapping** and drives its bone until the runtime selects this key's
|
|
@@ -4954,9 +4668,7 @@ time = to + (value โ from) ร scale the slider's mapping (ยง3.5.2)
|
|
|
4954
4668
|
value = from + (time โ to) / scale what A39 sets the dial to
|
|
4955
4669
|
```
|
|
4956
4670
|
|
|
4957
|
-
๐ **The `DEFORM` block prints the frame on every key
|
|
4958
|
-
changed and a block that went on printing the same figures under a changed meaning
|
|
4959
|
-
would be worse than the red it replaced. `gallery/look`'s `turn` is the animation
|
|
4671
|
+
๐ **The `DEFORM` block prints the frame on every key.** `gallery/look`'s `turn` is the animation
|
|
4960
4672
|
a slider applies, and this is one of its keys:
|
|
4961
4673
|
|
|
4962
4674
|
```bash
|
|
@@ -4976,24 +4688,23 @@ dial and not reached at all through another.
|
|
|
4976
4688
|
|
|
4977
4689
|
**Three consequences worth knowing before you build a rig like this:**
|
|
4978
4690
|
|
|
4979
|
-
- โญ **A slider at `mix: 1` in setup
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4691
|
+
- โญ **A slider may rest at `mix: 1` in setup.** Muting at setup and keying
|
|
4692
|
+
`slider.<name>.mix` from a playing animation (ยง4.12) is legitimate too, but it
|
|
4693
|
+
is not needed to keep `A39` honest about a slot the animation itself fades โ
|
|
4694
|
+
and it costs `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET`,
|
|
4983
4695
|
which excludes a slider below full authority. A slider muted at setup is **not**
|
|
4984
4696
|
a way in: `Slider.update` returns before it reads the bone, so its animation
|
|
4985
4697
|
keeps the track frame.
|
|
4986
4698
|
- **Two sliders applying one animation are two frames**, and both are measured.
|
|
4987
4699
|
A fold only one dial can reach is still a fold.
|
|
4988
4700
|
- โ ๏ธ **A key at a time no dial can select is named, not passed.** The cause is
|
|
4989
|
-
|
|
4701
|
+
the wrap: `FromRotate.value`
|
|
4990
4702
|
under `local: false` is an `atan2` ending `if (value < 0) value += 360`, so
|
|
4991
4703
|
**`[0, 360]` is the whole of its range** and a mapping needing anything outside
|
|
4992
|
-
it selects nothing. ๐จ A **rig spec**
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
from an older rigc, and this is the surface that reads what actually shipped.
|
|
4704
|
+
it selects nothing. ๐จ A **rig spec** cannot ask for one โ the compiler refuses
|
|
4705
|
+
both ends of that circle (ยง3.5.2) โ but an **artifact** can, because it may have
|
|
4706
|
+
come from the editor or from a hand edit, and this is the surface that reads
|
|
4707
|
+
what actually shipped.
|
|
4997
4708
|
`A39` measures the frame the runtime *does* land on, leaves the key out of
|
|
4998
4709
|
`deformKeysMeasured`, and names it:
|
|
4999
4710
|
|
|
@@ -5043,10 +4754,8 @@ the line says so rather than picking one silently.**
|
|
|
5043
4754
|
tie. โ Neither case is a refusal and neither is guessed past โ an ambiguous
|
|
5044
4755
|
discovery is a thing to report.
|
|
5045
4756
|
|
|
5046
|
-
**And a `build` says it too, on `A39`'s stats line**
|
|
5047
|
-
|
|
5048
|
-
the loop you run, and until this the whole finding lived on a line only `explain`
|
|
5049
|
-
prints. Nothing appears on a rig where the two answers agreed, which is every
|
|
4757
|
+
**And a `build` says it too, on `A39`'s stats line** โ because `explain` is not
|
|
4758
|
+
the loop you run. Nothing appears on a rig where the two answers agreed, which is every
|
|
5050
4759
|
`local: true` slider and every gallery example:
|
|
5051
4760
|
|
|
5052
4761
|
```
|
|
@@ -5068,8 +4777,8 @@ deformDialsDisagreed=1 deformDialDisagreed=dial|artifact:knob.x@2.321e-8|reaches
|
|
|
5068
4777
|
- A **tie** never carries `probe:`, `reaches:` or `outside:`, and never counts as a
|
|
5069
4778
|
disagreement. There is one belief there, not two.
|
|
5070
4779
|
|
|
5071
|
-
**And `explain`'s rollup carries it too, with the breadcrumb its neighbours have
|
|
5072
|
-
|
|
4780
|
+
**And `explain`'s rollup carries it too, with the breadcrumb its neighbours have.**
|
|
4781
|
+
The per-key `frame` lines
|
|
5073
4782
|
say it once each; the rollup says it once per animation, and ends by naming the
|
|
5074
4783
|
two readings a `build` prints it under โ because they differ by one letter, and a
|
|
5075
4784
|
breadcrumb naming only one would send a reader to grep for the other:
|
|
@@ -5078,10 +4787,8 @@ breadcrumb naming only one would send a reader to grep for the other:
|
|
|
5078
4787
|
.. 1 dial(s) the skeleton and the probe disagree about: the skeleton reads knob.x and the probe drives knob.y, 2 key time(s) outside what the skeleton's own field reaches <- A39 counts them as deformDialsDisagreed and spells them out as deformDialDisagreed
|
|
5079
4788
|
```
|
|
5080
4789
|
|
|
5081
|
-
The count after `outside` is the length of the stats line's own `outside:` list
|
|
5082
|
-
and
|
|
5083
|
-
`A39` really printed rather than restated โ `DW34`. An agreed dial adds no line
|
|
5084
|
-
(`DW35`) and a **tie** is never rolled up as a disagreement (`DW36`).
|
|
4790
|
+
The count after `outside` is the length of the stats line's own `outside:` list.
|
|
4791
|
+
An agreed dial adds no line, and a **tie** is never rolled up as a disagreement.
|
|
5085
4792
|
|
|
5086
4793
|
โ **None of it refuses a build**, and the reason is measured rather than chosen.
|
|
5087
4794
|
The field the survey drives is the largest response the probe found, so its reach
|
|
@@ -5109,33 +4816,21 @@ records that, so a slider-applied animation is measured in its slider frames onl
|
|
|
5109
4816
|
ยง4.11 opens on the triple: a deform timeline is the only one keyed on
|
|
5110
4817
|
**skin / slot / attachment**. So the skin is not context around the key, it is a
|
|
5111
4818
|
third of the key's own address โ and every pose `A39` and the `DEFORM` block take
|
|
5112
|
-
is
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
`
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
```
|
|
5125
|
-
DEFORM bounce suit/ball/ball key 1 t=0.340000 transform affine scale=[0.88, 1.16]
|
|
5126
|
-
skipped A39 reads no winding off this key: the slot shows no attachment at all at this
|
|
5127
|
-
time, not this mesh, so the runtime applies no deform to it here and draws
|
|
5128
|
-
none of it โ a triangle that draws no pixels cannot draw them backwards
|
|
5129
|
-
```
|
|
5130
|
-
|
|
5131
|
-
โ `A39` went **PASS โ SKIP** on a rig whose only edit was which skin one mesh sat
|
|
5132
|
-
in, in a sentence that reads as a verdict on that rig. Worn, the same build
|
|
5133
|
-
reports every figure the default-skin one does, to the last digit, and `A39` gates
|
|
5134
|
-
it: the two `DEFORM` blocks differ in the skin of the triple and in nothing else.
|
|
4819
|
+
is taken with **that skin worn**. You do not ask for it and there is no flag: the
|
|
4820
|
+
skin comes out of the timeline.
|
|
4821
|
+
|
|
4822
|
+
๐จ **Wearing no skin would measure a named-skin mesh as drawing nothing.** That is
|
|
4823
|
+
`spine-core`'s own initial state and the one `check` reports as
|
|
4824
|
+
`no skin set (the default skin alone)` (ยง9): every slot resolves through `SkeletonData.defaultSkin`
|
|
4825
|
+
and nothing else, so a deformed mesh in a named skin โ which the format not only
|
|
4826
|
+
allows but keys the timeline on โ would show **no attachment**. Worn, a build with
|
|
4827
|
+
its mesh in a named skin reports every figure the default-skin one does, to the
|
|
4828
|
+
last digit, and `A39` gates it: the two `DEFORM` blocks differ in the skin of the
|
|
4829
|
+
triple and in nothing else.
|
|
5135
4830
|
|
|
5136
4831
|
**Two things follow, and one of them is not about art:**
|
|
5137
4832
|
|
|
5138
|
-
- โ ๏ธ **A "nothing is drawn" sentence
|
|
4833
|
+
- โ ๏ธ **A "nothing is drawn" sentence names the skin it was measured in** โ
|
|
5139
4834
|
`the slot shows attachment "away" at this time, with skin "suit" worn, not this
|
|
5140
4835
|
mesh โฆ`. It is on the *shows-something-else* branch only, because that is the
|
|
5141
4836
|
branch a skin decides; an alpha is read off the pose and has no skin in it. The
|
|
@@ -5145,12 +4840,8 @@ it: the two `DEFORM` blocks differ in the skin of the triple and in nothing else
|
|
|
5145
4840
|
`Skeleton.setSkin` calls `updateCache`, which leaves a `skinRequired` bone
|
|
5146
4841
|
inactive and a `skinRequired` constraint out of the update cache under any skin
|
|
5147
4842
|
that does not list it. So a **slider** dressed into the same skin as the mesh
|
|
5148
|
-
it drives
|
|
5149
|
-
|
|
5150
|
-
was reported as *"played on a track"* โ an animation a slider is the only way
|
|
5151
|
-
into (ยง4.11.4) โ while a local-read one kept its mapping but never left
|
|
5152
|
-
`SliderPose.time` 0, so every key came back as *"at a time no dial selects"*.
|
|
5153
|
-
Both now reach their own key times.
|
|
4843
|
+
it drives is switched on while that mesh is measured, and reaches its own key
|
|
4844
|
+
times (ยง4.11.4).
|
|
5154
4845
|
|
|
5155
4846
|
โ **What it does not do is try every skin.** `Attachment.timelineSlots` lets one
|
|
5156
4847
|
deform reach a second slot โ a linked mesh with `inheritTimelines` โ and that copy
|
|
@@ -5209,23 +4900,19 @@ so `v` is three numbers and not one.
|
|
|
5209
4900
|
constraint on from an animation is the idiom (ยง4.10), so `A36`/`A37` only object to
|
|
5210
4901
|
all-zero mixes when **no** animation keys that constraint's `mix` **above 0**. If
|
|
5211
4902
|
you mute one at setup, key it up somewhere. `A23` asks the same question of a
|
|
5212
|
-
physics constraint
|
|
5213
|
-
three share one reading ([#752](https://github.com/firejune/rigc/issues/752)): the
|
|
4903
|
+
physics constraint, and the three share one reading: the
|
|
5214
4904
|
key **values** the loaded timeline poses, so a `mix` timeline keying 0 only is not a
|
|
5215
4905
|
rescue, and a Bezier between two keys of 0 whose handles lie above 0 is. A path
|
|
5216
4906
|
constraint is switched on by a key posing **any one** of its three mixes above 0,
|
|
5217
4907
|
because `PathConstraint.update` returns only when all three are 0. [measured] on
|
|
5218
4908
|
generated fixtures, a path constraint and a slider muted at rest and keyed to 0
|
|
5219
|
-
only pose every bone exactly where the same rig with no mix timeline does
|
|
5220
|
-
#752 both passed. `A23` alone also counts the unnamed global timeline for every
|
|
4909
|
+
only pose every bone exactly where the same rig with no mix timeline does. `A23` alone also counts the unnamed global timeline for every
|
|
5221
4910
|
constraint declaring `mixGlobal`, since only the physics family has one. The refusal
|
|
5222
4911
|
says both halves โ `path constraint "P" has mixRotate 0, mixX 0 and mixY 0 at setup
|
|
5223
4912
|
and none of the 2 animations keys its mix above 0; โฆ` โ and names both repairs.
|
|
5224
4913
|
|
|
5225
|
-
โ ๏ธ **The same question of an `ik` and a `transform` constraint is `A47` and `A48
|
|
5226
|
-
|
|
5227
|
-
it, and a rig resting either kind muted with nothing keying it gated green with 0
|
|
5228
|
-
failures. [measured] on generated fixtures, an ik at `mix` 0 and a transform at
|
|
4914
|
+
โ ๏ธ **The same question of an `ik` and a `transform` constraint is `A47` and `A48`.**
|
|
4915
|
+
[measured] on generated fixtures, an ik at `mix` 0 and a transform at
|
|
5229
4916
|
every mix 0, each with nothing keying it and each keyed to 0 only, pose every bone
|
|
5230
4917
|
exactly where the same rig with no constraint does. They read the timelines through
|
|
5231
4918
|
the same helper as `A23`/`A36`/`A37`, so a lifted Bezier between two keys of 0 is a
|
|
@@ -5253,11 +4940,9 @@ its mix, in the rig spec as invariants.consumerDrivenMix: [{ "constraint": "reac
|
|
|
5253
4940
|
"type": "ik", "why": โฆ }]`. A rig resting at 0 and keyed up by the animation that
|
|
5254
4941
|
needs it โ spineboy's aim โ is refused by neither.
|
|
5255
4942
|
|
|
5256
|
-
๐๏ธ **The third door is for a dial the file cannot show turning
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
export as the same bytes โ a production skeleton's rebuild was refused for exactly
|
|
5260
|
-
that, over an ik its game switches on at runtime. `invariants.consumerDrivenMix`
|
|
4943
|
+
๐๏ธ **The third door is for a dial the file cannot show turning.** A constraint
|
|
4944
|
+
resting muted that nothing keys up is either a leftover or a mix a game sets from
|
|
4945
|
+
code, and the two export as the same bytes. `invariants.consumerDrivenMix`
|
|
5261
4946
|
(ยง3.7) is the statement the file cannot make, and what it buys is a **SKIP by name,
|
|
5262
4947
|
never a pass**: the file still shows nothing moving the constraint.
|
|
5263
4948
|
|
|
@@ -5369,16 +5054,14 @@ first is a **shape** refusal from a spec parser โ `parseRigSpec`
|
|
|
5369
5054
|
compiled, and ask only what the one file in front of them can answer. Every
|
|
5370
5055
|
`parseMotionSpec` refusal reads `<file>: \`<key path>\` is <what it actually is>;
|
|
5371
5056
|
<the spelling that works>`, so its whole wave is **one row per FIELD** rather than
|
|
5372
|
-
one per message. (The rig spec's
|
|
5373
|
-
|
|
5057
|
+
one per message. (The rig spec parser's messages are prose, so they sit in the
|
|
5058
|
+
second table with everything else.)
|
|
5374
5059
|
|
|
5375
5060
|
๐จ **A key neither format has is refused by name, in both files.** Not a row in
|
|
5376
5061
|
the table below, because it is not about one field: every object in a rig spec and
|
|
5377
5062
|
in a motion spec is checked against the keys its shape actually owns, and a key
|
|
5378
|
-
outside that set stops the build
|
|
5379
|
-
|
|
5380
|
-
every assertion green. Four keys planted into one physics constraint all vanished,
|
|
5381
|
-
and no line of output named any of them.
|
|
5063
|
+
outside that set stops the build, because nothing else reads such a key: left
|
|
5064
|
+
alone it would be dropped from the emitted skeleton with every assertion green.
|
|
5382
5065
|
|
|
5383
5066
|
```
|
|
5384
5067
|
rigc compile error: rig.json: constraint "ctl" (physics) has 4 keys this compiler
|
|
@@ -5414,11 +5097,7 @@ nothing and the required-key message below it is what arrives โ `a rig spec ne
|
|
|
5414
5097
|
**"does not read"** means the key *is* in your file, spelt some other way, and
|
|
5415
5098
|
**"needs a โฆ"** / **"is absent"** means it is not in the file at all. Where both
|
|
5416
5099
|
are true at once โ a typo beside a key that really is missing โ the typo is named
|
|
5417
|
-
first, because it is the one you can fix without re-reading the file.
|
|
5418
|
-
[#672](https://github.com/firejune/rigc/issues/672) the rig spec's required-key
|
|
5419
|
-
checks ran *above* its scan, so `"slot"` for `"slots"` printed the sentence asking
|
|
5420
|
-
for an array the file already had, one character off, and never named the stray
|
|
5421
|
-
key; the motion spec has always had this order.
|
|
5100
|
+
first, because it is the one you can fix without re-reading the file.
|
|
5422
5101
|
|
|
5423
5102
|
โ ๏ธ One check stays above the scan in both parsers: the `spec` version tag. The
|
|
5424
5103
|
version is what decides *which* key set applies, so a file declaring a version this
|
|
@@ -5434,49 +5113,48 @@ one is listed: a rig spec's root, a motion spec's root, an animation, and a
|
|
|
5434
5113
|
the compiler tolerates is a key it cannot distinguish from one you meant it to
|
|
5435
5114
|
read. (The **cut manifest** is deliberately outside this: it is the record of the
|
|
5436
5115
|
pipeline that produced the art as much as an input, it carries fields the compiler
|
|
5437
|
-
states outright that it does not read โ `roi` โ and
|
|
5438
|
-
|
|
5439
|
-
same hole issue #307 closed for the motion spec.)
|
|
5116
|
+
states outright that it does not read โ `roi` โ and it may carry `note` and
|
|
5117
|
+
`archetype` as well. It has no shape parse at all.)
|
|
5440
5118
|
|
|
5441
5119
|
| Key | Refused when it is not | Why the shape matters |
|
|
5442
5120
|
| --- | --- | --- |
|
|
5443
5121
|
| the file itself | a JSON object | the version row below would otherwise report a missing `spec` tag in a file that has no fields at all |
|
|
5444
5122
|
| `spec` | `"rigc-motion/1"` | ยง4 |
|
|
5445
|
-
| `archetype`, `cut` | a non-empty string |
|
|
5123
|
+
| `archetype`, `cut` | a non-empty string | a spec with no `cut` cannot say what it was authored for |
|
|
5446
5124
|
| `note` (at any level) | a string | โ |
|
|
5447
|
-
| `easings` | an object |
|
|
5448
|
-
| `easings."<name>"` | **four finite numbers** | `bezierForChannel` destructures four handles with no guard: `[0.42, 0, 0.58]`
|
|
5449
|
-
| `groups` | an object keyed by group name | an array
|
|
5450
|
-
| `setup` | an object keyed by slot name | every `setup?.[slot]` lookup on an array is `undefined`, so the whole table
|
|
5451
|
-
| `setup."<slot>"` | an object of `{ attachment?, color? }` | ยง4.2 โ `"<slot>": "plate"` reads `.attachment` off a string as `undefined` and **hides the slot**
|
|
5125
|
+
| `easings` | an object | an absent table is refused at the table, not at the first key that names an easing |
|
|
5126
|
+
| `easings."<name>"` | **four finite numbers** | `bezierForChannel` destructures four handles with no guard: `[0.42, 0, 0.58]` would emit `"curve": [0.42, 0, 0.58, null]` (ยง4.1) |
|
|
5127
|
+
| `groups` | an object keyed by group name | an array would compile green with no group defined. Each group's member LIST stays a compile-time refusal โ see the rows further down |
|
|
5128
|
+
| `setup` | an object keyed by slot name | every `setup?.[slot]` lookup on an array is `undefined`, so the whole table would be silently absent |
|
|
5129
|
+
| `setup."<slot>"` | an object of `{ attachment?, color? }` | ยง4.2 โ `"<slot>": "plate"` reads `.attachment` off a string as `undefined` and **hides the slot** |
|
|
5452
5130
|
| `setup."<slot>".attachment` | a string or `null` | whether a string RESOLVES is still the compile-time row below; whether it is a string is this one |
|
|
5453
5131
|
| `setup."<slot>".color[i]` | a finite number in 0..1 | `channelHex` clamps with `Math.min`/`Math.max`, which pass `NaN` through, and `NaN.toString(16)` is the text `"NaN"` |
|
|
5454
5132
|
| `physics` | an object keyed by constraint name | as `setup` |
|
|
5455
5133
|
| `physics."<name>".bone` | a non-empty string | โ |
|
|
5456
5134
|
| `physics."<name>".<tuning field>` | a finite number | rounded to `NaN` and emitted as `null`, which the runtime reads as zero (ยง4.6) |
|
|
5457
5135
|
| `mix`, `mix.default`, `mix.pairs[i]` | an object / a number / a `["<from>", "<to>", <seconds>]` triple | not emitted into skeleton JSON, so nothing else ever looks at it (ยง4.6) |
|
|
5458
|
-
| `animations` | an object keyed by animation name | an absent one
|
|
5459
|
-
| `animations."<a>"` | an object |
|
|
5460
|
-
| `animations."<a>".duration` | a finite number โฅ 0 | R7's check is `Math.abs(compiled โ declared) > FRAME`, and a comparison against `NaN` is **false** โ so the one guard on the field
|
|
5136
|
+
| `animations` | an object keyed by animation name | an absent one would crash with a raw `TypeError` that names neither input file; an array would compile green with no animations |
|
|
5137
|
+
| `animations."<a>"` | an object | it would crash with a raw `TypeError` on `anim.tracks` |
|
|
5138
|
+
| `animations."<a>".duration` | a finite number โฅ 0 | R7's check is `Math.abs(compiled โ declared) > FRAME`, and a comparison against `NaN` is **false** โ so the one guard on the field would pass hardest exactly when the field is missing |
|
|
5461
5139
|
| `animations."<a>".loop` | `true` or `false` | optional; absent means the player decides (ยง4.3) |
|
|
5462
5140
|
| `animations."<a>".tracks` | an array | an animation whose timelines are all in the families beside `tracks` still writes `"tracks": []` |
|
|
5463
5141
|
| `โฆtracks[i]` | an object | โ |
|
|
5464
5142
|
| `โฆtracks[i].property` | a non-empty string | which properties exist is the compile-time row below (ยง4.4) |
|
|
5465
5143
|
| `โฆtracks[i].slot`/`.group`/`.bone`/`.physics`/`.path`/`.slider` | a string | whether exactly one is named, and what it resolves to, are compile-time rows below |
|
|
5466
|
-
| `โฆtracks[i].lag`, `.stagger` | a finite number | a string is CONCATENATED onto each key time and a boolean adds 1s, and the resulting refusal
|
|
5144
|
+
| `โฆtracks[i].lag`, `.stagger` | a finite number | a string is CONCATENATED onto each key time and a boolean adds 1s, and the resulting refusal would blame the key and the duration for a fault in neither |
|
|
5467
5145
|
| `โฆkeys[j]` | an object | โ |
|
|
5468
|
-
| `โฆkeys[j].t` | a finite number | **every** key family,
|
|
5146
|
+
| `โฆkeys[j].t` | a finite number | **every** key family, value tracks, slot tracks and `drawOrder` included |
|
|
5469
5147
|
| `โฆkeys[j].ease` | a string | โ |
|
|
5470
|
-
| `โฆdrawOrder`, `โฆevents`, `โฆik`, `โฆtransform`, `โฆdeform` | an array | an object
|
|
5471
|
-
| `โฆdrawOrder[i].offsets` | an array | a key with no offsets is the format's own "back to the setup draw order", and the test for it
|
|
5148
|
+
| `โฆdrawOrder`, `โฆevents`, `โฆik`, `โฆtransform`, `โฆdeform` | an array | an object would be refused as `no keys`, which is what an EMPTY array says |
|
|
5149
|
+
| `โฆdrawOrder[i].offsets` | an array | a key with no offsets is the format's own "back to the setup draw order", and the test for it is true for `{}` โ a complete statement of the draw order, made by accident |
|
|
5472
5150
|
| `โฆdrawOrder[i].offsets[j]` | `{ slot: string, offset: number }` | whether the slot is emitted, and whether the offset is whole and lands inside the array, are compile-time rows below |
|
|
5473
|
-
| `โฆik[i]`, `โฆtransform[i]`, `โฆdeform[i]` | an object | `null` in one of these lists
|
|
5151
|
+
| `โฆik[i]`, `โฆtransform[i]`, `โฆdeform[i]` | an object | `null` in one of these lists would crash with a raw `TypeError` on `track.constraint` / `track.skin` |
|
|
5474
5152
|
| `โฆ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 |
|
|
5475
5153
|
| `โฆdeform[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | โ |
|
|
5476
5154
|
| `โฆsequence` | an array | ยง4.13 โ one entry per skin/slot/attachment triple |
|
|
5477
5155
|
| `โฆsequence[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | โ |
|
|
5478
5156
|
| `โฆ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` |
|
|
5479
|
-
| `โฆsequence[i].keys[j].index` | a whole number โฅ 0 | stored as `index << 4`, which truncates a fraction (1.5
|
|
5157
|
+
| `โฆsequence[i].keys[j].index` | a whole number โฅ 0 | stored as `index << 4`, which truncates a fraction (1.5 shows frame 1). Whether it is inside the series' `count` is the compile-time row below |
|
|
5480
5158
|
| `โฆ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" `` |
|
|
5481
5159
|
|
|
5482
5160
|
The second wave is everything that needed the **other** file, the property table
|
|
@@ -5489,13 +5167,13 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5489
5167
|
| `a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)` | ยง3.3 โ write `[]` for a rig that draws nothing. โ ๏ธ These three arrive only when the key is really absent: **misspelt**, it is the unknown-key refusal above, naming what you wrote |
|
|
5490
5168
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
5491
5169
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
5492
|
-
| `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
|
|
5170
|
+
| `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 |
|
|
5493
5171
|
| `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 |
|
|
5494
|
-
| `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
|
|
5172
|
+
| `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: the lists are per-skin sets and a bone or constraint in two of them is active under either |
|
|
5495
5173
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
5496
5174
|
| `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 |
|
|
5497
5175
|
| `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` |
|
|
5498
|
-
| `the setup pose shows attachment "A" on slot "X", and the slot is emitted empty because the one manifest state that fills it has no art: "s" (no PNG at p)` | ยง0.2 โ a **different** fault with a similar shape: a manifest part does fill this slot and its art was not where the manifest pointed, so every state of it was dropped. **Restore the file or fix the path** โ posing `null` compiles, but it ships the rig with that part missing and the gate green. The path printed is the one rigc tried, which is what tells a deleted file from a misspelt `states:` entry; `all N manifest states that fill it have no art` is the same message where the part has more than one. Under `--atlas-in` the same sentence reads `no region "R" in <atlas>` and asks you to add the region to the pack โ no file was opened
|
|
5176
|
+
| `the setup pose shows attachment "A" on slot "X", and the slot is emitted empty because the one manifest state that fills it has no art: "s" (no PNG at p)` | ยง0.2 โ a **different** fault with a similar shape: a manifest part does fill this slot and its art was not where the manifest pointed, so every state of it was dropped. **Restore the file or fix the path** โ posing `null` compiles, but it ships the rig with that part missing and the gate green. The path printed is the one rigc tried, which is what tells a deleted file from a misspelt `states:` entry; `all N manifest states that fill it have no art` is the same message where the part has more than one. Under `--atlas-in` the same sentence reads `no region "R" in <atlas>` and asks you to add the region to the pack โ no file was opened |
|
|
5499
5177
|
| `a region needs width and height โ give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
|
|
5500
5178
|
| `a mesh needs width and height โ give them, or give an "image" and rigc will measure the PNG` | ยง3.4 โ the same rule for a mesh |
|
|
5501
5179
|
| `"type" is null, which is not a name. An attachment's type is one of region, mesh, linkedmesh, โฆ or the key is absent and reads as "region"` | ยง6 โ **remove the key**. Absent is the format's own default; present-and-null matches no parser case and the attachment is dropped in silence |
|
|
@@ -5505,7 +5183,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5505
5183
|
| `a linked mesh states "uvs", "triangles", โฆ, and a linked mesh has no geometry of its own` | ยง3.4 โ remove them, or remove `source` and author this as a mesh. The parser returns before `readVertices`, so those keys are read by nothing at all |
|
|
5506
5184
|
| `"source" is "X", and skin "S" โฆ slot "L" โฆ holds 2: "a", "b"` | ยง3.4 โ `source` is the PLACEHOLDER the source is filed under, not its `name`. A clause after the skin and after the slot says whether each was stated or taken from the parser's default โ **the default skin** and **this attachment's own slot**, which is the pair that surprises |
|
|
5507
5185
|
| `"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 |
|
|
5508
|
-
| `no "skin" is stated, so the parser looks for the source "X" in the default skin โ and this rig declares no default skin` | ยง3.4 โ state the `skin` the source is filed under. A link with no `skin` resolves through `SkeletonData.defaultSkin`, which is null in a file with no default skin, and the runtime reads `getAttachment` off it with a `TypeError` naming nothing
|
|
5186
|
+
| `no "skin" is stated, so the parser looks for the source "X" in the default skin โ and this rig declares no default skin` | ยง3.4 โ state the `skin` the source is filed under. A link with no `skin` resolves through `SkeletonData.defaultSkin`, which is null in a file with no default skin, and the runtime reads `getAttachment` off it with a `TypeError` naming nothing |
|
|
5509
5187
|
| `"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 |
|
|
5510
5188
|
| `"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 |
|
|
5511
5189
|
| `โฆ 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 |
|
|
@@ -5523,26 +5201,26 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5523
5201
|
| `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` |
|
|
5524
5202
|
| `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 |
|
|
5525
5203
|
| `image "X.png" is not on disk at โฆ` | fix the name, or point `--images` at the right directory |
|
|
5526
|
-
| `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
|
|
5527
|
-
| `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
|
|
5204
|
+
| `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) |
|
|
5205
|
+
| `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) |
|
|
5528
5206
|
| `--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) |
|
|
5529
5207
|
| `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 |
|
|
5530
5208
|
| `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 |
|
|
5531
|
-
| `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
|
|
5209
|
+
| `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 table, with the message it actually prints |
|
|
5532
5210
|
| `duplicate region name "X"` | two PNGs share a basename; one part, one page, one name |
|
|
5533
5211
|
| `"b/X.png" and the art already atlased as region "X" are two different files โ โฆ Rename one of the PNGs.` | R5 โ the region name is the basename, so only one of the two can hold it. Rename a file (not a placeholder: the placeholder is free to repeat) |
|
|
5534
5212
|
| `the image "X.png" was never added to the atlas, so there is no region "X" โฆ` | nothing in the spec โ every image an attachment names is measured and atlased, one per file whichever skin names it, so this says rigc skipped one. Report it on [the tracker](https://github.com/firejune/rigc/issues). The `#555` the message itself cites is the change that made one-per-file an invariant, not a place to file against |
|
|
5535
5213
|
| `motion spec names archetype "A" but the rig spec at โฆ is called "B"` | make `archetype` equal the rig's `name` |
|
|
5536
5214
|
| `animation "A" declares duration Ns but its last key is at Ms` | R7 โ fix whichever of the two you meant |
|
|
5537
5215
|
| `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` |
|
|
5538
|
-
| `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
|
|
5216
|
+
| `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 โ it compiles, and the slot shows nothing under the skins that lack it. `the slot has no attachments at all` is the same message where nothing fills the slot |
|
|
5539
5217
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
5540
5218
|
| `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 |
|
|
5541
5219
|
| `` `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 `"*"` |
|
|
5542
5220
|
| `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 |
|
|
5543
5221
|
| `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 |
|
|
5544
5222
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (ยง4.4) |
|
|
5545
|
-
| `animation "A" physics constraint "C" mass key at t=โฆ is 0 (massInverse Infinity); must be > 0 โ โฆ` | ยง4.4 โ a keyed physics value outside its row's bound. The message names the bound and then its basis
|
|
5223
|
+
| `animation "A" physics constraint "C" mass key at t=โฆ is 0 (massInverse Infinity); must be > 0 โ โฆ` | ยง4.4 โ a keyed physics value outside its row's bound. The message names the bound and then its basis: where the runtime's arithmetic fails, the expression and its lines (`mass` at 0 is the one here, and `damping` below 0); where the value runs and runs wrongly, that refusing it is rigc's call and what the value does. The bounds: `mass` is `> 0`, `damping` is inside `[0, 1]`, both ends included, `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere. โ ๏ธ 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`; a setup `strength` below 0 says the offset is pushed away and grows, the same arm this message quotes for a negative key. 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) |
|
|
5546
5224
|
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
5547
5225
|
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
5548
5226
|
| `key times must strictly increase (at t=โฆ)` | including after `lag` and `stagger` |
|
|
@@ -5604,19 +5282,19 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5604
5282
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | ยง4.12 โ put the name in `"path"` |
|
|
5605
5283
|
| `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 |
|
|
5606
5284
|
| `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 |
|
|
5607
|
-
| `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
|
|
5285
|
+
| `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 |
|
|
5608
5286
|
| `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]`, โฆ) |
|
|
5609
5287
|
| `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 |
|
|
5610
5288
|
| `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` |
|
|
5611
|
-
| `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"
|
|
5612
|
-
| `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`
|
|
5613
|
-
| `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
|
|
5614
|
-
| `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.
|
|
5615
|
-
| `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
|
|
5616
|
-
| `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
|
|
5617
|
-
| `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`
|
|
5618
|
-
| `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`
|
|
5619
|
-
| `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
|
|
5289
|
+
| `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"` |
|
|
5290
|
+
| `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` |
|
|
5291
|
+
| `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 |
|
|
5292
|
+
| `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. The check is exactly as wide as the runtime: `"NOSCALE"` would load as no mode, so the bone's world rotation, scale and shear would never be computed โ every attachment on it collapses to a point. Fix the spelling |
|
|
5293
|
+
| `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 |
|
|
5294
|
+
| `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 |
|
|
5295
|
+
| `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` is measured to throw the same way. Raised before the keys are read, with the slot named, for the same reason the row above is |
|
|
5296
|
+
| `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` โ 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 |
|
|
5297
|
+
| `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 |
|
|
5620
5298
|
| `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 |
|
|
5621
5299
|
| `skin "S" slot "X" attachment "P": "name" is V, which is not a string. An attachment's name is the runtime's `Attachment.name` โฆ` | ยง3.4 โ write the name as a string, or leave the key out and the attachment is named by its placeholder. It is written verbatim and `path` defaults to it, so a number there would reach the atlas as a region name nobody spelled |
|
|
5622
5300
|
|
|
@@ -5642,12 +5320,7 @@ Both halves of it are measured rather than fixed text: the count is this pair's,
|
|
|
5642
5320
|
where the atlas has a **near miss** the sentence prints that too, in `A08`'s own
|
|
5643
5321
|
clause and `A08`'s own words. When `--atlas-in` **was** given and the region is still
|
|
5644
5322
|
absent, the second half names the pack instead and asks you to fix the spec's region
|
|
5645
|
-
name or point the flag at the pack that has it.
|
|
5646
|
-
[#697](https://github.com/firejune/rigc/issues/697) there was no rigc sentence at
|
|
5647
|
-
all: the report printed in full and the run then died inside `AtlasAttachmentLoader`
|
|
5648
|
-
with `Region not found in atlas: shade (attachment: shade)` and a spine-core stack
|
|
5649
|
-
trace, at exit 1 โ the runtime's message about rigc's internals standing in for
|
|
5650
|
-
rigc's message about your two files.
|
|
5323
|
+
name or point the flag at the pack that has it.
|
|
5651
5324
|
|
|
5652
5325
|
### 5.2 Assertions โ the gate
|
|
5653
5326
|
|
|
@@ -5664,8 +5337,7 @@ The report prints one line per assertion:
|
|
|
5664
5337
|
- **PASS** โ it ran and held.
|
|
5665
5338
|
- **SKIP** โ it had *nothing to look at*, and the reason says what was missing. A
|
|
5666
5339
|
skip is never folded into the pass count.
|
|
5667
|
-
๐จ **"Nothing to look at" includes a subject the skeleton does not carry
|
|
5668
|
-
([#580](https://github.com/firejune/rigc/issues/580)). A rule named
|
|
5340
|
+
๐จ **"Nothing to look at" includes a subject the skeleton does not carry.** A rule named
|
|
5669
5341
|
โจsubjectโฉ_โจpropertyโฉ โ `A03_REGION_WIDTH_HEIGHT_FINITE`,
|
|
5670
5342
|
`A04_MESH_TRIANGLES_AND_ENCODING`, `A23_PHYSICS_CONSTRAINT_EFFECTIVE` โ walks
|
|
5671
5343
|
that subject and measures each member, so a skeleton with no region, no mesh or
|
|
@@ -5705,59 +5377,59 @@ and with no parse there is nothing for them to look at โ so they report `SKIP`
|
|
|
5705
5377
|
naming that, *the round trip did not produce a skeleton to measure* or *โฆan atlas
|
|
5706
5378
|
to measure*, and the summary's `<S>` goes up while `<M>` collapses. A run in that
|
|
5707
5379
|
state is not a rig that nearly passed; it is a rig that was measured on one rule.
|
|
5708
|
-
Fix A00 and run it again
|
|
5380
|
+
Fix A00 and run it again.
|
|
5709
5381
|
|
|
5710
5382
|
| Assertion | Profile | What tripped it, and where to fix it |
|
|
5711
5383
|
| --- | --- | --- |
|
|
5712
|
-
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas. Almost everything else in the report is downstream of this one โ fix it first. When it fails, every rule that reads the loaded skeleton or the loaded atlas reports **SKIP** saying so by name, so the row count stays at the full registry and the summary's *measured* figure tells you how little was actually asked
|
|
5384
|
+
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas. Almost everything else in the report is downstream of this one โ fix it first. When it fails, every rule that reads the loaded skeleton or the loaded atlas reports **SKIP** saying so by name, so the row count stays at the full registry and the summary's *measured* figure tells you how little was actually asked. โ ๏ธ **Two rules run before it and can be upstream of it**: `A31_DRAW_ORDER_OFFSETS_RESOLVE`, because a bad draw-order key makes the loader spin rather than return, and `A08_REGION_NAMES_MATCH_ATTACHMENTS`, because a `path` naming no region makes it throw. The round trip is still attempted either way; when the loader refuses a path A08 has already refused, this row **defers** to A08 by name instead of restating the miss in the parser's poorer words |
|
|
5713
5385
|
| `A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS` | both | a 4.1/4.2-shaped `ik`/`transform`/`path`/`physics`/`slider` array. rigc emits the 4.3 `constraints` array, so this normally means hand-edited JSON |
|
|
5714
|
-
| `A02_NO_BONE_TRANSFORM_KEY` | both |
|
|
5715
|
-
| `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
|
|
5716
|
-
| `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
|
|
5717
|
-
| `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
|
|
5718
|
-
| `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
|
|
5719
|
-
| `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.
|
|
5720
|
-
| `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.
|
|
5386
|
+
| `A02_NO_BONE_TRANSFORM_KEY` | both | `bone "โฆ" uses "transform", the key 4.0 and 4.1 spelled; 4.2 and 4.3 spell it "inherit"` โ rename it `inherit` in the rig spec |
|
|
5387
|
+
| `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 |
|
|
5388
|
+
| `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 |
|
|
5389
|
+
| `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. 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 |
|
|
5390
|
+
| `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: 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: 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**, 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). That clause is validity too: nothing in rigc can read such a page back. **SKIP** when the atlas declares no page |
|
|
5391
|
+
| `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. Three sentences are about blank lines and name three different repairs: `line 1: the file begins with a blank line` (or `with N blank lines` for a run, one finding) is a blank before the first page name โ delete it; `line N: the file ends with a blank line` (or `with N blank lines`, one finding) is a blank after the last page block's final line โ delete it, since rigc ends the file on one newline; `line N: consecutive blank lines` is two blank lines side by side between the first page and the last โ delete one. None of the three is `the last page block declares no region`, which means a page name and its fields with no region after them. rigc writes none of the three: under `--atlas-in` the pack's blank lines are put in rigc's shape on re-emission (ยง0.2), so any of them means a file rigc did not write, handed to `validate`. โ ๏ธ 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` |
|
|
5392
|
+
| `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. There is no `spine-html` clause here โ a placeholder is free to differ from the region its `path` names **SKIP** when no attachment names a region *and* the atlas declares none โ both of its subjects at once |
|
|
5721
5393
|
| `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 |
|
|
5722
|
-
| `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
|
|
5394
|
+
| `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**: 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: the NaN is produced by stepping, and a static rig is never stepped โ the same subject `A09` skips on |
|
|
5723
5395
|
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
5724
5396
|
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
5725
|
-
| `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
|
|
5397
|
+
| `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) **SKIP** also when the rig budgets **only** triangles and the skeleton carries no mesh. A declared slot budget still PASSes there, because zero mesh slots is a count measured against a ceiling |
|
|
5726
5398
|
| `A14_NO_FULL_FRAME_MESH` | renderer | a mesh spans the whole stage โ a full-frame canvas that can never dirty-skip. **SKIP** when the skeleton declares no stage (ยง3.1): there is no full frame to span, and *unmeasured* must not print the same green as *measured and clear* |
|
|
5727
|
-
| `A15_IDLE_NO_MESH_BONE_KEYS` | renderer | the `idle` animation keys a bone that drives a mesh, directly or as a control bone. **SKIP** when there is no `idle` animation, or when the one there is carries no bone timeline โ a rule whose subject does not exist is unmeasured and not satisfied
|
|
5399
|
+
| `A15_IDLE_NO_MESH_BONE_KEYS` | renderer | the `idle` animation keys a bone that drives a mesh, directly or as a control bone. **SKIP** when there is no `idle` animation, or when the one there is carries no bone timeline โ a rule whose subject does not exist is unmeasured and not satisfied |
|
|
5728
5400
|
| `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`) |
|
|
5729
|
-
| `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
|
|
5401
|
+
| `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 โ as it is for `A06`, `A19` and `A27`; see `A07` |
|
|
5730
5402
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug โ report it |
|
|
5731
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part cannot draw a transparent pixel, so it would paint a solid rectangle over what is behind it. **The texels decide, on both routes
|
|
5732
|
-
| `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
|
|
5403
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part cannot draw a transparent pixel, so it would paint a solid rectangle over what is behind it. **The texels decide, on both routes**; the file header is only the fast negative. A file with no alpha channel (colour type 4 or 6) and no `tRNS` chunk has nowhere to keep a clear texel and is refused without being opened, by a sentence that names its colour type โ so re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. A file whose header says it **could** be transparent is opened and read until its first texel below full alpha; one clear texel passes, and none is refused in the words the packed route uses for a region, naming the file and what it can hold โ `is opaque in every one of its 200x80 texels` โฆ `its file can hold transparency โ colour type 6 (truecolour + alpha) carries an alpha channel โ and no texel uses it`. Saving as RGBA is not the repair: the runtime draws the texels, not the declaration. On a loose page the file is the part, so the whole decoded image is the rectangle and no atlas coordinate is read. Only the base plate may be opaque, and which image that is is decided once for both routes: the plate the build names โ on a cut manifest, the part whose window is the crop โ and, only when it names none, an image at least the stage's size. The rig's statement comes first because the two can disagree: a stage stated small enough for an overlay to cover would otherwise exempt that overlay. A rig spec cannot name a base plate, so a stageless rig-spec build, and `validate <dir>` on a stageless skeleton, has nothing to decide it; an opaque part there is refused with *"nothing here decides which image that is: this skeleton declares no stage size to measure one against"* and the two ways to decide it โ a `skeleton` stage the plate covers, or a cut manifest (for `validate`, the specs it was built from: `--rig`, `--motion` and the `--manifest`). 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. โ ๏ธ **That scan states its verdict over the texels it READ, and never over texels that are not on the page**: 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**, and the clause above 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 a scan would come 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 **disappears**, the scan finding 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. โ ๏ธ **A page file that cannot be read as PNG at all is the same non-measurement for every part on it**: one row per page naming its parts and pointing at `A06`, which names what the file is. **SKIP** when the atlas declares no page |
|
|
5404
|
+
| `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. Fix the rig spec's `controls`/`chain`, or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment |
|
|
5733
5405
|
| `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 |
|
|
5734
|
-
| `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
|
|
5735
|
-
| `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
|
|
5736
|
-
| `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
|
|
5406
|
+
| `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 |
|
|
5407
|
+
| `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 โ or has `damping` outside `[0, 1]`, which says `` physics "C" has damping 1.5; must be inside [0, 1] โ the per-step decay is `damping ** (60 * step)`, and the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: above 1 every velocity grows on every step and the offset diverges ``, the bound read off the row the key's refusal reads; `1` and `0` are inside, since both are finite at every rate โ **at rest, and on every physics timeline key**. 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. 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; the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: at 0 `update` returns before it does anything (`PhysicsConstraint.js:109-111`), so the constraint is muted โ rest it above 0, or key its mix above 0 in an animation ``. โ ๏ธ Every reason A23 prints for a setup value is that value's row `basis` arm (ยง4.4's table) โ an arithmetic arm names its expression (`physics "C" has massInverse Infinity; mass must be > 0 โ at 0 โฆ`), a behavioural one says it is rigc's call and what the value does. A setup `mix` **below** 0 is told the jiggle is applied inverted, not that it is muted: such a constraint is measured moving its bone by exactly the opposite of a positive mix. 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. `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)), so this sentence is met on a file, never on a decompiled rebuild. **SKIP** when the skeleton declares no physics constraint โ the same sentence `A36` and `A37` print for their own constraint types |
|
|
5408
|
+
| `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 |
|
|
5737
5409
|
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
5738
|
-
| `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
|
|
5739
|
-
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | renderer | a single-region page whose region name is not the PNG's basename. **SKIP** when the atlas declares no region
|
|
5410
|
+
| `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: the completeness clause reads it as every declared slot lost and names them |
|
|
5411
|
+
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | renderer | a single-region page whose region name is not the PNG's basename. **SKIP** when the atlas declares no region |
|
|
5740
5412
|
| `A28_RIBBON_ROWS_SHARE_WEIGHTS` | archetype | the two vertices of a ribbon row carry different weights, so the strip would change width. **SKIPs** on authored geometry and on a contour mesh โ neither has rows rigc paired |
|
|
5741
5413
|
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
5742
5414
|
| `A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | the animation drives past the measured containment ceiling, or scales a bone in the axis subtree |
|
|
5743
5415
|
| `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 |
|
|
5744
5416
|
| `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 |
|
|
5745
5417
|
| `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 |
|
|
5746
|
-
| `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
|
|
5747
|
-
| `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
|
|
5748
|
-
| `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
|
|
5749
|
-
| `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
|
|
5418
|
+
| `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`. 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 |
|
|
5419
|
+
| `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). **SKIP** when no animation carries a deform timeline |
|
|
5420
|
+
| `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. 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 |
|
|
5421
|
+
| `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: `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 |
|
|
5750
5422
|
| `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` |
|
|
5751
|
-
| `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
|
|
5752
|
-
| `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 (
|
|
5753
|
-
| `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
|
|
5754
|
-
| `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
|
|
5755
|
-
| `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 โ at the key **as the runtime stores it**: spine-core keeps key times as 32-bit floats, so a key at `0.2` is posed at `0.20000000298โฆ`, the later of the two, and not one float step before it, where a first key still shows the setup value and a stepped key the one before
|
|
5756
|
-
| `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
|
|
5757
|
-
| `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 โ at the key **as the runtime stores it**: spine-core keeps key times as 32-bit floats, so a key at `0.2` is posed at `0.20000000298โฆ`, the later of the two, and not one float step before it, where a first key still shows the setup value and a stepped key the one before
|
|
5758
|
-
| `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
|
|
5759
|
-
| `A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT` | both | an ik constraint resting at `mix` 0 that no animation keys **away from 0** (ยง4.9, ยง4.12). `IkConstraint.update` returns on `mix === 0`, so it sits in the update cache and moves nothing. The keys are read the way `A23`/`A36`/`A37` read theirs โ every value the loaded timeline poses on its `mix` channel, Bezier samples included โ so a timeline keying 0 only is no rescue: [measured] it poses every bone exactly where the same rig with no constraint does
|
|
5760
|
-
| `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` | both | a transform constraint none of whose mixes **for a property it drives** is away from 0 at setup or on any value an animation poses (ยง4.10, ยง4.12), or one whose `properties` name no `to` at all. A property is applied only when its own mix `!== 0`, and a key that omits a mix reads it as 1, so the six-mix early return of `TransformConstraint.update` would take a key of `mixRotate: 0` alone as a rescue โ [measured] that key, and one keying `mixX` 1 on a rotate-only constraint, pose every bone exactly where no constraint does
|
|
5423
|
+
| `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 still refused. โ ๏ธ And the **spans between** consecutive keys are scanned too (ยง4.11.3): 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): 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), 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 |
|
|
5424
|
+
| `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 (two classes declare that flag falsely about themselves: a path constraint's `mix` and a slider's `time` compose although the flag says they do not). 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 |
|
|
5425
|
+
| `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. 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 |
|
|
5426
|
+
| `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. 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 |
|
|
5427
|
+
| `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 โ at the key **as the runtime stores it**: spine-core keeps key times as 32-bit floats, so a key at `0.2` is posed at `0.20000000298โฆ`, the later of the two, and not one float step before it, where a first key still shows the setup value and a stepped key the one before, 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 it is left where it was. **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 |
|
|
5428
|
+
| `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 |
|
|
5429
|
+
| `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 โ at the key **as the runtime stores it**: spine-core keeps key times as 32-bit floats, so a key at `0.2` is posed at `0.20000000298โฆ`, the later of the two, and not one float step before it, where a first key still shows the setup value and a stepped key the one before, 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 not measured 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. **SKIP** when no animation keys an `rgb` or `alpha` โ there is then no separable slot colour to read back |
|
|
5430
|
+
| `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. **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 a `hold` key to its own time as the runtime stores it, a 32-bit float โ 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 |
|
|
5431
|
+
| `A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT` | both | an ik constraint resting at `mix` 0 that no animation keys **away from 0** (ยง4.9, ยง4.12). `IkConstraint.update` returns on `mix === 0`, so it sits in the update cache and moves nothing. The keys are read the way `A23`/`A36`/`A37` read theirs โ every value the loaded timeline poses on its `mix` channel, Bezier samples included โ so a timeline keying 0 only is no rescue: [measured] it poses every bone exactly where the same rig with no constraint does. Live is the runtime's `!== 0`, so a negative mix is not refused. `ik constraint "C" has mix 0 at setup and none of the 1 animation keys its mix above 0; update() returns on mix 0, so "upper" never reaches for "goal" โ rest it above 0, or key its mix above 0 in an animation, or declare that the consumer drives its mix, in the rig spec as invariants.consumerDrivenMix: [{ "constraint": "C", "type": "ik", "why": โฆ }]`. A constraint the rig spec declares in `invariants.consumerDrivenMix` (ยง3.7) is not measured, and one the file also switches on is refused: `ik constraint "C" is declared in the rig spec as invariants.consumerDrivenMix, and the file already switches it on โ โฆ โ so the declaration exempts nothing; drop the entry`. **SKIP** when the skeleton declares no ik constraint, and **SKIP by name** when every ik constraint it declares is declared consumer-driven: `every ik constraint here is declared in the rig spec as invariants.consumerDrivenMix, so its mix is the consumer's to set and nothing in this file shows it moving โ "C" (why: โฆ): it rests muted and none of the 1 animation keys its mix above 0`. With a live one beside it the rule measures that one and the stats line names the declared (`ikConsumerDriven`) |
|
|
5432
|
+
| `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` | both | a transform constraint none of whose mixes **for a property it drives** is away from 0 at setup or on any value an animation poses (ยง4.10, ยง4.12), or one whose `properties` name no `to` at all. A property is applied only when its own mix `!== 0`, and a key that omits a mix reads it as 1, so the six-mix early return of `TransformConstraint.update` would take a key of `mixRotate: 0` alone as a rescue โ [measured] that key, and one keying `mixX` 1 on a rotate-only constraint, pose every bone exactly where no constraint does. A negative mix runs, and five transforms in the editor's example exports rest at โ1. `transform constraint "C" drives rotate and has mixRotate 0 at setup, and none of the 1 animation keys its mix above 0; a mix is read only for a property the constraint drives, and update() skips each one at 0, so nothing ever moves "follower" โ rest mixRotate above 0, or key its mix above 0 in an animation, or declare that the consumer drives its mix, in the rig spec as invariants.consumerDrivenMix: [{ "constraint": "C", "type": "transform", "why": โฆ }]`. A constraint the rig spec declares in `invariants.consumerDrivenMix` (ยง3.7) is not measured, and one the file also switches on is refused as `A47`'s is; one that drives no property is refused with its own sentence declared or not, since no mix it carries is read by anybody. **SKIP** when the skeleton declares no transform constraint, and **SKIP by name** when every transform constraint it declares is declared consumer-driven: `every transform constraint here is declared in the rig spec as invariants.consumerDrivenMix, so its mix is the consumer's to set and nothing in this file shows it moving โ "C" (why: โฆ): it rests muted and none of the 1 animation keys its mix above 0`. With a live one beside it the stats line names the declared (`transformConsumerDriven`) |
|
|
5761
5433
|
|
|
5762
5434
|
`both โ` marks a mixed assertion: its validity half always runs and its policy
|
|
5763
5435
|
clauses are gated by profile.
|
|
@@ -5772,46 +5444,40 @@ own behaviour is worse: an unknown attachment `type` returns `null` and the
|
|
|
5772
5444
|
attachment disappears, and a constraint entry with an unrecognised `type` matches no
|
|
5773
5445
|
case and vanishes.
|
|
5774
5446
|
|
|
5775
|
-
A deferral carries its reason, and there is one deferred attachment type
|
|
5776
|
-
**`point` appears nowhere in the benchmark corpus
|
|
5777
|
-
|
|
5778
|
-
deferral without its reason is a wall rather than a work item.
|
|
5779
|
-
beside it until [#691](https://github.com/firejune/rigc/issues/691) and is now
|
|
5780
|
-
emitted โ ยง3.4 has its fields.
|
|
5447
|
+
A deferral carries its reason, and there is one deferred attachment type:
|
|
5448
|
+
**`point` appears nowhere in the benchmark corpus**, so it is not on the ladder's
|
|
5449
|
+
critical path. The message says so, because a
|
|
5450
|
+
deferral without its reason is a wall rather than a work item.
|
|
5781
5451
|
|
|
5782
5452
|
โ ๏ธ **A spelling the format does not have is a different refusal and says so.**
|
|
5783
5453
|
`sequence` is not an attachment type, and a `"type"` that is `null` is not an absent
|
|
5784
5454
|
one โ telling either author that "rigc does not emit it yet" promises work that will
|
|
5785
5455
|
never be done, on a map the parser would have dropped in silence. Those rows below
|
|
5786
|
-
are `CompileError`s, and they name what the format actually defines
|
|
5787
|
-
([#577](https://github.com/firejune/rigc/issues/577)).
|
|
5456
|
+
are `CompileError`s, and they name what the format actually defines.
|
|
5788
5457
|
|
|
5789
5458
|
| You wrote | You get |
|
|
5790
5459
|
| --- | --- |
|
|
5791
5460
|
| attachment `type` of `point` | `this attachment is a "point" โ a position and an angle with no geometry at all โ "x", "y", "rotation" and "color" โฆ. rigc does not emit it yet, deliberately: it emits region, mesh, linkedmesh, boundingbox, clipping, path, and a point appears nowhere in the benchmark corpus โฆ` โ the message names the **construct**, not just its type string, and part 1-6 is where the sentence comes from |
|
|
5792
|
-
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) | **not a refusal
|
|
5461
|
+
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) | **not a refusal** โ both spellings compile to a linked mesh (ยง3.4). They share one parser branch and `source` is what decides between them (SPEC_COVERAGE part 1-6), so `source` on a mesh is a linked mesh whatever `type` says |
|
|
5793
5462
|
| attachment `type` of anything else โ `sequence`, a typo | `attachment type "X" is not one of the 7 the Spine 4.3 format defines (region, mesh, linkedmesh, boundingbox, path, point, clipping). โฆ the attachment is dropped from the skeleton without a word` โ a **`CompileError`**, not a deferral: rigc is not going to implement a name the format does not have. (`sequence` is a key on a region or a mesh, not a type of its own.) |
|
|
5794
|
-
| `"type": null` | `"type" is null, which is not a name. โฆ PRESENT-and-null is not absent: getValue(map, "type", "region") takes the default only when the key is missing, so this map matches no case, readAttachment returns null, and the attachment is dropped from the skeleton without a word. Remove the key, or name a type.` Leaving the key **out** is legal and reads as `region`; writing it as `null` is not the same thing
|
|
5463
|
+
| `"type": null` | `"type" is null, which is not a name. โฆ PRESENT-and-null is not absent: getValue(map, "type", "region") takes the default only when the key is missing, so this map matches no case, readAttachment returns null, and the attachment is dropped from the skeleton without a word. Remove the key, or name a type.` Leaving the key **out** is legal and reads as `region`; writing it as `null` is not the same thing |
|
|
5795
5464
|
| constraint `type` of anything else | `constraint type "X" is not one Spine 4.3 knows. The five are: ik, transform, path, physics, slider.` โ all five are emitted, so this is a typo, and a typo is what the parser drops in silence |
|
|
5796
5465
|
| any key neither format has, anywhere in either file | `<object> has a key this compiler does not read: "x" (did you mean "y"?) โฆ Known here: โฆ` (ยง5.1). Not a deferral either: a key nothing reads is a value you wrote and the emitted skeleton does not contain |
|
|
5797
5466
|
|
|
5798
5467
|
Two more limits that are not errors but will shape what you can attempt:
|
|
5799
5468
|
|
|
5800
|
-
- **The atlas packer and importer exist and are narrow
|
|
5469
|
+
- **The atlas packer and importer exist and are narrow.** `--pack`
|
|
5801
5470
|
arranges the parts onto shared pages and `--atlas-in` resolves them against a
|
|
5802
5471
|
pack somebody else made โ **ยง0.1** and **ยง0.2** state what each does and, more
|
|
5803
|
-
usefully, what neither does: no trimming, no rotation, no scaling
|
|
5804
|
-
|
|
5805
|
-
page** with `pma: false` and every region covering its whole page, and nothing
|
|
5472
|
+
usefully, what neither does: no trimming, no rotation, no scaling. The default is
|
|
5473
|
+
**one part per page** with `pma: false` and every region covering its whole page, and nothing
|
|
5806
5474
|
about a build changes unless one of those flags is given.
|
|
5807
5475
|
- **`drawOrderFolder`** is walked by the validator (so `A05` checks its curves and
|
|
5808
5476
|
`diff` counts its keys) and cannot be *written*: there is no motion-spec property
|
|
5809
|
-
for it. Everything a motion spec **can** key is ยง4.4's track table โ which
|
|
5477
|
+
for it. Everything a motion spec **can** key is ยง4.4's track table โ which
|
|
5810
5478
|
includes the `path` and `slider` groups (ยง4.12) โ plus the six families that sit
|
|
5811
5479
|
beside `tracks`: `drawOrder` (ยง4.7), `events` (ยง4.8), `ik` (ยง4.9), `transform`
|
|
5812
|
-
(ยง4.10), `deform` (ยง4.11) and `sequence` (ยง4.13).
|
|
5813
|
-
`drawOrderFolder` in this sentence until
|
|
5814
|
-
[#729](https://github.com/firejune/rigc/issues/729) made it writable.
|
|
5480
|
+
(ยง4.10), `deform` (ยง4.11) and `sequence` (ยง4.13).
|
|
5815
5481
|
|
|
5816
5482
|
---
|
|
5817
5483
|
|
|
@@ -5820,12 +5486,10 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
5820
5486
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
5821
5487
|
Saying nothing means `spine`, so "the one you meant" is a decision either way โ
|
|
5822
5488
|
the report's first line names the profile that judged it.
|
|
5823
|
-
๐ The two profiles judge attachment **naming** identically
|
|
5824
|
-
|
|
5825
|
-
renderer and archetype rules and has no opinion about how an attachment is
|
|
5489
|
+
๐ The two profiles judge attachment **naming** identically: `spine-html` adds
|
|
5490
|
+
the renderer and archetype rules and has no opinion about how an attachment is
|
|
5826
5491
|
spelled, so a rig whose named skins share a placeholder (ยง3.4.2) is green
|
|
5827
|
-
under either.
|
|
5828
|
-
one was not a property of the rig.
|
|
5492
|
+
under either.
|
|
5829
5493
|
2. Read the `SKIP` lines. Each one is a check that did *not* run โ make sure none of
|
|
5830
5494
|
them is a check you were relying on. The summary's *measured* figure is the
|
|
5831
5495
|
shortest version of this step: it is how many of the rules actually looked at
|
|
@@ -5839,7 +5503,7 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
5839
5503
|
of how it reads: if `A00` failed, most of the SKIP lines say the round trip
|
|
5840
5504
|
denied them a result, and the handful of `PASS` rows beside them were measured
|
|
5841
5505
|
on the raw text alone. Step 1 already sent you back; do not take anything from
|
|
5842
|
-
the rest of that report on the way
|
|
5506
|
+
the rest of that report on the way.
|
|
5843
5507
|
3. Read the `PROF` lines. They are where "was this rig held to that rule at all"
|
|
5844
5508
|
gets answered for everything the profile left out โ the renderer policy *and*
|
|
5845
5509
|
the archetype rules. A green under `spine` has been held to neither; a green
|
|
@@ -6028,9 +5692,8 @@ part's own height**, from four independent fits, which is the quantity that had
|
|
|
6028
5692
|
agree between them and did.
|
|
6029
5693
|
|
|
6030
5694
|
**A value is easier to get right than a curve.** The three traps above are all
|
|
6031
|
-
about measuring a *value*, and
|
|
6032
|
-
|
|
6033
|
-
build. What was wrong was the *shape between* them โ every accelerating segment
|
|
5695
|
+
about measuring a *value*, and on the ladder the values come out right early:
|
|
5696
|
+
rung 1's key values were exact at every keyframe on the second build. What was wrong was the *shape between* them โ every accelerating segment
|
|
6034
5697
|
had the decelerating curve and vice versa, from one inverted comparison. Nothing
|
|
6035
5698
|
in a static reading of the file can find that, because both candidates for the
|
|
6036
5699
|
shape are legal and the file reads fine either way, and a symmetric pair of easings
|
|
@@ -6335,8 +5998,7 @@ your candidate under one of its skins (below), `--all-frames` to list
|
|
|
6335
5998
|
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
6336
5999
|
per-slot report.
|
|
6337
6000
|
|
|
6338
|
-
๐จ **What `check` certifies is the DEFAULT skin, unless you pass `--skin
|
|
6339
|
-
(issue #571). With no `--skin` no skin is set at all, which is `spine-core`'s own
|
|
6001
|
+
๐จ **What `check` certifies is the DEFAULT skin, unless you pass `--skin`.** With no `--skin` no skin is set at all, which is `spine-core`'s own
|
|
6340
6002
|
initial state: every slot resolves through `SkeletonData.defaultSkin` alone, and a
|
|
6341
6003
|
slot whose art lives only in a named skin draws **nothing** โ on both sides, since
|
|
6342
6004
|
the reference frames came out of the same renderer. A multi-skin rig checked that
|
|
@@ -6376,7 +6038,7 @@ A skin name the candidate does not declare is refused with the ones it does โ
|
|
|
6376
6038
|
name.** `A39` and the `DEFORM` block pose each key with the skin that key is keyed
|
|
6377
6039
|
on โ a deform timeline's address is a `skin / slot / attachment` triple โ so a
|
|
6378
6040
|
mesh in a named skin is measured in that skin without anything being passed on the
|
|
6379
|
-
command line (ยง4.11.5
|
|
6041
|
+
command line (ยง4.11.5). The difference is where the name comes from,
|
|
6380
6042
|
not which command cares: `check` compares two picture sets and neither of them
|
|
6381
6043
|
records a skin unless you say so, while a deform key knows its own.
|
|
6382
6044
|
|
|
@@ -6387,8 +6049,7 @@ frames, and spineboy's `@30fps` sets do the same. The frame table still says
|
|
|
6387
6049
|
`frames 2 on disk, candidate samples 311, 2 compared` โ those are the files โ and a
|
|
6388
6050
|
**`sheet` line under it** carries the other 309: your candidate sampled at the set's
|
|
6389
6051
|
own rate, rendered into the same box the frames above were at the sheet's own scale,
|
|
6390
|
-
and compared tile by tile
|
|
6391
|
-
comparator for, in its own working directory, because the tool could not see its shot).
|
|
6052
|
+
and compared tile by tile.
|
|
6392
6053
|
|
|
6393
6054
|
โ ๏ธ **Read it as a series, not as one number** โ ยง9.2. And note what it does not
|
|
6394
6055
|
carry: MAE only. The `ฮpx` / `ref ฮ` thresholds are calibrated at frame scale and a
|
|
@@ -6417,8 +6078,8 @@ your shot's length is not a whole number of coarse intervals, expect **both**
|
|
|
6417
6078
|
duration the coarse sidecar understates, and a terminal pose only the finer set shows
|
|
6418
6079
|
you.
|
|
6419
6080
|
|
|
6420
|
-
`--fps <n>` exists for frame sets that have no `frames.json` beside them
|
|
6421
|
-
|
|
6081
|
+
`--fps <n>` exists for frame sets that have no `frames.json` beside them: it gives
|
|
6082
|
+
the rate those frames were
|
|
6422
6083
|
sampled at, and without it the 12 fps protocol rate is assumed and the report says
|
|
6423
6084
|
so rather than letting the assumption look like a measurement. Passing `--fps` with
|
|
6424
6085
|
a value the sidecar contradicts is an error, not an override.
|
|
@@ -6435,15 +6096,15 @@ fitting it. Two uses:
|
|
|
6435
6096
|
MAE moves by more than a point for a fraction of a pixel, that separation is
|
|
6436
6097
|
worth more than the absolute number.
|
|
6437
6098
|
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6099
|
+
A third use โ *"you already know your candidate's world coordinates match the
|
|
6100
|
+
reference's own, declared in `frames.json`"* โ is one **`check` covers for you.** Before
|
|
6101
|
+
fitting anything it renders your candidate
|
|
6441
6102
|
into the box `frames.json` records and measures where your pixels land. If they
|
|
6442
6103
|
land on the reference's to within a pixel, that box is yours too, and it is used:
|
|
6443
6104
|
it is not an *estimate* of where the frames were drawn, it is where they were
|
|
6444
6105
|
drawn, and the framing line says `frames.json's own box โ the candidate measured
|
|
6445
6106
|
into it`. If they do not, the box is refused and your candidate is framed by its
|
|
6446
|
-
own pixels
|
|
6107
|
+
own pixels โ which is the ordinary case, because the reference's
|
|
6447
6108
|
origin is in a file you are not allowed to open.
|
|
6448
6109
|
|
|
6449
6110
|
That is worth a paragraph rather than a line because of what it costs when it is
|
|
@@ -6458,11 +6119,7 @@ author could tell that from a wrong animation without running the pin by hand.
|
|
|
6458
6119
|
Pinning to paper over a **real** framing difference โ rather than one of the two
|
|
6459
6120
|
cases above โ is the dishonest use: it makes a genuine mismatch between your
|
|
6460
6121
|
candidate and the reference disappear from the report instead of showing up as
|
|
6461
|
-
`content`/`rms`/`union residual`.
|
|
6462
|
-
the old quad-corner framing could be wrong by more than a pixel for reasons that had
|
|
6463
|
-
nothing to do with either side's motion โ two honest ladder runs measured it costing
|
|
6464
|
-
30+ points of MAE with no key changed, which is why framing is now fitted to drawn
|
|
6465
|
-
pixels rather than quad corners (issue #34, closed by #39; see ยง9.2). Pin to a box
|
|
6122
|
+
`content`/`rms`/`union residual`. Pin to a box
|
|
6466
6123
|
you can name a reason for, and read the unpinned framing line first when you are not
|
|
6467
6124
|
sure whether you have one.
|
|
6468
6125
|
|
|
@@ -6483,7 +6140,7 @@ Why the split falls there, both halves measured on an 8-shot character (147 fram
|
|
|
6483
6140
|
that is not in the frames' coordinates puts the pooled correction over the
|
|
6484
6141
|
one-pixel threshold and the *whole root* falls back to a fit. Per set, the shots
|
|
6485
6142
|
that qualify read what pinning by hand reads: `idle` **18.77** where a whole-root
|
|
6486
|
-
run read 41.59, with not one key different
|
|
6143
|
+
run read 41.59, with not one key different.
|
|
6487
6144
|
- **Fitting per set is worse, so `check` does not.** `fitFraming` registers extent,
|
|
6488
6145
|
and extent is not alignment (see the โ ๏ธ in ยง9.2), so on a shot whose silhouette
|
|
6489
6146
|
genuinely differs one shot's frames do not constrain the fit enough: `hit` reads
|
|
@@ -6491,8 +6148,7 @@ Why the split falls there, both halves measured on an 8-shot character (147 fram
|
|
|
6491
6148
|
set reads 101.94 against 42.98. More frames is a better-conditioned fit, so the
|
|
6492
6149
|
fallback is deliberately the shared one.
|
|
6493
6150
|
|
|
6494
|
-
`--framing shared` measures **every** set in the shared framing
|
|
6495
|
-
whole-root run used to do. It answers one question and it is a good one โ *does a
|
|
6151
|
+
`--framing shared` measures **every** set in the shared framing. It answers one question and it is a good one โ *does a
|
|
6496
6152
|
single box serve every set?* โ and it is the wrong number to read as one shot's
|
|
6497
6153
|
fidelity.
|
|
6498
6154
|
|
|
@@ -6534,7 +6190,7 @@ and [the ladder's honesty rule](https://github.com/firejune/rigc/blob/main/docs/
|
|
|
6534
6190
|
them is a candidate's own reading against rendered frames โ the exam question, not the
|
|
6535
6191
|
answer key โ so none of them narrows a reference-side measure, and a guide that censored
|
|
6536
6192
|
them would be teaching less for no gain in honesty. The criterion is under *The honesty
|
|
6537
|
-
rule* in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md)
|
|
6193
|
+
rule* in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md); what it *does* seal is a score written
|
|
6538
6194
|
over a reference's own count, and no such figure appears here.
|
|
6539
6195
|
|
|
6540
6196
|
๐จ **If you drive the runtime yourself, a bone's local transform lives on
|
|
@@ -6547,8 +6203,8 @@ first thing it hits is not a subtlety. **spine-core 4.3 keeps a bone's local
|
|
|
6547
6203
|
transform on `bone.pose`, not on the bone**, so `bone.rotation = โฆ` โ or `.x`,
|
|
6548
6204
|
`.y`, `.scaleX` โ is neither an error nor a rotation: it adds a property nothing
|
|
6549
6205
|
reads, and every frame renders as the setup pose. Write `bone.pose.rotation`.
|
|
6550
|
-
|
|
6551
|
-
poses
|
|
6206
|
+
Written on the bone, rung 8's poses read a flat 17.3; driven through `bone.pose`,
|
|
6207
|
+
the same poses measure **2.76**.
|
|
6552
6208
|
|
|
6553
6209
|
๐จ **That trap has a twin one level up: the *setup* transform lives on
|
|
6554
6210
|
`bone.data.setupPose`, not on `bone.data`.** `BoneData` extends `PosedData`,
|
|
@@ -6561,8 +6217,8 @@ trap, because nothing on the path raises: `undefined` propagates to `NaN`, and
|
|
|
6561
6217
|
file, the next build reads those as zero, `validate` is green and `check` runs.
|
|
6562
6218
|
โ **A `null` in your own placements dump is the signature of having read
|
|
6563
6219
|
`bone.data` directly** โ nothing in this format is ever legitimately null. On
|
|
6564
|
-
spineboy it
|
|
6565
|
-
|
|
6220
|
+
spineboy it costs the candidate MAE 13.0 โ 114.6 with a green gate throughout.
|
|
6221
|
+
Note that the two names are not
|
|
6566
6222
|
the same thing: `bone.data.setupPose` is
|
|
6567
6223
|
the setup transform, while `bone.setupPose()` on a `Bone` is the method that
|
|
6568
6224
|
resets `bone.pose` back to it.
|
|
@@ -6572,9 +6228,7 @@ resets `bone.pose` back to it.
|
|
|
6572
6228
|
stores; what gets drawn is that stored quad โ `computeWorldVertices` reads
|
|
6573
6229
|
`getOffsets(pose)`, never the fields โ so writing them is, again, neither an
|
|
6574
6230
|
error nor a move. **Call `attachment.updateSequence()` after every write**, or
|
|
6575
|
-
every frame renders the quad it was loaded with.
|
|
6576
|
-
4,500 renders and reported the same number for all of them before this surfaced
|
|
6577
|
-
(the same loop log, ยง4.2).
|
|
6231
|
+
every frame renders the quad it was loaded with.
|
|
6578
6232
|
|
|
6579
6233
|
โ **An MAE that is identical across every pose, and that does not move for any
|
|
6580
6234
|
parameter you sweep โ a bone's local transform, an attachment's offsets โ is one
|
|
@@ -6751,8 +6405,7 @@ of them.
|
|
|
6751
6405
|
```
|
|
6752
6406
|
|
|
6753
6407
|
**Read the framing block first.** Everything below it is computed on the grid it
|
|
6754
|
-
chose, so an error there arrives disguised as motion
|
|
6755
|
-
happened to two honest ladder runs before this was fixed (issue #34).
|
|
6408
|
+
chose, so an error there arrives disguised as motion.
|
|
6756
6409
|
|
|
6757
6410
|
Unless the frames' own box already fits you (above), your candidate is framed **by
|
|
6758
6411
|
its own drawn pixels**. `check` renders it at the frames' own rate and grid, takes
|
|
@@ -6786,9 +6439,8 @@ pass count, then one of
|
|
|
6786
6439
|
|
|
6787
6440
|
That procedure is blind to the two things it must be blind to. **An invisible
|
|
6788
6441
|
margin cannot move it**: a region's quad runs past its own artwork wherever the art
|
|
6789
|
-
is transparent, and
|
|
6790
|
-
|
|
6791
|
-
identical to the last decimal). **A choice of units cannot move it either**: a rig
|
|
6442
|
+
is transparent, and nothing outside the drawing is looked at: art padded by 20 px
|
|
6443
|
+
on two sides reports numbers identical to the last decimal. **A choice of units cannot move it either**: a rig
|
|
6792
6444
|
scaled by 2 % renders to the same pixels and reads the same MAE.
|
|
6793
6445
|
|
|
6794
6446
|
The lines, in order:
|
|
@@ -6832,11 +6484,11 @@ a difference that would have been cheaper to leave alone. Measured floor: about
|
|
|
6832
6484
|
third of a pixel on the ladder's shots. On most that is invisible; on a small
|
|
6833
6485
|
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five, and
|
|
6834
6486
|
on the spineboy sets a **constant** one or two pixels was worth 10โ30 % of the
|
|
6835
|
-
figure
|
|
6487
|
+
figure. This is the floor the frames' own box has no share in, which is
|
|
6836
6488
|
why `check` prefers that box whenever your pixels are measured to land in it;
|
|
6837
6489
|
`--viewport` is how you stop it in the cases that box does not cover.
|
|
6838
6490
|
|
|
6839
|
-
โญ **What a fitted framing
|
|
6491
|
+
โญ **What a fitted framing does about it: one final whole-pixel pass.** After
|
|
6840
6492
|
the fit settles (or cycles), `check` searches every whole-pixel offset within ยฑ2 px
|
|
6841
6493
|
for the lowest MAE over the reference's own drawn pixels and moves the box to the
|
|
6842
6494
|
best one, when that is worth at least 1 % of the figure. So a fitted set's numbers
|
|
@@ -6869,10 +6521,7 @@ blunter.
|
|
|
6869
6521
|
|
|
6870
6522
|
โ ๏ธ **Half of that denominator is yours, so do not optimise against it.** The union
|
|
6871
6523
|
is the pixels *either* side drew, and a large, mostly transparent sprite adds many
|
|
6872
|
-
cheap pixels to it โ so the *mean falls* on a candidate that got worse.
|
|
6873
|
-
hypothetical: spineboy-2's muzzle flare walked its own scale to 13x under a fitting
|
|
6874
|
-
loop doing exactly this, and cost every set in that run its framing (issue #119).
|
|
6875
|
-
So the line under the MAE divides the same difference by the pixels the
|
|
6524
|
+
cheap pixels to it โ so the *mean falls* on a candidate that got worse. So the line under the MAE divides the same difference by the pixels the
|
|
6876
6525
|
**reference** drew, a denominator nothing you do can grow. Read the union figure to
|
|
6877
6526
|
compare two builds of your own rig, where both sides cover about the same ground,
|
|
6878
6527
|
and the reference-denominator figure when you are deciding whether a change made
|
|
@@ -6915,7 +6564,7 @@ appear under the MAE:
|
|
|
6915
6564
|
is `|MAE โ above| โค floor`. So `MAE โ above` is what the texture explained on these
|
|
6916
6565
|
frames and `floor` is the most it could ever explain. **A floor near zero is a proof
|
|
6917
6566
|
that the texture is not your problem** โ which is a finding worth having before a
|
|
6918
|
-
day of key-hunting
|
|
6567
|
+
day of key-hunting.
|
|
6919
6568
|
|
|
6920
6569
|
Measured on rung 3: **6.13 / 6.01** with the candidate's own full-resolution atlas,
|
|
6921
6570
|
of which **4.45 and 4.10 is texture** โ about **70 %** โ leaving **1.68 / 1.91** that
|
|
@@ -6930,8 +6579,8 @@ pixel* across stopped being visible at half scale, so a substituted run can repo
|
|
|
6930
6579
|
frame-change disagreement the graded run does not have. Read it for the floor, never as
|
|
6931
6580
|
the verdict.
|
|
6932
6581
|
|
|
6933
|
-
๐จ **Do not run this with `--atlas`, which
|
|
6934
|
-
|
|
6582
|
+
๐จ **Do not run this with `--atlas`, which substitutes region *geometry* along with
|
|
6583
|
+
the texture.** `--atlas` names **your own**
|
|
6935
6584
|
atlas for the case where it is not beside your skeleton; point it at a foreign one and
|
|
6936
6585
|
the skeleton is re-loaded against that atlas. An atlas entry is not only a page and a
|
|
6937
6586
|
rectangle โ it carries **`rotate:`** and the trim offsets that say where the opaque part
|
|
@@ -6940,15 +6589,14 @@ your attachments are re-seated and the quads change. Worse, `spine-core` impleme
|
|
|
6940
6589
|
region's rotated corner assignment for `rotate: 90` and for nothing else, so a
|
|
6941
6590
|
270-packed region is sampled from the wrong part of the page outright.
|
|
6942
6591
|
|
|
6943
|
-
โ **The tell
|
|
6944
|
-
number went the wrong way.** A texture floor can only *explain* error, so a diagnostic
|
|
6592
|
+
โ **The tell is that the number goes the wrong way.** A texture floor can only *explain* error, so a diagnostic
|
|
6945
6593
|
that sends the MAE **up** on every set has substituted geometry rather than pixels.
|
|
6946
|
-
Rung 7 is that case โ its pack is `rotate: 270` โ and under `--atlas` it
|
|
6947
|
-
24.93 โ 28.84 and 19.17 โ 27.75, which
|
|
6594
|
+
Rung 7 is that case โ its pack is `rotate: 270` โ and under `--atlas` it reads
|
|
6595
|
+
24.93 โ 28.84 and 19.17 โ 27.75, which cannot be read as a floor at all. Under
|
|
6948
6596
|
`--texture-from` the same rung measures a floor of **1.7 MAE** on every set, about 2 %
|
|
6949
6597
|
of each figure: there *is* a floor, it is small, and the rig is the story after all.
|
|
6950
6598
|
|
|
6951
|
-
โ **You
|
|
6599
|
+
โ **You do not have to inspect the atlas for `rotate` or trim before running it.**
|
|
6952
6600
|
`--texture-from` keeps every world vertex where your own atlas put it and remaps only
|
|
6953
6601
|
the texture coordinates, through the drawing's own coordinate space, so a rotated or
|
|
6954
6602
|
trimmed pack lands the same artwork in the same place. If a region of yours is missing
|
|
@@ -6987,7 +6635,7 @@ same failure as capturing a guard's expected value from a screen that is already
|
|
|
6987
6635
|
broken: the baseline records the defect, and then the *repair* is what looks wrong.
|
|
6988
6636
|
|
|
6989
6637
|
โ๏ธ **`frames.json`'s own box, and the `declared` line that says whether you got it.**
|
|
6990
|
-
Every set
|
|
6638
|
+
Every set prints one, taken or refused, with the numbers that decided:
|
|
6991
6639
|
|
|
6992
6640
|
```
|
|
6993
6641
|
declared frames.json's own box: TAKEN, coincident โ a fit there asks for 0.08 px, under
|
|
@@ -7005,14 +6653,14 @@ correction, which is what a different origin or a different unit looks like, and
|
|
|
7005
6653
|
|
|
7006
6654
|
โ ๏ธ **The test is on extent, and extent is not the same claim as coordinates.** A
|
|
7007
6655
|
candidate authored in the frames' own world units โ one whose setup box lands on the
|
|
7008
|
-
reference's to the pixel โ
|
|
7009
|
-
a few per cent at the extremes, because `fitFraming` registers extent and a
|
|
7010
|
-
narrower reads as 6 % of scale, which is arithmetically what a units error
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
โ **`check`
|
|
6656
|
+
reference's to the pixel โ fails an extent test whenever its union content box
|
|
6657
|
+
differs by a few per cent at the extremes, because `fitFraming` registers extent and a
|
|
6658
|
+
box 6 % narrower reads as 6 % of scale, which is arithmetically what a units error
|
|
6659
|
+
reads as. On extent alone, rung 7 is refused on **all twelve** of its sets, and the
|
|
6660
|
+
fitted framing it falls back to costs every one of them 0.28โ2.01 MAE against the
|
|
6661
|
+
declared box โ a real cost, and **not** a sign the coordinates are wrong.
|
|
6662
|
+
|
|
6663
|
+
โ **`check` separates the two by asking whether one similarity can explain the
|
|
7016
6664
|
disagreement at all.** A difference of units or of origin *is* a similarity, so the fit
|
|
7017
6665
|
absorbs it exactly and leaves a residual near zero โ a rig at 2 % different units leaves
|
|
7018
6666
|
0.27 px rms. A silhouette differing at the extremes is not a similarity, so the residual
|
|
@@ -7055,8 +6703,7 @@ frame and wrong only in the relation between two:
|
|
|
7055
6703
|
structure as matching. The column says `the reference moves here and yours holds
|
|
7056
6704
|
still`.
|
|
7057
6705
|
|
|
7058
|
-
|
|
7059
|
-
line whenever the MAE is flat and something still looks wrong: a flat MAE says the
|
|
6706
|
+
Read this line whenever the MAE is flat and something still looks wrong: a flat MAE says the
|
|
7060
6707
|
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
7061
6708
|
and blinks where the reference does.
|
|
7062
6709
|
|
|
@@ -7075,8 +6722,7 @@ shifts under three world units and comes back, so the two coarse samples land on
|
|
|
7075
6722
|
same pose either side of it. So ยง10.3's *key both ends of the hold* has to be applied
|
|
7076
6723
|
**at each committed rate separately**, and equal values are not enough โ the two
|
|
7077
6724
|
samples have to be **keys**, or a planner reduces through one of them and an
|
|
7078
|
-
interpolant inside its own tolerance is not equality.
|
|
7079
|
-
run, the second of them for exactly that reason. โ Measure the frames' own
|
|
6725
|
+
interpolant inside its own tolerance is not equality. โ Measure the frames' own
|
|
7080
6726
|
frame-to-frame change **once per committed rate**, and where a rate holds, pin both
|
|
7081
6727
|
ends as keys whenever a finer rate moves between them.
|
|
7082
6728
|
|
|
@@ -7120,9 +6766,9 @@ There are two matchers and the `how` column says which one answered:
|
|
|
7120
6766
|
much wider than its box, and may not contain another of your parts' ink. The last
|
|
7121
6767
|
is the one a dominant part slips through otherwise โ rung 2's course is 81 % of a
|
|
7122
6768
|
blob that also holds the water, the panel and both rings, so the blob is only
|
|
7123
|
-
1.24x its ink and no wider than its box
|
|
7124
|
-
|
|
7125
|
-
below
|
|
6769
|
+
1.24x its ink and no wider than its box; on size and width alone, *"course drift
|
|
6770
|
+
11.2 px"* would be the distance to a five-part centroid. The third condition sends
|
|
6771
|
+
it to the matcher below, which reads 0.0 px.
|
|
7126
6772
|
- `tmpl 0.62` โ the reference merged your slot into a neighbour (they touch, or one
|
|
7127
6773
|
is drawn over the other), so the fallback rendered **your slot on its own** and
|
|
7128
6774
|
correlated it against the reference around where you drew it. The number is the
|
|
@@ -7133,9 +6779,9 @@ There are two matchers and the `how` column says which one answered:
|
|
|
7133
6779
|
template pixel you draw something over cannot match the reference wherever the
|
|
7134
6780
|
slot really is, so it adds the same residual at every offset โ and, because
|
|
7135
6781
|
sliding the template moves those samples onto other pixels, their gradient
|
|
7136
|
-
decides the winner wherever the visible basin is shallow.
|
|
7137
|
-
|
|
7138
|
-
|
|
6782
|
+
decides the winner wherever the visible basin is shallow. Correlated with those
|
|
6783
|
+
pixels, four of the seven examples in this repository report 0.8โ2.2 px against
|
|
6784
|
+
frames rendered from themselves.
|
|
7139
6785
|
|
|
7140
6786
|
โ ๏ธ **Both matchers are capped, and a blank is a real answer.** A part can be
|
|
7141
6787
|
displaced by about its own size and still be that part in the picture; past that,
|
|
@@ -7208,24 +6854,17 @@ never against a number from a page about another rig.** A component match prints
|
|
|
7208
6854
|
the other sentence โ two centroids have no such bound, only the search radius.
|
|
7209
6855
|
|
|
7210
6856
|
๐จ **The clamp bounds the figure only while the whole-pixel winner is the
|
|
7211
|
-
identity
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
one
|
|
7222
|
-
|
|
7223
|
-
๐ธ The same page said `3.7 px` before issue #678, for an unrelated reason worth
|
|
7224
|
-
keeping: the coarse sweep started at `โradius` and stepped by its stride, so the
|
|
7225
|
-
identity offset was on the lattice only when the stride divided the radius, and
|
|
7226
|
-
the `ยฑ1` refinement around a winner two pixels out could not reach back to it.
|
|
7227
|
-
|
|
7228
|
-
**A second example, and the one the repair was measured on.** `gallery/flex` draws
|
|
6857
|
+
identity.** That is why the template carries only what shows: with the pixels the
|
|
6858
|
+
candidate draws *over itself* in it, four of the seven examples in this repository
|
|
6859
|
+
read **0.81, 1.11, 2.13 and 2.21 px** against frames rendered from themselves.
|
|
6860
|
+
Those pixels match nothing wherever the slot really is, so they add a residual at
|
|
6861
|
+
every offset โ and sliding the template moves them onto other pixels, so their
|
|
6862
|
+
gradient walks the winner off the origin wherever the visible basin is shallow. The
|
|
6863
|
+
exhaustive whole-pixel field for `flex`'s backdrop then has its minimum at `(2, 0)`
|
|
6864
|
+
scoring 7.75 against the identity offset's 8.76. Correlating only what shows puts
|
|
6865
|
+
all seven on `(0, 0)`.
|
|
6866
|
+
|
|
6867
|
+
**A second example, and the one that paragraph was measured on.** `gallery/flex` draws
|
|
7229
6868
|
a banner and a leaf over a full-stage backdrop, so almost every slot of it reaches
|
|
7230
6869
|
the template matcher:
|
|
7231
6870
|
|
|
@@ -7245,8 +6884,6 @@ bun cli.ts check --candidate <build> --frames <frames>
|
|
|
7245
6884
|
per-frame all 29 adjacent pair(s) change by as much as the reference's own frames do
|
|
7246
6885
|
```
|
|
7247
6886
|
|
|
7248
|
-
That figure was **2.21 px** on the same command before #698, on the same slot.
|
|
7249
|
-
|
|
7250
6887
|
โญ **And the same frames plus two deliberately wrong builds are what say which column
|
|
7251
6888
|
answers which question.** Each differs from the build above in exactly one way โ
|
|
7252
6889
|
every easing reversed `(x1,y1,x2,y2) โ (1โx2,1โy2,1โx1,1โy1)`, and the ball's bottom
|
|
@@ -7349,9 +6986,9 @@ about your bone tree rather than about a hole in your figure.
|
|
|
7349
6986
|
- **A difference smaller than the render scale.** At rung 3's 0.117 px per unit, a
|
|
7350
6987
|
key 4 units out moves nothing. Author to the frames' precision and record that
|
|
7351
6988
|
the rest was not checkable.
|
|
7352
|
-
- **Whether a mesh is deformed or merely posed.**
|
|
6989
|
+
- **Whether a mesh is deformed or merely posed.** The rasteriser draws
|
|
7353
6990
|
meshes โ weighted, deformed, both โ so a rung with meshes is measurable. What it
|
|
7354
|
-
|
|
6991
|
+
cannot tell you is *how* a silhouette got its shape: a hull moved by a
|
|
7355
6992
|
bone chain and the same hull moved by deform keys render to the same pixels, and
|
|
7356
6993
|
the frames cannot separate them. Choose on what the rig has to do next, not on
|
|
7357
6994
|
what the frames appear to say. โ ๏ธ **But `bench` does see it.** Take a disc that
|
|
@@ -7385,9 +7022,9 @@ about your bone tree rather than about a hole in your figure.
|
|
|
7385
7022
|
was, and the interesting ones are interesting because it left the interval. Rung
|
|
7386
7023
|
4's ball makes contact entirely inside one twelfth of a second: the sheet puts it
|
|
7387
7024
|
**40 px below both 12 fps frames that bracket it**, while its x sits within their
|
|
7388
|
-
own range. A search whose reach
|
|
7389
|
-
therefore
|
|
7390
|
-
back
|
|
7025
|
+
own range. A search whose reach is scaled from those two frames' own step
|
|
7026
|
+
therefore looks ยฑ9 px for something 40 px away and reports the interpolation
|
|
7027
|
+
back: read the tile rather than inferring it.
|
|
7391
7028
|
|
|
7392
7029
|
---
|
|
7393
7030
|
|
|
@@ -7409,18 +7046,10 @@ frames. Every line is marked with where it comes from:
|
|
|
7409
7046
|
- ๐ **stated** โ quoted or paraphrased from the page linked in the line.
|
|
7410
7047
|
- ๐งฉ **inferred** โ this guide's reading of those pages. Spine does not say it.
|
|
7411
7048
|
- ๐ฌ **observed** โ read off the editor's own export of a rigc build, not from a
|
|
7412
|
-
page. Two round trips stand behind these:
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
which was true while every observation had already been adopted; ยง10.6 then
|
|
7417
|
-
carried one that had **not** been โ the path `lengths` disagreement โ so an
|
|
7418
|
-
observation is now marked by where it was read, and each says for itself
|
|
7419
|
-
whether rigc agrees with it. โญ That outstanding one has since been adopted
|
|
7420
|
-
([#560](https://github.com/firejune/rigc/issues/560)) and the legend is kept in
|
|
7421
|
-
this shape anyway: the reason to mark an observation by its source rather than
|
|
7422
|
-
by whether rigc follows it is that the second fact goes stale and the first
|
|
7423
|
-
does not.
|
|
7049
|
+
page. Two round trips stand behind these: one through Spine 4.3.23 and an
|
|
7050
|
+
eight-rig trip through Spine **4.3.26**, whose findings are collected in ยง10.6.
|
|
7051
|
+
An observation is marked by where it was read, and each says for itself whether
|
|
7052
|
+
rigc agrees with it.
|
|
7424
7053
|
|
|
7425
7054
|
### 10.1 Structure
|
|
7426
7055
|
|
|
@@ -7461,11 +7090,11 @@ a rig whose names miss reads near zero on all of them however well it is built.
|
|
|
7461
7090
|
carry each part's own name straight through โ PNG basename โ slot โ attachment, and
|
|
7462
7091
|
the bone that moves it โ instead of inventing a scheme of your own.
|
|
7463
7092
|
|
|
7464
|
-
**Both directions are measured.**
|
|
7465
|
-
body part, each named for the part,
|
|
7466
|
-
measures **an order of magnitude** above
|
|
7467
|
-
either side seeing the other.
|
|
7468
|
-
|
|
7093
|
+
**Both directions are measured.** On the ladder, a run whose art ships a separate
|
|
7094
|
+
PNG per body part, each named for the part, and that carries those names through,
|
|
7095
|
+
posts `names` measures **an order of magnitude** above runs that invent their own โ
|
|
7096
|
+
without either side seeing the other. It is the largest single thing a convention
|
|
7097
|
+
has bought on the ladder.
|
|
7469
7098
|
|
|
7470
7099
|
โ ๏ธ **And the other half, which matters just as much: when the art is *not* named
|
|
7471
7100
|
after the parts, no naming strategy beats any other and the measure is noise.** On a
|
|
@@ -7486,16 +7115,13 @@ two animations' 16 and 2 bone-timeline keys all came back sorted, while the 30
|
|
|
7486
7115
|
`bones`, 24 `slots` and 3 `constraints` โ arrays โ came back in the build's own
|
|
7487
7116
|
order, element for element, and each slider kept its place among them.
|
|
7488
7117
|
|
|
7489
|
-
๐จ **Not every array: `skins` it re-sorts
|
|
7490
|
-
ARRAY alone"* for two releases, on those three arrays and nothing else, and
|
|
7491
|
-
`skins` is the one it was wrong about โ see the paragraph at the end of this
|
|
7118
|
+
๐จ **Not every array: `skins` it re-sorts** โ see the paragraph at the end of this
|
|
7492
7119
|
section.
|
|
7493
7120
|
|
|
7494
7121
|
โ ๏ธ **The order it sorts them into is natural and case-insensitive, not
|
|
7495
|
-
codepoint.**
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
`turn2, turn10, zoom`. The corpus says the same thing and always did โ of its 105
|
|
7122
|
+
codepoint.** `Turn, sweep, wave` came back `sweep, Turn, wave` and
|
|
7123
|
+
`turn10, turn2, zoom` came back `turn2, turn10, zoom`. The corpus says the same
|
|
7124
|
+
thing โ of its 105
|
|
7499
7125
|
name-keyed collections, 102 are consistent with a codepoint sort and **3 are
|
|
7500
7126
|
not**: `spineboy-pro.json` keys `portal-flare9` *before* `portal-flare10`, which
|
|
7501
7127
|
no codepoint sort produces. Every natural comparator reproduces all 105. (The
|
|
@@ -7506,54 +7132,34 @@ one key, deform blocks counted at each of their three levels;
|
|
|
7506
7132
|
โ in rigc: `animations` is emitted sorted (R10), because it is the one
|
|
7507
7133
|
object measured here whose ORDER is also an index space โ every reference into
|
|
7508
7134
|
the re-sorted *other* objects is by name on both sides, so nothing moves when
|
|
7509
|
-
they are re-keyed. โ ๏ธ
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
same text (ยง10.6b), and 11 of the twelve under `examples/` keyed a skin in draw
|
|
7513
|
-
order where the export sorts it. Nothing is refused on that map โ a pair the
|
|
7135
|
+
they are re-keyed. โ ๏ธ A skin's `attachments` slot keys are emitted sorted too,
|
|
7136
|
+
for the **text** and for no reference: a rebuild of an editor export is the export
|
|
7137
|
+
only if it is the same text (ยง10.6b). Nothing is refused on that map โ a pair the
|
|
7514
7138
|
comparator leaves open, or a slot name with a `/` in it, keeps the whole map in
|
|
7515
|
-
rigc's order instead. rigc emits **that comparator's own order**,
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
โ
**`events` is re-keyed too, and the references into it survive it.** The same
|
|
7524
|
-
session measured it: `zebra, mike, alpha` came back `alpha, mike, zebra`, and the
|
|
7525
|
-
firings still resolved **by name** โ `0.3 -> mike`, `0.6 -> alpha`, payloads
|
|
7526
|
-
intact (#539). So the editor treats `events` and `animations` differently, and
|
|
7139
|
+
rigc's order instead. rigc emits **that comparator's own order**, measured in full
|
|
7140
|
+
off five stored round trips (R10). Sorting the 105 collections that way reproduces
|
|
7141
|
+
**105 of 105**, the three codepoint cannot included, and refuses none of them.
|
|
7142
|
+
|
|
7143
|
+
โ
**`events` is re-keyed too, and the references into it survive it.** Measured:
|
|
7144
|
+
`zebra, mike, alpha` came back `alpha, mike, zebra`, and the firings still resolved
|
|
7145
|
+
**by name** โ `0.3 -> mike`, `0.6 -> alpha`, payloads intact. So the editor treats `events` and `animations` differently, and
|
|
7527
7146
|
rigc emits events in the order you declare them.
|
|
7528
7147
|
|
|
7529
|
-
๐จ **`skins` is re-sorted, with `default` pinned first โ measured
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
([#541](https://github.com/firejune/rigc/issues/541)), and the deform timelines
|
|
7148
|
+
๐จ **`skins` is re-sorted, with `default` pinned first โ measured.** A four-skin rig
|
|
7149
|
+
built `default, zulu, mike, alpha` exported `default, alpha, mike, zulu`, and the
|
|
7150
|
+
deform timelines
|
|
7533
7151
|
came back keyed `mike, zulu` rather than `zulu, mike` with it. Note `alpha` sorts
|
|
7534
7152
|
before `default` under every candidate comparator and still came back second: the
|
|
7535
7153
|
default skin is **pinned**, not sorted. It matters for the same reason `animations`
|
|
7536
7154
|
does โ `skins` carries ordinals in the binary half, `skins[readInt()]` for an
|
|
7537
|
-
attachment timeline and `skins[skinIndex]` for a linked mesh โ so
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
โ
**Which comparator it is
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
`animations` share one comparator โ measured, not inferred โ and the wider skin
|
|
7546
|
-
refusal is gone (R11).
|
|
7547
|
-
|
|
7548
|
-
โ
**The two readings this replaces.** A pull request once called `skins` *measured
|
|
7549
|
-
preserved*, on the strength of a one-skin rig where a one-element array comes back
|
|
7550
|
-
in order whatever the editor does to it;
|
|
7551
|
-
[#544](https://github.com/firejune/rigc/issues/544) corrected that to *unmeasured*,
|
|
7552
|
-
and added that it could not be measured because the editor refused a four-skin rig
|
|
7553
|
-
on import without printing a word. Both readings were the same generalisation โ *an
|
|
7554
|
-
editor does not move arrays* โ off the three arrays that were measured, and the
|
|
7555
|
-
"without a word" was rigc's own harness discarding the editor's stderr, which had
|
|
7556
|
-
named the cause all along (ยง3.4.2).
|
|
7155
|
+
attachment timeline and `skins[skinIndex]` for a linked mesh โ so it carries the
|
|
7156
|
+
same hazard as `animations`. โ in rigc: R11.
|
|
7157
|
+
|
|
7158
|
+
โ
**Which comparator it is, is measured too.** `alpha, mike, zulu` is the order
|
|
7159
|
+
codepoint, case-folding and natural order all produce, so that rig alone decides
|
|
7160
|
+
nothing between them. Two of the five round trips carried one name list as
|
|
7161
|
+
**both** collections and both came back in one order, so `skins` and `animations`
|
|
7162
|
+
share one comparator โ measured, not inferred (R11).
|
|
7557
7163
|
|
|
7558
7164
|
### 10.2 Draw order
|
|
7559
7165
|
|
|
@@ -7894,15 +7500,15 @@ and a character's body bone almost never has them there, which is the very shape
|
|
|
7894
7500
|
paragraph above was written from. Turn the parent by ฮด and a child *at the origin*
|
|
7895
7501
|
back by ฮด and the child is where it was; a child sitting 10 units off swings through
|
|
7896
7502
|
an arc of that radius first, and the counter-turn only spins it on the spot. The fold
|
|
7897
|
-
moves art, so it **changes the picture**. Measured on
|
|
7898
|
-
|
|
7899
|
-
**3 MAE on every `idle` frame** โ mean 23.0 with it against 19.9 without, same
|
|
7900
|
-
|
|
7503
|
+
moves art, so it **changes the picture**. Measured on a spineboy run whose `hip`
|
|
7504
|
+
carries no attachment and has three children 9โ13 units off it: the fold costs
|
|
7505
|
+
**3 MAE on every `idle` frame** โ mean 23.0 with it against 19.9 without, same
|
|
7506
|
+
search. โ Read the children's offsets before you fold. At the origin,
|
|
7901
7507
|
fold: it is exact and it is cheap. Off the origin the degeneracy is still there but
|
|
7902
7508
|
it is **soft**, not exact, and a soft degeneracy is *regularised, not folded* โ leave
|
|
7903
7509
|
the values alone and add a penalty on the gauge direction to the objective instead.
|
|
7904
|
-
|
|
7905
|
-
angles, and still decisive against the +181ยฐ against โ184ยฐ above.
|
|
7510
|
+
**2e-5 per squared degree** of hip rotation is enough there: invisible at
|
|
7511
|
+
animator-sized angles, and still decisive against the +181ยฐ against โ184ยฐ above.
|
|
7906
7512
|
|
|
7907
7513
|
### 10.4 Curves
|
|
7908
7514
|
|
|
@@ -7943,16 +7549,15 @@ by fitting each span's **own** handles, and then writes the nearest entry of a n
|
|
|
7943
7549
|
table, has bought a key count at one tolerance and shipped it at another. Nothing in
|
|
7944
7550
|
the loop can see that: the timeline count, the key count, the curve kinds and the
|
|
7945
7551
|
duration are all unmoved, so `diff` does not shift and the gate stays green, while
|
|
7946
|
-
the rendered result changes by a multiple.
|
|
7947
|
-
|
|
7552
|
+
the rendered result changes by a multiple. Measured on rung 8, that goes from
|
|
7553
|
+
**1.07 to 4.65 MAE** โ four times the fit's own floor. โ Two passes.
|
|
7948
7554
|
Pass A fits freely and exists only to *discover* which shapes the shot uses; those
|
|
7949
7555
|
are clustered into the table; pass B re-plans **every** timeline under the table it
|
|
7950
7556
|
will actually write. Never fit free handles and substitute the nearest named shape
|
|
7951
7557
|
after the fact. (The table's size then trades against the key count at a fixed
|
|
7952
7558
|
tolerance โ that shot ran 4 easings/368 keys, 8/314, 12/300, 16/284 โ because a
|
|
7953
|
-
richer table holds more spans.)
|
|
7954
|
-
|
|
7955
|
-
constraint.**
|
|
7559
|
+
richer table holds more spans.) **A constraint that is not enforced where the
|
|
7560
|
+
value is written is not a constraint.**
|
|
7956
7561
|
|
|
7957
7562
|
๐งฉ **โ A span with no interior sample takes the automatic handles, not linear.** Two
|
|
7958
7563
|
keys on adjacent samples leave pass B nothing to fit โ the samples cannot constrain
|
|
@@ -7960,9 +7565,9 @@ that span's shape at all โ and a planner with nothing to fit leaves `ease` off
|
|
|
7960
7565
|
is linear (ยง4.5): the one shape this section says to argue for rather than default to.
|
|
7961
7566
|
"No information" is not an argument for constant speed. Take instead the tangents the
|
|
7962
7567
|
keys either side imply โ the editor's own **automatic handles**, quoted above โ and
|
|
7963
|
-
snap *those* to the nearest table entry, exactly as you would a fitted span.
|
|
7964
|
-
|
|
7965
|
-
|
|
7568
|
+
snap *those* to the nearest table entry, exactly as you would a fitted span. Done for
|
|
7569
|
+
rung 8's adjacent pairs, the samples barely move (**17.34 โ 17.26 MAE**) while
|
|
7570
|
+
`curve_kinds` changes wholesale: nil in the frames, large in the
|
|
7966
7571
|
structure, which is the signature of a convention rather than a fidelity fix โ and
|
|
7967
7572
|
this section's whole subject.
|
|
7968
7573
|
|
|
@@ -7984,7 +7589,7 @@ of key does not have a transition, such as slot attachment or event keys"* โ
|
|
|
7984
7589
|
every key that carried a curve into a segment whose next key holds the same value on
|
|
7985
7590
|
every channel comes back as `"curve": "stepped"` โ 14 keys on the gallery's `nod`
|
|
7986
7591
|
alone, with every rendered frame byte-identical, because a curve over a flat segment
|
|
7987
|
-
draws nothing. rigc emits the same for a **named** easing (ยง4.5
|
|
7592
|
+
draws nothing. rigc emits the same for a **named** easing (ยง4.5), so
|
|
7988
7593
|
`diff`'s `animations.curve_kinds` reads 1.000 between a build and its own export
|
|
7989
7594
|
rather than charging the hold rewrites against the timing. A raw `curve` stays as
|
|
7990
7595
|
written: it is the file's own numbers, and the reference corpus has the editor itself
|
|
@@ -8012,8 +7617,8 @@ without the box and rebuilds the hull on its own.
|
|
|
8012
7617
|
the command line the โ above is the exception rather than the case.** The
|
|
8013
7618
|
sentence is still true of an export made without the box; what is measured is
|
|
8014
7619
|
that `-e json` with no export-settings file does not make one. Every field on the
|
|
8015
|
-
nonessential list came back present, unchanged and not zero on
|
|
8016
|
-
(
|
|
7620
|
+
nonessential list came back present, unchanged and not zero on the eight-rig
|
|
7621
|
+
round trip (Spine 4.3.26): the header's `fps` (24 on `fields`, the one rig that
|
|
8017
7622
|
declares it) and `images`; a mesh's `width`/`height` (64/48) and `edges`
|
|
8018
7623
|
(16 entries, identical); the editor colours of a **bounding box** (`3cff6bff`), a
|
|
8019
7624
|
**clipping** polygon (`ff3c6bff`) and a **path** (`ff6b3cff`); and a bone's
|
|
@@ -8032,10 +7637,10 @@ binary export is performed using default settings"* โ
|
|
|
8032
7637
|
those defaults**, so the answer above is measured here and documented nowhere.
|
|
8033
7638
|
โ In practice: do not plan around fields being dropped. An export you did not
|
|
8034
7639
|
personally make without the box is an export that has them, and the round trip is
|
|
8035
|
-
therefore **richer** than the build rather than poorer โ which is why
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
7640
|
+
therefore **richer** than the build rather than poorer โ which is why a second
|
|
7641
|
+
trip does not degrade a mesh's hull and edges: no import warning on any of the
|
|
7642
|
+
eight rigs, and a five-vertex mesh's `hull: 4` came back `4` rather than
|
|
7643
|
+
recomputed to `5`. A nonessential-**off** trip would need an
|
|
8039
7644
|
export-settings JSON, and โ the key name inside that file is not documented
|
|
8040
7645
|
either.
|
|
8041
7646
|
|
|
@@ -8045,18 +7650,15 @@ omit them in raw JSON and every UV collapses, in silence. Name an `image`.
|
|
|
8045
7650
|
|
|
8046
7651
|
โ
**You need not imitate the exporter's omissions โ the emitter does it for you.**
|
|
8047
7652
|
Spine's exporter drops fields equal to their default, which is why the format page is
|
|
8048
|
-
a long list of *"assume 0 if omitted"*, and
|
|
8049
|
-
|
|
8050
|
-
ones (R1, ยง2; the table is ยง10.6c). So an author may still state a default โ `x: 0`
|
|
7653
|
+
a long list of *"assume 0 if omitted"*, and rigc's emitter drops the same ones
|
|
7654
|
+
(R1, ยง2; the table is ยง10.6c). So an author may still state a default โ `x: 0`
|
|
8051
7655
|
in a rig spec is legitimate and stays in the spec โ and the file leaves it out.
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
the twelve under `examples/`, and one that leaves them out is the export's own text
|
|
8057
|
-
apart from the header's `hash` and `spine` (`IG83`, and [INGEST ยง2.3](INGEST.md)).
|
|
7656
|
+
**The round trip is the reason.** A rebuild of an editor export that writes back
|
|
7657
|
+
every default the export left out is a different file from the export in 2,338
|
|
7658
|
+
places over the twelve under `examples/`, and one that leaves them out is the
|
|
7659
|
+
export's own text apart from the header's `hash` and `spine` ([INGEST ยง2.3](INGEST.md)).
|
|
8058
7660
|
The habit worth carrying over is still not *omit defaults* โ it is *declare only what
|
|
8059
|
-
the shot needs*;
|
|
7661
|
+
the shot needs*; stating a default costs nothing in the file.
|
|
8060
7662
|
|
|
8061
7663
|
### 10.6 What a round trip gives back
|
|
8062
7664
|
|
|
@@ -8066,8 +7668,8 @@ is a construct that might vanish there, and an agent cannot see that it did.
|
|
|
8066
7668
|
|
|
8067
7669
|
๐ฌ The source is one run: eight discriminator rigs, each built to isolate a group
|
|
8068
7670
|
of fields, compiled by rigc **0.21.0** (emitting 4.3.13), imported into a licensed
|
|
8069
|
-
Spine **4.3.26** through the documented CLI and exported back
|
|
8070
|
-
|
|
7671
|
+
Spine **4.3.26** through the documented CLI and exported back, with the
|
|
7672
|
+
predictions written down before anything was opened. **Seven of the eight came
|
|
8071
7673
|
back differing from their build in three header fields and nothing else** โ
|
|
8072
7674
|
`hash` and `audio`, which the editor adds, and `spine`, which it stamps with its
|
|
8073
7675
|
own version. The eighth is the path rig, and it is the last bullet here.
|
|
@@ -8078,8 +7680,8 @@ comes back `48.0`) and **omitted defaults** โ the export drops any field equal
|
|
|
8078
7680
|
its parser default, so the header loses `x: 0` and `y: 0`, a bone loses `x: 0`, and
|
|
8079
7681
|
a key at t=0 loses its `"time": 0`. Every name-keyed object is also re-sorted, per
|
|
8080
7682
|
ยง10.1. None of those is a loss of information, and each is worth knowing before
|
|
8081
|
-
you read a `diff`. ๐
|
|
8082
|
-
|
|
7683
|
+
you read a `diff`. ๐ rigc leaves out the bone's `x: 0` and the key's `"time": 0`
|
|
7684
|
+
as well (ยง10.6c); the
|
|
8083
7685
|
header's origin is not, because the 4.3 JSON reader has no default for it
|
|
8084
7686
|
(`skeletonData.x = skeletonMap.x`, `SkeletonJson.js:70`), so an absent origin loads
|
|
8085
7687
|
as `undefined` where a written one loads as `0` โ writing it is exact for both
|
|
@@ -8116,11 +7718,9 @@ evidence that the format will carry what you write.
|
|
|
8116
7718
|
- ๐ฌ **A path attachment's `closed: true` and `constantSpeed: false` survive**, and
|
|
8117
7719
|
so do its `position`, `spacing` and three-channel `mix` timelines โ all three
|
|
8118
7720
|
channels of every `mix` key, and all twelve curve numbers on each key that
|
|
8119
|
-
carries a curve. โ ๏ธ Its `lengths`
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
What they are computed ON is a second question, which the round trip could not
|
|
8123
|
-
ask and #804 did: see the end of this section.
|
|
7721
|
+
carries a curve. โ ๏ธ Its `lengths` came back recomputed โ the paragraph after
|
|
7722
|
+
this list says how, and rigc emits that same computation. What they are
|
|
7723
|
+
computed ON is a second question: see the end of this section.
|
|
8124
7724
|
- ๐ฌ **`physics.mix` and `physics.reset` timelines survive**, the `reset` key
|
|
8125
7725
|
included โ a key that carries a time and no value at all โ and so does the
|
|
8126
7726
|
physics constraint's setup `mix`.
|
|
@@ -8132,8 +7732,7 @@ evidence that the format will carry what you write.
|
|
|
8132
7732
|
five-vertex mesh declaring `hull: 4` came back `4`, with its `edges`, `color`,
|
|
8133
7733
|
`uvs` and `triangles` unchanged. Beside it, on the same rig: the header's
|
|
8134
7734
|
`referenceScale`, a region's `rotation` / `scaleX` / `scaleY` / `color`, and an
|
|
8135
|
-
attachment whose `path` differs from its placeholder
|
|
8136
|
-
[#552](https://github.com/firejune/rigc/issues/552)) all survive. A `--pack`
|
|
7735
|
+
attachment whose `path` differs from its placeholder all survive. A `--pack`
|
|
8137
7736
|
build round-trips too.
|
|
8138
7737
|
|
|
8139
7738
|
๐จ **The one thing that did not come back is a path attachment's `lengths`, and it
|
|
@@ -8150,21 +7749,18 @@ under 4.3.26 (`[152.7006, 305.4012, 458.1019, 610.8025]`) and on an open one und
|
|
|
8150
7749
|
โ ๏ธ **That last reading settles the model and cannot settle the spelling.** A
|
|
8151
7750
|
4-sample chord sum agrees with the runtime's forward difference to about **nine
|
|
8152
7751
|
significant digits** โ *below* what float32 can hold, which is why both spellings
|
|
8153
|
-
reproduce both exports exactly, and
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
โญ **rigc emits the forward difference itself** since
|
|
8163
|
-
[#560](https://github.com/firejune/rigc/issues/560) โ `pathCurveLengths` in
|
|
7752
|
+
reproduce both exports exactly, and below what rigc's own file holds too, because
|
|
7753
|
+
rigc writes each number as its float32. At six fixed decimals the two would round
|
|
7754
|
+
apart on the **last** curve of both rigs above, where the running total has
|
|
7755
|
+
accumulated most โ `610.802519` against `610.802520`, `1127.735817` against
|
|
7756
|
+
`1127.735818`. So the editor is the evidence for *what* is computed, and only
|
|
7757
|
+
`PathConstraint` itself is evidence for *how*.
|
|
7758
|
+
|
|
7759
|
+
โญ **rigc emits the forward difference itself** โ `pathCurveLengths` in
|
|
8164
7760
|
[`src/compile.ts`](../src/compile.ts) is those runtime lines transcribed, down to
|
|
8165
7761
|
`Math.sqrt(dx * dx + dy * dy)` rather than `Math.hypot` and `0.16666667` rather
|
|
8166
7762
|
than `1 / 6`, both of which change the emitted file. All seven entries of the two
|
|
8167
|
-
exports above
|
|
7763
|
+
exports above come back at the precision the editor prints them, so a path rig
|
|
8168
7764
|
built here and one authored in the editor parameterise identically. โ ๏ธ The
|
|
8169
7765
|
consequence for you is a vocabulary one: `lengths` is **not** an arc length. It
|
|
8170
7766
|
sits about 0.5 % below the arc by construction, so a physical quantity โ how far a
|
|
@@ -8175,31 +7771,26 @@ out of the artifact.
|
|
|
8175
7771
|
wrap-around curve even on an open path** โ that open path's fourth entry,
|
|
8176
7772
|
`2136.228`, is the closed-chain cumulative. The parser allocates `vertexCount / 3`
|
|
8177
7773
|
and copies whatever is there, and `PathConstraint` reads at most
|
|
8178
|
-
`lengths[curveCount]`, so the trailing entry is never read โ and
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
- **Scale.** rigc blended weighted vertices through setup matrices that ignored
|
|
8189
|
-
bone scale, shear and `inherit`: a 50/50-weighted probe path over a bone at
|
|
8190
|
-
scale 2 measured 0.752ร the runtime's own `curves`. The matrices are the
|
|
8191
|
-
runtime's now, and an omitted `lengths` measures within 1e-4 of `curves`
|
|
8192
|
-
(`PS186`).
|
|
7774
|
+
`lengths[curveCount]`, so the trailing entry is never read โ and rigc writes it
|
|
7775
|
+
too, measured over the closed chain, so the `gallery/ride` build ends on the same `2136.228`.
|
|
7776
|
+
|
|
7777
|
+
๐จ **The computation is only half of the number โ the other half is the geometry
|
|
7778
|
+
it is fed.** Both rigs above are unweighted, unscaled and unconstrained; on a path
|
|
7779
|
+
that is not, two things decide what an omitted `lengths` measures:
|
|
7780
|
+
|
|
7781
|
+
- **Scale.** rigc blends weighted vertices through the runtime's own setup
|
|
7782
|
+
matrices โ bone scale, shear and `inherit` included โ so an omitted `lengths`
|
|
7783
|
+
measures within 1e-4 of the runtime's own `curves`.
|
|
8193
7784
|
- **Constraints.** The editor measures the pose the first update hands the path
|
|
8194
7785
|
constraint, with every constraint ordered before it applied; a transform
|
|
8195
7786
|
constraint on the path's slot bone ordered after it moves nothing. rigc does
|
|
8196
7787
|
not pose, so it cannot reproduce that โ an omitted `lengths` is the
|
|
8197
|
-
**unconstrained** setup figure
|
|
7788
|
+
**unconstrained** setup figure, and on a path a constraint moves at
|
|
8198
7789
|
rest that is not the editor's number.
|
|
8199
7790
|
|
|
8200
7791
|
โ **A stated `lengths` is carried as stated**, and `ingest` writes the source's
|
|
8201
7792
|
array into the spec, so a rebuilt export is its export on this field. What rigc
|
|
8202
|
-
|
|
7793
|
+
measures is an array you left out of a spec you wrote.
|
|
8203
7794
|
|
|
8204
7795
|
โ **What this means for you.** `lengths` is the one number in a path rig you
|
|
8205
7796
|
cannot check by looking: `diff` does not compare it, and `A33` asks only that it
|
|
@@ -8209,29 +7800,25 @@ path constraint, against **0.0000** on the other seven. If you are comparing a
|
|
|
8209
7800
|
path rig against an editor reference and everything structural agrees while the
|
|
8210
7801
|
picture does not, this is the first place to look.
|
|
8211
7802
|
|
|
8212
|
-
โ ๏ธ **What decides whether it moves a pixel is the POSITION mode**,
|
|
8213
|
-
|
|
8214
|
-
mode it cannot be: proportional spacing scales *with* the total, and that is
|
|
7803
|
+
โ ๏ธ **What decides whether it moves a pixel is the POSITION mode**, not
|
|
7804
|
+
`spacingMode: proportional`, which is the one mode it cannot be: proportional spacing scales *with* the total, and that is
|
|
8215
7805
|
exactly what cancels. The rig that drifted is `positionMode: fixed`, where an
|
|
8216
7806
|
absolute `position` is compared against a total that moved. Under
|
|
8217
7807
|
`positionMode: percent` the position scales with the total too, so a uniform
|
|
8218
|
-
change cancels out of both โ measured
|
|
8219
|
-
|
|
8220
|
-
|
|
7808
|
+
change cancels out of both โ measured: `gallery/ride` is percent/percent, and
|
|
7809
|
+
every one of its 74 rendered frames comes back **byte identical** across an
|
|
7810
|
+
emitted array all three of whose numbers change. โ Read a
|
|
8221
7811
|
`lengths` disagreement as *certainly wrong data, and visible only under
|
|
8222
7812
|
`positionMode: fixed`*.
|
|
8223
7813
|
|
|
8224
7814
|
### 10.6b The order it writes keys in
|
|
8225
7815
|
|
|
8226
7816
|
๐ฌ **The editor writes every object's keys in one fixed order per kind of
|
|
8227
|
-
object**, and
|
|
8228
|
-
writes the same. Read off the twelve exports under `examples/`: for each kind,
|
|
7817
|
+
object**, and rigc writes the same. Read off the twelve exports under `examples/`: for each kind,
|
|
8229
7818
|
every object's key sequence is a subsequence of one order, no two exports
|
|
8230
7819
|
contradict each other on any kind, and the table below is that order. It lives
|
|
8231
7820
|
in one place, [`src/keyorder.ts`](../src/keyorder.ts)'s `EDITOR_KEY_ORDER`, and
|
|
8232
|
-
the compiler applies it once to the finished skeleton
|
|
8233
|
-
to that one, and `IG77` holds that one to the exports on every run with a
|
|
8234
|
-
corpus.
|
|
7821
|
+
the compiler applies it once to the finished skeleton.
|
|
8235
7822
|
|
|
8236
7823
|
| Kind | Keys, in the order the editor writes them |
|
|
8237
7824
|
| --- | --- |
|
|
@@ -8274,8 +7861,8 @@ says which.**
|
|
|
8274
7861
|
- **A pair no export carries together has no measured order**, and the row
|
|
8275
7862
|
places it the way rigc already emitted it โ a transform constraint's
|
|
8276
7863
|
`rotation` before `x`, an ik key's `time` before `mix`, a physics
|
|
8277
|
-
constraint's `x`/`y` before `rotate`. `src/keyorder.ts` lists every such pair
|
|
8278
|
-
|
|
7864
|
+
constraint's `x`/`y` before `rotate`. `src/keyorder.ts` lists every such pair,
|
|
7865
|
+
and nothing here says the editor agrees.
|
|
8279
7866
|
- **A kind with no row keeps rigc's order whole**: a linked mesh, a path or
|
|
8280
7867
|
point attachment, a sequence, the path and slider constraints, an event
|
|
8281
7868
|
definition, and the keys of every timeline the twelve do not key with two
|
|
@@ -8289,10 +7876,8 @@ says which.**
|
|
|
8289
7876
|
`time` โ first in every other key kind the editor writes โ behind `value`.
|
|
8290
7877
|
|
|
8291
7878
|
๐ฌ **Two positions and one fold are measured on a production set, not on
|
|
8292
|
-
`examples
|
|
8293
|
-
|
|
8294
|
-
and `IG85` count both on every run with a corpus โ so the table cannot learn
|
|
8295
|
-
either, and what rigc writes for them was read off 42 production exports
|
|
7879
|
+
`examples/`**. The twelve exports carry no mesh `path` or `color` and no slot
|
|
7880
|
+
name outside ASCII, so the table cannot learn either, and what rigc writes for them was read off 42 production exports
|
|
8296
7881
|
instead:
|
|
8297
7882
|
|
|
8298
7883
|
- **A mesh attachment writes `path` and `color` right after `type`, `path` before `color` โ measured on a production set (#791), not on `examples/`.**
|
|
@@ -8300,51 +7885,46 @@ instead:
|
|
|
8300
7885
|
`color` wrote that second; no mesh carried both. The row does not list them,
|
|
8301
7886
|
so the position is the constructor's โ `meshTextureKeys` in
|
|
8302
7887
|
[`src/compile.ts`](../src/compile.ts), which every mesh route spreads right
|
|
8303
|
-
after `type
|
|
7888
|
+
after `type`. A region's `path` and a linked mesh's keys stay where
|
|
8304
7889
|
they were: neither was in the set, and an analogy is not a measurement.
|
|
8305
7890
|
- **A skin's slot keys are compared after `foldBeforeComparing`, which reads U+3000 as U+0020 and U+FF10โU+FF19 as U+0030โU+0039** โ
|
|
8306
7891
|
the ideographic space as a space, the full-width digits as `0`โ`9`
|
|
8307
7892
|
(`EDITOR_NAME_FOLD`). One map of 95 slot keys, 7 of them carrying those
|
|
8308
7893
|
characters, came back in an order this reproduces 95 of 95 and neither a
|
|
8309
|
-
codepoint sort nor the comparator without the fold does
|
|
7894
|
+
codepoint sort nor the comparator without the fold does. It is **not**
|
|
8310
7895
|
`normalize('NFKC')`, though NFKC folds both the same way: NFKC, a classifier
|
|
8311
7896
|
that reads every Unicode digit as a digit and every space separator as a space,
|
|
8312
7897
|
and a width fold alone all reproduce that map and disagree past it (on `๏ฌ`,
|
|
8313
7898
|
`ยฒ`, `ูฃ`, `๏ผก`, a no-break space), and what all three agree on is exactly
|
|
8314
7899
|
these two classes. The comparator is R10's, so animation and skin names are
|
|
8315
7900
|
compared after the fold too. A pair the fold does not close โ a tab, a
|
|
8316
|
-
no-break space โ still keeps the map in rigc's order, whole
|
|
7901
|
+
no-break space โ still keeps the map in rigc's order, whole.
|
|
8317
7902
|
|
|
8318
7903
|
๐ธ **An object keyed by NAMES is not a field order**, and the table does not
|
|
8319
7904
|
touch one. A bone's or a slot's timelines, and a transform constraint's
|
|
8320
7905
|
`properties` and each `to` inside them, are read into the runtime's arrays in the
|
|
8321
7906
|
order they are keyed โ for timelines, the order they are applied in โ so their
|
|
8322
|
-
order is more than a key's position, and rigc
|
|
8323
|
-
|
|
8324
|
-
|
|
7907
|
+
order is more than a key's position, and rigc emits the export's own on all
|
|
7908
|
+
twelve. A skin's `attachments` slot keys are the one name-keyed map rigc sorts: by
|
|
7909
|
+
the editor's comparator (ยง10.1). The per-slot maps inside are
|
|
8325
7910
|
the rig's own order.
|
|
8326
7911
|
|
|
8327
|
-
โ **What this means for you: nothing to write.** No
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
and rigc wrote, `"name": null`, and the three header keys (`IG78`) โ and since
|
|
8333
|
-
ยง10.6c it finds the header's `hash` and `spine` and nothing else (`IG83`).
|
|
7912
|
+
โ **What this means for you: nothing to write.** No key order is yours to state โ
|
|
7913
|
+
a rig spec is read by name, and its field order means nothing. A canonical-form
|
|
7914
|
+
comparison of an export's rebuild against the export finds no object whose keys
|
|
7915
|
+
sit elsewhere, and with ยง10.6c it finds the header's `hash` and `spine` and
|
|
7916
|
+
nothing else.
|
|
8334
7917
|
|
|
8335
7918
|
### 10.6c The keys it leaves out
|
|
8336
7919
|
|
|
8337
7920
|
The editor writes a key only where its value is not the one the parser reads in its
|
|
8338
|
-
absence, and
|
|
8339
|
-
does the same, in one pass over the finished skeleton (`withoutParserDefaults` in
|
|
7921
|
+
absence, and rigc's emitter does the same, in one pass over the finished skeleton (`withoutParserDefaults` in
|
|
8340
7922
|
`src/keyorder.ts`, run just before ยง10.6b's). A key is left out when its value is
|
|
8341
7923
|
**exactly** the one below โ the float the file would hold, not a value near it โ so a
|
|
8342
7924
|
rotation of `1e-45` is written and a rotation of `0` is not. The table is
|
|
8343
7925
|
`PARSER_DEFAULTS`, and every row is the linked `spine-core` parser's own fallback
|
|
8344
|
-
(`getValue(map, key, default)` in `SkeletonJson.js`)
|
|
8345
|
-
|
|
8346
|
-
same `SkeletonData` (`S103` on the in-tree builds, `IG82` on the corpus), and deletes
|
|
8347
|
-
every key of every in-tree build in turn to find one the table is missing (`S101`):
|
|
7926
|
+
(`getValue(map, key, default)` in `SkeletonJson.js`), and an object of each kind
|
|
7927
|
+
loads to the same `SkeletonData` with the key at that value and without it:
|
|
8348
7928
|
|
|
8349
7929
|
| Kind | Left out when it is |
|
|
8350
7930
|
| --- | --- |
|
|
@@ -8399,14 +7979,14 @@ read an absent `mixY` as that same `0`. *The key before's* is a sequence key's
|
|
|
8399
7979
|
(`skeletonData.x = skeletonMap.x`, `SkeletonJson.js:70`), so an absent origin loads
|
|
8400
7980
|
as `undefined` and a written `0` as `0`: there is no fallback for them to equal, and
|
|
8401
7981
|
rigc keeps writing the origin of a declared stage. The editor leaves it out
|
|
8402
|
-
(ยง10.6
|
|
7982
|
+
(ยง10.6), so a rebuild of an export whose stage sits at `0,0` spells
|
|
8403
7983
|
two fields the export does not โ `LOSS HEADER_ORIGIN` says so on `ingest`.
|
|
8404
7984
|
- **A transform constraint's `mixY` and `mixScaleY`.** Their fallback is the
|
|
8405
7985
|
constraint's `mixX` / `mixScaleX` โ but the parser reads those only for a property
|
|
8406
7986
|
the constraint drives, so on one that drives `y` alone the fallback is the pose's
|
|
8407
|
-
initial `0`, not `1`.
|
|
8408
|
-
`8-follow-through-pro-ball`'s `"mixY": 1` on two such constraints, they
|
|
8409
|
-
`0`, and `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT`
|
|
7987
|
+
initial `0`, not `1`. Measured: a row there would leave out
|
|
7988
|
+
`8-follow-through-pro-ball`'s `"mixY": 1` on two such constraints, they would load
|
|
7989
|
+
at `0`, and `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` refuses that rebuild.
|
|
8410
7990
|
- **A region's `path`, an attachment's `name`, an event key's payload.** Their
|
|
8411
7991
|
fallbacks are another value โ the attachment's own name, its placeholder, the event
|
|
8412
7992
|
definition's โ which rigc writes only where it differs.
|
|
@@ -8417,19 +7997,21 @@ read an absent `mixY` as that same `0`. *The key before's* is a sequence key's
|
|
|
8417
7997
|
stated.
|
|
8418
7998
|
- **Every kind with no row** โ the keys of a `shearx`, `sheary`, `inherit`, `alpha`,
|
|
8419
7999
|
`rgb`, `rgb2`, `rgba2`, path `spacing`/`mix`, physics `gravity`/`reset` or slider
|
|
8420
|
-
timeline, a linked mesh, a point attachment. No build
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
โ ๏ธ **Why 4.3's defaults, when
|
|
8000
|
+
timeline, a linked mesh, a point attachment. No in-tree build and no export
|
|
8001
|
+
carries an object of those kinds, and a row nothing loads is a claim about the
|
|
8002
|
+
parser nobody has checked โ where a missing row only costs a key the parser reads
|
|
8003
|
+
the same either way.
|
|
8004
|
+
|
|
8005
|
+
โ ๏ธ **Why 4.3's defaults, when they are not every generation's** (row 4 of
|
|
8006
|
+
[GENERATIONS.md](https://github.com/firejune/rigc/blob/main/docs/GENERATIONS.md)'s
|
|
8007
|
+
table).
|
|
8426
8008
|
An omitted physics `inertia` is 0.5 to the 4.3 parser and 1 to the 4.2 one, and that
|
|
8427
8009
|
is exactly why a key is left out only against the parser rigc links: the file states
|
|
8428
8010
|
`"spine": "4.3.13"`, and the parser it is gated against is that one. Writing the
|
|
8429
8011
|
default out would not make the file safe for a 4.2 reader: that reader applies its
|
|
8430
8012
|
own defaults to every *other* key the file leaves out as well โ every one the editor's
|
|
8431
|
-
own 4.3 export leaves out, too โ and
|
|
8432
|
-
generation's data on another generation's runtime does, none of it repaired by one
|
|
8013
|
+
own 4.3 export leaves out, too โ and that table's rows 1, 2 and 6 measure what
|
|
8014
|
+
reading one generation's data on another generation's runtime does, none of it repaired by one
|
|
8433
8015
|
key written out. That misread is refused where it can be seen, on the reading side
|
|
8434
8016
|
(`GENERATION_UNSUPPORTED` in `ingest`), rather than written for on the emitting one.
|
|
8435
8017
|
|
|
@@ -8447,7 +8029,7 @@ answer read off the exports:
|
|
|
8447
8029
|
- any figure for keys per second, or for how key density scales with frame rate;
|
|
8448
8030
|
- which curve type any particular example project or studio actually shipped;
|
|
8449
8031
|
- whether a **corpus** export โ one somebody else made, out of the editor's own
|
|
8450
|
-
dialog โ was made with Nonessential data checked. โ ๏ธ ยง10.5
|
|
8032
|
+
dialog โ was made with Nonessential data checked. โ ๏ธ ยง10.5 answers this for
|
|
8451
8033
|
the **CLI's** `-e json`, where it is measured; that measurement says nothing
|
|
8452
8034
|
about an export you were handed, and the two must not be read as one;
|
|
8453
8035
|
- how many bones, slots or timelines a rig of a given size ought to have;
|
|
@@ -8480,21 +8062,10 @@ placements become a rig and a motion, what goes between two key poses when nothi
|
|
|
8480
8062
|
was given for the in-betweens, and how to spread candidates so a `vote` ballot
|
|
8481
8063
|
informs.
|
|
8482
8064
|
|
|
8483
|
-
๐ **
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
so a part is no longer charged for disagreeing with the ground it is drawn
|
|
8488
|
-
against. Nothing about the fields, the coordinate contract or the refusals
|
|
8489
|
-
changed, and no threshold moved โ `--max-residual`, `--min-visible` and
|
|
8490
|
-
`--anchor-residual` are the numbers they were. โ ๏ธ **What did move is the residuals
|
|
8491
|
-
themselves**, downward and by small amounts, most on the parts whose edges are in
|
|
8492
|
-
the frame. โ A residual measured before that date and one measured after are not
|
|
8493
|
-
the same measurement; re-read a frame rather than comparing across it. The worked
|
|
8494
|
-
figures live in [MOTION.md](MOTION.md) ยง6 and
|
|
8495
|
-
[BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md) โ
|
|
8496
|
-
repository material, not shipped in the package โ each with its own re-baseline
|
|
8497
|
-
note.
|
|
8065
|
+
๐ **The objective behind every `residual` here and in ยง12 interpolates the frame in
|
|
8066
|
+
premultiplied space**: a tap that straddles a silhouette weights each texel's colour
|
|
8067
|
+
by how much material is there, so a part is not charged for disagreeing with the
|
|
8068
|
+
ground it is drawn against. The worked figures live in [MOTION.md](MOTION.md) ยง6.
|
|
8498
8069
|
|
|
8499
8070
|
### 11.1 It measures an input, so nothing here is a score
|
|
8500
8071
|
|
|
@@ -8592,9 +8163,7 @@ holds nothing because it already contains every angle.
|
|
|
8592
8163
|
an edge of a `--rotation` window narrower than a full turn, the detail names it:
|
|
8593
8164
|
`best placement at scale 0.500, the floor of --scale 0.5,2 โ the truth may lie below the window`.
|
|
8594
8165
|
That is the case where the window is the first thing to move rather than the
|
|
8595
|
-
frame or the threshold
|
|
8596
|
-
`scale=0.500` against art rendered at `0.311` per part pixel, and the message
|
|
8597
|
-
said only that the residual was above `--max-residual`. The sentence โ with its
|
|
8166
|
+
frame or the threshold. The sentence โ with its
|
|
8598
8167
|
*"may lie below"* โ is printed on a refusal only.
|
|
8599
8168
|
- ๐ **An accepted placement that stopped on a wall says which wall too**, beside
|
|
8600
8169
|
the value it holds on the console line and in `walls`:
|
|
@@ -8646,7 +8215,7 @@ two things.
|
|
|
8646
8215
|
with the `visibleShare` it was computed on. โ ๏ธ **`visibleShare` is a per-frame
|
|
8647
8216
|
diagnostic and not a summary statistic** โ it is measured through the fitted
|
|
8648
8217
|
placements, so a median or a mean of it is not comparable across fits. ยง12.3 says
|
|
8649
|
-
what that costs and [the
|
|
8218
|
+
what that costs and [the `visibleShare` study](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md)
|
|
8650
8219
|
measures it โ repository material, not shipped in the package.
|
|
8651
8220
|
- **Hierarchy and attachment geometry**, so the search collapses. A child bone
|
|
8652
8221
|
whose parent is already placed does not have four degrees of freedom: the rig
|
|
@@ -8679,7 +8248,7 @@ for the same frame** โ `--anchor poseA.json` โ and without one it runs that
|
|
|
8679
8248
|
internally, over exactly the parts your candidate draws. A part becomes an anchor
|
|
8680
8249
|
when `pose` came back **unambiguous** with `residual โค 0.16` and
|
|
8681
8250
|
`unexplained โค 0.45`; those two numbers are `--anchor-residual` and a reported
|
|
8682
|
-
field, and they are
|
|
8251
|
+
field, and they are a measurement run's own *clean frame* criterion
|
|
8683
8252
|
rather than a line invented here.
|
|
8684
8253
|
|
|
8685
8254
|
An anchored part fixes its **whole bone** โ four numbers read off the picture for
|
|
@@ -8712,9 +8281,9 @@ one of those placements reading `"inward"` to say what it rests on.
|
|
|
8712
8281
|
โ ๏ธ **Which means the inward step reaches exactly the bones that BRANCH.** A bone
|
|
8713
8282
|
whose children form a single sub-chain can never be determined, however good the
|
|
8714
8283
|
anchor below it is: two equations, four unknowns. That is `no-bracket`, and it is
|
|
8715
|
-
a fact about your rig's topology rather than about the frame. On
|
|
8716
|
-
|
|
8717
|
-
it is the bone that
|
|
8284
|
+
a fact about your rig's topology rather than about the frame. On a spineboy
|
|
8285
|
+
candidate `torso` is the only bone in the whole rig that branches โ and without
|
|
8286
|
+
this step it is the bone that records 30 `no-anchor` frames.
|
|
8718
8287
|
|
|
8719
8288
|
| Field on `bone.inward` | Meaning |
|
|
8720
8289
|
| --- | --- |
|
|
@@ -8772,9 +8341,8 @@ themselves.
|
|
|
8772
8341
|
evidence lives on the anchors below it. The bone's own `residual` and
|
|
8773
8342
|
`visibleShare` say how much of the answer *the frame can independently confirm* โ
|
|
8774
8343
|
and **the visibility floor still refuses it** when the answer is one nothing in
|
|
8775
|
-
the picture can check. That is deliberate
|
|
8776
|
-
|
|
8777
|
-
anchors and reverted, because it prints a part nobody can see as READ. The floor
|
|
8344
|
+
the picture can check. That is deliberate: exempting a placement nothing searched
|
|
8345
|
+
prints a part nobody can see as READ. The floor
|
|
8778
8346
|
is about what the picture can confirm, not about how the number was arrived at.
|
|
8779
8347
|
|
|
8780
8348
|
### 12.3 What the report adds to a `pose` report
|
|
@@ -8827,15 +8395,14 @@ the answer landed and is *less* steady, not more (p99 0.6588 against 0.5592).
|
|
|
8827
8395
|
**The field is reporting a bistable fit faithfully.** What is safe: reading it beside
|
|
8828
8396
|
its own residual, on its own frame โ which is what it exists for. What is safe as a
|
|
8829
8397
|
corpus statistic: a **maximum**, which saturates. Full method and evidence:
|
|
8830
|
-
[`bench/studies/2026-09-03-visibleshare`](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md)
|
|
8831
|
-
([#323](https://github.com/firejune/rigc/issues/323)).
|
|
8398
|
+
[`bench/studies/2026-09-03-visibleshare`](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md).
|
|
8832
8399
|
|
|
8833
8400
|
And per part:
|
|
8834
8401
|
|
|
8835
8402
|
| Field | Meaning |
|
|
8836
8403
|
| --- | --- |
|
|
8837
8404
|
| `role` | `anchor` (taken from the anchor pass, not re-fitted), `chain` (fitted through the rig), `inward` (**determined** from two or more anchored descendants, with nothing searched โ ยง12.2b), `unplaced` |
|
|
8838
|
-
| โ | โญ **A refused ANCHOR is not a contradiction, and it is the most useful row in the table.** The anchor pass judged that placement over the part's *whole* footprint โ all `pose` can see, and blind to what covers it โ while this instrument has just measured how much of the part is visible at all. Both readings are true. A refused anchor means *the placement may well be right and the confirmation is missing*, and every part whose `anchoredTo` names that bone rests on it. Measured
|
|
8405
|
+
| โ | โญ **A refused ANCHOR is not a contradiction, and it is the most useful row in the table.** The anchor pass judged that placement over the part's *whole* footprint โ all `pose` can see, and blind to what covers it โ while this instrument has just measured how much of the part is visible at all. Both readings are true. A refused anchor means *the placement may well be right and the confirmation is missing*, and every part whose `anchoredTo` names that bone rests on it. Measured over the 147 committed `ess` frames, `rear-bracer` clears `pose`'s criterion on 81 of 147 frames at a median visible share of **0.1%** โ suppressing the refusal there prints that as READ. โ ๏ธ **That pair of numbers was taken before the objective interpolated in premultiplied space (ยง11).** A four-frame spot check on the current objective (`--min-visible 0`, committed `ess` frames `idle/f0000`, `run/f0002`, `walk/f0004`, `aim/f0000`) moved `rear-bracer`'s anchor residual *down* on all four โ 0.1564โ0.1555, 0.1519โ0.1513, 0.1493โ0.1488, 0.1537โ0.1534 โ and flipped its eligibility on none, while `visibleShare` moved materially on one of the four (0.36โ0.89). โ Read the **shape** of the row, not those two digits. ๐จ **And that median in particular is one of the ones the `visibleShare` study found unsafe**: `rear-bracer`'s share swings by **0.87** on `idle/f0001` inside `pose`'s own convergence band when the chain anchors on `pose`'s criterion โ the basis this figure was taken on. The count and the shape are the reading; the 0.1 % is not a number |
|
|
8839
8406
|
| โ | The **other** parts on an anchored bone are refused on their own numbers too, and there they mean something different again: their placement is the **rig's** prediction from that anchor, so their residual is a measurement of the rig (a goggle plate that will not sit on the head it is parented to shows up exactly here) |
|
|
8840
8407
|
| `bone` | the bone this hangs off: its `parent`, its `setupRotationDeg`, its `depth` in links from the anchor, `anchoredTo`, the `dof` searched, the `window` taken, the other parts `sharedWith` it on that bone, and `carriedBones` |
|
|
8841
8408
|
| `bone.dof.pivotFree` | your candidate keys a `translate` timeline on this bone, so the arc this answer sits on has a centre the rig itself moves. The placement is still read off pixels; `localRotationDeg` alone will not reproduce it |
|
|
@@ -8857,7 +8424,7 @@ simply unused; a name the directory lacks is refused `no-part-image` by name.
|
|
|
8857
8424
|
| --- | --- |
|
|
8858
8425
|
| `--anchor <pose.json>` | use this `rigc pose` report instead of running one. Refused together with `--scale` / `--rotation`, which size the internal pass that then does not happen |
|
|
8859
8426
|
| `--atlas` | **refused by name.** Every other `--candidate` command takes it, so trying it here is reasonable โ but the part art comes from `--images` and the skeleton is all this needs of the candidate, so a flag that silently did nothing would be worse than one that says why |
|
|
8860
|
-
| `--hinge <min,max>` | the window each child's local rotation is searched over, in Spine degrees about its setup value. Default `-180,180` โ **a full turn, on purpose**: one degree of freedom is cheap enough to sweep exhaustively, and ยง11.4's warning about a window that does not contain the truth applies here too. The hinge step `3ยฐ` is a **ceiling** on the step, not the step: a window it does not divide is divided into whole steps no coarser than it, and `search` states the step that division produced, so `--hinge -20,20` prints `hinge -20ยฐโ20ยฐ step 2.857ยฐ (15 rungs)` and each chain part's note says it was searched `in 2.857ยฐ steps`. The full turn divides it exactly
|
|
8427
|
+
| `--hinge <min,max>` | the window each child's local rotation is searched over, in Spine degrees about its setup value. Default `-180,180` โ **a full turn, on purpose**: one degree of freedom is cheap enough to sweep exhaustively, and ยง11.4's warning about a window that does not contain the truth applies here too. The hinge step `3ยฐ` is a **ceiling** on the step, not the step: a window it does not divide is divided into whole steps no coarser than it, and `search` states the step that division produced, so `--hinge -20,20` prints `hinge -20ยฐโ20ยฐ step 2.857ยฐ (15 rungs)` and each chain part's note says it was searched `in 2.857ยฐ steps`. The full turn divides it exactly |
|
|
8861
8428
|
| `--stretch <ratio>` | also search a uniform bone scale, this ratio either way. Without it, stretch is searched **only where your own animations key a `scale` timeline on that bone** โ a rig that never scales a bone is a rig saying that bone does not stretch |
|
|
8862
8429
|
| `--min-visible <0..1>` | below this visible share a placement is refused `occluded` instead of reported flat (default `0.25`). A reporting threshold, not a pass bar; the placement is still in the JSON. โ ๏ธ **It is not inert, though**: a bone whose frozen share is under this floor gets one *unmasked* look before its visible set is fixed, so the flag also changes **where parts land** and not only which rows are refused. Two runs at different `--min-visible` are two fits, and their shares are not one column |
|
|
8863
8430
|
| `--max-residual <0..1>` | as ยง11, over the visible pixels (default `0.25`) |
|