spine-rigc 0.31.0 → 0.32.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 +4 -4
- package/docs/AUTHORING.md +205 -27
- package/docs/INGEST.md +8 -7
- package/docs/SPEC_COVERAGE.md +6 -5
- package/package.json +1 -1
- package/src/compile.ts +328 -10
- package/src/deformmeasure.ts +7 -2
- package/src/ingest.ts +154 -25
- package/src/motion.ts +82 -1
- package/src/rig.ts +145 -3
- package/src/timelines.ts +94 -6
- package/src/types.ts +63 -0
- package/src/validate.ts +440 -72
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 32 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 47: 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
|
|
@@ -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 47 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 47 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/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 32 validity rules (**the default**) · `spine-html` = all 47, 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` |
|
|
@@ -627,7 +627,7 @@ the first:
|
|
|
627
627
|
|
|
628
628
|
| gutter | meaning |
|
|
629
629
|
| --- | --- |
|
|
630
|
-
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `point`,
|
|
630
|
+
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `point`, a `sequence` block the parser would read as some other series, 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 |
|
|
631
631
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
632
632
|
| `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) |
|
|
633
633
|
|
|
@@ -1223,13 +1223,15 @@ the default `type`:
|
|
|
1223
1223
|
| `x`, `y` | offset from the bone, in the bone's local space |
|
|
1224
1224
|
| `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
|
|
1225
1225
|
| `scaleX`, `scaleY`, `color` | as Spine |
|
|
1226
|
+
| `sequence` | a **numbered image series** instead of one region: `{ "count", "start"?, "digits"?, "setup"? }`, and `path` (or the placeholder) is the series' stem. No `image` beside it — the frames are the images. §3.4.3 |
|
|
1226
1227
|
|
|
1227
1228
|
**Mesh attachment** ([Spine: meshes](http://esotericsoftware.com/spine-meshes)) —
|
|
1228
1229
|
either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
|
|
1229
1230
|
never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
|
|
1230
1231
|
omitted, rigc derives — `hull` and `edges` from the triangles, the size from the
|
|
1231
|
-
PNG — and the rules are a few paragraphs down. `type`, `image`, `path
|
|
1232
|
-
mean exactly what they mean on a region
|
|
1232
|
+
PNG — and the rules are a few paragraphs down. `type`, `image`, `path`, `color` and
|
|
1233
|
+
`sequence` mean exactly what they mean on a region (a sequence mesh takes authored
|
|
1234
|
+
geometry, never a `generator` — §3.4.3).
|
|
1233
1235
|
|
|
1234
1236
|
🔑 **`path` is one rule for both kinds.** A mesh derives it from `image` the way a
|
|
1235
1237
|
region does: stated wins, otherwise the PNG's basename when that differs from the
|
|
@@ -2331,6 +2333,69 @@ nothing at all. `check` then compares blank against blank and reports a perfect
|
|
|
2331
2333
|
`--skin <name>` to both, once per skin (**§9**); `tools/editor_roundtrip.ts`
|
|
2332
2334
|
loops over every skin the build declares for the same reason.
|
|
2333
2335
|
|
|
2336
|
+
#### 3.4.3 `sequence` — a numbered image series on one attachment
|
|
2337
|
+
|
|
2338
|
+
A region, a mesh or a linked mesh can draw one of a **series** of atlas regions
|
|
2339
|
+
instead of one: a flame, a blink, a spinning coin. The attachment says how many
|
|
2340
|
+
frames there are and how they are named, and a `sequence` timeline (§4.13) says
|
|
2341
|
+
which one shows. The format's own block, spelled as the file spells it
|
|
2342
|
+
(`readSequence`, `SkeletonJson.js:641-649`):
|
|
2343
|
+
|
|
2344
|
+
| Field | Meaning |
|
|
2345
|
+
| --- | --- |
|
|
2346
|
+
| `count` | how many frames. **Required** — the parser's default is 0, and a series of no frames loads holding no region and draws nothing, without an error |
|
|
2347
|
+
| `start` | the number the first frame's name carries. Parser default 1 |
|
|
2348
|
+
| `digits` | zero-pad the frame number to at least this many digits. Parser default 0 (no padding) |
|
|
2349
|
+
| `setup` | the frame the setup pose shows, 0-based. Parser default 0. ⚠️ Spelled `setup`, as the file spells it — `setupIndex` is the runtime's field name and is refused as a key this compiler does not read |
|
|
2350
|
+
|
|
2351
|
+
```json
|
|
2352
|
+
"glint": { "glint": { "path": "glint_", "sequence": { "count": 4, "start": 1, "digits": 4 } } }
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
Frame `i` is the atlas region **`<stem><start + i>`**, the number left-padded with
|
|
2356
|
+
zeros to `digits` (`Sequence.getPath`) — so the four frames above are `glint_0001`
|
|
2357
|
+
to `glint_0004`. The stem is `path`, or the placeholder when no `path` is stated
|
|
2358
|
+
(`path` defaults to the attachment's name, exactly as for one region). Each frame is
|
|
2359
|
+
resolved **by name**: on the loose route it is the PNG `<images>/<frame>.png`, and
|
|
2360
|
+
under `--atlas-in` it is the pack's region of that name. A region name may carry a
|
|
2361
|
+
folder — an editor's `fx/flame_0001` is `images/fx/flame_0001.png` — and it is the
|
|
2362
|
+
full name that is looked up.
|
|
2363
|
+
|
|
2364
|
+
🚫 **A missing frame is refused by name, with its number and the name looked for** —
|
|
2365
|
+
the compiler never draws one frame in place of another:
|
|
2366
|
+
|
|
2367
|
+
```
|
|
2368
|
+
skin "default" slot "glint" attachment "glint": sequence frame 4 of 5 (number 5) is the region "glint_0005", and there is no PNG for it at …/glint_0005.png
|
|
2369
|
+
```
|
|
2370
|
+
|
|
2371
|
+
The loader's own miss would be `Region not found in atlas: glint_0005 (attachment:
|
|
2372
|
+
glint)`, which says neither that the region was a frame nor of which series.
|
|
2373
|
+
|
|
2374
|
+
⚠️ **The rest of what is refused**, each a series the parser would load as something
|
|
2375
|
+
other than what was written (measured on spine-core 4.3.13,
|
|
2376
|
+
[#729](https://github.com/firejune/rigc/issues/729)):
|
|
2377
|
+
|
|
2378
|
+
- a `setup` at or past `count` — `Sequence.resolveIndex` clamps it to the last frame
|
|
2379
|
+
(`setup: 7` on four frames showed frame 4);
|
|
2380
|
+
- a fractional `count`, `start`, `digits` or `setup` — `start: 1.5` would ask the
|
|
2381
|
+
atlas for `glint_1.5`;
|
|
2382
|
+
- an `image` beside `sequence` — one file names one region, and the series names
|
|
2383
|
+
`count` of them;
|
|
2384
|
+
- a `generator` beside it on a mesh — a generator traces one plate, and which frame
|
|
2385
|
+
it should trace is not something the spec says. Author the geometry; every frame
|
|
2386
|
+
shares it;
|
|
2387
|
+
- a `sequence` on a `boundingbox`, `clipping` or `path` — the parser reads the key
|
|
2388
|
+
only on the three kinds that draw a region, so it would be dropped in silence. The
|
|
2389
|
+
refusal names region, mesh and linkedmesh.
|
|
2390
|
+
|
|
2391
|
+
`width` and `height` are the attachment's one size, every frame drawn into it. Omit
|
|
2392
|
+
them and rigc takes the frames' size — **only when every frame measures the same**;
|
|
2393
|
+
frames of different sizes are refused until you state the size, because picking one
|
|
2394
|
+
of them would be the compiler choosing a value. Under `--atlas-in` a stated size that
|
|
2395
|
+
disagrees with a packed frame is refused, as it is for one region.
|
|
2396
|
+
`A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` (§5.2) holds the block and
|
|
2397
|
+
every frame the timelines show against the file.
|
|
2398
|
+
|
|
2334
2399
|
### 3.5 `constraints` — 4.3's single typed array
|
|
2335
2400
|
|
|
2336
2401
|
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
@@ -2650,8 +2715,8 @@ driven constraint and its kind, the property, both `constraints` indices, the
|
|
|
2650
2715
|
runtime class whose `update` does the reading and the animation the key sits in,
|
|
2651
2716
|
and its repair is the reorder. It is a rule of its own rather than a clause of
|
|
2652
2717
|
`A40` because `A40`'s population is the sliders whose `mix` nothing keys — the
|
|
2653
|
-
exclusion *is* the shape of the hole — and not a clause of `A37`,
|
|
2654
|
-
|
|
2718
|
+
exclusion *is* the shape of the hole — and not a clause of `A37`, which asks
|
|
2719
|
+
whether some animation keys the `mix` above 0 and never which one.
|
|
2655
2720
|
⭐ **The runtime repairs this for bones and not for constraints**, which is why an
|
|
2656
2721
|
author cannot reason it out from the bone case: `Slider.sort` clears `sorted` on
|
|
2657
2722
|
every bone its animation keys so those bones re-sort *after* the slider, while
|
|
@@ -3106,6 +3171,7 @@ time puts it here.
|
|
|
3106
3171
|
| `ik` | IK constraint timelines — §4.9. Not a track: its keys carry named fields, not one `v` |
|
|
3107
3172
|
| `transform` | transform constraint timelines — §4.10. Same reason |
|
|
3108
3173
|
| `deform` | deform timelines — §4.11. Same reason |
|
|
3174
|
+
| `sequence` | which frame of an attachment's numbered series shows — §4.13. Same reason |
|
|
3109
3175
|
|
|
3110
3176
|
`groups` (`name → [member, …]`) lets one track target several bones, slots or
|
|
3111
3177
|
physics constraints at once; `lag` shifts every key of a track, and `stagger`
|
|
@@ -3281,8 +3347,8 @@ needs 4 channels, got 1`, a message about a key you had not written.
|
|
|
3281
3347
|
here, and would be refused by the runtime's own reader too (`Invalid timeline
|
|
3282
3348
|
type for a slot`). `A12_NO_DARK_COLOR` refuses `rgb2` — and `rgba2`, and the
|
|
3283
3349
|
slot field — in a file under the `spine-html` profile (SPEC_COVERAGE §2.1).
|
|
3284
|
-
`sequence` is a timeline on an **attachment**, not on a slot
|
|
3285
|
-
|
|
3350
|
+
`sequence` is a timeline on an **attachment**, not on a slot — it is the
|
|
3351
|
+
family beside `deform`, §4.13.
|
|
3286
3352
|
|
|
3287
3353
|
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
3288
3354
|
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
@@ -3336,6 +3402,17 @@ a delta from the constraint's own setting.
|
|
|
3336
3402
|
a file rigc did not write, naming the animation, the constraint, the key time
|
|
3337
3403
|
and the value — so the compiler is where a spec you wrote is refused, and the
|
|
3338
3404
|
assertion is where an import is.
|
|
3405
|
+
- ⚠️ **`damping`'s bound depends on the constraint's `fps`, and a rig played at
|
|
3406
|
+
60 fps hides it** ([#748](https://github.com/firejune/rigc/issues/748)). The
|
|
3407
|
+
decay is `damping ** (60 * step)` with `step` = `1 / fps`, so the exponent is
|
|
3408
|
+
exactly 1 at 60 fps, where a negative damping only flips the velocity's sign
|
|
3409
|
+
each step and can look like a jiggle settling. Wherever `60 / fps` is not a
|
|
3410
|
+
whole number the same negative is raised to a fractional power, which is NaN.
|
|
3411
|
+
[measured] one planted `damping` key of −0.5 on the generated physics fixture
|
|
3412
|
+
stays finite at 60 fps and at 30 (exponent 2), and is NaN within three steps of
|
|
3413
|
+
the key at 45 and at 120. `1` never decays and above `1` diverges at every
|
|
3414
|
+
rate. The refusal names the exponent and the rate for this reason, and `T101`
|
|
3415
|
+
holds it to the two-rate measurement.
|
|
3339
3416
|
- 🚫 **`inertia`, `wind` and `gravity` are bounded nowhere, and neither is the top
|
|
3340
3417
|
of `mix`.** The runtime documents no range for the first three, and
|
|
3341
3418
|
`PhysicsConstraintPose` documents `mix` as "a percentage (0+)" — so a negative
|
|
@@ -3352,7 +3429,15 @@ a delta from the constraint's own setting.
|
|
|
3352
3429
|
back ([#727](https://github.com/firejune/rigc/issues/727)). At rest the two
|
|
3353
3430
|
part ways: a `strength` of 0 is a constraint nothing pulls back, which `A23`
|
|
3354
3431
|
refuses, and a `mix` of 0 is a constraint muted until an animation keys it
|
|
3355
|
-
above 0 — the next bullet.
|
|
3432
|
+
above 0 — the next bullet. A **negative** setup `strength` is refused too, with
|
|
3433
|
+
a different sentence, because it is a different rig: the restoring term is
|
|
3434
|
+
added to the offset instead of taken out, so the offset is **pushed away** and
|
|
3435
|
+
grows — [measured] resting at −100 on the generated physics fixture it grew
|
|
3436
|
+
28.35× over 0.5 s with no sign change, where resting at 100 it swung back
|
|
3437
|
+
through 0. `A23` reads which sentence to print off the `strength` row of
|
|
3438
|
+
`PHYSICS_POSE_RULES` (its `outside` arms), and the key's refusal quotes the
|
|
3439
|
+
negative arm from the same row, so the two cannot say different things about
|
|
3440
|
+
one number ([#748](https://github.com/firejune/rigc/issues/748)).
|
|
3356
3441
|
- ⚠️ **A setup `mix` of `0` is legal when some animation keys it above 0** ([#743](https://github.com/firejune/rigc/issues/743)).
|
|
3357
3442
|
A constraint muted *at rest* is a rig whose physics is off until an animation
|
|
3358
3443
|
switches it on, which is a design rather than the silence `A23` was built for.
|
|
@@ -3362,6 +3447,11 @@ a delta from the constraint's own setting.
|
|
|
3362
3447
|
at 1 does, and keyed-to-0 poses it exactly where one with no timeline at all
|
|
3363
3448
|
does. The unnamed global timeline counts as a key for every constraint whose
|
|
3364
3449
|
own `mixGlobal` is set (§3.5), and so does an animation only a slider applies.
|
|
3450
|
+
"Keyed above 0" means **any value the timeline poses**, which includes a
|
|
3451
|
+
Bezier between two keys of 0 whose handles lie above 0: the runtime
|
|
3452
|
+
interpolates through the curve's samples, not between the keys, and such a
|
|
3453
|
+
pair moves the bone ([#752](https://github.com/firejune/rigc/issues/752)). The
|
|
3454
|
+
same reading is `A36`'s and `A37`'s (§4.12).
|
|
3365
3455
|
- 📏 **What a `strength` key of `0` costs, measured through spine-core** on the
|
|
3366
3456
|
generated overlay fixture, stepping at 60 fps from `Physics.reset` (#727). With
|
|
3367
3457
|
no wind or gravity the offset coasts to a limit rather than running away — a
|
|
@@ -3716,10 +3806,14 @@ mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
|
|
|
3716
3806
|
be **keyed over time** as `tracks` entries naming this constraint (§4.4); this
|
|
3717
3807
|
table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and `damping ≥ 1`
|
|
3718
3808
|
never settles — both are `A23`, here and on every timeline key that states them
|
|
3719
|
-
([#610](https://github.com/firejune/rigc/issues/610))
|
|
3809
|
+
([#610](https://github.com/firejune/rigc/issues/610)); a `damping` at or below 0
|
|
3810
|
+
is refused as well, and below 0 whether it is NaN depends on `fps` — §4.4
|
|
3811
|
+
([#748](https://github.com/firejune/rigc/issues/748)). ⚠️ `strength: 0` is `A23` **here and not on a key**:
|
|
3720
3812
|
at rest it is a constraint nothing pulls back, and on a key it is a release somebody
|
|
3721
3813
|
asked for, which §4.4 states with the measurement behind it
|
|
3722
|
-
([#727](https://github.com/firejune/rigc/issues/727)).
|
|
3814
|
+
([#727](https://github.com/firejune/rigc/issues/727)). A negative `strength` is
|
|
3815
|
+
`A23` here and on a key, and here its sentence is its own: the offset is pushed
|
|
3816
|
+
away and grows ([#748](https://github.com/firejune/rigc/issues/748)). `mix: 0` is the one value
|
|
3723
3817
|
here an animation can answer for: it rests the constraint **muted**, which is
|
|
3724
3818
|
legal, and `A23` names it only when no timeline in any animation keys that `mix`
|
|
3725
3819
|
above 0 — on a key it is the mute for a span (§4.4, [#743](https://github.com/firejune/rigc/issues/743)). None of the
|
|
@@ -4833,12 +4927,80 @@ so `v` is three numbers and not one.
|
|
|
4833
4927
|
|
|
4834
4928
|
⚠️ **A muted constraint with no timeline is a finding, not an idiom.** Turning a
|
|
4835
4929
|
constraint on from an animation is the idiom (§4.10), so `A36`/`A37` only object to
|
|
4836
|
-
all-zero mixes when **no** animation keys that constraint's `mix
|
|
4837
|
-
at setup, key it somewhere. `A23` asks the same question of a
|
|
4838
|
-
([#743](https://github.com/firejune/rigc/issues/743)) and
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4930
|
+
all-zero mixes when **no** animation keys that constraint's `mix` **above 0**. If
|
|
4931
|
+
you mute one at setup, key it up somewhere. `A23` asks the same question of a
|
|
4932
|
+
physics constraint ([#743](https://github.com/firejune/rigc/issues/743)), and the
|
|
4933
|
+
three share one reading ([#752](https://github.com/firejune/rigc/issues/752)): the
|
|
4934
|
+
key **values** the loaded timeline poses, so a `mix` timeline keying 0 only is not a
|
|
4935
|
+
rescue, and a Bezier between two keys of 0 whose handles lie above 0 is. A path
|
|
4936
|
+
constraint is switched on by a key posing **any one** of its three mixes above 0,
|
|
4937
|
+
because `PathConstraint.update` returns only when all three are 0. [measured] on
|
|
4938
|
+
generated fixtures, a path constraint and a slider muted at rest and keyed to 0
|
|
4939
|
+
only pose every bone exactly where the same rig with no mix timeline does, and until
|
|
4940
|
+
#752 both passed. `A23` alone also counts the unnamed global timeline for every
|
|
4941
|
+
constraint declaring `mixGlobal`, since only the physics family has one. The refusal
|
|
4942
|
+
says both halves — `path constraint "P" has mixRotate 0, mixX 0 and mixY 0 at setup
|
|
4943
|
+
and none of the 2 animations keys its mix above 0; …` — and names both repairs.
|
|
4944
|
+
|
|
4945
|
+
### 4.13 `sequence` — which frame of a numbered series shows
|
|
4946
|
+
|
|
4947
|
+
The other attachment timeline, beside `deform` and for the same reason: its key is
|
|
4948
|
+
three named fields rather than one `v`, and it is aimed at an attachment rather than
|
|
4949
|
+
a slot, so it is a family of its own
|
|
4950
|
+
(`animations.<a>.attachments.<skin>.<slot>.<attachment>.sequence` in the file):
|
|
4951
|
+
|
|
4952
|
+
```json
|
|
4953
|
+
"sequence": [
|
|
4954
|
+
{ "slot": "glint", "attachment": "glint", "keys": [
|
|
4955
|
+
{ "t": 0, "mode": "loop", "delay": 0.1 },
|
|
4956
|
+
{ "t": 0.5, "mode": "pingpong", "index": 1, "delay": 0.1 } ] }
|
|
4957
|
+
]
|
|
4958
|
+
```
|
|
4959
|
+
|
|
4960
|
+
| Field | Meaning |
|
|
4961
|
+
| --- | --- |
|
|
4962
|
+
| `skin` | the skin the attachment lives in; absent means `"default"` |
|
|
4963
|
+
| `slot`, `attachment` | the slot and the attachment's placeholder — the attachment must carry a `sequence` block (§3.4.3) |
|
|
4964
|
+
| `keys[].t` | seconds |
|
|
4965
|
+
| `keys[].mode` | one of `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse`. Parser default `hold` — **per key**, not carried |
|
|
4966
|
+
| `keys[].index` | the frame the key starts on, 0-based. Parser default 0 |
|
|
4967
|
+
| `keys[].delay` | seconds per frame. Parser default: **the previous key's** `delay`, 0 on the first |
|
|
4968
|
+
|
|
4969
|
+
From the key's time on, the frame shown is `index` advanced by one every `delay`
|
|
4970
|
+
seconds and folded back into the series by `mode` — `SequenceTimeline.applyToSlot`,
|
|
4971
|
+
with `i = index + floor((time - keyTime) / delay + 0.00001)` and `n = 2 * count - 2`:
|
|
4972
|
+
|
|
4973
|
+
| `mode` | frame shown |
|
|
4974
|
+
| --- | --- |
|
|
4975
|
+
| `hold` | `index`, never advancing |
|
|
4976
|
+
| `once` | `min(count - 1, i)` — stops on the last frame |
|
|
4977
|
+
| `loop` | `i % count` |
|
|
4978
|
+
| `pingpong` | `i % n`, then `n - that` once it reaches `count` — bounces off both ends |
|
|
4979
|
+
| `onceReverse` | `max(count - 1 - i, 0)` |
|
|
4980
|
+
| `loopReverse` | `count - 1 - (i % count)` |
|
|
4981
|
+
| `pingpongReverse` | `(i + count - 1) % n`, then folded as `pingpong` |
|
|
4982
|
+
|
|
4983
|
+
Measured on four frames at `delay: 0.1` from t = 0 (frames numbered 0–3):
|
|
4984
|
+
`loop` shows 0, 1, 2, 3, 0, 1 at t = 0.05, 0.15 … 0.55; `pingpong` 0, 1, 2, 3, 2, 1, 0;
|
|
4985
|
+
`onceReverse` 3, 2, 1, 0, 0. Before the first key the frame is the block's `setup`.
|
|
4986
|
+
|
|
4987
|
+
⚠️ **A sequence timeline does not lengthen an animation.** A loop keyed once at t=0
|
|
4988
|
+
in an animation nothing else extends has a duration of 0, and a player that does
|
|
4989
|
+
not loop the animation shows frame `index` forever (measured). The animation's
|
|
4990
|
+
`duration` is its last key, as everywhere (R7), so key something to the length you
|
|
4991
|
+
mean.
|
|
4992
|
+
|
|
4993
|
+
🚫 Refused by name, each measured to load without a word: a `mode` outside the seven
|
|
4994
|
+
(it loads as `hold`); an `index` that is fractional, negative, or at or past the
|
|
4995
|
+
series' `count` (truncated, clamped); an advancing mode at an **effective** delay of
|
|
4996
|
+
0 — stated, or carried from the key before — where `(time - keyTime) / 0` is
|
|
4997
|
+
Infinity, `Infinity | 0` is 0, and the key shows its first frame throughout; a track
|
|
4998
|
+
on an attachment with no `sequence` block (the parser gives every region a series of
|
|
4999
|
+
one, so every mode shows it); and a track on a **linked mesh that plays its source's
|
|
5000
|
+
timelines** (`timelines` absent or true), whose `timelineAttachment` is the source,
|
|
5001
|
+
so a key aimed at the link is applied to nothing — key the source instead, and the
|
|
5002
|
+
link steps its own series by it, or set `"timelines": false`. Only the fields the
|
|
5003
|
+
key states are emitted: the compiler writes no `mode`, `index` or `delay` for you.
|
|
4842
5004
|
|
|
4843
5005
|
---
|
|
4844
5006
|
|
|
@@ -4974,6 +5136,11 @@ same hole issue #307 closed for the motion spec.)
|
|
|
4974
5136
|
| `…ik[i]`, `…transform[i]`, `…deform[i]` | an object | `null` in one of these lists crashed with a raw `TypeError` on `track.constraint` / `track.skin` |
|
|
4975
5137
|
| `…ik[i].constraint`, `…transform[i].constraint` | a non-empty string | 4.3 writes the group as `ik.<constraint>`, so the name is the only target there is |
|
|
4976
5138
|
| `…deform[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | — |
|
|
5139
|
+
| `…sequence` | an array | §4.13 — one entry per skin/slot/attachment triple |
|
|
5140
|
+
| `…sequence[i].slot`, `.attachment`, `.skin` | a string (`skin` optional) | — |
|
|
5141
|
+
| `…sequence[i].keys[j].mode` | one of the seven — `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse` | `SequenceMode[mode]` is `undefined` for anything else and the mode bits store 0: the key loads without a word and plays as `hold` |
|
|
5142
|
+
| `…sequence[i].keys[j].index` | a whole number ≥ 0 | stored as `index << 4`, which truncates a fraction (1.5 showed frame 1). Whether it is inside the series' `count` is the compile-time row below |
|
|
5143
|
+
| `…sequence[i].keys[j].delay` | a finite number ≥ 0 — and **above 0 wherever the mode advances**, counting a delay carried from the key before | `(time - keyTime) / 0` is Infinity and `Infinity \| 0` is 0, so a `loop` at delay 0 shows its first frame throughout. The message reads `` `…keys[j]` plays "loop" at a delay of 0 … which is "hold" spelt as "loop" `` |
|
|
4977
5144
|
|
|
4978
5145
|
The second wave is everything that needed the **other** file, the property table
|
|
4979
5146
|
or the key's position in its own track. These are the frequent ones, verbatim:
|
|
@@ -5003,6 +5170,14 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5003
5170
|
| `"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 |
|
|
5004
5171
|
| `"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 |
|
|
5005
5172
|
| `"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 |
|
|
5173
|
+
| `… is a boundingbox and states a "sequence". A sequence is a numbered series of atlas regions, and only the 3 kinds that draw a region carry one — region, mesh, linkedmesh …` | §3.4.3 — put the series on a region or a mesh, or remove it |
|
|
5174
|
+
| `… "sequence" states no "count" …` / `… "sequence".setup is N, and a C-frame series has frames 0 to C-1 …` / `… "sequence".F is V; it is a whole number …` | §3.4.3 — the parser reads an omitted count as 0 frames and clamps a setup past the end; state the count, and a 0-based `setup` inside it |
|
|
5175
|
+
| `… states "image" beside "sequence" …` / `… states "generator" beside "sequence" …` | §3.4.3 — the frames are the images; remove `image`. A sequence mesh takes authored geometry |
|
|
5176
|
+
| `… sequence frame I of C (number N) is the region "R", and there is no PNG for it at P …` (or `… which the atlas at A does not have …` under `--atlas-in`) | §3.4.3 — add the frame, or state the `count` the series really has. The compiler draws no frame in place of another |
|
|
5177
|
+
| `… the N frames of this sequence measure W1, W2 in width … State "width"` | §3.4.3 — frames of different sizes; state the attachment's size |
|
|
5178
|
+
| `animation "A" sequence S/X/P: attachment "P" carries no "sequence" block, so there is no series to step …` | §4.13 — give the attachment a `sequence`, or remove the track |
|
|
5179
|
+
| `animation "A" sequence S/X/P (t=T): index I is past the end of a C-frame series …` | §4.13 — `index` is 0-based |
|
|
5180
|
+
| `animation "A" sequence S/X/P: attachment "P" is a linked mesh that plays its source's timelines …` | §4.13 — key the source (the link steps its own series by it), or set `"timelines": false` on the link |
|
|
5006
5181
|
| `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 |
|
|
5007
5182
|
| `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
|
|
5008
5183
|
| `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 |
|
|
@@ -5029,7 +5204,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5029
5204
|
| `physics constraint "*": the name is reserved — …` / `` `physics."*"` names a physics constraint "*", and that name is reserved … `` | §3.5 — `"*"` is the target of the timeline that names no constraint; rename the constraint |
|
|
5030
5205
|
| `animation "A": group "G" lists "*", which is not a constraint but the target that names none …` | §4.4 — write `"*"` as the track's `physics` field |
|
|
5031
5206
|
| `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (§4.4) |
|
|
5032
|
-
| `animation "A" physics constraint "C" mass key at t=… is 0 (massInverse Infinity); must be > 0 — …` | §4.4 — a keyed physics value the runtime cannot use. The message names the bound and the `PhysicsConstraint.js` lines that make it one: `mass` is `> 0`, `damping` is inside `(0, 1)`, `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)). ⚠️ Those are the bounds a **key** is held to. A setup `strength` of `0` is refused too, but by `A23` rather than here, and with its own sentence — `physics "C" has strength 0; nothing pulls it back` ([#727](https://github.com/firejune/rigc/issues/727)) |
|
|
5207
|
+
| `animation "A" physics constraint "C" mass key at t=… is 0 (massInverse Infinity); must be > 0 — …` | §4.4 — a keyed physics value the runtime cannot use. The message names the bound and the `PhysicsConstraint.js` lines that make it one: `mass` is `> 0`, `damping` is inside `(0, 1)`, `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)). ⚠️ Those are the bounds a **key** is held to. A setup `strength` of `0` is refused too, but by `A23` rather than here, and with its own sentence — `physics "C" has strength 0; nothing pulls it back` ([#727](https://github.com/firejune/rigc/issues/727)); a setup `strength` below 0 says the offset is pushed away and grows, the same arm this message quotes for a negative key ([#748](https://github.com/firejune/rigc/issues/748)). A `damping` key's sentence names the exponent `60 * step` and why a negative is NaN at any `fps` where `60 / fps` is not whole (§4.4) |
|
|
5033
5208
|
| `a key carries both a named easing and a raw curve; pick one` | R6 |
|
|
5034
5209
|
| `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
|
|
5035
5210
|
| `key times must strictly increase (at t=…)` | including after `lag` and `stagger` |
|
|
@@ -5218,7 +5393,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5218
5393
|
| `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)) |
|
|
5219
5394
|
| `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 |
|
|
5220
5395
|
| `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)) |
|
|
5221
|
-
| `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
|
|
5396
|
+
| `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 ([#748](https://github.com/firejune/rigc/issues/748)) — or has `damping` outside `(0, 1)` so it never settles — **at rest, and on every physics timeline key** ([#610](https://github.com/firejune/rigc/issues/610), [#743](https://github.com/firejune/rigc/issues/743)). 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 ([#727](https://github.com/firejune/rigc/issues/727)). 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; it is muted — rest it above 0, or key its mix above 0 in an animation`. 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 ([#752](https://github.com/firejune/rigc/issues/752)). `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), [#731](https://github.com/firejune/rigc/issues/731)), so this sentence is met on a file, never on a decompiled rebuild. **SKIP** when the skeleton declares no physics constraint ([#580](https://github.com/firejune/rigc/issues/580)) — the same sentence `A36` and `A37` have always printed for their own constraint types |
|
|
5222
5397
|
| `A24_AXIS_SPACE_STROKE` | archetype | a bone under the rig's `axisBone` was keyed with a screen-space Y component, or the axis bone itself was keyed. **SKIP** when the rig declares no axis bone, and also when no animation keys that bone or anything under it ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5223
5398
|
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
|
|
5224
5399
|
| `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not the rig's slot table — a slot is out of order, is not in the table at all, or is in the table and missing from the skeleton (§3.3). **SKIP** when the rig declares no canonical slot order. ⚠️ A skeleton with **no** slot beside a rig that declares some is **not** a skip, and it is the one rule in this family where an empty loop is not a vacuous pass ([#580](https://github.com/firejune/rigc/issues/580)): the completeness clause reads it as every declared slot lost and names them, which is the maximal case of what [#575](https://github.com/firejune/rigc/issues/575) filed |
|
|
@@ -5231,8 +5406,8 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5231
5406
|
| `A33_VERTEX_ATTACHMENT_GEOMETRY` | both | a bounding box, clipping polygon or path whose `vertexCount` is missing or disagrees with its vertex array, a weighted run that decodes to the wrong number of vertices or an out-of-range bone index, a clipping `end` naming a slot the skeleton does not have, a path whose vertex count is not a multiple of 3, or a path `lengths` array that does not strictly increase (§3.4). **SKIP** when the skeleton carries none of the three |
|
|
5232
5407
|
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik`, `transform`, `path`, `physics` or `slider` timeline names a constraint the skeleton does not declare, names one of another type, or carries no keys at all (§4.4, §4.9, §4.10, §4.12). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. The **empty** name under `physics` is not a miss — it is the timeline that names no constraint (§4.4's `"*"`), and it is refused only when it reaches none: `animation "A" physics constraint "" timeline "P": a physics group that names no constraint writes every physics constraint declaring "PGlobal": true, and none of "C", … does — the parser loads it, the runtime walks every constraint, and no constraint takes the key` ([#726](https://github.com/firejune/rigc/issues/726)). Who it reaches is asked of the runtime's own `PhysicsConstraintTimeline.global` on the file's constraints — the reading `A23` and `A42` share. **SKIP** when no animation carries one |
|
|
5233
5408
|
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | both | a deform key's run runs past the end of the attachment's deform array, holds a non-finite number, has an empty key array, or names a skin/slot/attachment triple that does not resolve (§4.11). The overrun is the quiet one — the parser copies into a `Float32Array` and drops the tail. ⛔ It does **not** require pair alignment: the runtime has no such rule and a trimmed editor run legitimately starts and ends mid-pair (§4.11, issue #262). **SKIP** when no animation carries a deform timeline |
|
|
5234
|
-
| `A36_PATH_CONSTRAINT_EFFECTIVE` | both | a path constraint whose slot has no path attachment in any skin, one that constrains no bone, or one whose three mixes are all 0 at setup with no animation keying
|
|
5235
|
-
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` (§3.5.2). **SKIP** when the skeleton declares no slider |
|
|
5409
|
+
| `A36_PATH_CONSTRAINT_EFFECTIVE` | both | a path constraint whose slot has no path attachment in any skin, one that constrains no bone, or one whose three mixes are all 0 at setup with no animation keying any of them **above 0** (§3.5.1, §4.12). The first is the quiet one: `update()` returns on its first line and the constraint reports mixes it never applies. The third reads the values the loaded timeline poses — keys and Bezier samples alike — the way `A23` reads a physics `mix`, so a `mix` timeline keying 0 only is not a rescue: [measured] it poses every bone exactly where no timeline does, and before [#752](https://github.com/firejune/rigc/issues/752) it passed. The detail says both halves: `path constraint "P" has mixRotate 0, mixX 0 and mixY 0 at setup and none of the 2 animations keys its mix above 0; update() returns on all-zero mixes, so nothing ever puts a bone on the path — rest one of the three above 0, or key its mix above 0 in an animation`. **SKIP** when the skeleton declares no path constraint |
|
|
5410
|
+
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` **above 0** (§3.5.2, §4.12) — the same reading as `A36`'s, so a `mix` timeline keying 0 only is no rescue ([#752](https://github.com/firejune/rigc/issues/752)): `slider "S" has mix 0 at setup and none of the 2 animations keys its mix above 0; update() returns on mix 0 — rest it above 0, or key its mix above 0 in an animation`. **SKIP** when the skeleton declares no slider |
|
|
5236
5411
|
| `A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED` | both | a bone or constraint a skin activates that is not `skinRequired` (the list changes nothing), or one that is `skinRequired` and no skin activates (it is never active). Two keys in two places, and only together do they mean "this belongs to that skin" (§3.4.1). **SKIP** when no skin activates anything and nothing is `skinRequired` |
|
|
5237
5412
|
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. ⚠️ And the **skin** it poses in is the one the timeline is keyed on (§4.11.5, issue #583), since a deform's address is a `skin / slot / attachment` triple: the pose wears that skin, which also switches on any `skin: true` bone or constraint it activates, and the "nothing is drawn" sentence names the skin it was read under. **SKIP** when no animation carries a deform timeline, when nothing keyed has triangles, when every mesh keyed is exempt, when every key measured draws no pixels or is unreachable *and no span between them folds where anything is drawn*, or when there is no rig info at all |
|
|
5238
5413
|
| `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 |
|
|
@@ -5241,6 +5416,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5241
5416
|
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | both | a slot's `dark` (§3.3) or an `rgba2` / `rgb2` 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` or `rgb2` 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. An `rgb2` key's light colour is compared over its three channels only: the light alpha is not its to state, and that it is left where it was is measured in the selftest (`S85`). **SKIP** when no slot declares a `dark` and no animation keys an `rgba2` or `rgb2` — there is then no two-colour tint to read back |
|
|
5242
5417
|
| `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)) |
|
|
5243
5418
|
| `A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN` | both | an `rgb` or `alpha` timeline (§4.4) the runtime does not hold as the file states it, in one of two shapes that both parse in silence. **A channel keyed twice**: another colour timeline of the same slot in the same animation poses a channel this one poses — `rgba` beside `alpha` is the shape a converter leaves when it writes a separable `rgb` back as `rgba` next to the `alpha` it kept. Every colour timeline poses its channels at every time, the setup value before its first key included, so the one the file states later overwrites the other everywhere; the detail names both timelines, the channel, and which one survives. **A key not posed as written**: the animation is stepped to each key's own time and the posed r g b (for `rgb`, against the hex, to half a quantisation step) or alpha (for `alpha`, against `value`, whose absence the parser reads as 0) is compared — a colour that is not six hex digits loads as NaN, and a key whose time another key repeats is read by nothing. ⚠️ An `rgb` alone written as an `rgba` holding the setup alpha is **not** caught and cannot be from the file: it is a correct `rgba`, and the difference shows only under another track that moves the alpha. The loaded timeline class and the channels a separable timeline leaves alone are measured in the selftest (`S83`–`S85`) rather than here, because against the linked parser neither can come out wrong. The channel table is `SLOT_COLOR_CHANNELS` in `src/timelines.ts`, shared with the compiler's refusal and held to the runtime's own property ids (`S89`). **SKIP** when no animation keys an `rgb` or `alpha` — there is then no separable slot colour to read back |
|
|
5419
|
+
| `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` | both | a **numbered series** (§3.4.3, §4.13) that the runtime does not show as the file states it. Every shape below loads without a word, measured on spine-core 4.3.13 ([#729](https://github.com/firejune/rigc/issues/729)). **The block**: a `sequence` with no `count` (`readSequence` reads 0, and the attachment holds no region) or a `setup` at or past `count` (`Sequence.resolveIndex` clamps it to the last frame). **The keys**: a `mode` outside the seven — `hold`, `once`, `loop`, `pingpong`, `onceReverse`, `loopReverse`, `pingpongReverse` — loads as `hold`; an `index` that is fractional (`index << 4` truncates it) or past the end (clamped); an advancing mode at an effective delay of 0 (the parser carries a key's `delay` from the key before; `(time - keyTime) / 0` is Infinity and `Infinity \| 0` is 0, so it never advances); a timeline on an attachment that carries no block (the parser gives every region a one-region series, so every mode shows it). **The pose**: every key is stepped to mid-frame sample times — enough to wrap every mode — and the region the slot shows is held to the frame the file's own statement gives, the arithmetic of `SequenceTimeline.applyToSlot` and the names of `Sequence.getPath` transcribed rather than read off the loaded timeline, so the check is not the runtime agreeing with itself. Before the first key the frame is `setup`. ⚠️ A sample where the slot shows another attachment is not compared, because the runtime writes nothing there; a timeline with no comparable sample is counted in `stats.sequenceSamplesUnshown`. `compile.ts` refuses every one of these shapes in a spec (§5.1); this is them held against a skeleton it did not write. **SKIP** when no attachment carries a `sequence` block and no animation keys a `sequence` timeline |
|
|
5244
5420
|
|
|
5245
5421
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
5246
5422
|
clauses are gated by profile.
|
|
@@ -5288,12 +5464,14 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
5288
5464
|
`--profile spine-html` over a packed atlas. The default is still **one part per
|
|
5289
5465
|
page** with `pma: false` and every region covering its whole page, and nothing
|
|
5290
5466
|
about a build changes unless one of those flags is given.
|
|
5291
|
-
- **`
|
|
5292
|
-
`
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
(§4.
|
|
5467
|
+
- **`drawOrderFolder`** is walked by the validator (so `A05` checks its curves and
|
|
5468
|
+
`diff` counts its keys) and cannot be *written*: there is no motion-spec property
|
|
5469
|
+
for it. Everything a motion spec **can** key is §4.4's track table — which now
|
|
5470
|
+
includes the `path` and `slider` groups (§4.12) — plus the six families that sit
|
|
5471
|
+
beside `tracks`: `drawOrder` (§4.7), `events` (§4.8), `ik` (§4.9), `transform`
|
|
5472
|
+
(§4.10), `deform` (§4.11) and `sequence` (§4.13). `sequence` stood beside
|
|
5473
|
+
`drawOrderFolder` in this sentence until
|
|
5474
|
+
[#729](https://github.com/firejune/rigc/issues/729) made it writable.
|
|
5297
5475
|
|
|
5298
5476
|
---
|
|
5299
5477
|
|
package/docs/INGEST.md
CHANGED
|
@@ -594,9 +594,9 @@ is the one failure a comparison of two sets cannot show you.
|
|
|
594
594
|
| `ANIMATION_GROUP` | `BLOCK` | 1 | the animation carries a group the motion spec has no home for. The detail names the ten it does carry. `drawOrderFolder` is the group to know about: the runtime reads it and builds a timeline from it, and no export in this corpus carries one | transcribe that group by hand (§2), or accept that the rebuild does not carry it |
|
|
595
595
|
| `ATTACHMENT_<TYPE>` | `BLOCK` | 1 | an attachment of a type rigc does not emit; the code is composed from the type, so on the one type left it reads `ATTACHMENT_POINT`. rigc emits region, mesh, linkedmesh, boundingbox, clipping and path — `linkedmesh` since [#691](https://github.com/firejune/rigc/issues/691), and `point` is the remaining deferred type | the rebuild will not have that attachment at all. `docs/SPEC_COVERAGE.md` part 1-6 says what a deferred type would carry |
|
|
596
596
|
| `ATTACHMENT_LINK_GEOMETRY` | `LOSS` | 0 | a **linked mesh** 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 the attachment draws the geometry its `source` names; the rig spec has no home for them either, because `build` refuses geometry on a link by name. The detail lists the keys and the source. Until [#710](https://github.com/firejune/rigc/issues/710) the rebuild dropped them with no line at all, so an `ingest` that normalised somebody's file said nothing about it | nothing. The rebuild is the mesh the runtime was already drawing — and if those keys were the geometry you meant, take `source` off and author it as a mesh of its own. `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` is the same fact at the gate |
|
|
597
|
-
| `ATTACHMENT_NAME` | `LOSS` | 0 | the attachment
|
|
598
|
-
| `ATTACHMENT_SEQUENCE` | `BLOCK` | 1 |
|
|
599
|
-
| `ATTACHMENT_TIMELINE` | `BLOCK` | 1 | an attachment timeline
|
|
597
|
+
| `ATTACHMENT_NAME` | `LOSS` | 0 | the rebuilt attachment answers to a different name than the source's. **Two shapes, one comparison.** Where only **one** skin fills the placeholder, any stated `name` is lost: rigc writes none — it composes `<skin>/<placeholder>` exactly where a placeholder is contested. Where the placeholder **is** contested, rigc composes that name, and the source's name — what it states, or its placeholder where it states none (`SkeletonJson.js:526`) — is compared with it: equal is rigc's own emit and prints nothing, different is a rename and the detail states **both strings**. Until [#746](https://github.com/firejune/rigc/issues/746) the contested half printed nothing at all. A contested placeholder the **default** skin fills gets no line — the compiler composes nothing there and refuses the rebuild by name. 🚨 **The name is also the ATLAS REGION KEY**, and the detail says what became of it: `readAttachment` reads `name = getValue(map, "name", placeholder)` and then `path = getValue(map, "path", name)`, so `path` defaults to the **name** and not to the placeholder. A name that differs from the placeholder is therefore **kept as `path`** on an attachment that resolves a region — region, mesh, linked mesh — and the detail names the region. The three shapes that keep nothing say which they are: the source stated its own `path` (carried unchanged), the name **is** the placeholder (same region either way), or the type resolves no region at all (`boundingbox`, `clipping`, `path`). Until [#742](https://github.com/firejune/rigc/issues/742) nothing was written, so the rebuild asked the atlas for the placeholder and `A08_REGION_NAMES_MATCH_ATTACHMENTS` refused it | nothing about the art, which is carried. The **name** is what is gone, so this matters where something downstream looks that attachment up by the name the source gave it |
|
|
598
|
+
| `ATTACHMENT_SEQUENCE` | `BLOCK` | 1 | a `sequence` block — a numbered image series — that the rig spec cannot say **as written**. Since [#729](https://github.com/firejune/rigc/issues/729) a well-formed block on a region, mesh or linked mesh is carried field for field, with no `image` on the loose route (the frames `<path><number>` are the art), and a `sequence` timeline with it; what is left here is a block the parser reads into a series other than the one written — no `count` (0 regions), a `setup` past the end (clamped), a fraction — or one on a `boundingbox`, `clipping` or `path`, where the parser never reads it. The detail quotes the block and says which | the rebuild draws the single region the attachment names. Fix the block in the source — a `count` is the usual one — and ingest again |
|
|
599
|
+
| `ATTACHMENT_TIMELINE` | `BLOCK` | 1 | an attachment timeline that is neither `deform` nor `sequence`. Both are carried since [#729](https://github.com/firejune/rigc/issues/729), and `readAnimation` tests an attachment timeline for exactly those two names and ignores anything else (`SkeletonJson.js:1147-1201`) — so what reaches this line is a name outside the format, which no player plays either | fix the timeline's name in the source, or accept that the rebuild does not carry it |
|
|
600
600
|
| `BONE_FIELD` | `BLOCK` | 1 | a bone field with no rig-spec field, so it is dropped. A 4.0/4.1 export spelling `transform` where 4.3 spells `inherit` lands here; so does a misspelling | check the name against AUTHORING §3 first — a typo and an unsupported field read exactly the same |
|
|
601
601
|
| `BONE_TIMELINE` | `BLOCK` | 1 | a bone timeline the motion spec has no track for. The detail names the eleven it has, read off the table. Since [#733](https://github.com/firejune/rigc/issues/733) carried `inherit` — the eleventh case of the runtime's own bone switch, a stepped mode per key — every bone timeline the runtime plays has a track, so this is reachable only for a name the **parser** throws on too (`Invalid timeline type for a bone`), the position `PHYSICS_TIMELINE` is in | check the spelling; there is no bone timeline left for the rebuild to be missing |
|
|
602
602
|
| `CONSTRAINT_FIELD` | `BLOCK` | 1 | as `BONE_FIELD`, on a constraint, with its type named beside it | as `BONE_FIELD` |
|
|
@@ -624,10 +624,11 @@ is the one failure a comparison of two sets cannot show you.
|
|
|
624
624
|
| `TRANSFORM_KEY_FIELD` | `BLOCK` | 1 | as `IK_KEY_FIELD`, on a `transform` timeline | as `IK_KEY_FIELD` |
|
|
625
625
|
|
|
626
626
|
⚠️ **One thing the table cannot carry: the region key is kept where a placeholder is
|
|
627
|
-
CONTESTED too,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
627
|
+
CONTESTED too, whether or not a line is printed.** On a contested placeholder rigc
|
|
628
|
+
composes `<skin>/<placeholder>`, so `ATTACHMENT_NAME` fires there only where the
|
|
629
|
+
source's name differs from that composed one ([#746](https://github.com/firejune/rigc/issues/746);
|
|
630
|
+
`IG68`–`IG70` hold both directions) — and until
|
|
631
|
+
[#742](https://github.com/firejune/rigc/issues/742) the region went with the name anyway:
|
|
631
632
|
`compile` pins a composed name's `path` at the **placeholder**, so two skins naming two
|
|
632
633
|
regions rebuilt onto **one**, neither of them the art the source drew, with no line
|
|
633
634
|
printed. `ingest` now writes the source's region as `path` on both, and `IG53` in
|
package/docs/SPEC_COVERAGE.md
CHANGED
|
@@ -624,14 +624,14 @@ with the member's own `skin: true` — either half alone is refused, because `Sk
|
|
|
624
624
|
|
|
625
625
|
| Part-1 type | rigc | Detail |
|
|
626
626
|
| --- | --- | --- |
|
|
627
|
-
| `region` | 🟡 | emits `width`, `height` (always, from PNG measurement — the fix for case 6c), `x`, `y` (only when non-zero), `rotation` (only when non-zero, cancelling the bone's world rotation), `scaleX`/`scaleY` (`compile.ts:1238-1239`), `color`, and `path` — taken from the rig spec, else derived from the image basename and omitted when that basename *is* the attachment name (the region name == attachment name == PNG basename convention **A08** + **A27** enforce).
|
|
628
|
-
| `mesh` | 🟡 | emits `type`, `uvs`, `triangles`, `vertices` (**weighted encoding only**), `hull`, `width`, `height`, `edges` (`compile.ts:1343`, and part 4's rung-6 entry measures it byte-identical to the reference), `path`, `color`.
|
|
629
|
-
| `linkedmesh` | 🟡 | emits `type`, `source`, its own `path`, `width`, `height` and `color`, and `slot`/`skin`/`timelines` **only where they differ from the parser's defaults** (this attachment's slot, the default skin, true).
|
|
627
|
+
| `region` | 🟡 | emits `width`, `height` (always, from PNG measurement — the fix for case 6c), `x`, `y` (only when non-zero), `rotation` (only when non-zero, cancelling the bone's world rotation), `scaleX`/`scaleY` (`compile.ts:1238-1239`), `color`, and `path` — taken from the rig spec, else derived from the image basename and omitted when that basename *is* the attachment name (the region name == attachment name == PNG basename convention **A08** + **A27** enforce). ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row |
|
|
628
|
+
| `mesh` | 🟡 | emits `type`, `uvs`, `triangles`, `vertices` (**weighted encoding only**), `hull`, `width`, `height`, `edges` (`compile.ts:1343`, and part 4's rung-6 entry measures it byte-identical to the reference), `path`, `color`. ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row. Unweighted meshes are 🚫 **A20_MESH_WEIGHTS_COHERENT** (`validate.ts:430-433`) |
|
|
629
|
+
| `linkedmesh` | 🟡 | emits `type`, `source`, its own `path`, `width`, `height` and `color`, and `slot`/`skin`/`timelines` **only where they differ from the parser's defaults** (this attachment's slot, the default skin, true). ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row. Geometry keys on a link are refused by name — the parser returns before `readVertices`, so it reads none of them. A chain (a `source` that is itself a link) and a link to itself are refused: they resolve in file order and load `worldVerticesLength` 0 in one of the two orders, silently. `A21`/`A28` leave a link out (its rim and rows are its source's), `A04`/`A20`/`A22` read it as the mesh it resolves to, `A13` counts it as a mesh slot of its own ([#691](https://github.com/firejune/rigc/issues/691)) |
|
|
630
630
|
| `boundingbox` | ✅ | `vertexCount` (required and cross-checked), `vertices` **or** by-name `weights`, `color`. **A33_VERTEX_ATTACHMENT_GEOMETRY** |
|
|
631
631
|
| `path` | ✅ | `vertexCount` (required, and checked as a multiple of 3 — the parser's own `vertexCount / 3` takes a fractional size in silence), `vertices` **or** by-name `weights`, `closed`, `constantSpeed`, `color`, and a **measured** `lengths`: the cumulative setup length at the end of each curve, taken through each influence's own bone and measured as `PathConstraint` measures it — its own four-sample forward difference, which is what the editor writes too and is about 0.5 % below the arc (AUTHORING §10.6) — refused if authored. **A33_VERTEX_ATTACHMENT_GEOMETRY** re-checks the structure and the array's monotonicity. A `deform` timeline may key one: the array is the control points, `A39` SKIPs on it (no triangles), and `lengths` stays the setup measurement because the format has nowhere to put a per-key one — which only a `constantSpeed: false` traversal reads (AUTHORING §4.11) |
|
|
632
632
|
| `point` | ❌ | deliberately deferred — never appears in the corpus (part 3-1) |
|
|
633
633
|
| `clipping` | ✅ under `--profile spine` · 🚫 under `spine-html` | `end` (refused when it names no slot), `convex`, `inverse`, `vertexCount`, geometry, `color`. **A33**, and **A11_NO_CLIPPING_ATTACHMENTS** is the renderer-profile refusal |
|
|
634
|
-
| `sequence` block |
|
|
634
|
+
| `sequence` block | ✅ | on a region, a mesh or a linked mesh: `count` (required — the parser's 0 loads no region), `start`, `digits`, `setup`, emitted as stated. The frames are the regions `<path><start + i>` zero-padded to `digits`, each atlased by name — the loose route's PNG of that name, or `--atlas-in`'s region — and a missing frame is refused with its number and the name looked for. A `setup` past the end (clamped), a fraction, an `image` or a `generator` beside it, and a `sequence` on any other kind are refused by name. **A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES** ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
635
635
|
|
|
636
636
|
Across all five types, rigc emits the attachment's own **`name`** in exactly one case: a placeholder
|
|
637
637
|
that more than one skin fills (`compile.ts`'s `composeSkinAttachmentName` and `nameSkinAttachment`).
|
|
@@ -682,7 +682,7 @@ editor-made meshes — those arrive as authored `uvs`/`triangles`/`weights`.
|
|
|
682
682
|
| `ik`, `transform` | ✅ — one unnamed timeline per constraint, keyed by the motion spec's `ik` / `transform` arrays |
|
|
683
683
|
| `path.position/spacing/mix`, `slider.time/mix` | ✅ (`PATH_TRACKS` / `SLIDER_TRACKS`) — authored as `tracks` entries naming `path` or `slider`, the same shape as `physics`, since both groups put named timelines under a constraint name. `path.mix` is one timeline of three channels |
|
|
684
684
|
| `attachments.<skin>.…deform` | ❌ (validator knows it: 1 channel, `validate.ts:104-107`) |
|
|
685
|
-
| `attachments.…sequence` |
|
|
685
|
+
| `attachments.…sequence` | ✅ — the motion spec's per-animation `sequence` array of `{ skin?, slot, attachment, keys: [{ t, mode?, index?, delay? }] }`, each field emitted only where stated. A mode outside the seven (read as `hold`), a fractional or out-of-range `index` (truncated, clamped), an advancing mode at an effective delay of 0 (never advances), an attachment with no `sequence` block (every mode shows its one region) and a linked mesh that plays its source's timelines (the key is applied to nothing) are refused by name. **A46** poses every key at mid-frame samples against the file's own statement ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
686
686
|
| `drawOrder` | ✅ — the motion spec's per-animation `drawOrder` array of `{ t, offsets: [{ slot, offset }] }` (`compile.ts:823`, `:850`, `compileDrawOrder` `:1918-1970`). A key with no `offsets`, and a key with an empty one, both emit the parser's reset-to-setup encoding, so two spellings cannot make two files. An unknown slot, a slot offset twice in one key, a non-whole offset and a landing outside the emitted slots are each refused by name, and **A31_DRAW_ORDER_OFFSETS_RESOLVE** re-checks the emitted file |
|
|
687
687
|
| `drawOrderFolder` | ❌ — editor bookkeeping, 4.3-only |
|
|
688
688
|
| `events` (+ the `root.events` block) | ✅ — the rig spec declares the names (`rig.ts` `RigEvent`), the motion spec's per-animation `events` array fires them, and **A32_EVENT_KEYS_RESOLVE** checks the three ways the timeline goes wrong quietly. `int`/`float`/`string` overrides and `audio`/`volume`/`balance` all round-trip |
|
|
@@ -738,6 +738,7 @@ This is the split Part 4(c) needs. **Spine-validity** = the file is wrong for an
|
|
|
738
738
|
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | validity | a slot `dark` the parser drops or reads as NaN, an `rgba2` or `rgb2` timeline on a slot with no dark colour to pose, or a key whose posed light/dark is not what it states |
|
|
739
739
|
| `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` | validity | a linked mesh, in either spelling, that also states `uvs`, `triangles`, `vertices`, `hull` or `edges` — keys the `source` branch returns before reading, so the file says one mesh and every runtime draws its source's |
|
|
740
740
|
| `A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN` | validity | an `rgb` or `alpha` timeline beside another colour timeline of the slot that poses the same channel — the later in the file overwrites the other at every time — or a key of one the pose does not reproduce |
|
|
741
|
+
| `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` | validity | a numbered series that loads as something other than what the file states — a `sequence` block with no `count` (0 regions) or a `setup` past the end (clamped), or a `sequence` key whose `mode` is outside the seven (read as `hold`), whose `index` is fractional or past the end, whose advancing mode runs at an effective delay of 0, or that steps an attachment with no block — and then the pose: every key sampled mid-frame, the region shown held to the frame the file's statement gives ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
741
742
|
| `A13_MESH_BUDGET` | **renderer-profile** | >4 mesh slots, >80 triangles per mesh |
|
|
742
743
|
| `A14_NO_FULL_FRAME_MESH` | **renderer-profile** | a mesh spanning the whole stage |
|
|
743
744
|
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | **renderer-profile** | an overlay page that can never be transparent — no alpha channel and no `tRNS` chunk |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|