spine-rigc 0.22.2 → 0.24.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 +80 -1
- package/cli.ts +263 -13
- package/docs/AUTHORING.md +554 -75
- package/docs/INGEST.md +238 -44
- package/docs/SPEC_COVERAGE.md +14 -3
- package/package.json +1 -1
- package/skills/ingest/SKILL.md +33 -11
- package/src/atlas.ts +135 -16
- package/src/check.ts +83 -1
- package/src/compile.ts +402 -74
- package/src/deformmeasure.ts +322 -151
- package/src/diff.ts +125 -2
- package/src/ingest.ts +1137 -0
- package/src/render.ts +113 -17
- package/src/rig.ts +92 -8
- package/src/timelines.ts +163 -0
- package/src/types.ts +74 -9
- package/src/validate.ts +765 -92
- package/tools/editor_roundtrip.ts +247 -36
package/docs/INGEST.md
CHANGED
|
@@ -50,12 +50,22 @@ invent one.
|
|
|
50
50
|
Two facts decide everything below, and they pull in opposite directions:
|
|
51
51
|
|
|
52
52
|
1. **rigc reads compiled skeleton JSON in more places than you would guess.**
|
|
53
|
-
`validate`, `render`, `preview`, `vote`, `check
|
|
54
|
-
`skeleton.json` path directly, and none of them needs a rig
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
skeleton is `build`, and `build`'s
|
|
58
|
-
|
|
53
|
+
`validate`, `render`, `preview`, `vote`, `check`, `diff` and — since #569 —
|
|
54
|
+
`ingest` all take a `skeleton.json` path directly, and none of them needs a rig
|
|
55
|
+
spec to do it.
|
|
56
|
+
2. **rigc still cannot EDIT one.** There is no command that opens a skeleton and
|
|
57
|
+
changes it. The only thing that produces a skeleton is `build`, and `build`'s
|
|
58
|
+
input is a rig spec plus a motion spec.
|
|
59
|
+
⇒ **Every route that ends in a changed file goes through the specs** — and
|
|
60
|
+
since #569 there are two ways to get them: write them (§2, transcription) or
|
|
61
|
+
have `ingest` write them for you from the file itself (§2.0).
|
|
62
|
+
|
|
63
|
+
⚠️ This clause read *"rigc cannot write one back… no route from skeleton JSON
|
|
64
|
+
to specs"* until 2026-09-17, and the half that was wrong is the second half.
|
|
65
|
+
The route exists now and its contract is an equality — `build(ingest(x))` is
|
|
66
|
+
`x`, byte for byte — which is a stronger statement than anything transcription
|
|
67
|
+
could make. What survives is the first half: nothing **edits** a skeleton, and
|
|
68
|
+
the specs remain the only thing a change is expressed in.
|
|
59
69
|
|
|
60
70
|
### 0.1 The table
|
|
61
71
|
|
|
@@ -71,6 +81,7 @@ an upstream `license.txt` (Appendix, and [NOTICE.md](../NOTICE.md)).
|
|
|
71
81
|
| **`vote --candidate <a> --candidate <b>`** | ✅ **yes, on either side** | a ballot page. Pairing a foreign export against your own transcription is a legitimate ballot, and the panes carry no paths |
|
|
72
82
|
| **`check --candidate <skeleton.json> --frames <dir>`** | ✅ **yes** | ⭐ it reads **frames and never a reference skeleton**, so a foreign export enters this one *twice over*: as the candidate, or — via `render` — as the source of the frames. §1.4 |
|
|
73
83
|
| **`diff <candidate.json> <reference.json>`** | ✅ **yes, both sides** | 49 structural measures over bones, slots, attachments, constraints, animations and events. ⛔ **Blind to every coordinate** — §1.3 |
|
|
84
|
+
| **`ingest <skeleton.json> --out <dir>`** | ✅ **yes — and it is the only reader that WRITES specs** | the `.json` alone; no atlas, no art, no project file. Out come `rig.json`, `motion.json` and a findings report, such that `build`ing them reproduces the skeleton it read **byte for byte**. The seventh reader, and the one that ends §2's hand work — §2.0 and §5 |
|
|
74
85
|
| **`pose --images <dir> --frame <png>`** | ⛔ **not the skeleton** | loose part PNGs and one picture. A packed atlas page is not loose parts, and pointing it at one produces a confident answer about nothing — §5 |
|
|
75
86
|
| **`explain --rig … --motion … --out …`** | ⛔ **no** | rig spec + motion spec. It explains **what you wrote**, which makes it a transcription instrument rather than a reading one — §1.5 |
|
|
76
87
|
| **`build --rig … --motion … --images …`** | ⛔ **no** | specs in, skeleton out. The only writer in the toolchain, and the reason §2 exists |
|
|
@@ -151,7 +162,11 @@ rigc validate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.
|
|
|
151
162
|
SKIP A32_EVENT_KEYS_RESOLVE: no animation carries an event timeline
|
|
152
163
|
SKIP A34_CONSTRAINT_TIMELINE_TARGETS: no animation carries a constraint timeline
|
|
153
164
|
SKIP A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: no animation carries a deform timeline
|
|
165
|
+
SKIP A04_MESH_TRIANGLES_AND_ENCODING: the skeleton carries no mesh attachment
|
|
154
166
|
SKIP A33_VERTEX_ATTACHMENT_GEOMETRY: the skeleton carries no bounding box, clipping attachment or path
|
|
167
|
+
SKIP A20_MESH_WEIGHTS_COHERENT: the skeleton carries no mesh attachment
|
|
168
|
+
SKIP A22_MESH_UVS_IN_UNIT_RANGE: the skeleton carries no mesh attachment
|
|
169
|
+
SKIP A23_PHYSICS_CONSTRAINT_EFFECTIVE: the skeleton declares no physics constraint
|
|
155
170
|
…
|
|
156
171
|
PROF A12_NO_DARK_COLOR: renderer rule, not in profile "spine"
|
|
157
172
|
PROF A21_MESH_RIM_PINNED: archetype rule, not in profile "spine"
|
|
@@ -164,10 +179,17 @@ Read it as three separate statements, because they answer three different questi
|
|
|
164
179
|
- **`PASS` / `FAIL`** — the rule ran, and this is its verdict.
|
|
165
180
|
- **`SKIP`** — the rule ran and had nothing to measure, and the line says what was
|
|
166
181
|
absent. A `SKIP` is never a pass, and on foreign data the `SKIP` list is also a
|
|
167
|
-
**free inventory of what the skeleton does not contain**. The
|
|
182
|
+
**free inventory of what the skeleton does not contain**. The nine lines above tell
|
|
168
183
|
you, without your having opened the JSON, that this export has no draw-order
|
|
169
|
-
timeline, no event timeline, no constraint timeline, no deform timeline,
|
|
170
|
-
bounding box, clipping attachment or
|
|
184
|
+
timeline, no event timeline, no constraint timeline, no deform timeline, no mesh
|
|
185
|
+
attachment, no physics constraint, and no bounding box, clipping attachment or
|
|
186
|
+
path. ⭐ Four of those lines used to read `PASS`
|
|
187
|
+
([#580](https://github.com/firejune/rigc/issues/580)): a rule that walks the
|
|
188
|
+
meshes, or the physics constraints, and finds none has measured nothing, and
|
|
189
|
+
reporting that as held both overstated the gate and cost you the inventory line.
|
|
190
|
+
What still passes over an empty list is the other kind of rule — `A01`, `A02`,
|
|
191
|
+
`A11`, `A12`, `A14` ask *how many of this does the file carry*, and **zero is the
|
|
192
|
+
answer**.
|
|
171
193
|
- **`PROF`** — the rule was excluded by the profile before its body ran. §3.3.
|
|
172
194
|
|
|
173
195
|
⚠️ **A green here is a statement about validity and nothing else.** It does not say
|
|
@@ -212,9 +234,11 @@ Spine runtime plays it, whatever rigc's own rasteriser or validator thinks.
|
|
|
212
234
|
|
|
213
235
|
### 1.3 `diff` — and the two things it cannot see
|
|
214
236
|
|
|
215
|
-
`diff` takes two compiled skeletons and reports 49 measures in eight groups
|
|
216
|
-
|
|
217
|
-
|
|
237
|
+
`diff` takes two compiled skeletons and reports 49 measures in eight groups, plus two
|
|
238
|
+
blocks that report and gate nothing: the `(reported)` measures beside `attachments`
|
|
239
|
+
and `animations`, and the `skeleton` header block at the top, which measures the stage
|
|
240
|
+
(issue #578). Both sides may be foreign; the interesting pairing during ingest is
|
|
241
|
+
**your transcription against the export it came from**:
|
|
218
242
|
|
|
219
243
|
```bash
|
|
220
244
|
rigc diff work/t3/skeleton.json \
|
|
@@ -227,6 +251,10 @@ rigc diff
|
|
|
227
251
|
reference …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
|
|
228
252
|
.. bones=3/3 slots=2/2 skins=1/1 attachments=2/2 constraints=0/0 animations=2/2 events=0/0 (candidate/reference)
|
|
229
253
|
|
|
254
|
+
skeleton (reported) (no mean) over 2 measures — the stage, which no reading of the frames could decide
|
|
255
|
+
1.000 stage_present 1/1 both sides declare a setup-pose stage, or neither does — …
|
|
256
|
+
1.000 stage_box 4/4 the stage is the same box (x, y, width, height, exactly as stated) — …
|
|
257
|
+
|
|
230
258
|
bones mean 1.000 over 8 measures
|
|
231
259
|
1.000 count 3/3 how many bones
|
|
232
260
|
1.000 names 3/3 the bone names themselves
|
|
@@ -253,11 +281,18 @@ Each pair is **matched / total**, where the total is the larger of the two sides
|
|
|
253
281
|
count of `2/3` means one side has three of something and only two were matched — and
|
|
254
282
|
it does not say *which* side has three. The `..` line above is where you read that.
|
|
255
283
|
|
|
256
|
-
⛔ **`diff` is blind to every coordinate
|
|
257
|
-
`x`/`y`/`rotation`, an attachment's offset, or a key's value —
|
|
258
|
-
*names*, *counts*, *order* and *kinds*. §4.1 moves a pivot 236.5
|
|
259
|
-
of the 49 measures still reads **1.000**. ⇒ Never take a green
|
|
260
|
-
a geometric edit did not land, and never take it as evidence
|
|
284
|
+
⛔ **`diff` is blind to every coordinate a bone, an attachment or a key carries.** No
|
|
285
|
+
measure reads a bone's `x`/`y`/`rotation`, an attachment's offset, or a key's value —
|
|
286
|
+
only *presence*, *names*, *counts*, *order* and *kinds*. §4.1 moves a pivot 236.5
|
|
287
|
+
units and every one of the 49 measures still reads **1.000**. ⇒ Never take a green
|
|
288
|
+
`diff` as evidence that a geometric edit did not land, and never take it as evidence
|
|
289
|
+
that one did.
|
|
290
|
+
|
|
291
|
+
⚠️ **The one exception is the skeleton's own declared box**, and it is an exception to
|
|
292
|
+
the sentence and not to the rule: `skeleton.stage_box` compares four world numbers,
|
|
293
|
+
but they are numbers an exporter *wrote into the header* rather than a pose anything
|
|
294
|
+
measured, and the block they sit in gates nothing. Moving a pivot does not move them
|
|
295
|
+
either.
|
|
261
296
|
|
|
262
297
|
⛔ **And its ratios are not a score.** [`src/diff.ts`](../src/diff.ts) says so in the
|
|
263
298
|
type itself (*"Unweighted mean of the measures below. NOT a quality score"*), and the
|
|
@@ -377,17 +412,90 @@ is fine — it is not created.
|
|
|
377
412
|
|
|
378
413
|
---
|
|
379
414
|
|
|
380
|
-
## 2.
|
|
415
|
+
## 2. Getting specs out of a skeleton
|
|
381
416
|
|
|
382
|
-
Everything in §1 reads. To **change** anything you need specs
|
|
383
|
-
|
|
384
|
-
hand, into a rig spec and a motion spec, and `build` emits a new skeleton from those.
|
|
417
|
+
Everything in §1 reads. To **change** anything you need specs. There are two routes
|
|
418
|
+
to them and you should almost always take the first.
|
|
385
419
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
420
|
+
### 2.0 `ingest` — let the tool write them
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
rigc ingest examples/spineboy/export/spineboy-ess.json --out specs/ --art none
|
|
424
|
+
rigc build --rig specs/rig.json --motion specs/motion.json --atlas-in examples/spineboy/export/spineboy.atlas --out spine
|
|
425
|
+
rigc diff spine/skeleton.json examples/spineboy/export/spineboy-ess.json
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`ingest` reads the skeleton — **only** the skeleton — and writes `rig.json`,
|
|
429
|
+
`motion.json` and `findings.json`. The contract is an equality rather than a
|
|
430
|
+
rulebook: `build(ingest(x))` is `x`, byte for byte on `skeleton.json`, and the
|
|
431
|
+
atlas comes back with the same region blocks (as a multiset — the page order is in
|
|
432
|
+
no field of the file). `bun run selftest` holds every rig this repository builds to
|
|
433
|
+
that on every run, which is the one gate here that compares an emitted file against
|
|
434
|
+
a file rigc did not write.
|
|
435
|
+
|
|
436
|
+
📊 **And it holds the twelve editor exports to the weaker claim that is available for
|
|
437
|
+
them** ([#594](https://github.com/firejune/rigc/issues/594)). Every
|
|
438
|
+
`examples/*/export/*.json` is ingested with `--art none`, rebuilt through the pack
|
|
439
|
+
beside it, and `diff`ed against the file it was read from: **12 of 12 come back with 0
|
|
440
|
+
blockers and 1.000 on all 49 ratio-bearing measures and all 5 reported ones.** Byte
|
|
441
|
+
identity is not the claim there and the reason is the input, not the round trip — §2.3
|
|
442
|
+
has the three kinds of difference, measured. ⚠️ Which pack is "the one beside it" is
|
|
443
|
+
resolved rather than guessed, for §0.2's reason: `spineboy/export` holds two, and
|
|
444
|
+
`spineboy-run.atlas` covers neither skeleton in it.
|
|
445
|
+
|
|
446
|
+
**What it will not do is invent.** Everything the spec format cannot hold is a
|
|
447
|
+
finding with a code — `BLOCK` for a construct the rebuild will be missing, `JUDGE`
|
|
448
|
+
for the two values a skeleton does not carry, `LOSS` for the one number rigc
|
|
449
|
+
re-derives on purpose. A blocker exits non-zero and still writes both files.
|
|
450
|
+
|
|
451
|
+
**Two values are not in a skeleton**, so `ingest` asks rather than guesses:
|
|
452
|
+
|
|
453
|
+
- **the stage** (`skeleton.width`/`height`) — `--stage x,y,w,h` is how you supply one
|
|
454
|
+
when the file has none. ⚠️ **This page said an editor export carries none until
|
|
455
|
+
[#594](https://github.com/firejune/rigc/issues/594) measured it: all twelve exports in
|
|
456
|
+
the fetched corpus carry a stage**, `ingest` reads it straight through, and not one of
|
|
457
|
+
them needed the flag. What holds without qualification is that the box cannot be
|
|
458
|
+
*derived* — posing the rig gives the *animated* extent, which is a different number
|
|
459
|
+
from the setup box — so a skeleton that really declares none is a `NO_STAGE` blocker
|
|
460
|
+
rather than a guess. It is also the value that costs least to get wrong: `diff`
|
|
461
|
+
reports it as two measures of its own (`stage_present`, `stage_box`, since
|
|
462
|
+
[#578](https://github.com/firejune/rigc/issues/578)) and they are `(reported)`, so
|
|
463
|
+
nothing on the ladder reads them and an absurd box is green nearly everywhere. The
|
|
464
|
+
corpus half of the selftest's `IG` suite is the one gate that does read them;
|
|
465
|
+
- **each animation's duration** — the format has no such field. The largest key time
|
|
466
|
+
is used, stated in the motion spec's `note`, and recorded as a finding per
|
|
467
|
+
animation. Edit it if you know the real number.
|
|
468
|
+
|
|
469
|
+
And two flags for what the skeleton also does not encode: `--art loose` (the default)
|
|
470
|
+
names an `image` per attachment resolved against loose PNGs, `--art none` states
|
|
471
|
+
`width`/`height` for `build --atlas-in`; and under `loose`, `--images <dir>` writes
|
|
472
|
+
the rig spec's own images directory relative to `--out`, so the rebuild is a plain
|
|
473
|
+
`build --rig … --motion … --out …` rather than one carrying `--images` forever. It is
|
|
474
|
+
refused together with `--art none`, which writes no `image` for a directory to be the
|
|
475
|
+
base of. [AUTHORING §0.3](AUTHORING.md) is the loop in full.
|
|
476
|
+
|
|
477
|
+
📝 Both written specs carry a `note` saying they are decompiled and naming the file
|
|
478
|
+
they came from. Leave it there — §2.4 is why.
|
|
479
|
+
|
|
480
|
+
### Transcription — the route that made a foreign skeleton yours
|
|
481
|
+
|
|
482
|
+
⚠️ **The rest of §2 is the route that existed before #569, and it is kept because
|
|
483
|
+
the reading it produces is still the right one** — it is what an author does *after*
|
|
484
|
+
`ingest`, and it is what to fall back on for the constructs `ingest` reports as
|
|
485
|
+
blockers. The numbers come out of the JSON into a rig spec and a motion spec by hand,
|
|
486
|
+
and `build` emits a new skeleton from those.
|
|
487
|
+
|
|
488
|
+
What you get for it is that the file becomes editable by declaration — a pivot move
|
|
489
|
+
is two numbers in a spec (§4.1) and a new animation is an added block (§4.3), where
|
|
490
|
+
before it was a hand-edit of emitted JSON with nothing checking it. That is now what
|
|
491
|
+
`ingest` hands you in one command; the sections below are how to read and change what
|
|
492
|
+
it hands you, and every rule in them applies to a spec `ingest` wrote.
|
|
493
|
+
|
|
494
|
+
📌 **The cost this section used to warn about is measured, and it is why §5 changed.**
|
|
495
|
+
The smallest skeleton of the corpus behind [#569](https://github.com/firejune/rigc/issues/569)
|
|
496
|
+
transcribed to a **257,422-byte** rig spec, of which 91.8 % is the six geometry
|
|
497
|
+
arrays — numbers, not decisions. A 558-line prototype decompiler reproduced 100 % of
|
|
498
|
+
it, and the only differing paths were the name and the `note`.
|
|
391
499
|
|
|
392
500
|
### 2.1 The workflow
|
|
393
501
|
|
|
@@ -409,6 +517,32 @@ before it was a hand-edit of emitted JSON with nothing checking it.
|
|
|
409
517
|
copying and not a detail. Leave `invariants` out entirely — it describes rigc's own
|
|
410
518
|
formations, and an absent field makes an archetype assertion `SKIP`, never pass
|
|
411
519
|
(AUTHORING §3.7).
|
|
520
|
+
|
|
521
|
+
📌 **Transcribe the export's empty slots too** — the ones no skin fills anywhere.
|
|
522
|
+
Such a slot still holds an index in the array, and everything below it is counted
|
|
523
|
+
from that index. Write it as `{ "name": …, "bone": … }` with no `attachment`, or
|
|
524
|
+
with `"attachment": null` if you prefer to say it out loud; either way it comes
|
|
525
|
+
back. Before issue #575 it did not: `build` dropped it in silence, so two exports
|
|
526
|
+
declaring 53 and 61 slots came back at 51 and 57 with a green gate, and `diff`
|
|
527
|
+
read 0.962 and 0.934 against the file they had been read from. If a
|
|
528
|
+
transcription's `slots.count` is under 1.000, this is the first thing to check.
|
|
529
|
+
|
|
530
|
+
⚠️ **No skeleton in `examples/` has one**, which is why the corpus never showed
|
|
531
|
+
this: all twelve exports fill every slot they declare from some skin. What they
|
|
532
|
+
*do* carry is the neighbouring shape — a slot a skin DOES fill whose setup pose
|
|
533
|
+
shows nothing (34 of `spineboy-pro`'s 52 slots). Both are written the same way in
|
|
534
|
+
the file: `attachment` simply absent.
|
|
535
|
+
|
|
536
|
+
⚠️ **If the export's `skeleton` block carries no `x`/`y`/`width`/`height`, write
|
|
537
|
+
`"width": null, "height": null` and do not invent one** (issue #578). That shape is
|
|
538
|
+
common — the twelve exports in `examples/` all carry the four, and 37 of 37 exports
|
|
539
|
+
in one production corpus carry none of them — and until the `null` pair existed the
|
|
540
|
+
only two moves were a made-up stage or a file that could not be transcribed. The
|
|
541
|
+
made-up stage was the worse one: it is a number nothing in this toolchain could
|
|
542
|
+
contradict, so it survived every gate and every `diff` in silence. Now it does not —
|
|
543
|
+
`diff`'s header block reports `skeleton.stage_present` and `skeleton.stage_box`
|
|
544
|
+
against the source you are copying. Copy the four numbers when they are there;
|
|
545
|
+
state the absence when they are not.
|
|
412
546
|
4. **`explain`, then `build`.** `explain` first, because it prints what you wrote in a
|
|
413
547
|
shape you can compare against the export by eye (§1.5) and it never gates. Then
|
|
414
548
|
`build` under `--profile spine`.
|
|
@@ -455,6 +589,25 @@ State the ambition in the right units, because three different things get called
|
|
|
455
589
|
| **Geometric agreement** — the same drawn pixels, allowing for the atlas | ✅ yes, and `check` measures it | see below |
|
|
456
590
|
| **Byte-identical JSON** | ⛔ **no, and not because of the geometry** | rigc writes defaults explicitly where the editor omits them, and the editor writes bookkeeping rigc has no field for. SPEC_COVERAGE records the count on rung 6: a field-by-field comparison against the reference export leaves **49 differences, every one benign** — 39 explicit defaults, 3 editor bookkeeping keys, 1 runtime version string, and 6 bone `icon` values, which was the only thing the rig spec could not say at all |
|
|
457
591
|
|
|
592
|
+
⚠️ **The third row holds for `ingest` too, and it is worth knowing in which direction.**
|
|
593
|
+
`build(ingest(x))` is byte-identical for a skeleton **rigc** emitted — that is the
|
|
594
|
+
contract `bun run selftest` gates on every run — and it is not, for a skeleton the
|
|
595
|
+
editor emitted. Measured over all twelve corpus exports, a field-by-field walk of the
|
|
596
|
+
rebuild against its source produces differences of exactly three kinds, in every file:
|
|
597
|
+
|
|
598
|
+
| Kind | Example, candidate vs reference | Why |
|
|
599
|
+
| --- | --- | --- |
|
|
600
|
+
| **header bookkeeping**, 3 per file | `skeleton.hash: undefined vs "VFWbaK2UoCM"`, `skeleton.audio: undefined vs null`, `skeleton.spine: "4.3.13" vs "4.3.75-beta"` | the rig spec has no field for `hash` or `audio`, and the version is the runtime rigc links. `ingest` reports all three as findings — `HEADER_BOOKKEEPING` and `HEADER_REDERIVED` |
|
|
601
|
+
| **an omitted default written out** | `…rotate[0].time: 0 vs undefined` | the editor omits a zero `time`; rigc writes it. AUTHORING §10.5's *do not imitate the exporter's omissions*, from the other side |
|
|
602
|
+
| **the emitted precision** | `…curve[0]: 0.066667 vs 0.06666667`, `uvs[0]: 0 vs 2.554152e-7` | rigc emits six decimals |
|
|
603
|
+
|
|
604
|
+
⇒ **So the corpus gate is `diff` at 1.000 rather than a byte comparison**, and it is
|
|
605
|
+
worth being exact about what that does and does not cover. `diff` compares structure —
|
|
606
|
+
counts, names, parentage, order, timeline kinds, key counts, curve kinds — and **not
|
|
607
|
+
the values inside the keys**, which is why the precision row above is invisible to it.
|
|
608
|
+
On rigc's own rigs byte identity covers both; on a foreign export the values are held
|
|
609
|
+
by `check` (pixels) or by nothing, depending on what you render.
|
|
610
|
+
|
|
458
611
|
The geometric row needs a real number, because a naive reading of `check` makes an
|
|
459
612
|
exact transcription look wrong. Here is the 3-timing transcription against frames
|
|
460
613
|
rendered from the export it was transcribed from:
|
|
@@ -656,12 +809,22 @@ other one is this project's own renderer and archetype policy.
|
|
|
656
809
|
| **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` |
|
|
657
810
|
| **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` |
|
|
658
811
|
|
|
659
|
-
|
|
660
|
-
|
|
812
|
+
Two further rules — **`A06`** and **`A20`** — are *mixed*: their validity clauses run
|
|
813
|
+
in both profiles and their policy clauses only under `spine-html`. `A06`'s
|
|
661
814
|
size-vs-PNG check is validity; one-part-per-page coverage, rotation and premultiplied
|
|
662
|
-
alpha are policy. `
|
|
663
|
-
|
|
664
|
-
|
|
815
|
+
alpha are policy. `A20`'s weight coherence is validity; requiring a mesh to be
|
|
816
|
+
weighted at all is policy.
|
|
817
|
+
|
|
818
|
+
**`A08` was the third until [#574](https://github.com/firejune/rigc/issues/574).** Its
|
|
819
|
+
policy clause required a skin entry's placeholder to be spelled exactly like the region
|
|
820
|
+
it resolves to — a rule the renderer it was gated under never performed, since
|
|
821
|
+
`spine-html` keys its images on the atlas region name reached through the attachment's
|
|
822
|
+
`path` and reads no placeholder at all. Measured before retiring it: the clause fired
|
|
823
|
+
on **0** attachments across the whole example corpus (no export in `examples/` carries
|
|
824
|
+
a `path` field), and on every rigc rig whose placeholder is not its PNG's basename —
|
|
825
|
+
which is what `path` exists for (AUTHORING §2, R5) and what a placeholder two named
|
|
826
|
+
skins share is emitted as. So it was policy that only ever refused this compiler's own
|
|
827
|
+
correct output.
|
|
665
828
|
|
|
666
829
|
⚠️ **`--profile spine-html` on foreign data produces a wall of failures that mean
|
|
667
830
|
nothing about the file.** Same `spineboy-pro.json`, same atlas, one flag changed — the
|
|
@@ -918,10 +1081,14 @@ Three things to read out of that, in order:
|
|
|
918
1081
|
something is invisible to every measure in that report. Pair it with a `check` against
|
|
919
1082
|
frames rendered from the original.
|
|
920
1083
|
|
|
921
|
-
⚠️ **Do not rename toward what a rule seems to want.** `
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1084
|
+
⚠️ **Do not rename toward what a rule seems to want.** `A27`'s
|
|
1085
|
+
region-name-matches-page-filename is `spine-html` policy (§3.3): under the default
|
|
1086
|
+
profile it does not fire, and renaming somebody's attachments to satisfy a policy they
|
|
1087
|
+
never opted into is a change with no benefit to them. `A08` carried a name-identity
|
|
1088
|
+
clause of the same kind until
|
|
1089
|
+
[#574](https://github.com/firejune/rigc/issues/574) retired it, and that one is the
|
|
1090
|
+
argument's own case study — the rename it seemed to want was one no renderer had ever
|
|
1091
|
+
asked for.
|
|
925
1092
|
|
|
926
1093
|
### 4.3 Extending a foreign skeleton with a new animation
|
|
927
1094
|
|
|
@@ -1010,12 +1177,36 @@ dependency *can* read it and rigc *does not*:
|
|
|
1010
1177
|
that already has the reader, not a parser to write. But it is not there, and nothing on
|
|
1011
1178
|
this page works on a `.skel` today. Re-export as JSON.
|
|
1012
1179
|
|
|
1013
|
-
|
|
1014
|
-
and
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1180
|
+
✅ **A skeleton-to-spec decompiler exists: `rigc ingest` (§2.0). This entry used to
|
|
1181
|
+
refuse one, and all three of its reasons were measured and refuted** — issue
|
|
1182
|
+
[#569](https://github.com/firejune/rigc/issues/569), 2026-09-17. The paragraph is
|
|
1183
|
+
kept below rather than deleted, because what it got wrong is more useful than a
|
|
1184
|
+
clean page:
|
|
1185
|
+
|
|
1186
|
+
> 🚫 ~~**No skeleton-to-spec decompiler.** Nothing turns skeleton JSON back into a rig
|
|
1187
|
+
> spec and a motion spec. §2 is hand work, and that is the current state rather than a
|
|
1188
|
+
> temporary one: a decompiler would have to invent the things the spec format exists to
|
|
1189
|
+
> make explicit — **which pivot, which generator, which invariant** — and the compiler's
|
|
1190
|
+
> own rule is that it never invents a value that is not in the spec.~~
|
|
1191
|
+
|
|
1192
|
+
| clause | what the measurement said |
|
|
1193
|
+
| --- | --- |
|
|
1194
|
+
| *which pivot* | ⛔ **refuted.** A bone's setup transform is in the skeleton, in full. 3,951 bones across 37 production exports and 15 rigs built from this tree were transcribed with **zero** decisions, and `diff`'s six bone measures — count, names, `parent_by_name`, order, `length_present`, `inherit_present` — read **1.000** on every file that built |
|
|
1195
|
+
| *which generator* | ⛔ **refuted, and the premise is the error.** A decompiler must choose **no** generator. A generator is a *model* (`src/rig.ts`: *"they encode a deformation model … and a model is not a table of numbers"*); the skeleton holds geometry, and geometry is what the rig spec's authored form takes. Inferring a model would be the invention this clause feared; writing the numbers is its opposite. `gallery/look`'s four generator-built meshes came back as authored geometry and the rebuild is **byte-identical** — so a generator can always be flattened, and that is the direction the information flows |
|
|
1196
|
+
| *which invariant* | ⛔ **refuted by omission, and this page already said how.** §2.1 step 3: *"Leave `invariants` out entirely — an absent field makes an archetype assertion SKIP, never pass."* `ingest` writes none. A decompiled spec is 91.8 % geometry, 8.2 % structure and **0 % intent**, and it says so instead of certifying something nobody measured |
|
|
1197
|
+
|
|
1198
|
+
⇒ **What survives is the stage, and one value is not "the things the spec format
|
|
1199
|
+
exists to make explicit".** The clause was not wrong that a decompiler meets an
|
|
1200
|
+
invention — it was wrong about *which*, and wrong that it is unavoidable: a refusal
|
|
1201
|
+
naming the field is what this repository does with a missing number everywhere else,
|
|
1202
|
+
and it is what `ingest` does here (§2.0). ⚠️ Not to be confused with the *atlas*
|
|
1203
|
+
importer below, which is a different direction and also exists.
|
|
1204
|
+
|
|
1205
|
+
⚠️ **What `ingest` is still not.** It reads skeleton JSON and writes two spec files.
|
|
1206
|
+
It does not read a `.spine` project or a binary `.skel` (the two entries above stand
|
|
1207
|
+
unchanged), it does not read the atlas or the art, it does not **edit** a skeleton,
|
|
1208
|
+
and it makes no claim about whether an agent could have *produced* the numbers it
|
|
1209
|
+
copied — only that the spec can carry them and `build` reproduces the file from them.
|
|
1019
1210
|
|
|
1020
1211
|
✅ **A packer and an importer both exist now, so do not report them as gaps.** This
|
|
1021
1212
|
non-goal used to read *"rigc emits one region per page and cannot do otherwise"*, and
|
|
@@ -1090,9 +1281,12 @@ something is drawn over them are readable through its own draw order and hierarc
|
|
|
1090
1281
|
|
|
1091
1282
|
📎 To be exact about what is missing: rigc *can* lift a region's drawing back off a
|
|
1092
1283
|
page — `extractRegion` does it, and the contour mesh generator uses it under
|
|
1093
|
-
`--atlas-in` — so what is absent is a **command**, not the capability.
|
|
1094
|
-
|
|
1095
|
-
rigc's own never is
|
|
1284
|
+
`--atlas-in` — so what is absent is a **command**, not the capability. Since issue
|
|
1285
|
+
#570 that includes a region the pack **turned** (`rotate: 90`, `180`, `270`, or the
|
|
1286
|
+
older `rotate: true`), which a foreign pack routinely is and rigc's own never is: the
|
|
1287
|
+
lift transcribes `MeshAttachment.computeUVs`, the one routine in spine-core that
|
|
1288
|
+
states where a turned region's texels are, so what a generator measures does not
|
|
1289
|
+
depend on how the art was delivered (AUTHORING §0.2).
|
|
1096
1290
|
|
|
1097
1291
|
🚫 **No `validate --fix`, and no normalisation pass.** Every recipe in §4 is a change
|
|
1098
1292
|
you state in a spec and rebuild. A tool that rewrote somebody's export in place would
|
package/docs/SPEC_COVERAGE.md
CHANGED
|
@@ -12,7 +12,7 @@ Reproduce the measurements with `bun run fetch-examples && bun run bench:usage`
|
|
|
12
12
|
|
|
13
13
|
> 🔼 **This note is dated and does not move; the measurements below are the corpus as it
|
|
14
14
|
> stood on 2026-08-22 and stay as written. Its statements about what rigc *does* have gone
|
|
15
|
-
> stale in
|
|
15
|
+
> stale in the places listed here, and the live ledger is [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md):**
|
|
16
16
|
>
|
|
17
17
|
> - **B2 is fixed** — A16 accepts `4.3`, `4.3.N` and `4.3.N-<suffix>`, so all twelve example
|
|
18
18
|
> exports pass it.
|
|
@@ -48,6 +48,18 @@ Reproduce the measurements with `bun run fetch-examples && bun run bench:usage`
|
|
|
48
48
|
> `length`/`scale`/`shear`/`inherit`/`skin`/`color` setup fields, slot `dark`/`blend`, region
|
|
49
49
|
> `path`/`scaleX`/`scaleY`/`color`, mesh `path`/`edges`/`color`, and the header's `fps` /
|
|
50
50
|
> `referenceScale` / `images`. Part 2's coverage tables predate all of it.
|
|
51
|
+
> - **A08's name-identity clause is retired (issue #574, 2026-09-17)** — so every line below
|
|
52
|
+
> that calls A08 *mixed* or names it as policy describes a rule that no longer exists:
|
|
53
|
+
> §2.1's `region` row ("the region name == attachment name == PNG basename convention
|
|
54
|
+
> **A08** + **A27** enforce" — A27 still enforces its half, A08 enforces none of it),
|
|
55
|
+
> §2.2's classification row and its "Nine assertions are renderer-profile or
|
|
56
|
+
> profile-mixed" paragraph, §4.1's row (c), and §4.3's item 2. A08 is plain **validity**
|
|
57
|
+
> now and identical under both profiles. The measurement behind it: `spine-html` resolves
|
|
58
|
+
> art through the attachment's `path` and keys its images on the atlas region name, in
|
|
59
|
+
> every published version of it, so the clause was a rule no renderer performed — and it
|
|
60
|
+
> fired on **0** attachments in this corpus, because no export in `examples/` carries a
|
|
61
|
+
> `path` field at all. The executive summary's item 8 is the one A08 line here that was
|
|
62
|
+
> already right and has only got more so: A08 is a Spine-validity rule.
|
|
51
63
|
>
|
|
52
64
|
> **The measurements of the CORPUS (Part 3) have not moved and stay as written.**
|
|
53
65
|
>
|
|
@@ -646,8 +658,7 @@ editor-made meshes — those arrive as authored `uvs`/`triangles`/`weights`.
|
|
|
646
658
|
| `slots.rgba` | ✅ |
|
|
647
659
|
| `slots.rgb`, `alpha` | ❌ emitted (validator knows the channel counts) |
|
|
648
660
|
| `slots.rgba2`, `rgb2` | 🚫 **A12_NO_DARK_COLOR** (`validate.ts:257-261`) |
|
|
649
|
-
| `physics.mix`, `physics.reset` | ✅ (`PHYSICS_TRACKS
|
|
650
|
-
| `physics.inertia/strength/damping/mass/wind/gravity` | ❌ |
|
|
661
|
+
| `physics.inertia/strength/damping/mass/wind/gravity`, `physics.mix`, `physics.reset` | ✅ — all eight (`PHYSICS_TRACKS` in `compile.ts`), authored as `tracks` entries naming `physics`. ⚠️ Their per-key defaults are the parser's, and part 1-8 above is the source: 0 on the six, 1 on `mix`. Not the constraint defaults at `:306-312` |
|
|
651
662
|
| `ik`, `transform` | ✅ — one unnamed timeline per constraint, keyed by the motion spec's `ik` / `transform` arrays |
|
|
652
663
|
| `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 |
|
|
653
664
|
| `attachments.<skin>.…deform` | ❌ (validator knows it: 1 channel, `validate.ts:104-107`) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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": {
|
package/skills/ingest/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ingest
|
|
3
|
-
description: Work with a Spine skeleton.json somebody else authored — exported from the Spine editor or another tool — using rigc. Read and validate it, understand a complaint rigc raised about it,
|
|
3
|
+
description: Work with a Spine skeleton.json somebody else authored — exported from the Spine editor or another tool — using rigc. Read and validate it, understand a complaint rigc raised about it, decompile it into rigc specs with `rigc ingest`, normalise, re-pivot or rename it, and extend it with an animation it does not have. Use when the input is an existing skeleton.json with its .atlas and page images rather than loose part PNGs. Not for Live2D file conversion or runtime tracking.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Bun 1.2 or later and the npm package spine-rigc.
|
|
6
6
|
---
|
|
@@ -17,26 +17,48 @@ not do for you.
|
|
|
17
17
|
|
|
18
18
|
- **Validation is never bypassed.** `validate` reads a foreign file as it is, and
|
|
19
19
|
`build` writes nothing on a red gate — AUTHORING §0.
|
|
20
|
-
- **The compiler never invents a value
|
|
21
|
-
|
|
22
|
-
before it compiles
|
|
20
|
+
- **The compiler never invents a value, and neither does the decompiler.** The
|
|
21
|
+
specs state every bone, slot and key; what the export left implicit is written
|
|
22
|
+
down before it compiles. `rigc ingest` obeys the same rule from the other side —
|
|
23
|
+
what it cannot read out of the skeleton is a named **finding**, never a guess —
|
|
24
|
+
INGEST §2.0 and §2.
|
|
23
25
|
- **The validator's messages are the instructions.** A red line on an export is a
|
|
24
26
|
fact about the file, and sometimes about the rule — INGEST §3 says which, and
|
|
25
27
|
AUTHORING §5 names the file to change.
|
|
26
28
|
|
|
29
|
+
## Start here: `rigc ingest`
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
rigc ingest hero.json --out specs/ --stage 0,0,1024,768
|
|
33
|
+
rigc build --rig specs/rig.json --motion specs/motion.json --images parts/ --out build/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It reads the skeleton — **only** the skeleton — and writes the rig spec and motion
|
|
37
|
+
spec that rebuild it, byte for byte. Two values are not in the file and it refuses
|
|
38
|
+
rather than guessing them: the **stage** (`--stage`, an editor export carries none)
|
|
39
|
+
and each animation's **duration** (the largest key time, recorded as a finding).
|
|
40
|
+
Read `findings.json`: a `BLOCK` line means the rebuild will be missing something and
|
|
41
|
+
the command exits non-zero. Keep the `note` both specs carry. INGEST §2.0.
|
|
42
|
+
|
|
27
43
|
## What this guide will not do
|
|
28
44
|
|
|
29
|
-
rigc cannot
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
rigc still cannot **edit** a skeleton: there is no command that opens one and
|
|
46
|
+
changes it, so every change is expressed in the specs — INGEST §0. `diff`'s ratios
|
|
47
|
+
say how much of a reference's structure a candidate reproduces, so extending or
|
|
48
|
+
renaming a foreign skeleton lowers them by design, and neither `validate` nor `diff`
|
|
49
|
+
has a pass bar — INGEST §0 and §4.
|
|
50
|
+
|
|
51
|
+
⚠️ This section said *"there is no route from it to specs, so every change goes
|
|
52
|
+
through transcription"* until 2026-09-17. That route exists now and it is the first
|
|
53
|
+
thing to reach for; transcription by hand is what you fall back on for a construct
|
|
54
|
+
`ingest` reports as a blocker.
|
|
34
55
|
|
|
35
56
|
## Read, in this order
|
|
36
57
|
|
|
37
58
|
1. [INGEST.md](../../docs/INGEST.md) — what every command will and will not do with
|
|
38
|
-
a foreign file (§0), transcription (§2), what each validator
|
|
39
|
-
an export (§3), and the re-pivot, rename and extend
|
|
59
|
+
a foreign file (§0), `ingest` and transcription (§2), what each validator
|
|
60
|
+
complaint means on an export (§3), and the re-pivot, rename and extend
|
|
61
|
+
recipes (§4).
|
|
40
62
|
2. [AUTHORING.md](../../docs/AUTHORING.md) — the two spec files the transcription
|
|
41
63
|
targets (§3–§4), the failure map (§5–§6), and the coordinate contract (§11).
|
|
42
64
|
3. Then [RIGGING.md](../../docs/RIGGING.md) for why the re-pivot edit has the shape
|