spine-rigc 0.22.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -1
- package/cli.ts +263 -13
- package/docs/AUTHORING.md +554 -75
- package/docs/INGEST.md +238 -44
- package/docs/SPEC_COVERAGE.md +14 -3
- package/package.json +1 -1
- package/skills/ingest/SKILL.md +33 -11
- package/src/atlas.ts +135 -16
- package/src/check.ts +83 -1
- package/src/compile.ts +402 -74
- package/src/deformmeasure.ts +322 -151
- package/src/diff.ts +125 -2
- package/src/ingest.ts +1137 -0
- package/src/render.ts +113 -17
- package/src/rig.ts +92 -8
- package/src/timelines.ts +163 -0
- package/src/types.ts +74 -9
- package/src/validate.ts +765 -92
- package/tools/editor_roundtrip.ts +247 -36
package/docs/AUTHORING.md
CHANGED
|
@@ -329,28 +329,129 @@ Four things are refused rather than warned about, because each of them otherwise
|
|
|
329
329
|
| 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 |
|
|
330
330
|
| a rectangle that runs off its page | `x + width` past the page width makes `u2 > 1`, which samples whatever the wrap mode does |
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
One limit, stated rather than discovered:
|
|
333
333
|
|
|
334
334
|
- an **optional state** (a manifest `states:` entry) whose region is not in the
|
|
335
335
|
pack is a `DROP`, not a refusal — the same documented absence a missing PNG has
|
|
336
|
-
always been, and the line names the atlas rather than a file nobody opened
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
always been, and the line names the atlas rather than a file nobody opened.
|
|
337
|
+
|
|
338
|
+
A **turned** region is not one of them, and used to be (issue #570). A pack made
|
|
339
|
+
by somebody else routinely rotates a region to save space — `rotate: 90`,
|
|
340
|
+
`rotate: 180`, `rotate: 270`, or the format's older `rotate: true` — and rigc
|
|
341
|
+
reads all of them: anything that measures a part's pixels sees the same grid it
|
|
342
|
+
would have seen from the loose PNG, so a `contour` mesh traces the same
|
|
343
|
+
silhouette and an authored mesh's fit figure is the same number. The one thing
|
|
344
|
+
that does not change is what rigc **writes**: its own packer never turns a
|
|
345
|
+
region.
|
|
346
|
+
|
|
347
|
+
⚠️ Two limits here are real and neither is about rotation:
|
|
348
|
+
|
|
349
|
+
- `--atlas-in` cannot recover what a `scale:` quantised away (above), turned or not;
|
|
350
|
+
- the **renderer** profile still refuses a turned region outright
|
|
351
|
+
(`A06`, `--profile spine-html`), because that profile is about artifacts rigc
|
|
352
|
+
itself emits and it never packs one turned. Reading a foreign pack and gating
|
|
353
|
+
one under somebody else's renderer policy are different questions.
|
|
341
354
|
|
|
342
355
|
`--pack` and `--atlas-in` are opposite directions through the same door and are
|
|
343
356
|
refused together.
|
|
344
357
|
|
|
358
|
+
### 0.3 Starting from a skeleton somebody else made — `ingest`
|
|
359
|
+
|
|
360
|
+
Everything above starts from two spec files you wrote. `rigc ingest` starts from a
|
|
361
|
+
**Spine 4.3 `skeleton.json` you were handed** and writes those two files for you, so
|
|
362
|
+
an existing rig is a starting point instead of 250 KB of arrays to retype.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
bun cli.ts ingest hero.json --out specs/ --stage 0,0,1024,768 --images parts/
|
|
366
|
+
# .. out /abs/path/specs
|
|
367
|
+
# .. art loose
|
|
368
|
+
# .. images ../parts/ (the rig spec's own, from /abs/path/specs)
|
|
369
|
+
# JUDGE DURATION: animation "idle" — skeleton JSON carries no duration; the largest key time (2.667) is used …
|
|
370
|
+
# LOSS PATH_LENGTHS: skin "default" slot "track" attachment "track" — the source states `lengths`; rigc RE-MEASURES it …
|
|
371
|
+
# rigc: wrote /abs/path/specs/rig.json
|
|
372
|
+
# rigc: wrote /abs/path/specs/motion.json
|
|
373
|
+
# rigc: wrote /abs/path/specs/findings.json
|
|
374
|
+
# rigc: build it with rigc build --rig /abs/path/specs/rig.json --motion /abs/path/specs/motion.json --out <dir>
|
|
375
|
+
|
|
376
|
+
bun cli.ts build --rig specs/rig.json --motion specs/motion.json --out spine
|
|
377
|
+
bun cli.ts diff spine/skeleton.json hero.json # 1.000 on every measure
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**The loop it belongs in is the one above, with a different first step.** `ingest`
|
|
381
|
+
writes the specs; you *edit* them the way you would edit specs you wrote; `build`
|
|
382
|
+
gates; `explain`, `render`, `preview` and `check` read the result. Nothing
|
|
383
|
+
downstream knows or cares that the file started somewhere else — which is the point,
|
|
384
|
+
and also the hazard the `note` below exists for.
|
|
385
|
+
|
|
386
|
+
**The contract is an equality, not a rulebook.** `build(ingest(x))` is `x`: the
|
|
387
|
+
rebuilt `skeleton.json` is byte for byte the file `ingest` read, and the rebuilt
|
|
388
|
+
atlas holds the same **region blocks** — as a multiset, because the order the pages
|
|
389
|
+
come out in is in no field of the skeleton and a decompiled spec cannot know it.
|
|
390
|
+
That is a gate rather than a claim: `bun run selftest` round-trips every rig this
|
|
391
|
+
repository builds on every run.
|
|
392
|
+
|
|
393
|
+
**The flags are for what a skeleton does not encode**, and nothing else is a flag.
|
|
394
|
+
|
|
395
|
+
| flag | what it decides |
|
|
396
|
+
| --- | --- |
|
|
397
|
+
| `--art loose` (default) | name an `image` per attachment — `<path or placeholder>.png` — so the rebuild resolves loose PNGs and rigc measures them |
|
|
398
|
+
| `--art none` | state `width`/`height` only, so the rebuild is `build --atlas-in <pack.atlas>` and every part resolves out of the pack |
|
|
399
|
+
| `--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 |
|
|
400
|
+
| `--stage x,y,w,h` | the setup bounding box. **Required for an editor export**, which carries none |
|
|
401
|
+
| `--name <n>` | the rig spec's `name`, which the motion spec's `archetype` must equal (default: the file's basename) |
|
|
402
|
+
|
|
403
|
+
⚠️ **`ingest --images` and `build --images` point opposite ways.** `build --images`
|
|
404
|
+
*overrides* the rig spec's own directory for one invocation; `ingest --images`
|
|
405
|
+
*writes* it, once, so no invocation needs the override. They share a name because
|
|
406
|
+
they name the same field — and `ingest` spells the value with the same function
|
|
407
|
+
`build` spells `skeleton.images` with, so a spec and the skeleton it came from say
|
|
408
|
+
where the parts are in one convention.
|
|
409
|
+
|
|
410
|
+
🚨 **The stage is the one value `ingest` will not guess.** rigc always emits
|
|
411
|
+
`skeleton.width`/`height` and an editor export never does, so a foreign file needs
|
|
412
|
+
`--stage`; without it the missing box is a **blocker**, named. It is not derivable —
|
|
413
|
+
posing the rig gives the *animated* extent, which is a different number from the
|
|
414
|
+
editor's setup box — and it is the value that costs least to get wrong, because no
|
|
415
|
+
measure `diff` reports reads the skeleton header at all. Supply it from the project
|
|
416
|
+
the file came from, or from the editor's own canvas.
|
|
417
|
+
|
|
418
|
+
⚠️ **The duration is a convention, and it is recorded as one.** Skeleton JSON has no
|
|
419
|
+
duration field. The largest key time is the only derivable answer and it is what a
|
|
420
|
+
runtime plays to — but it is wrong for an animation that holds its last pose past its
|
|
421
|
+
last key, and nothing in the file can tell the two apart. `ingest` writes the largest
|
|
422
|
+
key time, states the convention in the motion spec's `note`, and records a finding per
|
|
423
|
+
animation. If you know the real number, edit it: the declared duration is checked
|
|
424
|
+
against the compiled keys, so an honest one costs you nothing.
|
|
425
|
+
|
|
426
|
+
**Read the findings; they are the product.** Three kinds, and the exit code turns on
|
|
427
|
+
the first:
|
|
428
|
+
|
|
429
|
+
| gutter | meaning |
|
|
430
|
+
| --- | --- |
|
|
431
|
+
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `linkedmesh`, `point`, an attachment `sequence`, an unknown field on a bone, slot or constraint, a timeline family the motion spec has no track for. The command exits non-zero **and still writes both specs**, because a spec plus a list of what is missing from it beats no spec |
|
|
432
|
+
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
433
|
+
| `LOSS` | the skeleton says it and rigc re-derives it, on purpose. A path attachment's `lengths` is the one that matters — it is `PathConstraint`'s own four-sample measurement rather than an arc length (#560), so a transcribed one would freeze whatever produced the source |
|
|
434
|
+
|
|
435
|
+
📝 **Do not delete the `note`.** Both written specs carry one saying the file is
|
|
436
|
+
decompiled and naming the skeleton it came from. A decompiled spec is
|
|
437
|
+
indistinguishable from an authored one by inspection, every gate here calls it green —
|
|
438
|
+
because it *is* green — and no gate can catch the note's absence. It carries no
|
|
439
|
+
timestamp, deliberately: `A18_DETERMINISTIC_EMIT` compares two independent compiles
|
|
440
|
+
byte for byte, and a dated note would break the first rebuild from the spec.
|
|
441
|
+
|
|
442
|
+
⛔ **It reads skeleton JSON and nothing else** — not a `.spine` project, not a binary
|
|
443
|
+
`.skel`, not the atlas, not the art. A path that is not a `.json` is refused by name.
|
|
444
|
+
[INGEST.md](INGEST.md) is the whole page on working from a file you were handed.
|
|
445
|
+
|
|
345
446
|
The other commands:
|
|
346
447
|
|
|
347
448
|
```bash
|
|
348
449
|
bun cli.ts explain --rig … --motion … --out … # the compiled rig as a table
|
|
349
450
|
bun cli.ts validate path/to/spine # re-gate artifacts already on disk
|
|
350
451
|
bun cli.ts diff candidate.json reference.json
|
|
351
|
-
bun cli.ts check --candidate path/to/spine --frames path/to/frames
|
|
452
|
+
bun cli.ts check --candidate path/to/spine --frames path/to/frames [--skin …]
|
|
352
453
|
bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
353
|
-
bun cli.ts render --candidate path/to/spine [--animation …] [--fps 12] [--max 256]
|
|
454
|
+
bun cli.ts render --candidate path/to/spine [--animation …] [--skin …] [--fps 12] [--max 256]
|
|
354
455
|
bun cli.ts preview --candidate path/to/spine [--animation …] [--out preview.html]
|
|
355
456
|
bun cli.ts vote --candidate path/to/a --candidate path/to/b [--out ballot.html]
|
|
356
457
|
bun cli.ts vote --record vote-<id>.json [--ballot ballot.html] [--ledger votes.jsonl]
|
|
@@ -372,6 +473,9 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
372
473
|
for opposite fixes. A measure with nothing to compare says `0/0` and says so.
|
|
373
474
|
- **`check`** renders your candidate into the reference frames' own pixel grid and
|
|
374
475
|
compares pixels — the only thing here that can see a wrong animation. **§9.**
|
|
476
|
+
🚨 What it certifies is the **default skin** unless you pass `--skin <name>`:
|
|
477
|
+
on a rig with named skins, a run with no skin draws none of their art on either
|
|
478
|
+
side and reports a perfect `0.0000` about it (**§9**).
|
|
375
479
|
- **`bench <rung>`** runs one rung of [the benchmark ladder](https://github.com/firejune/rigc/blob/main/docs/LADDER.md): validate
|
|
376
480
|
under `--profile spine`, then diff against that rung's reference export, and with
|
|
377
481
|
`--frames` the `check` table as well. Unlike the three above it is a **finish
|
|
@@ -673,8 +777,8 @@ is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
|
673
777
|
|
|
674
778
|
| Field | Spine meaning | Default |
|
|
675
779
|
| --- | --- | --- |
|
|
676
|
-
| `x`, `y` | setup-pose bounding box origin | `0` |
|
|
677
|
-
| `width`, `height` | setup-pose bounding box size | falls back to the manifest's crop; **with neither
|
|
780
|
+
| `x`, `y` | setup-pose bounding box origin | `0` — and refused outright beside a stated absence, below |
|
|
781
|
+
| `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** |
|
|
678
782
|
| `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
|
|
679
783
|
| `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
|
|
680
784
|
| `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) |
|
|
@@ -690,6 +794,40 @@ links, not the editor that will open the file, and the warning is harmless
|
|
|
690
794
|
`width`/`height` are what `A14` and `A19` measure against, so a guessed stage is a
|
|
691
795
|
gate measuring against a number nobody wrote down.
|
|
692
796
|
|
|
797
|
+
⭐ **A skeleton may declare no stage, and saying so is not the same as saying
|
|
798
|
+
nothing** (issue #578). Write the pair as `null`:
|
|
799
|
+
|
|
800
|
+
```json
|
|
801
|
+
"skeleton": { "width": null, "height": null }
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
and the emitted header carries **none** of `x`/`y`/`width`/`height` — which is
|
|
805
|
+
what an export of a skeleton whose stage was never set looks like, and the shape a
|
|
806
|
+
transcriber of one now has something to write. `null` is this spec's spelling for
|
|
807
|
+
a stated absence wherever it has one (`slots[].attachment` is `null` for "show
|
|
808
|
+
nothing"), so nothing new is introduced here but a third value of a field that
|
|
809
|
+
already existed.
|
|
810
|
+
|
|
811
|
+
Three readings stay apart, and the middle one is the point of the other two:
|
|
812
|
+
|
|
813
|
+
| What the spec says | What happens |
|
|
814
|
+
| --- | --- |
|
|
815
|
+
| a number for each | the stage, as before; a manifest `crop` is the fallback |
|
|
816
|
+
| `"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 |
|
|
817
|
+
| neither | **refused**, exactly as before: `no stage size: …` |
|
|
818
|
+
| one `null`, one number | refused — a stage has both extents or neither, and which half was meant is not derivable |
|
|
819
|
+
| the pair `null` **and** an `x` or `y` | refused — an origin for a box that is not there |
|
|
820
|
+
|
|
821
|
+
⚠️ A stage-less skeleton is **unmeasured, not certified**: `A14_NO_FULL_FRAME_MESH`
|
|
822
|
+
reports **SKIP** on one, because there is no full frame for a mesh to span. And
|
|
823
|
+
`rigc diff` reports it — `skeleton.stage_present` and `skeleton.stage_box`, in the
|
|
824
|
+
header block at the top of the report — so a stage somebody invented now reads
|
|
825
|
+
below 1.000 against a source that has none. Both are reported and gate nothing, for
|
|
826
|
+
the reason every reported measure is: no reading of the rendered frames could have
|
|
827
|
+
decided a setup-pose bounding box. The measure inventory that says so lives in
|
|
828
|
+
[BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md), which
|
|
829
|
+
is repository material and not in the published package.
|
|
830
|
+
|
|
693
831
|
### 3.2 `bones` — Spine's bone list
|
|
694
832
|
|
|
695
833
|
`parent` is resolved **by name against bones already declared**, exactly as the
|
|
@@ -720,17 +858,38 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
720
858
|
| --- | --- | --- |
|
|
721
859
|
| `name` | required, unique | — |
|
|
722
860
|
| `bone` | required; must be a bone this rig declares | — |
|
|
723
|
-
| `attachment` | the **setup pose** attachment name, or `null` for "show nothing" | must come from here or from `motion.setup` (R3) |
|
|
861
|
+
| `attachment` | the **setup pose** attachment name, or `null` for "show nothing" | must come from here or from `motion.setup` (R3) — **except** on a slot nothing fills, where it can only be `null` and may be left out |
|
|
724
862
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
725
863
|
| `dark` | two-colour tint, `rrggbb` | — (🚫 `A12` under `spine-html`) |
|
|
726
864
|
| `blend` | `normal` · `additive` · `multiply` · `screen` | `normal` |
|
|
727
865
|
|
|
728
|
-
|
|
729
|
-
entry, no manifest part —
|
|
730
|
-
|
|
731
|
-
slot
|
|
732
|
-
|
|
733
|
-
|
|
866
|
+
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
867
|
+
skin entry, no manifest part — is emitted **empty**: `name` and `bone`, and no
|
|
868
|
+
`attachment` key at all, which is how the format spells "shows nothing"
|
|
869
|
+
(`SkeletonJson`'s slot reader takes `attachment` with a `null` default; 34 of the 52 slots of the
|
|
870
|
+
official `spineboy-pro` export omit the key, though those are slots a skin fills
|
|
871
|
+
whose setup pose shows nothing). So the emitted slots array
|
|
872
|
+
**is** the rig's slot table, and `A26_SLOT_DRAW_ORDER` checks it in both directions:
|
|
873
|
+
nothing out of order, and nothing missing.
|
|
874
|
+
|
|
875
|
+
Declaring a slot no cut fills is therefore normal — it fixes where that slot sits
|
|
876
|
+
whether or not this cut has art for it — and transcribing a foreign skeleton that
|
|
877
|
+
carries an empty slot reproduces it exactly.
|
|
878
|
+
|
|
879
|
+
⚠️ **This changed with issue #575.** Such a slot used to be *dropped*, with no message, and
|
|
880
|
+
the gate allowed the emitted array to be any subsequence of the rig's. What it cost
|
|
881
|
+
is the index: every slot below the dropped one moved up one place, which is what a
|
|
882
|
+
`drawOrder` key's offsets are counted against and what an index-keyed consumer
|
|
883
|
+
splits on. Two production exports declaring 53 and 61 slots built green at 51 and 57
|
|
884
|
+
and read 0.962 and 0.934 under `diff` against the file they were transcribed from.
|
|
885
|
+
If you have a rig that leaned on the drop, the emitted array simply grows; nothing
|
|
886
|
+
else about it moves.
|
|
887
|
+
|
|
888
|
+
🚫 **Naming an attachment on a slot nothing fills is refused by name**: `the setup
|
|
889
|
+
pose shows attachment "x" on slot "y", which no skin and no manifest part fills`.
|
|
890
|
+
That is the half-finished wiring-up the old silence hid — the slot is emitted empty
|
|
891
|
+
and the name resolves to nothing, so either give the slot an attachment or state the
|
|
892
|
+
setup pose as `null`.
|
|
734
893
|
|
|
735
894
|
### 3.4 `skins` — placeholder → attachment maps
|
|
736
895
|
|
|
@@ -743,6 +902,33 @@ A skin can also say which bones and constraints it **switches on**, and that nee
|
|
|
743
902
|
one more level, so a skin entry has a second spelling — see §3.4.1. The short one
|
|
744
903
|
above is unchanged and is what almost every rig wants.
|
|
745
904
|
|
|
905
|
+
🔸 **A skin may fill no slot with anything that needs art, and that build is
|
|
906
|
+
green.** The atlas is built out of what the skins reference, so a rig whose skins
|
|
907
|
+
name no `image` — an empty `default`, or one carrying only a `boundingbox`, a
|
|
908
|
+
`clipping` polygon or a `path`, none of which has a page — compiles to an atlas
|
|
909
|
+
with **no pages**, and rigc writes `skeleton.atlas` as an **empty file** (zero
|
|
910
|
+
bytes). Four rules then report SKIP by name rather than a pass, because a page is
|
|
911
|
+
their whole subject: `A07_ATLAS_TEXT_SHAPE`, `A06_ATLAS_PAGE_SIZE_MATCHES_PNG`,
|
|
912
|
+
`A17_ATLAS_PAGE_FILES_EXIST` and — under `spine-html` —
|
|
913
|
+
`A19_OVERLAY_PNGS_HAVE_ALPHA` and `A27_REGION_NAME_MATCHES_PAGE_FILENAME`. Until
|
|
914
|
+
[#608](https://github.com/firejune/rigc/issues/608) the same compile wrote one
|
|
915
|
+
newline instead and `A07` refused it with two findings, so a hit-box skeleton — a
|
|
916
|
+
correct rig, whose geometry `A33_VERTEX_ATTACHMENT_GEOMETRY` passes — could not be
|
|
917
|
+
built at all.
|
|
918
|
+
|
|
919
|
+
⚠️ **This is not the case where an attachment WANTS a region.** A region or mesh
|
|
920
|
+
attachment that states `width`/`height` and names no `image` still resolves a
|
|
921
|
+
region by `path`, and with no atlas to supply it that is
|
|
922
|
+
`A08_REGION_NAMES_MATCH_ATTACHMENTS` naming the skin, the slot, the placeholder
|
|
923
|
+
and the path — which is what a spec written by `ingest --art none` does when it is
|
|
924
|
+
built without `--atlas-in` (§0.2, §0.3). The empty atlas is legal; an attachment
|
|
925
|
+
pointing into it is not.
|
|
926
|
+
|
|
927
|
+
⚠️ **`rigc render` still refuses such a build**, by name and before it draws
|
|
928
|
+
anything: `… posed no drawable attachment in any animation or in its setup pose —
|
|
929
|
+
there is nothing to draw`. That is the honest division — the rig is valid Spine
|
|
930
|
+
data, and there is no picture of it.
|
|
931
|
+
|
|
746
932
|
**Region attachment** ([Spine: region attachments](http://esotericsoftware.com/spine-regions)),
|
|
747
933
|
the default `type`:
|
|
748
934
|
|
|
@@ -751,7 +937,7 @@ the default `type`:
|
|
|
751
937
|
| `type` | `"region"`, or omit |
|
|
752
938
|
| `image` | **rigc extension.** A PNG relative to the rig's `images` directory; rigc measures it (R5) |
|
|
753
939
|
| `width`, `height` | required by the format — give them, or give an `image` |
|
|
754
|
-
| `path` | the atlas region to resolve; defaults to the attachment's own name. rigc sets it for you when the PNG basename differs from the placeholder, and whenever it composes a `name` because more than one skin fills this placeholder (§3.4.2) |
|
|
940
|
+
| `path` | the atlas region to resolve; defaults to the attachment's own name. rigc sets it for you when the PNG basename differs from the placeholder, and whenever it composes a `name` because more than one skin fills this placeholder (§3.4.2). **One rule, both kinds** — see the note under *Mesh attachment* |
|
|
755
941
|
| `x`, `y` | offset from the bone, in the bone's local space |
|
|
756
942
|
| `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
|
|
757
943
|
| `scaleX`, `scaleY`, `color` | as Spine |
|
|
@@ -760,7 +946,23 @@ the default `type`:
|
|
|
760
946
|
either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
|
|
761
947
|
never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
|
|
762
948
|
omitted, rigc derives — `hull` and `edges` from the triangles, the size from the
|
|
763
|
-
PNG — and the rules are a few paragraphs down.
|
|
949
|
+
PNG — and the rules are a few paragraphs down. `type`, `image`, `path` and `color`
|
|
950
|
+
mean exactly what they mean on a region.
|
|
951
|
+
|
|
952
|
+
🔑 **`path` is one rule for both kinds.** A mesh derives it from `image` the way a
|
|
953
|
+
region does: stated wins, otherwise the PNG's basename when that differs from the
|
|
954
|
+
placeholder, otherwise nothing. The parser reads `path` off both with the same line
|
|
955
|
+
(`getValue(map, "path", name)`, `SkeletonJson.ts:541` and `:570`), and `path`
|
|
956
|
+
defaults to the attachment's **name** rather than to the placeholder — so a mesh
|
|
957
|
+
with `image: hair_short.png` under a placeholder called `hair` resolves the region
|
|
958
|
+
`hair`, which no atlas has. Until
|
|
959
|
+
[#577](https://github.com/firejune/rigc/issues/577) a region derived it and an
|
|
960
|
+
authored mesh did not, so that rig **built** and then failed
|
|
961
|
+
`A00_ROUNDTRIP_PARSE: threw: Region not found in atlas: hair` — which is the
|
|
962
|
+
loader's sentence and all the report had. Since
|
|
963
|
+
[#589](https://github.com/firejune/rigc/issues/589) the same miss is named by
|
|
964
|
+
`A08_REGION_NAMES_MATCH_ATTACHMENTS`, with the skin, the slot, the placeholder
|
|
965
|
+
and the attachment's own name beside the path.
|
|
764
966
|
|
|
765
967
|
Geometry comes in one of two fields:
|
|
766
968
|
|
|
@@ -1492,12 +1694,31 @@ skin's entry as its placeholder and name the others. Trip 7 supported it and tri
|
|
|
1492
1694
|
8 refuted it: that is the spelling the editor refuses at the door. There is no
|
|
1493
1695
|
third spelling, which is why this is a refusal rather than a naming scheme.
|
|
1494
1696
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1697
|
+
What to know about it, and nothing to author:
|
|
1698
|
+
|
|
1699
|
+
- **The renderer accepts this shape too, and that is measured rather than
|
|
1700
|
+
assumed.** `spine-html@0.4.1` resolves a part in two steps and neither one
|
|
1701
|
+
reads a placeholder or an attachment name: `DomTexture.js:78,102` builds its
|
|
1702
|
+
image map with `put(atlasRegion.name, …)` over every region of the atlas, and
|
|
1703
|
+
`SpineHtmlRenderer.js:172` reads it back as
|
|
1704
|
+
`const regionImage = region && this.regionImages.get(region.name)`, where
|
|
1705
|
+
`region` came off the attachment — which `AtlasAttachmentLoader` resolved
|
|
1706
|
+
through `path`. Every published version of that renderer keys the same way.
|
|
1707
|
+
⚠️ `A08` used to carry a `--profile spine-html` clause requiring a
|
|
1708
|
+
placeholder to be spelled exactly like the region it resolves to, which made
|
|
1709
|
+
this shape and a green `spine-html` **mutually exclusive** from
|
|
1710
|
+
[#567](https://github.com/firejune/rigc/issues/567) onwards; the first
|
|
1711
|
+
production rig with named skins hit it three times. That clause is retired —
|
|
1712
|
+
restated as the join the renderer actually performs it was a tautology over
|
|
1713
|
+
the resolve check beside it
|
|
1714
|
+
([#574](https://github.com/firejune/rigc/issues/574)).
|
|
1497
1715
|
- **`path` is restated, and it has to be.** `path` defaults to the attachment's
|
|
1498
1716
|
**name**, not to its placeholder, so an entry given a name and no path would
|
|
1499
|
-
resolve its texture at `zulu/patch` and find no such region.
|
|
1500
|
-
says so
|
|
1717
|
+
resolve its texture at `zulu/patch` and find no such region.
|
|
1718
|
+
`A08_REGION_NAMES_MATCH_ATTACHMENTS` says so if it is ever dropped, naming the
|
|
1719
|
+
skin, the slot, the placeholder and the path
|
|
1720
|
+
([#589](https://github.com/firejune/rigc/issues/589)); `A00_ROUNDTRIP_PARSE`
|
|
1721
|
+
reported it in the parser's own words until then, and now defers to A08.
|
|
1501
1722
|
- **Only contested placeholders are touched.** One skin filling a placeholder, or
|
|
1502
1723
|
two skins filling a slot under *different* placeholders, emit exactly what they
|
|
1503
1724
|
always did — every rig in this repository is byte-identical across the change.
|
|
@@ -1525,6 +1746,15 @@ bounding box in slot `head-bb`, and reuses `hoverglow-small` across eight slots.
|
|
|
1525
1746
|
a name shared between slots is normal and rigc leaves it alone; what #541 refused
|
|
1526
1747
|
was one slot holding two.
|
|
1527
1748
|
|
|
1749
|
+
🚨 **Once a rig has named skins, no instrument here can see them until you say
|
|
1750
|
+
which one** ([#571](https://github.com/firejune/rigc/issues/571)). `render` and
|
|
1751
|
+
`check` set no skin unless told to, so every slot resolves through the *default*
|
|
1752
|
+
skin alone and the art you just moved into `base`, `zulu` and `mike` draws
|
|
1753
|
+
nothing at all. `check` then compares blank against blank and reports a perfect
|
|
1754
|
+
`0.0000` — about the very placeholder this subsection is about. Pass
|
|
1755
|
+
`--skin <name>` to both, once per skin (**§9**); `tools/editor_roundtrip.ts`
|
|
1756
|
+
loops over every skin the build declares for the same reason.
|
|
1757
|
+
|
|
1528
1758
|
### 3.5 `constraints` — 4.3's single typed array
|
|
1529
1759
|
|
|
1530
1760
|
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
@@ -2131,7 +2361,8 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2131
2361
|
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
2132
2362
|
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
2133
2363
|
| `slot` | `attachment` | the attachment name, or `null` for "show nothing" |
|
|
2134
|
-
| `physics` | `
|
|
2364
|
+
| `physics` | `inertia`, `strength`, `damping`, `mass`, `wind`, `gravity` | `[value]` — the constraint's own tuning, keyed over time |
|
|
2365
|
+
| `physics` | `mix` | `[mix]`, **0 or more** — the constraint's authority |
|
|
2135
2366
|
| `physics` | `reset` | `null` — the key *is* the event |
|
|
2136
2367
|
| `path` | `position`, `spacing` | `[value]` — see §4.12 |
|
|
2137
2368
|
| `path` | `mix` | `[mixRotate, mixX, mixY]` — one timeline, three channels |
|
|
@@ -2141,6 +2372,42 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2141
2372
|
Translate values are **relative to the bone's setup position**; scale values are
|
|
2142
2373
|
multipliers where `1` is setup; rotation is in degrees.
|
|
2143
2374
|
|
|
2375
|
+
**A physics constraint's six tuning timelines override §4.6's table for the
|
|
2376
|
+
length of an animation.** `{ "physics": "hair", "property": "wind", "keys": […] }`
|
|
2377
|
+
is a wind that rises and falls; `damping` is how fast the jiggle settles,
|
|
2378
|
+
`strength` how hard it is pulled back, `inertia` how much of the bone's motion it
|
|
2379
|
+
carries, `mass` the weight it swings with, `gravity` the constant pull. They are
|
|
2380
|
+
the same field names §4.6 uses at rest, and one key states the whole value — not
|
|
2381
|
+
a delta from the constraint's own setting.
|
|
2382
|
+
|
|
2383
|
+
- ⚠️ **The per-key default is 0 on all six, and 1 on `mix` — not the
|
|
2384
|
+
constraint's default.** The parser opens every physics key at 0 and only
|
|
2385
|
+
`mix` reassigns it, so a key that omits its number reads 0 for `damping`, not
|
|
2386
|
+
the 0.85 §4.6 gives a constraint that states none. rigc never omits a channel,
|
|
2387
|
+
so this bites only when you compare an emitted file against an editor export,
|
|
2388
|
+
or when you read an export by hand: a `damping` key with no `value` in
|
|
2389
|
+
somebody else's file means **0**.
|
|
2390
|
+
- ⚠️ `mass` is the one whose keyed number is not what the runtime stores. The
|
|
2391
|
+
key states a mass and the pose holds `1 / mass`, so a `mass` key of `0` is an
|
|
2392
|
+
infinite inverse mass — the constraint stops moving.
|
|
2393
|
+
- **Four of the seven are bounded, and a key outside its bound is a compile
|
|
2394
|
+
error** ([#610](https://github.com/firejune/rigc/issues/610)). `mass` and
|
|
2395
|
+
`strength` must be `> 0`, `damping` must be strictly inside `(0, 1)`, and `mix`
|
|
2396
|
+
must be `0` or more. `A23_PHYSICS_CONSTRAINT_EFFECTIVE` applies the same four to
|
|
2397
|
+
a file rigc did not write, naming the animation, the constraint, the key time
|
|
2398
|
+
and the value — so the compiler is where a spec you wrote is refused, and the
|
|
2399
|
+
assertion is where an import is.
|
|
2400
|
+
- 🚫 **`inertia`, `wind` and `gravity` are bounded nowhere, and neither is the top
|
|
2401
|
+
of `mix`.** The runtime documents no range for the first three, and
|
|
2402
|
+
`PhysicsConstraintPose` documents `mix` as "a percentage (0+)" — so a negative
|
|
2403
|
+
wind is the other direction and a `mix` of `1.5` is an over-mix. Both are real
|
|
2404
|
+
and both compile.
|
|
2405
|
+
- ⚠️ **A `mix` key of exactly `0` is legal where a setup `mix` of `0` is not**, and
|
|
2406
|
+
the difference is not an inconsistency. `PhysicsConstraint.update` opens with
|
|
2407
|
+
`if (mix === 0) return;`, so muting a constraint for a stretch of an animation is
|
|
2408
|
+
what a mix timeline is for; a constraint muted *at rest* does nothing at all
|
|
2409
|
+
unless some animation rescues it, which is the silence `A23` was built for.
|
|
2410
|
+
|
|
2144
2411
|
On a track that names a `group`, one key's `v` may instead be a **map keyed by
|
|
2145
2412
|
member name**, whose entries are each exactly the `v` above — or a `derive`
|
|
2146
2413
|
model the compiler evaluates per member. §4.5.1.
|
|
@@ -2426,8 +2693,11 @@ key times. One lag, one place.
|
|
|
2426
2693
|
|
|
2427
2694
|
`name → { bone, x?, y?, rotate?, scaleX?, shearX?, inertia?, strength?, damping?,
|
|
2428
2695
|
mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
2429
|
-
`constraints` array.
|
|
2430
|
-
|
|
2696
|
+
`constraints` array. Seven of them — the six tuning numbers and `mix` — can also
|
|
2697
|
+
be **keyed over time** as `tracks` entries naming this constraint (§4.4); this
|
|
2698
|
+
table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and `damping ≥ 1`
|
|
2699
|
+
never settles — both are `A23`, here and on every timeline key that states them
|
|
2700
|
+
([#610](https://github.com/firejune/rigc/issues/610)). Every field but `bone` and `note` must be a finite
|
|
2431
2701
|
number: a non-number is rounded to `NaN` and emitted as `null`, which the runtime
|
|
2432
2702
|
reads as **zero**, so `"mass": "heavy"` used to ship a constraint that never
|
|
2433
2703
|
settles with no word from anybody (#307).
|
|
@@ -2586,6 +2856,18 @@ here was measured off a real rig. Copy the shape, not the values.
|
|
|
2586
2856
|
runtime. **Stating a flag on every key still overrides the rig** — the format
|
|
2587
2857
|
keys them per key on purpose, a bend that flips partway through is a real thing
|
|
2588
2858
|
to write, and it is what the editor's own export does.
|
|
2859
|
+
- 🔁 **A spec that came out of `ingest` (§0.3) states all three on every key, and
|
|
2860
|
+
that is not noise.** The stamping above reads a silent key as *"the rig's
|
|
2861
|
+
value"*, which is right for a spec somebody wrote; in a **decompiled** spec a
|
|
2862
|
+
silent key means *"the parser's default"*, because that is what the export the
|
|
2863
|
+
spec came from actually plays. The two readings differ exactly when the rig
|
|
2864
|
+
declares a non-default flag and the export's keys omit it — measured: an ik
|
|
2865
|
+
timeline keying only `mix` under a constraint declaring `bendPositive: false`
|
|
2866
|
+
rebuilds with `bendPositive: false` on every key if the flag is left silent,
|
|
2867
|
+
and with `true` — what the source plays — when `ingest` writes it out. So
|
|
2868
|
+
`ingest` restates every field any key of a track names, at the parser's default
|
|
2869
|
+
where the source omits one, and records a `CONSTRAINT_KEY_RESTATED` finding. On
|
|
2870
|
+
rigc's own output the two agree and the round trip is byte-identical.
|
|
2589
2871
|
- `mix` outside `0..1` is a compile error: `IkConstraintPose.mix` is documented as
|
|
2590
2872
|
a percentage. A **transform** mix is documented *unbounded*, which is why §4.10
|
|
2591
2873
|
has no such rule — the asymmetry is the runtime's, not ours.
|
|
@@ -2706,7 +2988,7 @@ Per key:
|
|
|
2706
2988
|
|
|
2707
2989
|
| Field | Meaning |
|
|
2708
2990
|
| --- | --- |
|
|
2709
|
-
| `vertices` | the run: `x, y`
|
|
2991
|
+
| `vertices` | the run: consecutive numbers copied into the deform array from the start index on, `x, y` per vertex or influence — an **odd** count is legal and ends on a lone x (§ the 📌 below). **Absent** = back to the setup pose |
|
|
2710
2992
|
| `transform` | the run stated as a **model** instead, evaluated over the attachment's own geometry — §4.11.1. Never with `vertices`, `fromVertex` or `offset` |
|
|
2711
2993
|
| `fromVertex` | which VERTEX the run starts at — rigc translates it |
|
|
2712
2994
|
| `offset` | the same start as a raw index into the deform array. Never with `fromVertex` |
|
|
@@ -2756,14 +3038,12 @@ geometry to the next one's. So a named `ease` behaves as it does anywhere, and a
|
|
|
2756
3038
|
raw `curve` is 4 numbers whose value axis runs 0..1, not the range of your vertex
|
|
2757
3039
|
offsets.
|
|
2758
3040
|
|
|
2759
|
-
rigc refuses
|
|
2760
|
-
|
|
3041
|
+
rigc refuses these, and the first is the quietest defect in the whole animation
|
|
3042
|
+
half of the format:
|
|
2761
3043
|
|
|
2762
3044
|
| You wrote | You get |
|
|
2763
3045
|
| --- | --- |
|
|
2764
3046
|
| a run that ends past the array | `the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long (4 vertices)` |
|
|
2765
|
-
| an odd `offset` | `offset 3 is odd. The deform array is x, y pairs, so an odd start puts every x of this run on a y` |
|
|
2766
|
-
| an odd number of `vertices` | `"vertices" holds 3 numbers; the deform array is x, y PAIRS` |
|
|
2767
3047
|
| `fromVertex` on a multi-bone vertex | `"fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` |
|
|
2768
3048
|
| an attachment that is not there | `slot "flat" in skin "default" has no attachment "flatt" (it has: flat)` |
|
|
2769
3049
|
| a deform on a region attachment | `a deform timeline keys the vertices of an attachment, and this one is a "region"` |
|
|
@@ -2781,15 +3061,30 @@ tail and deforms the rest of the mesh correctly, which looks nearly right, and
|
|
|
2781
3061
|
emitted file, measuring the array's length from the attachment rather than assuming
|
|
2782
3062
|
an encoding.
|
|
2783
3063
|
|
|
2784
|
-
📌 **
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
`
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
3064
|
+
📌 **A run is not required to be an even number of numbers, and it is not required
|
|
3065
|
+
to start on an even index.** Two rows stood here saying otherwise until issue
|
|
3066
|
+
#576. Nothing in either reader aligns a run to a pair: `SkeletonJson` does
|
|
3067
|
+
`Utils.arrayCopy(vertices, 0, deform, offset, vertices.length)` — a raw copy, at
|
|
3068
|
+
the raw index the key gives — and `SkeletonBinary` reads a count and a start and
|
|
3069
|
+
fills `for (let v = start; v < end; v++)`. So a run may begin and end mid-pair,
|
|
3070
|
+
which is exactly what an editor's trimmed delta run looks like: `spineboy-pro`'s
|
|
3071
|
+
`hoverboard-board` key starts at 1 and carries 147 numbers of a 148-long array,
|
|
3072
|
+
the whole delta minus one leading zero.
|
|
3073
|
+
|
|
3074
|
+
⭐ **An odd run's last number is an x with no y beside it, and that is a
|
|
3075
|
+
statement, not an accident.** It moves that vertex in x and leaves its y at the
|
|
3076
|
+
setup value, because the parser copies your numbers and touches nothing else. It
|
|
3077
|
+
is also the reason the rule could not stay as an authoring convenience: padding a
|
|
3078
|
+
`0` to make the run even *changes what plays* wherever that setup y is non-zero,
|
|
3079
|
+
so there is no second spelling of such a key — refusing it would mean no
|
|
3080
|
+
transcription of that file can be written in this spec at all. `A35` reached the
|
|
3081
|
+
same conclusion from the other side in issue #262, and between that fix and this
|
|
3082
|
+
one the two halves of rigc disagreed about what the format holds.
|
|
3083
|
+
|
|
3084
|
+
⚠️ What you lose with those rows is a typo filter: `offset: 3` written where
|
|
3085
|
+
`fromVertex: 3` was meant now compiles. It always half-did — `offset: 4` meant as
|
|
3086
|
+
vertex 4 lands on vertex 2 and was never refused — so read the field name twice.
|
|
3087
|
+
`offset` is an index into the deform array; `fromVertex` is a vertex.
|
|
2793
3088
|
|
|
2794
3089
|
🖼️ **Worked examples, and they use a deform for four different things** — all
|
|
2795
3090
|
four are repository material rather than part of the published package, so the
|
|
@@ -3096,6 +3391,13 @@ keys were passed over and how many reversed triangles nothing gated:
|
|
|
3096
3391
|
8 reversed triangle(s) nothing gates <- A39 counts them as deformKeysNotDrawn
|
|
3097
3392
|
```
|
|
3098
3393
|
|
|
3394
|
+
⭐ **On the "shows another attachment" half, that sentence also names the skin the
|
|
3395
|
+
pose was taken in** — `the slot shows attachment "away" at this time, with skin
|
|
3396
|
+
"suit" worn, not this mesh …` — because what a slot shows is resolved through the
|
|
3397
|
+
worn skin first and `defaultSkin` second, so the reading is only meaningful beside
|
|
3398
|
+
the dress it was taken in (§4.11.5). The alpha half carries no such clause: an
|
|
3399
|
+
alpha is read off the pose and no skin is in it.
|
|
3400
|
+
|
|
3099
3401
|
🚫 **What it does not print, and why — `coverage`.** A deform **cannot move
|
|
3100
3402
|
coverage.** That figure is rasterised from the attachment's **uvs** against the
|
|
3101
3403
|
part's alpha, and a deform moves positions and never uvs, so it is identical at
|
|
@@ -3371,6 +3673,70 @@ records that, so a slider-applied animation is measured in its slider frames onl
|
|
|
3371
3673
|
|
|
3372
3674
|
---
|
|
3373
3675
|
|
|
3676
|
+
### 4.11.5 Which skin a deform key is measured in — the one it is keyed on
|
|
3677
|
+
|
|
3678
|
+
§4.11 opens on the triple: a deform timeline is the only one keyed on
|
|
3679
|
+
**skin / slot / attachment**. So the skin is not context around the key, it is a
|
|
3680
|
+
third of the key's own address — and every pose `A39` and the `DEFORM` block take
|
|
3681
|
+
is now taken with **that skin worn**
|
|
3682
|
+
([#583](https://github.com/firejune/rigc/issues/583)). You do not ask for it and
|
|
3683
|
+
there is no flag: the skin comes out of the timeline.
|
|
3684
|
+
|
|
3685
|
+
🚨 **It used to wear nothing at all**, which is `spine-core`'s own initial state
|
|
3686
|
+
and the same one `check` reports as `no skin set (the default skin alone)`
|
|
3687
|
+
(§9) — every slot resolved through `SkeletonData.defaultSkin` and nothing else.
|
|
3688
|
+
Move a deformed mesh into a named skin, which the format not only allows but keys
|
|
3689
|
+
the timeline on, and the slot showed **no attachment**. This is what
|
|
3690
|
+
`gallery/squash`'s ball printed with its one mesh moved into a skin `suit`, on
|
|
3691
|
+
every one of its five keys, before #583:
|
|
3692
|
+
|
|
3693
|
+
```
|
|
3694
|
+
DEFORM bounce suit/ball/ball key 1 t=0.340000 transform affine scale=[0.88, 1.16]
|
|
3695
|
+
skipped A39 reads no winding off this key: the slot shows no attachment at all at this
|
|
3696
|
+
time, not this mesh, so the runtime applies no deform to it here and draws
|
|
3697
|
+
none of it — a triangle that draws no pixels cannot draw them backwards
|
|
3698
|
+
```
|
|
3699
|
+
|
|
3700
|
+
— `A39` went **PASS → SKIP** on a rig whose only edit was which skin one mesh sat
|
|
3701
|
+
in, in a sentence that reads as a verdict on that rig. Worn, the same build
|
|
3702
|
+
reports every figure the default-skin one does, to the last digit, and `A39` gates
|
|
3703
|
+
it: the two `DEFORM` blocks differ in the skin of the triple and in nothing else.
|
|
3704
|
+
|
|
3705
|
+
**Two things follow, and one of them is not about art:**
|
|
3706
|
+
|
|
3707
|
+
- ⚠️ **A "nothing is drawn" sentence now names the skin it was measured in** —
|
|
3708
|
+
`the slot shows attachment "away" at this time, with skin "suit" worn, not this
|
|
3709
|
+
mesh …`. It is on the *shows-something-else* branch only, because that is the
|
|
3710
|
+
branch a skin decides; an alpha is read off the pose and has no skin in it. The
|
|
3711
|
+
clause is what separates "this slot is empty" from "this slot is empty in the
|
|
3712
|
+
dress this mesh lives in", and only the second is a measurement.
|
|
3713
|
+
- ⭐ **A `skin: true` bone or constraint (§3.4.1) is switched on too.**
|
|
3714
|
+
`Skeleton.setSkin` calls `updateCache`, which leaves a `skinRequired` bone
|
|
3715
|
+
inactive and a `skinRequired` constraint out of the update cache under any skin
|
|
3716
|
+
that does not list it. So a **slider** dressed into the same skin as the mesh
|
|
3717
|
+
it drives used to be measured with itself switched off, and failed in two ways
|
|
3718
|
+
depending on its `local` flag: a world-read property never moved, so the frame
|
|
3719
|
+
was reported as *"played on a track"* — an animation a slider is the only way
|
|
3720
|
+
into (§4.11.4) — while a local-read one kept its mapping but never left
|
|
3721
|
+
`SliderPose.time` 0, so every key came back as *"at a time no dial selects"*.
|
|
3722
|
+
Both now reach their own key times.
|
|
3723
|
+
|
|
3724
|
+
⛔ **What it does not do is try every skin.** `Attachment.timelineSlots` lets one
|
|
3725
|
+
deform reach a second slot — a linked mesh with `inheritTimelines` — and that copy
|
|
3726
|
+
may live in a skin of its own; `setSkin` dresses the whole skeleton at once, so
|
|
3727
|
+
under the timeline's own skin that copy resolves to nothing and is reported as
|
|
3728
|
+
drawing nothing, which is exactly what the runtime does with that same skin on.
|
|
3729
|
+
Posing the key again under some other skin would be rigc choosing which dress the
|
|
3730
|
+
character is wearing, and which skin is worn is yours. What it owes you instead is
|
|
3731
|
+
the skin in the sentence, so the reading is never mistaken for a verdict.
|
|
3732
|
+
|
|
3733
|
+
📌 **`check` is the other half of the same question and it is not automatic**:
|
|
3734
|
+
a rig whose art lives in named skins is rendered and checked **once per skin**,
|
|
3735
|
+
with `--skin` on both sides (§9). The difference is where the name comes from —
|
|
3736
|
+
a deform timeline carries its own skin and a frame set does not.
|
|
3737
|
+
|
|
3738
|
+
---
|
|
3739
|
+
|
|
3374
3740
|
### 4.12 `path` and `slider` timelines — tracks, not their own groups
|
|
3375
3741
|
|
|
3376
3742
|
Unlike `ik` and `transform`, these two are ordinary `tracks` entries: the format
|
|
@@ -3509,9 +3875,13 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3509
3875
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
3510
3876
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
3511
3877
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
3512
|
-
| `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 |
|
|
3878
|
+
| `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 |
|
|
3879
|
+
| `the setup pose shows attachment "A" on slot "X", which no skin and no manifest part fills` | §3.3 — the slot is emitted empty, so `A` resolves to nothing. Give the slot an attachment (a skin entry or a manifest part), or state the setup pose as `null` |
|
|
3513
3880
|
| `a region needs width and height — give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
|
|
3514
3881
|
| `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 |
|
|
3882
|
+
| `"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 |
|
|
3883
|
+
| `attachment type "X" is not one of the 7 the Spine 4.3 format defines (…)` | §6 — a name the format does not have. Not a deferral, and not something rigc will grow: fix the spelling (`sequence` is a key on a region or a mesh, not a type) |
|
|
3884
|
+
| `this attachment is a "linkedmesh"` / `"point"` … `rigc does not emit it yet` | §6 — a construct the format has and rigc does not write. The message says what it would carry; SPEC_COVERAGE part 1-6 is the row it reads from |
|
|
3515
3885
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
3516
3886
|
| `hull vertices must come first; vertex i is on the boundary and vertex j is not. The triangles' outline runs …: list those K vertices first, in that order, then the M interior vertices` | §3.4 — renumber the vertices: the printed walk first, then the interior |
|
|
3517
3887
|
| `hull vertices must trace the outline in order; the triangles' outline runs …, so vertex a has to follow vertex b in the list, and vertex c does` | §3.4 — renumber along the printed walk |
|
|
@@ -3525,6 +3895,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3525
3895
|
| `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` |
|
|
3526
3896
|
| `animation "A" keys unknown bone "X"` | the track's `bone` is not in the rig |
|
|
3527
3897
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
3898
|
+
| `animation "A" physics constraint "C" mass key at t=… is 0 (massInverse Infinity); must be > 0 — …` | §4.4 — a keyed physics value the runtime cannot use. The message names the bound and the `PhysicsConstraint.js` lines that make it one: `mass` and `strength` are `> 0`, `damping` is inside `(0, 1)`, `mix` is `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)) |
|
|
3528
3899
|
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
3529
3900
|
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
3530
3901
|
| `key times must strictly increase (at t=…)` | including after `lag` and `stagger` |
|
|
@@ -3541,7 +3912,9 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3541
3912
|
| `animation "A" group "G" P (t=…): derive <kind> projects onto "scaleX" and member "M" states depth −z` | §4.5.1 — a foreshortening needs the part in front of the axis; a part behind it takes the displacement projection |
|
|
3542
3913
|
| `animation "A" group "G" P (t=…): derive <kind> states carried=… on a "scalex" track` | §4.5.1 — the foreshortening reads no depth difference, so `carried` belongs on the displacement track |
|
|
3543
3914
|
| `animation "A" group "G" P (t=…): derive <kind> turns member "M" … past its own edge` | §4.5.1 — `cos(α − t) ≤ 0` would mirror the drawing; the turn is past what this construction carries (FACE §8) |
|
|
3544
|
-
| `no stage size: give the rig spec a \`skeleton.width\`/\`skeleton.height\`` | §3.1 |
|
|
3915
|
+
| `no stage size: give the rig spec a \`skeleton.width\`/\`skeleton.height\`` | §3.1 — or state `"width": null, "height": null` if the skeleton you are transcribing declares no stage |
|
|
3916
|
+
| `"skeleton" states width: null and a height of N` / `states height: null and a width of N` | §3.1 — a stage has both extents or neither |
|
|
3917
|
+
| `"skeleton" declares no stage (width: null, height: null) and still states x` | §3.1 — an origin for a box that is not there |
|
|
3545
3918
|
| `N mesh slot(s) emitted but the rig "X" allows 0 — a mesh rigc GENERATED counts against \`invariants.meshSlots\`…` | §3.4 / §3.7 — a rig that invokes a mesh generator declares the budget; undeclared is zero. Add `"invariants": { "meshSlots": N, "meshTriangles": M }` |
|
|
3546
3919
|
| `drawOrder at t=…: slot "X" is not one this rig emits` / `is offset twice in one key` / `puts it at N, outside the … emitted slots` | §4.7 |
|
|
3547
3920
|
| `events at t=…: event "X" is not declared in the rig spec's "events" block` | declare it in the rig spec (§3.6), or fix the name |
|
|
@@ -3556,7 +3929,6 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3556
3929
|
| `ik constraint "X": key 0 names "softness" and key 1 (t=…) does not` | §4.9 — every key is read with its own default, so state the field on every key or on none |
|
|
3557
3930
|
| `ik constraint "X" (t=…): mix is 1.5, outside 0..1` | §4.9 — an IK mix is a percentage; a transform mix is unbounded |
|
|
3558
3931
|
| `deform …: the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long` | §4.11 — shorten the run or move its start; the parser would drop the tail in silence |
|
|
3559
|
-
| `deform … (t=…): offset 3 is odd` | §4.11 — in **this spec** a run starts on an even index, or names its vertex with `fromVertex`. Not a validity rule; the runtime allows either (issue #262) |
|
|
3560
3932
|
| `deform …: "fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` | §4.11 — key the control bone, or write bind-space pairs and start with `offset` |
|
|
3561
3933
|
| `deform …: slot "X" in skin "default" has no attachment "Y" (it has: …)` | §4.11 — fix the placeholder name |
|
|
3562
3934
|
| `deform … (t=…): the key carries both a "transform" and a "vertices" run` | §4.11.1 — a model and a table are two answers to one question; drop one |
|
|
@@ -3600,6 +3972,18 @@ The report prints one line per assertion:
|
|
|
3600
3972
|
- **PASS** — it ran and held.
|
|
3601
3973
|
- **SKIP** — it had *nothing to look at*, and the reason says what was missing. A
|
|
3602
3974
|
skip is never folded into the pass count.
|
|
3975
|
+
🚨 **"Nothing to look at" includes a subject the skeleton does not carry**
|
|
3976
|
+
([#580](https://github.com/firejune/rigc/issues/580)). A rule named
|
|
3977
|
+
⟨subject⟩_⟨property⟩ — `A03_REGION_WIDTH_HEIGHT_FINITE`,
|
|
3978
|
+
`A04_MESH_TRIANGLES_AND_ENCODING`, `A23_PHYSICS_CONSTRAINT_EFFECTIVE` — walks
|
|
3979
|
+
that subject and measures each member, so a skeleton with no region, no mesh or
|
|
3980
|
+
no physics constraint gives it nothing and it **SKIPs**, naming the subject. A
|
|
3981
|
+
rule named NO_⟨construct⟩ — `A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS`,
|
|
3982
|
+
`A02_NO_BONE_TRANSFORM_KEY`, `A11_NO_CLIPPING_ATTACHMENTS`,
|
|
3983
|
+
`A12_NO_DARK_COLOR`, `A14_NO_FULL_FRAME_MESH` — asks how many of a thing the
|
|
3984
|
+
artifact carries, and **zero is the answer**, so it **PASSes**. ⇒ On a foreign
|
|
3985
|
+
export the SKIP list is a free inventory of what the skeleton does not contain,
|
|
3986
|
+
and the PASS list never contains a rule that looked at nothing.
|
|
3603
3987
|
- **PROF** — the profile you chose does not carry that kind of rule. Two kinds sit
|
|
3604
3988
|
outside `spine`, not one: the renderer rules **and** the archetype rules. The
|
|
3605
3989
|
exclusion is checked before the assertion's body runs, so an archetype rule with
|
|
@@ -3609,48 +3993,70 @@ The report prints one line per assertion:
|
|
|
3609
3993
|
- **FAIL** — the detail names the object, the value found and the value required.
|
|
3610
3994
|
That detail is the instruction; the table below says which file to change.
|
|
3611
3995
|
|
|
3996
|
+
**Every assertion leaves exactly one row, on every run.** The four kinds partition
|
|
3997
|
+
the registry, so the rows you can see are the whole of what was asked — there is
|
|
3998
|
+
no fifth state in which a rule quietly did not come up. The last line of the
|
|
3999
|
+
report states that partition, and every figure in it is a count of *assertions*
|
|
4000
|
+
you can reproduce by counting rows:
|
|
4001
|
+
|
|
4002
|
+
```
|
|
4003
|
+
.. <N> assertions: <M> measured (<P> passed, <F> failed), <S> skipped, <X> not in profile "<profile>"
|
|
4004
|
+
```
|
|
4005
|
+
|
|
4006
|
+
`<M>` is `<P> + <F>`, and `<N>` is all four added together. ⚠️ `<F>` counts
|
|
4007
|
+
assertions and not `FAIL` lines: one assertion that finds six wrong vertices
|
|
4008
|
+
prints six rows and is one failure here.
|
|
4009
|
+
|
|
4010
|
+
🚨 **When `A00_ROUNDTRIP_PARSE` fails, read the report as a report about A00 and
|
|
4011
|
+
nothing else.** Most of the rules read the skeleton or the atlas that A00 loads,
|
|
4012
|
+
and with no parse there is nothing for them to look at — so they report `SKIP`
|
|
4013
|
+
naming that, *the round trip did not produce a skeleton to measure* or *…an atlas
|
|
4014
|
+
to measure*, and the summary's `<S>` goes up while `<M>` collapses. A run in that
|
|
4015
|
+
state is not a rig that nearly passed; it is a rig that was measured on one rule.
|
|
4016
|
+
Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
4017
|
+
|
|
3612
4018
|
| Assertion | Profile | What tripped it, and where to fix it |
|
|
3613
4019
|
| --- | --- | --- |
|
|
3614
|
-
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas.
|
|
4020
|
+
| `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 ([#568](https://github.com/firejune/rigc/issues/568)). ⚠️ **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 ([#589](https://github.com/firejune/rigc/issues/589)) |
|
|
3615
4021
|
| `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 |
|
|
3616
4022
|
| `A02_NO_BONE_TRANSFORM_KEY` | both | a bone uses 4.2's `transform`; rename it `inherit` in the rig spec |
|
|
3617
|
-
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size — the attachment has no `image` and no `width`/`height
|
|
3618
|
-
| `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) |
|
|
3619
|
-
| `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** |
|
|
3620
|
-
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk. Under `spine-html` also: `pma`, rotation, and a page that is neither **one part covering it exactly** (the unpacked convention) nor a **tiling** — a page whose regions all sit inside it and none of which overlap ([#266](https://github.com/firejune/rigc/issues/266)). A packed atlas therefore gates under this profile; what the message names is the region that runs off its page, or the pair that shares texels |
|
|
3621
|
-
| `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 |
|
|
3622
|
-
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both
|
|
4023
|
+
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size — the attachment has no `image` and no `width`/`height`. **SKIP** when the skeleton carries no region attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4024
|
+
| `A04_MESH_TRIANGLES_AND_ENCODING` | both | authored mesh geometry: triangle count not a multiple of 3, an index out of range, or a `vertices` length that disagrees with `uvs` (the weighted/unweighted trap) **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4025
|
+
| `A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel**. **SKIP** when no animation carries a timeline at all ([#580](https://github.com/firejune/rigc/issues/580)). Timelines with no `curve` on any key still PASS: every timeline name is checked against the channel table whether or not a curve sits on one |
|
|
4026
|
+
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk. Under `spine-html` also: `pma`, rotation, and a page that is neither **one part covering it exactly** (the unpacked convention) nor a **tiling** — a page whose regions all sit inside it and none of which overlap ([#266](https://github.com/firejune/rigc/issues/266)). A packed atlas therefore gates under this profile; what the message names is the region that runs off its page, or the pair that shares texels. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4027
|
+
| `A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file. ⚠️ An atlas with **no page block at all** — no non-blank line — is not one of those: its subject is absent, so this reports **SKIP** naming the byte count it read, and so do the four rules below whose subject is a page ([#608](https://github.com/firejune/rigc/issues/608)). A rig whose skins need no art writes exactly that file (§3.4), and before #608 this row refused it with two findings naming a page block that was not there. What an empty atlas does **not** excuse is an attachment that wants a region out of it — that is `A08` |
|
|
4028
|
+
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. Until [#589](https://github.com/firejune/rigc/issues/589) they were unreachable: the loader threw first and the miss arrived as `A00_ROUNDTRIP_PARSE`. There is no `spine-html` clause here any more — a placeholder is free to differ from the region its `path` names ([#574](https://github.com/firejune/rigc/issues/574)) **SKIP** when no attachment names a region *and* the atlas declares none — both of its subjects at once ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
3623
4029
|
| `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 |
|
|
3624
|
-
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale |
|
|
4030
|
+
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale. **SKIP** when the skeleton carries no animation ([#580](https://github.com/firejune/rigc/issues/580)): the NaN is produced by stepping, and a static rig is never stepped — the same subject `A09` skips on |
|
|
3625
4031
|
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
3626
4032
|
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
3627
|
-
| `A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither — which means *unmeasured*, not that the budget is inert: the same `meshSlots` is a **compile-time** refusal for rigc's own generators, before the gate (§3.7, issue #274) |
|
|
3628
|
-
| `A14_NO_FULL_FRAME_MESH` | renderer | a mesh spans the whole stage — a full-frame canvas that can never dirty-skip |
|
|
3629
|
-
| `A15_IDLE_NO_MESH_BONE_KEYS` | renderer | the `idle` animation keys a bone that drives a mesh, directly or as a control bone |
|
|
4033
|
+
| `A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither — which means *unmeasured*, not that the budget is inert: the same `meshSlots` is a **compile-time** refusal for rigc's own generators, before the gate (§3.7, issue #274) **SKIP** also when the rig budgets **only** triangles and the skeleton carries no mesh ([#580](https://github.com/firejune/rigc/issues/580)). A declared slot budget still PASSes there, because zero mesh slots is a count measured against a ceiling |
|
|
4034
|
+
| `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* |
|
|
4035
|
+
| `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 ([#568](https://github.com/firejune/rigc/issues/568)) |
|
|
3630
4036
|
| `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`) |
|
|
3631
|
-
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out` |
|
|
4037
|
+
| `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)) |
|
|
3632
4038
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
3633
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)) |
|
|
3634
|
-
| `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, or a binding at weight 0 |
|
|
4039
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)) **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4040
|
+
| `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, or a binding at weight 0. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
3635
4041
|
| `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 |
|
|
3636
|
-
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count |
|
|
3637
|
-
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, is muted by `mix: 0`, has `mass: 0`, has `strength: 0`, or has `damping` outside `(0, 1)` so it never settles |
|
|
3638
|
-
| `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 |
|
|
4042
|
+
| `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)) |
|
|
4043
|
+
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, is muted by `mix: 0`, has `mass: 0`, has `strength: 0`, or has `damping` outside `(0, 1)` so it never settles — **at rest, and on every physics timeline key** ([#610](https://github.com/firejune/rigc/issues/610)). The timeline arm reads each key through the runtime's own `PhysicsConstraint*Timeline.set`, so a keyed `mass` is judged as the `massInverse` it becomes, and the detail names the animation, the constraint, the key time, the value and the bound. One difference between the two arms, and the runtime is the reason for it: a **key** of `mix: 0` is accepted, because `update` opens with `if (mix === 0) return;` and muting a constraint for a stretch is what a mix timeline is for — the editor's own `sack-pro` example keys it there on 24 of its 36 mix keys. `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. **SKIP** when the skeleton declares no physics constraint ([#580](https://github.com/firejune/rigc/issues/580)) — the same sentence `A36` and `A37` have always printed for their own constraint types |
|
|
4044
|
+
| `A24_AXIS_SPACE_STROKE` | archetype | a bone under the rig's `axisBone` was keyed with a screen-space Y component, or the axis bone itself was keyed. **SKIP** when the rig declares no axis bone, and also when no animation keys that bone or anything under it ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
3639
4045
|
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
3640
|
-
| `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not
|
|
3641
|
-
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | renderer | a single-region page whose region name is not the PNG's basename |
|
|
4046
|
+
| `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not the rig's slot table — a slot is out of order, is not in the table at all, or is in the table and missing from the skeleton (§3.3). **SKIP** when the rig declares no canonical slot order. ⚠️ A skeleton with **no** slot beside a rig that declares some is **not** a skip, and it is the one rule in this family where an empty loop is not a vacuous pass ([#580](https://github.com/firejune/rigc/issues/580)): the completeness clause reads it as every declared slot lost and names them, which is the maximal case of what [#575](https://github.com/firejune/rigc/issues/575) filed |
|
|
4047
|
+
| `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 ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
3642
4048
|
| `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 |
|
|
3643
4049
|
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
3644
4050
|
| `A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | the animation drives past the measured containment ceiling, or scales a bone in the axis subtree |
|
|
3645
4051
|
| `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 |
|
|
3646
4052
|
| `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 |
|
|
3647
4053
|
| `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 |
|
|
3648
|
-
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik`, `transform`, `path` or `slider` timeline names a constraint the skeleton does not declare, names one of another type, or carries no keys at all (§4.9, §4.10, §4.12). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. **SKIP** when no animation carries one |
|
|
4054
|
+
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik`, `transform`, `path`, `physics` or `slider` timeline names a constraint the skeleton does not declare, names one of another type, or carries no keys at all (§4.4, §4.9, §4.10, §4.12). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. **SKIP** when no animation carries one |
|
|
3649
4055
|
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | both | a deform key's run runs past the end of the attachment's deform array, holds a non-finite number, has an empty key array, or names a skin/slot/attachment triple that does not resolve (§4.11). The overrun is the quiet one — the parser copies into a `Float32Array` and drops the tail. ⛔ It does **not** require pair alignment: the runtime has no such rule and a trimmed editor run legitimately starts and ends mid-pair (§4.11, issue #262). **SKIP** when no animation carries a deform timeline |
|
|
3650
4056
|
| `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 its `mix` (§3.5.1). The first is the quiet one: `update()` returns on its first line and the constraint reports mixes it never applies. **SKIP** when the skeleton declares no path constraint |
|
|
3651
4057
|
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` (§3.5.2). **SKIP** when the skeleton declares no slider |
|
|
3652
4058
|
| `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` |
|
|
3653
|
-
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. **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 |
|
|
4059
|
+
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. ⚠️ And the **skin** it poses in is the one the timeline is keyed on (§4.11.5, issue #583), since a deform's address is a `skin / slot / attachment` triple: the pose wears that skin, which also switches on any `skin: true` bone or constraint it activates, and the "nothing is drawn" sentence names the skin it was read under. **SKIP** when no animation carries a deform timeline, when nothing keyed has triangles, when every mesh keyed is exempt, when every key measured draws no pixels or is unreachable *and no span between them folds where anything is drawn*, or when there is no rig info at all |
|
|
3654
4060
|
| `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 additive (a slot colour, an attachment swap, a draw order, a sequence), where `"additive": true` is not the fix and one of the two has to go. The detail names the bone or slot and the property, every slider keying it in `constraints` order with its flag, and which one wins today. Three 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, and two sliders on different properties. **SKIP** when fewer than two sliders are at full authority; a PASS means two were compared |
|
|
3655
4061
|
| `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` | both | a physics constraint driving a component the **Spine editor** cannot hold, on a rig that declared `invariants.editorRoundTrip` (§3.7). The editor's physics model holds `x` and `y` only, with no cap on how many at once, so a constraint driving `rotate`, `scaleX` or `shearX` is imported, exported and handed back driving **nothing** — measured over three rigs and twelve constraints with the predictions written first ([#540](https://github.com/firejune/rigc/issues/540)). The detail names the constraint and each component. ⚠️ rigc's own output is correct — every runtime plays a rotation jiggle — so this is opt-in and the default is *not* silence: on a rig that declares nothing it **SKIPs**, and the SKIP names the constraint and the component anyway, so an author learns without having asked. Fix by driving the constraint in `x`/`y`, or by dropping the declaration if the rig never goes near the editor. Disjoint from `A23_PHYSICS_CONSTRAINT_EFFECTIVE` by construction: A23 refuses an **empty** driven set, which is what comes back from the editor, and this refuses a non-empty one that will not survive going in. **SKIP** also when the rig declares the editor and carries no physics constraint at all |
|
|
3656
4062
|
|
|
@@ -3661,19 +4067,30 @@ clauses are gated by profile.
|
|
|
3661
4067
|
|
|
3662
4068
|
## 6. What rigc will refuse — do not spend a loop on these
|
|
3663
4069
|
|
|
3664
|
-
|
|
3665
|
-
a **`NotImplementedError` naming the
|
|
3666
|
-
worse: an unknown attachment `type` returns `null` and the
|
|
3667
|
-
and a constraint entry with an unrecognised `type` matches no
|
|
4070
|
+
Most of these are in the Spine 4.3 format and the emitter does not write them. Each
|
|
4071
|
+
of *those* is a **`NotImplementedError` naming the construct**, because the parser's
|
|
4072
|
+
own behaviour is worse: an unknown attachment `type` returns `null` and the
|
|
4073
|
+
attachment disappears, and a constraint entry with an unrecognised `type` matches no
|
|
4074
|
+
case and vanishes.
|
|
3668
4075
|
|
|
3669
|
-
|
|
3670
|
-
**neither of
|
|
4076
|
+
A deferral carries its reason, and the reason is the same one in every deferred row:
|
|
4077
|
+
**neither of those types appears anywhere in the benchmark corpus** (SPEC_COVERAGE
|
|
3671
4078
|
parts 3-1 and 4-2), so neither is on the ladder's critical path. The message
|
|
3672
4079
|
says so, because a deferral without its reason is a wall rather than a work item.
|
|
3673
4080
|
|
|
4081
|
+
⚠️ **A spelling the format does not have is a different refusal and says so.**
|
|
4082
|
+
`sequence` is not an attachment type, and a `"type"` that is `null` is not an absent
|
|
4083
|
+
one — telling either author that "rigc does not emit it yet" promises work that will
|
|
4084
|
+
never be done, on a map the parser would have dropped in silence. Those rows below
|
|
4085
|
+
are `CompileError`s, and they name what the format actually defines
|
|
4086
|
+
([#577](https://github.com/firejune/rigc/issues/577)).
|
|
4087
|
+
|
|
3674
4088
|
| You wrote | You get |
|
|
3675
4089
|
| --- | --- |
|
|
3676
|
-
| attachment `type` of `point` or `linkedmesh` | `attachment
|
|
4090
|
+
| attachment `type` of `point` or `linkedmesh` | `this attachment is a "linkedmesh" — a mesh that takes its geometry from another mesh instead of stating any — a region/mesh head, then "source" …. rigc does not emit it yet, deliberately: it emits region, mesh, boundingbox, clipping, path, and neither a point nor a linked mesh appears anywhere in the benchmark corpus …` — the message names the **construct**, not just its type string, and part 1-6 is where the sentence comes from |
|
|
4091
|
+
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) | the same refusal, prefixed `(a mesh carrying "source" is one)`. The two spellings share one parser branch and `source` is what decides between them (SPEC_COVERAGE part 1-6), so `source` on a mesh is a linked mesh whatever `type` says. It used to be refused as *2 keys this compiler does not read: "source", "skin" … fix the spelling or remove it*, whose remedy destroys the construct ([#577](https://github.com/firejune/rigc/issues/577)) |
|
|
4092
|
+
| 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.) |
|
|
4093
|
+
| `"type": null` | `"type" is null, which is not a name. … PRESENT-and-null is not absent: getValue(map, "type", "region") takes the default only when the key is missing, so this map matches no case, readAttachment returns null, and the attachment is dropped from the skeleton without a word. Remove the key, or name a type.` Leaving the key **out** is legal and reads as `region`; writing it as `null` is not the same thing ([#577](https://github.com/firejune/rigc/issues/577)) |
|
|
3677
4094
|
| 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 |
|
|
3678
4095
|
| a path attachment's `lengths` | `"lengths" is not authored — rigc measures the setup arc length of each curve off the geometry` (§3.4). Not a deferral: a second copy of a number the vertices already fix |
|
|
3679
4096
|
| a `deform` timeline on a path attachment | `a path attachment does have a vertex array, and rigc does not key it yet` — the format allows it and an animated track is a real idiom, but a deformed path invalidates the `lengths` a `constantSpeed: false` traversal reads. Move the curve by posing the bones its vertices are bound to |
|
|
@@ -3702,19 +4119,35 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
3702
4119
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
3703
4120
|
Saying nothing means `spine`, so "the one you meant" is a decision either way —
|
|
3704
4121
|
the report's first line names the profile that judged it.
|
|
4122
|
+
📎 The two profiles judge attachment **naming** identically since
|
|
4123
|
+
[#574](https://github.com/firejune/rigc/issues/574): `spine-html` adds the
|
|
4124
|
+
renderer and archetype rules and has no opinion about how an attachment is
|
|
4125
|
+
spelled, so a rig whose named skins share a placeholder (§3.4.2) is green
|
|
4126
|
+
under either. Before that it was green under exactly one of them, and which
|
|
4127
|
+
one was not a property of the rig.
|
|
3705
4128
|
2. Read the `SKIP` lines. Each one is a check that did *not* run — make sure none of
|
|
3706
|
-
them is a check you were relying on.
|
|
4129
|
+
them is a check you were relying on. The summary's *measured* figure is the
|
|
4130
|
+
shortest version of this step: it is how many of the rules actually looked at
|
|
4131
|
+
anything, and it is the number to quote when you say a rig gated green.
|
|
3707
4132
|
⚠️ Under `--profile spine` a foreign skeleton usually produces **no SKIP lines
|
|
3708
4133
|
at all**, and that is not a clean bill of health. The archetype assertions are
|
|
3709
4134
|
excluded by the profile before the missing `invariants` block could make them
|
|
3710
4135
|
skip, so they come back `PROF` instead. Do not go looking for a SKIP that the
|
|
3711
4136
|
profile already accounted for; read step 3 instead.
|
|
4137
|
+
⚠️ And a **red** report is where this step matters most, which is the opposite
|
|
4138
|
+
of how it reads: if `A00` failed, most of the SKIP lines say the round trip
|
|
4139
|
+
denied them a result, and the handful of `PASS` rows beside them were measured
|
|
4140
|
+
on the raw text alone. Step 1 already sent you back; do not take anything from
|
|
4141
|
+
the rest of that report on the way ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
3712
4142
|
3. Read the `PROF` lines. They are where "was this rig held to that rule at all"
|
|
3713
4143
|
gets answered for everything the profile left out — the renderer policy *and*
|
|
3714
4144
|
the archetype rules. A green under `spine` has been held to neither; a green
|
|
3715
4145
|
under `spine-html` has been held to both.
|
|
3716
|
-
4. Run `explain` and read the slots table: every slot you declared
|
|
3717
|
-
(§3.3
|
|
4146
|
+
4. Run `explain` and read the slots table: every slot you declared **is** there
|
|
4147
|
+
(§3.3 — `A26_SLOT_DRAW_ORDER` holds that, and a slot nothing fills reads
|
|
4148
|
+
`setup=null attachments=[]`), so what this step is for is the two things the
|
|
4149
|
+
gate cannot check — that the order is the one you meant, and that each slot
|
|
4150
|
+
shows the setup attachment you meant.
|
|
3718
4151
|
5. If you were given **frames**, run `check` and read the table (§9). Steps 1–4 are
|
|
3719
4152
|
all about validity and structure; none of them can tell you the animation is
|
|
3720
4153
|
wrong, and this is the step that can. Do it before step 6, not after — `bench`
|
|
@@ -4196,10 +4629,56 @@ your animation is called something the frame directory is not, `--framing shared
|
|
|
4196
4629
|
to fit one framing across every set instead of one each,
|
|
4197
4630
|
`--texture-from <atlas>` to attribute how much of the MAE is texture resampling
|
|
4198
4631
|
rather than the rig (**§9.2**'s atlas floor — and note that it is *not* `--atlas`,
|
|
4199
|
-
which re-seats your geometry on that atlas's packing), `--
|
|
4632
|
+
which re-seats your geometry on that atlas's packing), `--skin <name>` to pose
|
|
4633
|
+
your candidate under one of its skins (below), `--all-frames` to list
|
|
4200
4634
|
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
4201
4635
|
per-slot report.
|
|
4202
4636
|
|
|
4637
|
+
🚨 **What `check` certifies is the DEFAULT skin, unless you pass `--skin`**
|
|
4638
|
+
(issue #571). With no `--skin` no skin is set at all, which is `spine-core`'s own
|
|
4639
|
+
initial state: every slot resolves through `SkeletonData.defaultSkin` alone, and a
|
|
4640
|
+
slot whose art lives only in a named skin draws **nothing** — on both sides, since
|
|
4641
|
+
the reference frames came out of the same renderer. A multi-skin rig checked that
|
|
4642
|
+
way compares blank against blank and reports `MAE mean 0.00`, which reads like the
|
|
4643
|
+
best possible answer and is a measurement of no art at all. On a single-skin rig
|
|
4644
|
+
this is the whole rig and there is nothing to pass.
|
|
4645
|
+
|
|
4646
|
+
So a multi-skin rig is checked **once per skin**, and both sides are rendered
|
|
4647
|
+
under the same one:
|
|
4648
|
+
|
|
4649
|
+
```bash
|
|
4650
|
+
bun cli.ts render --candidate path/to/spine --skin patch --out frames/patch
|
|
4651
|
+
bun cli.ts check --candidate path/to/spine --frames frames/patch --skin patch
|
|
4652
|
+
```
|
|
4653
|
+
|
|
4654
|
+
Three things keep that honest, and none of them is a convention you have to
|
|
4655
|
+
remember:
|
|
4656
|
+
|
|
4657
|
+
- `render --skin` writes the name into `frames.json`, so a frame set says which
|
|
4658
|
+
picture of the rig it is. A set rendered with **no** `--skin` records nothing,
|
|
4659
|
+
because "no skin was set" and "this file predates the field" are the same bytes
|
|
4660
|
+
on disk and neither is a claim.
|
|
4661
|
+
- `check` reads that back. A candidate posed under a **different** skin from the
|
|
4662
|
+
one the frames record — or under none, where the frames name one — is
|
|
4663
|
+
**refused by name** rather than scored, because the number would be about the
|
|
4664
|
+
difference between two skins. Frames that record nothing cannot be checked, and
|
|
4665
|
+
the report says so in a `⚠️` note instead of implying agreement.
|
|
4666
|
+
- The report header names the skin on both sides on every run, `--skin` or not:
|
|
4667
|
+
`skin candidate patch frames patch`, or `candidate no skin set (the default
|
|
4668
|
+
skin alone)`. `check.json` carries the same two under `skin` and
|
|
4669
|
+
`referenceSkin`.
|
|
4670
|
+
|
|
4671
|
+
A skin name the candidate does not declare is refused with the ones it does —
|
|
4672
|
+
`the candidate declares no skin "path"; it declares [default, patch, torn]`.
|
|
4673
|
+
|
|
4674
|
+
📌 **Deform measurement needs no such flag, because the timeline carries the
|
|
4675
|
+
name.** `A39` and the `DEFORM` block pose each key with the skin that key is keyed
|
|
4676
|
+
on — a deform timeline's address is a `skin / slot / attachment` triple — so a
|
|
4677
|
+
mesh in a named skin is measured in that skin without anything being passed on the
|
|
4678
|
+
command line (§4.11.5, issue #583). The difference is where the name comes from,
|
|
4679
|
+
not which command cares: `check` compares two picture sets and neither of them
|
|
4680
|
+
records a skin unless you say so, while a deform key knows its own.
|
|
4681
|
+
|
|
4203
4682
|
⭐ **A frame set may ship a contact sheet instead of every frame, and the sheet is
|
|
4204
4683
|
compared too.** A long shot does not commit 311 near-duplicate PNGs: rung 2's sets
|
|
4205
4684
|
ship `f0000.png` and `f0310.png` plus a `contact.png` holding all 311 sampled
|