spine-rigc 0.34.0 → 0.35.1
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 +5 -5
- package/cli.ts +37 -6
- package/docs/AUTHORING.md +417 -43
- package/docs/INGEST.md +64 -16
- package/docs/SPEC_COVERAGE.md +1 -1
- package/package.json +1 -1
- package/src/compile.ts +164 -21
- package/src/ingest.ts +196 -26
- package/src/keyorder.ts +547 -0
- package/src/mesh.ts +54 -17
- package/src/rig.ts +119 -9
- package/src/types.ts +38 -18
- package/src/validate.ts +129 -12
package/docs/AUTHORING.md
CHANGED
|
@@ -328,9 +328,12 @@ when drawing through the pack's own texels is the point.
|
|
|
328
328
|
([#762](https://github.com/firejune/rigc/issues/762)). A depth sheet or a soft
|
|
329
329
|
mask is read in the drawing's pixels on a `scale:` page as on loose parts — each
|
|
330
330
|
vertex's texel position over the stated scale — and a mesh fit's overshoot is
|
|
331
|
-
printed in them, with the texel it was measured on named beside it.
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
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
|
|
334
|
+
texels there are, and the two distances are applied on them as `value × scale`
|
|
335
|
+
texels — the scale the header states, never a measured one (below). What stays
|
|
336
|
+
in texels is only what is counted off them: a contour's hole count.
|
|
334
337
|
|
|
335
338
|
🚨 **A page that declares a size it does not have is a different thing, and it is
|
|
336
339
|
refused** ([#715](https://github.com/firejune/rigc/issues/715)). The common shape
|
|
@@ -396,10 +399,8 @@ compile error carrying `A06`'s whole sentence — ratio and repair — on `expla
|
|
|
396
399
|
and on `build` alike, where on `build` it arrives before the gate would have said
|
|
397
400
|
it. Carry out the repair and every figure comes back. ⚠️ They come back measured
|
|
398
401
|
on the **coarser** texels the page really has, so a figure that depends on the
|
|
399
|
-
grid need not equal the one the pack the page was halved from reads
|
|
400
|
-
|
|
401
|
-
full-resolution page traced 15, because a trace runs on the texels there are and
|
|
402
|
-
its `margin` and `tolerance` are applied on them.
|
|
402
|
+
grid need not equal the one the pack the page was halved from reads — and a
|
|
403
|
+
contour's outline is one of them, below.
|
|
403
404
|
|
|
404
405
|
📐 **A fit's overshoot is stated in the drawing's pixels on every page**
|
|
405
406
|
([#762](https://github.com/firejune/rigc/issues/762)). It is a distance, and on a
|
|
@@ -417,12 +418,44 @@ the drawing, so that is the step the figure moves in:
|
|
|
417
418
|
```
|
|
418
419
|
|
|
419
420
|
All three pages read the fan at 16.00px, because its rim lands on whole texels of
|
|
420
|
-
each. A figure that does not is exact only to that step —
|
|
421
|
-
6.00px on the `scale: 0.5` page (3.00 texels)
|
|
422
|
-
different outline measured on a coarser grid,
|
|
423
|
-
so. On a loose part and a page at scale 1 the
|
|
424
|
-
is the one it always was. A `contour`'s hole
|
|
425
|
-
says `texel(s)` on such a page.
|
|
421
|
+
each. A figure that does not is exact only to that step — a contour at
|
|
422
|
+
`tolerance: 3`, `margin: 4` reads 6.00px on the `scale: 0.5` page (3.00 texels)
|
|
423
|
+
against the full page's 5.39px, a different outline measured on a coarser grid,
|
|
424
|
+
and the line's clause is what says so. On a loose part and a page at scale 1 the
|
|
425
|
+
texels are the drawing, and the line is the one it always was. A `contour`'s hole
|
|
426
|
+
count is a count of those cells and says `texel(s)` on such a page.
|
|
427
|
+
|
|
428
|
+
📐 **A `contour`'s `margin` and `tolerance` are the drawing's pixels on every
|
|
429
|
+
page** ([#779](https://github.com/firejune/rigc/issues/779)). They are your
|
|
430
|
+
statement, in the unit every other size in the spec is in, so on a `scale:` page
|
|
431
|
+
the trace applies them as `margin × scale` and `tolerance × scale` texels, and
|
|
432
|
+
`maxVertices` is judged on the outline that asks for. They used to be applied in
|
|
433
|
+
texels unconverted, which asked each page a different question. Measured on
|
|
434
|
+
`halvedMeshPacks`, one spec — `tolerance: 1.5`, `margin: 2`, `maxVertices: 48`:
|
|
435
|
+
|
|
436
|
+
| page | applied in texels (before) | applied as the drawing's pixels (now) |
|
|
437
|
+
| --- | --- | --- |
|
|
438
|
+
| declared size | 15 vertices | **15** vertices, the same mesh |
|
|
439
|
+
| `scale: 2` | refused: `simplified to 66 vertices at tolerance 1.5, past the 48` | **15** vertices, the declared outline to 0.00px |
|
|
440
|
+
| `scale: 0.5` | 11 vertices, 4.20px from the declared outline | refused: the outline crosses itself after a margin of 2px, applied as 1 texel |
|
|
441
|
+
|
|
442
|
+
A **finer** page resolves everything the declared one does, so it traces the
|
|
443
|
+
declared outline, vertex for vertex. A **coarser** one holds less: its silhouette
|
|
444
|
+
is a staircase of texels 2px of the drawing apart, and a tolerance under one texel
|
|
445
|
+
keeps the steps — which is what the declared page does too at a tolerance under
|
|
446
|
+
one of *its* pixels (at `tolerance: 0.75` it keeps 66 vertices, and given the
|
|
447
|
+
budget for them it is refused by the same crossing). So
|
|
448
|
+
on a coarser page, ask for a tolerance of at least a texel, or supply the loose
|
|
449
|
+
art; the spec above at twice its figures traces 11 vertices there against the
|
|
450
|
+
declared page's 10, 3.69px apart. Every refusal a trace raises on a `scale:` page
|
|
451
|
+
names the figure the spec states, the texels it was applied as and the scale:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# rigc compile error: skin "default" slot "blob" attachment "blob": the outline crosses itself: edge 8 meets edge 10
|
|
455
|
+
# after a margin of 2px (the drawing's pixels — applied as 1 texel(s) of this page, whose scale: 0.5 makes a texel
|
|
456
|
+
# 2.00px of the drawing) was pushed out of a silhouette narrower than that — lower the margin, or the art has a
|
|
457
|
+
# neck too thin to mesh
|
|
458
|
+
```
|
|
426
459
|
|
|
427
460
|
🚨 **A page that is not a PNG is refused by name, before anything is compiled
|
|
428
461
|
against it** ([#732](https://github.com/firejune/rigc/issues/732)). rigc reads PNG
|
|
@@ -904,11 +937,27 @@ one A09 does compare.
|
|
|
904
937
|
|
|
905
938
|
## 2. The rules that decide what lands in the file
|
|
906
939
|
|
|
907
|
-
**R1 — A field is emitted
|
|
908
|
-
the
|
|
909
|
-
|
|
910
|
-
the
|
|
911
|
-
|
|
940
|
+
**R1 — A field is emitted when you declare it, and left out where the parser would
|
|
941
|
+
read the same value without it.** Omit a field and Spine's default stands; write it
|
|
942
|
+
and it reaches the file — unless what you wrote **is** that default, in which case
|
|
943
|
+
the emitter leaves it out, the way the editor's own exporter does (issue
|
|
944
|
+
[#716](https://github.com/firejune/rigc/issues/716)). Writing `x: 0` is still
|
|
945
|
+
legitimate, and your spec still says it: the spec is the record of what you wrote,
|
|
946
|
+
and the file is what the runtime reads. The two cannot disagree about a value,
|
|
947
|
+
because a key is left out only where the 4.3 parser loads the same `SkeletonData`
|
|
948
|
+
without it — exact equality with the float the file would hold, so `x: 1e-45` is
|
|
949
|
+
written — and the selftest loads every build both ways to hold that (§10.6c).
|
|
950
|
+
|
|
951
|
+
⚠️ This rule said *"not 'when it differs from the default'"* until #716, on two
|
|
952
|
+
grounds: that a rig may need to say `x: 0` out loud, and that deciding emission from
|
|
953
|
+
the value would make the file depend on arithmetic. Neither survived being measured.
|
|
954
|
+
What reads the file is the 4.3 parser, which reads the absent key as the same number,
|
|
955
|
+
and the editor, whose own export leaves the same key out — so there is nobody to say
|
|
956
|
+
it out loud *to*; and the decision is an exact comparison with the parser's fallback,
|
|
957
|
+
which is not arithmetic in any sense a reader has to redo. What the
|
|
958
|
+
old rule cost was concrete: a rebuild of an editor export restated **2,338** keys the
|
|
959
|
+
export leaves to the parser, so `ingest → build` could never give an editor's file
|
|
960
|
+
back.
|
|
912
961
|
|
|
913
962
|
**R2 — The compiler never invents a value.** No defaults guessed from the art, no
|
|
914
963
|
re-measured plates, no reasonable fallbacks. A missing number is a `CompileError`
|
|
@@ -996,6 +1045,15 @@ page:
|
|
|
996
1045
|
| leaves a remaining **tie in the order your file declares it** | `turn` before `Turn`; `Mango` before `mango` — each given in that order and returned in it |
|
|
997
1046
|
| writes **leaves then folders at the root**, and **sub-folders then leaves inside a folder** | `h` before `f/sub1/deep/q`; `f/sub1/x` before `f/leafA` |
|
|
998
1047
|
|
|
1048
|
+
🔸 **One clause of the rule is not from those files.** Before comparing, the
|
|
1049
|
+
comparator reads U+3000 IDEOGRAPHIC SPACE as a space and the full-width digits
|
|
1050
|
+
U+FF10–U+FF19 as `0`–`9`. No probe carried either; the fold was measured on a
|
|
1051
|
+
production skin's slot keys ([#791](https://github.com/firejune/rigc/issues/791),
|
|
1052
|
+
§10.6b says what was measured and why it is those two classes and not NFKC), and
|
|
1053
|
+
it applies here because R10, R11 and a skin's slot keys are one comparator. So
|
|
1054
|
+
`shot2` sorts before `shot10`, and a pair an ideographic space decides is
|
|
1055
|
+
ordered rather than refused as `separator`.
|
|
1056
|
+
|
|
999
1057
|
⭐ **A tie is not an ambiguity, and that is what retired most of this rule's
|
|
1000
1058
|
refusals.** Two names the comparator cannot separate come back in the order the
|
|
1001
1059
|
file gave them, so the order rigc emits for such a pair is **your own declaration
|
|
@@ -1082,6 +1140,7 @@ is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
|
1082
1140
|
| `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
|
|
1083
1141
|
| `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
|
|
1084
1142
|
| `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 (issue #370) |
|
|
1143
|
+
| `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 ([#716](https://github.com/firejune/rigc/issues/716)). Anything but a string or `null` is a compile error naming the value |
|
|
1085
1144
|
|
|
1086
1145
|
`spine` and `hash` are not yours to write: rigc emits its own version label
|
|
1087
1146
|
(`A16` re-checks it is on the 4.3 line) and inventing a hash would claim an export
|
|
@@ -1769,9 +1828,9 @@ included, because without it the generator is refused (issue #274). The
|
|
|
1769
1828
|
|
|
1770
1829
|
| Field | Meaning |
|
|
1771
1830
|
| --- | --- |
|
|
1772
|
-
| `tolerance` | **required.** Douglas-Peucker tolerance, in
|
|
1773
|
-
| `margin` | how far the outline is pushed out past the traced silhouette, in pixels. Default `1` |
|
|
1774
|
-
| `maxVertices` | refuse rather than emit more outline vertices than this. Default `64` |
|
|
1831
|
+
| `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, [#779](https://github.com/firejune/rigc/issues/779)). Bigger spends fewer vertices and cuts more corners |
|
|
1832
|
+
| `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` |
|
|
1833
|
+
| `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` |
|
|
1775
1834
|
| `alpha` | the alpha at or above which a pixel counts as art, `1`..`255`. Default `1` — any pixel that is not fully transparent |
|
|
1776
1835
|
|
|
1777
1836
|
The numbers above are invented, and the pair that matters is `tolerance` and
|
|
@@ -1821,6 +1880,7 @@ mesh rather than a rim prefix, and `A28_RIBBON_ROWS_SHARE_WEIGHTS` **SKIPs** wit
|
|
|
1821
1880
|
| a neck narrower than `margin` | `the outline crosses itself: edge 0 meets edge 3 after a margin of 3px was pushed out of a silhouette narrower than that` |
|
|
1822
1881
|
| more outline than `maxVertices` | `the silhouette simplified to 15 vertices at tolerance 1.5, past the 4 this mesh allows` — refused, never silently decimated |
|
|
1823
1882
|
| a `margin` too small for the `tolerance` | `the mesh covers 91.81% of the art (2936 of 3198 px), under the 99.5% a contour mesh guarantees` |
|
|
1883
|
+
| any of the three above, on a packed page that declares a `scale:` | `…after a margin of 2px (the drawing's pixels — applied as 1 texel(s) of this page, whose scale: 0.5 makes a texel 2.00px of the drawing) was pushed out…` — the spec's figure, the texels the trace ran it at, and the scale that converted it; a count of the part's cells says `texels` there. A tolerance under one texel of a coarser page keeps its stair steps, as one under a pixel does on the declared page (§0.2) |
|
|
1824
1884
|
|
|
1825
1885
|
That last one is the guarantee: **the emitted triangles cover at least 99.5% of
|
|
1826
1886
|
the art**, measured by rasterising them back over the mask, and a build that would
|
|
@@ -2526,6 +2586,13 @@ carrying here:
|
|
|
2526
2586
|
Every constraint may also carry `skin: true`, which makes it run only under the skin
|
|
2527
2587
|
that lists it — see §3.4.1, and note that the flag alone does nothing.
|
|
2528
2588
|
|
|
2589
|
+
🎛️ **An ik or transform whose mix a game sets from code has no field here**, and
|
|
2590
|
+
that is deliberate. Every key on a constraint object is a Spine field the emitter
|
|
2591
|
+
writes; a statement to the gate about who turns the dial is what the artifact cannot
|
|
2592
|
+
say about itself, so it lives in `invariants.consumerDrivenMix` (§3.7), beside
|
|
2593
|
+
`deformMayFold` — the one other exemption from a named rule, which names its subject
|
|
2594
|
+
the same way ([#784](https://github.com/firejune/rigc/issues/784)).
|
|
2595
|
+
|
|
2529
2596
|
#### 3.5.1 `path` — bones that travel along a curve
|
|
2530
2597
|
|
|
2531
2598
|
**When you need one:** anything that moves *along* something rather than around a
|
|
@@ -3075,7 +3142,8 @@ is not an array. Every field is optional and each is the payload a firing
|
|
|
3075
3142
|
|
|
3076
3143
|
Optional with one exception, and only meaningful for rigc's own formations:
|
|
3077
3144
|
`meshSlots` and `meshTriangles` (the two halves of the mesh budget `A13` measures
|
|
3078
|
-
against), `axisBone`, `massBone`, `detached`, `deformMayFold`, `editorRoundTrip
|
|
3145
|
+
against), `axisBone`, `massBone`, `detached`, `deformMayFold`, `editorRoundTrip`,
|
|
3146
|
+
`consumerDrivenMix`.
|
|
3079
3147
|
Nothing in skeleton
|
|
3080
3148
|
JSON records that a
|
|
3081
3149
|
bone carries a cut's axis or that a parentage is forbidden, so the rig spec says it
|
|
@@ -3083,7 +3151,10 @@ and the validator's archetype assertions read it. **An assertion whose field is
|
|
|
3083
3151
|
absent reports SKIP, never a pass.** If you are reproducing a foreign skeleton,
|
|
3084
3152
|
leave this out entirely and run `--profile spine` — and expect `PROF` rather than
|
|
3085
3153
|
that SKIP, because the profile excludes an archetype assertion before its body
|
|
3086
|
-
could notice the missing field (§5.2).
|
|
3154
|
+
could notice the missing field (§5.2). The one field `ingest` writes is
|
|
3155
|
+
`consumerDrivenMix`, and only for a constraint the file rests muted and never keys
|
|
3156
|
+
up, each with a `CONSUMER_DRIVEN_MIX` finding ([INGEST.md](INGEST.md) §2.0) — see
|
|
3157
|
+
the last block of this section.
|
|
3087
3158
|
|
|
3088
3159
|
🚨 **The exception: `meshSlots` is required by a rig that invokes a mesh
|
|
3089
3160
|
generator** (`ring`, `ribbon`, `contour`, `grid` — §3.4), and it is a **compile-time**
|
|
@@ -3108,8 +3179,9 @@ loads and still animates and merely lies — something released into the world t
|
|
|
3108
3179
|
must not ride the part that released it. [RIGGING.md](RIGGING.md) §10.3 has a
|
|
3109
3180
|
worked one.
|
|
3110
3181
|
|
|
3111
|
-
🚨 **`deformMayFold` is the
|
|
3112
|
-
|
|
3182
|
+
🚨 **`deformMayFold` is one of the two fields here that turn a check OFF**
|
|
3183
|
+
(`consumerDrivenMix`, below, is the other), so it is held to a shape that is
|
|
3184
|
+
refused rather than skipped. It is
|
|
3113
3185
|
`[{ "slot": …, "why": … }]`, it exempts that slot from
|
|
3114
3186
|
`A39_DEFORM_KEEPS_TRIANGLE_WINDING`, and three shapes are compile errors: a slot
|
|
3115
3187
|
the rig does not declare, a slot that carries no mesh, and a missing or blank
|
|
@@ -3166,6 +3238,29 @@ drawn and are gated (§4.11.3); this used to be a rule you had to follow and is
|
|
|
3166
3238
|
now one the gate keeps
|
|
3167
3239
|
([#403](https://github.com/firejune/rigc/issues/403)).
|
|
3168
3240
|
|
|
3241
|
+
🎛️ **`consumerDrivenMix` names the ik and transform constraints whose mix the
|
|
3242
|
+
CONSUMER sets** — from code, at runtime — rather than any animation in this file
|
|
3243
|
+
([#784](https://github.com/firejune/rigc/issues/784)). It is
|
|
3244
|
+
`[{ "constraint": …, "type": "ik" | "transform", "why": … }]`, and it exempts that
|
|
3245
|
+
constraint from `A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT` or
|
|
3246
|
+
`A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` (§4.12). A constraint resting muted
|
|
3247
|
+
that nothing keys up is either a leftover or a dial a game turns, the two export as
|
|
3248
|
+
the same bytes, and the gate refuses the shape because nothing *in the file* ever
|
|
3249
|
+
moves it — this is the statement that something outside it does, the rig-side
|
|
3250
|
+
spelling of `gallery/look`'s rule that a face angle is a value rather than a time.
|
|
3251
|
+
|
|
3252
|
+
- `type` is required because a constraint's name is unique **per kind** — an ik and
|
|
3253
|
+
a transform may share one — so a name alone could exempt two constraints.
|
|
3254
|
+
- Five shapes are compile errors, each by name: a constraint the rig does not
|
|
3255
|
+
declare under that `type`, a `type` other than `ik` or `transform` (a physics,
|
|
3256
|
+
path or slider constraint has its own muted-at-rest rule — `A23`, `A36`, `A37` —
|
|
3257
|
+
and none of them reads this declaration, so the entry would exempt nothing), the
|
|
3258
|
+
same constraint twice, a missing or blank `why`, and a key the entry does not have.
|
|
3259
|
+
- ⛔ A declared constraint the file **also** switches on — resting live, or keyed
|
|
3260
|
+
above 0 — is refused at the gate: the declaration exempts nothing there.
|
|
3261
|
+
- What it buys is a **SKIP by name, never a pass** (§4.12 has the line). The
|
|
3262
|
+
declaration is a statement to the gate and changes no emitted byte.
|
|
3263
|
+
|
|
3169
3264
|
---
|
|
3170
3265
|
|
|
3171
3266
|
## 4. The motion spec, field by field
|
|
@@ -4059,8 +4154,11 @@ here was measured off a real rig. Copy the shape, not the values.
|
|
|
4059
4154
|
rebuilds with `bendPositive: false` on every key if the flag is left silent,
|
|
4060
4155
|
and with `true` — what the source plays — when `ingest` writes it out. So
|
|
4061
4156
|
`ingest` restates every field any key of a track names, at the parser's default
|
|
4062
|
-
where the source omits one
|
|
4063
|
-
|
|
4157
|
+
where the source omits one — in the **spec**. The emitter then leaves each such
|
|
4158
|
+
value out of the file again wherever it is the one the parser reads without it
|
|
4159
|
+
(§10.6c), so the rebuild is the source's own text, and `CONSTRAINT_KEY_RESTATED`
|
|
4160
|
+
is printed only for a value the file would still carry. On rigc's own output the
|
|
4161
|
+
two agree and the round trip is byte-identical.
|
|
4064
4162
|
- `mix` outside `0..1` is a compile error: `IkConstraintPose.mix` is documented as
|
|
4065
4163
|
a percentage. A **transform** mix is documented *unbounded*, which is why §4.10
|
|
4066
4164
|
has no such rule — the asymmetry is the runtime's, not ours.
|
|
@@ -4082,6 +4180,13 @@ rigc refuses four things here:
|
|
|
4082
4180
|
| `softness` on the first key only | `key 0 names "softness" and key 1 (t=…) does not … it would snap to 0` |
|
|
4083
4181
|
| `mix: 1.5` | `mix is 1.5, outside 0..1 — the runtime documents it as a percentage 0-1` |
|
|
4084
4182
|
|
|
4183
|
+
🎛️ **A constraint this section never keys** — resting at `mix` 0 with no `ik` track
|
|
4184
|
+
in any animation lifting it — is refused by `A47` with three doors: rest it above 0,
|
|
4185
|
+
key it here, or declare that the consumer drives its mix in the rig spec's
|
|
4186
|
+
`invariants.consumerDrivenMix` (§3.7), for an ik a game turns on from code. The
|
|
4187
|
+
third is a statement about the object, not a way past the gate: what it buys is a
|
|
4188
|
+
SKIP by name (§4.12).
|
|
4189
|
+
|
|
4085
4190
|
The type check matters because the parser resolves a timeline's target by name
|
|
4086
4191
|
**and** type: `findConstraint(name, IkConstraintData)` misses a transform
|
|
4087
4192
|
constraint of the same name, returns null, and `readAnimation` throws — in the
|
|
@@ -4141,7 +4246,10 @@ exactly what a mix that was 0 at setup needs said.
|
|
|
4141
4246
|
omitted mix as 1, so a key of `mixRotate: 0` alone on a rotate-only constraint
|
|
4142
4247
|
carries five mixes of 1 that nothing reads. `A48` judges only the mixes of the
|
|
4143
4248
|
properties the constraint drives (§4.12).
|
|
4144
|
-
- The refusals are §4.9's, with `transform` in place of `ik
|
|
4249
|
+
- The refusals are §4.9's, with `transform` in place of `ik` — and so is the third
|
|
4250
|
+
door: a transform resting muted on every mix it reads that no key here lifts is
|
|
4251
|
+
refused by `A48` with *rest it above 0, key it, or declare that the consumer drives
|
|
4252
|
+
its mix* in `invariants.consumerDrivenMix` (§3.7, §4.12).
|
|
4145
4253
|
|
|
4146
4254
|
### 4.11 `deform` — moving an attachment's vertices
|
|
4147
4255
|
|
|
@@ -5049,8 +5157,36 @@ rescue and a 0-only timeline is not, with two differences that are the runtime's
|
|
|
5049
5157
|
|
|
5050
5158
|
`ik constraint "reach" has mix 0 at setup and none of the 1 animation keys its mix
|
|
5051
5159
|
above 0; update() returns on mix 0, so "upper" never reaches for "goal" — rest it
|
|
5052
|
-
above 0, or key its mix above 0 in an animation
|
|
5053
|
-
|
|
5160
|
+
above 0, or key its mix above 0 in an animation, or declare that the consumer drives
|
|
5161
|
+
its mix, in the rig spec as invariants.consumerDrivenMix: [{ "constraint": "reach",
|
|
5162
|
+
"type": "ik", "why": … }]`. A rig resting at 0 and keyed up by the animation that
|
|
5163
|
+
needs it — spineboy's aim — is refused by neither.
|
|
5164
|
+
|
|
5165
|
+
🎛️ **The third door is for a dial the file cannot show turning**
|
|
5166
|
+
([#784](https://github.com/firejune/rigc/issues/784)). A constraint resting muted
|
|
5167
|
+
that nothing keys up is either a leftover or a mix a game sets from code, and the two
|
|
5168
|
+
export as the same bytes — a production skeleton's rebuild was refused for exactly
|
|
5169
|
+
that, over an ik its game switches on at runtime. `invariants.consumerDrivenMix`
|
|
5170
|
+
(§3.7) is the statement the file cannot make, and what it buys is a **SKIP by name,
|
|
5171
|
+
never a pass**: the file still shows nothing moving the constraint.
|
|
5172
|
+
|
|
5173
|
+
```
|
|
5174
|
+
SKIP A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT: 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 — "reach" (why: a game turns it on from code): it rests muted and none of the 1 animation keys its mix above 0
|
|
5175
|
+
```
|
|
5176
|
+
|
|
5177
|
+
- **Beside a live constraint of the same kind** the live one is still measured, so
|
|
5178
|
+
the rule PASSes and the build's stats line names the declared one
|
|
5179
|
+
(`ikConsumerDriven=reach`, `transformConsumerDriven=…`) — `A39`'s shape for
|
|
5180
|
+
`deformMayFold`. A SKIP there would print "nothing measured" over a rule that
|
|
5181
|
+
measured one.
|
|
5182
|
+
- ⛔ **A declared constraint the file also switches on is refused** — resting live,
|
|
5183
|
+
or keyed above 0 by any animation — because the declaration exempts nothing
|
|
5184
|
+
there: the gate passes it without one. [measured] an ik keyed at mix 0.5 with code
|
|
5185
|
+
writing 1: written before `state.apply` it is overwritten (applied 0.5), written
|
|
5186
|
+
after it wins (1.0). Which of the two authors holds a frame is the order of the
|
|
5187
|
+
consumer's own loop, which is the scene's, not the object's.
|
|
5188
|
+
- `validate <dir>` has no rig spec and so no declaration: a muted constraint there
|
|
5189
|
+
is refused with all three doors.
|
|
5054
5190
|
|
|
5055
5191
|
### 4.13 `sequence` — which frame of a numbered series shows
|
|
5056
5192
|
|
|
@@ -5499,7 +5635,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5499
5635
|
| `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`) |
|
|
5500
5636
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) — as it is for `A06`, `A19` and `A27`; see `A07` ([#608](https://github.com/firejune/rigc/issues/608)) |
|
|
5501
5637
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
5502
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part
|
|
5638
|
+
| `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** ([#777](https://github.com/firejune/rigc/issues/777)); 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. Until #777 the loose route stopped at the header, so the same fully opaque RGBA part passed a loose build and was refused by `--pack` of the same rig. 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 ([#770](https://github.com/firejune/rigc/issues/770)): 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 ([#266](https://github.com/firejune/rigc/issues/266)). ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page** ([#705](https://github.com/firejune/rigc/issues/705)): a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. ⚠️ **A page whose IMAGE is not the size the atlas declares for it is the same non-measurement for every region on it** ([#715](https://github.com/firejune/rigc/issues/715)), and #705's clause does not cover that case: a page rescaled after packing leaves most rectangles partly on it, at coordinates that address a different part of the picture, so the scan came back with a confident verdict over texels nobody had located — on a two-region pack at a uniform 0.5 an opaque part's failure **disappeared**, the scan having found a transparent texel 32 texels away from it. The row names the page's two sizes and points at `A06`, which judges the page grid and prints the header that repairs it (§0.2). It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. Before #705 the walk was silent about its own reach, so a part nobody could read printed *opaque in every one of its 12x8 texels* over zero of them, which is a refusal pointing at the wrong file: the art it names may be transparent, and the repair is the rectangle in `A06`'s row above. ⚠️ **A page file that cannot be read as PNG at all is the same non-measurement for every part on it** ([#732](https://github.com/firejune/rigc/issues/732)): one row per page naming its parts and pointing at `A06`, which names what the file is — where it used to print `threw: cannot decode PNG …: unexpected end of file`, an inflate error about a file that was never a PNG. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5503
5639
|
| `A20_MESH_WEIGHTS_COHERENT` | both ◑ | a weighted vertex with no bone, a negative weight, a bone index out of range, or weights that do not sum to 1. Under `spine-html` also: an unweighted mesh, a binding at weight 0, or **a bone the mesh declares that no vertex binds** — `mesh "x" declares bone "grip_b" and none of its 25 vertices binds it; the weights reference "box", "grip_a"`. Those three are one sentence about rigc's own generators: the bone set a generated mesh declares is the bone set its weights reference, so a `controls` or `chain` name that moves nothing is a defect where a foreign mesh's is not ([#684](https://github.com/firejune/rigc/issues/684)). Fix the rig spec's `controls`/`chain`, or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5504
5640
|
| `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 |
|
|
5505
5641
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
@@ -5527,8 +5663,8 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5527
5663
|
| `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` | both | a **linked mesh** (§3.4) — `type: "linkedmesh"`, or a `type: "mesh"` carrying `source` — that also states `uvs`, `triangles`, `vertices`, `hull` or `edges`. The parser returns from the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so those keys are read by **nothing at all** and `setSourceMesh` fills the attachment with the source's arrays instead: the file says one mesh and every runtime draws another, in silence. The detail names the attachment by skin, slot and placeholder, every key it states, the `source` and where the parser looks for it — the two defaults spelled out, because an omitted `skin` is the **default** skin rather than the one the link is written in — and the shape the keys describe beside the shape the attachment loaded. ⚠️ **`width`/`height` are not part of this.** `setSourceMesh` overwrites both with the source's, so they are as dead at runtime — but the parser reads them (`:569-570`), the format carries them on a link and rigc emits them, so refusing them would refuse every link rigc writes (§3.4). `compile.ts` refuses the same shape outright in a rig rigc builds (§5.1); this is that fact held against a skeleton it did not write, and `ingest` reports it as `ATTACHMENT_LINK_GEOMETRY` ([INGEST §2.0](INGEST.md)). **SKIP** when no attachment in the skeleton takes its geometry from another — which is almost every skeleton, so a pass here means a link was read ([#710](https://github.com/firejune/rigc/issues/710)) |
|
|
5528
5664
|
| `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 (a correct file was refused that way until [#771](https://github.com/firejune/rigc/issues/771)), and the posed r g b (for `rgb`, against the hex, to half a quantisation step) or alpha (for `alpha`, against `value`, whose absence the parser reads as 0) is compared — a colour that is not six hex digits loads as NaN, and a key whose time another key repeats is read by nothing. ⚠️ An `rgb` alone written as an `rgba` holding the setup alpha is **not** caught and cannot be from the file: it is a correct `rgba`, and the difference shows only under another track that moves the alpha. The loaded timeline class and the channels a separable timeline leaves alone are measured in the selftest (`S83`–`S85`) rather than here, because against the linked parser neither can come out wrong. The channel table is `SLOT_COLOR_CHANNELS` in `src/timelines.ts`, shared with the compiler's refusal and held to the runtime's own property ids (`S89`). **SKIP** when no animation keys an `rgb` or `alpha` — there is then no separable slot colour to read back |
|
|
5529
5665
|
| `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` | both | a **numbered series** (§3.4.3, §4.13) that the runtime does not show as the file states it. Every shape below loads without a word, measured on spine-core 4.3.13 ([#729](https://github.com/firejune/rigc/issues/729)). **The block**: a `sequence` with no `count` (`readSequence` reads 0, and the attachment holds no region) or a `setup` at or past `count` (`Sequence.resolveIndex` clamps it to the last frame). **The keys**: a `mode` outside the seven — `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse` — loads as `hold`; an `index` that is fractional (`index << 4` truncates it) or past the end (clamped); an advancing mode at an effective delay of 0 (the parser carries a key's `delay` from the key before; `(time - keyTime) / 0` is Infinity and `Infinity \| 0` is 0, so it never advances); a timeline on an attachment that carries no block (the parser gives every region a one-region series, so every mode shows it). **The pose**: every key is stepped to mid-frame sample times — enough to wrap every mode, and a `hold` key to its own time as the runtime stores it, a 32-bit float ([#771](https://github.com/firejune/rigc/issues/771)) — 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 |
|
|
5530
|
-
| `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, and before [#765](https://github.com/firejune/rigc/issues/765) it passed. 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`. **SKIP** when the skeleton declares no ik constraint |
|
|
5531
|
-
| `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 ([#765](https://github.com/firejune/rigc/issues/765)). 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`. **SKIP** when the skeleton declares no transform constraint |
|
|
5666
|
+
| `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, and before [#765](https://github.com/firejune/rigc/issues/765) it passed. 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` ([#784](https://github.com/firejune/rigc/issues/784)). **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`) |
|
|
5667
|
+
| `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 ([#765](https://github.com/firejune/rigc/issues/765)). 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 ([#784](https://github.com/firejune/rigc/issues/784)). **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`) |
|
|
5532
5668
|
|
|
5533
5669
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
5534
5670
|
clauses are gated by profile.
|
|
@@ -7167,8 +7303,9 @@ about your bone tree rather than about a hole in your figure.
|
|
|
7167
7303
|
|
|
7168
7304
|
Every reference in this repository was made in the Spine editor by a person, and a
|
|
7169
7305
|
rig authored here is measured against one. rigc's own defaults are deliberately
|
|
7170
|
-
*absent* rather than opinionated (R1: a field
|
|
7171
|
-
|
|
7306
|
+
*absent* rather than opinionated (R1: a field reaches the file when you declare it,
|
|
7307
|
+
and nothing you did not declare is supplied), so nothing in the compiler will push you
|
|
7308
|
+
toward the shape an editor rig has.
|
|
7172
7309
|
This section is that push, and it comes from **Spine's public documentation only** —
|
|
7173
7310
|
what the editor does when nobody tells it otherwise, and what its user guide
|
|
7174
7311
|
recommends.
|
|
@@ -7274,10 +7411,16 @@ population is every object the format keys by a name and that carries more than
|
|
|
7274
7411
|
one key, deform blocks counted at each of their three levels;
|
|
7275
7412
|
[`src/compile.ts`](../src/compile.ts) states it in full beside
|
|
7276
7413
|
`editorAnimationOrder`, so the count can be re-taken rather than trusted.)
|
|
7277
|
-
⇒ in rigc:
|
|
7414
|
+
⇒ in rigc: `animations` is emitted sorted (R10), because it is the one
|
|
7278
7415
|
object measured here whose ORDER is also an index space — every reference into
|
|
7279
7416
|
the re-sorted *other* objects is by name on both sides, so nothing moves when
|
|
7280
|
-
they are re-keyed.
|
|
7417
|
+
they are re-keyed. ⚠️ Since [#716](https://github.com/firejune/rigc/issues/716)
|
|
7418
|
+
a skin's `attachments` slot keys are emitted sorted too, for the **text** and
|
|
7419
|
+
for no reference: a rebuild of an editor export is the export only if it is the
|
|
7420
|
+
same text (§10.6b), and 11 of the twelve under `examples/` keyed a skin in draw
|
|
7421
|
+
order where the export sorts it. Nothing is refused on that map — a pair the
|
|
7422
|
+
comparator leaves open, or a slot name with a `/` in it, keeps the whole map in
|
|
7423
|
+
rigc's order instead. rigc emits **that comparator's own order**, which
|
|
7281
7424
|
[#728](https://github.com/firejune/rigc/issues/728) then measured in full off five
|
|
7282
7425
|
stored round trips (R10) rather than quantifying over a family. Sorting the
|
|
7283
7426
|
105 collections that way reproduces **105 of 105**, the three codepoint cannot
|
|
@@ -7808,11 +7951,20 @@ either.
|
|
|
7808
7951
|
*"assume … if omitted"* default — the same fact R5 states from the parser's side:
|
|
7809
7952
|
omit them in raw JSON and every UV collapses, in silence. Name an `image`.
|
|
7810
7953
|
|
|
7811
|
-
|
|
7812
|
-
to their default, which is why the format page is
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7954
|
+
✅ **You need not imitate the exporter's omissions — the emitter does it for you.**
|
|
7955
|
+
Spine's exporter drops fields equal to their default, which is why the format page is
|
|
7956
|
+
a long list of *"assume 0 if omitted"*, and since
|
|
7957
|
+
[#716](https://github.com/firejune/rigc/issues/716) rigc's emitter drops the same
|
|
7958
|
+
ones (R1, §2; the table is §10.6c). So an author may still state a default — `x: 0`
|
|
7959
|
+
in a rig spec is legitimate and stays in the spec — and the file leaves it out.
|
|
7960
|
+
⚠️ This paragraph said *"do not imitate the exporter's omissions … rigc deliberately
|
|
7961
|
+
does the opposite"* until then. The advice was re-derived rather than kept: **the
|
|
7962
|
+
round trip is the reason.** A rebuild of an editor export that writes back every
|
|
7963
|
+
default the export left out is a different file from the export in 2,338 places over
|
|
7964
|
+
the twelve under `examples/`, and one that leaves them out is the export's own text
|
|
7965
|
+
apart from the header's `hash` and `spine` (`IG83`, and [INGEST §2.3](INGEST.md)).
|
|
7966
|
+
The habit worth carrying over is still not *omit defaults* — it is *declare only what
|
|
7967
|
+
the shot needs*; what changed is that stating a default costs nothing in the file.
|
|
7816
7968
|
|
|
7817
7969
|
### 10.6 What a round trip gives back
|
|
7818
7970
|
|
|
@@ -7834,7 +7986,12 @@ comes back `48.0`) and **omitted defaults** — the export drops any field equal
|
|
|
7834
7986
|
its parser default, so the header loses `x: 0` and `y: 0`, a bone loses `x: 0`, and
|
|
7835
7987
|
a key at t=0 loses its `"time": 0`. Every name-keyed object is also re-sorted, per
|
|
7836
7988
|
§10.1. None of those is a loss of information, and each is worth knowing before
|
|
7837
|
-
you read a `diff`.
|
|
7989
|
+
you read a `diff`. 🔁 Since [#716](https://github.com/firejune/rigc/issues/716) the
|
|
7990
|
+
bone's `x: 0` and the key's `"time": 0` are left out by rigc as well (§10.6c); the
|
|
7991
|
+
header's origin is not, because the 4.3 JSON reader has no default for it
|
|
7992
|
+
(`skeletonData.x = skeletonMap.x`, `SkeletonJson.js:70`), so an absent origin loads
|
|
7993
|
+
as `undefined` where a written one loads as `0` — writing it is exact for both
|
|
7994
|
+
readers, and leaving it out would move a loaded value.
|
|
7838
7995
|
|
|
7839
7996
|
⚠️ Read every line below as *this construct survived*, never as *this construct is
|
|
7840
7997
|
recommended*. §10.1–§10.5 are the recommendations; this subsection is only the
|
|
@@ -7946,6 +8103,223 @@ identical** on an emitted array all three of whose numbers moved. ⇒ Read a
|
|
|
7946
8103
|
`lengths` disagreement as *certainly wrong data, and visible only under
|
|
7947
8104
|
`positionMode: fixed`*.
|
|
7948
8105
|
|
|
8106
|
+
### 10.6b The order it writes keys in
|
|
8107
|
+
|
|
8108
|
+
🔬 **The editor writes every object's keys in one fixed order per kind of
|
|
8109
|
+
object**, and since [#716](https://github.com/firejune/rigc/issues/716) rigc
|
|
8110
|
+
writes the same. Read off the twelve exports under `examples/`: for each kind,
|
|
8111
|
+
every object's key sequence is a subsequence of one order, no two exports
|
|
8112
|
+
contradict each other on any kind, and the table below is that order. It lives
|
|
8113
|
+
in one place, [`src/keyorder.ts`](../src/keyorder.ts)'s `EDITOR_KEY_ORDER`, and
|
|
8114
|
+
the compiler applies it once to the finished skeleton — `CUR83` holds this table
|
|
8115
|
+
to that one, and `IG77` holds that one to the exports on every run with a
|
|
8116
|
+
corpus.
|
|
8117
|
+
|
|
8118
|
+
| Kind | Keys, in the order the editor writes them |
|
|
8119
|
+
| --- | --- |
|
|
8120
|
+
| `top level` | `skeleton`, `bones`, `slots`, `constraints`, `skins`, `events`, `animations` |
|
|
8121
|
+
| `header` | `hash`, `spine`, `x`, `y`, `width`, `height`, `images`, `audio` |
|
|
8122
|
+
| `bone` | `name`, `parent`, `length`, `rotation`, `x`, `y`, `scaleX`, `scaleY`, `inherit`, `color`, `icon` |
|
|
8123
|
+
| `slot` | `name`, `bone`, `color`, `attachment`, `blend` |
|
|
8124
|
+
| `ik constraint` | `type`, `name`, `target`, `bones`, `mix`, `bendPositive` |
|
|
8125
|
+
| `transform constraint` | `type`, `name`, `source`, `bones`, `rotation`, `x`, `y`, `properties`, `localSource`, `localTarget`, `mixRotate`, `mixX`, `mixY`, `mixScaleX`, `mixShearY` |
|
|
8126
|
+
| `physics constraint` | `type`, `name`, `bone`, `x`, `y`, `rotate`, `damping` |
|
|
8127
|
+
| `skin` | `name`, `attachments` |
|
|
8128
|
+
| `region attachment` | `x`, `y`, `scaleX`, `scaleY`, `rotation`, `width`, `height` |
|
|
8129
|
+
| `mesh attachment` | `type`, `uvs`, `triangles`, `vertices`, `hull`, `edges`, `width`, `height` |
|
|
8130
|
+
| `boundingbox attachment` | `type`, `vertexCount`, `vertices` |
|
|
8131
|
+
| `clipping attachment` | `type`, `end`, `vertexCount`, `vertices`, `color` |
|
|
8132
|
+
| `animation` | `slots`, `bones`, `ik`, `transform`, `physics`, `attachments`, `drawOrder`, `events` |
|
|
8133
|
+
| `bone rotate key` | `time`, `value`, `curve` |
|
|
8134
|
+
| `bone translate key` | `time`, `x`, `y`, `curve` |
|
|
8135
|
+
| `bone translatex key` | `time`, `value`, `curve` |
|
|
8136
|
+
| `bone translatey key` | `time`, `value`, `curve` |
|
|
8137
|
+
| `bone scale key` | `time`, `x`, `y`, `curve` |
|
|
8138
|
+
| `bone shear key` | `time`, `x`, `y`, `curve` |
|
|
8139
|
+
| `slot attachment key` | `time`, `name` |
|
|
8140
|
+
| `slot rgba key` | `time`, `color`, `curve` |
|
|
8141
|
+
| `ik key` | `time`, `mix`, `softness`, `bendPositive`, `curve` |
|
|
8142
|
+
| `transform key` | `time`, `mixRotate`, `mixX`, `mixY`, `curve` |
|
|
8143
|
+
| `physics damping key` | `time`, `value` |
|
|
8144
|
+
| `physics inertia key` | `time`, `value` |
|
|
8145
|
+
| `physics mass key` | `time`, `value` |
|
|
8146
|
+
| `physics mix key` | `time`, `value`, `curve` |
|
|
8147
|
+
| `physics wind key` | `time`, `value` |
|
|
8148
|
+
| `attachment deform key` | `time`, `offset`, `vertices`, `curve` |
|
|
8149
|
+
| `drawOrder key` | `time`, `offsets` |
|
|
8150
|
+
| `drawOrder offset` | `slot`, `offset` |
|
|
8151
|
+
| `event key` | `time`, `name` |
|
|
8152
|
+
|
|
8153
|
+
⚠️ **Three things about that table are rigc's rather than the editor's, and it
|
|
8154
|
+
says which.**
|
|
8155
|
+
|
|
8156
|
+
- **A pair no export carries together has no measured order**, and the row
|
|
8157
|
+
places it the way rigc already emitted it — a transform constraint's
|
|
8158
|
+
`rotation` before `x`, an ik key's `time` before `mix`, a physics
|
|
8159
|
+
constraint's `x`/`y` before `rotate`. `src/keyorder.ts` lists every such pair.
|
|
8160
|
+
Such a pair did not move, and nothing here says the editor agrees.
|
|
8161
|
+
- **A kind with no row keeps rigc's order whole**: a linked mesh, a path or
|
|
8162
|
+
point attachment, a sequence, the path and slider constraints, an event
|
|
8163
|
+
definition, and the keys of every timeline the twelve do not key with two
|
|
8164
|
+
fields (`scalex`, `rgb`, `alpha`, `rgba2`, `sequence`, the path and slider
|
|
8165
|
+
timelines, …). Unmeasured is not certified.
|
|
8166
|
+
- **A key a row does not list stays where its constructor put it** — a bone's
|
|
8167
|
+
`shearX`, a region's `name` and `path`, a header's `fps`. The row's keys are
|
|
8168
|
+
permuted among the places they hold. Sending such a key to the end instead was
|
|
8169
|
+
rejected on a measured case: the corpus's physics `strength` keys are all at
|
|
8170
|
+
t=0, so the editor wrote them `value` alone, and that rule would have moved
|
|
8171
|
+
`time` — first in every other key kind the editor writes — behind `value`.
|
|
8172
|
+
|
|
8173
|
+
🔬 **Two positions and one fold are measured on a production set, not on
|
|
8174
|
+
`examples/`** ([#791](https://github.com/firejune/rigc/issues/791)). The twelve
|
|
8175
|
+
exports carry no mesh `path` or `color` and no slot name outside ASCII — `IG84`
|
|
8176
|
+
and `IG85` count both on every run with a corpus — so the table cannot learn
|
|
8177
|
+
either, and what rigc writes for them was read off 42 production exports
|
|
8178
|
+
instead:
|
|
8179
|
+
|
|
8180
|
+
- **A mesh attachment writes `path` and `color` right after `type`, `path` before `color` — measured on a production set (#791), not on `examples/`.**
|
|
8181
|
+
18 meshes whose `path` differs from their name wrote it second, and 1 with a
|
|
8182
|
+
`color` wrote that second; no mesh carried both. The row does not list them,
|
|
8183
|
+
so the position is the constructor's — `meshTextureKeys` in
|
|
8184
|
+
[`src/compile.ts`](../src/compile.ts), which every mesh route spreads right
|
|
8185
|
+
after `type` (`S104`). A region's `path` and a linked mesh's keys stay where
|
|
8186
|
+
they were: neither was in the set, and an analogy is not a measurement.
|
|
8187
|
+
- **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** —
|
|
8188
|
+
the ideographic space as a space, the full-width digits as `0`–`9`
|
|
8189
|
+
(`EDITOR_NAME_FOLD`). One map of 95 slot keys, 7 of them carrying those
|
|
8190
|
+
characters, came back in an order this reproduces 95 of 95 and neither a
|
|
8191
|
+
codepoint sort nor the comparator without the fold does (`S105`). It is **not**
|
|
8192
|
+
`normalize('NFKC')`, though NFKC folds both the same way: NFKC, a classifier
|
|
8193
|
+
that reads every Unicode digit as a digit and every space separator as a space,
|
|
8194
|
+
and a width fold alone all reproduce that map and disagree past it (on `fi`,
|
|
8195
|
+
`²`, `٣`, `A`, a no-break space), and what all three agree on is exactly
|
|
8196
|
+
these two classes. The comparator is R10's, so animation and skin names are
|
|
8197
|
+
compared after the fold too. A pair the fold does not close — a tab, a
|
|
8198
|
+
no-break space — still keeps the map in rigc's order, whole (`S106`).
|
|
8199
|
+
|
|
8200
|
+
🔸 **An object keyed by NAMES is not a field order**, and the table does not
|
|
8201
|
+
touch one. A bone's or a slot's timelines, and a transform constraint's
|
|
8202
|
+
`properties` and each `to` inside them, are read into the runtime's arrays in the
|
|
8203
|
+
order they are keyed — for timelines, the order they are applied in — so their
|
|
8204
|
+
order is more than a key's position, and rigc already emits the export's own on
|
|
8205
|
+
all twelve. A skin's `attachments` slot keys are the one name-keyed map this
|
|
8206
|
+
changed: sorted by the editor's comparator (§10.1). The per-slot maps inside are
|
|
8207
|
+
the rig's own order.
|
|
8208
|
+
|
|
8209
|
+
⇒ **What this means for you: nothing to write.** No input format changed and no
|
|
8210
|
+
key order is yours to state — a rig spec is read by name, and its field order
|
|
8211
|
+
still means nothing. What changed is that a canonical-form comparison of an
|
|
8212
|
+
export's rebuild against the export no longer finds an object whose keys sit
|
|
8213
|
+
elsewhere (`IG76`). What it found next was the defaults the export leaves out
|
|
8214
|
+
and rigc wrote, `"name": null`, and the three header keys (`IG78`) — and since
|
|
8215
|
+
§10.6c it finds the header's `hash` and `spine` and nothing else (`IG83`).
|
|
8216
|
+
|
|
8217
|
+
### 10.6c The keys it leaves out
|
|
8218
|
+
|
|
8219
|
+
The editor writes a key only where its value is not the one the parser reads in its
|
|
8220
|
+
absence, and since [#716](https://github.com/firejune/rigc/issues/716) rigc's emitter
|
|
8221
|
+
does the same, in one pass over the finished skeleton (`withoutParserDefaults` in
|
|
8222
|
+
`src/keyorder.ts`, run just before §10.6b's). A key is left out when its value is
|
|
8223
|
+
**exactly** the one below — the float the file would hold, not a value near it — so a
|
|
8224
|
+
rotation of `1e-45` is written and a rotation of `0` is not. The table is
|
|
8225
|
+
`PARSER_DEFAULTS`, and every row is the linked `spine-core` parser's own fallback
|
|
8226
|
+
(`getValue(map, key, default)` in `SkeletonJson.js`); the selftest loads an object of
|
|
8227
|
+
each kind with the key at that value and without it, and requires the two to be the
|
|
8228
|
+
same `SkeletonData` (`S103` on the in-tree builds, `IG82` on the corpus), and deletes
|
|
8229
|
+
every key of every in-tree build in turn to find one the table is missing (`S101`):
|
|
8230
|
+
|
|
8231
|
+
| Kind | Left out when it is |
|
|
8232
|
+
| --- | --- |
|
|
8233
|
+
| `header` | `referenceScale` 100 |
|
|
8234
|
+
| `bone` | `length` 0 · `rotation` 0 · `x` 0 · `y` 0 · `scaleX` 1 · `scaleY` 1 · `shearX` 0 · `shearY` 0 · `inherit` "normal" · `skin` false · `iconSize` 1 · `iconRotation` 0 |
|
|
8235
|
+
| `slot` | `color` "ffffffff" · `attachment` null · `blend` "normal" · `visible` true |
|
|
8236
|
+
| `ik constraint` | `skin` false · `mix` 1 · `softness` 0 · `bendPositive` true · `compress` false · `stretch` false |
|
|
8237
|
+
| `transform constraint` | `skin` false · `localSource` false · `localTarget` false · `additive` false · `clamp` false · `rotation` 0 · `x` 0 · `y` 0 · `scaleX` 0 · `scaleY` 0 · `shearY` 0 · `mixRotate` 1 · `mixX` 1 · `mixScaleX` 1 · `mixShearY` 1 |
|
|
8238
|
+
| `path constraint` | `skin` false · `positionMode` "percent" · `spacingMode` "length" · `rotateMode` "tangent" · `rotation` 0 · `position` 0 · `spacing` 0 · `mixRotate` 1 · `mixX` 1 · `mixY` `mixX`'s, only at 1 |
|
|
8239
|
+
| `physics constraint` | `skin` false · `x` 0 · `y` 0 · `rotate` 0 · `scaleX` 0 · `shearX` 0 · `limit` 5000 · `fps` 60 · `inertia` 0.5 · `strength` 100 · `damping` 0.85 · `mass` 1 · `wind` 0 · `gravity` 0 · `mix` 1 · `inertiaGlobal` false · `strengthGlobal` false · `dampingGlobal` false · `massGlobal` false · `windGlobal` false · `gravityGlobal` false · `mixGlobal` false |
|
|
8240
|
+
| `slider constraint` | `skin` false · `additive` false · `loop` false · `mix` 1 · `from` 0 · `to` 0 · `scale` 1 · `max` 0 · `local` false |
|
|
8241
|
+
| `region attachment` | `x` 0 · `y` 0 · `scaleX` 1 · `scaleY` 1 · `rotation` 0 · `color` "ffffffff" |
|
|
8242
|
+
| `mesh attachment` | `color` "ffffffff" · `hull` 0 · `width` 0 · `height` 0 |
|
|
8243
|
+
| `path attachment` | `closed` false · `constantSpeed` true |
|
|
8244
|
+
| `clipping attachment` | `convex` false · `inverse` false |
|
|
8245
|
+
| `sequence` | `start` 1 · `setup` 0 |
|
|
8246
|
+
| `event` | `int` 0 · `float` 0 · `string` "" · `audio` null |
|
|
8247
|
+
| `bone rotate key` | `time` 0 · `value` 0 |
|
|
8248
|
+
| `bone translate key` | `time` 0 · `x` 0 · `y` 0 |
|
|
8249
|
+
| `bone translatex key` | `time` 0 · `value` 0 |
|
|
8250
|
+
| `bone translatey key` | `time` 0 · `value` 0 |
|
|
8251
|
+
| `bone scale key` | `time` 0 · `x` 1 · `y` 1 |
|
|
8252
|
+
| `bone scalex key` | `time` 0 · `value` 1 |
|
|
8253
|
+
| `bone scaley key` | `time` 0 · `value` 1 |
|
|
8254
|
+
| `bone shear key` | `time` 0 · `x` 0 · `y` 0 |
|
|
8255
|
+
| `slot attachment key` | `time` 0 · `name` null |
|
|
8256
|
+
| `slot rgba key` | `time` 0 |
|
|
8257
|
+
| `ik key` | `time` 0 · `mix` 1 · `softness` 0 · `bendPositive` true · `compress` false · `stretch` false |
|
|
8258
|
+
| `transform key` | `time` 0 · `mixRotate` 1 · `mixX` 1 · `mixY` `mixX`'s, only at 1 · `mixScaleX` 1 · `mixScaleY` 1 · `mixShearY` 1 |
|
|
8259
|
+
| `path position key` | `time` 0 · `value` 0 |
|
|
8260
|
+
| `physics damping key` | `time` 0 · `value` 0 |
|
|
8261
|
+
| `physics inertia key` | `time` 0 · `value` 0 |
|
|
8262
|
+
| `physics mass key` | `time` 0 · `value` 0 |
|
|
8263
|
+
| `physics strength key` | `time` 0 · `value` 0 |
|
|
8264
|
+
| `physics wind key` | `time` 0 · `value` 0 |
|
|
8265
|
+
| `physics mix key` | `time` 0 · `value` 1 |
|
|
8266
|
+
| `attachment deform key` | `time` 0 · `offset` 0 |
|
|
8267
|
+
| `attachment sequence key` | `time` 0 · `index` 0 · `mode` "hold" · `delay` the key before's, 0 on key 0 |
|
|
8268
|
+
| `drawOrder key` | `time` 0 |
|
|
8269
|
+
| `event key` | `time` 0 |
|
|
8270
|
+
|
|
8271
|
+
Read a row as *"left out when it is"*: `x` 0 is a key left out at `0`; `mixY` `mixX`'s
|
|
8272
|
+
is a key left out where it equals the same object's `mixX` as the parser reads it,
|
|
8273
|
+
and *only at 1* narrows that to the one value the editor leaves it out at — measured:
|
|
8274
|
+
`sack-pro` writes `"mixX": 0, "mixY": 0` on six transform keys, where the parser would
|
|
8275
|
+
read an absent `mixY` as that same `0`. *The key before's* is a sequence key's
|
|
8276
|
+
`delay`, which the parser carries from the previous key (`lastDelay`).
|
|
8277
|
+
|
|
8278
|
+
⚠️ **What is not a row, and why.**
|
|
8279
|
+
|
|
8280
|
+
- **The header's `x`, `y` and `fps`.** The 4.3 JSON reader assigns them raw
|
|
8281
|
+
(`skeletonData.x = skeletonMap.x`, `SkeletonJson.js:70`), so an absent origin loads
|
|
8282
|
+
as `undefined` and a written `0` as `0`: there is no fallback for them to equal, and
|
|
8283
|
+
rigc keeps writing the origin of a declared stage. The editor leaves it out
|
|
8284
|
+
(§10.6's round trip 6), so a rebuild of an export whose stage sits at `0,0` spells
|
|
8285
|
+
two fields the export does not — `LOSS HEADER_ORIGIN` says so on `ingest`.
|
|
8286
|
+
- **A transform constraint's `mixY` and `mixScaleY`.** Their fallback is the
|
|
8287
|
+
constraint's `mixX` / `mixScaleX` — but the parser reads those only for a property
|
|
8288
|
+
the constraint drives, so on one that drives `y` alone the fallback is the pose's
|
|
8289
|
+
initial `0`, not `1`. A row there was tried and measured wrong: it left out
|
|
8290
|
+
`8-follow-through-pro-ball`'s `"mixY": 1` on two such constraints, they loaded at
|
|
8291
|
+
`0`, and `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` refused the rebuild.
|
|
8292
|
+
- **A region's `path`, an attachment's `name`, an event key's payload.** Their
|
|
8293
|
+
fallbacks are another value — the attachment's own name, its placeholder, the event
|
|
8294
|
+
definition's — which rigc writes only where it differs.
|
|
8295
|
+
- **A slider's `time` and a sequence's `digits`.** The parser has a fallback for
|
|
8296
|
+
both, and nothing here can load it: `time` is read only on a slider with no `bone`,
|
|
8297
|
+
which no build or export carries, and `digits` renames every frame of the series,
|
|
8298
|
+
so no atlas a build resolves against loads it either way. Both are written as
|
|
8299
|
+
stated.
|
|
8300
|
+
- **Every kind with no row** — the keys of a `shearx`, `sheary`, `inherit`, `alpha`,
|
|
8301
|
+
`rgb`, `rgb2`, `rgba2`, path `spacing`/`mix`, physics `gravity`/`reset` or slider
|
|
8302
|
+
timeline, a linked mesh, a point attachment. No build the selftest loads and no
|
|
8303
|
+
export carries an object of those kinds, and a row nothing loads is a claim about
|
|
8304
|
+
the parser nobody has checked — where a missing row only costs a key the parser
|
|
8305
|
+
reads the same either way. `S101` names such a key the day a build emits one.
|
|
8306
|
+
|
|
8307
|
+
⚠️ **Why 4.3's defaults, when #706 row 4 says they are not every generation's.**
|
|
8308
|
+
An omitted physics `inertia` is 0.5 to the 4.3 parser and 1 to the 4.2 one, and that
|
|
8309
|
+
is exactly why a key is left out only against the parser rigc links: the file states
|
|
8310
|
+
`"spine": "4.3.13"`, and the parser it is gated against is that one. Writing the
|
|
8311
|
+
default out would not make the file safe for a 4.2 reader: that reader applies its
|
|
8312
|
+
own defaults to every *other* key the file leaves out as well — every one the editor's
|
|
8313
|
+
own 4.3 export leaves out, too — and #706 rows 1, 2 and 6 measure what reading one
|
|
8314
|
+
generation's data on another generation's runtime does, none of it repaired by one
|
|
8315
|
+
key written out. That misread is refused where it can be seen, on the reading side
|
|
8316
|
+
(`GENERATION_UNSUPPORTED` in `ingest`), rather than written for on the emitting one.
|
|
8317
|
+
|
|
8318
|
+
⇒ **What this means for you: nothing to write, and nothing to stop writing.** A
|
|
8319
|
+
default you state stays in your spec and leaves the file; `explain` prints a bone
|
|
8320
|
+
key's channels as the parser reads them, so a key that left the file still shows its
|
|
8321
|
+
value there.
|
|
8322
|
+
|
|
7949
8323
|
### 10.7 What this section does not claim
|
|
7950
8324
|
|
|
7951
8325
|
Conventions that are visible in reference exports but that **no public Spine page
|