spine-rigc 2.49.0 → 2.51.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/NOTICE.md +2 -2
- package/README.md +8 -8
- package/docs/AUTHORING.md +31 -33
- package/docs/FACE.md +18 -18
- package/docs/INGEST.md +17 -15
- package/docs/RIGGING.md +3 -3
- package/package.json +1 -1
- package/skills/rigc-face/SKILL.md +1 -1
- package/src/assertions/bodies/a06.ts +1 -1
- package/src/assertions/bodies/a12.ts +6 -3
- package/src/assertions/bodies/a19.ts +7 -7
- package/src/assertions/bodies/a20.ts +2 -2
- package/src/assertions/emitted/index.ts +4 -2
- package/src/assertions/facts/atlas_regions.ts +2 -2
- package/src/assertions/facts/skin_entries.ts +1 -3
- package/src/assertions/harness.ts +5 -1
- package/src/assertions/kinds.ts +35 -5
- package/src/assertions/model/index.ts +6 -8
- package/src/assertions/model/skin_entries.ts +2 -4
- package/src/assertions/reasons.ts +1 -1
- package/src/assertions/report.ts +13 -2
- package/src/atlas.ts +1 -1
- package/src/cli/shared.ts +25 -17
- package/src/cli/spine_commands.ts +1 -1
- package/src/compile.ts +1 -1
- package/src/rig.ts +1 -1
- package/src/validate.ts +18 -24
- package/src/assertions/bodies/a11.ts +0 -15
- package/src/assertions/bodies/a27.ts +0 -33
package/NOTICE.md
CHANGED
|
@@ -40,8 +40,8 @@ Software's terms, not a licence term of this project:
|
|
|
40
40
|
development dependency for its own gate, and so does an install with
|
|
41
41
|
`spine-core` added beside it. Validation is not optional on either: the gate
|
|
42
42
|
runs through the runtime where it is installed and through rigc's own validator
|
|
43
|
-
where it is not, and the two are held to the same verdicts in CI on
|
|
44
|
-
|
|
43
|
+
where it is not, and the two are held to the same verdicts in CI on 48 of the
|
|
44
|
+
49 assertions — the official parser's own parse runs only where the runtime is.
|
|
45
45
|
|
|
46
46
|
> **If a product integrates a Spine Runtime to play rigc's output, that integration
|
|
47
47
|
> is subject to the terms above** — rigc neither creates those terms nor removes
|
package/README.md
CHANGED
|
@@ -65,12 +65,12 @@ it cuts both ways:
|
|
|
65
65
|
is a development dependency: this repository uses it to hold rigc's own validator
|
|
66
66
|
to the round trip's verdicts — in CI, on the public example recipes and every
|
|
67
67
|
selftest call, and on a private corpus of production rigs measured before each
|
|
68
|
-
release. Of the
|
|
68
|
+
release. Of the 49 assertions, 48 run on both; `A00_ROUNDTRIP_PARSE`, the official
|
|
69
69
|
parser's own parse, runs only where the runtime is installed and is reported as a
|
|
70
70
|
SKIP elsewhere. There, in the parse's place, the report also carries the model
|
|
71
71
|
side's own two parse rules, `A00_MODEL_READ` and `A00_MODEL_REGIONS_ON_PAGES`,
|
|
72
72
|
which no round-trip rule matches and the registry does not hold — so its summary
|
|
73
|
-
totals
|
|
73
|
+
totals 51 on the core entry, and its last line counts 42 rules on the model side
|
|
74
74
|
([AUTHORING §5.2](docs/AUTHORING.md) has a row for each). Install it beside rigc
|
|
75
75
|
(`bun add -d @esotericsoftware/spine-core`) and the same `rigc` runs the round
|
|
76
76
|
trip on every build. The
|
|
@@ -619,13 +619,13 @@ and `bun run fetch-examples`. The reasoning behind them is in
|
|
|
619
619
|
[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md).
|
|
620
620
|
|
|
621
621
|
`build` and `validate` both default to `--profile spine` — the 35 validity rules, which
|
|
622
|
-
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile
|
|
623
|
-
adds all
|
|
622
|
+
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile rig-play`
|
|
623
|
+
adds all 49: the other 14 are one renderer's policy and one canvas budget's, and they
|
|
624
624
|
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
625
625
|
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
626
|
-
yourself: author under `--profile
|
|
626
|
+
yourself: author under `--profile rig-play` and read the extra 14 as findings, and
|
|
627
627
|
gate the release under `--profile spine`.** A `deform` key that folds a mesh inside
|
|
628
|
-
out is written out under the default and refused by name under `
|
|
628
|
+
out is written out under the default and refused by name under `rig-play`, which
|
|
629
629
|
is the shape of what that split buys you. A report always names
|
|
630
630
|
the profile it ran and lists what that profile left out.
|
|
631
631
|
|
|
@@ -782,7 +782,7 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
782
782
|
| 📥 **[docs/INGEST.md](docs/INGEST.md)** | **working with a skeleton you did not author.** What every command can and cannot do with a foreign `skeleton.json`, reading it with the toolchain, transcription as the route that makes it yours, what each validator complaint means on an export, and the re-pivot/rename/extend recipes. Ships in the package too |
|
|
783
783
|
| 🤖 **[docs/PROMPTING.md](docs/PROMPTING.md)** | **handing the authoring to an AI agent** — the prompt clauses a measured pilot run paid for, and what you can leave unsaid. Ships in the package too |
|
|
784
784
|
| 🔬 **[docs/SPEC_COVERAGE.md](docs/SPEC_COVERAGE.md)** | **the Spine 4.3 format, field by field.** Every key the 4.3 JSON parser and atlas reader take, what each defaults to, and the parser line it comes from — the rows rigc's refusals cite by part number. Ships in the package too |
|
|
785
|
-
| 🎓 **[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the
|
|
785
|
+
| 🎓 **[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the 49 named assertions with their profiles, and the selftest that has watched every one of them fire. Repository material — it is not in the npm package |
|
|
786
786
|
| 📋 [LADDER.md](https://github.com/rig-c/core/blob/main/docs/LADDER.md) · [GATE.md](https://github.com/rig-c/core/blob/main/docs/GATE.md) · [PILOT.md](https://github.com/rig-c/core/blob/main/docs/PILOT.md) | the live rung ledger, the clause statements a candidate is graded against, and how to run an agent through the ladder and score what comes back |
|
|
787
787
|
| 🔬 [SURVEY_2026-08-22.md](https://github.com/rig-c/core/blob/main/docs/SURVEY_2026-08-22.md) | the survey the ladder was climbed by: rigc and the official examples as measured on 2026-08-22, and the gap list ordered by rung. A dated record, not kept current. Repository material |
|
|
788
788
|
| 🧬 [GENERATIONS.md](https://github.com/rig-c/core/blob/main/docs/GENERATIONS.md) | **Spine data from another generation.** Why a 3.8–4.2 file read as 4.3 fails in silence, the policy that follows (detect from `skeleton.spine`, never guess, play on the matching runtime), what `A16` and `ingest` do with such a file, and the editor as the migration path. Repository material |
|
|
@@ -832,7 +832,7 @@ quality."* All six, with their verdicts, are in
|
|
|
832
832
|
[docs/LADDER.md](https://github.com/rig-c/core/blob/main/docs/LADDER.md).
|
|
833
833
|
|
|
834
834
|
The whole dossier — the yardstick, `diff` and `check` and what neither of them can
|
|
835
|
-
see, every rung, the run viewer, the
|
|
835
|
+
see, every rung, the run viewer, the 49 assertions and the selftest behind them — is
|
|
836
836
|
[docs/BENCHMARK.md](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md).
|
|
837
837
|
Live rung status is
|
|
838
838
|
[docs/LADDER.md](https://github.com/rig-c/core/blob/main/docs/LADDER.md).
|
package/docs/AUTHORING.md
CHANGED
|
@@ -239,7 +239,7 @@ What the flags mean:
|
|
|
239
239
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
240
240
|
| `--cut` | `build`, `explain` and `validate`: look up a named cut in `--cuts <cuts.json>`, **instead of** `--rig`/`--motion`/`--out` — the two spellings are one build stated two ways and are refused together. A `cuts.json` is `{ "<name>": { "rig": …, "motion": …, "out": …, "manifest"?: … } }`, every path in it relative to the table's own file, so the table lives with the project that owns the art |
|
|
241
241
|
| `--cuts` | the `cuts.json` `--cut` names. Required beside it — `--cut` alone is refused, with no guess at where the table lives |
|
|
242
|
-
| `--profile` | `spine` = the 35 validity rules (**the default**) · `
|
|
242
|
+
| `--profile` | `spine` = the 35 validity rules (**the default**) · `rig-play` = all 49, opt-in. `spine-html`, the profile's former name, is read as `rig-play` until the next minor release and prints a deprecation line on stderr naming `rig-play` |
|
|
243
243
|
| `--report` | `build` and `repack`: also write the gate's report as a JSON document (`build-report/1`) to this file — every gate's `PASS`, `SKIP` and `FAIL` rows, its summary figures and stats line, which supplier judged it, and every `pack:` line's figures. Written when the command writes `--out` and when a gate is red, before the exit; a file already at the path is removed first, so a compile error or a refused repack leaves none. Never inside `--out`, refused by name. The printed lines are byte-identical with it and without it — **§5.3** |
|
|
244
244
|
| `--candidate` | `check`, `bench`, `render`, `inspect`, `preview`, `chainfit` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. A directory holding no `skeleton.json`, or a path with nothing at it, is refused `nothing at <path>`, exit 2, nothing written, by every command that takes a build — `validate <dir>` and a directory given to `diff` included (`RC27`). `--atlas <path>` names the atlas when it does not sit beside the skeleton. On `render` and `check` a rigc build whose `skeleton.model.json` is `rigc-compiled/3` or `/2` needs no atlas at all: with no `--atlas` and no atlas beside it, it is drawn from the document's `pages`, the same frames, geometry and figures as with it, and the `atlas` line says `— not there`. An atlas that is there is still read, and held to `pages`: one that differs (edited after the build) is drawn through spine-core, and the `poser` line names the first difference. A build drawn through an atlas that is not there — a `rigc-compiled/1` document, or a build the core refuses — is refused `nothing at <the atlas>: … is drawn through its atlas (<why>)`, exit 2, nothing written; a Spine export without its atlas is refused `nothing at <the atlas>` as it always was. A directory whose files are not one build — a `skeleton.json` or a `skeleton.atlas` from another build beside this one's `skeleton.model.json` — is drawn through spine-core where the runtime can load the pair, the `poser` line naming the two digests or the atlas's first difference; where it cannot, it is refused `<skeleton> does not load against <the atlas>: spine-core draws this pair (<the poser line's why>) and could not resolve it — "<the runtime's own words>". <dir> is not one build: …`, exit 2, nothing written (`RC18`) — another build's skeleton beside this build's atlas typically ends `"Region not found in atlas: <region> (attachment: <name>)"`. Rebuild, or put the build's own three files back beside each other. A Spine export beside an atlas it was not exported with is refused in the same words, ending `The skeleton and the atlas are not one pair`. An atlas copied from another build's directory usually stops earlier, at its pages — their paths are relative to the directory it was written in — and is refused naming the first one: `nothing at <the page>: <the atlas> names it as a page, and spine-core draws this pair through that atlas (<why>). … <dir> is not one build: …`, exit 2, nothing written (`RC18`); an export whose atlas names a page that is not there is refused the same way, ending `and the page has to be there`. A build moved or copied whole away from the directory it was built in stops at the same place, on either poser, and is refused naming the page and the file that names it: `nothing at <the page>: <the skeleton.model.json> names it as a page, and the core draws this build from it. A page path is relative to the build's directory, so a build moved or copied away from the directory it was built in names pages that are not here — build it again where it is, or build it with --copy-images, which writes its pages beside it` (under `--poser spine`, `<the atlas> names it as a page, and spine-core draws this pair through that atlas (--poser spine)` with the same ending), exit 2, nothing written (`RC21`); only where the core refused the files does a page refusal say the directory is not one build. `bench --bones` and `rigc bonedist` — on its `--candidate` and on its `--reference` — refuse a pair the runtime cannot load in the words above with `spine-core poses this pair (bonedist's candidate: …)` or `(bonedist's reference: …)`, exit 2, nothing written (`RC20`); they read no page image, so a build whose pages are elsewhere is measured as it is in place. A `skeleton.json` that is not JSON is refused `rigc: cannot read <the file>: <the parser's words> (line N, column N)`, exit 2, by `render`, `check`, `bench` and `bonedist` as by every other command that reads one (`RC22`). With the atlas gone, `check` on a `rigc-compiled/3` build reads its pages' `scale:` lines off the document, so its texture note and `check.json`'s `textureFrom.candidateScales` are what they are with the atlas; on a `/2` build its texture note says whether the atlas declares a `scale:` line is not read (that document does not state one), and `candidateScales` is `null` (`RC13`). **`vote` and `preview` take it more than once**: `vote` 2–4 times, one per pane, labelled A, B, C, D in the order given; `preview` any number of times, one pane per candidate in the order given, each headed by its path and its gate line, and the same skeleton twice (a directory and its `skeleton.json` are one) is refused by name. `--atlas` goes with one candidate only. Everywhere else a repeat is a typo and is refused |
|
|
245
245
|
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview` (each candidate's own first, with several), and for `vote` the first of candidate A. A name the skeleton does not have is refused, with the ones it does have listed — and for `vote` and a several-candidate `preview`, so is a name that only *some* candidates have, naming the one that lacks it |
|
|
@@ -306,7 +306,7 @@ reported `already linked`.
|
|
|
306
306
|
|
|
307
307
|
Pick the profile deliberately, and know which one you got by saying nothing. The
|
|
308
308
|
default is `spine`: "is this valid Spine 4.3 that any runtime plays correctly",
|
|
309
|
-
which is the question a rig authored anywhere is asking. `--profile
|
|
309
|
+
which is the question a rig authored anywhere is asking. `--profile rig-play`
|
|
310
310
|
adds one renderer's policy and one project's canvas budget on top, and those
|
|
311
311
|
extra rules fire on perfectly correct Spine data (clipping attachments,
|
|
312
312
|
unweighted meshes, packed atlases) — reach for it when you are shipping into
|
|
@@ -314,8 +314,8 @@ unweighted meshes, packed atlases) — reach for it when you are shipping into
|
|
|
314
314
|
lists what that profile left out, on `PROF` lines.
|
|
315
315
|
|
|
316
316
|
*Packed atlases* are in that list for what a page may hold, not for being packed:
|
|
317
|
-
under `
|
|
318
|
-
--profile
|
|
317
|
+
under `rig-play` a page is **one part per page OR a tiling page**, so `--pack
|
|
318
|
+
--profile rig-play` is an ordinary build — and it is the only one that puts the
|
|
319
319
|
renderer's own rulebook over shared-page sampling. What a *tiling* page has to
|
|
320
320
|
satisfy under that profile is §5.2's `A49` row: no two regions on one page
|
|
321
321
|
drawing over each other — refused where their rectangles overlap **and** their
|
|
@@ -600,7 +600,7 @@ what changes is that two rectangles may overlap where neither footprint is.
|
|
|
600
600
|
against 155 and 44, all by 1, between their own `rect` packs under `pot` and
|
|
601
601
|
`free`. No texel is read from a neighbour (that is three orders of magnitude
|
|
602
602
|
louder, `PK06`).
|
|
603
|
-
- **The gate reads footprints, not rectangles.** Under `--profile
|
|
603
|
+
- **The gate reads footprints, not rectangles.** Under `--profile rig-play`
|
|
604
604
|
`A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` refuses two regions on one page only
|
|
605
605
|
where their rectangles overlap **and** what they draw overlaps — a mesh's
|
|
606
606
|
hull (its UVs over the region's bounds; a linked mesh its source's), a region
|
|
@@ -928,7 +928,7 @@ report carries the rectangle to cut instead of leaving it to be derived:
|
|
|
928
928
|
|
|
929
929
|
- `--atlas-in` cannot recover what a `scale:` quantised away (above), turned or not;
|
|
930
930
|
- the **renderer** profile refuses a turned region outright
|
|
931
|
-
(`A06`, `--profile
|
|
931
|
+
(`A06`, `--profile rig-play`), because that profile is about artifacts rigc
|
|
932
932
|
itself emits and it never packs one turned. Reading a foreign pack and gating
|
|
933
933
|
one under somebody else's renderer policy are different questions.
|
|
934
934
|
|
|
@@ -2057,7 +2057,7 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
2057
2057
|
| `bone` | required; must be a bone this rig declares | — |
|
|
2058
2058
|
| `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 |
|
|
2059
2059
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
2060
|
-
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2` and `rgb2` tracks key it over time and require it | — (🚫 `A12` under `
|
|
2060
|
+
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2` and `rgb2` tracks key it over time and require it | — (🚫 `A12` under `rig-play`) |
|
|
2061
2061
|
| `blend` | `normal` · `additive` · `multiply` · `screen`. Only the first letter's case is free: `Additive` reads as `additive`, while `ADDITIVE` or `mUlTiPlY` reads as **no mode** in the runtime with no error, so `build` refuses it by name (§5.1). Emitted as written | `normal` |
|
|
2062
2062
|
|
|
2063
2063
|
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
@@ -2119,8 +2119,8 @@ name no `image` — an empty `default`, or one carrying only a `boundingbox`, a
|
|
|
2119
2119
|
with **no pages**, and rigc writes `skeleton.atlas` as an **empty file** (zero
|
|
2120
2120
|
bytes). Four rules then report SKIP by name rather than a pass, because a page is
|
|
2121
2121
|
their whole subject: `A07_ATLAS_TEXT_SHAPE`, `A06_ATLAS_PAGE_SIZE_MATCHES_PNG`,
|
|
2122
|
-
`A17_ATLAS_PAGE_FILES_EXIST` and — under `
|
|
2123
|
-
`A19_OVERLAY_PNGS_HAVE_ALPHA
|
|
2122
|
+
`A17_ATLAS_PAGE_FILES_EXIST` and — under `rig-play` —
|
|
2123
|
+
`A19_OVERLAY_PNGS_HAVE_ALPHA`.
|
|
2124
2124
|
|
|
2125
2125
|
⚠️ **This is not the case where an attachment WANTS a region.** A region or mesh
|
|
2126
2126
|
attachment that states `width`/`height` and names no `image` still resolves a
|
|
@@ -2457,10 +2457,10 @@ attachment rather than a second set of art.
|
|
|
2457
2457
|
| `convex` | default **false**. True tells the runtime the polygon is convex so it can clip without triangulating it, and a polygon that deforms concave is clipped by its convex hull instead (`ClippingAttachment.convex`). Nothing here checks that the polygon is in fact convex |
|
|
2458
2458
|
| `inverse` | default **false**. True makes everything **outside** the polygon visible instead of everything inside, and inverse clipping is always treated as convex (`ClippingAttachment.inverse`) |
|
|
2459
2459
|
|
|
2460
|
-
⚠️ **
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2460
|
+
⚠️ **No profile refuses a clipping attachment.** Until issue #1404 the rule A11
|
|
2461
|
+
refused one under `--profile rig-play`, because the renderer it was written for,
|
|
2462
|
+
spine-html 0.4.1, skipped clipping silently. The player that profile now names
|
|
2463
|
+
draws a clip in both of its modes, so A11 is retired and both profiles build one.
|
|
2464
2464
|
|
|
2465
2465
|
**Path** ([Spine: paths](http://esotericsoftware.com/spine-paths)) —
|
|
2466
2466
|
`"type": "path"`. The composite cubic Bezier a path **constraint** (§3.5.1) slides
|
|
@@ -2562,7 +2562,7 @@ window off the attachment's own `image`; a `ring` and a `ribbon` are built from
|
|
|
2562
2562
|
the numbers in this block and rigc compares neither to the PNG. The emitted
|
|
2563
2563
|
`width`/`height` are this `size`, so a `size` that is not the art's own scales the
|
|
2564
2564
|
drawing — measured: a 240x240 part declared `"size": [64, 64]` builds green under
|
|
2565
|
-
`--profile
|
|
2565
|
+
`--profile rig-play`. That is R1 rather than a gap: the compiler emits the
|
|
2566
2566
|
number the spec states and does not re-measure a plate to overrule it.
|
|
2567
2567
|
|
|
2568
2568
|
⭐ **Several grips split the ring by where the rig put them, on this route as on
|
|
@@ -3152,7 +3152,7 @@ one 96x64 plate of two islands under two two-link chains, `cell: 8`,
|
|
|
3152
3152
|
The `influence` line is the falloff's report: the most bones any vertex binds
|
|
3153
3153
|
(read it against `maxBones`), the mean, and how many vertices one bone owns
|
|
3154
3154
|
outright. A named bone that **no** vertex binds is listed there by name — it is
|
|
3155
|
-
in no weight, so no other figure moves — and under `--profile
|
|
3155
|
+
in no weight, so no other figure moves — and under `--profile rig-play`
|
|
3156
3156
|
`A20_MESH_WEIGHTS_COHERENT` refuses it as a declared bone nothing binds. The
|
|
3157
3157
|
`lattice` line says how many cells had art and how many were kept, and when
|
|
3158
3158
|
islands were joined, how many and how many cells the join added. The coverage
|
|
@@ -4413,13 +4413,13 @@ attachment, rgba, rgb, alpha, rgba2, rgb2)`, §5.1's row.
|
|
|
4413
4413
|
has one, so a file keying it without one parses cleanly and then throws in the
|
|
4414
4414
|
player the first time the animation is applied. `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN`
|
|
4415
4415
|
(§5.2) holds the same pairing on a skeleton rigc did not write. 🚫 Both halves
|
|
4416
|
-
of the two-colour tint are refused under `--profile
|
|
4416
|
+
of the two-colour tint are refused under `--profile rig-play`, whose DOM mode
|
|
4417
4417
|
ignores them; the default `spine` profile that `build` runs reports `A12` as
|
|
4418
4418
|
`PROF` and never applies it.
|
|
4419
4419
|
- The six are every slot timeline the format has; anything else is refused
|
|
4420
4420
|
here, and would be refused by the runtime's own reader too (`Invalid timeline
|
|
4421
4421
|
type for a slot`). `A12_NO_DARK_COLOR` refuses `rgb2` — and `rgba2`, and the
|
|
4422
|
-
slot field — in a file under the `
|
|
4422
|
+
slot field — in a file under the `rig-play` profile.
|
|
4423
4423
|
`sequence` is a timeline on an **attachment**, not on a slot — it is the
|
|
4424
4424
|
family beside `deform`, §4.13.
|
|
4425
4425
|
|
|
@@ -6485,11 +6485,11 @@ name or point the flag at the pack that has it.
|
|
|
6485
6485
|
|
|
6486
6486
|
The report prints one line per assertion:
|
|
6487
6487
|
|
|
6488
|
-
**No run reproduces this:** assembled — one line of each verdict kind; the `PASS` and the `PROF` are verbatim from a `build` this page states, the `SKIP` is cut at the ellipsis and only `--profile
|
|
6488
|
+
**No run reproduces this:** assembled — one line of each verdict kind; the `PASS` and the `PROF` are verbatim from a `build` this page states, the `SKIP` is cut at the ellipsis and only `--profile rig-play` prints it, and the `FAIL` is invented, because a green build prints none
|
|
6489
6489
|
```
|
|
6490
6490
|
PASS A08_REGION_NAMES_MATCH_ATTACHMENTS
|
|
6491
6491
|
SKIP A21_MESH_RIM_PINNED: the skeleton has no weighted mesh attachment, …
|
|
6492
|
-
PROF
|
|
6492
|
+
PROF A12_NO_DARK_COLOR: renderer rule, not in profile "spine"
|
|
6493
6493
|
FAIL A20_MESH_WEIGHTS_COHERENT: mesh "x" vertex 12 weights sum to 0.9000 (see rigc guide A20_MESH_WEIGHTS_COHERENT)
|
|
6494
6494
|
```
|
|
6495
6495
|
|
|
@@ -6502,8 +6502,8 @@ The report prints one line per assertion:
|
|
|
6502
6502
|
that subject and measures each member, so a skeleton with no region, no mesh or
|
|
6503
6503
|
no physics constraint gives it nothing and it **SKIPs**, naming the subject. A
|
|
6504
6504
|
rule named NO_⟨construct⟩ — `A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS`,
|
|
6505
|
-
`A02_NO_BONE_TRANSFORM_KEY`, `
|
|
6506
|
-
`
|
|
6505
|
+
`A02_NO_BONE_TRANSFORM_KEY`, `A12_NO_DARK_COLOR`,
|
|
6506
|
+
`A14_NO_FULL_FRAME_MESH` — asks how many of a thing the
|
|
6507
6507
|
artifact carries, and **zero is the answer**, so it **PASSes**. ⇒ On a foreign
|
|
6508
6508
|
export the SKIP list is a free inventory of what the skeleton does not contain,
|
|
6509
6509
|
and the PASS list never contains a rule that looked at nothing.
|
|
@@ -6538,9 +6538,9 @@ prints six rows and is one failure here.
|
|
|
6538
6538
|
`A00_MODEL_READ` and `A00_MODEL_REGIONS_ON_PAGES` — the first two rows of the
|
|
6539
6539
|
table below — which stand where `A00_ROUNDTRIP_PARSE`'s parse stands and have no
|
|
6540
6540
|
round-trip counterpart, so they are not in the registry. That is the whole of the
|
|
6541
|
-
difference between the two totals: `<N>` is
|
|
6542
|
-
and the core report's last line states the split —
|
|
6543
|
-
which are the
|
|
6541
|
+
difference between the two totals: `<N>` is 51 on the core entry and 49 on `cli.ts build`,
|
|
6542
|
+
and the core report's last line states the split — 42 rules on the model side,
|
|
6543
|
+
which are the 40 registry assertions the model side runs and those two; the 8 restated over the emitted text;
|
|
6544
6544
|
and `A00_ROUNDTRIP_PARSE`, which it reports as a SKIP.
|
|
6545
6545
|
|
|
6546
6546
|
🚨 **When `A00_ROUNDTRIP_PARSE` fails, read the report as a report about A00 and
|
|
@@ -6561,28 +6561,26 @@ Fix A00 and run it again.
|
|
|
6561
6561
|
| <a id="A03_REGION_WIDTH_HEIGHT_FINITE"></a>`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 |
|
|
6562
6562
|
| <a id="A04_MESH_TRIANGLES_AND_ENCODING"></a>`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 |
|
|
6563
6563
|
| <a id="A05_CURVE_ARRAY_LENGTH"></a>`A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel**. **SKIP** when no animation carries a timeline at all. Timelines with no `curve` on any key still PASS: every timeline name is checked against the channel table whether or not a curve sits on one Under `cli_core.ts build` the same clause runs over the text the emitter wrote and prints this line in the same words (`RC28`). |
|
|
6564
|
-
| <a id="A06_ATLAS_PAGE_SIZE_MATCHES_PNG"></a>`A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk — the message names the ratio it measured on **each** axis and the header that states the same art truthfully, and §0.2 has the whole of it: a runtime maps a region as a fraction of the DECLARED size and never reads the texture's own, so such a page draws, and what it breaks is every reader that addresses the page in texels. A uniform ratio is a `size:`/`scale:` pair away from honest and the message prints it; two ratios are not, because `scale:` carries one number, and the message says so rather than offering a header that would not work. **Or** a region's rectangle is not inside the page it names — rotation honoured, so a region at `rotate: 90` or `270` occupies `height x width` of the page and a region that fits only because it is turned is inside it. The message names the region, the rectangle it occupies, the page and the page's size. That clause is **validity** and runs under both profiles: a rectangle outside its page makes `u2 > 1` and samples whatever the wrap mode returns, and `--atlas-in` already refuses the same rectangle at compile time (§0.2). Under `
|
|
6564
|
+
| <a id="A06_ATLAS_PAGE_SIZE_MATCHES_PNG"></a>`A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk — the message names the ratio it measured on **each** axis and the header that states the same art truthfully, and §0.2 has the whole of it: a runtime maps a region as a fraction of the DECLARED size and never reads the texture's own, so such a page draws, and what it breaks is every reader that addresses the page in texels. A uniform ratio is a `size:`/`scale:` pair away from honest and the message prints it; two ratios are not, because `scale:` carries one number, and the message says so rather than offering a header that would not work. **Or** a region's rectangle is not inside the page it names — rotation honoured, so a region at `rotate: 90` or `270` occupies `height x width` of the page and a region that fits only because it is turned is inside it. The message names the region, the rectangle it occupies, the page and the page's size. That clause is **validity** and runs under both profiles: a rectangle outside its page makes `u2 > 1` and samples whatever the wrap mode returns, and `--atlas-in` already refuses the same rectangle at compile time (§0.2). Under `rig-play` also: `pma` and rotation. Two regions on one page over the same texels was a clause here until issue #1099 and is `A49`'s now, read over what the two regions draw rather than over their rectangles. **Or** the page file is not a PNG at all, and then nothing about its size is measured: the message carries the page's path and the size the atlas declares, and names what the file is by its first bytes — one of WebP, JPEG, GIF, KTX, KTX2 by its signature, or no image format rigc recognises, the bytes in hex either way — or calls it **truncated** when it begins as a PNG and runs out before its `IEND`, or names the dimension when its `IHDR` states a width or height of **0**, which the format forbids (§0.2). **Or** the file reads as a PNG and its **image data cannot be decoded** — its compressed stream is inflated (not unfiltered) and the decoder refuses it, or it inflates short of the rows the header states — and the message names the file and carries the decoder's own words (`the decoder stops with "incorrect data check"`). That clause is validity too: nothing in rigc can read such a page back — and its failure is the **one** line that names such a page: `A19` SKIPs over it and points here rather than naming it again, the way `A17` alone names a missing file. **SKIP** when the atlas declares no page — and, rather than a PASS, when a page's file is **not on disk** and nothing else here failed: that page's size was not measured, so the reason names the pages not read (or says none was, when every file is missing) and points at `A17`, which fails naming each missing file — one missing file is named once, by the rule whose subject it is. With another `A06` failure standing, the missing page is `A17`'s alone |
|
|
6565
6565
|
| <a id="A07_ATLAS_TEXT_SHAPE"></a>`A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file. Three sentences are about blank lines and name three different repairs: `line 1: the file begins with a blank line` (or `with N blank lines` for a run, one finding) is a blank before the first page name — delete it; `line N: the file ends with a blank line` (or `with N blank lines`, one finding) is a blank after the last page block's final line — delete it, since rigc ends the file on one newline; `line N: consecutive blank lines` is two blank lines side by side between the first page and the last — delete one. None of the three is `the last page block declares no region`, which means a page name and its fields with no region after them. rigc writes none of the three: under `--atlas-in` the pack's blank lines are put in rigc's shape on re-emission (§0.2), so any of them means a file rigc did not write, handed to `validate`. ⚠️ An atlas with **no page block at all** — no non-blank line — is not one of those: its subject is absent, so this reports **SKIP** naming the byte count it read, and so do the four rules below whose subject is a page ([#608](https://github.com/rig-c/core/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` Under `cli_core.ts build` the same clause runs over the text the emitter wrote and prints this line in the same words (`RC28`). |
|
|
6566
|
-
| <a id="A08_REGION_NAMES_MATCH_ATTACHMENTS"></a>`A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. There is no `
|
|
6566
|
+
| <a id="A08_REGION_NAMES_MATCH_ATTACHMENTS"></a>`A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. There is no `rig-play` clause here — a placeholder is free to differ from the region its `path` names **SKIP** when no attachment names a region *and* the atlas declares none — both of its subjects at once |
|
|
6567
6567
|
| <a id="A09_ANIMATION_DURATION_MATCHES_SPEC"></a>`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 |
|
|
6568
6568
|
| <a id="A10_NO_NAN_AFTER_STEPPING"></a>`A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a pose that is not finite. It reads the **whole world transform** — all six terms `a`, `b`, `c`, `d`, `worldX`, `worldY` of every bone — then every vertex of every region and mesh the slots show, at the **setup pose** and at every stepped frame of every animation, through the same scan `render` refuses on, so the two cannot disagree. The detail names where (`the setup pose`, or `animation "x" frame i of 120 (t=…s)`), then the bone and the term (`bone "x" has a NaN`), or the slot, attachment and vertex (`vertex 3 has x Infinity`) when every bone is finite and a vertex is not — a `scaleX` chain whose product stays under the largest double does that. A bone at `rotation: 1e309` has a finite position and a `NaN` matrix, which is why the position alone is not read. Look for a number past the largest double, a degenerate curve or a zero scale. 🦴 **It also poses every bone `inherit` key at its own time**: the runtime resolves a mode by folding the case of its first letter and nothing else, and a spelling that misses is stored as **NaN** — the world position stays finite, `updateWorldTransform` matches no mode, and the bone keeps the rotation, scale and shear it had. The detail names the animation, the bone, the key's time and its spelling, beside the five. A bone whose *setup* spelling misses poses no mode at all, and is named the same way from the stepping loop. Neither is reachable from a rig spec — `build` refuses both spellings by name (§5.1) — so on a green build this clause is about files rigc did not write. Which mode a correct spelling poses is not judged here, because the lookup that resolved it is the one that would be checked. 🧍 **A skeleton with no animation is still read**: its setup pose is the whole of what it shows, so A10 reads it — the world transform and the vertices as above, and the bone's mode and the slot colours a stepped frame is read for, which on a static rig nothing else would reach — and names it `the setup pose` (`the setup pose: slot "x" colour is non-finite`). The stepping half had nothing, and a PASS row carries no detail, so the stats line says so: `nanStepping=skipped`, beside `animations=0`. `A09` does not follow — a declared duration is a fact about an animation and nothing else, so a static rig still gives it nothing. **SKIP** only when the skeleton carries **no bone and no animation**: there is then no pose to read and nothing to step |
|
|
6569
|
-
| <a id="
|
|
6570
|
-
| <a id="A12_NO_DARK_COLOR"></a>`A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
6569
|
+
| <a id="A12_NO_DARK_COLOR"></a>`A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored by rig-play's DOM mode. The rule is worded for that mode alone: rig-play's canvas mode carries `dark` on its frame, but no test of the player's holds that it draws it, so nothing here says it does |
|
|
6571
6570
|
| <a id="A13_MESH_BUDGET"></a>`A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither — which means *unmeasured*, not that the budget is inert: the same `meshSlots` is a **compile-time** refusal for rigc's own generators, before the gate (§3.7) **SKIP** also when the rig budgets **only** triangles and the skeleton carries no mesh. A declared slot budget still PASSes there, because zero mesh slots is a count measured against a ceiling |
|
|
6572
6571
|
| <a id="A14_NO_FULL_FRAME_MESH"></a>`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* |
|
|
6573
6572
|
| <a id="A15_IDLE_NO_MESH_BONE_KEYS"></a>`A15_IDLE_NO_MESH_BONE_KEYS` | renderer | the `idle` animation keys a bone that drives a mesh, directly or as a control bone. The rule assumes meshes are mostly static — the renderer skips redrawing a mesh nothing moved — so it has three states. **FAIL per bone** when the rig declares nothing: `idle keys bone "X", which drives a mesh — meshes never idle-skip`, and the **first** of those lines, once per run, names the case: `K bone(s) keyed by idle drive meshes; if this idle is meant to deform them (a painting rig), declare invariants.idleDrivesMeshes: { "why": … } in the rig spec — or, where the motion belongs to a pivot above the mesh, key that pivot one link up (FACE.md §3)`. It rides the first finding, so the count of FAIL lines is still the count of bones. **SKIP with the cost** when the rig declares `invariants.idleDrivesMeshes` (§3.7): `declared by the rig ("<why>"): idle keys N bone(s) that drive M mesh attachment(s) totalling V vertices — "a", "b" — and each of those meshes is recomputed on every frame it is shown`. **FAIL on a stale declaration** — declared, and the `idle` keys no mesh-driving bone, carries no bone timeline or does not exist: `the rig "X" declares invariants.idleDrivesMeshes ("…"), but "idle" keys no bone that drives a mesh, so the declaration switches off nothing — remove it`. Otherwise **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 |
|
|
6574
6573
|
| <a id="A16_SKELETON_VERSION_4_3"></a>`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`) Under `cli_core.ts build` the same clause runs over the text the emitter wrote and prints this line in the same words (`RC28`). |
|
|
6575
|
-
| <a id="A17_ATLAS_PAGE_FILES_EXIST"></a>`A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. This is the one rule that names a missing page file; `A06` and `A19` SKIP over the page they could not read and point here rather than naming it again. **SKIP** when the atlas declares no page — as it is for `A06
|
|
6574
|
+
| <a id="A17_ATLAS_PAGE_FILES_EXIST"></a>`A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. This is the one rule that names a missing page file; `A06` and `A19` SKIP over the page they could not read and point here rather than naming it again. **SKIP** when the atlas declares no page — as it is for `A06` and `A19`; see `A07` |
|
|
6576
6575
|
| <a id="A18_DETERMINISTIC_EMIT"></a>`A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed — in `skeleton.json`, `skeleton.atlas` or the model document `skeleton.model.json`, the one named (the document's first differing line with it). That is a compiler bug, not a spec bug — report it Under `cli_core.ts build` the same clause runs over the text the emitter wrote and prints this line in the same words (`RC28`). |
|
|
6577
6576
|
| <a id="A19_OVERLAY_PNGS_HAVE_ALPHA"></a>`A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part cannot draw a transparent pixel, so it would paint a solid rectangle over what is behind it. **The texels decide, on both routes**; the file header is only the fast negative. A file with no alpha channel (colour type 4 or 6) and no `tRNS` chunk has nowhere to keep a clear texel and is refused without being opened, by a sentence that names its colour type — so re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. A file whose header says it **could** be transparent is opened and read until its first texel below full alpha; one clear texel passes, and none is refused in the words the packed route uses for a region, naming the file and what it can hold — `is opaque in every one of its 200x80 texels` … `its file can hold transparency — colour type 6 (truecolour + alpha) carries an alpha channel — and no texel uses it`. Saving as RGBA is not the repair: the runtime draws the texels, not the declaration. On a loose page the file is the part, so the whole decoded image is the rectangle and no atlas coordinate is read. Only the base plate may be opaque, and which image that is is decided once for both routes: the plate the build names — on a cut manifest, the part whose window is the crop — and, only when it names none, an image at least the stage's size. The rig's statement comes first because the two can disagree: a stage stated small enough for an overlay to cover would otherwise exempt that overlay. A rig spec cannot name a base plate, so a stageless rig-spec build, and `validate <dir>` on a stageless skeleton, has nothing to decide it; an opaque part there is refused with *"nothing here decides which image that is: this skeleton declares no stage size to measure one against"* and the two ways to decide it — a `skeleton` stage the plate covers, or a cut manifest (for `validate`, the specs it was built from: `--rig`, `--motion` and the `--manifest`). On a rig-spec build the sentence goes on *"this skeleton declares no stage size to measure one against, and the build names no base plate"* and offers *"build from a cut manifest, whose base plate is the part whose window is the crop"*; a manifest build exempts the plate it names, with a stage or without one. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art. ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page**: a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. ⚠️ **A page whose IMAGE is not the size the atlas declares for it is the same non-measurement for every region on it**, and the clause above does not cover that case: a page rescaled after packing leaves most rectangles partly on it, at coordinates that address a different part of the picture, so a scan would come back with a confident verdict over texels nobody had located — on a two-region pack at a uniform 0.5 an opaque part's failure **disappears**, the scan finding a transparent texel 32 texels away from it. The row names the page's two sizes and points at `A06`, which judges the page grid and prints the header that repairs it (§0.2). It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. **SKIP** when the atlas declares no page — and, rather than a PASS, when a page carrying a part was not read and nothing else here failed: the reason names the pages not read and how many of the pages carrying a part were (or says none was), and for each page points at the one rule that names its file — `A17` when the file is **not on disk**, `A06` when it is on disk and **cannot be read as PNG** (not a PNG, truncated, an `IHDR` dimension of 0, or image data that does not decode; `A06` fails naming what the file is on every such page, under both profiles). Undecodable data is found where this rule decodes, so the header's fast negative above still refuses a part with no alpha channel and no `tRNS` without opening it — a verdict about the header, not about the pixels. So one unreadable file is one red line, never two: with another `A19` failure standing, the unread page is that rule's alone and this one says nothing about it. A page holding only the base plate moves nothing here, missing or unreadable: this rule had no part on it to read. |
|
|
6578
|
-
| <a id="A20_MESH_WEIGHTS_COHERENT"></a>`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. On a rig spec's `"boneIndexing": "raw"` mesh an index out of range or a run that does not decode is refused at compile first (§5.1, issue #917), so here it names a skeleton rigc did not compile. Under `
|
|
6577
|
+
| <a id="A20_MESH_WEIGHTS_COHERENT"></a>`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. On a rig spec's `"boneIndexing": "raw"` mesh an index out of range or a run that does not decode is refused at compile first (§5.1, issue #917), so here it names a skeleton rigc did not compile. Under `rig-play` also: an unweighted mesh, a binding at weight 0, or **a bone the mesh declares that no vertex binds** — `mesh "x" declares bone "grip_b" and none of its 25 vertices binds it; the weights reference "box", "grip_a"`. Those three are one sentence about rigc's own generators: the bone set a generated mesh declares is the bone set its weights reference, so a `controls` or `chain` name that moves nothing is a defect where a foreign mesh's is not. Fix the rig spec's `controls`/`chain`, a `segments` generator's `bones` (its `influence` line names a bone no vertex binds), or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment |
|
|
6579
6578
|
| <a id="A21_MESH_RIM_PINNED"></a>`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. **SKIPs** by name on authored or linked geometry and on a `segments` mesh, whose outline is weighted by distance like the rest of it — none of them claims a pinned rim |
|
|
6580
6579
|
| <a id="A22_MESH_UVS_IN_UNIT_RANGE"></a>`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 |
|
|
6581
6580
|
| <a id="A23_PHYSICS_CONSTRAINT_EFFECTIVE"></a>`A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, rests at `mix: 0` with **no timeline in any animation keying that `mix` above 0**, has `mass: 0`, has `strength` at or below 0 — `0` says `nothing pulls it back`, below 0 says the offset `is pushed away and grows with every step`, both read off the row's `outside` arms, which the key's refusal quotes too — or has `damping` outside `[0, 1]`, which says `` physics "C" has damping 1.5; must be inside [0, 1] — the per-step decay is `damping ** (60 * step)`, and the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: above 1 every velocity grows on every step and the offset diverges ``, the bound read off the row the key's refusal reads; `1` and `0` are inside, since both are finite at every rate — **at rest, and on every physics timeline key**. The timeline arm reads each key through the runtime's own `PhysicsConstraint*Timeline.set`, so a keyed `mass` is judged as the `massInverse` it becomes, and the detail names the animation, the constraint, the key time, the value and the bound. Two differences between the two arms, and the runtime is the reason for both: a **key** of `mix: 0` is accepted, because `update` opens with `if (mix === 0) return;` and muting a constraint for a stretch is what a mix timeline is for — the editor's own `sack-pro` example keys it there on 24 of its 36 mix keys — and a **key** of `strength: 0` is accepted, because it releases the constraint for the span with `damping` and `inertia` still applied and the next key pulls the offset back, measured through spine-core at no NaN, a coast to a limit and a return in 54 steps. As a **setup** value `strength: 0` is still refused by the arm above, and `mix: 0` is refused only when nothing keys it above 0. The `mix` branch above is why `mix` is the one setup value a key can answer for: at rest the constraint is **inert** rather than broken, so a rig that rests muted and is keyed above 0 is refused by nothing, while a rig resting at `mass: 0` is `massInverse` Infinity before anything plays and no key reaches back into that. The detail of the refusal says both halves and how many animations were searched: `` physics "C" has mix 0 and none of the 3 animations keys its mix above 0; the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: at 0 `update` returns before it does anything (`PhysicsConstraint.js:109-111`), so the constraint is muted — rest it above 0, or key its mix above 0 in an animation ``. ⚠️ Every reason A23 prints for a setup value is that value's row `basis` arm (§4.4's table) — an arithmetic arm names its expression (`physics "C" has massInverse Infinity; mass must be > 0 — at 0 …`), a behavioural one says it is rigc's call and what the value does. A setup `mix` **below** 0 is told the jiggle is applied inverted, not that it is muted: such a constraint is measured moving its bone by exactly the opposite of a positive mix. The search counts the unnamed global timeline for every constraint whose own `mixGlobal` is set, reads each key through the runtime's accessor, counts every sample of a Bezier between two keys as a value the timeline poses — so two keys of 0 joined by a curve lifted above 0 are a rescue, measured to move the bone — and takes an animation a slider applies like any other. It is the one reading `A36` and `A37` use as well. `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. `ingest` does not carry a constraint that drives no component into the spec it writes: it omits it with its timelines and reports `PHYSICS_DRIVES_NOTHING` ([INGEST §2.0](INGEST.md)), so this sentence is met on a file, never on a decompiled rebuild. 🦴 **A lever drive on a bone with no lever** (issue #1195): a constraint driving `rotate`, `shearX` or `scaleX` on a bone whose `length` is 0 (or states none) is refused, at rest, with one sentence per reading. `rotate` and `shearX` chase the bone's tip, so with no tip the solver aims the bone at a **world** direction — measured through rigc's poser on a chain held still for 3 s, a length-0 bone turns 53.204° from a 90° rest and holds there, where length 0.01 moves 0.0000: `` physics "C" drives rotate on bone "B" whose length is 0 — with no tip the solver aims the bone at world angle 0 and holds it there; give "B" a length or drive x/y instead ``. The rest angle is not read: a bone resting at world 0° does not move, but only until a key or a parent turns it. `scaleX` divides the motion along the bone by its length and adds nothing at 0, so the drive does nothing: `` physics "C" drives scaleX on bone "B" whose length is 0, which the solver divides the motion by, so it drives nothing; give "B" a length ``. Fix in the **rig spec**: give the bone its `length` (§3.2), measured off the art it carries, or, for the first sentence, drive the constraint in `x`/`y` in the **motion spec** instead. `x` and `y` read the bone's origin and never its tip, so a constraint driving only those on a length-0 bone is not refused; the editor's own example exports carry them there. The Spine editor's export drops `rotate`, `scaleX` and `shearX` from a physics constraint on a zero-length bone and keeps them on any bone with a length — measured on 4.3.23 and 4.3.26, issue #1196 — so what this clause refuses is also what the editor would silently lose. **SKIP** when the skeleton declares no physics constraint — the same sentence `A36` and `A37` print for their own constraint types |
|
|
6582
6581
|
| <a id="A24_AXIS_SPACE_STROKE"></a>`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 |
|
|
6583
6582
|
| <a id="A25_DETACHED_BONE_PARENTAGE"></a>`A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
6584
6583
|
| <a id="A26_SLOT_DRAW_ORDER"></a>`A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not the rig's slot table — a slot is out of order, is not in the table at all, or is in the table and missing from the skeleton (§3.3). **SKIP** when the rig declares no canonical slot order. ⚠️ A skeleton with **no** slot beside a rig that declares some is **not** a skip, and it is the one rule in this family where an empty loop is not a vacuous pass: the completeness clause reads it as every declared slot lost and names them |
|
|
6585
|
-
| <a id="A27_REGION_NAME_MATCHES_PAGE_FILENAME"></a>`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 |
|
|
6586
6584
|
| <a id="A28_RIBBON_ROWS_SHARE_WEIGHTS"></a>`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, a contour mesh and a `segments` lattice — none has rows rigc paired |
|
|
6587
6585
|
| <a id="A29_STROKE_WITHIN_CONTACT_DEPTH"></a>`A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
6588
6586
|
| <a id="A30_STROKE_WITHIN_CAP_CONTAINMENT"></a>`A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | the animation drives past the measured containment ceiling, or scales a bone in the axis subtree |
|
|
@@ -6738,7 +6736,7 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
6738
6736
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
6739
6737
|
Saying nothing means `spine`, so "the one you meant" is a decision either way —
|
|
6740
6738
|
the report's first line names the profile that judged it.
|
|
6741
|
-
📎 The two profiles judge attachment **naming** identically: `
|
|
6739
|
+
📎 The two profiles judge attachment **naming** identically: `rig-play` adds
|
|
6742
6740
|
the renderer and archetype rules and has no opinion about how an attachment is
|
|
6743
6741
|
spelled, so a rig whose named skins share a placeholder (§3.4.9) is green
|
|
6744
6742
|
under either.
|
|
@@ -6759,7 +6757,7 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
6759
6757
|
3. Read the `PROF` lines. They are where "was this rig held to that rule at all"
|
|
6760
6758
|
gets answered for everything the profile left out — the renderer policy *and*
|
|
6761
6759
|
the archetype rules. A green under `spine` has been held to neither; a green
|
|
6762
|
-
under `
|
|
6760
|
+
under `rig-play` has been held to both.
|
|
6763
6761
|
4. Run `explain` and read the slots table: every slot you declared **is** there
|
|
6764
6762
|
(§3.3 — `A26_SLOT_DRAW_ORDER` holds that, and a slot nothing fills reads
|
|
6765
6763
|
`setup=null attachments=[]`), so what this step is for is the two things the
|
package/docs/FACE.md
CHANGED
|
@@ -539,7 +539,7 @@ which `rigc explain` prints as a parent column (§9.2).
|
|
|
539
539
|
⭐ **The same reasoning puts the head's pivot one link above the mesh.** A head
|
|
540
540
|
rotates about the top of the neck, not about the middle of its own face, so the
|
|
541
541
|
roll belongs on a `headroll` bone above `head`. There is a rule that enforces it
|
|
542
|
-
under `--profile
|
|
542
|
+
under `--profile rig-play`: `A15_IDLE_NO_MESH_BONE_KEYS` refuses an `idle` that
|
|
543
543
|
keys a bone driving a mesh — its own slot bone or its control bone — because that
|
|
544
544
|
renderer must never idle-skip a mesh. Keying the pivot one link up satisfies it,
|
|
545
545
|
and **the rig that satisfies the assertion is the better rig anyway.**
|
|
@@ -850,13 +850,13 @@ counter-scale the turn stops reading at about **18°**; with it, about **26°**.
|
|
|
850
850
|
Eight degrees of usable range for two tracks, and the record reports nothing else
|
|
851
851
|
in the experiment came close to that ratio.
|
|
852
852
|
|
|
853
|
-
⚠️ **A `clipping` attachment would lift the iris's travel ceiling and
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
853
|
+
⚠️ **A `clipping` attachment would lift the iris's travel ceiling, and the worked
|
|
854
|
+
example has none.** Spine has them (AUTHORING §3.4) and both profiles build one
|
|
855
|
+
since issue #1404 retired A11, but the worked example was drawn while A11 refused
|
|
856
|
+
a clip under `--profile rig-play`. Its ceiling is therefore geometric: the
|
|
857
857
|
iris's ink ring has radius 28.5 against a socket opening 36 half-wide, so
|
|
858
858
|
`36 − 28.5 = 7.5` units is as far as it can travel before it crosses its own lash.
|
|
859
|
-
⇒ **
|
|
859
|
+
⇒ **Without a clip, compute that ceiling from the art before keying a gaze**; the first draft of
|
|
860
860
|
the worked example keyed 9 and had to come back to 7.
|
|
861
861
|
|
|
862
862
|
---
|
|
@@ -1473,7 +1473,7 @@ bun install # once
|
|
|
1473
1473
|
|
|
1474
1474
|
bun cli.ts build --rig gallery/portrait/rig.json \
|
|
1475
1475
|
--motion gallery/portrait/motion.json \
|
|
1476
|
-
--out gallery/portrait/build --profile
|
|
1476
|
+
--out gallery/portrait/build --profile rig-play
|
|
1477
1477
|
bun cli.ts render --candidate gallery/portrait/build --fps 25 --max 640 \
|
|
1478
1478
|
--out gallery/portrait/render
|
|
1479
1479
|
```
|
|
@@ -1481,8 +1481,8 @@ bun cli.ts render --candidate gallery/portrait/build --fps 25 --max 640 \
|
|
|
1481
1481
|
```
|
|
1482
1482
|
MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
1483
1483
|
MESH hair_bang authored 15 vertices / 16 triangles (budget 32) bones=[bang] attachments=[hair_bang] covers 100.00% of the art, reaching 55.22px past it
|
|
1484
|
-
.. validate (spine-core round trip + machine assertions, profile
|
|
1485
|
-
.. profile
|
|
1484
|
+
.. validate (spine-core round trip + machine assertions, profile rig-play)
|
|
1485
|
+
.. profile rig-play — every assertion applies
|
|
1486
1486
|
```
|
|
1487
1487
|
|
|
1488
1488
|
Green — the profile line above says so in the tool's own words.
|
|
@@ -1515,7 +1515,7 @@ for (const k of d.keys) if (k.transform) {
|
|
|
1515
1515
|
await Bun.write("/tmp/swapped.motion.json", JSON.stringify(m, null, 2));
|
|
1516
1516
|
'
|
|
1517
1517
|
bun cli.ts build --rig gallery/portrait/rig.json --motion /tmp/swapped.motion.json \
|
|
1518
|
-
--out /tmp/swapped --profile
|
|
1518
|
+
--out /tmp/swapped --profile rig-play
|
|
1519
1519
|
|
|
1520
1520
|
# (b) FOLD IT: evaluate the same closed form at 40°, past the 31.37° of §4.2,
|
|
1521
1521
|
# so the two far columns swap order and the mesh turns inside out. Since
|
|
@@ -1527,23 +1527,23 @@ for (const k of d.keys) if (k.transform) k.transform.degrees = 40;
|
|
|
1527
1527
|
await Bun.write("/tmp/folded.motion.json", JSON.stringify(m, null, 2));
|
|
1528
1528
|
'
|
|
1529
1529
|
bun cli.ts build --rig gallery/portrait/rig.json --motion /tmp/folded.motion.json \
|
|
1530
|
-
--out /tmp/folded --profile
|
|
1530
|
+
--out /tmp/folded --profile rig-play
|
|
1531
1531
|
```
|
|
1532
1532
|
|
|
1533
1533
|
**What comes back from both:**
|
|
1534
1534
|
|
|
1535
1535
|
| | good | (a) one band inverted | (b) mesh folded |
|
|
1536
1536
|
| --- | --- | --- | --- |
|
|
1537
|
-
| `--profile
|
|
1537
|
+
| `--profile rig-play`, **without `A39`** | green | **green, and the same counts** | **green, and the same counts** |
|
|
1538
1538
|
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | PASS | **PASS** | **PASS** |
|
|
1539
1539
|
| the `MESH` coverage line | 100.00%, 95.90px past | **byte-identical** | **byte-identical** |
|
|
1540
1540
|
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | PASS | PASS | **FAIL, both keys, 8 of 32 triangles** |
|
|
1541
|
-
| `--profile
|
|
1541
|
+
| `--profile rig-play`, **with `A39`** | green | green | **refused, and nothing written** |
|
|
1542
1542
|
| the `DEFORM` block, `head` key 1 `area` | x0.637174 … x1.319122 | **x0.765250 … x1.362834** | **x−0.288121 … x1.820211** |
|
|
1543
1543
|
| … and its `winding` | 32 of 32 kept | 32 of 32 kept | **24 of 32 kept** |
|
|
1544
1544
|
|
|
1545
1545
|
🚨 **All three were green, and the coverage line is the same string in all three,
|
|
1546
|
-
because it reports the SETUP pose.** The `--profile
|
|
1546
|
+
because it reports the SETUP pose.** The `--profile rig-play` row with `A39` is
|
|
1547
1547
|
the only verdict that separates them, and the two rows above it measure only the
|
|
1548
1548
|
setup geometry: `A35` is silent about build (b). `A35` checks that a deform run *fits* its
|
|
1549
1549
|
attachment — an honest and useful check, and orthogonal to whether the numbers in
|
|
@@ -1609,7 +1609,7 @@ bun cli.ts explain --rig gallery/portrait/rig.json \
|
|
|
1609
1609
|
legitimate counter-example is the fold, which is `A39`'s.
|
|
1610
1610
|
|
|
1611
1611
|
**`A39_DEFORM_KEEPS_TRIANGLE_WINDING` closes the half of that gap the fold
|
|
1612
|
-
lives in** — `--profile
|
|
1612
|
+
lives in** — `--profile rig-play`. Build (b) above is refused
|
|
1613
1613
|
by name, on both its keys, with the triangles listed:
|
|
1614
1614
|
|
|
1615
1615
|
```
|
|
@@ -1711,7 +1711,7 @@ bun cli.ts check --candidate /tmp/folded --frames gallery/portrait/re
|
|
|
1711
1711
|
```
|
|
1712
1712
|
|
|
1713
1713
|
⚠️ **The third of those needs a build §9.2's own command will not write.** Build (b)
|
|
1714
|
-
under `--profile
|
|
1714
|
+
under `--profile rig-play` is refused and nothing
|
|
1715
1715
|
lands in `/tmp/folded` — which is the row above it in §9.2's table, working. Take
|
|
1716
1716
|
that candidate from the **default** profile, where `A39` reads `PROF` and the
|
|
1717
1717
|
artifact is written — a profile selects which assertions apply and not what is
|
|
@@ -1835,12 +1835,12 @@ it come off:
|
|
|
1835
1835
|
MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
1836
1836
|
MESH hair_bang authored 15 vertices / 16 triangles (budget 32) bones=[bang] attachments=[hair_bang] covers 100.00% of the art, reaching 55.22px past it
|
|
1837
1837
|
.. validate (spine-core round trip + machine assertions, profile spine)
|
|
1838
|
-
.. profile spine —
|
|
1838
|
+
.. profile spine — 6 renderer-policy and 8 archetype assertion(s) do not apply
|
|
1839
1839
|
```
|
|
1840
1840
|
|
|
1841
1841
|
`spine` is the default, so that is the `build` above with no `--profile` on it —
|
|
1842
1842
|
the report names what the profile leaves out rather than this page counting it.
|
|
1843
|
-
§9.2 runs the same rig under `--profile
|
|
1843
|
+
§9.2 runs the same rig under `--profile rig-play` and quotes what comes back
|
|
1844
1844
|
there; `A13_MESH_BUDGET` and `A15_IDLE_NO_MESH_BONE_KEYS` are among the rules
|
|
1845
1845
|
the default profile excludes.
|
|
1846
1846
|
|
package/docs/INGEST.md
CHANGED
|
@@ -137,7 +137,7 @@ rigc validate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
|
137
137
|
```
|
|
138
138
|
rigc validate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
139
139
|
.. atlas …/examples/3-timing-and-spacing/export/3-timing-and-spacing.atlas
|
|
140
|
-
.. profile spine —
|
|
140
|
+
.. profile spine — 6 renderer-policy and 8 archetype assertion(s) do not apply
|
|
141
141
|
PASS A07_ATLAS_TEXT_SHAPE
|
|
142
142
|
PASS A00_ROUNDTRIP_PARSE
|
|
143
143
|
PASS A16_SKELETON_VERSION_4_3
|
|
@@ -168,7 +168,7 @@ Read it as three separate statements, because they answer three different questi
|
|
|
168
168
|
timeline, no event timeline, no constraint timeline, no deform timeline, no mesh
|
|
169
169
|
attachment, no physics constraint, and no bounding box, clipping attachment or
|
|
170
170
|
path. ⭐ What passes over an empty list is the other kind of rule — `A01`, `A02`,
|
|
171
|
-
`
|
|
171
|
+
`A12`, `A14` ask *how many of this does the file carry*, and **zero is the
|
|
172
172
|
answer**.
|
|
173
173
|
- **`PROF`** — the rule was excluded by the profile before its body ran. §3.3.
|
|
174
174
|
|
|
@@ -985,29 +985,29 @@ prints a `CONSUMER_DRIVEN_MIX` judgement (§2.0), and `build` then SKIPs it by n
|
|
|
985
985
|
runtime is content and the burden is on the rule. Reporting that is a better answer
|
|
986
986
|
than a quietly edited export.
|
|
987
987
|
|
|
988
|
-
### 3.3 Profile choice, and the
|
|
988
|
+
### 3.3 Profile choice, and the fourteen rules that will not fire
|
|
989
989
|
|
|
990
990
|
`--profile spine` is the default and answers *"is this valid Spine 4.3 that any
|
|
991
991
|
runtime plays correctly?"* — and it is the right profile for foreign data, because the
|
|
992
992
|
other one is this project's own renderer and archetype policy.
|
|
993
993
|
|
|
994
|
-
**
|
|
994
|
+
**Fourteen assertions do not run under `spine`, and they come back `PROF`, not
|
|
995
995
|
`SKIP`:**
|
|
996
996
|
|
|
997
997
|
| Excluded as | Rules |
|
|
998
998
|
| --- | --- |
|
|
999
|
-
| **renderer policy** (
|
|
999
|
+
| **renderer policy** (6) | `A12_NO_DARK_COLOR`, `A13_MESH_BUDGET`, `A14_NO_FULL_FRAME_MESH`, `A15_IDLE_NO_MESH_BONE_KEYS`, `A19_OVERLAY_PNGS_HAVE_ALPHA`, `A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` |
|
|
1000
1000
|
| **archetype policy** (8) | `A21_MESH_RIM_PINNED`, `A24_AXIS_SPACE_STROKE`, `A25_DETACHED_BONE_PARENTAGE`, `A26_SLOT_DRAW_ORDER`, `A28_RIBBON_ROWS_SHARE_WEIGHTS`, `A29_STROKE_WITHIN_CONTACT_DEPTH`, `A30_STROKE_WITHIN_CAP_CONTAINMENT`, `A39_DEFORM_KEEPS_TRIANGLE_WINDING` |
|
|
1001
1001
|
|
|
1002
1002
|
Two further rules — **`A06`** and **`A20`** — are *mixed*: their validity clauses run
|
|
1003
|
-
in both profiles and their policy clauses only under `
|
|
1003
|
+
in both profiles and their policy clauses only under `rig-play`. `A06`'s
|
|
1004
1004
|
size-vs-PNG check is validity; rotation and premultiplied alpha are policy (two
|
|
1005
1005
|
regions over the same texels was a policy clause of `A06` until it became `A49`). `A20`'s weight coherence is validity; requiring a mesh to be
|
|
1006
1006
|
weighted at all is policy.
|
|
1007
1007
|
|
|
1008
|
-
⚠️ **`--profile
|
|
1008
|
+
⚠️ **`--profile rig-play` on foreign data produces a wall of failures that mean
|
|
1009
1009
|
nothing about the file.** Same `spineboy-pro.json`, same atlas, one flag changed — the
|
|
1010
|
-
run ends `rigc:
|
|
1010
|
+
run ends `rigc: 12 assertion(s) failed`, and this is the tally with one real message
|
|
1011
1011
|
per rule — `A06` takes a page that is one part covering it exactly *or a tiling of
|
|
1012
1012
|
regions*, so a packed atlas passes both profiles and the wall is policy only:
|
|
1013
1013
|
|
|
@@ -1015,11 +1015,12 @@ regions*, so a packed atlas passes both profiles and the wall is policy only:
|
|
|
1015
1015
|
| --- | --- | --- |
|
|
1016
1016
|
| **10** | `A15_IDLE_NO_MESH_BONE_KEYS` | `idle keys bone "front-shoulder", which drives a mesh — meshes never idle-skip` |
|
|
1017
1017
|
| **2** | `A20_MESH_WEIGHTS_COHERENT` | `mesh "front-shin" is unweighted; the ring tier drives meshes by bones` |
|
|
1018
|
-
| **1** | `A11_NO_CLIPPING_ATTACHMENTS` | `1 clipping attachment(s); the renderer skips them silently` |
|
|
1019
1018
|
|
|
1020
1019
|
Every one of those is a correct statement about a correct file: a bone *does* key a
|
|
1021
|
-
mesh, a mesh *is* unweighted
|
|
1022
|
-
|
|
1020
|
+
mesh, and a mesh *is* unweighted. (A third line named the export's clipping
|
|
1021
|
+
attachment until issue #1404 retired that rule: the player `rig-play` names draws
|
|
1022
|
+
clips.)
|
|
1023
|
+
⇒ **Do not run `rig-play` against
|
|
1023
1024
|
somebody's export unless they asked whether it satisfies this project's renderer
|
|
1024
1025
|
policy**, which is a different question from whether their file is valid.
|
|
1025
1026
|
|
|
@@ -1263,10 +1264,11 @@ Three things to read out of that, in order:
|
|
|
1263
1264
|
something is invisible to every measure in that report. Pair it with a `check` against
|
|
1264
1265
|
frames rendered from the original.
|
|
1265
1266
|
|
|
1266
|
-
⚠️ **Do not rename toward what a rule seems to want.** `
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1267
|
+
⚠️ **Do not rename toward what a rule seems to want.** A `rig-play` policy rule
|
|
1268
|
+
(§3.3) does not fire under the default profile, and renaming somebody's attachments to
|
|
1269
|
+
satisfy a policy they never opted into is a change with no benefit to them. (This
|
|
1270
|
+
paragraph's example was A27, region name against page filename, until issue #1404
|
|
1271
|
+
retired it: no reader joins a region to its page's file name.)
|
|
1270
1272
|
|
|
1271
1273
|
### 4.3 Extending a foreign skeleton with a new animation
|
|
1272
1274
|
|
package/docs/RIGGING.md
CHANGED
|
@@ -572,7 +572,7 @@ The same rig keys `headroll` and never `head`: *"a head rotates about the top of
|
|
|
572
572
|
neck, not about the middle of its own face."* A renderer policy assertion
|
|
573
573
|
(`A15_IDLE_NO_MESH_BONE_KEYS`, AUTHORING §5) forced that answer independently — two
|
|
574
574
|
arguments, one bone. ⚠️ **It is a renderer rule, so like §10.3's `A25` it only fires
|
|
575
|
-
under `--profile
|
|
575
|
+
under `--profile rig-play`**; under `--profile spine` it reports `PROF` and the
|
|
576
576
|
structural argument is the only one you get. [FACE.md](FACE.md) §3 is the same bone
|
|
577
577
|
from the mesh's side.
|
|
578
578
|
|
|
@@ -1201,7 +1201,7 @@ Declare the pair in `invariants.detached` (AUTHORING §3.7) and it fires:
|
|
|
1201
1201
|
|
|
1202
1202
|
```bash
|
|
1203
1203
|
rigc build --rig detached.rig.json --motion stack.motion.json --images parts \
|
|
1204
|
-
--out det --profile
|
|
1204
|
+
--out det --profile rig-play
|
|
1205
1205
|
```
|
|
1206
1206
|
|
|
1207
1207
|
```
|
|
@@ -1414,7 +1414,7 @@ rigc build --rig stack.rig.json --motion stack.motion.json --images parts --out
|
|
|
1414
1414
|
# .. pages=4 regions=4 bones=6 slots=5 animations=4 version=4.3.13 … profile=spine
|
|
1415
1415
|
```
|
|
1416
1416
|
|
|
1417
|
-
✅ It is green under **both** profiles, so `--profile
|
|
1417
|
+
✅ It is green under **both** profiles, so `--profile rig-play` is available for
|
|
1418
1418
|
§10.3 without any unrelated failure in the report.
|
|
1419
1419
|
|
|
1420
1420
|
### The three variants the sections build
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.0",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@ names the reversed triangles with their signed areas. `invariants.deformMayFold`
|
|
|
37
37
|
in the rig spec is the one declared exemption, so reach for it when the slot
|
|
38
38
|
folds on purpose and never to quiet a refusal you have not read. It is an
|
|
39
39
|
**archetype** rule: a `--profile spine` build prints `PROF` for it and says
|
|
40
|
-
nothing about the fold, so author under `--profile
|
|
40
|
+
nothing about the fold, so author under `--profile rig-play`.
|
|
41
41
|
|
|
42
42
|
What `A39` still cannot say is whether the projection was the right one —
|
|
43
43
|
nothing measures whether 12° was the angle the shot wanted. FACE §9.2 is that
|