spine-rigc 0.15.0 → 0.16.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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "rigc",
3
+ "owner": {
4
+ "name": "firejune",
5
+ "url": "https://github.com/firejune"
6
+ },
7
+ "description": "rigc, the rig compiler for Spine, packaged as agent skills over the guides the npm package ships.",
8
+ "plugins": [
9
+ {
10
+ "name": "rigc",
11
+ "source": "./",
12
+ "description": "Author Spine 4.3 rigs with rigc: an entry skill with the install line and the build, validate, render, check and vote loop, plus routers for rigging, motion, faces and foreign skeletons, each linking the shipped guide that owns its rules.",
13
+ "homepage": "https://github.com/firejune/rigc#readme",
14
+ "repository": "https://github.com/firejune/rigc",
15
+ "license": "MIT",
16
+ "keywords": ["spine", "spine2d", "skeletal-animation", "rig", "compiler", "ai-agents"]
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
+ "name": "rigc",
4
+ "description": "Author Spine 4.3 rigs with rigc, the rig compiler that verifies its own output through a spine-core round-trip. Skills that route an agent to the shipped guide for the need at hand: the hierarchy, a motion, a face, or a skeleton somebody else authored.",
5
+ "author": {
6
+ "name": "firejune",
7
+ "url": "https://github.com/firejune"
8
+ },
9
+ "homepage": "https://github.com/firejune/rigc#readme",
10
+ "repository": "https://github.com/firejune/rigc",
11
+ "license": "MIT",
12
+ "keywords": ["spine", "spine2d", "skeletal-animation", "rig", "compiler", "ai-agents"]
13
+ }
package/LICENSE CHANGED
@@ -19,3 +19,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+
24
+ THIRD-PARTY MATERIAL
25
+
26
+ The licence above covers rigc's own code, documentation and art. It does not cover
27
+ third-party material distributed alongside them - in particular the rendered
28
+ reference frames under bench/reference/, which carry Esoteric Software's example
29
+ image data and are governed by each example's own license.txt beside them:
30
+ redistribution only with that file, and no commercial use. See NOTICE.md.
package/NOTICE.md CHANGED
@@ -54,9 +54,11 @@ Two consequences worth stating plainly:
54
54
  ## Example assets
55
55
 
56
56
  The official Spine example projects are the yardstick this compiler is measured
57
- against. They are owned by Esoteric Software and are **not redistributed** in this
58
- repository: `scripts/fetch-examples.sh` downloads them into a gitignored
59
- `examples/` directory for local evaluation.
57
+ against. They are owned by Esoteric Software. **The example projects themselves are
58
+ not committed to this repository**: `scripts/fetch-examples.sh` downloads them into a
59
+ gitignored `examples/` directory for local evaluation. What this repository *does*
60
+ commit is its own rendered frames of them, under the grant the examples' own licence
61
+ files carry — *Rendered reference frames*, below.
60
62
 
61
63
  Each example directory upstream carries its own `license.txt`, so the terms are
62
64
  per-directory rather than repository-wide. Verified on 2026-08-22 against
@@ -87,7 +89,9 @@ the copyright line:
87
89
  So, for this repository's purposes:
88
90
 
89
91
  - **Images** — redistributable only with the accompanying `license.txt`, and
90
- **non-commercial only**. That is why they are fetched rather than committed.
92
+ **non-commercial only**. That is why the example projects are fetched rather than
93
+ committed, and why the rendered frames this repository *does* commit each carry a
94
+ verbatim copy of that file beside them — *Rendered reference frames*, below.
91
95
  - **Project files** (`.spine`, and the exports derived from them) — **public
92
96
  domain**, usable as the basis for derivative work. This is what makes the
93
97
  examples usable as a structural yardstick.
@@ -95,3 +99,31 @@ So, for this repository's purposes:
95
99
  grant its siblings carry does not exist for it — there is no licence file to
96
100
  accompany its images with. `scripts/fetch-examples.sh` prints a warning naming
97
101
  it. Treat its images as not redistributable.
102
+
103
+ ### Rendered reference frames
104
+
105
+ `bench/reference/` contains **1,293 PNG frames rendered by this project** from the
106
+ examples' own exports: `bench/render_reference.ts` loads each example's `export/`
107
+ out of the gitignored `examples/`, poses it with `spine-core`, and rasterises each
108
+ posed attachment.
109
+
110
+ A rendered frame contains those images' pixels, so committing one **is**
111
+ redistribution — and each example's `license.txt` grants exactly that, *"as long as
112
+ they are accompanied by this license file"*. So **a verbatim copy of the relevant
113
+ `license.txt` sits at each example root** under `bench/reference/`, put there by
114
+ `render_reference.ts` rather than left to memory; all eight are present. The
115
+ **non-commercial** condition in that same file rides along with those images, and
116
+ `LICENSE` says so — rigc's MIT grant covers rigc's own code, documentation and art,
117
+ and does not extend to this material.
118
+
119
+ ⚠️ **`7-anticipation` is excluded, and mechanically so.** With no upstream
120
+ `license.txt` there is no grant to rely on, so its frames are never committed:
121
+ `render_reference.ts` writes them only into a gitignored directory, refuses any
122
+ `--out` inside the repository that `git check-ignore` will not accept, fails closed
123
+ if git cannot answer, and drops a `LOCAL-ONLY.txt` beside them in place of the
124
+ licence file that does not exist.
125
+
126
+ `bench/reference/` is repository material — it is not in `package.json`'s `files`
127
+ list, so it is not part of the published npm package. The full reasoning, and the
128
+ per-rung framing behind it, is in
129
+ [`bench/reference/README.md`](https://github.com/firejune/rigc/blob/main/bench/reference/README.md).
package/README.md CHANGED
@@ -18,7 +18,7 @@ their own work.
18
18
  <img src="https://raw.githubusercontent.com/firejune/rigc/main/assets/rigc-demo.gif" alt="Loose part PNGs assembling themselves into a character that breathes, blinks and waves" width="600" />
19
19
  </p>
20
20
 
21
- <p align="center"><em>Fourteen hand-drawn part PNGs, one rig spec, one motion spec — the assembly,
21
+ <p align="center"><em>Fourteen part PNGs drawn from scratch for this repo, one rig spec, one motion spec — the assembly,
22
22
  the breathing and the wave are all rigc-compiled Spine animations, rendered with
23
23
  <code>rigc render</code>.</em></p>
24
24
 
@@ -73,6 +73,22 @@ the following is a restatement of Esoteric Software's terms, not a term of ours:
73
73
 
74
74
  See [NOTICE.md](NOTICE.md) for the full notice.
75
75
 
76
+ 📐 **What of Esoteric Software's is in this repository, and under what grant.** No
77
+ example asset is committed: `bun run fetch-examples` downloads the example projects
78
+ into a gitignored `examples/`. What *is* committed is `bench/reference/` — 1,293 PNG
79
+ frames **this project renders** from those examples' own exports, so a frame carries
80
+ those images' pixels and committing one **is** redistribution. Each example's own
81
+ `license.txt` permits exactly that, *"as long as they are accompanied by this license
82
+ file"*, and a verbatim copy of it sits at each example root here — all eight, written
83
+ there by the render script rather than left to memory. The same file's
84
+ **non-commercial** condition rides along with those images, and
85
+ [LICENSE](LICENSE) says so: rigc's MIT grant covers rigc's own code, documentation
86
+ and art, not this material. `7-anticipation` publishes no `license.txt` upstream, so
87
+ no such grant exists for it and its frames are never committed — they render only
88
+ into a gitignored directory, enforced by `git check-ignore`. Full reasoning:
89
+ [`bench/reference/README.md`](https://github.com/firejune/rigc/blob/main/bench/reference/README.md)
90
+ (repository material, not in the npm package).
91
+
76
92
  The problem rigc is aimed at is narrow. An agent asked to author a rig has no way
77
93
  to tell whether it succeeded: Spine's JSON parser accepts a great deal of nonsense
78
94
  without a murmur — a constraint in the 4.2 shape simply vanishes, a `size:` that
@@ -114,10 +130,34 @@ the CLI in place); the two are interchangeable — `rigc build …` is
114
130
  `bun cli.ts build …`.
115
131
 
116
132
  Two commands are repository workflows rather than package ones: `bench` and
117
- `check` measure against Spine's official example projects and the reference
118
- frames rendered from them, which are fetched rather than redistributed (see
119
- [NOTICE.md](NOTICE.md)). They need a clone and `bun run fetch-examples`, and say
120
- so by name when the corpus is absent.
133
+ `check` measure against Spine's official example projects fetched, never
134
+ committed and against reference frames this project renders from them, which
135
+ **are** committed, each example's own `license.txt` beside them under the
136
+ redistribution grant those files carry; the images stay **non-commercial only**.
137
+ The reasoning is in
138
+ [`bench/reference/README.md`](https://github.com/firejune/rigc/blob/main/bench/reference/README.md)
139
+ and the terms in [NOTICE.md](NOTICE.md). Both commands need a clone and `bun run
140
+ fetch-examples`, and say so by name when the corpus is absent.
141
+
142
+ ### Install it into your agent
143
+
144
+ The guides under [Documentation](#documentation) also ship as
145
+ [Agent Skills](https://agentskills.io) — `skills/<name>/SKILL.md`, in this
146
+ repository and in the npm package — so an agent finds rigc the way it finds its
147
+ other tools. Each skill is a router and nothing more: when to load it, the
148
+ non-negotiables in a line apiece, and a link to the guide that owns every rule, so
149
+ a rule keeps living in exactly one place. The repository is also a Claude Code
150
+ plugin marketplace:
151
+
152
+ ```shell
153
+ /plugin marketplace add firejune/rigc
154
+ /plugin install rigc@rigc
155
+ ```
156
+
157
+ With the package already installed, `claude --plugin-dir node_modules/spine-rigc`
158
+ loads the same skills without a marketplace. The plugin carries no version of its
159
+ own — `/plugin update` follows `main` commit by commit, and the only version on
160
+ disk stays the one in `package.json`.
121
161
 
122
162
  ## First rig in ten minutes
123
163
 
@@ -410,8 +450,9 @@ was verified, and what writing it cost. Repository material: a clone and
410
450
 
411
451
  <p align="center"><em>The portrait rig playing its three animations in one take — the turn is
412
452
  the shot: both silhouette edges move apart, which a flat slide cannot do, because every
413
- feature carries its own depth. Scene direction of this kind was Live2D's territory; the
414
- split was authoring cost, and the cost is now one stated expression per key. Compiled and
453
+ feature carries its own depth. Scene direction of this kind is authorable on plain Spine
454
+ 4.3 no plugin, no runtime patch and the split was authoring cost rather than runtime
455
+ capability; the cost is now one stated expression per key. Compiled and
415
456
  rendered entirely by the published package.</em></p>
416
457
 
417
458
  🎞️ **How the three films on this page were made** is kept with them, one directory per
@@ -428,7 +469,7 @@ commands take it and what its default is.
428
469
 
429
470
  | Command | Does |
430
471
  | --- | --- |
431
- | `build --rig … --motion … --out …` | compiles, gates, and **writes only if the gate is green**. `--images <dir>` says where the rig spec's `image` names resolve, `--manifest` adds measured art, and `--copy-images` copies every page PNG into `--out` so the directory is self-contained |
472
+ | `build --rig … --motion … --out …` | compiles, gates, and **writes only if the gate is green**. `--images <dir>` says where the rig spec's `image` names resolve, `--manifest` adds measured art, and `--copy-images` copies every page PNG into `--out` so the directory is self-contained, with `skeleton.images` pointing at it so the editor's import finds the parts |
432
473
  | `build … --pack` | the same build with every part arranged onto **shared** atlas pages, written into `--out` — losslessly, and gated a second time as the pair that ships. `--page-size` and `--padding` tune it |
433
474
  | `build … --atlas-in <file.atlas>` | the same build with every part resolved to a **region of an existing pack** instead of a loose PNG; a name the atlas lacks, a size the spec disagrees with or a rectangle off its page is refused by name |
434
475
  | `validate <dir>` | re-gates artifacts already on disk |
@@ -449,7 +490,7 @@ and `bun run fetch-examples`. The reasoning behind all three is in
449
490
 
450
491
  `build` and `validate` both default to `--profile spine` — the 25 validity rules, which
451
492
  ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
452
- adds all 39: the other 14 are one renderer's policy and one canvas budget's, and they
493
+ adds all 40: the other 15 are one renderer's policy and one canvas budget's, and they
453
494
  fire on perfectly correct editor-produced Spine data, so reach for that profile when
454
495
  you are shipping into *that* project rather than to be thorough. A report always names
455
496
  the profile it ran and lists what that profile left out.
@@ -471,7 +512,7 @@ the art. Its shape is under
471
512
  | 📥 **[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 |
472
513
  | 🤖 **[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 |
473
514
  | 🔬 **[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 |
474
- | 🎓 **[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 36 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 |
515
+ | 🎓 **[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 40 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 |
475
516
  | 📋 [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 |
476
517
 
477
518
  ## Why you can trust the output
@@ -481,11 +522,12 @@ rigc is measured against **Spine's own official example projects** — the
481
522
  as the graduation exam.
482
523
 
483
524
  🎓 **The ladder is complete, 2026-08-28.** All eight numbered rungs and the
484
- spineboy graduation exam are cleared and hold under the current gate, **v2.3**, every clause PASS or SKIP:
485
- worst attributable slot drift **5.55 px** against a 6.0 px bar, and **0 of 124**
486
- frame-change disagreements. Recompiling the same spec in a different session
487
- reproduced every field of the measurement record **to the digit**. The rungs stay
488
- in place as regression gates.
525
+ spineboy graduation exam are cleared and hold under the current gate, **v2.4**, every clause PASS or SKIP:
526
+ worst attributable slot drift **5.5550 px** against a 6.0 px bar a **1.0801×**
527
+ margin, the thinnest of the ladder's **G2** figures, and **G5**'s 1.0376× is thinner
528
+ still and **0 of 124** frame-change disagreements. Recompiling the same spec in a
529
+ different session reproduced every field of the measurement record **to the digit**.
530
+ The rungs stay in place as regression gates.
489
531
 
490
532
  🗓️ **One rung's pass was withdrawn and restored on 2026-09-02, and both are dated
491
533
  facts.** `check`'s extent tolerance ([PR #254](https://github.com/firejune/rigc/pull/254))
@@ -496,11 +538,15 @@ clause questions that exposed, as **v2.3**: a read-down names the framing of its
496
538
  evidence, and a slot whose attributability is **measured** to be capped below the bar
497
539
  reads down when everything observable about it is independently verified strict. That
498
540
  rung's third attempt clears on those grounds, on the candidate it already had.
499
- **Rungs 1–6 and 8 and the graduation exam were unaffected throughout**: each reproduces
500
- its gated figures to the digit, and the 5.55 px and 0-of-124 figures above are among
501
- them. Both verdicts, and the sweep of every candidate under the new gate, are in
541
+ **Rungs 1–6 and 8 and the graduation exam were unaffected throughout**: each keeps its
542
+ pass on the clause, and recompiling a stored candidate reproduces its record **to the
543
+ digit within one gate**. Across an instrument change the digits do move, and the
544
+ record says where — the graduation **G2** figure went 5.5491 → 5.5544 → **5.5550 px**
545
+ over the #301 sampler repair and v2.4's adoption of the re-rendered reference basis,
546
+ while **0 of 124** held throughout. Both verdicts, and the sweep of every candidate under the gate of the day, are in
502
547
  [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md)'s *PR #254 instrument re-inspection* and *gate-v2.3
503
- re-inspection*.
548
+ re-inspection*; the standing figures quoted above are from its *gate-v2.4
549
+ re-inspection*, which is the current sweep.
504
550
 
505
551
  ⚠️ **What that certifies, stated exactly.** That **the tool, the guide and the
506
552
  protocol reach the bar across a bounded series of honest attempts, each residual
@@ -509,6 +555,17 @@ predecessor's specs under the run protocol's inheritance clause. It is **not**
509
555
  that an agent authors a spineboy-scale rig from the brief alone in one run: the
510
556
  ladder has not demonstrated that, and each row records which of the two it is.
511
557
 
558
+ 🧪 **A separate series measures that harder question, and it has not been kind.**
559
+ From-zero attempts at spineboy — no inherited specs — have landed at **18.2, 18.8,
560
+ 19.57, 7.86, 9.33 and 18.98 px** worst drift against the 6.0 px bar, a spread with no
561
+ monotone trend, and the two most recent — both **2026-09-03**, after the ladder's
562
+ 2026-08-28 completion — are recorded 🔴 **FAIL**. They move no rung and reopen nothing —
563
+ a from-zero run is a tooling-progress measurement rather than a re-climb, which is
564
+ why the certification above is scoped to tool + guide + protocol. One of those
565
+ attempts states the residual in its own words: *"in motion it is not at editor
566
+ quality."* All six, with their verdicts, are in
567
+ [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
568
+
512
569
  The whole dossier — the yardstick, `diff` and `check` and what neither of them can
513
570
  see, every rung, the run viewer, the 40 assertions and the selftest behind them — is
514
571
  [docs/BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
package/cli.ts CHANGED
@@ -743,6 +743,10 @@ function cmdBuild(flags: Record<string, string>): void {
743
743
  'the loose part PNGs, which a packed atlas does not reference. Drop --copy-images',
744
744
  );
745
745
  }
746
+ // The copy itself happens after the gate (below). The header has to know NOW,
747
+ // because the skeleton text the gate reads is the skeleton text that is written
748
+ // — `skeleton.images` says where the parts will be (issue #370).
749
+ if (flags['copy-images'] !== undefined) opts.copyImages = true;
746
750
  // `--pack --profile spine-html` used to be the third refusal here, because
747
751
  // A06's coverage clause was "one part per page" flat and a legitimate pack
748
752
  // arrived at the gate reading as a defect. Since issue #266's second follow-up
@@ -2248,12 +2252,13 @@ function cmdExplain(flags: Record<string, string>): void {
2248
2252
  const FLAG_MEANINGS: Record<string, string> = {
2249
2253
  rig: 'the rig spec — skeleton structure',
2250
2254
  motion: 'the motion spec — time',
2251
- out: 'directory for skeleton.json + skeleton.atlas; atlas page paths are written relative to it',
2255
+ out: 'directory for skeleton.json + skeleton.atlas; atlas page paths and skeleton.images are written relative to it',
2252
2256
  images: "override the rig spec's own images directory (relative to your working directory)",
2253
2257
  manifest: 'a cut manifest, for a rig with measured art behind it; a foreign skeleton has none',
2254
2258
  'copy-images':
2255
2259
  'also copy every referenced page PNG into --out and rewrite the atlas to the copies, so the directory is ' +
2256
- 'self-contained enough to zip or commit on its own (default: page paths still point at the source art)',
2260
+ 'self-contained enough to zip or commit on its own, and point skeleton.images at --out itself so the editor finds ' +
2261
+ 'the parts beside the skeleton on import (default: page paths still point at the source art)',
2257
2262
  pack: 'arrange every part PNG onto shared atlas page(s) written into --out as real PNGs, instead of one page ' +
2258
2263
  'per part. Lossless: every region is a byte-for-byte copy and nothing is resampled, trimmed or rotated ' +
2259
2264
  '(default: one part, one page, pointing at the source art)',
package/docs/AUTHORING.md CHANGED
@@ -155,15 +155,15 @@ What the flags mean:
155
155
  | --- | --- |
156
156
  | `--rig` | the rig spec — skeleton structure |
157
157
  | `--motion` | the motion spec — time |
158
- | `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths are written relative to it |
159
- | `--copy-images` | `build` only: also copies every referenced page PNG into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own. Default is unchanged — page paths still point at the source art (issue #217) |
158
+ | `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths and `skeleton.images` are written relative to it |
159
+ | `--copy-images` | `build` only: also copies every referenced page PNG into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own, and points `skeleton.images` at `--out` itself so the editor's import finds the parts beside the skeleton (issue #370; §3.1 says why it is spelled `../<out>/` and not `./`). Default is unchanged — page paths still point at the source art (issue #217) |
160
160
  | `--pack` | `build` only: arrange every part onto **shared** atlas page(s), written into `--out` as real PNGs, instead of one page per part. Lossless — nothing is resampled, trimmed or rotated. Default is unchanged (issue #4) — **§0.1** |
161
161
  | `--page-size` | `build --pack` only: the largest page edge (default `2048`). A ceiling, not the size: page edges are powers of two and the one written is the smallest that holds the pack — **§0.1** |
162
162
  | `--padding` | `build --pack` only: the gutter each region reserves on every side (default `2`), filled by extending the region's own edge pixels outwards. `0` is not a legal-but-tight choice, it is bleed — **§0.1** |
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 22 validity rules (**the default**) · `spine-html` = all 36, opt-in |
166
+ | `--profile` | `spine` = the 25 validity rules (**the default**) · `spine-html` = all 40, 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` |
@@ -587,11 +587,15 @@ is recorded in `bench/runs/README.md`, *What a run may read*.)
587
587
  | `width`, `height` | setup-pose bounding box size | falls back to the manifest's crop; **with neither, the compile fails** |
588
588
  | `fps` | nonessential editor hint | `SkeletonData.fps` stays 30 |
589
589
  | `referenceScale` | 4.2+ physics/scale reference | parser default 100 |
590
- | `images` | nonessential path hint the editor writes | carried through verbatim |
590
+ | `images` | where the editor's import looks for the part PNGs, as a path from the skeleton file | **written for you**: under `--copy-images` the `--out` directory itself, spelled `../<its basename>/` (a literal `./` is dropped by the editor on import; a named directory is kept and every part is found — measured on 4.3.23); otherwise the relative path from `--out` to the one directory the spec names every part PNG in (the rig's images directory, or the manifest's plates). A declared value is carried through verbatim — and overridden by `--copy-images`, which moved the parts. Parts spread over several directories have no single true path, so nothing is written (issue #370) |
591
591
 
592
592
  `spine` and `hash` are not yours to write: rigc emits its own version label
593
593
  (`A16` re-checks it is on the 4.3 line) and inventing a hash would claim an export
594
- this file did not come from.
594
+ this file did not come from. The editor's import then warns `Data version 4.3.13
595
+ does not match Spine version 4.3.23. The Spine version should match the version
596
+ that exported the data file.` and imports anyway — the label is the runtime rigc
597
+ links, not the editor that will open the file, and the warning is harmless
598
+ (issue #370).
595
599
 
596
600
  `width`/`height` are what `A14` and `A19` measure against, so a guessed stage is a
597
601
  gate measuring against a number nobody wrote down.
@@ -663,8 +667,10 @@ the default `type`:
663
667
  | `scaleX`, `scaleY`, `color` | as Spine |
664
668
 
665
669
  **Mesh attachment** ([Spine: meshes](http://esotericsoftware.com/spine-meshes)) —
666
- either authored geometry (`uvs` + `triangles` + geometry, plus `hull`, `edges`,
667
- `width`, `height`) **or** a `generator`, never both.
670
+ either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
671
+ never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
672
+ omitted, rigc derives — `hull` and `edges` from the triangles, the size from the
673
+ PNG — and the rules are a few paragraphs down.
668
674
 
669
675
  Geometry comes in one of two fields:
670
676
 
@@ -705,6 +711,52 @@ above is load-bearing: if `vertices.length` equals `uvs.length` the parser reads
705
711
  unweighted x/y pairs, otherwise it reads the weighted run. A coincidental length
706
712
  match reads weight data as coordinates, silently — that is `A04`.
707
713
 
714
+ ⭐ **`hull` is derived from the triangles, and the vertex order has to let it be.**
715
+ Spine's `hull` is the number of vertices that make up the outline polygon, and the
716
+ format's one rule about it is that *the hull vertices are always first in the
717
+ vertices list* — first, and **in order**: the editor draws the outline by joining
718
+ hull vertex `i` to `i + 1` and constrains its triangulation to those segments, and
719
+ the binary format does not even store a triangle count — it reads
720
+ `2·vertices − hull − 2` triangles, Euler's count for a hole-free triangulation. The
721
+ triangles already fix the outline (an edge used by exactly one triangle is on it),
722
+ so an omitted `hull` is read off them and a stated one is checked against the same
723
+ derivation. What cannot be described that way is refused by name, and every one of
724
+ these messages prints the outline walk, because the walk is the fix:
725
+
726
+ | Refused | Message |
727
+ | --- | --- |
728
+ | a stated `hull` that disagrees | `hull 25 disagrees with the triangles, whose outline has 16 vertices (0 → 1 → 2 → …). Delete "hull" and rigc derives it, or state 16` |
729
+ | outline vertices that are not the first `hull` of the list | `hull vertices must come first; vertex 19 is on the boundary and vertex 6 is not. The triangles' outline runs 0 → 1 → 2 → 3 → 4 → 9 → 14 → 19 → 24 → 23 → 22 → 21 → 20 → 15 → 10 → 5: list those 16 vertices first, in that order, then the 9 interior vertices` |
730
+ | outline vertices that are first but zigzag | `hull vertices must trace the outline in order; the triangles' outline runs 0 → 1 → 3 → 5 → … → 4 → 2, so vertex 3 has to follow vertex 1 in the list, and vertex 2 does. Renumber the vertices along that walk` |
731
+ | triangles with no single outline | `the triangles do not tile the outline: 25 vertices with a 16-vertex outline tile as 32 triangles and there are 33` — an unused vertex or a doubled interior triangle; or `the triangles' outline is not one closed loop: vertex 4 has 4 boundary edges` — a pinch, a hole, or a doubled triangle on the perimeter |
732
+
733
+ 🚨 **A row-major grid is the case this catches**, and until
734
+ [#368](https://github.com/firejune/rigc/issues/368) it was `gallery/portrait`'s: a
735
+ 5×5 grid's perimeter is 16 of its 25 vertices, interleaved with the interior, so
736
+ no `hull` can describe that list — and `hull: 0`, which rigc used to write there,
737
+ is what the editor repairs on import by making **every** vertex a hull vertex in
738
+ list order and saying so in a WARNING. A two-column strip is the same trap with
739
+ every vertex on the outline: the list zigzags across, the outline runs down one
740
+ side and up the other, and the hull it would declare self-intersects. The fix is
741
+ the order the editor itself writes: the perimeter first, walked around, then the
742
+ interior — [FACE.md §4.3](FACE.md) has the recipe, and both gallery grids ship
743
+ that way. Nothing about a render depends on it; what depends on it is the mesh
744
+ as the editor shows it to the person refining the draft.
745
+
746
+ `edges` is **always written**, in the encoding the editor's own exports use:
747
+ vertex index pairs with each index **times two** — `[0, 2, 2, 4, …]`, the offset
748
+ into the flat `x, y` array, the same doubling the loader applies to `hull`. rigc
749
+ writes the outline loop first and then every interior triangle edge, so the
750
+ editor's constrained triangulation reproduces these exact triangles instead of
751
+ reporting *mesh internal edges lost*. A stated `edges` — a transcription of an
752
+ export carries the edges somebody drew — passes through verbatim.
753
+
754
+ `width`/`height` are the size of the image the mesh is drawn on. Stated wins;
755
+ omitted, they are the named PNG's measured size (R5), the same number a region
756
+ reads. With neither a size nor an `image`: `a mesh needs width and height — give
757
+ them, or give an "image" and rigc will measure the PNG`. They were written as 0
758
+ before #368 — a size no spec stated.
759
+
708
760
  ⚠️ **Authored geometry is not a rigc generator, and the gate says so.** rigc built
709
761
  neither its rim nor its rows, so it gets to assume nothing about its topology:
710
762
  `A21_MESH_RIM_PINNED` and `A28_RIBBON_ROWS_SHARE_WEIGHTS` **SKIP** on an authored
@@ -1398,6 +1450,15 @@ stepped.
1398
1450
  pages is that Bezier is the shape to adopt and linear the one you argue for. So
1399
1451
  leaving `ease` off is a positive claim of constant speed, not a way of declining to
1400
1452
  decide; §8 has what that bet cost on the ladder.
1453
+ - **A named easing on a hold is emitted `stepped`.** When every value channel of a
1454
+ key equals the next key's *as emitted* — a `translate` whose x moves and whose y
1455
+ does not is **not** a hold — the curve would run from a value to the same value and
1456
+ draw nothing, and the editor writes that segment as `"curve": "stepped"`. So rigc
1457
+ does too (§10.4, issue #369): the frames are byte-identical either way, and a build
1458
+ stops differing from its own editor export on `diff`'s `animations.curve_kinds`. A
1459
+ raw `curve` is **not** rewritten — it states the file's own numbers verbatim, and
1460
+ the editor's own exports do carry beziers over holds, so a transcription has to be
1461
+ able to write one back.
1401
1462
  - `curve` is the raw form: **four numbers per value channel**, concatenated in field
1402
1463
  order, as absolute `(time, value)` control points. A short array multiplies
1403
1464
  `undefined` into the cubic and yields `NaN` with no error, so rigc length- and
@@ -1979,7 +2040,8 @@ count.
1979
2040
 
1980
2041
  📘 **[FACE.md](FACE.md) is the recipe for that second case**, and it is where the
1981
2042
  grid questions this section leaves to its reader are answered: where to put the
1982
- columns, why `hull: 0` is the honest declaration on a grid, the closed form for
2043
+ columns, why the perimeter has to come first in the vertex list (`hull` is read
2044
+ off the triangles, §3.4), the closed form for
1983
2045
  the angle at which any column pair folds — and the fact that a folded key passes
1984
2046
  `A35` and every other assertion, so the arithmetic has to be checked before the
1985
2047
  build.
@@ -2314,6 +2376,11 @@ or the key's position in its own track. These are the frequent ones, verbatim:
2314
2376
  | `slot "X" names bone "Y", which this rig does not declare` | add the bone, or fix the slot's `bone` |
2315
2377
  | `no setup pose for slot "X": give the motion spec a \`setup\` entry or the rig slot an \`attachment\`` | R3 — pick one file and declare it there |
2316
2378
  | `a region needs width and height — give them, or give an "image" and rigc will measure the PNG` | add `image`, or both sizes |
2379
+ | `a mesh needs width and height — give them, or give an "image" and rigc will measure the PNG` | §3.4 — the same rule for a mesh |
2380
+ | `hull N disagrees with the triangles, whose outline has K vertices (0 → …)` | §3.4 — delete `hull`, or state K |
2381
+ | `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 |
2382
+ | `hull vertices must trace the outline in order; the triangles' outline runs …, so vertex a has to follow vertex b in the list, and vertex c does` | §3.4 — renumber along the printed walk |
2383
+ | `the triangles do not tile the outline: …` / `the triangles' outline is not one closed loop: …` | §3.4 — a doubled triangle, an unused vertex, a pinch or a hole in `triangles` |
2317
2384
  | `image "X.png" is not on disk at …` | fix the name, or point `--images` at the right directory |
2318
2385
  | `duplicate region name "X"` | two PNGs share a basename; one part, one page, one name |
2319
2386
  | `motion spec names archetype "A" but the rig spec at … is called "B"` | make `archetype` equal the rig's `name` |
@@ -3855,6 +3922,9 @@ frames. Every line is marked with where it comes from:
3855
3922
 
3856
3923
  - 📗 **stated** — quoted or paraphrased from the page linked in the line.
3857
3924
  - 🧩 **inferred** — this guide's reading of those pages. Spine does not say it.
3925
+ - 🔬 **observed** — read off the editor's own export of a rigc build in the round
3926
+ trip of [issue #285](https://github.com/firejune/rigc/issues/285) (Spine 4.3.23),
3927
+ not from a page. Used only where rigc now emits the same thing.
3858
3928
 
3859
3929
  ### 10.1 Structure
3860
3930
 
@@ -4338,6 +4408,18 @@ of key does not have a transition, such as slot attachment or event keys"* —
4338
4408
  [Dopesheet](http://esotericsoftware.com/spine-dopesheet). This is why rigc refuses
4339
4409
  `ease` and `curve` on attachment keys (§4.4) and on draw-order keys (§4.7).
4340
4410
 
4411
+ 🔬 **A Bezier on a hold is written `stepped`.** Import a build, export it again, and
4412
+ every key that carried a curve into a segment whose next key holds the same value on
4413
+ every channel comes back as `"curve": "stepped"` — 14 keys on the gallery's `nod`
4414
+ alone, with every rendered frame byte-identical, because a curve over a flat segment
4415
+ draws nothing. rigc emits the same for a **named** easing (§4.5, issue #369), so
4416
+ `diff`'s `animations.curve_kinds` reads 1.000 between a build and its own export
4417
+ rather than charging the hold rewrites against the timing. A raw `curve` stays as
4418
+ written: it is the file's own numbers, and the reference corpus has the editor itself
4419
+ shipping beziers over holds in 18 keys, so the format legitimately holds both. ⚠️ The
4420
+ editor also turned some *linear* holds into `stepped` on one example and none on two
4421
+ others; a rule that cannot be stated is not adopted, so those still show in `diff`.
4422
+
4341
4423
  ### 10.5 What the export leaves out
4342
4424
 
4343
4425
  📗 **Nonessential data is off unless someone checked the box.** *"Data marked
@@ -4348,8 +4430,11 @@ of key does not have a transition, such as slot attachment or event keys"* —
4348
4430
  nonessential: the skeleton's `fps`, `images` and `audio`; a mesh's and a linked
4349
4431
  mesh's `width` and `height`; a mesh's `edges`; and the editor colours of bounding
4350
4432
  box, path, point and clipping attachments. ⇒ a mesh in an export made without that
4351
- box carries **no** `width`/`height`. rigc's `image` supplies them from the PNG (R5),
4352
- so you never write them by hand.
4433
+ box carries **no** `width`/`height` and no `edges`. rigc's `image` supplies the
4434
+ sizes from the PNG (R5) and the triangles supply `edges` and `hull` (§3.4), so you
4435
+ never write any of them by hand — and rigc's own output always carries all
4436
+ three, because the editor's *import* treats their absence as an export made
4437
+ without the box and rebuilds the hull on its own.
4353
4438
 
4354
4439
  ⚠️ **A region's `width`/`height` are not on that list.** They are documented with no
4355
4440
  *"assume … if omitted"* default — the same fact R5 states from the parser's side:
package/docs/FACE.md CHANGED
@@ -486,15 +486,29 @@ brackets the fold this section predicts against `A39` itself: **33° gates green
486
486
  and 34° does not**, with the refusal naming the row pair the closed form names.
487
487
  That is this table checked from the other end, on the other axis.
488
488
 
489
- ### 4.3 `hull` is `0`, and the vertex order is why
490
-
491
- ⚠️ **A grid's perimeter is 16 of its 25 vertices and they are not a prefix of any
492
- row-major order.** Declaring a hull would mean re-ordering the vertex list
493
- perimeter-firstand then **a reader could not count the emitted deform run
494
- against the grid**, which is what makes `explain`'s printed offsets (§1.1)
495
- checkable against the columns at all. `0` is the honest declaration. AUTHORING §3.4 has what a hull
496
- buys and the worked example's FINDINGS.md prices what omitting it costs in an
497
- editor round trip.
489
+ ### 4.3 The perimeter comes first, and `hull` is read off the triangles
490
+
491
+ ⚠️ **A grid's perimeter is 16 of its 25 vertices, and in row-major order they are
492
+ interleaved with the interior.** Spine's `hull` is the first `hull` vertices of
493
+ the list, in order the editor draws the outline by joining them in sequence — so
494
+ a row-major grid cannot declare one. And an undeclared hull is not neutral: rigc
495
+ used to write `hull: 0` there, and the editor's import repairs that by making
496
+ **every** vertex a hull vertex in list order, which is a self-intersecting outline
497
+ on the mesh the person refining the draft sees
498
+ ([#368](https://github.com/firejune/rigc/issues/368) records the round trip that
499
+ found it). So the list is written the way the editor writes one: **the perimeter
500
+ first, walked around — top row, right column, bottom row, left column — then the
501
+ interior, row-major.** rigc derives `hull` from the triangles (AUTHORING §3.4) and
502
+ refuses any other order with the walk to renumber along, so getting this wrong
503
+ costs one loop rather than a silent file.
504
+
505
+ **What it costs the reader.** `explain` prints one offset per vertex in list
506
+ order, so the run no longer reads as one row of five values repeated down the
507
+ grid: entries 0–15 walk the perimeter and 16–24 are the interior. The column
508
+ table is still what to check it against — a vertex's offset depends on its column
509
+ alone — and the right column's five entries (4–8) carrying one value is the
510
+ quickest check that the walk and the table agree. `gallery/portrait`'s README
511
+ shows the listing beside the order.
498
512
 
499
513
  ⚠️ **A large `MESH` overshoot on a face is not a defect.** A rectangular grid
500
514
  over an oval face has transparent corners, and the line says so:
package/docs/INGEST.md CHANGED
@@ -142,7 +142,7 @@ rigc validate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
142
142
  ```
143
143
  rigc validate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
144
144
  .. atlas …/examples/3-timing-and-spacing/export/3-timing-and-spacing.atlas
145
- .. profile spine — 7 renderer-policy and 7 archetype assertion(s) do not apply
145
+ .. profile spine — 7 renderer-policy and 8 archetype assertion(s) do not apply
146
146
  PASS A07_ATLAS_TEXT_SHAPE
147
147
  PASS A00_ROUNDTRIP_PARSE
148
148
  PASS A16_SKELETON_VERSION_4_3
@@ -645,19 +645,19 @@ has three possible meanings and the message alone does not separate them:
645
645
  runtime is content and the burden is on the rule. Reporting that is a better answer
646
646
  than a quietly edited export.
647
647
 
648
- ### 3.3 Profile choice, and the fourteen rules that will not fire
648
+ ### 3.3 Profile choice, and the fifteen rules that will not fire
649
649
 
650
650
  `--profile spine` is the default and answers *"is this valid Spine 4.3 that any
651
651
  runtime plays correctly?"* — and it is the right profile for foreign data, because the
652
652
  other one is this project's own renderer and archetype policy.
653
653
 
654
- **Fourteen assertions do not run under `spine`, and they come back `PROF`, not
654
+ **Fifteen assertions do not run under `spine`, and they come back `PROF`, not
655
655
  `SKIP`:**
656
656
 
657
657
  | Excluded as | Rules |
658
658
  | --- | --- |
659
659
  | **renderer policy** (7) | `A11_NO_CLIPPING_ATTACHMENTS`, `A12_NO_DARK_COLOR`, `A13_MESH_BUDGET`, `A14_NO_FULL_FRAME_MESH`, `A15_IDLE_NO_MESH_BONE_KEYS`, `A19_OVERLAY_PNGS_HAVE_ALPHA`, `A27_REGION_NAME_MATCHES_PAGE_FILENAME` |
660
- | **archetype policy** (7) | `A21_MESH_RIM_PINNED`, `A24_AXIS_SPACE_STROKE`, `A25_DETACHED_BONE_PARENTAGE`, `A26_SLOT_DRAW_ORDER`, `A28_RIBBON_ROWS_SHARE_WEIGHTS`, `A29_STROKE_WITHIN_CONTACT_DEPTH`, `A30_STROKE_WITHIN_CAP_CONTAINMENT` |
660
+ | **archetype policy** (8) | `A21_MESH_RIM_PINNED`, `A24_AXIS_SPACE_STROKE`, `A25_DETACHED_BONE_PARENTAGE`, `A26_SLOT_DRAW_ORDER`, `A28_RIBBON_ROWS_SHARE_WEIGHTS`, `A29_STROKE_WITHIN_CONTACT_DEPTH`, `A30_STROKE_WITHIN_CAP_CONTAINMENT`, `A39_DEFORM_KEEPS_TRIANGLE_WINDING` |
661
661
 
662
662
  Three further rules — **`A06`**, **`A08`** and **`A20`** — are *mixed*: their validity
663
663
  clauses run in both profiles and their policy clauses only under `spine-html`. `A06`'s
package/docs/PROMPTING.md CHANGED
@@ -5,8 +5,11 @@ author a rig with rigc. It is not authoring guidance; that is
5
5
  [AUTHORING.md](AUTHORING.md), and the agent should read that file, not this one.
6
6
  This page tells you what to put in the prompt, and it earns its claims from a
7
7
  measured case: in August 2026 a deliberately small model — Gemini 3.7 Flash on
8
- the Antigravity harness — authored a benchmark rung unattended for three hours,
9
- and every stumble below is one it actually made, priced by the instruments in
8
+ the Antigravity harness — authored a benchmark rung over a **3 h 01 m** session,
9
+ observed by the operator; the harness kept no tool transcript, so the run is
10
+ recorded **Unverified** on honesty, with two of its four tiers *unevidenced*
11
+ ([the verdict](https://github.com/firejune/rigc/blob/main/docs/pilot/verdicts/2026-08-24-rung3-1.md)
12
+ states all of that). Every stumble below is one it actually made, priced by the instruments in
10
13
  [PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md)'s protocol. A large model makes fewer of these mistakes
11
14
  unprompted; the clauses cost you three lines and make the small model's run
12
15
  land and the large model's run land cleaner.