spine-rigc 0.20.3 → 0.21.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 CHANGED
@@ -508,9 +508,9 @@ work on any frames you have, and `bench` is a repository workflow that needs a c
508
508
  and `bun run fetch-examples`. The reasoning behind all three is in
509
509
  [the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
510
510
 
511
- `build` and `validate` both default to `--profile spine` — the 26 validity rules, which
511
+ `build` and `validate` both default to `--profile spine` — the 27 validity rules, which
512
512
  ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
513
- adds all 41: the other 15 are one renderer's policy and one canvas budget's, and they
513
+ adds all 42: the other 15 are one renderer's policy and one canvas budget's, and they
514
514
  fire on perfectly correct editor-produced Spine data, so reach for that profile when
515
515
  you are shipping into *that* project rather than to be thorough. A report always names
516
516
  the profile it ran and lists what that profile left out.
@@ -576,7 +576,7 @@ letting `A17` blame the editor for the harness's own doing.
576
576
  | 📥 **[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 |
577
577
  | 🤖 **[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 |
578
578
  | 🔬 **[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 |
579
- | 🎓 **[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 41 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 |
579
+ | 🎓 **[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 42 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 |
580
580
  | 📋 [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 |
581
581
  | 🗺️ [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 |
582
582
 
@@ -632,7 +632,7 @@ quality."* All six, with their verdicts, are in
632
632
  [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
633
633
 
634
634
  The whole dossier — the yardstick, `diff` and `check` and what neither of them can
635
- see, every rung, the run viewer, the 41 assertions and the selftest behind them — is
635
+ see, every rung, the run viewer, the 42 assertions and the selftest behind them — is
636
636
  [docs/BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
637
637
  Live rung status is
638
638
  [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
package/docs/AUTHORING.md CHANGED
@@ -163,7 +163,7 @@ What the flags mean:
163
163
  | `--atlas-in` | `build` only: resolve every part against the **regions of a pre-packed `.atlas`** instead of against loose PNGs. Region geometry is read from the file and sizes are descaled by the page's `scale:`; the atlas is re-emitted into `--out`, re-anchored — **§0.2** |
164
164
  | `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory). For `pose` it is the directory of **loose part PNGs to place** — every `.png` in it is a part, in name order. For `chainfit` it is only where each attachment's image name **resolves**: the candidate decides what the parts are, so extra PNGs are unused and a missing name is refused by name (§12.3) |
165
165
  | `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
166
- | `--profile` | `spine` = the 26 validity rules (**the default**) · `spine-html` = all 41, opt-in |
166
+ | `--profile` | `spine` = the 27 validity rules (**the default**) · `spine-html` = all 42, opt-in |
167
167
  | `--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 |
168
168
  | `--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 |
169
169
  | `--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` |
@@ -566,7 +566,8 @@ behind it writes literal `x`/`y` instead.
566
566
 
567
567
  **R10 — The `animations` object is keyed in the editor's order, not in yours, and
568
568
  names that have no one order are refused.** Declare animations in whatever order
569
- reads best; the emit keys them codepoint-ascending. This is the one place rigc
569
+ reads best; the emit keys them the way the Spine editor does — **natural and
570
+ case-insensitive**. This is the one place rigc
570
571
  reorders anything you wrote, and it is not cosmetic: a `slider`'s animation is a
571
572
  **name** in JSON and an **ordinal** in the format's binary half, so an editor that
572
573
  re-sorts the object repoints every slider whose animation moved index — silently,
@@ -575,25 +576,41 @@ in a file that still parses and still gates green (§3.5.2,
575
576
  animation's own body is byte-identical either way, and every other collection is
576
577
  emitted in the order you gave it.
577
578
 
578
- ⚠️ **Codepoint is not the editor's comparator.** The editor sorts **natural and
579
- case-insensitive** — measured, two rigs, one axis each:
579
+ ⚠️ **What is measured about that comparator, and what is not.** The editor sorts
580
+ natural and case-insensitive — measured, two rigs, one axis each:
580
581
  `Turn, sweep, wave` came back `sweep, Turn, wave`, and `turn10, turn2, zoom` came
581
582
  back `turn2, turn10, zoom` ([#539](https://github.com/firejune/rigc/issues/539)).
582
- Codepoint agrees with it on most names and not on all, so rigc emits codepoint and
583
- **refuses the sets where the two could differ**, naming the pair. The rule you have
584
- to hold is therefore about *names*, and it is three things:
583
+ But "natural and case-insensitive" is a **family** of comparators, not one, and
584
+ **four** of its choices have never been measured. rigc emits the order every
585
+ member of that family agrees on, and **refuses the sets where one of the four
586
+ would decide**, naming the pair. So the rule you have to hold is about *names*,
587
+ and it is four things:
585
588
 
586
589
  | Do not let two animation names differ | Because | Instead |
587
590
  | --- | --- | --- |
588
- | by **case** at the character that orders them (`Turn` against `sweep`, or `Turn` against `turn`) | folding the case reverses them, and a pure case tie is settled by a tie-break nobody has measured | pick one case for all of them, or change a letter |
589
- | by a **number** read two ways (`turn2` against `turn10`, `turn01` against `turn1`, `1turn` against `turn`) | as text `turn10` sorts first and as a number it does not; `01` and `1` are one number written twice | pad the digits to the same width — `turn02` beside `turn10` |
591
+ | by **case alone** (`Turn` against `turn`) | they fold together, so only a tie-break separates them, and nobody has measured which way it breaks | pick one case for all of them, or change a letter |
592
+ | by a **number written two ways** (`turn01` against `turn1`) | `01` and `1` are one number twice; shorter-first, longer-first and lexicographic are all real tie-breaks | write the number one way — with leading zeros or without, but not both |
593
+ | by a **digit run against a word** (`1turn` against `turn`) | comparators differ on whether a number sorts before a word | rename so a run of digits is never compared against a word |
590
594
  | by a **separator** — anything that is neither a letter nor a digit (`wave_x` against `wavea`, `wave` against `wave-`) | a collator may treat `-` or a space as ignorable, and `_` sits *between* `Z` and `a`, so folding up and folding down order it oppositely | rename so the first character that differs is a letter or a digit |
591
595
 
592
- ⭐ **Capitals and digits are not what is refused** — only pairs whose order turns
593
- on them. `Sweep, Turn, Wave, Zoom02, Zoom10` builds: every comparator puts those
594
- five in one order, so codepoint *is* the editor's order for them. A set with no
595
- such pair is safe under **every** candidate comparator, which is why rigc does not
596
- have to reproduce the editor's sort to know your rig is safe under it.
596
+ ⭐ **Capitals and numbered series are not what is refused** — only pairs one of
597
+ those four decides. `Sweep, Turn, Wave, Zoom02, Zoom10` builds, and so does
598
+ `shot1 … shot12`: every member of the family puts each of those sets in one
599
+ order, and that order is what rigc emits. A numbered series that crosses 9 → 10 is
600
+ keyed **1, 2, … 9, 10, 11, 12**, which is what the editor does with it — and is
601
+ not what a codepoint sort does.
602
+
603
+ ✅ **This list had two more rows before
604
+ [#543](https://github.com/firejune/rigc/issues/543), and both were artefacts of
605
+ the emit rather than facts about the editor.** rigc used to key `animations`
606
+ **codepoint-ascending** and refuse every pair codepoint and the editor could order
607
+ differently — which refused a pair that folds the other way (`Turn` against
608
+ `sweep`) and a pair of digit runs of unequal width (`turn10` against `turn2`).
609
+ Those are the only two name sets anybody has ever put through the editor and read
610
+ back, so the tool was refusing precisely the pairs it knew the most about, and its
611
+ only repair was *rename* — the one repair a transcription cannot take. Emitting a
612
+ member of the family instead moves no byte on any set the old rule accepted; it
613
+ just stops refusing the ones it did.
597
614
 
598
615
  ---
599
616
 
@@ -1377,6 +1394,14 @@ carrying here:
1377
1394
  - A physics constraint's five components all default to 0, so one that names none of
1378
1395
  them parses cleanly and does nothing at all. rigc refuses it up front, and `A23`
1379
1396
  catches it from the other side.
1397
+ - An **ik** and a **physics** constraint both carry `ScaleYMode` under the key
1398
+ `scaleY`, spelled `"none"`, `"uniform"` or `"volume"`. It is an enum resolved by
1399
+ `Utils.enumValue`, so only the first letter's case is free and an unrecognised
1400
+ name is assigned as `undefined` with no error; rigc checks it, like the three
1401
+ path modes below. ⚠️ `src/rig.ts` called the physics one **`scaleYMode`** until
1402
+ issue #545 — the runtime's field name rather than the format's key — and nothing
1403
+ read it, so a spec that wrote `scaleYMode` set no mode and said nothing. A rig
1404
+ that still writes it is now refused by name, with `scaleY` beside it.
1380
1405
 
1381
1406
  Every constraint may also carry `skin: true`, which makes it run only under the skin
1382
1407
  that lists it — see §3.4.1, and note that the flag alone does nothing.
@@ -1482,25 +1507,32 @@ animation now stands at the position. `gallery/look` went into a licensed editor
1482
1507
  `sweep, tilt, turn` with **`yaw -> "sweep"`**: a file that parses, gates green and
1483
1508
  applies the wrong animation. ⭐ Its second slider is what named the mechanism
1484
1509
  rather than a second casualty — `tilt` survived because it sat at index 1 in both
1485
- orderings. rigc now emits animations codepoint-ascending so the editor's re-sort
1510
+ orderings. rigc now emits animations in the editor's own order so its re-sort
1486
1511
  moves no index ([#535](https://github.com/firejune/rigc/issues/535)); on the same
1487
1512
  rig through the same editor that restored `yaw -> "turn"` and took the
1488
1513
  re-rendered mean absolute error from 10.4655 / 8.4961 / 8.7140 down to
1489
- 0.3035 / 0.0769 / 0.0588.
1490
-
1491
- ⚠️ Codepoint is not the editor's own comparator — it sorts natural and
1492
- case-insensitive ([#539](https://github.com/firejune/rigc/issues/539)) — so the
1493
- emit is only its order for names no comparator can put two ways, and the rest are
1494
- a compile error. **R10** has the three shapes to avoid.
1495
-
1496
- ⚠️ **What that repair does not reach: names a codepoint sort and a friendlier one
1497
- disagree about.** Every animation name in every editor-authored file this
1498
- repository has is lowercase ASCII with `-` or `_`, so nothing measured here
1499
- separates codepoint order from a case-insensitive or digit-aware one. Names
1500
- differing only in case (`Turn` / `turn`) or carrying unpadded digits (`turn2` /
1501
- `turn10`) are where the two could part, and there the hazard returns. Until
1502
- somebody round-trips such a pair, **name animations so that every ordering anyone
1503
- might use agrees** — one case, and digits padded or absent.
1514
+ 0.3035 / 0.0769 / 0.0588. (`look`'s three names are ones a codepoint sort orders
1515
+ identically, which is what rigc emitted when that trip was measured.)
1516
+
1517
+ ⚠️ The editor's comparator is natural and case-insensitive
1518
+ ([#539](https://github.com/firejune/rigc/issues/539)), and four of its choices are
1519
+ unmeasured — so the emit is that family's order for names none of the four
1520
+ decides, and the rest are a compile error. **R10** has the four shapes to avoid.
1521
+
1522
+ ✅ **What that repair does not reach is a compile error now, not a hazard.** This
1523
+ paragraph used to say that names a codepoint sort and a friendlier one disagree
1524
+ about — `Turn` / `turn`, `turn2` / `turn10` — were where *the hazard returns*, and
1525
+ that nobody had round-tripped such a pair. Somebody has: `Turn, sweep, wave` came
1526
+ back `sweep, Turn, wave` and `turn10, turn2, zoom` came back `turn2, turn10, zoom`
1527
+ ([#539](https://github.com/firejune/rigc/issues/539)). ⇒ rigc no longer leaves
1528
+ that to naming discipline — and since
1529
+ [#543](https://github.com/firejune/rigc/issues/543) it does better than refusing
1530
+ those two, because they are the two sets the editor's answer is **known** for:
1531
+ both are emitted in the order it returned. What is still a compile error is the
1532
+ set whose order turns on one of the four unmeasured choices, printed with both
1533
+ names, which of them decides it, and the rename that settles it. What changed is
1534
+ the price of forgetting: a build that stops, rather than a slider that silently
1535
+ applies the wrong animation.
1504
1536
 
1505
1537
  ⚠️ **The fields of the model you did not choose are refused, not ignored.** The
1506
1538
  parser reads `time` only in the bone-less branch and `property`/`from`/`to`/`scale`/
@@ -1770,7 +1802,8 @@ is not an array. Every field is optional and each is the payload a firing
1770
1802
 
1771
1803
  Optional with one exception, and only meaningful for rigc's own formations:
1772
1804
  `meshSlots` and `meshTriangles` (the two halves of the mesh budget `A13` measures
1773
- against), `axisBone`, `massBone`, `detached`, `deformMayFold`. Nothing in skeleton
1805
+ against), `axisBone`, `massBone`, `detached`, `deformMayFold`, `editorRoundTrip`.
1806
+ Nothing in skeleton
1774
1807
  JSON records that a
1775
1808
  bone carries a cut's axis or that a parentage is forbidden, so the rig spec says it
1776
1809
  and the validator's archetype assertions read it. **An assertion whose field is
@@ -1808,6 +1841,33 @@ and the entry is gone. ⇒ An exemption whose `why` reads *"known defect, see
1808
1841
  against a fix, not a fix — and the thing that made it repayable was A39
1809
1842
  measuring the ceiling the art could actually take.
1810
1843
 
1844
+ 🎬 **`editorRoundTrip` is the one field here that names a CONSUMER rather than a
1845
+ shape.** Write `"editorRoundTrip": true` when this rig is authored to come back
1846
+ out of the Spine editor — imported, hand-edited, exported — and
1847
+ `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` refuses a physics constraint driving a
1848
+ component that editor cannot hold. `true` is the only accepted value; a `false`
1849
+ would be a key nothing reads ([#545](https://github.com/firejune/rigc/issues/545)).
1850
+
1851
+ ⚠️ **Leaving it out is not a weaker gate, and this is the part worth reading.**
1852
+ rigc's output is not wrong here: a physics constraint driving `rotate` is valid
1853
+ Spine 4.3 that every runtime plays — a cowlick, a tail, an ear — so refusing it by
1854
+ default would be refusing correct data on behalf of a pipeline nobody declared.
1855
+ What a rig that says nothing gets instead is the **SKIP**, and the SKIP names the
1856
+ constraint and the component:
1857
+
1858
+ ```
1859
+ SKIP A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP: the rig "look" does not declare `invariants.editorRoundTrip`, so nothing here is gated against the Spine editor. What is here: physics "whip" drives rotate, and the editor's physics model holds x and y only, so a round trip returns that constraint driving nothing at all (issue #540)
1860
+ ```
1861
+
1862
+ 📏 **Measured, not inferred** ([#540](https://github.com/firejune/rigc/issues/540)):
1863
+ three rigs, twelve constraints, predictions written before the round trip. A lone
1864
+ `y` came back and `x` + `y` together came back — the rule is membership, not arity
1865
+ — while a lone `rotate`, a lone `scaleX` and a lone `shearX` each came back driving
1866
+ **no component at all**, and neither `scaleY` mode rescues `scaleX`. So the fix for
1867
+ a refusal is to drive the constraint in `x`/`y`, or to drop the declaration if this
1868
+ rig never goes near the editor. ⇒ `A23_PHYSICS_CONSTRAINT_EFFECTIVE` is the same
1869
+ loss seen from the far side: it is what fires on the file the editor hands **back**.
1870
+
1811
1871
  🚫 **Do not reach for it to cover a part you have faded out.** A key whose slot
1812
1872
  draws no pixels at that key's own time is already passed over — `A39` measures
1813
1873
  that and says so (§4.11, and the `skipped` line in the `DEFORM` block). Declaring
@@ -3220,6 +3280,38 @@ compiled, and ask only what the one file in front of them can answer. Every
3220
3280
  one per message. (The rig spec's parser predates the convention and its messages
3221
3281
  are prose, so they sit in the second table with everything else.)
3222
3282
 
3283
+ 🚨 **A key neither format has is refused by name, in both files.** Not a row in
3284
+ the table below, because it is not about one field: every object in a rig spec and
3285
+ in a motion spec is checked against the keys its shape actually owns, and a key
3286
+ outside that set stops the build. Issue #545 is why — before it, such a key was
3287
+ never looked at, never mentioned and never emitted, and the build exited 0 with
3288
+ every assertion green. Four keys planted into one physics constraint all vanished,
3289
+ and no line of output named any of them.
3290
+
3291
+ ```
3292
+ rigc compile error: rig.json: constraint "ctl" (physics) has 4 keys this compiler
3293
+ does not read: "scaleYMode" (did you mean "scaleY", "scaleX"?), "scale" (did you
3294
+ mean "scaleX", "scaleY"?), "wobble", "ROTATE" (did you mean "rotate"?). Nothing
3295
+ reads such a key, so it would be dropped from the emitted skeleton in silence —
3296
+ fix the spelling or remove it. Known here: bone, damping, dampingGlobal, fps, …
3297
+ ```
3298
+
3299
+ Read it as a **repair**, not a rule: every stray key on that object is named at
3300
+ once, the closest known spellings come with it (the search is case-insensitive, so
3301
+ a real key in the wrong case leads the list), and the shape's whole key set is
3302
+ printed after. What it will *not* do is guess — a key four edits from anything gets
3303
+ no suggestion, only the set.
3304
+
3305
+ ⚠️ There is **no forward-compatibility escape**, and no `note` field except where
3306
+ one is listed: a rig spec's root, a motion spec's root, an animation, and a
3307
+ `physics` tuning entry. Prose anywhere else has to go in a document, because a key
3308
+ the compiler tolerates is a key it cannot distinguish from one you meant it to
3309
+ read. (The **cut manifest** is deliberately outside this: it is the record of the
3310
+ pipeline that produced the art as much as an input, it carries fields the compiler
3311
+ states outright that it does not read — `roi` — and the fixtures in this repository
3312
+ already give it `note` and `archetype`. It has no shape parse at all, which is the
3313
+ same hole issue #307 closed for the motion spec.)
3314
+
3223
3315
  | Key | Refused when it is not | Why the shape matters |
3224
3316
  | --- | --- | --- |
3225
3317
  | the file itself | a JSON object | the version row below would otherwise report a missing `spec` tag in a file that has no fields at all |
@@ -3333,7 +3425,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
3333
3425
  | `skin "S": uses the long form … and also has a key "X"` | §3.4.1 — move the slot inside `attachments` |
3334
3426
  | `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 |
3335
3427
  | `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
3336
- | `N pair(s) of animation names have no one order: … "Turn" / "sweep" (case) — codepoint puts "Turn" first only because of letter case; folded, "sweep" comes first; rename one of them so nothing but case has to be compared` | **R10** — rename until no pair is left. The kind in brackets is which of the three it is: `case`, `number` (pad the digit runs to the same width) or `separator` (make the first character that differs a letter or a digit). rigc keys `animations` codepoint-ascending and the editor sorts natural and case-insensitive ([#539](https://github.com/firejune/rigc/issues/539)); on names where those can disagree, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
3428
+ | `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)) |
3337
3429
 
3338
3430
  ### 5.2 Assertions — the gate
3339
3431
 
@@ -3402,6 +3494,7 @@ The report prints one line per assertion:
3402
3494
  | `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` |
3403
3495
  | `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. **SKIP** when no animation carries a deform timeline, when nothing keyed has triangles, when every mesh keyed is exempt, when every key measured draws no pixels or is unreachable *and no span between them folds where anything is drawn*, or when there is no rig info at all |
3404
3496
  | `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` | both | two or more sliders whose animations key the same timeline, where a later one is not `additive` — it writes that property outright at `mix: 1` and every earlier slider on it is dead (§3.5.2). Also fires when the shared timeline **cannot** be additive (a slot colour, an attachment swap, a draw order, a sequence), where `"additive": true` is not the fix and one of the two has to go. The detail names the bone or slot and the property, every slider keying it in `constraints` order with its flag, and which one wins today. Three shapes are deliberately not findings: a slider below `mix: 1` or with its `mix` keyed (the apply is then a lerp from the current pose, not an overwrite), two `skinRequired` sliders no skin activates together, and two sliders on different properties. **SKIP** when fewer than two sliders are at full authority; a PASS means two were compared |
3497
+ | `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 |
3405
3498
 
3406
3499
  `both ◑` marks a mixed assertion: its validity half always runs and its policy
3407
3500
  clauses are gated by profile.
@@ -3426,6 +3519,7 @@ says so, because a deferral without its reason is a wall rather than a work item
3426
3519
  | 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 |
3427
3520
  | 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 |
3428
3521
  | 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 |
3522
+ | 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 |
3429
3523
 
3430
3524
  Two more limits that are not errors but will shape what you can attempt:
3431
3525
 
@@ -4900,10 +4994,13 @@ one key, deform blocks counted at each of their three levels;
4900
4994
  ⇒ in rigc: only `animations` is emitted sorted (R10), because it is the one
4901
4995
  object measured here whose ORDER is also an index space — every reference into
4902
4996
  the re-sorted *other* objects is by name on both sides, so nothing moves when
4903
- they are re-keyed. rigc emits **codepoint** and refuses the name sets on which
4904
- codepoint and the editor's comparator could differ, rather than reproducing a
4905
- comparator whose leading-zero, case-tie, digit-against-word and separator
4906
- behaviour is still unmeasured.
4997
+ they are re-keyed. rigc emits **that comparator's own order** and refuses the name
4998
+ sets on which its leading-zero, case-tie, digit-against-word or separator
4999
+ behaviour — the four choices still unmeasured — would decide a pair. Sorting the
5000
+ 105 collections that way reproduces **105 of 105**, the three codepoint cannot
5001
+ included, and refuses none of them; the codepoint rule that stood until
5002
+ [#543](https://github.com/firejune/rigc/issues/543) reproduced 102 and refused
5003
+ those same 3.
4907
5004
 
4908
5005
  ✅ **`events` is re-keyed too, and the references into it survive it.** The same
4909
5006
  session measured it: `zebra, mike, alpha` came back `alpha, mike, zebra`, and the
@@ -4911,6 +5008,21 @@ firings still resolved **by name** — `0.3 -> mike`, `0.6 -> alpha`, payloads
4911
5008
  intact (#539). So the editor treats `events` and `animations` differently, and
4912
5009
  rigc emits events in the order you declare them.
4913
5010
 
5011
+ ⚠️ **Read *"leaves every ARRAY alone"* above as bones, slots and constraints —
5012
+ `skins` is the array that round trip was not taken over.** `gallery/look` declares
5013
+ one skin, as does every other rig in this repository and all twelve editor exports
5014
+ in `examples/`, and a one-element array comes back in order whatever the editor
5015
+ does to it — so nothing here is
5016
+ evidence about `skins`, and a pull request that once called them *measured
5017
+ preserved* was reading a vacuous result ([#544](https://github.com/firejune/rigc/issues/544)).
5018
+ It matters because `skins` carries ordinals in the binary half too —
5019
+ `skins[readInt()]` for an attachment timeline and a linked mesh's skin index — so
5020
+ a re-order there would repoint them the way the `animations` re-key repoints a
5021
+ slider. ⛔ And it cannot be measured today: the editor refuses a four-skin rig on
5022
+ import without writing a project file or printing a word
5023
+ ([#541](https://github.com/firejune/rigc/issues/541)). ⇒ **If you author more than
5024
+ one skin, nothing on this page says the editor survives it.**
5025
+
4914
5026
  ### 10.2 Draw order
4915
5027
 
4916
5028
  📗 **An overlap change is a draw-order key.** The draw order *"can be keyed"*, and
package/docs/FACE.md CHANGED
@@ -1182,7 +1182,10 @@ breaks the moment the two share a target — in the worked example both `turn` a
1182
1182
 
1183
1183
  ✅ **The editor half, measured.** This paragraph said *unknown* until the round
1184
1184
  trip was taken with `tools/editor_roundtrip.ts` on a licensed editor (data
1185
- version 4.3.26) against a 4.3.13 build of this worked example. What it found:
1185
+ version 4.3.26) against a 4.3.13 build of
1186
+ [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) — this
1187
+ subsection's worked case, not the `gallery/portrait` this page names at the top,
1188
+ which declares no constraints at all and so can carry no slider. What it found:
1186
1189
 
1187
1190
  - **Both sliders come back, and the parameter axis survives.** `additive`,
1188
1191
  `local`, `bone`, `property`, `from`, `max` and `scale` are identical field for
@@ -1194,7 +1197,11 @@ version 4.3.26) against a 4.3.13 build of this worked example. What it found:
1194
1197
  animation is an ordinal in the format, so `yaw -> "turn"` returned as
1195
1198
  `yaw -> "sweep"` — the first animation of the sorted list
1196
1199
  ([#535](https://github.com/firejune/rigc/issues/535)). rigc now emits
1197
- animations codepoint-ascending; on the same rig through the same editor that
1200
+ animations in the editor's own order — natural and case-insensitive
1201
+ ([#539](https://github.com/firejune/rigc/issues/539),
1202
+ [#543](https://github.com/firejune/rigc/issues/543)); this rig's names are ones
1203
+ a codepoint sort orders identically, which is what it emitted at the time of
1204
+ the round trip below. On the same rig through the same editor that
1198
1205
  restored `yaw -> "turn"` and took the re-rendered mean absolute error from
1199
1206
  10.4655 / 8.4961 / 8.7140 (`sweep` / `tilt` / `turn`) to
1200
1207
  0.3035 / 0.0769 / 0.0588, worst drift 16.535 px to 3.947 px.
@@ -1202,16 +1209,55 @@ version 4.3.26) against a 4.3.13 build of this worked example. What it found:
1202
1209
  `rotate: 1` is absent from the export, and an absent `rotate` parses as **0**
1203
1210
  (`SkeletonJson`), so the returned file states *drives nothing* rather than
1204
1211
  omitting a default — which is why `A23_PHYSICS_CONSTRAINT_EFFECTIVE` refuses it
1205
- by name. Independent of the ordering defect, and open as
1212
+ by name. Independent of the ordering defect, and filed as
1206
1213
  [#536](https://github.com/firejune/rigc/issues/536).
1214
+
1215
+ ✅ **Why, measured since.** It is not elision and not a defect in one field:
1216
+ the editor's physics model holds `x` and `y` and nothing else, with no limit on
1217
+ how many at once. Three rigs, twelve constraints, predictions written before the
1218
+ round trip — a lone `y` came back, `x` and `y` together came back, and a lone
1219
+ `rotate`, a lone `scaleX` and a lone `shearX` each came back as **no components
1220
+ at all**, with every constraint's fixed-point `strength` returning exactly so a
1221
+ silent harness failure could not read as a finding
1222
+ ([#540](https://github.com/firejune/rigc/issues/540)). ⇒ **A rotation-driven
1223
+ jiggle does not survive the editor, and no `scaleY` mode substitutes for it.**
1224
+ ⚠️ Still open on #536: whether the loss happens at import or at export. The
1225
+ project file's bytes cannot settle it — it carries derived float32s that no
1226
+ input declares — and the answer is invisible to an author either way.
1227
+
1228
+ ✅ **And the gate now says so before the trip, not after.** A face rig that is
1229
+ authored to come back out of the editor declares
1230
+ `"invariants": { "editorRoundTrip": true }` (AUTHORING §3.7) and
1231
+ `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` refuses the constraint by name at build
1232
+ time, with the fix in the message: drive it in `x`/`y`, or drop the declaration.
1233
+ ⚠️ A rig that declares nothing is **not** silent either — A41 SKIPs and the skip
1234
+ names the constraint and the component, which is the whole reason the rule is
1235
+ opt-in rather than default-off. rigc's own output was never wrong here: a
1236
+ rotation jiggle is valid Spine 4.3 that every runtime plays, and refusing it for
1237
+ everybody would be refusing correct data on behalf of one consumer. ⇒ A23 and
1238
+ A41 are the same loss from opposite sides of the trip: A41 fires on what goes
1239
+ in, A23 on what comes back.
1207
1240
  - 🔸 Unexplained: `diff` reports `animations.curve_kinds` moved on **196 of 200**
1208
1241
  keys in every round trip taken, the clean one included. Visually small once the
1209
1242
  ordering is fixed — but it is 98% of the keys, and *small* is not *explained*.
1210
1243
 
1211
- ⚠️ **What it still does not measure:** a rig carrying more than one skin or more
1212
- than one event, which is where the same shape — an ordinal into an object the
1213
- editor re-keys — could bite next (AUTHORING §10.1). The runtime half was never in
1214
- doubt: every figure above came back through `spine-core`.
1244
+ ✅ **The two things this paragraph said it still did not measure have since been
1245
+ measured, and they came out opposite ways** — [#544](https://github.com/firejune/rigc/issues/544)
1246
+ is the card for having left the sentence standing:
1247
+
1248
+ - **More than one event is safe.** The editor re-keys `events` the way it re-keys
1249
+ `animations` — `zebra, mike, alpha` came back `alpha, mike, zebra` — but every
1250
+ firing resolved **by name**, `0.3 -> mike` and `0.6 -> alpha`, payloads intact
1251
+ ([#539](https://github.com/firejune/rigc/issues/539)). The ordinal shape does
1252
+ *not* bite here, and rigc emits events in the order you declare them.
1253
+ - **More than one skin is worse than unmeasured.** A four-skin rig builds green,
1254
+ parses in `spine-core`, and the editor **refuses to import it** — no project
1255
+ file, no message ([#541](https://github.com/firejune/rigc/issues/541)). So there
1256
+ is no export to read, and every figure on this page was taken on a rig carrying
1257
+ exactly one skin (AUTHORING §10.1).
1258
+
1259
+ The runtime half was never in doubt: every figure above came back through
1260
+ `spine-core`.
1215
1261
 
1216
1262
  ---
1217
1263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-rigc",
3
- "version": "0.20.3",
3
+ "version": "0.21.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": {