spine-rigc 1.0.0 → 1.0.2
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 +35 -71
- package/docs/AUTHORING.md +727 -1160
- package/docs/FACE.md +205 -371
- package/docs/INGEST.md +155 -305
- package/docs/MOTION.md +11 -27
- package/docs/PROMPTING.md +1 -1
- package/docs/RIGGING.md +10 -26
- package/docs/SPEC_COVERAGE.md +27 -871
- package/package.json +1 -1
- package/src/atlas.ts +9 -8
- package/src/compile.ts +1 -1
- package/src/generation.ts +2 -1
- package/src/ladder.ts +1 -1
- package/src/rig.ts +4 -4
- package/src/validate.ts +20 -14
package/docs/INGEST.md
CHANGED
|
@@ -12,14 +12,6 @@ it first and keep it open; this page never restates a field it documents.
|
|
|
12
12
|
question neither of them answers: **what the toolchain will and will not do with
|
|
13
13
|
somebody else's file, and what the honest routes through it are.**
|
|
14
14
|
|
|
15
|
-
🔓 **Nothing in this repository's benchmark protocol applies to you.** No reading is
|
|
16
|
-
forbidden, no reference is sealed, no attempt is scored, and no rung is being
|
|
17
|
-
attempted. Those rules exist to keep one *measured experiment* honest; you are
|
|
18
|
-
working on somebody's own data. Where this page points at a stored transcription it
|
|
19
|
-
is pointing at **worked precedent you are meant to read**, not at a candidate you are
|
|
20
|
-
not allowed to see. AUTHORING's own exemption line says the same thing from the
|
|
21
|
-
authoring side.
|
|
22
|
-
|
|
23
15
|
🚨 **The two numbers this page produces are not grades, and they measure different
|
|
24
16
|
things.** `validate`'s red says *this file breaks a stated rule* — a fact about the
|
|
25
17
|
file, not about your work, and sometimes (§3.2) a fact about the rule. `diff`'s
|
|
@@ -38,7 +30,7 @@ invent one.
|
|
|
38
30
|
- Why the re-pivot in **§4.1** is shaped the way it is, and the child-bone row it
|
|
39
31
|
warns about worked on a bone that actually has one: [RIGGING.md](RIGGING.md) §3.
|
|
40
32
|
Its §2 is how to tell whether the pivot you are moving *to* is identified at all
|
|
41
|
-
- What the format holds
|
|
33
|
+
- What the Spine 4.3 format holds, field by field:
|
|
42
34
|
[SPEC_COVERAGE.md](SPEC_COVERAGE.md)
|
|
43
35
|
- If you are the *person operating* an agent rather than the agent:
|
|
44
36
|
[PROMPTING.md](PROMPTING.md)
|
|
@@ -50,22 +42,14 @@ invent one.
|
|
|
50
42
|
Two facts decide everything below, and they pull in opposite directions:
|
|
51
43
|
|
|
52
44
|
1. **rigc reads compiled skeleton JSON in more places than you would guess.**
|
|
53
|
-
`validate`, `render`, `preview`, `vote`, `check`, `diff` and
|
|
54
|
-
`ingest` all take a `skeleton.json` path directly, and none of them needs a rig
|
|
45
|
+
`validate`, `render`, `preview`, `vote`, `check`, `diff` and `ingest` all take a `skeleton.json` path directly, and none of them needs a rig
|
|
55
46
|
spec to do it.
|
|
56
|
-
2. **rigc
|
|
47
|
+
2. **rigc cannot EDIT one.** There is no command that opens a skeleton and
|
|
57
48
|
changes it. The only thing that produces a skeleton is `build`, and `build`'s
|
|
58
49
|
input is a rig spec plus a motion spec.
|
|
59
50
|
⇒ **Every route that ends in a changed file goes through the specs** — and
|
|
60
|
-
|
|
61
|
-
|
|
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.
|
|
51
|
+
there are two ways to get them: write them (§2, transcription) or have
|
|
52
|
+
`ingest` write them for you from the file itself (§2.0).
|
|
69
53
|
|
|
70
54
|
### 0.1 The table
|
|
71
55
|
|
|
@@ -76,12 +60,12 @@ an upstream `license.txt` (Appendix, and [NOTICE.md](../NOTICE.md)).
|
|
|
76
60
|
| Command | Takes a foreign `skeleton.json`? | What it needs, and what it gives back |
|
|
77
61
|
| --- | --- | --- |
|
|
78
62
|
| **`validate <skeleton.json>`** | ✅ **yes — this is its foreign-data form** | the `.json`, plus one `.atlas` beside it or named with `--atlas`. Runs the assertions and prints `PASS`/`FAIL`/`SKIP`/`PROF` per rule, naming the profile that judged it. §1.1 |
|
|
79
|
-
| **`render --candidate <skeleton.json>`** | ✅ **yes** | PNG frames plus a contact sheet, per animation. ⭐ **It does not gate** — it
|
|
63
|
+
| **`render --candidate <skeleton.json>`** | ✅ **yes** | PNG frames plus a contact sheet, per animation. ⭐ **It does not gate** — it draws a file `validate` refuses, which is how you tell a red about the file from a red about the rule (§3.2) |
|
|
80
64
|
| **`preview --candidate <skeleton.json>`** | ✅ **yes** | one self-contained `.html` that plays it in the official Spine Web Player. Needs a network the first time it is opened ([NOTICE.md](../NOTICE.md)) |
|
|
81
65
|
| **`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 |
|
|
82
66
|
| **`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 |
|
|
83
67
|
| **`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 — for a skeleton rigc emitted**. ⚠️ For an editor export the claim is
|
|
68
|
+
| **`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 — for a skeleton rigc emitted**. ⚠️ For an editor export the claim is identity in canonical form apart from `hash` and `spine`, which §2.3 states in full. The seventh reader, and the one that ends §2's hand work — §2.0 and §5 |
|
|
85
69
|
| **`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 |
|
|
86
70
|
| **`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 |
|
|
87
71
|
| **`build --rig … --motion … --images …`** | ⛔ **no** | specs in, skeleton out. The only writer in the toolchain, and the reason §2 exists |
|
|
@@ -183,11 +167,7 @@ Read it as three separate statements, because they answer three different questi
|
|
|
183
167
|
you, without your having opened the JSON, that this export has no draw-order
|
|
184
168
|
timeline, no event timeline, no constraint timeline, no deform timeline, no mesh
|
|
185
169
|
attachment, no physics constraint, and no bounding box, clipping attachment or
|
|
186
|
-
path. ⭐
|
|
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`,
|
|
170
|
+
path. ⭐ What passes over an empty list is the other kind of rule — `A01`, `A02`,
|
|
191
171
|
`A11`, `A12`, `A14` ask *how many of this does the file carry*, and **zero is the
|
|
192
172
|
answer**.
|
|
193
173
|
- **`PROF`** — the rule was excluded by the profile before its body ran. §3.3.
|
|
@@ -236,8 +216,8 @@ Spine runtime plays it, whatever rigc's own rasteriser or validator thinks.
|
|
|
236
216
|
|
|
237
217
|
`diff` takes two compiled skeletons and reports 49 measures in eight groups, plus two
|
|
238
218
|
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
|
-
|
|
219
|
+
and `animations`, and the `skeleton` header block at the top, which measures the stage.
|
|
220
|
+
A ninth group of six joins them when something has paired the two sides'
|
|
241
221
|
animations — `--as <candidate>=<reference>`, or one animation each side, which pairs by
|
|
242
222
|
position (§1.3.1). Both sides may be foreign; the interesting pairing during ingest is
|
|
243
223
|
**your transcription against the export it came from**:
|
|
@@ -290,13 +270,11 @@ units and every one of the 49 measures still reads **1.000**. ⇒ Never take a g
|
|
|
290
270
|
`diff` as evidence that a geometric edit did not land, and never take it as evidence
|
|
291
271
|
that one did.
|
|
292
272
|
|
|
293
|
-
⚠️
|
|
294
|
-
(§2.3
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
`rigc diff <a.json> <b.json>` does not have. The sentence above is about this command
|
|
299
|
-
and stays true of it.
|
|
273
|
+
⚠️ **`diff` has no value-level measure, and the reason is an input rather than a
|
|
274
|
+
policy** (§2.3 says what a value comparison covers): comparing values means reading
|
|
275
|
+
both files through `spine-core`, and a skeleton whose attachments carry a `sequence`
|
|
276
|
+
cannot be parsed without the atlas that resolves it — so the measure takes two
|
|
277
|
+
skeletons **and two packs**, which `rigc diff <a.json> <b.json>` does not have.
|
|
300
278
|
|
|
301
279
|
⚠️ **The one exception is the skeleton's own declared box**, and it is an exception to
|
|
302
280
|
the sentence and not to the rule: `skeleton.stage_box` compares four world numbers,
|
|
@@ -305,13 +283,11 @@ measured, and the block they sit in gates nothing. Moving a pivot does not move
|
|
|
305
283
|
either.
|
|
306
284
|
|
|
307
285
|
⭐ **Declared is not the same as written down, and for the origin it is the
|
|
308
|
-
difference between a green round trip and a false finding
|
|
309
|
-
([#620](https://github.com/firejune/rigc/issues/620)). The editor omits a header
|
|
286
|
+
difference between a green round trip and a false finding.** The editor omits a header
|
|
310
287
|
field at its default, so a stage sitting at `0,0` exports as a `width` and a
|
|
311
288
|
`height` and no `x`/`y` at all — there is no other spelling for it. The measure
|
|
312
|
-
reads that omission as the `0` it means,
|
|
313
|
-
|
|
314
|
-
four "exactly as stated" scored the same box **2/4**. The extent is still read
|
|
289
|
+
reads that omission as the `0` it means, so a rigc build whose stage is at the
|
|
290
|
+
origin and the editor's export of that build read `stage_box` **4/4**. The extent is still read
|
|
315
291
|
exactly as stated: it is what decides whether there is a stage at all, so a missing
|
|
316
292
|
`width` is an absent stage rather than a stage of width zero.
|
|
317
293
|
|
|
@@ -491,20 +467,15 @@ rigc diff spine/skeleton.json examples/spineboy/export/spineboy-ess.json
|
|
|
491
467
|
`motion.json` and `findings.json`. The contract is an equality rather than a
|
|
492
468
|
rulebook: `build(ingest(x))` is `x`, byte for byte on `skeleton.json`, and the
|
|
493
469
|
atlas comes back with the same region blocks (as a multiset — the page order is in
|
|
494
|
-
no field of the file).
|
|
495
|
-
that on every run, which is the one gate here that compares an emitted file against
|
|
496
|
-
a file rigc did not write.
|
|
470
|
+
no field of the file).
|
|
497
471
|
|
|
498
|
-
📊 **
|
|
499
|
-
|
|
500
|
-
`examples/*/export/*.json` is ingested with `--art none`, rebuilt through the pack
|
|
472
|
+
📊 **For an editor export the claim is weaker, and measured.** Every
|
|
473
|
+
`examples/*/export/*.json` ingested with `--art none`, rebuilt through the pack
|
|
501
474
|
beside it, and `diff`ed against the file it was read from: **12 of 12 come back with 0
|
|
502
|
-
blockers and 1.000 on all 49 ratio-bearing measures and all 5 reported ones** — and
|
|
503
|
-
|
|
504
|
-
measures** too, over **193,927** compared values. Byte
|
|
475
|
+
blockers and 1.000 on all 49 ratio-bearing measures and all 5 reported ones** — and on
|
|
476
|
+
all **nine value measures** too, over **193,927** compared values. Byte
|
|
505
477
|
identity is not the claim there and the reason is the input, not the round trip — §2.3
|
|
506
|
-
has the
|
|
507
|
-
reach. ⚠️ Which pack is "the one beside it" is
|
|
478
|
+
has the pass line, and what the value measures do and do not reach. ⚠️ Which pack is "the one beside it" is
|
|
508
479
|
resolved rather than guessed, for §0.2's reason: `spineboy/export` holds two, and
|
|
509
480
|
`spineboy-run.atlas` covers neither skeleton in it.
|
|
510
481
|
|
|
@@ -520,53 +491,40 @@ of this section**, with its gutter, its effect on the exit code and what to do.
|
|
|
520
491
|
⛔ **And it reads one generation.** Spine data is locked to the generation that
|
|
521
492
|
exported it, and a mismatch is silent rather than loud: 4.3 takes constraints from the
|
|
522
493
|
top-level `constraints` array alone, so a 4.0–4.2 file's `ik`/`transform`/`path`/
|
|
523
|
-
`physics` arrays load as nothing at all
|
|
524
|
-
runtime and loaded 0 of 8,672 constraints
|
|
525
|
-
([#706](https://github.com/firejune/rigc/issues/706) row 1). So `ingest` reads
|
|
494
|
+
`physics` arrays load as nothing at all. So `ingest` reads
|
|
526
495
|
`skeleton.spine` before it reads a field of the file, and a file from another
|
|
527
496
|
generation is a blocker naming that generation and counting, **on that file**, what a
|
|
528
497
|
4.3 reader loses by it. Reading such a file with *that generation's own* defaults is a
|
|
529
|
-
different job
|
|
530
|
-
runtime's `SkeletonJson` — and it is not in this tool, which is why the finding points
|
|
498
|
+
different job, and it is not in this tool, which is why the finding points
|
|
531
499
|
at the policy rather than implying the file was read.
|
|
532
500
|
|
|
533
501
|
**Two values are not in a skeleton**, so `ingest` asks rather than guesses:
|
|
534
502
|
|
|
535
503
|
- **the stage** (`skeleton.width`/`height`) — a file that declares none is **carried as
|
|
536
504
|
declaring none**: the rig spec states `"width": null, "height": null` (§2.1 step 3's
|
|
537
|
-
spelling
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
#714 the absence was a blocker and the flag the only road through it — a number the
|
|
543
|
-
source never stated, on the shape #714 counts in 48 of 48 production exports.
|
|
544
|
-
⚠️ **This page said an editor export carries none until
|
|
545
|
-
[#594](https://github.com/firejune/rigc/issues/594) measured it: all twelve exports in
|
|
546
|
-
the fetched corpus carry a stage**, `ingest` reads it straight through, and not one of
|
|
547
|
-
them needed the flag. What holds without qualification is that the box cannot be
|
|
505
|
+
spelling), the rebuild emits a header with none of `x`/`y`/`width`/`height`, and it
|
|
506
|
+
is the file that was read, byte for byte. No finding is recorded, because nothing
|
|
507
|
+
was lost and nobody decided anything. `--stage x,y,w,h` is how a caller *adds* a box
|
|
508
|
+
to such a file, and that is a `NO_STAGE` **judgement**. All twelve exports in the
|
|
509
|
+
fetched corpus carry a stage, and `ingest` reads it straight through. The box cannot be
|
|
548
510
|
*derived* — posing the rig gives the *animated* extent, which is a different number
|
|
549
|
-
from the setup box. 🔸 **Half a stage is
|
|
511
|
+
from the setup box. 🔸 **Half a stage is a `NO_STAGE` blocker**: an origin with no
|
|
550
512
|
extent, or one extent without the other, declares no stage and is not the absence
|
|
551
513
|
either, and the rig spec holds a stage as four fields or none. It is also the value that costs least to get wrong: `diff`
|
|
552
|
-
reports it as two measures of its own (`stage_present`, `stage_box
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
flag is refused beside a box the file states** — two sources for one value, both named,
|
|
557
|
-
and the file is the record of what was measured
|
|
558
|
-
([#626](https://github.com/firejune/rigc/issues/626)). It used to be read only *after*
|
|
559
|
-
the file's box, so `--stage` at any of the twelve did nothing and said nothing;
|
|
514
|
+
reports it as two measures of its own (`stage_present`, `stage_box`) and they are
|
|
515
|
+
`(reported)`, so no score reads them and an absurd box is green nearly everywhere.
|
|
516
|
+
⛔ **The flag is refused beside a box the file states** — two sources for one value,
|
|
517
|
+
both named, and the file is the record of what was measured;
|
|
560
518
|
- **each animation's duration** — the format has no such field. The largest key time
|
|
561
519
|
is used, stated in the motion spec's `note`, and recorded as a finding per
|
|
562
520
|
animation. Edit it if you know the real number.
|
|
563
521
|
|
|
564
|
-
🎛️ **And one statement is not in a skeleton either: who turns a muted constraint on
|
|
565
|
-
|
|
522
|
+
🎛️ **And one statement is not in a skeleton either: who turns a muted constraint on.**
|
|
523
|
+
An ik or transform constraint
|
|
566
524
|
resting at 0 on every mix it reads, that no animation keys above 0, is either a
|
|
567
525
|
leftover that moves nothing or a dial a game sets from code — and the two export as
|
|
568
|
-
the same bytes. `build` refuses the shape by name (`A47`/`A48`),
|
|
569
|
-
|
|
526
|
+
the same bytes. `build` refuses the shape by name (`A47`/`A48`), so without a
|
|
527
|
+
statement the rebuild of a file whose game switches that ik on at runtime is refused.
|
|
570
528
|
So `ingest` reads it the way under which the file is correct: it writes the
|
|
571
529
|
constraint into the rig spec's `invariants.consumerDrivenMix` ([AUTHORING
|
|
572
530
|
§3.7](AUTHORING.md)), with a `why` saying the entry is `ingest`'s reading, and prints a
|
|
@@ -575,8 +533,8 @@ declaration is a statement to the gate, never emitted — and it gates green, wi
|
|
|
575
533
|
constraint SKIPped by name rather than measured. It is the only field of `invariants`
|
|
576
534
|
`ingest` ever writes, and the rig spec's `note` says so where it is present.
|
|
577
535
|
⚠️ **None of the twelve exports carries the shape**: every constraint they rest muted
|
|
578
|
-
is keyed up by an animation, spineboy's aim rig being the idiom,
|
|
579
|
-
|
|
536
|
+
is keyed up by an animation, spineboy's aim rig being the idiom, so `ingest` prints no
|
|
537
|
+
such line and writes no declaration for any of them.
|
|
580
538
|
|
|
581
539
|
And two flags for what the skeleton also does not encode: `--art loose` (the default)
|
|
582
540
|
names an `image` per attachment resolved against loose PNGs, `--art none` states
|
|
@@ -584,7 +542,7 @@ names an `image` per attachment resolved against loose PNGs, `--art none` states
|
|
|
584
542
|
same pack read for a report rather than for an artifact: `explain` **poses** the rig
|
|
585
543
|
to print its `DEFORM` block, a pose resolves every attachment against an atlas, and a
|
|
586
544
|
size-only spec carries none of its own, so without the flag that pair is refused by
|
|
587
|
-
name rather than posed
|
|
545
|
+
name rather than posed; and
|
|
588
546
|
under `loose`, `--images <dir>` writes
|
|
589
547
|
the rig spec's own images directory relative to `--out`, so the rebuild is a plain
|
|
590
548
|
`build --rig … --motion … --out …` rather than one carrying `--images` forever. It is
|
|
@@ -605,80 +563,62 @@ are on disk either way. (The one thing `ingest` does refuse outright is an optio
|
|
|
605
563
|
contradicts the file, which is not a finding: `--stage` beside a box the skeleton
|
|
606
564
|
declares.)
|
|
607
565
|
|
|
608
|
-
🔒 **Derived, not kept by hand.** The ingest suite of `bun run selftest` (`IG25`)
|
|
609
|
-
reads the codes out of [`src/ingest.ts`](../src/ingest.ts) and refuses a row this
|
|
610
|
-
table lacks, a row naming a code nothing emits, and a gutter cell that is not the
|
|
611
|
-
kind the source records — with `IG26` as its red-first, which removes a row, invents
|
|
612
|
-
one and flips a gutter in turn and requires each to be named. Six of these codes are
|
|
613
|
-
**composed** in the source rather than written out: five over a union of three or two
|
|
614
|
-
names, which the scan expands, and `ATTACHMENT_<TYPE>` from the file's own text, which
|
|
615
|
-
it cannot — so that one is a row about a family and says so. The scan counts the
|
|
616
|
-
`note(` calls in the file against the sites it resolved, because a code it cannot read
|
|
617
|
-
is the one failure a comparison of two sets cannot show you.
|
|
618
|
-
|
|
619
566
|
| code | gutter | exit | what it means | what to do |
|
|
620
567
|
| --- | --- | --- | --- | --- |
|
|
621
568
|
| `ANIMATION_GROUP` | `BLOCK` | 1 | the animation carries a group the motion spec has no home for. The detail names the ten it does carry. `drawOrderFolder` is the group to know about: the runtime reads it and builds a timeline from it, and no export in this corpus carries one | transcribe that group by hand (§2), or accept that the rebuild does not carry it |
|
|
622
|
-
| `ATTACHMENT_<TYPE>` | `BLOCK` | 1 | an attachment of a type rigc does not emit; the code is composed from the type, so on the one type left it reads `ATTACHMENT_POINT`. rigc emits region, mesh, linkedmesh, boundingbox, clipping and path
|
|
623
|
-
| `ATTACHMENT_LINK_GEOMETRY` | `LOSS` | 0 | a **linked mesh** that also states `uvs`, `triangles`, `vertices`, `hull` or `edges`. The parser returns from the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so those keys are read by nothing at all and the attachment draws the geometry its `source` names; the rig spec has no home for them either, because `build` refuses geometry on a link by name. The detail lists the keys and the source
|
|
624
|
-
| `ATTACHMENT_SEQUENCE` | `BLOCK` | 1 | a `sequence` block — a numbered image series — that the rig spec cannot say **as written**.
|
|
625
|
-
| `ATTACHMENT_TIMELINE` | `BLOCK` | 1 | an attachment timeline that is neither `deform` nor `sequence`. Both are carried
|
|
569
|
+
| `ATTACHMENT_<TYPE>` | `BLOCK` | 1 | an attachment of a type rigc does not emit; the code is composed from the type, so on the one type left it reads `ATTACHMENT_POINT`. rigc emits region, mesh, linkedmesh, boundingbox, clipping and path, and `point` is the one deferred type | the rebuild will not have that attachment at all. `docs/SPEC_COVERAGE.md` part 1-6 says what a deferred type would carry |
|
|
570
|
+
| `ATTACHMENT_LINK_GEOMETRY` | `LOSS` | 0 | a **linked mesh** that also states `uvs`, `triangles`, `vertices`, `hull` or `edges`. The parser returns from the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so those keys are read by nothing at all and the attachment draws the geometry its `source` names; the rig spec has no home for them either, because `build` refuses geometry on a link by name. The detail lists the keys and the source | nothing. The rebuild is the mesh the runtime was already drawing — and if those keys were the geometry you meant, take `source` off and author it as a mesh of its own. `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` is the same fact at the gate |
|
|
571
|
+
| `ATTACHMENT_SEQUENCE` | `BLOCK` | 1 | a `sequence` block — a numbered image series — that the rig spec cannot say **as written**. A well-formed block on a region, mesh or linked mesh is carried field for field, with no `image` on the loose route (the frames `<path><number>` are the art), and a `sequence` timeline with it; what is left here is a block the parser reads into a series other than the one written — no `count` (0 regions), a `setup` past the end (clamped), a fraction — or one on a `boundingbox`, `clipping` or `path`, where the parser never reads it. The detail quotes the block and says which | the rebuild draws the single region the attachment names. Fix the block in the source — a `count` is the usual one — and ingest again |
|
|
572
|
+
| `ATTACHMENT_TIMELINE` | `BLOCK` | 1 | an attachment timeline that is neither `deform` nor `sequence`. Both are carried, and `readAnimation` tests an attachment timeline for exactly those two names and ignores anything else (`SkeletonJson.js:1147-1201`) — so what reaches this line is a name outside the format, which no player plays either | fix the timeline's name in the source, or accept that the rebuild does not carry it |
|
|
626
573
|
| `BONE_FIELD` | `BLOCK` | 1 | a bone field with no rig-spec field, so it is dropped. A 4.0/4.1 export spelling `transform` where 4.3 spells `inherit` lands here; so does a misspelling | check the name against AUTHORING §3 first — a typo and an unsupported field read exactly the same |
|
|
627
|
-
| `BONE_TIMELINE` | `BLOCK` | 1 | a bone timeline the motion spec has no track for. The detail names the eleven it has, read off the table.
|
|
574
|
+
| `BONE_TIMELINE` | `BLOCK` | 1 | a bone timeline the motion spec has no track for. The detail names the eleven it has, read off the table. Every bone timeline the runtime plays has a track, `inherit` included — the eleventh case of the runtime's own bone switch, a stepped mode per key — so this is reachable only for a name the **parser** throws on too (`Invalid timeline type for a bone`), the position `PHYSICS_TIMELINE` is in | check the spelling; there is no bone timeline left for the rebuild to be missing |
|
|
628
575
|
| `CONSTRAINT_FIELD` | `BLOCK` | 1 | as `BONE_FIELD`, on a constraint, with its type named beside it | as `BONE_FIELD` |
|
|
629
|
-
| `CONSTRAINT_KEY_RESTATED` | `LOSS` | 0 | an `ik` or `transform` track whose keys do not all state the same fields. The motion spec takes one field set per track, so a field **any** key states is written on **every** key of the spec at the value the parser would have read there — and the line is printed only for a value the **file** will still carry.
|
|
630
|
-
| `CONSUMER_DRIVEN_MIX` | `JUDGE` | 0 | an `ik` or `transform` constraint resting at 0 on every mix it reads — an ik's `mix`; a transform's mixes for the `to` properties it declares — that no animation keys above 0, reading every key the way `A47`/`A48` do: an omitted mix is the parser's 1 and a Bezier handle above 0 lifts a 0 → 0 pair. Nothing in the file ever switches it on, and the file cannot say whether that is a leftover or a mix a game sets from code, so the rig spec **declares** it in `invariants.consumerDrivenMix`
|
|
576
|
+
| `CONSTRAINT_KEY_RESTATED` | `LOSS` | 0 | an `ik` or `transform` track whose keys do not all state the same fields. The motion spec takes one field set per track, so a field **any** key states is written on **every** key of the spec at the value the parser would have read there — and the line is printed only for a value the **file** will still carry. The emitter leaves a value out wherever it is the one the parser reads without it ([AUTHORING §10.6c](AUTHORING.md)), so a restated default is the source's own text again and says nothing; what is left is a value the table has no row for, or a transform key's `mixY` the source left out beside a `mixX` that is not 1, which the emitter keeps because that is where the editor writes it (§10.6c's *only at 1*). Measured on the twelve exports: 0 tracks print it | nothing. Same values, and where this prints, a larger file — the rebuild plays what the source plays |
|
|
577
|
+
| `CONSUMER_DRIVEN_MIX` | `JUDGE` | 0 | an `ik` or `transform` constraint resting at 0 on every mix it reads — an ik's `mix`; a transform's mixes for the `to` properties it declares — that no animation keys above 0, reading every key the way `A47`/`A48` do: an omitted mix is the parser's 1 and a Bezier handle above 0 lifts a 0 → 0 pair. Nothing in the file ever switches it on, and the file cannot say whether that is a leftover or a mix a game sets from code, so the rig spec **declares** it in `invariants.consumerDrivenMix` and the rebuild's gate SKIPs it by name. It is a judgement for `DURATION`'s reason: a statement the skeleton does not carry, made and printed — and not a `LOSS`, because the rebuilt skeleton is the source's bytes. A transform that declares no `to` at all is not a candidate: `A48` refuses it with its own sentence, which no declaration answers | nothing, if a game drives that mix. If it is a leftover, delete the entry and rest a mix it reads above 0 — or remove the constraint — and the gate measures it again |
|
|
631
578
|
| `CONSTRAINT_TYPE` | `BLOCK` | 1 | a constraint whose `type` is none rigc knows, so the whole constraint is dropped rather than approximated | the rebuild has no such constraint; check the spelling before assuming the type is unsupported |
|
|
632
579
|
| `DURATION` | `JUDGE` | 0 | skeleton JSON has no duration field at all. The largest key time is used, which is what a runtime plays to — and wrong for an animation that holds its last pose past its last key | if you know the real number, edit `duration` in the motion spec. It costs nothing: the declared duration is checked against the compiled keys |
|
|
633
|
-
| `GENERATION_UNKNOWN` | `BLOCK` | 1 | `skeleton.spine` names no generation rigc knows, or the header states none at all. A version is read as its LEADING `major.minor` token — a down-export writes `4.0-from-4.1.24`, which is 4.0 data from a 4.1 editor — and it is never rounded to the nearest generation:
|
|
634
|
-
| `GENERATION_UNSUPPORTED` | `BLOCK` | 1 | the file is Spine data from another generation and this reader reads 4.3. The detail names the generation, the string it was read from, and what a 4.3 reader loses on **this** file: constraints parked in the top-level `ik` / `transform` / `path` / `physics` / `slider` arrays 4.3 folded into `constraints` and this reader never opens
|
|
635
|
-
| `HEADER_BOOKKEEPING` | `LOSS` | 0 | a header field the editor writes and the rig spec has no home for — `hash`, the editor's project hash, which is a value about a file rigc did not write. Dropped, and nothing reads it back. `audio`
|
|
580
|
+
| `GENERATION_UNKNOWN` | `BLOCK` | 1 | `skeleton.spine` names no generation rigc knows, or the header states none at all. A version is read as its LEADING `major.minor` token — a down-export writes `4.0-from-4.1.24`, which is 4.0 data from a 4.1 editor — and it is never rounded to the nearest generation: rounding `3.8.99` up hands 3.8 data to a 4.2 runtime, and it poses as NaN | check the string against the file you were handed. A real generation rigc does not list is worth reporting, with the string beside it |
|
|
581
|
+
| `GENERATION_UNSUPPORTED` | `BLOCK` | 1 | the file is Spine data from another generation and this reader reads 4.3. The detail names the generation, the string it was read from, and what a 4.3 reader loses on **this** file: constraints parked in the top-level `ik` / `transform` / `path` / `physics` / `slider` arrays 4.3 folded into `constraints` and this reader never opens, bones carrying `transform` where 4.3 spells `inherit`, and physics constraints omitting `inertia` / `damping`, whose default is not the same number in 4.2 as in 4.3 | re-export the file as 4.3 from an editor of its own generation, or transcribe it by hand (§2). Reading it with **that generation's** defaults is not in this tool |
|
|
582
|
+
| `HEADER_BOOKKEEPING` | `LOSS` | 0 | a header field the editor writes and the rig spec has no home for — `hash`, the editor's project hash, which is a value about a file rigc did not write. Dropped, and nothing reads it back. `audio` is not on this line: the rig spec states it ([AUTHORING §3.1](AUTHORING.md)) and `ingest` carries it, `null` included | nothing. It is one of the two declared exceptions §2.3's pass line is stated apart from |
|
|
636
583
|
| `HEADER_ORIGIN` | `LOSS` | 0 | the source declares an extent and omits `x`/`y`. Inside a declared extent an omitted origin **is** 0, so the spec states it — and the rebuild then spells two fields the source did not | nothing. Same box, different bytes — which is why byte identity is not the claim for an export that takes this branch |
|
|
637
584
|
| `HEADER_REDERIVED` | `LOSS` | 0 | `skeleton.spine`: the rebuild writes the version of the runtime rigc links. The line says whether that is the same string the source states | nothing — but read the line: a 4.2 export rebuilds as 4.3 in that one field, and a source from another generation raises `GENERATION_UNSUPPORTED` beside it, which is the blocker about the DATA rather than about the string |
|
|
638
585
|
| `IK_KEY_FIELD` | `BLOCK` | 1 | a key field on an `ik` timeline that is not part of its shape | check the spelling; an unknown field is dropped from the rebuilt track |
|
|
639
|
-
| `NO_STAGE` | `BLOCK` `JUDGE` | 1 | the skeleton declares no stage, and one of two things follows. A **judgement** — exit 0 — when `--stage x,y,w,h` supplied a box, because nothing measured the box you gave it. A **blocker** when the header states **half** a stage — an origin with no extent, or one extent without the other — which the rig spec cannot hold; the detail names the fields it states. A header with **none** of the four is not a finding at all: it is carried as `"width": null, "height": null` and rebuilds byte for byte
|
|
586
|
+
| `NO_STAGE` | `BLOCK` `JUDGE` | 1 | the skeleton declares no stage, and one of two things follows. A **judgement** — exit 0 — when `--stage x,y,w,h` supplied a box, because nothing measured the box you gave it. A **blocker** when the header states **half** a stage — an origin with no extent, or one extent without the other — which the rig spec cannot hold; the detail names the fields it states. A header with **none** of the four is not a finding at all: it is carried as `"width": null, "height": null` and rebuilds byte for byte | for the judgement, nothing if the box came from the project the file came from. For the blocker, supply the box with `--stage`, or take the stray field(s) out of the source and the absence is carried. It cannot be derived: posing the rig gives the animated extent, which is a different number |
|
|
640
587
|
| `PATH_TIMELINE` | `BLOCK` | 1 | a path-constraint timeline the motion spec has no track for — it carries position, spacing and mix | transcribe it, or accept that the rebuild plays nothing there |
|
|
641
|
-
| `PHYSICS_DRIVES_NOTHING` | `LOSS` | 0 | a physics constraint none of whose `x`, `y`, `rotate`, `scaleX`, `shearX` is above 0 — absent, or stated at 0 or below. `PhysicsConstraint.update` applies a component only above 0 (`PhysicsConstraint.js:112`), so it moves no bone, and `build` refuses exactly that shape by name at `A23_PHYSICS_CONSTRAINT_EFFECTIVE
|
|
642
|
-
| `PHYSICS_GLOBAL_REACHES_NOTHING` | `LOSS` | 0 | a physics timeline keyed under the **empty** name — the one that names no constraint, which the runtime applies to every physics constraint declaring that property global (`"strengthGlobal": true` for `strength`; `reset` resets every physics constraint and asks no flag) — in a file where no physics constraint the rebuild carries declares it. The motion spec spells that timeline `"physics": "*"`
|
|
588
|
+
| `PHYSICS_DRIVES_NOTHING` | `LOSS` | 0 | a physics constraint none of whose `x`, `y`, `rotate`, `scaleX`, `shearX` is above 0 — absent, or stated at 0 or below. `PhysicsConstraint.update` applies a component only above 0 (`PhysicsConstraint.js:112`), so it moves no bone, and `build` refuses exactly that shape by name at `A23_PHYSICS_CONSTRAINT_EFFECTIVE`. The rig spec **omits** it, together with every timeline keyed to it (a track naming it would be an unknown constraint to the rebuild, refused at compile) and its place on any skin's `physics` list; the detail names each, and the values it did state. Measured on a generated rig through spine-core, posing the source with and without such a constraint differs by **0** on every bone world value — and by at most 9e-8 when it sits on the root, which is the runtime's `modifyWorld` recomputing a local transform it had no reason to, not a component. ⚠️ **One thing does move:** a duration is the last key an animation has left, so an omitted timeline that held the last key shortens the rebuilt animation, and the detail says which animation and both lengths | nothing, if it was meant to do nothing. If it was meant to jiggle, the file never said so: give it the component it should drive and it is carried like any other. Where the detail names a shortened animation and the length matters to whatever loops it, key something at the length it had |
|
|
589
|
+
| `PHYSICS_GLOBAL_REACHES_NOTHING` | `LOSS` | 0 | a physics timeline keyed under the **empty** name — the one that names no constraint, which the runtime applies to every physics constraint declaring that property global (`"strengthGlobal": true` for `strength`; `reset` resets every physics constraint and asks no flag) — in a file where no physics constraint the rebuild carries declares it. The motion spec spells that timeline `"physics": "*"` and `build` refuses one that reaches nobody by name, so the rig spec **omits** it: in the source it walked every constraint and wrote into none. A constraint `PHYSICS_DRIVES_NOTHING` omitted counts as not carried — it was the only thing such a timeline could reach, and it moved no bone. ⚠️ As with that row, a duration is the last key an animation has left, so an omitted timeline that held the last key shortens the rebuilt animation and the detail says both lengths. An unnamed timeline that **does** reach a constraint is not a finding at all: it is carried as `"*"` and rebuilt under the empty name byte for byte | nothing, if it was meant to do nothing. If it was meant to drive the constraints, the file never said which: set `"<property>Global": true` on them in the rig spec and key it as `"physics": "*"` |
|
|
643
590
|
| `PHYSICS_TIMELINE` | `BLOCK` | 1 | the same for a physics constraint, whose eight the motion spec carries in full — so this is reachable only for a name the **parser** falls through too | as `PATH_TIMELINE` |
|
|
644
591
|
| `SLIDER_TIMELINE` | `BLOCK` | 1 | the same for a slider, which carries time and mix | as `PATH_TIMELINE` |
|
|
645
592
|
| `SLOT_FIELD` | `BLOCK` | 1 | as `BONE_FIELD`, on a slot | as `BONE_FIELD` |
|
|
646
|
-
| `SLOT_TIMELINE` | `BLOCK` | 1 | a slot timeline the motion spec has no track for.
|
|
647
|
-
| `SPEC_REFUSED` | `BLOCK` | 1 | the specs were written and **rigc's own parser refuses one of them** — the detail carries that refusal word for word, after the file and the spec it is about. It is the one finding that is not about a single construct: it is whatever `parseRigSpec` or `parseMotionSpec` names, from a shape the format holds and the spec cannot say (a constraint that is `skinRequired` under no skin) to a defect in this decompiler
|
|
593
|
+
| `SLOT_TIMELINE` | `BLOCK` | 1 | a slot timeline the motion spec has no track for. The spec has a track for all six the format has, so what still reaches this line is a name **outside** the format — `sequence` written on a slot rather than an attachment is the likeliest — and the detail says so: the runtime's own reader throws `Invalid timeline type for a slot` on it, so no player loads that file either. The detail names the tracks the spec does have and, when the format has any it lacks, those too, **both read off the tables**. `rgb` and `alpha` are carried under their own names and on their own key times, never folded into one `rgba` — that would state each channel at the other's key times, a value nobody keyed | fix the timeline's name, or accept that the rebuild plays nothing there |
|
|
594
|
+
| `SPEC_REFUSED` | `BLOCK` | 1 | the specs were written and **rigc's own parser refuses one of them** — the detail carries that refusal word for word, after the file and the spec it is about. It is the one finding that is not about a single construct: it is whatever `parseRigSpec` or `parseMotionSpec` names, from a shape the format holds and the spec cannot say (a constraint that is `skinRequired` under no skin) to a defect in this decompiler | read the quoted sentence against the skeleton: it names the object. Both specs are on disk for exactly that, and `build` will refuse them until the shape has a spelling — [AUTHORING §5.1](AUTHORING.md) is the list of what a parser says |
|
|
648
595
|
| `TIMELINE_FIELD` | `BLOCK` | 1 | a key field on a bone, path, physics or slider timeline that is not part of that timeline's shape. On an `inherit` key that includes a `curve`: the parser reads `time` and `inherit` there and nothing else, and `build` refuses a curve on that track by name | check the spelling; the field is dropped from the rebuilt key |
|
|
649
|
-
| `TIMELINE_KEY_RESTATED` | `LOSS` | 0 | an editor omits a channel that equals the parser's default, and the motion spec's `v` is positional, so the omission is written out at that default **in the spec** — and the line is printed only where the **file** will carry it too.
|
|
596
|
+
| `TIMELINE_KEY_RESTATED` | `LOSS` | 0 | an editor omits a channel that equals the parser's default, and the motion spec's `v` is positional, so the omission is written out at that default **in the spec** — and the line is printed only where the **file** will carry it too. The emitter leaves a channel out wherever it is the one the parser reads without it ([AUTHORING §10.6c](AUTHORING.md)), so on every key kind with a row the rebuild is the source's own text and nothing is said — measured on the twelve exports, 0 tracks print it. What still prints it is a timeline whose keys have no row (`shearx`, `alpha`, path `spacing`, …), and an `inherit` key: one that omits the mode is written as `normal` — the parser's default — and one spelled with a capital first letter (`NoScale`) as the editor's `noScale`, the same mode either way | nothing. The same values the runtime reads, spelled out — a larger file and the same animation |
|
|
650
597
|
| `TRANSFORM_KEY_FIELD` | `BLOCK` | 1 | as `IK_KEY_FIELD`, on a `transform` timeline | as `IK_KEY_FIELD` |
|
|
651
598
|
|
|
652
|
-
⚠️ **An attachment's `name` has no row, because nothing about it is lost
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
placeholder, and a contested placeholder was renamed `<skin>/<placeholder>` by the
|
|
657
|
-
compiler. The field exists now, `ingest` carries a stated `name` verbatim — equal to its
|
|
658
|
-
key or not, exactly as the source spells it — and writes none where the source states
|
|
659
|
-
none, and `compile` emits exactly what the spec states. The code went with the loss it
|
|
660
|
-
named; `IG86`–`IG89` in `bun run selftest` hold the carry, the rebuild's loaded names and
|
|
661
|
-
regions, and the two-skin shape with a link in each.
|
|
599
|
+
⚠️ **An attachment's `name` has no row, because nothing about it is lost.** `ingest`
|
|
600
|
+
carries a stated `name` verbatim — equal to its key or not, exactly as the source spells
|
|
601
|
+
it — and writes none where the source states none, and `compile` emits exactly what the
|
|
602
|
+
spec states.
|
|
662
603
|
|
|
663
604
|
### Transcription — the route that made a foreign skeleton yours
|
|
664
605
|
|
|
665
|
-
⚠️ **The rest of §2 is
|
|
666
|
-
|
|
606
|
+
⚠️ **The rest of §2 is transcription by hand, and the reading it produces is the
|
|
607
|
+
right one** — it is what an author does *after*
|
|
667
608
|
`ingest`, and it is what to fall back on for the constructs `ingest` reports as
|
|
668
609
|
blockers. The numbers come out of the JSON into a rig spec and a motion spec by hand,
|
|
669
610
|
and `build` emits a new skeleton from those.
|
|
670
611
|
|
|
671
612
|
What you get for it is that the file becomes editable by declaration — a pivot move
|
|
672
|
-
is two numbers in a spec (§4.1) and a new animation is an added block (§4.3),
|
|
673
|
-
|
|
613
|
+
is two numbers in a spec (§4.1) and a new animation is an added block (§4.3), rather
|
|
614
|
+
than a hand-edit of emitted JSON with nothing checking it. That is what
|
|
674
615
|
`ingest` hands you in one command; the sections below are how to read and change what
|
|
675
616
|
it hands you, and every rule in them applies to a spec `ingest` wrote.
|
|
676
617
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
it, and the only differing paths were the name and the `note`.
|
|
618
|
+
A file from another Spine generation is the one case where transcription is not the
|
|
619
|
+
first fallback: migrate it through the editor of its own generation first, as
|
|
620
|
+
[GENERATIONS.md](https://github.com/firejune/rigc/blob/main/docs/GENERATIONS.md) §4
|
|
621
|
+
states.
|
|
682
622
|
|
|
683
623
|
### 2.1 The workflow
|
|
684
624
|
|
|
@@ -693,7 +633,7 @@ it, and the only differing paths were the name and the `note`.
|
|
|
693
633
|
the loose `pendulum.png` beside it is exactly 745×212. ⚠️ Do **not** take it from
|
|
694
634
|
the atlas region bounds: that page carries `scale: 0.5`, so `pendulum`'s bounds read
|
|
695
635
|
`373, 106`. Two numbers for one part, and the attachment's is the one in world
|
|
696
|
-
units. `--atlas-in`
|
|
636
|
+
units. `--atlas-in` does that division for you (§2.3), but it can only land
|
|
697
637
|
within the pack's own rounding — by hand, off the attachment, it is exact.
|
|
698
638
|
3. **Transcribe the rig spec: header, bones, slots, skins.** Bones parents-first; the
|
|
699
639
|
`slots` array *is* the draw order (AUTHORING R4), so its order is data you are
|
|
@@ -705,25 +645,17 @@ it, and the only differing paths were the name and the `note`.
|
|
|
705
645
|
Such a slot still holds an index in the array, and everything below it is counted
|
|
706
646
|
from that index. Write it as `{ "name": …, "bone": … }` with no `attachment`, or
|
|
707
647
|
with `"attachment": null` if you prefer to say it out loud; either way it comes
|
|
708
|
-
back.
|
|
709
|
-
|
|
710
|
-
read 0.962 and 0.934 against the file they had been read from. If a
|
|
711
|
-
transcription's `slots.count` is under 1.000, this is the first thing to check.
|
|
648
|
+
back. If a transcription's `slots.count` is under 1.000, a missing empty slot is
|
|
649
|
+
the first thing to check.
|
|
712
650
|
|
|
713
|
-
⚠️ **No skeleton in `examples/` has one
|
|
714
|
-
this: all twelve exports fill every slot they declare from some skin. What they
|
|
651
|
+
⚠️ **No skeleton in `examples/` has one**: all twelve exports fill every slot they declare from some skin. What they
|
|
715
652
|
*do* carry is the neighbouring shape — a slot a skin DOES fill whose setup pose
|
|
716
653
|
shows nothing (34 of `spineboy-pro`'s 52 slots). Both are written the same way in
|
|
717
654
|
the file: `attachment` simply absent.
|
|
718
655
|
|
|
719
656
|
⚠️ **If the export's `skeleton` block carries no `x`/`y`/`width`/`height`, write
|
|
720
|
-
`"width": null, "height": null` and do not invent one**
|
|
721
|
-
|
|
722
|
-
common — the twelve exports in `examples/` all carry the four, and 37 of 37 exports
|
|
723
|
-
in one production corpus carry none of them — and until the `null` pair existed the
|
|
724
|
-
only two moves were a made-up stage or a file that could not be transcribed. The
|
|
725
|
-
made-up stage was the worse one: it is a number nothing in this toolchain could
|
|
726
|
-
contradict, so it survived every gate and every `diff` in silence. Now it does not —
|
|
657
|
+
`"width": null, "height": null` and do not invent one** — which is also what
|
|
658
|
+
`rigc ingest` writes for such a file. A made-up stage is a number no gate refuses;
|
|
727
659
|
`diff`'s header block reports `skeleton.stage_present` and `skeleton.stage_box`
|
|
728
660
|
against the source you are copying. Copy the four numbers when they are there;
|
|
729
661
|
state the absence when they are not.
|
|
@@ -747,11 +679,9 @@ the second is the one that costs a day:
|
|
|
747
679
|
all, and then you are debugging your own incomplete work rather than the format.
|
|
748
680
|
|
|
749
681
|
⚠️ **When a kind turns out not to be expressible, stop and say so — that is a finding,
|
|
750
|
-
not a blocker to route around.** [
|
|
751
|
-
per-skeleton survey of exactly this
|
|
752
|
-
|
|
753
|
-
that they were inexpressible. ⇒ Check the survey for your feature before concluding
|
|
754
|
-
either way, and if it is genuinely absent, the shape of the answer is *"this export
|
|
682
|
+
not a blocker to route around.** [SURVEY_2026-08-22.md](https://github.com/firejune/rigc/blob/main/docs/SURVEY_2026-08-22.md) is the
|
|
683
|
+
per-skeleton survey of exactly this. ⇒ Check the survey for your feature before
|
|
684
|
+
concluding either way, and if it is genuinely absent, the shape of the answer is *"this export
|
|
755
685
|
uses X, which the motion spec cannot say"* with a pointer — not a silent
|
|
756
686
|
approximation.
|
|
757
687
|
|
|
@@ -771,16 +701,15 @@ State the ambition in the right units, because three different things get called
|
|
|
771
701
|
| --- | --- | --- |
|
|
772
702
|
| **Structural agreement** — same bones, slots, attachments, timelines, key counts, curve kinds | ✅ yes, and `diff` measures it | the 3-timing transcription reads **1.000 on all 49 measures**. Aim here first |
|
|
773
703
|
| **Geometric agreement** — the same drawn pixels, allowing for the atlas | ✅ yes, and `check` measures it | see below |
|
|
774
|
-
| **Byte-identical JSON** | ✅ **for a rebuild, in canonical form, apart from `hash` and `spine`** — the pass line below | a **rebuild** of an editor export (`ingest`, then `build`) is the export. A **transcription** by hand is not held to it:
|
|
704
|
+
| **Byte-identical JSON** | ✅ **for a rebuild, in canonical form, apart from `hash` and `spine`** — the pass line below | a **rebuild** of an editor export (`ingest`, then `build`) is the export. A **transcription** by hand is not held to it: what differs there is what a person chose to write, not the emitter |
|
|
775
705
|
|
|
776
706
|
⭐ **The pass line of the byte round trip, stated once:** `build(ingest(x))` of an
|
|
777
707
|
editor export `x` is **identical to `x` in canonical form, apart from `hash` and
|
|
778
708
|
`spine`** — canonical form being `JSON.stringify(JSON.parse(text), null, 2)` of each
|
|
779
709
|
file, which keeps every number as parsed, every key in its order and every omitted key
|
|
780
710
|
omitted, and drops only whitespace and the exponent's spelling (an export setting, not
|
|
781
|
-
a property of the rig).
|
|
782
|
-
|
|
783
|
-
emitted, `build(ingest(x))` is byte-identical outright (`IG63`, `IG71`).
|
|
711
|
+
a property of the rig). It holds on all twelve exports under `examples/`; for a
|
|
712
|
+
skeleton **rigc** emitted, `build(ingest(x))` is byte-identical outright.
|
|
784
713
|
|
|
785
714
|
The two **declared exceptions** are the header keys the editor writes and the rig spec
|
|
786
715
|
has no field for, by design, and each has its finding:
|
|
@@ -790,50 +719,37 @@ has no field for, by design, and each has its finding:
|
|
|
790
719
|
| `hash` | `undefined` vs `"VFWbaK2UoCM"` | the editor's project hash — a value about a file rigc did not write, so a spec that carried it would be claiming an export it did not come from. `HEADER_BOOKKEEPING` |
|
|
791
720
|
| `spine` | `"4.3.13"` vs `"4.3.75-beta"` | the version of the runtime rigc links, stamped by design and re-checked by `A16`. `HEADER_REDERIVED` |
|
|
792
721
|
|
|
793
|
-
🔢 **
|
|
794
|
-
The **header's `audio`**
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
out what the parser reads the same way without it; the emitter now leaves out exactly
|
|
799
|
-
those ([AUTHORING §10.6c](AUTHORING.md)), and `IG81` counts 0 of either. One shape of
|
|
800
|
-
it is left and it is deliberate: a stage at the origin, which the editor writes as
|
|
722
|
+
🔢 **The rest of the header, and every omitted default, comes back as the export
|
|
723
|
+
spells it.** The **header's `audio`** is a stated value like `images`, and the rig
|
|
724
|
+
spec carries it ([AUTHORING §3.1](AUTHORING.md)). The emitter leaves out exactly the
|
|
725
|
+
keys the parser reads the same way without them ([AUTHORING §10.6c](AUTHORING.md)).
|
|
726
|
+
One shape is left and it is deliberate: a stage at the origin, which the editor writes as
|
|
801
727
|
`width`/`height` with no `x`/`y` and rigc spells whole. The 4.3 JSON reader has no
|
|
802
728
|
default for the origin — an absent one loads as `undefined`, a written `0` as `0` — so
|
|
803
729
|
it is not a key the parser reads the same way without it, and the row is not in the
|
|
804
|
-
table; `LOSS HEADER_ORIGIN` names it
|
|
805
|
-
([#622](https://github.com/firejune/rigc/issues/622)). No file in this corpus takes that
|
|
730
|
+
table; `LOSS HEADER_ORIGIN` names it. No file in the fetched corpus takes that
|
|
806
731
|
branch: all twelve declare an origin away from 0.
|
|
807
732
|
|
|
808
|
-
🔢 **
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
`
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
the values inside the keys**, which is why a moved value is invisible to it.
|
|
823
|
-
On rigc's own rigs byte identity covers both; on a foreign export it used to be
|
|
824
|
-
`check` (pixels) or nothing, depending on what you render.
|
|
825
|
-
|
|
826
|
-
⭐ **The values are gated now, and by a second measure rather than by `diff`**
|
|
827
|
-
([issue #615](https://github.com/firejune/rigc/issues/615)). Structure at 1.000 is
|
|
828
|
-
silent about the numbers inside it: a decompiler that halved every rotation, dropped
|
|
829
|
-
every bone's `length` or mirrored every vertex would read 1.000 on all 49 measures and
|
|
830
|
-
on every `(reported)` one. So the corpus round trip also compares **value by value**,
|
|
733
|
+
🔢 **Numbers are spelled as the editor spells them** — each as the shortest decimal
|
|
734
|
+
naming its float32 — and `ingest` carries every number as the double it parsed. The
|
|
735
|
+
whitespace of an export is not compared at all — it is an export setting,
|
|
736
|
+
pretty-printed in the examples and one line from the command line.
|
|
737
|
+
|
|
738
|
+
⇒ **`diff` at 1.000 is a statement about structure** — counts, names, parentage,
|
|
739
|
+
order, timeline kinds, key counts, curve kinds — and **not the values inside the
|
|
740
|
+
keys**, which is why a moved value is invisible to it.
|
|
741
|
+
|
|
742
|
+
⭐ **The values are measured by a second comparison rather than by `diff`.**
|
|
743
|
+
Structure at 1.000 is silent about the numbers inside it: a decompiler that halved
|
|
744
|
+
every rotation, dropped every bone's `length` or mirrored every vertex would read
|
|
745
|
+
1.000 on all 49 measures and on every `(reported)` one. So the example round trip is
|
|
746
|
+
also compared **value by value**,
|
|
831
747
|
with the format's defaults taken from the parser rather than from a table — both files
|
|
832
748
|
are read through `spine-core` and the parsed forms are compared path by path, under a
|
|
833
749
|
tolerance that is the sum of the 1e-6 grid rigc's closed-form models are evaluated on
|
|
834
|
-
— the one absolute grid it
|
|
835
|
-
storage. Nine measures
|
|
836
|
-
|
|
750
|
+
— the one absolute grid it emits on — and one float32 step of the runtime's
|
|
751
|
+
storage. Nine measures — here is the `6-arcs` export's, wrapped to fit this
|
|
752
|
+
page:
|
|
837
753
|
|
|
838
754
|
```
|
|
839
755
|
values: 9/9 measure(s) at 1.000 over 13865 compared value(s); skeleton 1.000 ·
|
|
@@ -841,8 +757,8 @@ bones 1.000 · slots 1.000 · attachments 1.000 · constraints 1.000 · events 1
|
|
|
841
757
|
key_times 1.000 · key_values 1.000 · curves 1.000
|
|
842
758
|
```
|
|
843
759
|
|
|
844
|
-
Over the
|
|
845
|
-
on all nine.
|
|
760
|
+
Over the twelve exports that is **193,927 values** compared, and all twelve read
|
|
761
|
+
1.000 on all nine.
|
|
846
762
|
|
|
847
763
|
`docs/BENCHMARK.md`'s *The nine value measures* is the full statement. What it still
|
|
848
764
|
does **not** cover, in the same breath:
|
|
@@ -852,7 +768,7 @@ does **not** cover, in the same breath:
|
|
|
852
768
|
| `spine` and `hash` | the rig spec has no field for either, by design, and `ingest` reports both as findings — the declared exceptions above |
|
|
853
769
|
| anything below one float32 step | the parser stores frames, curves and vertices in a `Float32Array`, so a difference it cannot represent is invisible to any reading of the parsed form |
|
|
854
770
|
| a Bezier's handles *as written* | the parser samples them into the curve, so a moved handle arrives as moved samples rather than as the handle it was |
|
|
855
|
-
| how the file is **spelled** | nothing, on an editor export, but the declared exceptions:
|
|
771
|
+
| how the file is **spelled** | nothing, on an editor export, but the declared exceptions: the rebuild spells every number as the export does, writes every object's keys in the export's order ([AUTHORING §10.6b](AUTHORING.md)) and leaves out every key the export leaves to the parser ([AUTHORING §10.6c](AUTHORING.md)). The value measure cannot see spelling — it compares what the parser loaded — and the pass line above is what can |
|
|
856
772
|
| how it **looks** | that is `check`, and `--texture-from` is how its figure is attributed |
|
|
857
773
|
|
|
858
774
|
The geometric row needs a real number, because a naive reading of `check` makes an
|
|
@@ -905,20 +821,12 @@ drawing. rigc states 746, the export says 745, and putting the pixel back by han
|
|
|
905
821
|
the same build to `x1.0000` / MAE **0.00** — which is how the residual is known to be
|
|
906
822
|
the rounding and nothing else.
|
|
907
823
|
|
|
908
|
-
⇒ **The routes
|
|
824
|
+
⇒ **The routes differ in what their MAE is MADE OF rather than in whether they are
|
|
909
825
|
right.** Loose art or `--pack` gives exact geometry through coarser texels; `--atlas-in`
|
|
910
826
|
gives the reference's texels through geometry good to half a texel. Read `above it`
|
|
911
827
|
before the MAE either way, and read the `in units` line first — it is the line that
|
|
912
828
|
catches a whole-figure scale error, and it is the only one that does.
|
|
913
829
|
|
|
914
|
-
> 🕰️ **This row used to read `pendulum 373x106`, `square 80x80`, `x0.8092`, MAE
|
|
915
|
-
> 124.97 — the pack's texel counts taken as world sizes, so every attachment came out
|
|
916
|
-
> at half size, green, with nothing in the report saying so.** Found while writing this
|
|
917
|
-
> page and fixed as [issue #267](https://github.com/firejune/rigc/issues/267). The
|
|
918
|
-
> control that isolated it is now a selftest: import a pack with **no** `scale:` line
|
|
919
|
-
> (rigc's own `--pack` output writes none) and the skeleton is byte-identical to the
|
|
920
|
-
> loose build.
|
|
921
|
-
|
|
922
830
|
### 2.4 The worked precedent, and what to take from it
|
|
923
831
|
|
|
924
832
|
Three transcriptions of official Spine exports live in this repository under
|
|
@@ -968,9 +876,9 @@ they are the whole set an ingest task realistically meets.
|
|
|
968
876
|
|
|
969
877
|
📊 **All twelve skeletons in the fetched corpus come back green** under the default
|
|
970
878
|
profile with the right atlas named. That is the baseline, and it is the honest headline:
|
|
971
|
-
**a correct editor export passes.**
|
|
972
|
-
|
|
973
|
-
|
|
879
|
+
**a correct editor export passes.** The two sections below are worth reading in full
|
|
880
|
+
because they mean opposite things — the first is a wrong input, and the second is data
|
|
881
|
+
that looks wrong and is not.
|
|
974
882
|
|
|
975
883
|
**`A00_ROUNDTRIP_PARSE` — the atlas does not cover the skeleton.**
|
|
976
884
|
|
|
@@ -992,7 +900,7 @@ named attachment, so you can tell "wrong atlas" from "the export is missing a re
|
|
|
992
900
|
by whether the missing names are a *coherent subset*. Fix by naming the right atlas —
|
|
993
901
|
`spineboy-ess.json` is green against `spineboy.atlas`.
|
|
994
902
|
|
|
995
|
-
**`A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` —
|
|
903
|
+
**`A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` — a trimmed deform run is not a defect.**
|
|
996
904
|
|
|
997
905
|
```bash
|
|
998
906
|
rigc validate examples/spineboy/export/spineboy-pro.json \
|
|
@@ -1007,30 +915,18 @@ rigc validate examples/spineboy/export/spineboy-pro.json \
|
|
|
1007
915
|
rigc: green
|
|
1008
916
|
```
|
|
1009
917
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
⭐ **The lesson survives the fix, and it is the reason to read this.** A validity rule
|
|
1023
|
-
stricter than the runtime does not look like a bug — it looks like a finding about
|
|
1024
|
-
somebody's file, and the honest reading of that message (*"your x values land on y
|
|
1025
|
-
slots"*) sends an agent to change correct data. Fixed as
|
|
1026
|
-
[issue #262](https://github.com/firejune/rigc/issues/262): the two parity clauses are
|
|
1027
|
-
gone, and the remaining A35 clauses — the run fitting inside the deform array, finite
|
|
1028
|
-
values, a non-empty key array, the attachment existing in the skin — are correct and
|
|
1029
|
-
catch real breakage. The over-long run in particular is still refused, and still the
|
|
1030
|
-
quietest defect the format has.
|
|
1031
|
-
|
|
1032
|
-
🚨 **The general lesson matters more than the specific bug.** A `FAIL` on foreign data
|
|
1033
|
-
has three possible meanings and the message alone does not separate them:
|
|
918
|
+
`hoverboard-board` is an unweighted mesh with 148 floats; the key carries `offset: 1`
|
|
919
|
+
and 147 values, covering `1..148` — the whole array minus a leading zero the editor
|
|
920
|
+
trimmed. A trim can land on a y component, so an odd offset or an odd-length run is what
|
|
921
|
+
a trimmed run looks like, and Spine's own parser copies the run in at the raw index with
|
|
922
|
+
no alignment requirement anywhere. A35 refuses what does break — a run that does not fit
|
|
923
|
+
inside the deform array, non-finite values, an empty key array, an attachment missing
|
|
924
|
+
from the skin. The over-long run in particular is refused, and it is the quietest
|
|
925
|
+
defect the format has.
|
|
926
|
+
|
|
927
|
+
🚨 **A validity rule stricter than the runtime does not look like a bug — it looks like
|
|
928
|
+
a finding about somebody's file.** A `FAIL` on foreign data has three possible meanings
|
|
929
|
+
and the message alone does not separate them:
|
|
1034
930
|
|
|
1035
931
|
1. **the data is broken** — fix the data;
|
|
1036
932
|
2. **the input was wrong** — wrong atlas, missing page, truncated file (§3.1, and
|
|
@@ -1042,8 +938,7 @@ wrong.** An ik or transform resting muted that no animation keys up moves nothin
|
|
|
1042
938
|
the file*, and `validate <file>` has no rig spec and so no way to be told a game turns
|
|
1043
939
|
it on from code — it refuses with three doors, the third being that statement. The
|
|
1044
940
|
rebuild route makes it for you: `ingest` declares the constraint consumer-driven and
|
|
1045
|
-
prints a `CONSUMER_DRIVEN_MIX` judgement (§2.0), and `build` then SKIPs it by name
|
|
1046
|
-
([#784](https://github.com/firejune/rigc/issues/784)).
|
|
941
|
+
prints a `CONSUMER_DRIVEN_MIX` judgement (§2.0), and `build` then SKIPs it by name.
|
|
1047
942
|
|
|
1048
943
|
⇒ Before changing anybody's export because rigc objected, check case 3: does the file
|
|
1049
944
|
**parse** (`A00`), **step without NaN** (`A10`), and **render**? If all three, the
|
|
@@ -1070,23 +965,11 @@ size-vs-PNG check is validity; one-part-per-page coverage, rotation and premulti
|
|
|
1070
965
|
alpha are policy. `A20`'s weight coherence is validity; requiring a mesh to be
|
|
1071
966
|
weighted at all is policy.
|
|
1072
967
|
|
|
1073
|
-
**`A08` was the third until [#574](https://github.com/firejune/rigc/issues/574).** Its
|
|
1074
|
-
policy clause required a skin entry's placeholder to be spelled exactly like the region
|
|
1075
|
-
it resolves to — a rule the renderer it was gated under never performed, since
|
|
1076
|
-
`spine-html` keys its images on the atlas region name reached through the attachment's
|
|
1077
|
-
`path` and reads no placeholder at all. Measured before retiring it: the clause fired
|
|
1078
|
-
on **0** attachments across the whole example corpus (no export in `examples/` carries
|
|
1079
|
-
a `path` field), and on every rigc rig whose placeholder is not its PNG's basename —
|
|
1080
|
-
which is what `path` exists for (AUTHORING §2, R5) and what a placeholder two named
|
|
1081
|
-
skins share is emitted as. So it was policy that only ever refused this compiler's own
|
|
1082
|
-
correct output.
|
|
1083
|
-
|
|
1084
968
|
⚠️ **`--profile spine-html` on foreign data produces a wall of failures that mean
|
|
1085
969
|
nothing about the file.** Same `spineboy-pro.json`, same atlas, one flag changed — the
|
|
1086
970
|
run ends `rigc: 13 assertion(s) failed`, and this is the tally with one real message
|
|
1087
|
-
per rule
|
|
1088
|
-
|
|
1089
|
-
follow-up 2 — so a packed atlas now passes both profiles and the wall is policy only):
|
|
971
|
+
per rule — `A06` takes a page that is one part covering it exactly *or a tiling of
|
|
972
|
+
regions*, so a packed atlas passes both profiles and the wall is policy only:
|
|
1090
973
|
|
|
1091
974
|
| Count | Rule | One of its messages |
|
|
1092
975
|
| --- | --- | --- |
|
|
@@ -1096,10 +979,7 @@ follow-up 2 — so a packed atlas now passes both profiles and the wall is polic
|
|
|
1096
979
|
|
|
1097
980
|
Every one of those is a correct statement about a correct file: a bone *does* key a
|
|
1098
981
|
mesh, a mesh *is* unweighted, a clipping attachment *is* present.
|
|
1099
|
-
|
|
1100
|
-
a correct statement — which is why it belonged in a different section from these.)
|
|
1101
|
-
And it is not a big-skeleton problem — `3-timing-and-spacing`, with two regions on one
|
|
1102
|
-
page, fails `A06` twice for the same reason. ⇒ **Do not run `spine-html` against
|
|
982
|
+
⇒ **Do not run `spine-html` against
|
|
1103
983
|
somebody's export unless they asked whether it satisfies this project's renderer
|
|
1104
984
|
policy**, which is a different question from whether their file is valid.
|
|
1105
985
|
|
|
@@ -1339,11 +1219,7 @@ frames rendered from the original.
|
|
|
1339
1219
|
⚠️ **Do not rename toward what a rule seems to want.** `A27`'s
|
|
1340
1220
|
region-name-matches-page-filename is `spine-html` policy (§3.3): under the default
|
|
1341
1221
|
profile it does not fire, and renaming somebody's attachments to satisfy a policy they
|
|
1342
|
-
never opted into is a change with no benefit to them.
|
|
1343
|
-
clause of the same kind until
|
|
1344
|
-
[#574](https://github.com/firejune/rigc/issues/574) retired it, and that one is the
|
|
1345
|
-
argument's own case study — the rename it seemed to want was one no renderer had ever
|
|
1346
|
-
asked for.
|
|
1222
|
+
never opted into is a change with no benefit to them.
|
|
1347
1223
|
|
|
1348
1224
|
### 4.3 Extending a foreign skeleton with a new animation
|
|
1349
1225
|
|
|
@@ -1432,45 +1308,31 @@ dependency *can* read it and rigc *does not*:
|
|
|
1432
1308
|
that already has the reader, not a parser to write. But it is not there, and nothing on
|
|
1433
1309
|
this page works on a `.skel` today. Re-export as JSON.
|
|
1434
1310
|
|
|
1435
|
-
✅ **A skeleton-to-spec decompiler exists: `rigc ingest` (§2.0)
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
| *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 |
|
|
1452
|
-
|
|
1453
|
-
⇒ **What survives is the stage, and one value is not "the things the spec format
|
|
1454
|
-
exists to make explicit".** The clause was not wrong that a decompiler meets an
|
|
1455
|
-
invention — it was wrong about *which*, and wrong that it is unavoidable: a refusal
|
|
1456
|
-
naming the field is what this repository does with a missing number everywhere else,
|
|
1457
|
-
and a stated absence is what `ingest` writes where the skeleton has none (§2.0, #714). ⚠️ Not to be confused with the *atlas*
|
|
1458
|
-
importer below, which is a different direction and also exists.
|
|
1459
|
-
|
|
1460
|
-
⚠️ **What `ingest` is still not.** It reads skeleton JSON and writes two spec files.
|
|
1461
|
-
It does not read a `.spine` project or a binary `.skel` (the two entries above stand
|
|
1462
|
-
unchanged), it does not read the atlas or the art, it does not **edit** a skeleton,
|
|
1311
|
+
✅ **A skeleton-to-spec decompiler exists: `rigc ingest` (§2.0), and it decides
|
|
1312
|
+
nothing the skeleton does not state.** A bone's setup transform — the pivot — is in
|
|
1313
|
+
the skeleton in full, so it is copied with no decision. `ingest` chooses **no**
|
|
1314
|
+
generator: a generator is a *model* (`src/rig.ts`: *"they encode a deformation model …
|
|
1315
|
+
and a model is not a table of numbers"*), the skeleton holds geometry, and geometry is
|
|
1316
|
+
what the rig spec's authored form takes — so a generator-built mesh comes back as
|
|
1317
|
+
authored geometry and rebuilds **byte-identical**. And it writes no `invariants`: an
|
|
1318
|
+
absent field makes an archetype assertion SKIP, never pass (§2.1 step 3), so a
|
|
1319
|
+
decompiled spec carries no intent and says so instead of certifying something nobody
|
|
1320
|
+
measured. Where the skeleton has no value — the stage — `ingest` writes a stated
|
|
1321
|
+
absence (§2.0). ⚠️ Not to be confused with the *atlas* importer below, which is a
|
|
1322
|
+
different direction and also exists.
|
|
1323
|
+
|
|
1324
|
+
⚠️ **What `ingest` is not.** It reads skeleton JSON and writes two spec files.
|
|
1325
|
+
It does not read a `.spine` project or a binary `.skel` (the two entries above), it
|
|
1326
|
+
does not read the atlas or the art, it does not **edit** a skeleton,
|
|
1463
1327
|
and it makes no claim about whether an agent could have *produced* the numbers it
|
|
1464
1328
|
copied — only that the spec can carry them and `build` reproduces the file from them.
|
|
1465
1329
|
|
|
1466
|
-
✅ **A packer and an importer both exist
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
drawing's size rather than the pack's — to within the pack's own rounding, which is
|
|
1473
|
-
§2.3's row and the whole of [issue #267](https://github.com/firejune/rigc/issues/267).
|
|
1330
|
+
✅ **A packer and an importer both exist, so do not report them as gaps.**
|
|
1331
|
+
`build --pack` writes shared pages losslessly and `build --atlas-in` resolves against a
|
|
1332
|
+
pack somebody else made (AUTHORING §0.1–§0.2). One-region-per-page is the **default**,
|
|
1333
|
+
not the only shape. `--atlas-in` applies the page's `scale:`, so an imported pack states
|
|
1334
|
+
the drawing's size rather than the pack's — to within the pack's own rounding, which is
|
|
1335
|
+
§2.3's row.
|
|
1474
1336
|
|
|
1475
1337
|
🚫 **No CLI unpacker, so `pose` needs loose art.** `pose` reads *loose part PNGs*
|
|
1476
1338
|
against one picture. A foreign export hands you a packed page instead, and pointing
|
|
@@ -1493,18 +1355,6 @@ rigc pose
|
|
|
1493
1355
|
found on a 7x3 anchor grid, step 4 at 32x reduction
|
|
1494
1356
|
```
|
|
1495
1357
|
|
|
1496
|
-
📏 **Instrument re-baseline, 2026-09-03 — [#306](https://github.com/firejune/rigc/issues/306).**
|
|
1497
|
-
Both blocks in this section were re-run and their residuals moved: the packed page
|
|
1498
|
-
reads **0.2083** where this page used to print 0.2078, `pendulum.png` **0.0425**
|
|
1499
|
-
where it printed 0.0410, and `square.png` **0.0331** where it printed 0.0330.
|
|
1500
|
-
`pose`'s objective now interpolates the frame premultiplied, so a tap across a
|
|
1501
|
-
silhouette no longer charges a part for the ground's colour — and the frames these
|
|
1502
|
-
commands read are rendered by `rigc render`, so #306's arithmetic and
|
|
1503
|
-
[#301](https://github.com/firejune/rigc/pull/301)'s renderer fix both moved them.
|
|
1504
|
-
⚠️ A residual from before that date and one from after are not the same
|
|
1505
|
-
measurement. The reading below does not depend on the digits: the point is that a
|
|
1506
|
-
packed page is placed *without* being refused, and it still is.
|
|
1507
|
-
|
|
1508
1358
|
⚠️ **`residual=0.2083` is *under* the default 0.25 refusal bar**, so nothing refused
|
|
1509
1359
|
it, and `PLACE` rather than `AMBIG` means nothing flagged it either. With the same frame
|
|
1510
1360
|
and the two real loose parts, the answer is what it should be:
|
|
@@ -1536,8 +1386,8 @@ something is drawn over them are readable through its own draw order and hierarc
|
|
|
1536
1386
|
|
|
1537
1387
|
📎 To be exact about what is missing: rigc *can* lift a region's drawing back off a
|
|
1538
1388
|
page — `extractRegion` does it, and the contour mesh generator uses it under
|
|
1539
|
-
`--atlas-in` — so what is absent is a **command**, not the capability.
|
|
1540
|
-
|
|
1389
|
+
`--atlas-in` — so what is absent is a **command**, not the capability. That includes a
|
|
1390
|
+
region the pack **turned** (`rotate: 90`, `180`, `270`, or the
|
|
1541
1391
|
older `rotate: true`), which a foreign pack routinely is and rigc's own never is: the
|
|
1542
1392
|
lift transcribes `MeshAttachment.computeUVs`, the one routine in spine-core that
|
|
1543
1393
|
states where a turned region's texels are, so what a generator measures does not
|