spine-rigc 0.22.2 → 0.23.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 +64 -1
- package/cli.ts +205 -11
- package/docs/AUTHORING.md +357 -54
- package/docs/INGEST.md +185 -41
- package/docs/SPEC_COVERAGE.md +13 -1
- package/package.json +1 -1
- package/skills/ingest/SKILL.md +33 -11
- package/src/atlas.ts +57 -13
- package/src/check.ts +83 -1
- package/src/compile.ts +284 -58
- package/src/diff.ts +125 -2
- package/src/ingest.ts +1078 -0
- package/src/render.ts +104 -10
- package/src/rig.ts +92 -8
- package/src/types.ts +39 -7
- package/src/validate.ts +205 -27
- package/tools/editor_roundtrip.ts +172 -21
package/docs/AUTHORING.md
CHANGED
|
@@ -329,28 +329,119 @@ 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
|
|
366
|
+
# .. out /abs/path/specs
|
|
367
|
+
# .. art loose
|
|
368
|
+
# JUDGE DURATION: animation "idle" — skeleton JSON carries no duration; the largest key time (2.667) is used …
|
|
369
|
+
# LOSS PATH_LENGTHS: skin "default" slot "track" attachment "track" — the source states `lengths`; rigc RE-MEASURES it …
|
|
370
|
+
# rigc: wrote /abs/path/specs/rig.json
|
|
371
|
+
# rigc: wrote /abs/path/specs/motion.json
|
|
372
|
+
# rigc: wrote /abs/path/specs/findings.json
|
|
373
|
+
|
|
374
|
+
bun cli.ts build --rig specs/rig.json --motion specs/motion.json --images parts/ --out spine
|
|
375
|
+
bun cli.ts diff spine/skeleton.json hero.json # 1.000 on every measure
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**The loop it belongs in is the one above, with a different first step.** `ingest`
|
|
379
|
+
writes the specs; you *edit* them the way you would edit specs you wrote; `build`
|
|
380
|
+
gates; `explain`, `render`, `preview` and `check` read the result. Nothing
|
|
381
|
+
downstream knows or cares that the file started somewhere else — which is the point,
|
|
382
|
+
and also the hazard the `note` below exists for.
|
|
383
|
+
|
|
384
|
+
**The contract is an equality, not a rulebook.** `build(ingest(x))` is `x`: the
|
|
385
|
+
rebuilt `skeleton.json` is byte for byte the file `ingest` read, and the rebuilt
|
|
386
|
+
atlas holds the same **region blocks** — as a multiset, because the order the pages
|
|
387
|
+
come out in is in no field of the skeleton and a decompiled spec cannot know it.
|
|
388
|
+
That is a gate rather than a claim: `bun run selftest` round-trips every rig this
|
|
389
|
+
repository builds on every run.
|
|
390
|
+
|
|
391
|
+
**Two flags, for the two things a skeleton does not encode.**
|
|
392
|
+
|
|
393
|
+
| flag | what it decides |
|
|
394
|
+
| --- | --- |
|
|
395
|
+
| `--art loose` (default) | name an `image` per attachment — `<path or placeholder>.png` — so the rebuild is `build --images <dir>` and rigc measures the PNGs |
|
|
396
|
+
| `--art none` | state `width`/`height` only, so the rebuild is `build --atlas-in <pack.atlas>` and every part resolves out of the pack |
|
|
397
|
+
| `--stage x,y,w,h` | the setup bounding box. **Required for an editor export**, which carries none |
|
|
398
|
+
| `--name <n>` | the rig spec's `name`, which the motion spec's `archetype` must equal (default: the file's basename) |
|
|
399
|
+
|
|
400
|
+
🚨 **The stage is the one value `ingest` will not guess.** rigc always emits
|
|
401
|
+
`skeleton.width`/`height` and an editor export never does, so a foreign file needs
|
|
402
|
+
`--stage`; without it the missing box is a **blocker**, named. It is not derivable —
|
|
403
|
+
posing the rig gives the *animated* extent, which is a different number from the
|
|
404
|
+
editor's setup box — and it is the value that costs least to get wrong, because no
|
|
405
|
+
measure `diff` reports reads the skeleton header at all. Supply it from the project
|
|
406
|
+
the file came from, or from the editor's own canvas.
|
|
407
|
+
|
|
408
|
+
⚠️ **The duration is a convention, and it is recorded as one.** Skeleton JSON has no
|
|
409
|
+
duration field. The largest key time is the only derivable answer and it is what a
|
|
410
|
+
runtime plays to — but it is wrong for an animation that holds its last pose past its
|
|
411
|
+
last key, and nothing in the file can tell the two apart. `ingest` writes the largest
|
|
412
|
+
key time, states the convention in the motion spec's `note`, and records a finding per
|
|
413
|
+
animation. If you know the real number, edit it: the declared duration is checked
|
|
414
|
+
against the compiled keys, so an honest one costs you nothing.
|
|
415
|
+
|
|
416
|
+
**Read the findings; they are the product.** Three kinds, and the exit code turns on
|
|
417
|
+
the first:
|
|
418
|
+
|
|
419
|
+
| gutter | meaning |
|
|
420
|
+
| --- | --- |
|
|
421
|
+
| `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 |
|
|
422
|
+
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
423
|
+
| `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 |
|
|
424
|
+
|
|
425
|
+
📝 **Do not delete the `note`.** Both written specs carry one saying the file is
|
|
426
|
+
decompiled and naming the skeleton it came from. A decompiled spec is
|
|
427
|
+
indistinguishable from an authored one by inspection, every gate here calls it green —
|
|
428
|
+
because it *is* green — and no gate can catch the note's absence. It carries no
|
|
429
|
+
timestamp, deliberately: `A18_DETERMINISTIC_EMIT` compares two independent compiles
|
|
430
|
+
byte for byte, and a dated note would break the first rebuild from the spec.
|
|
431
|
+
|
|
432
|
+
⛔ **It reads skeleton JSON and nothing else** — not a `.spine` project, not a binary
|
|
433
|
+
`.skel`, not the atlas, not the art. A path that is not a `.json` is refused by name.
|
|
434
|
+
[INGEST.md](INGEST.md) is the whole page on working from a file you were handed.
|
|
435
|
+
|
|
345
436
|
The other commands:
|
|
346
437
|
|
|
347
438
|
```bash
|
|
348
439
|
bun cli.ts explain --rig … --motion … --out … # the compiled rig as a table
|
|
349
440
|
bun cli.ts validate path/to/spine # re-gate artifacts already on disk
|
|
350
441
|
bun cli.ts diff candidate.json reference.json
|
|
351
|
-
bun cli.ts check --candidate path/to/spine --frames path/to/frames
|
|
442
|
+
bun cli.ts check --candidate path/to/spine --frames path/to/frames [--skin …]
|
|
352
443
|
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]
|
|
444
|
+
bun cli.ts render --candidate path/to/spine [--animation …] [--skin …] [--fps 12] [--max 256]
|
|
354
445
|
bun cli.ts preview --candidate path/to/spine [--animation …] [--out preview.html]
|
|
355
446
|
bun cli.ts vote --candidate path/to/a --candidate path/to/b [--out ballot.html]
|
|
356
447
|
bun cli.ts vote --record vote-<id>.json [--ballot ballot.html] [--ledger votes.jsonl]
|
|
@@ -372,6 +463,9 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
372
463
|
for opposite fixes. A measure with nothing to compare says `0/0` and says so.
|
|
373
464
|
- **`check`** renders your candidate into the reference frames' own pixel grid and
|
|
374
465
|
compares pixels — the only thing here that can see a wrong animation. **§9.**
|
|
466
|
+
🚨 What it certifies is the **default skin** unless you pass `--skin <name>`:
|
|
467
|
+
on a rig with named skins, a run with no skin draws none of their art on either
|
|
468
|
+
side and reports a perfect `0.0000` about it (**§9**).
|
|
375
469
|
- **`bench <rung>`** runs one rung of [the benchmark ladder](https://github.com/firejune/rigc/blob/main/docs/LADDER.md): validate
|
|
376
470
|
under `--profile spine`, then diff against that rung's reference export, and with
|
|
377
471
|
`--frames` the `check` table as well. Unlike the three above it is a **finish
|
|
@@ -673,8 +767,8 @@ is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
|
673
767
|
|
|
674
768
|
| Field | Spine meaning | Default |
|
|
675
769
|
| --- | --- | --- |
|
|
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
|
|
770
|
+
| `x`, `y` | setup-pose bounding box origin | `0` — and refused outright beside a stated absence, below |
|
|
771
|
+
| `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
772
|
| `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
|
|
679
773
|
| `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
|
|
680
774
|
| `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 +784,40 @@ links, not the editor that will open the file, and the warning is harmless
|
|
|
690
784
|
`width`/`height` are what `A14` and `A19` measure against, so a guessed stage is a
|
|
691
785
|
gate measuring against a number nobody wrote down.
|
|
692
786
|
|
|
787
|
+
⭐ **A skeleton may declare no stage, and saying so is not the same as saying
|
|
788
|
+
nothing** (issue #578). Write the pair as `null`:
|
|
789
|
+
|
|
790
|
+
```json
|
|
791
|
+
"skeleton": { "width": null, "height": null }
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
and the emitted header carries **none** of `x`/`y`/`width`/`height` — which is
|
|
795
|
+
what an export of a skeleton whose stage was never set looks like, and the shape a
|
|
796
|
+
transcriber of one now has something to write. `null` is this spec's spelling for
|
|
797
|
+
a stated absence wherever it has one (`slots[].attachment` is `null` for "show
|
|
798
|
+
nothing"), so nothing new is introduced here but a third value of a field that
|
|
799
|
+
already existed.
|
|
800
|
+
|
|
801
|
+
Three readings stay apart, and the middle one is the point of the other two:
|
|
802
|
+
|
|
803
|
+
| What the spec says | What happens |
|
|
804
|
+
| --- | --- |
|
|
805
|
+
| a number for each | the stage, as before; a manifest `crop` is the fallback |
|
|
806
|
+
| `"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 |
|
|
807
|
+
| neither | **refused**, exactly as before: `no stage size: …` |
|
|
808
|
+
| one `null`, one number | refused — a stage has both extents or neither, and which half was meant is not derivable |
|
|
809
|
+
| the pair `null` **and** an `x` or `y` | refused — an origin for a box that is not there |
|
|
810
|
+
|
|
811
|
+
⚠️ A stage-less skeleton is **unmeasured, not certified**: `A14_NO_FULL_FRAME_MESH`
|
|
812
|
+
reports **SKIP** on one, because there is no full frame for a mesh to span. And
|
|
813
|
+
`rigc diff` reports it — `skeleton.stage_present` and `skeleton.stage_box`, in the
|
|
814
|
+
header block at the top of the report — so a stage somebody invented now reads
|
|
815
|
+
below 1.000 against a source that has none. Both are reported and gate nothing, for
|
|
816
|
+
the reason every reported measure is: no reading of the rendered frames could have
|
|
817
|
+
decided a setup-pose bounding box. The measure inventory that says so lives in
|
|
818
|
+
[BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md), which
|
|
819
|
+
is repository material and not in the published package.
|
|
820
|
+
|
|
693
821
|
### 3.2 `bones` — Spine's bone list
|
|
694
822
|
|
|
695
823
|
`parent` is resolved **by name against bones already declared**, exactly as the
|
|
@@ -720,17 +848,38 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
720
848
|
| --- | --- | --- |
|
|
721
849
|
| `name` | required, unique | — |
|
|
722
850
|
| `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) |
|
|
851
|
+
| `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
852
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
725
853
|
| `dark` | two-colour tint, `rrggbb` | — (🚫 `A12` under `spine-html`) |
|
|
726
854
|
| `blend` | `normal` · `additive` · `multiply` · `screen` | `normal` |
|
|
727
855
|
|
|
728
|
-
|
|
729
|
-
entry, no manifest part —
|
|
730
|
-
|
|
731
|
-
slot
|
|
732
|
-
|
|
733
|
-
|
|
856
|
+
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
857
|
+
skin entry, no manifest part — is emitted **empty**: `name` and `bone`, and no
|
|
858
|
+
`attachment` key at all, which is how the format spells "shows nothing"
|
|
859
|
+
(`SkeletonJson`'s slot reader takes `attachment` with a `null` default; 34 of the 52 slots of the
|
|
860
|
+
official `spineboy-pro` export omit the key, though those are slots a skin fills
|
|
861
|
+
whose setup pose shows nothing). So the emitted slots array
|
|
862
|
+
**is** the rig's slot table, and `A26_SLOT_DRAW_ORDER` checks it in both directions:
|
|
863
|
+
nothing out of order, and nothing missing.
|
|
864
|
+
|
|
865
|
+
Declaring a slot no cut fills is therefore normal — it fixes where that slot sits
|
|
866
|
+
whether or not this cut has art for it — and transcribing a foreign skeleton that
|
|
867
|
+
carries an empty slot reproduces it exactly.
|
|
868
|
+
|
|
869
|
+
⚠️ **This changed with issue #575.** Such a slot used to be *dropped*, with no message, and
|
|
870
|
+
the gate allowed the emitted array to be any subsequence of the rig's. What it cost
|
|
871
|
+
is the index: every slot below the dropped one moved up one place, which is what a
|
|
872
|
+
`drawOrder` key's offsets are counted against and what an index-keyed consumer
|
|
873
|
+
splits on. Two production exports declaring 53 and 61 slots built green at 51 and 57
|
|
874
|
+
and read 0.962 and 0.934 under `diff` against the file they were transcribed from.
|
|
875
|
+
If you have a rig that leaned on the drop, the emitted array simply grows; nothing
|
|
876
|
+
else about it moves.
|
|
877
|
+
|
|
878
|
+
🚫 **Naming an attachment on a slot nothing fills is refused by name**: `the setup
|
|
879
|
+
pose shows attachment "x" on slot "y", which no skin and no manifest part fills`.
|
|
880
|
+
That is the half-finished wiring-up the old silence hid — the slot is emitted empty
|
|
881
|
+
and the name resolves to nothing, so either give the slot an attachment or state the
|
|
882
|
+
setup pose as `null`.
|
|
734
883
|
|
|
735
884
|
### 3.4 `skins` — placeholder → attachment maps
|
|
736
885
|
|
|
@@ -751,7 +900,7 @@ the default `type`:
|
|
|
751
900
|
| `type` | `"region"`, or omit |
|
|
752
901
|
| `image` | **rigc extension.** A PNG relative to the rig's `images` directory; rigc measures it (R5) |
|
|
753
902
|
| `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) |
|
|
903
|
+
| `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
904
|
| `x`, `y` | offset from the bone, in the bone's local space |
|
|
756
905
|
| `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
|
|
757
906
|
| `scaleX`, `scaleY`, `color` | as Spine |
|
|
@@ -760,7 +909,19 @@ the default `type`:
|
|
|
760
909
|
either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
|
|
761
910
|
never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
|
|
762
911
|
omitted, rigc derives — `hull` and `edges` from the triangles, the size from the
|
|
763
|
-
PNG — and the rules are a few paragraphs down.
|
|
912
|
+
PNG — and the rules are a few paragraphs down. `type`, `image`, `path` and `color`
|
|
913
|
+
mean exactly what they mean on a region.
|
|
914
|
+
|
|
915
|
+
🔑 **`path` is one rule for both kinds.** A mesh derives it from `image` the way a
|
|
916
|
+
region does: stated wins, otherwise the PNG's basename when that differs from the
|
|
917
|
+
placeholder, otherwise nothing. The parser reads `path` off both with the same line
|
|
918
|
+
(`getValue(map, "path", name)`, `SkeletonJson.ts:541` and `:570`), and `path`
|
|
919
|
+
defaults to the attachment's **name** rather than to the placeholder — so a mesh
|
|
920
|
+
with `image: hair_short.png` under a placeholder called `hair` resolves the region
|
|
921
|
+
`hair`, which no atlas has. Until
|
|
922
|
+
[#577](https://github.com/firejune/rigc/issues/577) a region derived it and an
|
|
923
|
+
authored mesh did not, so that rig **built** and then failed
|
|
924
|
+
`A00_ROUNDTRIP_PARSE: threw: Region not found in atlas: hair`.
|
|
764
925
|
|
|
765
926
|
Geometry comes in one of two fields:
|
|
766
927
|
|
|
@@ -1492,8 +1653,24 @@ skin's entry as its placeholder and name the others. Trip 7 supported it and tri
|
|
|
1492
1653
|
8 refuted it: that is the spelling the editor refuses at the door. There is no
|
|
1493
1654
|
third spelling, which is why this is a refusal rather than a naming scheme.
|
|
1494
1655
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1656
|
+
What to know about it, and nothing to author:
|
|
1657
|
+
|
|
1658
|
+
- **The renderer accepts this shape too, and that is measured rather than
|
|
1659
|
+
assumed.** `spine-html@0.4.1` resolves a part in two steps and neither one
|
|
1660
|
+
reads a placeholder or an attachment name: `DomTexture.js:78,102` builds its
|
|
1661
|
+
image map with `put(atlasRegion.name, …)` over every region of the atlas, and
|
|
1662
|
+
`SpineHtmlRenderer.js:172` reads it back as
|
|
1663
|
+
`const regionImage = region && this.regionImages.get(region.name)`, where
|
|
1664
|
+
`region` came off the attachment — which `AtlasAttachmentLoader` resolved
|
|
1665
|
+
through `path`. Every published version of that renderer keys the same way.
|
|
1666
|
+
⚠️ `A08` used to carry a `--profile spine-html` clause requiring a
|
|
1667
|
+
placeholder to be spelled exactly like the region it resolves to, which made
|
|
1668
|
+
this shape and a green `spine-html` **mutually exclusive** from
|
|
1669
|
+
[#567](https://github.com/firejune/rigc/issues/567) onwards; the first
|
|
1670
|
+
production rig with named skins hit it three times. That clause is retired —
|
|
1671
|
+
restated as the join the renderer actually performs it was a tautology over
|
|
1672
|
+
the resolve check beside it
|
|
1673
|
+
([#574](https://github.com/firejune/rigc/issues/574)).
|
|
1497
1674
|
- **`path` is restated, and it has to be.** `path` defaults to the attachment's
|
|
1498
1675
|
**name**, not to its placeholder, so an entry given a name and no path would
|
|
1499
1676
|
resolve its texture at `zulu/patch` and find no such region. `A00_ROUNDTRIP_PARSE`
|
|
@@ -1525,6 +1702,15 @@ bounding box in slot `head-bb`, and reuses `hoverglow-small` across eight slots.
|
|
|
1525
1702
|
a name shared between slots is normal and rigc leaves it alone; what #541 refused
|
|
1526
1703
|
was one slot holding two.
|
|
1527
1704
|
|
|
1705
|
+
🚨 **Once a rig has named skins, no instrument here can see them until you say
|
|
1706
|
+
which one** ([#571](https://github.com/firejune/rigc/issues/571)). `render` and
|
|
1707
|
+
`check` set no skin unless told to, so every slot resolves through the *default*
|
|
1708
|
+
skin alone and the art you just moved into `base`, `zulu` and `mike` draws
|
|
1709
|
+
nothing at all. `check` then compares blank against blank and reports a perfect
|
|
1710
|
+
`0.0000` — about the very placeholder this subsection is about. Pass
|
|
1711
|
+
`--skin <name>` to both, once per skin (**§9**); `tools/editor_roundtrip.ts`
|
|
1712
|
+
loops over every skin the build declares for the same reason.
|
|
1713
|
+
|
|
1528
1714
|
### 3.5 `constraints` — 4.3's single typed array
|
|
1529
1715
|
|
|
1530
1716
|
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
@@ -2586,6 +2772,18 @@ here was measured off a real rig. Copy the shape, not the values.
|
|
|
2586
2772
|
runtime. **Stating a flag on every key still overrides the rig** — the format
|
|
2587
2773
|
keys them per key on purpose, a bend that flips partway through is a real thing
|
|
2588
2774
|
to write, and it is what the editor's own export does.
|
|
2775
|
+
- 🔁 **A spec that came out of `ingest` (§0.3) states all three on every key, and
|
|
2776
|
+
that is not noise.** The stamping above reads a silent key as *"the rig's
|
|
2777
|
+
value"*, which is right for a spec somebody wrote; in a **decompiled** spec a
|
|
2778
|
+
silent key means *"the parser's default"*, because that is what the export the
|
|
2779
|
+
spec came from actually plays. The two readings differ exactly when the rig
|
|
2780
|
+
declares a non-default flag and the export's keys omit it — measured: an ik
|
|
2781
|
+
timeline keying only `mix` under a constraint declaring `bendPositive: false`
|
|
2782
|
+
rebuilds with `bendPositive: false` on every key if the flag is left silent,
|
|
2783
|
+
and with `true` — what the source plays — when `ingest` writes it out. So
|
|
2784
|
+
`ingest` restates every field any key of a track names, at the parser's default
|
|
2785
|
+
where the source omits one, and records a `CONSTRAINT_KEY_RESTATED` finding. On
|
|
2786
|
+
rigc's own output the two agree and the round trip is byte-identical.
|
|
2589
2787
|
- `mix` outside `0..1` is a compile error: `IkConstraintPose.mix` is documented as
|
|
2590
2788
|
a percentage. A **transform** mix is documented *unbounded*, which is why §4.10
|
|
2591
2789
|
has no such rule — the asymmetry is the runtime's, not ours.
|
|
@@ -2706,7 +2904,7 @@ Per key:
|
|
|
2706
2904
|
|
|
2707
2905
|
| Field | Meaning |
|
|
2708
2906
|
| --- | --- |
|
|
2709
|
-
| `vertices` | the run: `x, y`
|
|
2907
|
+
| `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
2908
|
| `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
2909
|
| `fromVertex` | which VERTEX the run starts at — rigc translates it |
|
|
2712
2910
|
| `offset` | the same start as a raw index into the deform array. Never with `fromVertex` |
|
|
@@ -2756,14 +2954,12 @@ geometry to the next one's. So a named `ease` behaves as it does anywhere, and a
|
|
|
2756
2954
|
raw `curve` is 4 numbers whose value axis runs 0..1, not the range of your vertex
|
|
2757
2955
|
offsets.
|
|
2758
2956
|
|
|
2759
|
-
rigc refuses
|
|
2760
|
-
|
|
2957
|
+
rigc refuses these, and the first is the quietest defect in the whole animation
|
|
2958
|
+
half of the format:
|
|
2761
2959
|
|
|
2762
2960
|
| You wrote | You get |
|
|
2763
2961
|
| --- | --- |
|
|
2764
2962
|
| 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
2963
|
| `fromVertex` on a multi-bone vertex | `"fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` |
|
|
2768
2964
|
| an attachment that is not there | `slot "flat" in skin "default" has no attachment "flatt" (it has: flat)` |
|
|
2769
2965
|
| a deform on a region attachment | `a deform timeline keys the vertices of an attachment, and this one is a "region"` |
|
|
@@ -2781,15 +2977,30 @@ tail and deforms the rest of the mesh correctly, which looks nearly right, and
|
|
|
2781
2977
|
emitted file, measuring the array's length from the attachment rather than assuming
|
|
2782
2978
|
an encoding.
|
|
2783
2979
|
|
|
2784
|
-
📌 **
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
`
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2980
|
+
📌 **A run is not required to be an even number of numbers, and it is not required
|
|
2981
|
+
to start on an even index.** Two rows stood here saying otherwise until issue
|
|
2982
|
+
#576. Nothing in either reader aligns a run to a pair: `SkeletonJson` does
|
|
2983
|
+
`Utils.arrayCopy(vertices, 0, deform, offset, vertices.length)` — a raw copy, at
|
|
2984
|
+
the raw index the key gives — and `SkeletonBinary` reads a count and a start and
|
|
2985
|
+
fills `for (let v = start; v < end; v++)`. So a run may begin and end mid-pair,
|
|
2986
|
+
which is exactly what an editor's trimmed delta run looks like: `spineboy-pro`'s
|
|
2987
|
+
`hoverboard-board` key starts at 1 and carries 147 numbers of a 148-long array,
|
|
2988
|
+
the whole delta minus one leading zero.
|
|
2989
|
+
|
|
2990
|
+
⭐ **An odd run's last number is an x with no y beside it, and that is a
|
|
2991
|
+
statement, not an accident.** It moves that vertex in x and leaves its y at the
|
|
2992
|
+
setup value, because the parser copies your numbers and touches nothing else. It
|
|
2993
|
+
is also the reason the rule could not stay as an authoring convenience: padding a
|
|
2994
|
+
`0` to make the run even *changes what plays* wherever that setup y is non-zero,
|
|
2995
|
+
so there is no second spelling of such a key — refusing it would mean no
|
|
2996
|
+
transcription of that file can be written in this spec at all. `A35` reached the
|
|
2997
|
+
same conclusion from the other side in issue #262, and between that fix and this
|
|
2998
|
+
one the two halves of rigc disagreed about what the format holds.
|
|
2999
|
+
|
|
3000
|
+
⚠️ What you lose with those rows is a typo filter: `offset: 3` written where
|
|
3001
|
+
`fromVertex: 3` was meant now compiles. It always half-did — `offset: 4` meant as
|
|
3002
|
+
vertex 4 lands on vertex 2 and was never refused — so read the field name twice.
|
|
3003
|
+
`offset` is an index into the deform array; `fromVertex` is a vertex.
|
|
2793
3004
|
|
|
2794
3005
|
🖼️ **Worked examples, and they use a deform for four different things** — all
|
|
2795
3006
|
four are repository material rather than part of the published package, so the
|
|
@@ -3509,9 +3720,13 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3509
3720
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
3510
3721
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
3511
3722
|
| `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 |
|
|
3723
|
+
| `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 |
|
|
3724
|
+
| `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
3725
|
| `a region needs width and height — give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
|
|
3514
3726
|
| `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 |
|
|
3727
|
+
| `"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 |
|
|
3728
|
+
| `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) |
|
|
3729
|
+
| `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
3730
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
3516
3731
|
| `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
3732
|
| `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 |
|
|
@@ -3541,7 +3756,9 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3541
3756
|
| `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
3757
|
| `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
3758
|
| `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 |
|
|
3759
|
+
| `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 |
|
|
3760
|
+
| `"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 |
|
|
3761
|
+
| `"skeleton" declares no stage (width: null, height: null) and still states x` | §3.1 — an origin for a box that is not there |
|
|
3545
3762
|
| `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
3763
|
| `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
3764
|
| `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 +3773,6 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3556
3773
|
| `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
3774
|
| `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
3775
|
| `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
3776
|
| `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
3777
|
| `deform …: slot "X" in skin "default" has no attachment "Y" (it has: …)` | §4.11 — fix the placeholder name |
|
|
3562
3778
|
| `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 |
|
|
@@ -3609,9 +3825,31 @@ The report prints one line per assertion:
|
|
|
3609
3825
|
- **FAIL** — the detail names the object, the value found and the value required.
|
|
3610
3826
|
That detail is the instruction; the table below says which file to change.
|
|
3611
3827
|
|
|
3828
|
+
**Every assertion leaves exactly one row, on every run.** The four kinds partition
|
|
3829
|
+
the registry, so the rows you can see are the whole of what was asked — there is
|
|
3830
|
+
no fifth state in which a rule quietly did not come up. The last line of the
|
|
3831
|
+
report states that partition, and every figure in it is a count of *assertions*
|
|
3832
|
+
you can reproduce by counting rows:
|
|
3833
|
+
|
|
3834
|
+
```
|
|
3835
|
+
.. <N> assertions: <M> measured (<P> passed, <F> failed), <S> skipped, <X> not in profile "<profile>"
|
|
3836
|
+
```
|
|
3837
|
+
|
|
3838
|
+
`<M>` is `<P> + <F>`, and `<N>` is all four added together. ⚠️ `<F>` counts
|
|
3839
|
+
assertions and not `FAIL` lines: one assertion that finds six wrong vertices
|
|
3840
|
+
prints six rows and is one failure here.
|
|
3841
|
+
|
|
3842
|
+
🚨 **When `A00_ROUNDTRIP_PARSE` fails, read the report as a report about A00 and
|
|
3843
|
+
nothing else.** Most of the rules read the skeleton or the atlas that A00 loads,
|
|
3844
|
+
and with no parse there is nothing for them to look at — so they report `SKIP`
|
|
3845
|
+
naming that, *the round trip did not produce a skeleton to measure* or *…an atlas
|
|
3846
|
+
to measure*, and the summary's `<S>` goes up while `<M>` collapses. A run in that
|
|
3847
|
+
state is not a rig that nearly passed; it is a rig that was measured on one rule.
|
|
3848
|
+
Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
3849
|
+
|
|
3612
3850
|
| Assertion | Profile | What tripped it, and where to fix it |
|
|
3613
3851
|
| --- | --- | --- |
|
|
3614
|
-
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas. Everything else in the report is downstream of this one — fix it first |
|
|
3852
|
+
| `A00_ROUNDTRIP_PARSE` | both | `spine-core` could not parse the skeleton or the atlas. 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)) |
|
|
3615
3853
|
| `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
3854
|
| `A02_NO_BONE_TRANSFORM_KEY` | both | a bone uses 4.2's `transform`; rename it `inherit` in the rig spec |
|
|
3617
3855
|
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size — the attachment has no `image` and no `width`/`height` |
|
|
@@ -3619,14 +3857,14 @@ The report prints one line per assertion:
|
|
|
3619
3857
|
| `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
3858
|
| `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
3859
|
| `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
|
|
3860
|
+
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | an atlas region name carrying stray whitespace. rigc writes the atlas, so this means a hand-edited file, and `A07` names the same line with its line number. ⚠️ A `path` that resolves to **no** region never reaches here: `AtlasAttachmentLoader` throws `Region not found in atlas: <path> (attachment: <name>)` while the skeleton is still loading, so it arrives 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)) |
|
|
3623
3861
|
| `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
3862
|
| `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale |
|
|
3625
3863
|
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
3626
3864
|
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
3627
3865
|
| `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 |
|
|
3866
|
+
| `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* |
|
|
3867
|
+
| `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
3868
|
| `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
3869
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out` |
|
|
3632
3870
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
@@ -3637,7 +3875,7 @@ The report prints one line per assertion:
|
|
|
3637
3875
|
| `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
3876
|
| `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 |
|
|
3639
3877
|
| `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
|
|
3878
|
+
| `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) |
|
|
3641
3879
|
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | renderer | a single-region page whose region name is not the PNG's basename |
|
|
3642
3880
|
| `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
3881
|
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
@@ -3661,19 +3899,30 @@ clauses are gated by profile.
|
|
|
3661
3899
|
|
|
3662
3900
|
## 6. What rigc will refuse — do not spend a loop on these
|
|
3663
3901
|
|
|
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
|
|
3902
|
+
Most of these are in the Spine 4.3 format and the emitter does not write them. Each
|
|
3903
|
+
of *those* is a **`NotImplementedError` naming the construct**, because the parser's
|
|
3904
|
+
own behaviour is worse: an unknown attachment `type` returns `null` and the
|
|
3905
|
+
attachment disappears, and a constraint entry with an unrecognised `type` matches no
|
|
3906
|
+
case and vanishes.
|
|
3668
3907
|
|
|
3669
|
-
|
|
3670
|
-
**neither of
|
|
3908
|
+
A deferral carries its reason, and the reason is the same one in every deferred row:
|
|
3909
|
+
**neither of those types appears anywhere in the benchmark corpus** (SPEC_COVERAGE
|
|
3671
3910
|
parts 3-1 and 4-2), so neither is on the ladder's critical path. The message
|
|
3672
3911
|
says so, because a deferral without its reason is a wall rather than a work item.
|
|
3673
3912
|
|
|
3913
|
+
⚠️ **A spelling the format does not have is a different refusal and says so.**
|
|
3914
|
+
`sequence` is not an attachment type, and a `"type"` that is `null` is not an absent
|
|
3915
|
+
one — telling either author that "rigc does not emit it yet" promises work that will
|
|
3916
|
+
never be done, on a map the parser would have dropped in silence. Those rows below
|
|
3917
|
+
are `CompileError`s, and they name what the format actually defines
|
|
3918
|
+
([#577](https://github.com/firejune/rigc/issues/577)).
|
|
3919
|
+
|
|
3674
3920
|
| You wrote | You get |
|
|
3675
3921
|
| --- | --- |
|
|
3676
|
-
| attachment `type` of `point` or `linkedmesh` | `attachment
|
|
3922
|
+
| 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 |
|
|
3923
|
+
| 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)) |
|
|
3924
|
+
| 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.) |
|
|
3925
|
+
| `"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
3926
|
| 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
3927
|
| 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
3928
|
| 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 +3951,35 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
3702
3951
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
3703
3952
|
Saying nothing means `spine`, so "the one you meant" is a decision either way —
|
|
3704
3953
|
the report's first line names the profile that judged it.
|
|
3954
|
+
📎 The two profiles judge attachment **naming** identically since
|
|
3955
|
+
[#574](https://github.com/firejune/rigc/issues/574): `spine-html` adds the
|
|
3956
|
+
renderer and archetype rules and has no opinion about how an attachment is
|
|
3957
|
+
spelled, so a rig whose named skins share a placeholder (§3.4.2) is green
|
|
3958
|
+
under either. Before that it was green under exactly one of them, and which
|
|
3959
|
+
one was not a property of the rig.
|
|
3705
3960
|
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.
|
|
3961
|
+
them is a check you were relying on. The summary's *measured* figure is the
|
|
3962
|
+
shortest version of this step: it is how many of the rules actually looked at
|
|
3963
|
+
anything, and it is the number to quote when you say a rig gated green.
|
|
3707
3964
|
⚠️ Under `--profile spine` a foreign skeleton usually produces **no SKIP lines
|
|
3708
3965
|
at all**, and that is not a clean bill of health. The archetype assertions are
|
|
3709
3966
|
excluded by the profile before the missing `invariants` block could make them
|
|
3710
3967
|
skip, so they come back `PROF` instead. Do not go looking for a SKIP that the
|
|
3711
3968
|
profile already accounted for; read step 3 instead.
|
|
3969
|
+
⚠️ And a **red** report is where this step matters most, which is the opposite
|
|
3970
|
+
of how it reads: if `A00` failed, most of the SKIP lines say the round trip
|
|
3971
|
+
denied them a result, and the handful of `PASS` rows beside them were measured
|
|
3972
|
+
on the raw text alone. Step 1 already sent you back; do not take anything from
|
|
3973
|
+
the rest of that report on the way ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
3712
3974
|
3. Read the `PROF` lines. They are where "was this rig held to that rule at all"
|
|
3713
3975
|
gets answered for everything the profile left out — the renderer policy *and*
|
|
3714
3976
|
the archetype rules. A green under `spine` has been held to neither; a green
|
|
3715
3977
|
under `spine-html` has been held to both.
|
|
3716
|
-
4. Run `explain` and read the slots table: every slot you declared
|
|
3717
|
-
(§3.3
|
|
3978
|
+
4. Run `explain` and read the slots table: every slot you declared **is** there
|
|
3979
|
+
(§3.3 — `A26_SLOT_DRAW_ORDER` holds that, and a slot nothing fills reads
|
|
3980
|
+
`setup=null attachments=[]`), so what this step is for is the two things the
|
|
3981
|
+
gate cannot check — that the order is the one you meant, and that each slot
|
|
3982
|
+
shows the setup attachment you meant.
|
|
3718
3983
|
5. If you were given **frames**, run `check` and read the table (§9). Steps 1–4 are
|
|
3719
3984
|
all about validity and structure; none of them can tell you the animation is
|
|
3720
3985
|
wrong, and this is the step that can. Do it before step 6, not after — `bench`
|
|
@@ -4196,10 +4461,48 @@ your animation is called something the frame directory is not, `--framing shared
|
|
|
4196
4461
|
to fit one framing across every set instead of one each,
|
|
4197
4462
|
`--texture-from <atlas>` to attribute how much of the MAE is texture resampling
|
|
4198
4463
|
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), `--
|
|
4464
|
+
which re-seats your geometry on that atlas's packing), `--skin <name>` to pose
|
|
4465
|
+
your candidate under one of its skins (below), `--all-frames` to list
|
|
4200
4466
|
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
4201
4467
|
per-slot report.
|
|
4202
4468
|
|
|
4469
|
+
🚨 **What `check` certifies is the DEFAULT skin, unless you pass `--skin`**
|
|
4470
|
+
(issue #571). With no `--skin` no skin is set at all, which is `spine-core`'s own
|
|
4471
|
+
initial state: every slot resolves through `SkeletonData.defaultSkin` alone, and a
|
|
4472
|
+
slot whose art lives only in a named skin draws **nothing** — on both sides, since
|
|
4473
|
+
the reference frames came out of the same renderer. A multi-skin rig checked that
|
|
4474
|
+
way compares blank against blank and reports `MAE mean 0.00`, which reads like the
|
|
4475
|
+
best possible answer and is a measurement of no art at all. On a single-skin rig
|
|
4476
|
+
this is the whole rig and there is nothing to pass.
|
|
4477
|
+
|
|
4478
|
+
So a multi-skin rig is checked **once per skin**, and both sides are rendered
|
|
4479
|
+
under the same one:
|
|
4480
|
+
|
|
4481
|
+
```bash
|
|
4482
|
+
bun cli.ts render --candidate path/to/spine --skin patch --out frames/patch
|
|
4483
|
+
bun cli.ts check --candidate path/to/spine --frames frames/patch --skin patch
|
|
4484
|
+
```
|
|
4485
|
+
|
|
4486
|
+
Three things keep that honest, and none of them is a convention you have to
|
|
4487
|
+
remember:
|
|
4488
|
+
|
|
4489
|
+
- `render --skin` writes the name into `frames.json`, so a frame set says which
|
|
4490
|
+
picture of the rig it is. A set rendered with **no** `--skin` records nothing,
|
|
4491
|
+
because "no skin was set" and "this file predates the field" are the same bytes
|
|
4492
|
+
on disk and neither is a claim.
|
|
4493
|
+
- `check` reads that back. A candidate posed under a **different** skin from the
|
|
4494
|
+
one the frames record — or under none, where the frames name one — is
|
|
4495
|
+
**refused by name** rather than scored, because the number would be about the
|
|
4496
|
+
difference between two skins. Frames that record nothing cannot be checked, and
|
|
4497
|
+
the report says so in a `⚠️` note instead of implying agreement.
|
|
4498
|
+
- The report header names the skin on both sides on every run, `--skin` or not:
|
|
4499
|
+
`skin candidate patch frames patch`, or `candidate no skin set (the default
|
|
4500
|
+
skin alone)`. `check.json` carries the same two under `skin` and
|
|
4501
|
+
`referenceSkin`.
|
|
4502
|
+
|
|
4503
|
+
A skin name the candidate does not declare is refused with the ones it does —
|
|
4504
|
+
`the candidate declares no skin "path"; it declares [default, patch, torn]`.
|
|
4505
|
+
|
|
4203
4506
|
⭐ **A frame set may ship a contact sheet instead of every frame, and the sheet is
|
|
4204
4507
|
compared too.** A long shot does not commit 311 near-duplicate PNGs: rung 2's sets
|
|
4205
4508
|
ship `f0000.png` and `f0310.png` plus a `contact.png` holding all 311 sampled
|