spine-rigc 0.25.6 → 0.27.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 +5 -5
- package/cli.ts +25 -9
- package/docs/AUTHORING.md +187 -34
- package/docs/INGEST.md +20 -3
- package/docs/SPEC_COVERAGE.md +9 -6
- package/package.json +1 -1
- package/src/compile.ts +405 -76
- package/src/diff.ts +14 -1
- package/src/generation.ts +138 -0
- package/src/ingest.ts +313 -14
- package/src/render.ts +69 -7
- package/src/rig.ts +159 -31
- package/src/types.ts +25 -0
- package/src/validate.ts +622 -59
package/README.md
CHANGED
|
@@ -533,9 +533,9 @@ first three work on any reference you have, and `bench` is a repository workflow
|
|
|
533
533
|
and `bun run fetch-examples`. The reasoning behind them is in
|
|
534
534
|
[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
535
535
|
|
|
536
|
-
`build` and `validate` both default to `--profile spine` — the
|
|
536
|
+
`build` and `validate` both default to `--profile spine` — the 30 validity rules, which
|
|
537
537
|
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
|
|
538
|
-
adds all
|
|
538
|
+
adds all 45: the other 15 are one renderer's policy and one canvas budget's, and they
|
|
539
539
|
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
540
540
|
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
541
541
|
yourself: author under `--profile spine-html` and read the extra 15 as findings, and
|
|
@@ -587,7 +587,7 @@ what each is worth.
|
|
|
587
587
|
**What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
|
|
588
588
|
`.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
|
|
589
589
|
the file are **findings** with codes rather than plausible values: a construct the
|
|
590
|
-
spec format cannot hold (`
|
|
590
|
+
spec format cannot hold (`point`, a `sequence` block, an unknown field
|
|
591
591
|
on a bone, slot or constraint) is a blocker, the command exits non-zero, and both
|
|
592
592
|
specs are still written — a spec plus a list of what is missing from it beats no spec.
|
|
593
593
|
One thing it drops on purpose and says so: a path attachment's `lengths`, which is
|
|
@@ -681,7 +681,7 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
681
681
|
| 📥 **[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 |
|
|
682
682
|
| 🤖 **[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 |
|
|
683
683
|
| 🔬 **[docs/SPEC_COVERAGE.md](docs/SPEC_COVERAGE.md)** | Spine 4.3's full export surface against what rigc emits and what the official examples measurably use, with the ordered gap list |
|
|
684
|
-
| 🎓 **[the benchmark dossier](https://github.com/firejune/rigc/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
|
|
684
|
+
| 🎓 **[the benchmark dossier](https://github.com/firejune/rigc/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 45 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 |
|
|
685
685
|
| 📋 [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) · [GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md) · [PILOT.md](https://github.com/firejune/rigc/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 |
|
|
686
686
|
| 🗺️ [ROADMAP.md](https://github.com/firejune/rigc/blob/main/ROADMAP.md) | where this is going, and where it has been. What 1.0 has to mean before the number is claimed — conditions rather than a feature list, because direction here comes from what users hit |
|
|
687
687
|
| 📐 [CLAUDE.md](https://github.com/firejune/rigc/blob/main/CLAUDE.md) | **the doctrine** — why the validator's messages are the product, why nothing reaches disk before green, why no number is ever invented, and what a change has to keep. [CONTRIBUTING.md](https://github.com/firejune/rigc/blob/main/CONTRIBUTING.md) calls it worth ten minutes before a first patch. Repository material |
|
|
@@ -738,7 +738,7 @@ quality."* All six, with their verdicts, are in
|
|
|
738
738
|
[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
|
739
739
|
|
|
740
740
|
The whole dossier — the yardstick, `diff` and `check` and what neither of them can
|
|
741
|
-
see, every rung, the run viewer, the
|
|
741
|
+
see, every rung, the run viewer, the 45 assertions and the selftest behind them — is
|
|
742
742
|
[docs/BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
743
743
|
Live rung status is
|
|
744
744
|
[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
package/cli.ts
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
type DeformSpan,
|
|
68
68
|
} from './src/deformmeasure.ts';
|
|
69
69
|
import { diffLines, diffSkeletons, reportedFigures, sectionFigures, type DiffReport } from './src/diff.ts';
|
|
70
|
-
import { ingest, IngestError, INGEST_GUTTERS, type IngestStage } from './src/ingest.ts';
|
|
70
|
+
import { ingest, IngestError, IngestSpecRefused, INGEST_GUTTERS, type IngestFinding, type IngestStage } from './src/ingest.ts';
|
|
71
71
|
import { copyAtlasPages } from './src/emit.ts';
|
|
72
72
|
import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, packAtlas, parseAtlasText } from './src/atlas.ts';
|
|
73
73
|
import { parseJsonWithPosition } from './src/json-position.ts';
|
|
@@ -2923,14 +2923,30 @@ function cmdIngest(flags: Record<string, string>, positional: string[]): void {
|
|
|
2923
2923
|
console.log(` .. art ${art}`);
|
|
2924
2924
|
if (specImages !== undefined) console.log(` .. images ${specImages} (the rig spec's own, from ${outDir})`);
|
|
2925
2925
|
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2926
|
+
/**
|
|
2927
|
+
* What the run has to report, however the parse went.
|
|
2928
|
+
*
|
|
2929
|
+
* 🔒 A spec the tree's own parser refuses is a **finding**, not an escape
|
|
2930
|
+
* (issue #692): the three files are written, the coded `BLOCK` line is
|
|
2931
|
+
* printed, and the exit code comes off the findings like every other run's.
|
|
2932
|
+
* The two specs are read as `unknown` because that is all this function does
|
|
2933
|
+
* with them — `JSON.stringify` — and a cast to `RigSpec` here would be this
|
|
2934
|
+
* file claiming a parse that did not happen.
|
|
2935
|
+
*/
|
|
2936
|
+
let result: { rig: unknown; motion: unknown; findings: IngestFinding[] };
|
|
2937
|
+
try {
|
|
2938
|
+
result = ingest(readJsonFile(skeletonPath), {
|
|
2939
|
+
name: flags.name ?? basename(skeletonPath, '.json'),
|
|
2940
|
+
art,
|
|
2941
|
+
images: specImages,
|
|
2942
|
+
stage,
|
|
2943
|
+
source: basename(skeletonPath),
|
|
2944
|
+
version: readVersion(),
|
|
2945
|
+
});
|
|
2946
|
+
} catch (err) {
|
|
2947
|
+
if (!(err instanceof IngestSpecRefused)) throw err;
|
|
2948
|
+
result = { rig: err.rig, motion: err.motion, findings: err.findings };
|
|
2949
|
+
}
|
|
2934
2950
|
|
|
2935
2951
|
mkdirSync(outDir, { recursive: true });
|
|
2936
2952
|
// Indent 2, which is what `compile` writes the skeleton with. One emitter
|
package/docs/AUTHORING.md
CHANGED
|
@@ -169,7 +169,7 @@ What the flags mean:
|
|
|
169
169
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
170
170
|
| `--cut` | `build`, `explain` and `validate`: look up a named cut in `--cuts <cuts.json>`, **instead of** `--rig`/`--motion`/`--out` — the two spellings are one build stated two ways and are refused together. A `cuts.json` is `{ "<name>": { "rig": …, "motion": …, "out": …, "manifest"?: … } }`, every path in it relative to the table's own file, so the table lives with the project that owns the art |
|
|
171
171
|
| `--cuts` | the `cuts.json` `--cut` names. Required beside it — `--cut` alone is refused, with no guess at where the table lives |
|
|
172
|
-
| `--profile` | `spine` = the
|
|
172
|
+
| `--profile` | `spine` = the 30 validity rules (**the default**) · `spine-html` = all 45, opt-in |
|
|
173
173
|
| `--candidate` | `check`, `bench`, `render`, `preview`, `chainfit` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton. **`vote` is the one command that takes it more than once** — repeat it 2–4 times, one per pane, labelled A, B, C, D in the order given; everywhere else a repeat is a typo and is refused |
|
|
174
174
|
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview`, 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`, so is a name that only *some* candidates have |
|
|
175
175
|
| `--record` | `vote` only: a saved vote to check against its ballot and append to the ledger, instead of writing a ballot. This is the command's second mode; it takes no `--candidate` |
|
|
@@ -208,10 +208,15 @@ part per page" flat and rigc's own pack could not satisfy it. Since
|
|
|
208
208
|
[#266](https://github.com/firejune/rigc/issues/266) that clause is **one part per
|
|
209
209
|
page OR a tiling page**, so the combination is an ordinary build — and it is the
|
|
210
210
|
only one that puts the renderer's own rulebook over shared-page sampling. What a
|
|
211
|
-
*tiling* page has to satisfy is stated where the clause is,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
*tiling* page has to satisfy under that profile is stated where the clause is,
|
|
212
|
+
§5.2's `A06` row: no two regions on one page overlapping. Rotation is still
|
|
213
|
+
refused, and that is a separate clause about rigc's packer never turning a region.
|
|
214
|
+
|
|
215
|
+
⚠️ The other half of that sentence — every region wholly inside the page it names
|
|
216
|
+
— left this profile in [#694](https://github.com/firejune/rigc/issues/694). It is
|
|
217
|
+
**validity**, so no profile switches it off: a rectangle outside its page is
|
|
218
|
+
broken for every consumer, while two regions over the same texels is something
|
|
219
|
+
correct, editor-exported data does.
|
|
215
220
|
|
|
216
221
|
### 0.1 Packing the parts onto shared pages — `--pack`
|
|
217
222
|
|
|
@@ -345,7 +350,7 @@ Four things are refused rather than warned about, because each of them otherwise
|
|
|
345
350
|
| a region name the atlas does not have | `AtlasAttachmentLoader` returns null and the part silently does not draw. The refusal lists the near misses — the usual cause is one character |
|
|
346
351
|
| a size the spec disagrees with | the same silence `A06` exists for, one link earlier: a quad sized against a region of another size collapses |
|
|
347
352
|
| 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 |
|
|
348
|
-
| a rectangle that runs off its page | `x + width` past the page width makes `u2 > 1`, which samples whatever the wrap mode does |
|
|
353
|
+
| a rectangle that runs off its page | `x + width` past the page width makes `u2 > 1`, which samples whatever the wrap mode does. The gate names the same rectangle, under every profile, for a pack that reaches it without passing through here — `A06`, §5.2 ([#694](https://github.com/firejune/rigc/issues/694)) |
|
|
349
354
|
|
|
350
355
|
One limit, stated rather than discovered:
|
|
351
356
|
|
|
@@ -491,10 +496,27 @@ the first:
|
|
|
491
496
|
|
|
492
497
|
| gutter | meaning |
|
|
493
498
|
| --- | --- |
|
|
494
|
-
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `
|
|
499
|
+
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `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 |
|
|
495
500
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
496
501
|
| `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how. 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. The header ones are cheaper: `HEADER_BOOKKEEPING` for a field the spec has no home for, `HEADER_REDERIVED` for the version string, `HEADER_ORIGIN` for an origin the source left to the format and the rebuild writes out (#622) |
|
|
497
502
|
|
|
503
|
+
⛔ **It reads one generation of the format, and a file from another one ends loud.**
|
|
504
|
+
Spine data is locked to the generation that exported it and a mismatch does not
|
|
505
|
+
throw: 4.3 takes constraints from the top-level `constraints` array alone, so a
|
|
506
|
+
4.0–4.2 file's `ik`/`transform`/`path`/`physics` arrays load as nothing at all — 1,302
|
|
507
|
+
shipped skeletons parsed on a 4.3 runtime and loaded 0 of 8,672 constraints
|
|
508
|
+
([#706](https://github.com/firejune/rigc/issues/706) row 1). So `ingest` reads
|
|
509
|
+
`skeleton.spine` before it reads a field of the file. A file from another generation is
|
|
510
|
+
a `BLOCK GENERATION_UNSUPPORTED` naming the generation, the string it was read from,
|
|
511
|
+
and what a 4.3 reader loses **on that file**: the constraints parked in those arrays
|
|
512
|
+
counted by kind, the bones carrying 4.2's `transform` where 4.3 spells `inherit`, and
|
|
513
|
+
the physics constraints omitting `inertia`/`damping`, whose default is not the same
|
|
514
|
+
number in the two. A label naming no generation rigc knows — or a header stating none —
|
|
515
|
+
is a `BLOCK GENERATION_UNKNOWN`, never rounded to the nearest: a catalog that rounded
|
|
516
|
+
handed 19 skeletons labelled `3.8.99` a 4.2 runtime and every one of them posed as NaN
|
|
517
|
+
(row 7). Reading a file with *that generation's own* defaults is #706's item 2 and is
|
|
518
|
+
not in this tool — re-export as 4.3, or transcribe by hand ([INGEST.md](INGEST.md) §2).
|
|
519
|
+
|
|
498
520
|
📝 **Do not delete the `note`.** Both written specs carry one saying the file is
|
|
499
521
|
decompiled and naming the skeleton it came from. A decompiled spec is
|
|
500
522
|
indistinguishable from an authored one by inspection, every gate here calls it green —
|
|
@@ -944,7 +966,7 @@ and the inheritance silently falls back to Normal — assertion `A02` refuses it
|
|
|
944
966
|
| `bone` | required; must be a bone this rig declares | — |
|
|
945
967
|
| `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 |
|
|
946
968
|
| `color` | `rrggbbaa` tint | opaque white |
|
|
947
|
-
| `dark` | two-colour tint, `rrggbb` | — (🚫 `A12` under `spine-html`) |
|
|
969
|
+
| `dark` | two-colour tint, `rrggbb`. The **setup** half; §4.4's `rgba2` track keys it over time and requires it | — (🚫 `A12` under `spine-html`) |
|
|
948
970
|
| `blend` | `normal` · `additive` · `multiply` · `screen` | `normal` |
|
|
949
971
|
|
|
950
972
|
✅ **Every slot you declare is emitted, in this order.** A slot nothing fills — no
|
|
@@ -1186,6 +1208,65 @@ rather than the figure it was filed over:
|
|
|
1186
1208
|
README carries the inradius arithmetic, both coverage readings, and the rim move
|
|
1187
1209
|
that settled it.
|
|
1188
1210
|
|
|
1211
|
+
**Linked mesh** ([Spine: linked meshes](http://esotericsoftware.com/spine-meshes)) —
|
|
1212
|
+
a mesh that draws **another mesh's geometry** with **its own art**. It is the type
|
|
1213
|
+
a skin variant uses: one triangulation and one set of weights, several outfits over
|
|
1214
|
+
it. Say `type: "linkedmesh"`, or put `source` on a `type: "mesh"` — the format has
|
|
1215
|
+
both spellings, they share one parser branch, and `source` is what decides between
|
|
1216
|
+
them (`SkeletonJson.ts:568-569`, `:582`), so rigc reads them the same way.
|
|
1217
|
+
|
|
1218
|
+
| Field | Meaning |
|
|
1219
|
+
| --- | --- |
|
|
1220
|
+
| `source` | **required.** The **placeholder** of the mesh whose geometry this one draws — the key it is filed under in its skin, not its `name`. A miss is refused naming the skin, the slot and what that slot holds |
|
|
1221
|
+
| `slot` | the slot the source lives in. Default: **this attachment's own slot**. Resolved by name against the rig's slots |
|
|
1222
|
+
| `skin` | the skin the source lives in. Default: **`default`** — the default skin, *not* the skin this link is written in. Resolved by name |
|
|
1223
|
+
| `timelines` | default **`true`**: the link plays the source's `deform` keys. `false` makes it its own timeline target, so only keys written against the link move it |
|
|
1224
|
+
| `image`, `path`, `width`, `height`, `color` | exactly as on a mesh — the link resolves **its own** region, which is the point of the type |
|
|
1225
|
+
|
|
1226
|
+
```json
|
|
1227
|
+
"skins": {
|
|
1228
|
+
"base": { "cloak": { "cloak": { "type": "mesh", "image": "cloak_red.png", "uvs": [], "triangles": [], "weights": [] } } },
|
|
1229
|
+
"winter": { "cloak": { "cloak": { "type": "linkedmesh", "image": "cloak_blue.png", "source": "cloak", "skin": "base" } } }
|
|
1230
|
+
}
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
🚨 **A linked mesh states no geometry of its own, and every geometry key on one is
|
|
1234
|
+
refused by name.** `uvs`, `triangles`, `vertices`, `weights`, `boneIndexing`,
|
|
1235
|
+
`hull`, `edges` and `generator` are read by **nothing**: the parser returns from
|
|
1236
|
+
the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`). Measured on
|
|
1237
|
+
a forged skeleton — a link declaring 5 uvs, 3 triangles, `hull: 5` and
|
|
1238
|
+
`edges: [0, 2]` beside a 4-vertex source loaded with the **source's** 8-long
|
|
1239
|
+
`worldVerticesLength`, 6 triangles, `hullLength` 8 and 10 edges. Nothing the author
|
|
1240
|
+
wrote reached anything and nothing said so. ⇒ The same fact is held against a
|
|
1241
|
+
skeleton rigc did **not** write, where the compiler never sees the spec:
|
|
1242
|
+
`A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` (§5.2) names the attachment, the
|
|
1243
|
+
keys and whose geometry is drawn instead, and `ingest` reports one as
|
|
1244
|
+
`ATTACHMENT_LINK_GEOMETRY` before dropping it
|
|
1245
|
+
([INGEST §2.0](INGEST.md)) — [#710](https://github.com/firejune/rigc/issues/710).
|
|
1246
|
+
|
|
1247
|
+
🚫 **A chain is refused, and so is a link to itself.** A `source` that names
|
|
1248
|
+
another linked mesh resolves in the order the file was read: measured through
|
|
1249
|
+
spine-core, the chained link loaded the full geometry with the source declared
|
|
1250
|
+
first, and `worldVerticesLength` **0**, 0 triangles and a 0x0 size with the two
|
|
1251
|
+
keys swapped in the same file — silently, both ways. A construct whose meaning
|
|
1252
|
+
depends on JSON key order is one rigc will not write. Point `source` at the mesh.
|
|
1253
|
+
|
|
1254
|
+
⚠️ **`width`/`height` are emitted and the gate cannot see them.** The runtime
|
|
1255
|
+
overwrites both with the source's when it resolves the link
|
|
1256
|
+
(`MeshAttachment.setSourceMesh`; measured: a link stating `99x77` beside a 32x32
|
|
1257
|
+
source loads as 32x32). They are written because the editor reads them off the
|
|
1258
|
+
file and because the spec stated them — R1 — and no assertion can check them.
|
|
1259
|
+
|
|
1260
|
+
🔸 **`A21_MESH_RIM_PINNED` and `A28_RIBBON_ROWS_SHARE_WEIGHTS` leave a link out**,
|
|
1261
|
+
for the same reason they leave authored geometry out: the rim and the rows it draws
|
|
1262
|
+
are its source's and are measured there. `A21` drops it from the set it measures and
|
|
1263
|
+
**SKIPs by name** — naming the link and its source — when that leaves nothing;
|
|
1264
|
+
`A28` passes over it. `A04`, `A20` and `A22` read a link exactly as they read any
|
|
1265
|
+
other mesh, because after the round trip it **is** the source's triangles, weights
|
|
1266
|
+
and uvs. `A13_MESH_BUDGET` counts it as a mesh of its own: the runtime draws it as
|
|
1267
|
+
one, so a link in a second slot is a second mesh slot against
|
|
1268
|
+
`invariants.meshSlots`.
|
|
1269
|
+
|
|
1189
1270
|
The generators are `ring`, `ribbon`, `contour` and `grid` (see
|
|
1190
1271
|
[`src/mesh.ts`](../src/mesh.ts)); the first two encode a deformation model rather
|
|
1191
1272
|
than a table of numbers, which is why they are code invoked by data. The last two
|
|
@@ -1968,6 +2049,20 @@ rigc emits all five: `ik`
|
|
|
1968
2049
|
and `slider`. Field lists are in [`src/rig.ts`](../src/rig.ts); the traps worth
|
|
1969
2050
|
carrying here:
|
|
1970
2051
|
|
|
2052
|
+
- 🔑 **A constraint name is unique per KIND, not across the array.** Spine
|
|
2053
|
+
resolves one with `SkeletonData.findConstraint(name, type)`, which tests
|
|
2054
|
+
`constraint instanceof type` **before** it compares the name, and every
|
|
2055
|
+
resolution in the format goes through it: a timeline group, a skin's member
|
|
2056
|
+
list (§3.4.1), a slider's animation. So an `ik` constraint and a `transform`
|
|
2057
|
+
constraint may both be called `leg` — the editor exports both, the runtime
|
|
2058
|
+
finds each from its own group, and a motion spec's `ik` block, `transform`
|
|
2059
|
+
block and `path`/`physics`/`slider` tracks each name the kind they mean and
|
|
2060
|
+
resolve the same way (§4.9, §4.12). Two constraints **of one kind** sharing a
|
|
2061
|
+
name are refused (§5.1), because no timeline could say which was meant. Until
|
|
2062
|
+
[#692](https://github.com/firejune/rigc/issues/692) the rig spec kept one
|
|
2063
|
+
namespace over the whole array, so a rig the editor exports and the runtime
|
|
2064
|
+
plays — an IK chain and the transform constraint that follows it, both carrying
|
|
2065
|
+
the chain's name — could not be written down at all.
|
|
1971
2066
|
- A transform constraint's `properties` names come from a fixed six — `rotate`,
|
|
1972
2067
|
`x`, `y`, `scaleX`, `scaleY`, `shearY`. rigc refuses anything else by name; in
|
|
1973
2068
|
raw JSON the parser throws.
|
|
@@ -2759,6 +2854,7 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2759
2854
|
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
2760
2855
|
| `bone` | `translatex`, `translatey`, `scalex`, `scaley`, `shearx`, `sheary`, `rotate` | `[value]` |
|
|
2761
2856
|
| `slot` | `rgba` | `[r, g, b, a]` in 0..1 |
|
|
2857
|
+
| `slot` | `rgba2` | `[lr, lg, lb, la, dr, dg, db]` in 0..1 — the two-colour tint, light then dark, **seven** channels. The slot must declare a setup `dark` (§3.3) |
|
|
2762
2858
|
| `slot` | `attachment` | the attachment name, or `null` for "show nothing" |
|
|
2763
2859
|
| `physics` | `inertia`, `strength`, `damping`, `mass`, `wind`, `gravity` | `[value]` — the constraint's own tuning, keyed over time |
|
|
2764
2860
|
| `physics` | `mix` | `[mix]`, **0 or more** — the constraint's authority |
|
|
@@ -2802,9 +2898,9 @@ accepts is what it accepts.
|
|
|
2802
2898
|
it actually poses, both ways, which is what makes the list checkable at all: it
|
|
2803
2899
|
was stated there too until the refusal had something to state.
|
|
2804
2900
|
|
|
2805
|
-
⚠️ **A `slot` track's `property` is one of the
|
|
2901
|
+
⚠️ **A `slot` track's `property` is one of the three above, and anything else is a
|
|
2806
2902
|
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
2807
|
-
attachment, rgba)`, §5.1's row. Until
|
|
2903
|
+
attachment, rgba, rgba2)`, §5.1's row. Until
|
|
2808
2904
|
[#650](https://github.com/firejune/rigc/issues/650) it was not: the emitter had a
|
|
2809
2905
|
branch for `attachment` and wrote **everything else** as an rgba timeline under
|
|
2810
2906
|
the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
@@ -2813,21 +2909,33 @@ the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
|
2813
2909
|
one-channel spelling of the same mistake was refused at compile as `rgba value
|
|
2814
2910
|
needs 4 channels, got 1`, a message about a key you had not written.
|
|
2815
2911
|
|
|
2816
|
-
- The
|
|
2912
|
+
- The three are the emitter's own dispatch table (`SLOT_TRACKS` in
|
|
2817
2913
|
`src/compile.ts`): `compileTrack` reads it to pick its branch, and the refusal
|
|
2818
2914
|
prints `Object.keys` of the same object, so what you are told a slot accepts
|
|
2819
2915
|
is what it accepts.
|
|
2820
|
-
- **Nothing derives this page's copy of that
|
|
2821
|
-
|
|
2916
|
+
- **Nothing derives this page's copy of that list from the table**, and it is
|
|
2917
|
+
three names long: no `DQ*`/`RD*`/`CUR*` control reads §4.4 (the only gated
|
|
2822
2918
|
table on this page is §3.5.2.1's, held by `RD01`–`RD06`). What keeps the two
|
|
2823
2919
|
in step is the control that quotes the message — `RF23` in `selftest.ts` —
|
|
2824
|
-
which goes red if the accepted
|
|
2825
|
-
it.
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
`
|
|
2830
|
-
|
|
2920
|
+
which goes red if the accepted list ever widens without this page moving with
|
|
2921
|
+
it. It did, on the day `rgba2` was added
|
|
2922
|
+
([#690](https://github.com/firejune/rigc/issues/690)), which is the mechanism
|
|
2923
|
+
working rather than a hole in it.
|
|
2924
|
+
- 🎨 **`rgba2` keys the two-colour tint, and the slot has to own one first.** A
|
|
2925
|
+
track `{ "slot": "X", "property": "rgba2" }` on a slot whose rig spec declares
|
|
2926
|
+
no `dark` (§3.3) is a compile error with the slot named, and it is not a
|
|
2927
|
+
formality: the runtime allocates a slot's dark colour only when its setup pose
|
|
2928
|
+
has one, so a file keying it without one parses cleanly and then throws in the
|
|
2929
|
+
player the first time the animation is applied. `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN`
|
|
2930
|
+
(§5.2) holds the same pairing on a skeleton rigc did not write. 🚫 Both halves
|
|
2931
|
+
of the two-colour tint are refused under `--profile spine-html`, whose renderer
|
|
2932
|
+
ignores them; the default `spine` profile that `build` runs reports `A12` as
|
|
2933
|
+
`PROF` and never applies it.
|
|
2934
|
+
- The format has three more slot timelines (`rgb`, `alpha`, `rgb2`) and rigc
|
|
2935
|
+
emits none of them, so their names are refused here too; `A12_NO_DARK_COLOR`
|
|
2936
|
+
refuses `rgb2` — and `rgba2`, and the slot field — in a file under that
|
|
2937
|
+
renderer's profile (SPEC_COVERAGE §2.1). `sequence` is a timeline on an
|
|
2938
|
+
**attachment**, not on a slot, and rigc does not emit that either.
|
|
2831
2939
|
|
|
2832
2940
|
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
2833
2941
|
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
@@ -2845,7 +2953,7 @@ resolved against the rig.
|
|
|
2845
2953
|
with no table claiming the property the track fell through to the slot branch,
|
|
2846
2954
|
and what you were told was that the first member is not a slot — on a file that
|
|
2847
2955
|
named neither a slot nor that member. A group of **slots** got §4.4's slot row
|
|
2848
|
-
instead (`slot "M" has no timeline "P" (it has: attachment, rgba)`), which is
|
|
2956
|
+
instead (`slot "M" has no timeline "P" (it has: attachment, rgba, rgba2)`), which is
|
|
2849
2957
|
true of the member and names one family out of three on a track whose family
|
|
2850
2958
|
nothing had determined.
|
|
2851
2959
|
- **A constraint property never reaches it.** `position`, `spacing` and `time` are
|
|
@@ -3508,6 +3616,35 @@ Per key:
|
|
|
3508
3616
|
| `offset` | the same start as a raw index into the deform array. Never with `fromVertex` |
|
|
3509
3617
|
| `ease` / `curve` | one channel, and it eases the **blend**, not a coordinate |
|
|
3510
3618
|
|
|
3619
|
+
**The target is any attachment that has a vertex array** — a mesh, a bounding
|
|
3620
|
+
box, a clipping polygon, or a **path**. The array a key edits is that
|
|
3621
|
+
attachment's own, so everything below about runs, start indices and the two
|
|
3622
|
+
encodings reads the same whichever it is. A region attachment has no vertex array
|
|
3623
|
+
and is refused by name.
|
|
3624
|
+
|
|
3625
|
+
⭐ **A path's vertices are its control points** — knots and their Bezier handles
|
|
3626
|
+
alike, in the order §3.5.1 lists them — so a run covers them in that order and a
|
|
3627
|
+
`fromVertex` counts them the same way. Its deform array is `vertexCount * 2` long
|
|
3628
|
+
unweighted, and one pair per influence weighted, exactly as a mesh's is. What is
|
|
3629
|
+
and is not measured on one:
|
|
3630
|
+
|
|
3631
|
+
- `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` measures the run against that length, as
|
|
3632
|
+
it does for every other target.
|
|
3633
|
+
- `A39_DEFORM_KEEPS_TRIANGLE_WINDING` reports **SKIP**, naming the slot and
|
|
3634
|
+
saying the attachment has no triangles. A path is knots and handles; there is
|
|
3635
|
+
no winding to keep, and reporting a pass for a measurement that did not happen
|
|
3636
|
+
is the one thing an assertion here may not do.
|
|
3637
|
+
- **`lengths` is not re-measured, and cannot be.** It is a field of the
|
|
3638
|
+
attachment (§3.5.1) and the format has nowhere to put a per-key one, so the
|
|
3639
|
+
array every exporter writes — rigc's included — is the **setup** measurement.
|
|
3640
|
+
`PathConstraint.computeWorldPositions` reads it only when `constantSpeed` is
|
|
3641
|
+
`false`; under the parser's default, `true`, it re-measures the curve from the
|
|
3642
|
+
posed vertices every frame. So a path constraint follows the deformed curve as
|
|
3643
|
+
written, and under `constantSpeed: false` it traverses the deformed curve at
|
|
3644
|
+
the **setup** spacing. That is the format's behaviour rather than rigc's
|
|
3645
|
+
choice, which is why it is stated here instead of refused: an editor export of
|
|
3646
|
+
the same rig does the same thing.
|
|
3647
|
+
|
|
3511
3648
|
Four things are worth having straight before you write one.
|
|
3512
3649
|
|
|
3513
3650
|
**A key is a sparse edit, and a key with no `vertices` is the setup pose.** The
|
|
@@ -3561,6 +3698,7 @@ half of the format:
|
|
|
3561
3698
|
| `fromVertex` on a multi-bone vertex | `"fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` |
|
|
3562
3699
|
| an attachment that is not there | `slot "flat" in skin "default" has no attachment "flatt" (it has: flat)` |
|
|
3563
3700
|
| a deform on a region attachment | `a deform timeline keys the vertices of an attachment, and this one is a "region"` |
|
|
3701
|
+
| a run past a path's control points | `this attachment's deform array is 18 long (9 vertices)` — the same bound as any other target, counted in the control points §3.5.1 declares |
|
|
3564
3702
|
| `transform` beside a `vertices` run | `the key carries both a "transform" and a "vertices" run, and they are two answers to one question` |
|
|
3565
3703
|
| `transform` with `fromVertex` or `offset` | `A transform is a model of the whole attachment and is evaluated over all 25 of its vertices, so it always starts at deform index 0` |
|
|
3566
3704
|
| a `transform` on an attachment whose weights do not close at 1 | `this one has a vertex the arithmetic cannot place — vertex 1's 2 weights sum to 0.9000 rather than 1` |
|
|
@@ -4438,6 +4576,9 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4438
4576
|
| `a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)` | §3.3 — write `[]` for a rig that draws nothing. ⚠️ These three arrive only when the key is really absent: **misspelt**, it is the unknown-key refusal above, naming what you wrote |
|
|
4439
4577
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
4440
4578
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
4579
|
+
| `two ik constraints are called "X" — a constraint resolves by name AND type (\`SkeletonData.findConstraint\`), so names are unique PER KIND: an ik and a transform constraint may share one, two of a kind may not` | §3.5 — rename one of the two. The kind in the sentence is the pair's own, so `two transform constraints are called "X"` is the same refusal on another kind; a name shared **across** kinds is not this error and never was one to fix |
|
|
4580
|
+
| `physics constraint "X" is declared in both the rig spec and the motion spec's physics table` | §4.6 — the rig spec declares a physics constraint's structure and the motion spec's `physics` table declares one outright; pick the file it belongs in. Per kind, like every other constraint name: an `ik` "X" in the rig spec beside a `physics` "X" here is two constraints and is not this error |
|
|
4581
|
+
| `skin "S" activates ik constraint "X", which skin "T" already activates; a constraint belongs to one skin` | §3.4.1 — a constraint runs under one skin or under all of them. The kind is in the sentence because `ik` "X" and `transform` "X" are two constraints, and each may belong to a different skin |
|
|
4441
4582
|
| `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
|
|
4442
4583
|
| `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 |
|
|
4443
4584
|
| `the setup pose shows attachment "A" on slot "X", which no skin and no manifest part fills` | §3.3 — the slot is emitted empty and nothing was ever going to fill it, so `A` resolves to nothing. Give the slot an attachment (a skin entry or a manifest part), or state the setup pose as `null` |
|
|
@@ -4446,7 +4587,14 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4446
4587
|
| `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 |
|
|
4447
4588
|
| `"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 |
|
|
4448
4589
|
| `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) |
|
|
4449
|
-
| `this attachment is a "
|
|
4590
|
+
| `this attachment is a "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 |
|
|
4591
|
+
| `a linked mesh needs "source" — the PLACEHOLDER of the mesh whose geometry it draws …` | §3.4 — `source` is what MAKES a mesh linked, and the parser falsy-tests it, so an absent or empty one is read as an ordinary mesh and throws on the `uvs` a link has not got |
|
|
4592
|
+
| `a linked mesh states "uvs", "triangles", …, and a linked mesh has no geometry of its own` | §3.4 — remove them, or remove `source` and author this as a mesh. The parser returns before `readVertices`, so those keys are read by nothing at all |
|
|
4593
|
+
| `"source" is "X", and skin "S" … slot "L" … holds 2: "a", "b"` | §3.4 — `source` is the PLACEHOLDER the source is filed under, not its `name`. A clause after the skin and after the slot says whether each was stated or taken from the parser's default — **the default skin** and **this attachment's own slot**, which is the pair that surprises |
|
|
4594
|
+
| `"slot" is "X", which the rig does not declare as a slot` / `"skin" is "X", … the rig declares no such skin` | §3.4 — a link resolves both by name. Left to the round trip these are the runtime's `Source mesh slot not found` and `Skin not found`, which name neither the attachment nor where it looked |
|
|
4595
|
+
| `"source" is "X", which is itself a linked mesh, and a chain of them is refused` | §3.4 — point `source` at the mesh. A chain resolves in file order and loads nothing at all in one of the two orders, silently |
|
|
4596
|
+
| `"source" is "X", which is a "region" attachment and not a mesh` | §3.4 — a link takes another MESH's geometry; off any other type the runtime reads `undefined` and says nothing |
|
|
4597
|
+
| `a linked mesh needs width and height — give them, or give an "image" and rigc will measure the PNG` | §3.4 — the mesh rule, on a link. Its art is its own |
|
|
4450
4598
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
4451
4599
|
| `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 |
|
|
4452
4600
|
| `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 |
|
|
@@ -4498,7 +4646,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4498
4646
|
| `animation "A" keys "X" as an ik constraint, but the rig declares it as a "transform" constraint` | §4.9 — a timeline's target resolves by name AND type; put the entry under the right group |
|
|
4499
4647
|
| `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 |
|
|
4500
4648
|
| `ik constraint "X" (t=…): mix is 1.5, outside 0..1` | §4.9 — an IK mix is a percentage; a transform mix is unbounded |
|
|
4501
|
-
| `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 |
|
|
4649
|
+
| `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 vertices)` | §4.11 — shorten the run or move its start; the parser would drop the tail in silence. The count in brackets is the **target's own**: a mesh's or a path's vertices, or a weighted attachment's bone influences |
|
|
4502
4650
|
| `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` |
|
|
4503
4651
|
| `deform …: slot "X" in skin "default" has no attachment "Y" (it has: …)` | §4.11 — fix the placeholder name |
|
|
4504
4652
|
| `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 |
|
|
@@ -4525,9 +4673,11 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4525
4673
|
| `animation "A" keys "X" as a path constraint, but the rig declares it as a "slider"` | §4.12 — a timeline group resolves by name AND type; use the field named after the constraint's own type |
|
|
4526
4674
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
4527
4675
|
| `rgba value needs 4 channels, got 3` | §4.4 — an `rgba` key is `[r, g, b, a]`. It names no animation, slot or key time, and the only input that reaches it is a slot `rgba` key: the setup pose's `color` is refused earlier, by its own row, with the slot named |
|
|
4676
|
+
| `rgba2 value needs 7 channels, got 6` | §4.4 — an `rgba2` key is `[lr, lg, lb, la, dr, dg, db]`: the light colour with its alpha, then the dark colour **without** one. Six is the commonest way to get it wrong, because the dark half looks like it should take an alpha too — the format has no channel for it, and neither does the runtime's `setFrame`. Like the row above it names no animation or key time; the only input that reaches it is a slot `rgba2` key |
|
|
4528
4677
|
| `animation "A" bone "B" has no timeline "P" (it has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate)` | §4.4 — a bone has exactly ten timelines and `P` is none of them. Fix the spelling — the single-axis ones are lower-case (`translatex`, not `translateX`). A **constraint** property is refused first, by its own row, naming the field its constraint's name goes in. When `P` is a slot timeline the message says so and where to put the name: `. "rgba" is a slot timeline — put the name in "slot"`. Before [#656](https://github.com/firejune/rigc/issues/656) all of them read `bone "B" cannot take slot property "P"`, which named the slot family whatever you had written and listed nothing |
|
|
4529
|
-
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate; a slot group has: attachment, rgba; a physics constraint group has: inertia, strength, damping, mass, wind, gravity, mix, reset)` | §4.3, §4.4 — a group's family is decided by the property, and `P` is in none of the three tables, so there is no family to resolve the members as. Fix the spelling and the group becomes whichever family the property names. The group is refused before its members are looked up, so a member the rig does not declare is a **later** message; an unknown group NAME is an earlier one. Before [#661](https://github.com/firejune/rigc/issues/661) a group of bones read `animation "A" targets unknown slot "M"` and a group of slots got the slot row below, naming one family out of three |
|
|
4530
|
-
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba)` | §4.4 — a slot has exactly
|
|
4678
|
+
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate; a slot group has: attachment, rgba, rgba2; a physics constraint group has: inertia, strength, damping, mass, wind, gravity, mix, reset)` | §4.3, §4.4 — a group's family is decided by the property, and `P` is in none of the three tables, so there is no family to resolve the members as. Fix the spelling and the group becomes whichever family the property names. The group is refused before its members are looked up, so a member the rig does not declare is a **later** message; an unknown group NAME is an earlier one. Before [#661](https://github.com/firejune/rigc/issues/661) a group of bones read `animation "A" targets unknown slot "M"` and a group of slots got the slot row below, naming one family out of three |
|
|
4679
|
+
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba, rgba2)` | §4.4 — a slot has exactly three timelines and `P` is none of them. Fix the spelling; a bone or constraint property written on a slot track is refused by its own row instead. Before [#650](https://github.com/firejune/rigc/issues/650) every other name compiled as an **rgba** timeline called `P`, and what you saw was `A00_ROUNDTRIP_PARSE` on the emitted file — or, for the one-channel spelling, `rgba value needs 4 channels, got 1` |
|
|
4680
|
+
| `animation "A" slot "X" rgba2: slot "X" declares no setup "dark", and an "rgba2" timeline poses a slot's dark colour …` | §3.3, §4.4 — the two-colour tint has a setup half and a keyed half, and the keyed half cannot exist without the other. `Slot`'s constructor allocates a dark colour only for a slot whose setup pose declares one, and `RGBA2Timeline` writes it unconditionally — so without the `dark` the file loads, and the first `state.apply` throws `TypeError: null is not an object` in the consumer's process. Give the slot the `dark` it holds at rest, or key `rgba` if only the light colour moves. Raised before the keys are read, with the slot named, for the same reason the row above is |
|
|
4531
4681
|
| `N pair(s) of animation names have no one order: … "turn" / "Turn" (case) — they are one name in two cases, and which of them the editor puts first is not measured; rename one of them so they differ by more than letter case` | **R10** — rename until no pair is left. The kind in brackets says which of the editor comparator's four UNMEASURED choices decides the pair: `case` (a pure case tie), `number` (one number written two ways, or a run of digits against a word) or `separator` (make the first character that differs a letter or a digit). rigc keys `animations` in the editor's own comparator — natural and case-insensitive ([#539](https://github.com/firejune/rigc/issues/539), [#543](https://github.com/firejune/rigc/issues/543)) — so a pair that comparator settles is emitted rather than refused, and only the four choices nobody has measured are a compile error; on those, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
|
|
4532
4682
|
| `N pair(s) of skin names have no one order: … "Zulu" / "mike" (case) — folded to one case "Zulu" and "mike" order the other way round, so whether the editor folds SKIN names decides this pair` | **R11** — rename until no pair is left. The same shape as the row above with a **wider** family: #539 measured the editor's comparator for animation names and thereby ruled codepoint out, and nothing has ruled anything out for skin names, so a pair the candidates could disagree about is refused even where the animation rule would emit it. `Zulu`/`mike` and `mike10`/`mike2` build as animation names and are refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) |
|
|
4533
4683
|
| `slot "patch": placeholder "patch" is filled by the "default" skin AND by skins "zulu", "mike", and the Spine editor has no way to hold that … Move the default skin's entry for this slot into a named skin — call it "base"` | **R12** — do what it says: move that entry out of `default` into a named skin. The editor has no representation for a placeholder the default skin shares with a named one, in either spelling, and §3.4.2 has both measurements. Renaming the placeholder does not help; the shape is what is refused |
|
|
@@ -4628,7 +4778,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4628
4778
|
| `A03_REGION_WIDTH_HEIGHT_FINITE` | both | a region loaded `NaN` or a non-positive size — the attachment has no `image` and no `width`/`height`. **SKIP** when the skeleton carries no region attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4629
4779
|
| `A04_MESH_TRIANGLES_AND_ENCODING` | both | authored mesh geometry: triangle count not a multiple of 3, an index out of range, or a `vertices` length that disagrees with `uvs` (the weighted/unweighted trap) **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4630
4780
|
| `A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel**. **SKIP** when no animation carries a timeline at all ([#580](https://github.com/firejune/rigc/issues/580)). Timelines with no `curve` on any key still PASS: every timeline name is checked against the channel table whether or not a curve sits on one |
|
|
4631
|
-
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk
|
|
4781
|
+
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both ◑ | the atlas `size:` disagrees with the PNG on disk, **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 ([#694](https://github.com/firejune/rigc/issues/694)): a rectangle outside its page makes `u2 > 1` and samples whatever the wrap mode returns, and `--atlas-in` already refuses the same rectangle at compile time (§0.2). Under `spine-html` also: `pma`, rotation, and two regions on one page over the same texels — a packed page must be **one part covering it exactly** (the unpacked convention) or a **tiling** ([#266](https://github.com/firejune/rigc/issues/266)), and what that message names is the pair that shares texels. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4632
4782
|
| `A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file. ⚠️ An atlas with **no page block at all** — no non-blank line — is not one of those: its subject is absent, so this reports **SKIP** naming the byte count it read, and so do the four rules below whose subject is a page ([#608](https://github.com/firejune/rigc/issues/608)). A rig whose skins need no art writes exactly that file (§3.4), and before #608 this row refused it with two findings naming a page block that was not there. What an empty atlas does **not** excuse is an attachment that wants a region out of it — that is `A08` |
|
|
4633
4783
|
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. Until [#589](https://github.com/firejune/rigc/issues/589) they were unreachable: the loader threw first and the miss arrived as `A00_ROUNDTRIP_PARSE`. There is no `spine-html` clause here any more — a placeholder is free to differ from the region its `path` names ([#574](https://github.com/firejune/rigc/issues/574)) **SKIP** when no attachment names a region *and* the atlas declares none — both of its subjects at once ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4634
4784
|
| `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 |
|
|
@@ -4641,7 +4791,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4641
4791
|
| `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`) |
|
|
4642
4792
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) — as it is for `A06`, `A19` and `A27`; see `A07` ([#608](https://github.com/firejune/rigc/issues/608)) |
|
|
4643
4793
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
4644
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)) **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4794
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)). ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page** ([#705](https://github.com/firejune/rigc/issues/705)): a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. Before #705 the walk was silent about its own reach, so a part nobody could read printed *opaque in every one of its 12x8 texels* over zero of them, which is a refusal pointing at the wrong file: the art it names may be transparent, and the repair is the rectangle in `A06`'s row above **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4645
4795
|
| `A20_MESH_WEIGHTS_COHERENT` | both ◑ | a weighted vertex with no bone, a negative weight, a bone index out of range, or weights that do not sum to 1. Under `spine-html` also: an unweighted mesh, a binding at weight 0, or **a bone the mesh declares that no vertex binds** — `mesh "x" declares bone "grip_b" and none of its 25 vertices binds it; the weights reference "box", "grip_a"`. Those three are one sentence about rigc's own generators: the bone set a generated mesh declares is the bone set its weights reference, so a `controls` or `chain` name that moves nothing is a defect where a foreign mesh's is not ([#684](https://github.com/firejune/rigc/issues/684)). Fix the rig spec's `controls`/`chain`, or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
4646
4796
|
| `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 |
|
|
4647
4797
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
@@ -4665,6 +4815,8 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4665
4815
|
| `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` | both | two or more sliders whose animations key the same timeline, where a later one is not `additive` — it writes that property outright at `mix: 1` and every earlier slider on it is dead (§3.5.2). Also fires when the shared timeline **cannot** be applied additively (a slot colour, an attachment swap, a draw order, an ik mix, a path's `spacing`, most physics properties), where `"additive": true` is not the fix and one of the two has to go. ⭐ Which of the two it is, is **posed rather than read off `Timeline.additive`**: the shared timeline is applied twice with `add` set and the detail says what it did ([#655](https://github.com/firejune/rigc/issues/655) — two classes declare that flag falsely about themselves, so a path constraint's `mix` and a slider's `time` were refused although they compose). The detail names the bone or slot and the property, every slider keying it in `constraints` order with its flag, which one wins today, and the class that was posed. Four shapes are deliberately not findings: a slider below `mix: 1` or with its `mix` keyed (the apply is then a lerp from the current pose, not an overwrite), two `skinRequired` sliders no skin activates together, two sliders on different properties, and a shared timeline that writes **nothing a pose holds** — an `events` timeline fires no event under a slider (`firedEvents` is null), so neither slider has anything there for the other to erase. **SKIP** when fewer than two sliders are at full authority; a PASS means two were compared |
|
|
4666
4816
|
| `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` | both | a physics constraint driving a component the **Spine editor** cannot hold, on a rig that declared `invariants.editorRoundTrip` (§3.7). The editor's physics model holds `x` and `y` only, with no cap on how many at once, so a constraint driving `rotate`, `scaleX` or `shearX` is imported, exported and handed back driving **nothing** — measured over three rigs and twelve constraints with the predictions written first ([#540](https://github.com/firejune/rigc/issues/540)). The detail names the constraint and each component. ⚠️ rigc's own output is correct — every runtime plays a rotation jiggle — so this is opt-in and the default is *not* silence: on a rig that declares nothing it **SKIPs**, and the SKIP names the constraint and the component anyway, so an author learns without having asked. Fix by driving the constraint in `x`/`y`, or by dropping the declaration if the rig never goes near the editor. Disjoint from `A23_PHYSICS_CONSTRAINT_EFFECTIVE` by construction: A23 refuses an **empty** driven set, which is what comes back from the editor, and this refuses a non-empty one that will not survive going in. **SKIP** also when the rig declares the editor and carries no physics constraint at all |
|
|
4667
4817
|
| `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` | both | a slider whose animation keys a property of a constraint **at or before it** in `constraints` (§3.5.2) — a slider's `mix` or `time`, an ik or transform mix, a path `position`, `spacing` or `mix`, any physics value. That array is the update order for every kind, and each constraint reads its own applied pose when its turn comes — `Slider.update` takes `mix` as the alpha it applies with and `time` as the time it applies at, `PhysicsConstraint.update` returns on `mix` 0 before reading the rest — so the key lands after the only read of it and `Posed.resetConstrained` discards it before the next frame: what the driven constraint drives is dead at every position of the driving dial, although its pose still holds the number ([#658](https://github.com/firejune/rigc/issues/658), [#665](https://github.com/firejune/rigc/issues/665)). The detail names the slider, the driven constraint with its kind, both array indices, the property, the runtime class whose `update` reads it, and the animation the key sits in. Fix by moving the driver earlier, or by keying that property from a slider that already is. **The two indices equal is the same failure**: a slider cannot key its own `mix` or `time`, and one muted at setup that keys its own `mix` up never applies anything at all — `A37` is silent there, because it asks whether *an* animation keys the mix and not which one. **Two shapes it deliberately leaves out**, both measured: a `physics` `reset` key, which fires on a crossed frame time and so never fires from a slider at all, in either order — the reorder would repair nothing; and a physics timeline naming no constraint, which is every physics constraint declaring that property global and IS refused for the ones already run. Disjoint from `A40` by construction: `A40` asks who writes a shared property last and excludes every slider whose `mix` is keyed, this asks whether anything reads what was written. **SKIP** when the skeleton declares no slider, and when no slider's animation keys a constraint property — that SKIP names any `reset` keys it found — a pass means a driver and a driven were compared |
|
|
4818
|
+
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | both | a slot's `dark` (§3.3) or an `rgba2` timeline (§4.4) that the runtime does not hold as the file states it. Three shapes, all of which parse in silence: a `dark` the slot reader **drops** — it takes the field through a truthiness test, so `""` is discarded without a word and the slot renders with one colour; a `dark` that is **not six hex digits** — `Color.setFromString` slices fixed offsets and stores whatever `parseInt` gives back, so `"4020"` loads a channel of `NaN`; and an `rgba2` timeline on a slot with **no `dark` at all**, where the runtime allocates no dark colour and the first `state.apply` throws in the consumer's process. The keyed half is read by posing: the animation is stepped to each key's own time and the posed `color` and `darkColor` are compared against the hex the key states, to half a quantisation step (`1/510`). The detail names the slot, the value found and the value required. ⚠️ The required value is parsed **here** and not through `Color.fromString`, because a check that read it out of the parser it is checking would agree with that parser whatever it did. `compile.ts` refuses the third shape outright in a rig rigc builds; this is the same fact held against a skeleton it did not write. **SKIP** when no slot declares a `dark` and no animation keys an `rgba2` — there is then no two-colour tint to read back |
|
|
4819
|
+
| `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` | both | a **linked mesh** (§3.4) — `type: "linkedmesh"`, or a `type: "mesh"` carrying `source` — that also states `uvs`, `triangles`, `vertices`, `hull` or `edges`. The parser returns from the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so those keys are read by **nothing at all** and `setSourceMesh` fills the attachment with the source's arrays instead: the file says one mesh and every runtime draws another, in silence. The detail names the attachment by skin, slot and placeholder, every key it states, the `source` and where the parser looks for it — the two defaults spelled out, because an omitted `skin` is the **default** skin rather than the one the link is written in — and the shape the keys describe beside the shape the attachment loaded. ⚠️ **`width`/`height` are not part of this.** `setSourceMesh` overwrites both with the source's, so they are as dead at runtime — but the parser reads them (`:569-570`), the format carries them on a link and rigc emits them, so refusing them would refuse every link rigc writes (§3.4). `compile.ts` refuses the same shape outright in a rig rigc builds (§5.1); this is that fact held against a skeleton it did not write, and `ingest` reports it as `ATTACHMENT_LINK_GEOMETRY` ([INGEST §2.0](INGEST.md)). **SKIP** when no attachment in the skeleton takes its geometry from another — which is almost every skeleton, so a pass here means a link was read ([#710](https://github.com/firejune/rigc/issues/710)) |
|
|
4668
4820
|
|
|
4669
4821
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
4670
4822
|
clauses are gated by profile.
|
|
@@ -4679,10 +4831,12 @@ own behaviour is worse: an unknown attachment `type` returns `null` and the
|
|
|
4679
4831
|
attachment disappears, and a constraint entry with an unrecognised `type` matches no
|
|
4680
4832
|
case and vanishes.
|
|
4681
4833
|
|
|
4682
|
-
A deferral carries its reason, and
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4834
|
+
A deferral carries its reason, and there is one deferred attachment type left:
|
|
4835
|
+
**`point` appears nowhere in the benchmark corpus** (SPEC_COVERAGE parts 3-1 and
|
|
4836
|
+
4-2), so it is not on the ladder's critical path. The message says so, because a
|
|
4837
|
+
deferral without its reason is a wall rather than a work item. `linkedmesh` stood
|
|
4838
|
+
beside it until [#691](https://github.com/firejune/rigc/issues/691) and is now
|
|
4839
|
+
emitted — §3.4 has its fields.
|
|
4686
4840
|
|
|
4687
4841
|
⚠️ **A spelling the format does not have is a different refusal and says so.**
|
|
4688
4842
|
`sequence` is not an attachment type, and a `"type"` that is `null` is not an absent
|
|
@@ -4693,13 +4847,12 @@ are `CompileError`s, and they name what the format actually defines
|
|
|
4693
4847
|
|
|
4694
4848
|
| You wrote | You get |
|
|
4695
4849
|
| --- | --- |
|
|
4696
|
-
| attachment `type` of `point`
|
|
4697
|
-
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) |
|
|
4850
|
+
| attachment `type` of `point` | `this attachment is a "point" — a position and an angle with no geometry at all — "x", "y", "rotation" and "color" …. rigc does not emit it yet, deliberately: it emits region, mesh, linkedmesh, boundingbox, clipping, path, and a point appears nowhere in the benchmark corpus …` — the message names the **construct**, not just its type string, and part 1-6 is where the sentence comes from |
|
|
4851
|
+
| a mesh carrying `source` (`type: "mesh"` **or** `type: "linkedmesh"`) | **not a refusal any more** — both spellings compile to a linked mesh (§3.4, [#691](https://github.com/firejune/rigc/issues/691)). They share one parser branch and `source` is what decides between them (SPEC_COVERAGE part 1-6), so `source` on a mesh is a linked mesh whatever `type` says. It was once 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)) |
|
|
4698
4852
|
| 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.) |
|
|
4699
4853
|
| `"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)) |
|
|
4700
4854
|
| 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 |
|
|
4701
4855
|
| 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 |
|
|
4702
|
-
| 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 |
|
|
4703
4856
|
| any key neither format has, anywhere in either file | `<object> has a key this compiler does not read: "x" (did you mean "y"?) … Known here: …` (§5.1). Not a deferral either: a key nothing reads is a value you wrote and the emitted skeleton does not contain |
|
|
4704
4857
|
|
|
4705
4858
|
Two more limits that are not errors but will shape what you can attempt:
|