@rig-c/core 2.81.1 → 2.82.1
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 +46 -260
- package/docs/AUTHORING.md +112 -1
- package/docs/FACE.md +3 -1
- package/docs/INGEST.md +140 -1
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +4 -2
- package/src/cli/core_commands.ts +25 -1
- package/src/cli/shared.ts +4 -3
package/README.md
CHANGED
|
@@ -51,6 +51,22 @@ Everything in that table needs Bun and this package — `preview` and `vote` als
|
|
|
51
51
|
`@esotericsoftware/spine-core` installed beside it (see below) — and no clone, no
|
|
52
52
|
reference art, no art pipeline, no server.
|
|
53
53
|
|
|
54
|
+
### Depth-driven rigging
|
|
55
|
+
|
|
56
|
+
Explicit depth, which a drawing alone does not state, makes facial features
|
|
57
|
+
shift by different amounts during a small head turn: [`gallery/portrait`](https://github.com/rig-c/core/tree/main/gallery/portrait)
|
|
58
|
+
uses per-part depths, while [`gallery/look`](https://github.com/rig-c/core/tree/main/gallery/look)
|
|
59
|
+
samples a depth map at the mesh vertices.
|
|
60
|
+
With [`@rig-c/parts`' depth fields](https://github.com/rig-c/parts/blob/main/docs/AUTHORING.md#per-layer-depth-sheets-773),
|
|
61
|
+
See-through's per-layer sheets are bound explicitly with `near` and `zScale`,
|
|
62
|
+
then consumed by head-turn roles in `turn.roles`.
|
|
63
|
+
The [depth-yaw flow](docs/FACE.md#a-measured-alpha-aperture) shows how a turning
|
|
64
|
+
surface and its alpha clipping aperture move together.
|
|
65
|
+
The [value-driven turn](docs/FACE.md#the-turn-as-a-value-rather-than-a-time) is a
|
|
66
|
+
rig control; the consumer chooses when to move it, and
|
|
67
|
+
[getting-started](https://github.com/rig-c/getting-started) is the painting-to-rig
|
|
68
|
+
walkthrough.
|
|
69
|
+
|
|
54
70
|
### Alpha-derived clipping
|
|
55
71
|
|
|
56
72
|
Alpha-derived clipping takes a named source slot/skin/attachment plus explicit
|
|
@@ -59,7 +75,8 @@ Region, authored/resolved linked mesh and generated grid/contour depth sources u
|
|
|
59
75
|
ordinary weighted clipping, with the existing validation gate before emission.
|
|
60
76
|
See [AUTHORING: Alpha-derived clipping](docs/AUTHORING.md#alpha-derived-clipping)
|
|
61
77
|
for source-motion limits, fit reports and named refusals, and
|
|
62
|
-
[the face guide](docs/FACE.md#a-measured-alpha-aperture) for the depth-yaw flow
|
|
78
|
+
[the face guide](docs/FACE.md#a-measured-alpha-aperture) for the depth-yaw flow
|
|
79
|
+
used in depth-driven rigging.
|
|
63
80
|
|
|
64
81
|
## What rigc is, and what it is not
|
|
65
82
|
|
|
@@ -89,45 +106,7 @@ it cuts both ways:
|
|
|
89
106
|
|
|
90
107
|
### Licensing, stated plainly
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
the following is a restatement of Esoteric Software's terms, not a term of ours:
|
|
94
|
-
|
|
95
|
-
1. rigc's output **is Spine skeleton data**.
|
|
96
|
-
2. A product that plays it with **a Spine Runtime** — the official runtimes, the Web
|
|
97
|
-
Player — has integrated the Spine Runtimes. rigc also writes the model document
|
|
98
|
-
its own core poses, and a product may play that or anything else; what the terms
|
|
99
|
-
turn on is the integration.
|
|
100
|
-
3. Integrating a Spine Runtime into a product is permitted **under Section 2 of the
|
|
101
|
-
[Spine Editor License Agreement](https://esotericsoftware.com/spine-editor-license)**,
|
|
102
|
-
or otherwise on the condition that **each user of the product obtains their own
|
|
103
|
-
Spine editor licence** and the product carries the Runtimes licence and copyright
|
|
104
|
-
notice — the Runtimes licence's own two routes, in its words.
|
|
105
|
-
4. The published rigc **links no Spine runtime**. This repository does — `spine-core`
|
|
106
|
-
as a development dependency, for its own gate — and so does an install with the
|
|
107
|
-
runtime added beside it.
|
|
108
|
-
|
|
109
|
-
> **If a product integrates a Spine Runtime to play rigc's output, that integration
|
|
110
|
-
> is subject to the terms above** — rigc neither adds those terms nor removes them.
|
|
111
|
-
> Running the published rigc links no Spine runtime, and whether its output is then
|
|
112
|
-
> played by one, by rigc's own core, or by something else is the consumer's choice.
|
|
113
|
-
|
|
114
|
-
See [NOTICE.md](NOTICE.md) for the full notice.
|
|
115
|
-
|
|
116
|
-
📐 **What of Esoteric Software's is in this repository, and under what grant.** No
|
|
117
|
-
example asset is committed: `bun run fetch-examples` downloads the example projects
|
|
118
|
-
into a gitignored `examples/`. What *is* committed is `bench/reference/` — 1,293 PNG
|
|
119
|
-
frames **this project renders** from those examples' own exports, so a frame carries
|
|
120
|
-
those images' pixels and committing one **is** redistribution. Each example's own
|
|
121
|
-
`license.txt` permits exactly that, *"as long as they are accompanied by this license
|
|
122
|
-
file"*, and a verbatim copy of it sits at each example root here — all eight, written
|
|
123
|
-
there by the render script rather than left to memory. The same file's
|
|
124
|
-
**non-commercial** condition rides along with those images, and
|
|
125
|
-
[LICENSE](LICENSE) says so: rigc's MIT grant covers rigc's own code, documentation
|
|
126
|
-
and art, not this material. `7-anticipation` publishes no `license.txt` upstream, so
|
|
127
|
-
no such grant exists for it and its frames are never committed — they render only
|
|
128
|
-
into a gitignored directory, enforced by `git check-ignore`. Full reasoning:
|
|
129
|
-
[`bench/reference/README.md`](https://github.com/rig-c/core/blob/main/bench/reference/README.md)
|
|
130
|
-
(repository material, not in the npm package).
|
|
109
|
+
See [NOTICE.md](NOTICE.md) for licensing and third-party terms.
|
|
131
110
|
|
|
132
111
|
The problem rigc is aimed at is narrow. An agent asked to author a rig has no way
|
|
133
112
|
to tell whether it succeeded: Spine's JSON parser accepts a great deal of nonsense
|
|
@@ -467,39 +446,7 @@ origin and tip in world units and in pixels, so a position read off the picture
|
|
|
467
446
|
straight back into the spec. [AUTHORING §13](docs/AUTHORING.md#13-asking-the-picture-a-question--rigc-inspect)
|
|
468
447
|
maps each question to its view.
|
|
469
448
|
|
|
470
|
-
**`render`**
|
|
471
|
-
with a `contact.png` beside them — every frame of the shot as one labelled grid,
|
|
472
|
-
which is the picture to open first, because spacing is a comparison *across*
|
|
473
|
-
frames. It draws with rigc's own rasteriser (the one `check` measures with), so
|
|
474
|
-
it needs no browser and no network, and the `frames.json` it leaves beside the
|
|
475
|
-
directories makes the result a frame set like any other — the world box every
|
|
476
|
-
frame is a picture of. `--animation <name>` narrows it to one, `--fps` and
|
|
477
|
-
`--max` change the rate and the frame size. A set drawn at any rate but the
|
|
478
|
-
default is written to `<anim>@<fps>fps/` rather than `<anim>/`, so two rates sit
|
|
479
|
-
side by side: `--fps 24` writes `<anim>@24fps/`, and the default 12 writes
|
|
480
|
-
`<anim>/`. Each set's summary line ends with the directory it wrote.
|
|
481
|
-
|
|
482
|
-
> 🚨 **`--skin <name>` if your rig has more than one.** With no `--skin` no skin
|
|
483
|
-
> is set at all, so every slot resolves through the **default** skin alone — and
|
|
484
|
-
> a slot whose art lives only in a named skin draws *nothing*. That is not a
|
|
485
|
-
> quirk of `render`: `check` compares `render`'s frames, so a multi-skin rig
|
|
486
|
-
> checked with no skin compares blank against blank and reports a perfect
|
|
487
|
-
> `0.0000` about art nobody drew. `render --skin` records the name in
|
|
488
|
-
> `frames.json`, `check --skin` poses the candidate under it, and a candidate
|
|
489
|
-
> scored against frames rendered under a *different* skin is refused by name
|
|
490
|
-
> rather than measured.
|
|
491
|
-
|
|
492
|
-
**`preview`** writes a single self-contained `.html`: your skeleton, your atlas
|
|
493
|
-
and every page's PNG bytes are embedded in it as data URIs, and it plays them in
|
|
494
|
-
the **official [Spine Web Player](https://esotericsoftware.com/spine-player)**.
|
|
495
|
-
Double-click it, or attach it to a message — the file carries the whole artifact.
|
|
496
|
-
It is also the strongest interop statement in this repository: a rig that plays
|
|
497
|
-
there has been played by Esoteric Software's own runtime rather than by ours.
|
|
498
|
-
|
|
499
|
-
> ⚖️ The player itself is **referenced, not embedded** — the page loads it from
|
|
500
|
-
> unpkg, so the first open needs a network, and rigc redistributes nothing
|
|
501
|
-
> Esoteric Software owns (see [NOTICE.md](NOTICE.md)). Everything the player
|
|
502
|
-
> draws is inside your file.
|
|
449
|
+
**`render`** writes PNG frames and a contact sheet; **`preview`** writes a playable HTML file — see [AUTHORING §0](docs/AUTHORING.md#0-the-loop).
|
|
503
450
|
|
|
504
451
|
**7. Let someone choose.** Sooner or later you will have two builds that both pass
|
|
505
452
|
the gate and no instrument that can separate them. `vote` puts them in one page
|
|
@@ -523,7 +470,7 @@ that does not belong to it. See
|
|
|
523
470
|
field by field, the emission rules, every named failure mapped to the file that
|
|
524
471
|
has to change, and §8–§9 for reproducing a shot you were given as pictures. It
|
|
525
472
|
ships inside the npm package too, at
|
|
526
|
-
`node_modules
|
|
473
|
+
`node_modules/@rig-c/core/docs/AUTHORING.md`.
|
|
527
474
|
- `rigc explain --rig buoy.rig.json --motion buoy.motion.json --out spine` prints
|
|
528
475
|
the compiled rig as a table — every bone with its resolved parent, the slots in
|
|
529
476
|
draw order, every timeline key by key — and writes nothing. It is what to reach
|
|
@@ -631,203 +578,42 @@ material, like the gallery: a clone runs them.
|
|
|
631
578
|
|
|
632
579
|
## Commands
|
|
633
580
|
|
|
634
|
-
|
|
635
|
-
`rigc help <command>` prints the same page — and
|
|
636
|
-
[AUTHORING.md §0](docs/AUTHORING.md) is the same list with what each flag means, which
|
|
637
|
-
commands take it and what its default is.
|
|
581
|
+
`rigc help <command>` prints the command’s flags; [AUTHORING §0](docs/AUTHORING.md#0-the-loop) is the CLI reference.
|
|
638
582
|
|
|
639
583
|
| Command | Does |
|
|
640
584
|
| --- | --- |
|
|
641
|
-
| `build
|
|
642
|
-
| `
|
|
643
|
-
| `
|
|
644
|
-
| `
|
|
645
|
-
| `
|
|
646
|
-
| `
|
|
647
|
-
| `
|
|
648
|
-
| `
|
|
649
|
-
| `
|
|
650
|
-
| `
|
|
651
|
-
| `
|
|
652
|
-
| `
|
|
653
|
-
| `
|
|
654
|
-
| `
|
|
655
|
-
| `
|
|
656
|
-
| `
|
|
657
|
-
| `
|
|
658
|
-
| `
|
|
659
|
-
| `guide <RULE>` / `guide §<n>[.<m>[.<k>]] [--all]` | the guide's row for the rule a failure line names — every `FAIL` line, `vote --record` refusal, `ingest` finding and coded compile error ends with ` (see rigc guide <RULE>)`; only legacy uncoded library errors point at `§5.1` — or a section's heading, own prose and subsection index, read from the installed `docs/AUTHORING.md` when it runs. `--all` includes the subsection bodies; a leaf prints its full text either way. Sizes describe full ledger spans. `--all` with a rule is refused, exit 2; an unknown name is refused with the nearest real ones, exit 1 |
|
|
660
|
-
| `skills install [--dir …] [--copy]` | links every agent skill the package ships into `.agents/skills` (or `--dir`) as relative symlinks, or copies them with `--copy` — where Codex, Gemini CLI and Antigravity look. A second run has nothing to do; an entry already there that is not this command's is refused by name and nothing is written |
|
|
661
|
-
| `review <dir> […]` | the human review of a build, handed to the `rig-review` bin of `@rig-c/review` installed beside the package; rigc does not depend on it and never installs it. `rigc review <dir> [--reference <video>] [--animation <name>] [--compare <dir>]` is `rig-review open <dir> …` with those flags 1:1 and prints the loopback URL to give a person; a first argument that is one of review's own words (`open`, `check`, `feedback`, `control`, `strip`, `compare`) or starts with `-` (`--help`, `--`) is handed over untouched. stdout is review's byte for byte, stderr review's plus the launcher's own refusals, the exit code review's. Refused by name, exit 2, before the bin starts: `REVIEW_OUTPUT_MISSING <path>` (the target is not a directory); `REVIEW_NOT_INSTALLED` with two lines, `bun add -d @rig-c/review` and `bunx @rig-c/review open <dir> …` carrying the flags given — the same on a terminal and off, with nothing installed or written; `REVIEW_BUN_TOO_OLD: Bun <found>, @rig-c/review needs <required>`, the floor read off the installed package's `engines.bun`; and an empty argument (`""`) under a Bun that drops one (Bun 1.2.0, measured by a probe), `rigc review: argument N is empty, and Bun <version>, the Bun running this, drops an empty argument when it starts a process — …`. docs/AUTHORING.md lists every line |
|
|
662
|
-
|
|
663
|
-
`diff`, `bonedist`, `check` and `bench` measure against something you were given; the
|
|
664
|
-
first three work on any reference you have, and `bench` is a repository workflow that needs a clone
|
|
665
|
-
and `bun run fetch-examples`. The reasoning behind them is in
|
|
666
|
-
[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md).
|
|
667
|
-
|
|
668
|
-
`build` and `validate` both default to `--profile spine` — the 36 validity rules, which
|
|
669
|
-
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile rig-play`
|
|
670
|
-
adds all 50: the other 14 are one renderer's policy and one canvas budget's, and they
|
|
671
|
-
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
672
|
-
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
673
|
-
yourself: author under `--profile rig-play` and read the extra 14 as findings, and
|
|
674
|
-
gate the release under `--profile spine`.** A `deform` key that folds a mesh inside
|
|
675
|
-
out is written out under the default and refused by name under `rig-play`, which
|
|
676
|
-
is the shape of what that split buys you. A report always names
|
|
677
|
-
the profile it ran and lists what that profile left out.
|
|
678
|
-
|
|
679
|
-
Several cuts can also be registered in a `cuts.json` and built by name
|
|
680
|
-
(`build --cut my_cut --cuts path/to/cuts.json`); every path in that table resolves
|
|
681
|
-
relative to the `cuts.json` file itself, so the table lives with the project that owns
|
|
682
|
-
the art. Its shape is under
|
|
683
|
-
[Usage](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md#usage).
|
|
585
|
+
| `build` | Compile, gate and write the rig. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
586
|
+
| `validate` | Re-gate artifacts already on disk. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
587
|
+
| `repack` | Repack a build from its output. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
588
|
+
| `ingest` | Read a skeleton back into specs. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
589
|
+
| `split` | Separate disconnected mesh pieces. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
590
|
+
| `explain` | Print the compiled rig as a table. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
591
|
+
| `render` | Write PNG frames and a contact sheet. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
592
|
+
| `preview` | Write a playable HTML preview. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
593
|
+
| `vote` | Make a ballot and record its answer. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
594
|
+
| `pose` | Read part placements from a picture. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
595
|
+
| `chainfit` | Read occluded parts through a candidate rig. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
596
|
+
| `diff` | Compare two skeletons structurally. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
597
|
+
| `bonedist` | Measure per-bone world-transform distance. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
598
|
+
| `check` | Compare a candidate against reference pictures. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
599
|
+
| `bench` | Run a rung of the benchmark ladder. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
600
|
+
| `guide` | Read a named failure or guide section. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
601
|
+
| `skills install` | Install the packaged agent skills. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
602
|
+
| `review` | Hand a build to the review package. [Reference](docs/AUTHORING.md#0-the-loop). |
|
|
684
603
|
|
|
685
604
|
### Starting from a skeleton you already have
|
|
686
605
|
|
|
687
|
-
|
|
688
|
-
rebuild it. It is the only command that runs against `build`'s direction, and the
|
|
689
|
-
only one whose contract is an equality rather than a rulebook:
|
|
690
|
-
|
|
691
|
-
```bash
|
|
692
|
-
rigc ingest hero.json --out specs/ --images parts/
|
|
693
|
-
rigc build --rig specs/rig.json --motion specs/motion.json --out build/
|
|
694
|
-
rigc diff build/skeleton.json hero.json
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
`--images parts/` is what makes the second line carry no flag: it writes the spec's
|
|
698
|
-
own `images` directory, spelled from `--out`, so the specs are self-contained from
|
|
699
|
-
there on. Leave it off and the `image` names resolve against `specs/` itself, which
|
|
700
|
-
holds no art — every rebuild then has to repeat `build --images parts/`.
|
|
701
|
-
|
|
702
|
-
**`build(ingest(x))` is `x`.** Over the eleven rigs this repository builds — the seven
|
|
703
|
-
gallery examples, the three generated probes and a coverage probe written for the
|
|
704
|
-
purpose — the rebuilt `skeleton.json` is byte for byte the file the decompiler read.
|
|
705
|
-
The atlas is held to a weaker
|
|
706
|
-
claim on purpose, and the weakening is measured rather than assumed: it comes back
|
|
707
|
-
equal as a **multiset of region blocks**, because the order the pages are collected in
|
|
708
|
-
is in no field of the skeleton.
|
|
709
|
-
|
|
710
|
-
**And over twelve skeletons nobody here wrote.** Every editor export in the fetched
|
|
711
|
-
example corpus, ingested, rebuilt through the pack beside it and `diff`ed against the
|
|
712
|
-
source, comes back **12 of 12, no blockers, 1.000 on every measure the report
|
|
713
|
-
carries** — and each rebuild is its export's own text in canonical form, apart from
|
|
714
|
-
the header's `hash` and `spine`: the editor's project hash and the runtime version
|
|
715
|
-
rigc stamps, the two keys the rig spec has no field for by design (12 of 12) — and
|
|
716
|
-
the header's box, which `build` computes rather than carries (issue #907): the rebuild
|
|
717
|
-
writes spine-core's `getBounds` over the export on the header's 1e-6 grid at float32, equal on all twelve,
|
|
718
|
-
where the editor wrote its own arithmetic, up to 0.0071 units away.
|
|
719
|
-
[INGEST.md §2.3](docs/INGEST.md) states that pass line and why those two are the
|
|
720
|
-
exceptions.
|
|
721
|
-
|
|
722
|
-
**What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
|
|
723
|
-
`.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
|
|
724
|
-
the file are **findings** with codes rather than plausible values: a construct the
|
|
725
|
-
spec format cannot hold (`point`, a `sequence` block, an unknown field
|
|
726
|
-
on a bone, slot or constraint) is a blocker, the command exits non-zero, and both
|
|
727
|
-
specs are still written — a spec plus a list of what is missing from it beats no spec.
|
|
728
|
-
|
|
729
|
-
⚠️ **Two values are not in a skeleton at all.**
|
|
730
|
-
|
|
731
|
-
- **The stage.** `skeleton.width`/`height`. A skeleton that declares none is carried as
|
|
732
|
-
declaring none — the rig spec states `"width": null, "height": null` and the rebuild
|
|
733
|
-
carries no box either, byte for byte — and `--stage x,y,w,h` is how a
|
|
734
|
-
caller *adds* one, recorded as a judgement. It is not derivable — posing the rig gives
|
|
735
|
-
the *animated* extent, which is a different number from the setup box. ⛔ **And `--stage` beside a box the file already states is refused
|
|
736
|
-
too**, for the opposite reason: two sources for one value, where the file is the record
|
|
737
|
-
of what was measured. **All twelve exports in the example corpus carry a stage** and
|
|
738
|
-
none of them needs the flag. It is the value that costs least to get wrong, because
|
|
739
|
-
`diff` reports the box and gates nothing on it. 🔁 The box an export's header carries
|
|
740
|
-
is its setup-pose bounding box, and it becomes the rebuild's *stage*; the rebuild's own
|
|
741
|
-
header is computed again, because since issue #907 `build` writes the setup-pose
|
|
742
|
-
bounding box there — never the stage — held to spine-core's `getBounds`. 📦 A rig
|
|
743
|
-
that has to carry its stage in the Spine files asks for it as a bounding box
|
|
744
|
-
(`skeleton.stageBox`, written by `build` from the stage and held by
|
|
745
|
-
`A50_STAGE_BOX_IS_THE_STAGE`), and `ingest --stage-box <slot>` reads that box back as
|
|
746
|
-
the rebuild's stage — [AUTHORING §3.1](docs/AUTHORING.md) has the field and the
|
|
747
|
-
paragraph for a consumer of the files.
|
|
748
|
-
- **An animation's duration.** The format has no such field. The largest key time is
|
|
749
|
-
the only derivable answer and it is what a runtime plays to; it is wrong for an
|
|
750
|
-
animation that holds its last pose past its last key, so it is recorded as a finding
|
|
751
|
-
on every animation rather than chosen quietly.
|
|
752
|
-
|
|
753
|
-
Both specs carry a `note` that `ingest` writes itself, saying the file is decompiled
|
|
754
|
-
and naming the skeleton it came from — because a decompiled spec is indistinguishable
|
|
755
|
-
from an authored one by inspection, every gate here calls it green (it *is* green),
|
|
756
|
-
and no gate can catch a missing note.
|
|
757
|
-
|
|
758
|
-
[docs/INGEST.md](docs/INGEST.md) is the whole page on working from a file you were
|
|
759
|
-
handed; [docs/AUTHORING.md](docs/AUTHORING.md) §0.3 is the loop.
|
|
606
|
+
See [INGEST: Starting from a skeleton you already have](docs/INGEST.md#starting-from-a-skeleton-you-already-have) for the ingest-and-rebuild loop.
|
|
760
607
|
|
|
761
608
|
### The editor round trip — for a licence holder, never in CI
|
|
762
609
|
|
|
763
|
-
|
|
764
|
-
build → **import into the Spine editor** → export back to JSON → gate, `diff`,
|
|
765
|
-
`render` and `check` the export against the build it came from.
|
|
766
|
-
|
|
767
|
-
```
|
|
768
|
-
bun cli.ts build --rig … --motion … --out build/ --copy-images
|
|
769
|
-
bun tools/editor_roundtrip.ts --build build/ --editor /Applications/Spine.app/Contents/MacOS/Spine
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
Of the editor's `--version` it records one line, the `Spine <x.y.z> …` line that
|
|
773
|
-
carries the version, and nothing else that command prints — the rest of it
|
|
774
|
-
names the licence holder — or `editor version: not found in --version output`
|
|
775
|
-
where no such line is there. That call carries the same `-u` as the import and
|
|
776
|
-
the export, so a trip pinned with `--editor-version <v>` names the editor it
|
|
777
|
-
pinned and says so on the line (`(read from --version under -u <v>, …)`); an
|
|
778
|
-
unpinned trip names the launcher's default. Once the export exists the version
|
|
779
|
-
is held against the export's own `skeleton.spine`, and a disagreement is a FAIL
|
|
780
|
-
naming both values — the line would otherwise name an editor the trip did not
|
|
781
|
-
run on. It prints the import and export exit codes, the
|
|
782
|
-
validator's verdict on the export, every `diff` measure that moved, `check`'s mean MAE and worst drift per
|
|
783
|
-
animation **for each skin the build and the export both declare** — one
|
|
784
|
-
render-and-check block per skin, with a per-skin roll-up under them, because a
|
|
785
|
-
rig's contested art lives in its named skins and a single un-skinned check draws
|
|
786
|
-
none of it; a skin only one side declares is a FAIL naming it as **lost** (or
|
|
787
|
-
**added**) **by the export**, with `diff`'s `attachments.skins` beside it, and is
|
|
788
|
-
rendered on neither side — and a
|
|
789
|
-
field-by-field list of what the editor rewrote. Every step quotes what its child
|
|
790
|
-
said when that child did not do what it was for, the renderers included — the
|
|
791
|
-
editor's words in full and in order, except its `Licensed to:` line, which names
|
|
792
|
-
the licence holder and is replaced by a line saying it was withheld; a skin
|
|
793
|
-
**neither** side can draw — a hit-box rig, say — is a **SKIP** naming that, not a
|
|
794
|
-
red, because `check` had nothing to compare and `diff` and `validate` have
|
|
795
|
-
already measured the rig. One side drawing where the other does not is the
|
|
796
|
-
divergence the trip exists to find and stays a failure. A human edit made in the
|
|
797
|
-
editor survives the trip back.
|
|
798
|
-
|
|
799
|
-
🔒 **It requires a licensed Spine editor on the machine, by construction**, and
|
|
800
|
-
drives only the [documented command line](https://esotericsoftware.com/spine-command-line-interface)
|
|
801
|
-
— never the UI, and it produces nothing the editor did not produce. With no
|
|
802
|
-
editor present it refuses by name and exits non-zero, and so does the **trial**:
|
|
803
|
-
the trial cannot save projects or export animation data, so the refusal names
|
|
804
|
-
what it found — the executable, the bundle, the `CFBundleName` that bundle
|
|
805
|
-
declares, or the banner the binary prints about itself — rather than starting it
|
|
806
|
-
and failing downstream. Both refusals point at `--exported <file>`, which
|
|
807
|
-
measures an export the editor already made and is the half of this tool that
|
|
808
|
-
needs no editor at all. A build whose `skeleton.json` is missing, is not JSON, or
|
|
809
|
-
holds JSON that is not one object is refused by name before the editor starts,
|
|
810
|
-
and so is a build with no atlas or an atlas that is a directory, or an
|
|
811
|
-
`--exported` file that is not one JSON object.
|
|
812
|
-
|
|
813
|
-
⛔ **Run the round trip by hand, on a machine that has the editor.** CI has no
|
|
814
|
-
editor, so no automated check runs it. The by-hand measurement before a landing
|
|
815
|
-
trips the production rigs and one **parts-built** rig, the exam's task-01
|
|
816
|
-
painting through `@rig-c/parts`. The production rigs were authored in the editor,
|
|
817
|
-
so they are already in the editor's own shape. A refusal that only a generated
|
|
818
|
-
rig carries cannot show up on them. Issue #1407 was such a refusal: a green build
|
|
819
|
-
whose meshes listed their bones in two orders, which the editor would not import.
|
|
820
|
-
|
|
821
|
-
⚠️ Build with `--copy-images`. An ordinary build's atlas names its pages by a
|
|
822
|
-
relative path back to the art directory, and the round trip copies that atlas to
|
|
823
|
-
a directory at another depth — the tool refuses such a build by name rather than
|
|
824
|
-
letting `A17` blame the editor for the harness's own doing.
|
|
610
|
+
See [INGEST: The editor round trip](docs/INGEST.md#the-editor-round-trip--for-a-licence-holder-never-in-ci) for the licensed-editor workflow.
|
|
825
611
|
|
|
826
612
|
## Documentation
|
|
827
613
|
|
|
828
614
|
| Document | For |
|
|
829
615
|
| --- | --- |
|
|
830
|
-
| 📘 **[docs/AUTHORING.md](docs/AUTHORING.md)** | **the format guide, and the one to read before writing a spec.** Both input files field by field with a complete minimal example each, every field with its Spine meaning, the rules that decide what is emitted, the build → read the report → fix → repeat loop, the map from every named failure to the file that has to change, and the features rigc refuses by name so you do not spend a loop discovering them. It travels **inside the npm package**, at `node_modules
|
|
616
|
+
| 📘 **[docs/AUTHORING.md](docs/AUTHORING.md)** | **the format guide, and the one to read before writing a spec.** Both input files field by field with a complete minimal example each, every field with its Spine meaning, the rules that decide what is emitted, the build → read the report → fix → repeat loop, the map from every named failure to the file that has to change, and the features rigc refuses by name so you do not spend a loop discovering them. It travels **inside the npm package**, at `node_modules/@rig-c/core/docs/AUTHORING.md` |
|
|
831
617
|
| 🦴 **[docs/RIGGING.md](docs/RIGGING.md)** | **authoring the hierarchy.** Where a bone goes and why the art is pushed out on an offset, why a pivot in the wrong place looks like a search failure and what identifies one, moving a pivot and the child row that gets forgotten, gauges, siblings-not-a-chain, what a chain can reach and how many links it needs, why a local key is not a world key, duplicate art at mirrored pivots, and constraints as structure. Every section is a stumble the run records hold more than once, ranked by how often. Ships in the package too |
|
|
832
618
|
| 🎞️ **[docs/MOTION.md](docs/MOTION.md)** | **the key-pose recipe.** How to get two poses, what a pair of poses does and does not fix, the in-betweening rules and where each comes from, and how to spread candidates so a ballot informs. Ships in the package too |
|
|
833
619
|
| 🙂 **[docs/FACE.md](docs/FACE.md)** | **authoring a face.** A blink, a gaze and a 2.5D head turn on plain Spine data: the one line of yaw arithmetic every number in a turn comes from, depth as the parameter you are actually authoring, where to put a grid's columns and the angle at which any grid folds, what foreshortens and what does not, channel allocation before the first key, and the three cliffs with their angles. Also the deform audit gap, demonstrated — a folded mesh gates green — and the differential check that works today |
|
|
@@ -836,12 +622,13 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
836
622
|
| 🔬 **[docs/SPEC_COVERAGE.md](docs/SPEC_COVERAGE.md)** | **the Spine 4.3 format, field by field.** Every key the 4.3 JSON parser and atlas reader take, what each defaults to, and the parser line it comes from — the rows rigc's refusals cite by part number. Ships in the package too |
|
|
837
623
|
| 🎓 **[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the 50 named assertions with their profiles, and the selftest that has watched every one of them fire. Repository material — it is not in the npm package |
|
|
838
624
|
| 📋 [LADDER.md](https://github.com/rig-c/core/blob/main/docs/LADDER.md) · [GATE.md](https://github.com/rig-c/core/blob/main/docs/GATE.md) · [PILOT.md](https://github.com/rig-c/core/blob/main/docs/PILOT.md) | the live rung ledger, the clause statements a candidate is graded against, and how to run an agent through the ladder and score what comes back |
|
|
839
|
-
| 🔬 [SURVEY_2026-08-22.md](https://github.com/rig-c/core/blob/main/docs/SURVEY_2026-08-22.md) | the survey the ladder was climbed by: rigc and the official examples as measured on 2026-08-22, and the gap list ordered by rung. A dated record, not kept current. Repository material |
|
|
840
625
|
| 🧬 [GENERATIONS.md](https://github.com/rig-c/core/blob/main/docs/GENERATIONS.md) | **Spine data from another generation.** Why a 3.8–4.2 file read as 4.3 fails in silence, the policy that follows (detect from `skeleton.spine`, never guess, play on the matching runtime), what `A16` and `ingest` do with such a file, and the editor as the migration path. Repository material |
|
|
841
626
|
| 📓 [CASES.md](https://github.com/rig-c/core/blob/main/docs/CASES.md) | **converting a Live2D model to Spine, and verifying the conversion.** The method — the source's own player headless, parts from its texture along the drawables, motion baked as deform keys, reference frames first, `check` — run once end to end on two Live2D sample models with no editor and no converter, and verified against the source's renderer and Spine's own runtime: the three instruments, the two rigc defects verifying it led to and v1.2.1 fixed, and where the method stops. Figures only — the models' licence keeps every asset out. Repository material |
|
|
842
627
|
| 🗺️ [ROADMAP.md](https://github.com/rig-c/core/blob/main/ROADMAP.md) | where this is going, and where it has been. What 1.0 had to mean before the number was claimed, and what it was claimed on — conditions rather than a feature list, because direction here comes from what users hit |
|
|
843
628
|
| 📐 [CLAUDE.md](https://github.com/rig-c/core/blob/main/CLAUDE.md) | **the doctrine** — why the validator's messages are the product, why nothing reaches disk before green, why no number is ever invented, and what a change has to keep. [CONTRIBUTING.md](https://github.com/rig-c/core/blob/main/CONTRIBUTING.md) calls it worth ten minutes before a first patch. Repository material |
|
|
844
629
|
|
|
630
|
+
[Dated survey](https://github.com/rig-c/core/blob/main/docs/SURVEY_2026-08-22.md).
|
|
631
|
+
|
|
845
632
|
## Why you can trust the output
|
|
846
633
|
|
|
847
634
|
rigc is measured against **Spine's own official example projects** — the
|
|
@@ -897,5 +684,4 @@ has to clear before it lands. Releases are cut by release-please —
|
|
|
897
684
|
|
|
898
685
|
## Licence
|
|
899
686
|
|
|
900
|
-
|
|
901
|
-
requirement that this project inherits, are in [NOTICE.md](NOTICE.md).
|
|
687
|
+
See [NOTICE.md](NOTICE.md) for licensing and third-party terms.
|
package/docs/AUTHORING.md
CHANGED
|
@@ -20,7 +20,7 @@ and has no way to answer. This is not a caveat: a build with **every easing in t
|
|
|
20
20
|
file reversed** gates green. If you were given pictures, `check` (**§9**) is the half of the loop
|
|
21
21
|
that can see that, and a run that skips it has verified nothing about the motion.
|
|
22
22
|
|
|
23
|
-
- Formats and CLI reference: [
|
|
23
|
+
- Formats and CLI reference: this guide, starting with [§0](#0-the-loop)
|
|
24
24
|
- The rig spec's own source-level documentation: [`src/rig.ts`](../src/rig.ts)
|
|
25
25
|
- The motion spec and emitted shapes: [`src/types.ts`](../src/types.ts)
|
|
26
26
|
- What the motion spec's own parser proves, and which refusals it deliberately
|
|
@@ -113,6 +113,97 @@ can see where Spine stops.
|
|
|
113
113
|
|
|
114
114
|
## 0. The loop
|
|
115
115
|
|
|
116
|
+
Every command takes its paths explicitly. `rigc <command> --help` prints its flags —
|
|
117
|
+
`rigc help <command>` prints the same page — and
|
|
118
|
+
[AUTHORING.md §0](../docs/AUTHORING.md) is the same list with what each flag means, which
|
|
119
|
+
commands take it and what its default is.
|
|
120
|
+
|
|
121
|
+
| Command | Does |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `build --rig … --motion … --out …` | compiles, gates, and **writes only if the gate is green**. `--images <dir>` says where the rig spec's `image` names resolve, `--manifest` adds measured art, and `--copy-images` copies every page PNG into `--out` so the directory is self-contained, with `skeleton.images` pointing at it so the editor's import finds the parts |
|
|
124
|
+
| `build … --pack` | the same build with every part arranged onto **shared** atlas pages, written into `--out` — losslessly, and gated a second time as the pair that ships. `--page-size`, `--padding`, `--page-edges` and `--pack-shape` tune it — `--pack-shape polygon` lets a neighbour sit inside a mesh's rectangle where its hull is not, and with `--page-edges free` costs 12–56 times `rect`'s pack (3–11 s against 0.2–0.3 s on 90–144 parts over two to four pages; past 5 s from about 60 parts, a second or so below 45 on one page — AUTHORING §0.1) |
|
|
125
|
+
| `build … --atlas-in <file.atlas>` | the same build with every part resolved to a **region of an existing pack** instead of a loose PNG; a name the atlas lacks, a size the spec disagrees with or a rectangle off its page is refused by name |
|
|
126
|
+
| `validate <dir>` | re-gates artifacts already on disk |
|
|
127
|
+
| `repack <dir> --out <dir>` | a packed build **repacked from its own output** — `skeleton.json`, `skeleton.atlas` and the pages, with no parts kept — under the packing flags `build --pack` takes, so a packer improvement reaches a build whose parts are gone. Every region is lifted off its page, the skeleton read back with `ingest`, and `build --pack` run over the lifted parts with the same gate; it writes only after every region is shown pixel-identical to the input's, the skeleton byte-identical and the gate green, and refuses by name an atlas it cannot lift exactly. A skeleton the rebuild does not reproduce byte for byte — a build written before 2.2.0, whose header box was its stage — is refused naming every differing path, and written, every path printed, only under `--accept-skeleton-differences`. `--stage-box <slot>` reads the stage from the box a build carries for it (`skeleton.stageBox`), the one way the three files alone carry the stage exactly ([AUTHORING §0.4](../docs/AUTHORING.md)) |
|
|
128
|
+
| `repack <dir> --out <dir> --keep-pages` | **`skeleton.model.json` alone, for the pages already in place** — for a player that reads the document beside an atlas and pages that already shipped. The same read and stage rules, then `build --atlas-in` over the input atlas in place of the lift and the pack; it writes the document, and nothing else, only after its `pages` are shown to state the atlas as it stands, the rebuilt skeleton byte-identical to the one on disk (so `spine.sha256` names it — no acceptance flag here) and the gate green. `--out` is usually the build's own directory and must not already hold a `skeleton.model.json`; a page or region line the document has no field for is refused by name ([AUTHORING §0.4](../docs/AUTHORING.md)) |
|
|
129
|
+
| `ingest <skeleton.json> --out <dir>` | `build` run backwards: reads a Spine 4.3 skeleton and writes the rig spec and motion spec that **rebuild it**, plus a findings report naming everything it could not carry. a skeleton that declares no stage is carried as declaring none, `--stage x,y,w,h` adds a box to one — and is refused, rather than ignored, beside one that declares a box — and `--images <dir>` writes the spec's own images directory — the opposite direction from `build --images`, which overrides it — so the rebuild carries no flag at all |
|
|
130
|
+
| `split --rig … --motion … --slot <name> --out <dir>` | the answer to a mesh refused because its triangles are **several pieces that share no vertex** — two legs after the triangles bridging them were deleted. It reads the two specs and writes them back into an absent or empty `--out` with one mesh per piece: piece 1 stays in the slot, piece k gets a new slot `<slot>_<k>` after it, each with the original's uvs and weights unchanged, its triangles renumbered outline first where the hull rule needs it, and every deform key re-sliced to the new indices — plus `split.json`, where every original vertex and triangle went. One piece is said so and nothing is written; what it cannot carry is refused by name ([AUTHORING §3.4.11](../docs/AUTHORING.md)) |
|
|
131
|
+
| `explain --rig … --motion …` | the compiled rig as a table — every bone with its resolved parent, the slots in draw order, every timeline key by key. Writes nothing. What to reach for when a rig compiles and still looks wrong |
|
|
132
|
+
| `render --candidate <dir>` | PNG frames plus a contact sheet, in `render/`, one directory per set: `<anim>@<fps>fps/` rather than `<anim>/` at any rate but the default — `--fps 24` writes `<anim>@24fps/`, and the default 12 writes `<anim>/`. `--hide <slot,…>` or `--slot <slot,…>` draws part of the rig on the **same grid** as the whole, so the two frames overlay and the difference is the part; `frames.json` records the subset and `check` refuses such a set as a reference. `--geometry` adds a `geometry.json` per set: every frame's bone world transforms and skinned attachment vertices, with the rest geometry, on the frames' own grid. `--folds` adds a `folds.json` per set and prints one line per mesh with a triangle turned over against the setup pose: the frames, the worst frame's count and which triangles, their world box and their setup-pose area. `--pass-through <cloth>:<leg>` adds a `pass-through.json` per set and prints the frames where a cloth drawn over a leg has its side inside the leg's drawn outline, with the depth and the world box — a reading, not a gate. `--seam <slot>:<slot>` adds a `seam.json` per set and prints, per pair, the frames where two slots that meet open background inside the joint (a gap) or show the overlap band of one past the other (a ghost), with the worst frame's cell count and world box; `ghostA` and `ghostB` partition the same ghost by which named pair member drew it, each with its own frames, spans and worst; `gapOpen` and `gapCovered` partition `gap` by zero versus positive rendered alpha from the whole skeleton, including faint and clipped drawings ([AUTHORING §8.8](../docs/AUTHORING.md#88-where-two-slots-stop-meeting--render---seam)) — a reading, not a gate; the exit status never moves. `--overlaps` adds an `overlaps.json` per set and prints one `OVERLAP "<under>" under "<over>"` line per pair of slots where the one drawn later covers the earlier one and the motion moves it — the frames, and the most pixels covered and the frame of them — with an outline plate per covered frame; the render reports what was drawn, and the draw-order key is the author's remedy — a reading, not a gate. `--extremes` adds `extremes.json` and a native six-panel `extremes.png` (setup, geometry-pair min/max and three premultiplied RGBA diffs with a legend), plus one `EXTREMES` line per set. Per-channel minima/maxima and the farthest original-vertex geometry pair are distinct readings; valid channel extrema survive a geometry/image SKIP, with channel refusals reported separately; still or uncomparable geometry reports a named SKIP without changing render exit status. `--extremes-only` writes the same `extremes.json` and readable `extremes.png` bytes while skipping ordinary frame rasterization/encoding, `contact.png` and `frames.json`; it implies `--extremes` and takes no other diagnostic or sheet output flags. Look at min, max and diff before a person does ([AUTHORING §8.11](../docs/AUTHORING.md#811-min-max-and-diff-before-a-person-looks--render---extremes)). `--angle-range <bone>:<relative-to>` prints how far one bone turns against another over each set: the lowest and highest angle and the frames they fall on — a reading, not a gate. `--viewport <x,y,w,h>` or `--viewport-from <frames.json>` draws over a given box instead of a fitted one, so two builds land on one pixel grid; a slot that reaches past it is named by set and frame, and `frames.json` records it. `--environment <schedule.json>` draws the rig under the wind and gravity its physics answers, in time — the same frames on the core and on spine-core; an inspection of the response the rig states, not a scene. `--sheet [--sheet-format json-hash] [--page-edges pot\|free]` also packs each set's straight-alpha transparent frames into `sheet.png` + `sheet.json` (TexturePacker JSON Hash with a shared world-origin `anchor` and `2`-px clamped edge extrusion, verified through `pixi.js@8.22.0` and `phaser@3.90.0` by `tools/sheet_readers.ts`) and `sheet-manifest.json` (`rigc-sheet/1`), leaving diagnostic `f####.png` and `contact.png` unchanged ([AUTHORING §8.10](../docs/AUTHORING.md#810-sprite-sheets-from-a-render--render---sheet)) — a self-contained sprite object per animation set; when animations play and how a scene is composed remains the consumer's |
|
|
133
|
+
| `preview --candidate <dir>` | one self-contained `.html` that plays it, headed by the line `validate <dir>` prints for it and the rigc version — a refused candidate is still previewed, and its header says so in the gate's words. Repeat `--candidate` for one page with a pane per candidate, in the order given; a green `build` ends by naming this command for its own `--out` |
|
|
134
|
+
| `vote --candidate a --candidate b` | one `.html` that asks a human which; `vote --record <file>` checks the answer into `votes.jsonl` |
|
|
135
|
+
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
|
136
|
+
| `chainfit --candidate <dir> --images <dir> --frame <png>` | reads the parts `pose` refuses, through the candidate's own draw order and hierarchy: masked residuals over **visible** pixels, one hinge per child instead of four degrees of freedom, and the `rotate` key value each answer implies. A bone with two or more anchored descendants is **determined** rather than searched, and the residual that over-determination leaves is reported |
|
|
137
|
+
| `diff <candidate.json> <reference.json>` | structural comparison of two skeletons, one ratio per measure and deliberately no combined score |
|
|
138
|
+
| `bonedist --candidate … --reference … --bones …` | per-frame, per-bone world-transform distance against another skeleton — the ladder's stage 3, run on its own. `--bones <correspondence.json \| identity>` is required rather than defaulted: a candidate is entitled to its own bone names, so the pairing is stated |
|
|
139
|
+
| `check --candidate <dir> --frames <dir> [--out <dir>]` | the candidate against reference pictures — the only instrument here that can see a *wrong animation*. `--out` also writes, for every frame the table lists, the picture its figures came from: reference, candidate, difference and overlay at native size |
|
|
140
|
+
| `bench <rung> --candidate <dir>` | one rung of the benchmark ladder |
|
|
141
|
+
| `guide <RULE>` / `guide §<n>[.<m>[.<k>]] [--all]` | the guide's row for the rule a failure line names — every `FAIL` line, `vote --record` refusal, `ingest` finding and coded compile error ends with ` (see rigc guide <RULE>)`; only legacy uncoded library errors point at `§5.1` — or a section's heading, own prose and subsection index, read from the installed `docs/AUTHORING.md` when it runs. `--all` includes the subsection bodies; a leaf prints its full text either way. Sizes describe full ledger spans. `--all` with a rule is refused, exit 2; an unknown name is refused with the nearest real ones, exit 1 |
|
|
142
|
+
| `skills install [--dir …] [--copy]` | links every agent skill the package ships into `.agents/skills` (or `--dir`) as relative symlinks, or copies them with `--copy` — where Codex, Gemini CLI and Antigravity look. A second run has nothing to do; an entry already there that is not this command's is refused by name and nothing is written |
|
|
143
|
+
| `review <dir> […]` | the human review of a build, handed to the `rig-review` bin of `@rig-c/review` installed beside the package; rigc does not depend on it and never installs it. `rigc review <dir> [--reference <video>] [--animation <name>] [--compare <dir>]` is `rig-review open <dir> …` with those flags 1:1 and prints the loopback URL to give a person; a first argument that is one of review's own words (`open`, `check`, `feedback`, `control`, `strip`, `compare`) or starts with `-` (`--help`, `--`) is handed over untouched. stdout is review's byte for byte, stderr review's plus the launcher's own refusals, the exit code review's. Refused by name, exit 2, before the bin starts: `REVIEW_OUTPUT_MISSING <path>` (the target is not a directory); `REVIEW_NOT_INSTALLED` with two lines, `bun add -d @rig-c/review` and `bunx @rig-c/review open <dir> …` carrying the flags given — the same on a terminal and off, with nothing installed or written; `REVIEW_BUN_TOO_OLD: Bun <found>, @rig-c/review needs <required>`, the floor read off the installed package's `engines.bun`; and an empty argument (`""`) under a Bun that drops one (Bun 1.2.0, measured by a probe), `rigc review: argument N is empty, and Bun <version>, the Bun running this, drops an empty argument when it starts a process — …`. docs/AUTHORING.md lists every line |
|
|
144
|
+
|
|
145
|
+
`diff`, `bonedist`, `check` and `bench` measure against something you were given; the
|
|
146
|
+
first three work on any reference you have, and `bench` is a repository workflow that needs a clone
|
|
147
|
+
and `bun run fetch-examples`. The reasoning behind them is in
|
|
148
|
+
[the benchmark dossier](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md).
|
|
149
|
+
|
|
150
|
+
`build` and `validate` both default to `--profile spine` — the 36 validity rules, which
|
|
151
|
+
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile rig-play`
|
|
152
|
+
adds all 50: the other 14 are one renderer's policy and one canvas budget's, and they
|
|
153
|
+
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
154
|
+
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
155
|
+
yourself: author under `--profile rig-play` and read the extra 14 as findings, and
|
|
156
|
+
gate the release under `--profile spine`.** A `deform` key that folds a mesh inside
|
|
157
|
+
out is written out under the default and refused by name under `rig-play`, which
|
|
158
|
+
is the shape of what that split buys you. A report always names
|
|
159
|
+
the profile it ran and lists what that profile left out.
|
|
160
|
+
|
|
161
|
+
Several cuts can also be registered in a `cuts.json` and built by name
|
|
162
|
+
(`build --cut my_cut --cuts path/to/cuts.json`); every path in that table resolves
|
|
163
|
+
relative to the `cuts.json` file itself, so the table lives with the project that owns
|
|
164
|
+
the art. Its shape is under
|
|
165
|
+
[Usage](https://github.com/rig-c/core/blob/main/docs/BENCHMARK.md#usage).
|
|
166
|
+
|
|
167
|
+
**`render`** samples every animation at 12 fps and writes `render/<animation>/f0000.png…`
|
|
168
|
+
with a `contact.png` beside them — every frame of the shot as one labelled grid,
|
|
169
|
+
which is the picture to open first, because spacing is a comparison *across*
|
|
170
|
+
frames. It draws with rigc's own rasteriser (the one `check` measures with), so
|
|
171
|
+
it needs no browser and no network, and the `frames.json` it leaves beside the
|
|
172
|
+
directories makes the result a frame set like any other — the world box every
|
|
173
|
+
frame is a picture of. `--animation <name>` narrows it to one, `--fps` and
|
|
174
|
+
`--max` change the rate and the frame size. A set drawn at any rate but the
|
|
175
|
+
default is written to `<anim>@<fps>fps/` rather than `<anim>/`, so two rates sit
|
|
176
|
+
side by side: `--fps 24` writes `<anim>@24fps/`, and the default 12 writes
|
|
177
|
+
`<anim>/`. Each set's summary line ends with the directory it wrote.
|
|
178
|
+
|
|
179
|
+
> 🚨 **`--skin <name>` if your rig has more than one.** With no `--skin` no skin
|
|
180
|
+
> is set at all, so every slot resolves through the **default** skin alone — and
|
|
181
|
+
> a slot whose art lives only in a named skin draws *nothing*. That is not a
|
|
182
|
+
> quirk of `render`: `check` compares `render`'s frames, so a multi-skin rig
|
|
183
|
+
> checked with no skin compares blank against blank and reports a perfect
|
|
184
|
+
> `0.0000` about art nobody drew. `render --skin` records the name in
|
|
185
|
+
> `frames.json`, `check --skin` poses the candidate under it, and a candidate
|
|
186
|
+
> scored against frames rendered under a *different* skin is refused by name
|
|
187
|
+
> rather than measured.
|
|
188
|
+
|
|
189
|
+
**`preview`** writes a single self-contained `.html`: your skeleton, your atlas
|
|
190
|
+
and every page's PNG bytes are embedded in it as data URIs, and it plays them in
|
|
191
|
+
the **official [Spine Web Player](https://esotericsoftware.com/spine-player)**.
|
|
192
|
+
Double-click it, or attach it to a message — the file carries the whole artifact.
|
|
193
|
+
It is also the strongest interop statement in this repository: a rig that plays
|
|
194
|
+
there has been played by Esoteric Software's own runtime rather than by ours.
|
|
195
|
+
|
|
196
|
+
> ⚖️ The player itself is **referenced, not embedded** — the page loads it from
|
|
197
|
+
> unpkg, so the first open needs a network, and rigc redistributes nothing
|
|
198
|
+
> Esoteric Software owns (see [NOTICE.md](../NOTICE.md)). Everything the player
|
|
199
|
+
> draws is inside your file.
|
|
200
|
+
|
|
201
|
+
Further repository material:
|
|
202
|
+
|
|
203
|
+
| Document | For |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| 🔬 [SURVEY_2026-08-22.md](https://github.com/rig-c/core/blob/main/docs/SURVEY_2026-08-22.md) | the survey the ladder was climbed by: rigc and the official examples as measured on 2026-08-22, and the gap list ordered by rung. A dated record, not kept current. Repository material |
|
|
206
|
+
|
|
116
207
|
```bash
|
|
117
208
|
bun install # once
|
|
118
209
|
|
|
@@ -286,6 +377,7 @@ What the flags mean:
|
|
|
286
377
|
| `--max` | `render` and `inspect`: the longest side of the frame in pixels (default `256`, finite and at least `16`). On `render`, refused with `--viewport-from`, which already fixes the pixel size — **§8.6**, **§13** |
|
|
287
378
|
| `--show`, `--dim`, `--at`, `--zoom` | `inspect` only — what it draws, whether the art is faded, which times of `--animation` it poses and the bone, slot or folds it frames to; §13 says each, with the refusals |
|
|
288
379
|
| `--geometry` | `render` only: also write a `geometry.json` into each frame directory — per frame, every bone's world transform and every slot's region or mesh vertices in world units after skinning, plus each attachment's rest geometry and topology, on the frames' own grid and viewport. Every other file is byte for byte what the render writes without it. Refused with `--slot`/`--hide`, because the geometry is the whole pose whatever is drawn — **§8.2** |
|
|
380
|
+
| `--extremes-only` | `render` only: imply `--extremes`, but write only `extremes.json` and readable `extremes.png` with the same bytes. Pose the same grid; rasterize only the setup/min/max pictures used by the plate. No ordinary frame PNGs, contact sheet or `frames.json`. Takes no `--sheet`, `--sheet-format`, `--page-edges`, `--geometry`, `--folds`, `--pass-through`, `--seam`, `--overlaps` or `--angle-range`; drop the named flag or use `--extremes` instead — **§8.11** |
|
|
289
381
|
| `--extremes` | `render` only: add `extremes.json` and an `EXTREMES` line per set; a moving set with comparable geometry and readable images also gets a native six-panel `extremes.png`: setup, geometry-pair min/max and three premultiplied RGBA diffs with a legend. Per-channel extrema are independent of that geometry pair; valid channels survive geometry/image SKIPs, and channel refusals are named separately. Still or uncomparable geometry is a named SKIP, with no invented pair or PNG; observation failures do not change a successful render's exit status. Supports `--slot`/`--hide` on both posers, on the whole rig's viewport. Without the flag, no extremes observations are added — **§8.11** |
|
|
290
382
|
| `--folds` | `render` only: also read which mesh triangles **fold** — turn over against the setup pose — and write a `folds.json` into each frame directory: per mesh, the frames with a folded triangle, the worst frame's count and which triangles, by index, with their world box and setup-pose area. One printed line per mesh that folds; a triangle degenerate or inverted at the setup pose is said once and never counted. A reading, not a gate. Every other file is byte for byte what the render writes without it, `geometry.json` included. Refused with `--slot`/`--hide`, for `--geometry`'s reason — **§8.3** |
|
|
291
383
|
| `--pass-through` | `render` only: `<cloth>:<leg>[,<cloth>:<leg>…]` — also read where each cloth slot **passes through** its leg slot, and write a `pass-through.json` into each frame directory: the frames where the cloth draws over the leg and its drawn side lies inside the leg's drawn outline by 2 steps or more, each with the depth and the world box. One printed line per pair, and one per frame that passes through. The first name must draw over the second for a frame to be read — the declared order, never both. A reading, not a gate. Every other file is byte for byte what the render writes without it. An unknown slot, a slot that draws on no frame, a malformed list and a pair named twice are refused by name, and so is `--slot`/`--hide` beside it — **§8.5** |
|
|
@@ -9478,6 +9570,25 @@ bounds plus its `2`-px border on each side exceed `2048` (`w + 4 > 2048` or
|
|
|
9478
9570
|
bun cli.ts render --candidate path/to/build --extremes [--animation turn] [--fps 12] [--max 256]
|
|
9479
9571
|
```
|
|
9480
9572
|
|
|
9573
|
+
Use `--extremes-only` instead when only those observations are needed:
|
|
9574
|
+
|
|
9575
|
+
```sh
|
|
9576
|
+
bun cli.ts render --candidate path/to/build --extremes-only --out extremes/
|
|
9577
|
+
```
|
|
9578
|
+
|
|
9579
|
+
It implies `--extremes` and writes the same `extremes.json` and readable
|
|
9580
|
+
`extremes.png` bytes, sampling the same poses and fitting the same viewport.
|
|
9581
|
+
Only the setup/min/max pictures needed by the plate are rasterized; ordinary
|
|
9582
|
+
frame PNGs, the contact sheet and `frames.json` are not produced. A still or
|
|
9583
|
+
refused set writes its JSON SKIP with no PNG. Skin, subset, environment, fps,
|
|
9584
|
+
animation and viewport options keep their meanings. Other diagnostic and sheet
|
|
9585
|
+
output flags listed in the `--extremes-only` row above are refused together
|
|
9586
|
+
rather than silently ignored. Use `--extremes` when those outputs are needed.
|
|
9587
|
+
Selected set directories are replaced just as in an ordinary render; a prior
|
|
9588
|
+
root `frames.json` is removed because it would advertise frames no longer there.
|
|
9589
|
+
Unselected set directories are left alone. Use a dedicated output root when
|
|
9590
|
+
retaining an ordinary render beside these observations.
|
|
9591
|
+
|
|
9481
9592
|
Look at **min · max · diff before a person does**. A baked line carried by a
|
|
9482
9593
|
moving part can be obvious in the difference image while a seam metric misses
|
|
9483
9594
|
it. This flag makes the picture; it does not classify tears or ghosts and does
|
package/docs/FACE.md
CHANGED
|
@@ -1947,7 +1947,9 @@ turning, blinking and looking — and the one general lesson that might carry in
|
|
|
1947
1947
|
that work is §7's: **allocate the channels before the first key, because Spine
|
|
1948
1948
|
blends and does not add.**
|
|
1949
1949
|
|
|
1950
|
-
|
|
1950
|
+
<a id="a-measured-alpha-aperture"></a>
|
|
1951
|
+
|
|
1952
|
+
### Depth-driven rigging: a measured alpha aperture
|
|
1951
1953
|
|
|
1952
1954
|
A named socket plate can supply an `alphaClip` clipping attachment (AUTHORING,
|
|
1953
1955
|
**Alpha-derived clipping**). This also accepts a generated grid/contour source
|
package/docs/INGEST.md
CHANGED
|
@@ -20,7 +20,7 @@ reproduces* — so deliberately extending or renaming a foreign skeleton **lower
|
|
|
20
20
|
them**, by design (§4.2, §4.3). Neither one has a pass bar, and this page does not
|
|
21
21
|
invent one.
|
|
22
22
|
|
|
23
|
-
- Formats and the CLI reference: [
|
|
23
|
+
- Formats and the CLI reference: [AUTHORING §0](AUTHORING.md#0-the-loop)
|
|
24
24
|
- The rig spec, field by field: **AUTHORING §3**, and [`src/rig.ts`](../src/rig.ts)
|
|
25
25
|
- The motion spec, field by field: **AUTHORING §4**
|
|
26
26
|
- Named failures, and the file each one points at: **AUTHORING §5–§6**
|
|
@@ -1490,3 +1490,142 @@ The transcription these recipes start from is
|
|
|
1490
1490
|
unmodified. The re-pivoted, renamed and extended variants in §4 were built from copies
|
|
1491
1491
|
of it; none is committed, because each is an illustration of an edit rather than a
|
|
1492
1492
|
transcription of anything.
|
|
1493
|
+
|
|
1494
|
+
### Starting from a skeleton you already have
|
|
1495
|
+
|
|
1496
|
+
`rigc ingest` reads a Spine 4.3 `skeleton.json` and writes the two spec files that
|
|
1497
|
+
rebuild it. It is the only command that runs against `build`'s direction, and the
|
|
1498
|
+
only one whose contract is an equality rather than a rulebook:
|
|
1499
|
+
|
|
1500
|
+
```bash
|
|
1501
|
+
rigc ingest hero.json --out specs/ --images parts/
|
|
1502
|
+
rigc build --rig specs/rig.json --motion specs/motion.json --out build/
|
|
1503
|
+
rigc diff build/skeleton.json hero.json
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
`--images parts/` is what makes the second line carry no flag: it writes the spec's
|
|
1507
|
+
own `images` directory, spelled from `--out`, so the specs are self-contained from
|
|
1508
|
+
there on. Leave it off and the `image` names resolve against `specs/` itself, which
|
|
1509
|
+
holds no art — every rebuild then has to repeat `build --images parts/`.
|
|
1510
|
+
|
|
1511
|
+
**`build(ingest(x))` is `x`.** Over the eleven rigs this repository builds — the seven
|
|
1512
|
+
gallery examples, the three generated probes and a coverage probe written for the
|
|
1513
|
+
purpose — the rebuilt `skeleton.json` is byte for byte the file the decompiler read.
|
|
1514
|
+
The atlas is held to a weaker
|
|
1515
|
+
claim on purpose, and the weakening is measured rather than assumed: it comes back
|
|
1516
|
+
equal as a **multiset of region blocks**, because the order the pages are collected in
|
|
1517
|
+
is in no field of the skeleton.
|
|
1518
|
+
|
|
1519
|
+
**And over twelve skeletons nobody here wrote.** Every editor export in the fetched
|
|
1520
|
+
example corpus, ingested, rebuilt through the pack beside it and `diff`ed against the
|
|
1521
|
+
source, comes back **12 of 12, no blockers, 1.000 on every measure the report
|
|
1522
|
+
carries** — and each rebuild is its export's own text in canonical form, apart from
|
|
1523
|
+
the header's `hash` and `spine`: the editor's project hash and the runtime version
|
|
1524
|
+
rigc stamps, the two keys the rig spec has no field for by design (12 of 12) — and
|
|
1525
|
+
the header's box, which `build` computes rather than carries (issue #907): the rebuild
|
|
1526
|
+
writes spine-core's `getBounds` over the export on the header's 1e-6 grid at float32, equal on all twelve,
|
|
1527
|
+
where the editor wrote its own arithmetic, up to 0.0071 units away.
|
|
1528
|
+
[INGEST.md §2.3](../docs/INGEST.md) states that pass line and why those two are the
|
|
1529
|
+
exceptions.
|
|
1530
|
+
|
|
1531
|
+
**What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
|
|
1532
|
+
`.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
|
|
1533
|
+
the file are **findings** with codes rather than plausible values: a construct the
|
|
1534
|
+
spec format cannot hold (`point`, a `sequence` block, an unknown field
|
|
1535
|
+
on a bone, slot or constraint) is a blocker, the command exits non-zero, and both
|
|
1536
|
+
specs are still written — a spec plus a list of what is missing from it beats no spec.
|
|
1537
|
+
|
|
1538
|
+
⚠️ **Two values are not in a skeleton at all.**
|
|
1539
|
+
|
|
1540
|
+
- **The stage.** `skeleton.width`/`height`. A skeleton that declares none is carried as
|
|
1541
|
+
declaring none — the rig spec states `"width": null, "height": null` and the rebuild
|
|
1542
|
+
carries no box either, byte for byte — and `--stage x,y,w,h` is how a
|
|
1543
|
+
caller *adds* one, recorded as a judgement. It is not derivable — posing the rig gives
|
|
1544
|
+
the *animated* extent, which is a different number from the setup box. ⛔ **And `--stage` beside a box the file already states is refused
|
|
1545
|
+
too**, for the opposite reason: two sources for one value, where the file is the record
|
|
1546
|
+
of what was measured. **All twelve exports in the example corpus carry a stage** and
|
|
1547
|
+
none of them needs the flag. It is the value that costs least to get wrong, because
|
|
1548
|
+
`diff` reports the box and gates nothing on it. 🔁 The box an export's header carries
|
|
1549
|
+
is its setup-pose bounding box, and it becomes the rebuild's *stage*; the rebuild's own
|
|
1550
|
+
header is computed again, because since issue #907 `build` writes the setup-pose
|
|
1551
|
+
bounding box there — never the stage — held to spine-core's `getBounds`. 📦 A rig
|
|
1552
|
+
that has to carry its stage in the Spine files asks for it as a bounding box
|
|
1553
|
+
(`skeleton.stageBox`, written by `build` from the stage and held by
|
|
1554
|
+
`A50_STAGE_BOX_IS_THE_STAGE`), and `ingest --stage-box <slot>` reads that box back as
|
|
1555
|
+
the rebuild's stage — [AUTHORING §3.1](../docs/AUTHORING.md) has the field and the
|
|
1556
|
+
paragraph for a consumer of the files.
|
|
1557
|
+
- **An animation's duration.** The format has no such field. The largest key time is
|
|
1558
|
+
the only derivable answer and it is what a runtime plays to; it is wrong for an
|
|
1559
|
+
animation that holds its last pose past its last key, so it is recorded as a finding
|
|
1560
|
+
on every animation rather than chosen quietly.
|
|
1561
|
+
|
|
1562
|
+
Both specs carry a `note` that `ingest` writes itself, saying the file is decompiled
|
|
1563
|
+
and naming the skeleton it came from — because a decompiled spec is indistinguishable
|
|
1564
|
+
from an authored one by inspection, every gate here calls it green (it *is* green),
|
|
1565
|
+
and no gate can catch a missing note.
|
|
1566
|
+
|
|
1567
|
+
[docs/INGEST.md](../docs/INGEST.md) is the whole page on working from a file you were
|
|
1568
|
+
handed; [docs/AUTHORING.md](../docs/AUTHORING.md) §0.3 is the loop.
|
|
1569
|
+
|
|
1570
|
+
### The editor round trip — for a licence holder, never in CI
|
|
1571
|
+
|
|
1572
|
+
`tools/editor_roundtrip.ts` drives the loop the output's whole premise rests on:
|
|
1573
|
+
build → **import into the Spine editor** → export back to JSON → gate, `diff`,
|
|
1574
|
+
`render` and `check` the export against the build it came from.
|
|
1575
|
+
|
|
1576
|
+
```
|
|
1577
|
+
bun cli.ts build --rig … --motion … --out build/ --copy-images
|
|
1578
|
+
bun tools/editor_roundtrip.ts --build build/ --editor /Applications/Spine.app/Contents/MacOS/Spine
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
Of the editor's `--version` it records one line, the `Spine <x.y.z> …` line that
|
|
1582
|
+
carries the version, and nothing else that command prints — the rest of it
|
|
1583
|
+
names the licence holder — or `editor version: not found in --version output`
|
|
1584
|
+
where no such line is there. That call carries the same `-u` as the import and
|
|
1585
|
+
the export, so a trip pinned with `--editor-version <v>` names the editor it
|
|
1586
|
+
pinned and says so on the line (`(read from --version under -u <v>, …)`); an
|
|
1587
|
+
unpinned trip names the launcher's default. Once the export exists the version
|
|
1588
|
+
is held against the export's own `skeleton.spine`, and a disagreement is a FAIL
|
|
1589
|
+
naming both values — the line would otherwise name an editor the trip did not
|
|
1590
|
+
run on. It prints the import and export exit codes, the
|
|
1591
|
+
validator's verdict on the export, every `diff` measure that moved, `check`'s mean MAE and worst drift per
|
|
1592
|
+
animation **for each skin the build and the export both declare** — one
|
|
1593
|
+
render-and-check block per skin, with a per-skin roll-up under them, because a
|
|
1594
|
+
rig's contested art lives in its named skins and a single un-skinned check draws
|
|
1595
|
+
none of it; a skin only one side declares is a FAIL naming it as **lost** (or
|
|
1596
|
+
**added**) **by the export**, with `diff`'s `attachments.skins` beside it, and is
|
|
1597
|
+
rendered on neither side — and a
|
|
1598
|
+
field-by-field list of what the editor rewrote. Every step quotes what its child
|
|
1599
|
+
said when that child did not do what it was for, the renderers included — the
|
|
1600
|
+
editor's words in full and in order, except its `Licensed to:` line, which names
|
|
1601
|
+
the licence holder and is replaced by a line saying it was withheld; a skin
|
|
1602
|
+
**neither** side can draw — a hit-box rig, say — is a **SKIP** naming that, not a
|
|
1603
|
+
red, because `check` had nothing to compare and `diff` and `validate` have
|
|
1604
|
+
already measured the rig. One side drawing where the other does not is the
|
|
1605
|
+
divergence the trip exists to find and stays a failure. A human edit made in the
|
|
1606
|
+
editor survives the trip back.
|
|
1607
|
+
|
|
1608
|
+
🔒 **It requires a licensed Spine editor on the machine, by construction**, and
|
|
1609
|
+
drives only the [documented command line](https://esotericsoftware.com/spine-command-line-interface)
|
|
1610
|
+
— never the UI, and it produces nothing the editor did not produce. With no
|
|
1611
|
+
editor present it refuses by name and exits non-zero, and so does the **trial**:
|
|
1612
|
+
the trial cannot save projects or export animation data, so the refusal names
|
|
1613
|
+
what it found — the executable, the bundle, the `CFBundleName` that bundle
|
|
1614
|
+
declares, or the banner the binary prints about itself — rather than starting it
|
|
1615
|
+
and failing downstream. Both refusals point at `--exported <file>`, which
|
|
1616
|
+
measures an export the editor already made and is the half of this tool that
|
|
1617
|
+
needs no editor at all. A build whose `skeleton.json` is missing, is not JSON, or
|
|
1618
|
+
holds JSON that is not one object is refused by name before the editor starts,
|
|
1619
|
+
and so is a build with no atlas or an atlas that is a directory, or an
|
|
1620
|
+
`--exported` file that is not one JSON object.
|
|
1621
|
+
|
|
1622
|
+
⛔ **Run the round trip by hand, on a machine that has the editor.** CI has no
|
|
1623
|
+
editor, so no automated check runs it. The
|
|
1624
|
+
[pre-landing fixture policy](https://github.com/rig-c/core/blob/main/CLAUDE.md#the-selftest-and-its-fixtures)
|
|
1625
|
+
is the canonical explanation of which rigs the by-hand measurement covers and why.
|
|
1626
|
+
|
|
1627
|
+
⚠️ Build with `--copy-images`. An ordinary build's atlas names its pages by a
|
|
1628
|
+
relative path back to the art directory, and the round trip copies that atlas to
|
|
1629
|
+
a directory at another depth — the tool refuses such a build by name rather than
|
|
1630
|
+
letting `A17` blame the editor for the harness's own doing.
|
|
1631
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.82.1",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/rigc/SKILL.md
CHANGED
|
@@ -148,5 +148,7 @@ spec files field by field, the emission rules, the loop, and the failure map. Th
|
|
|
148
148
|
Every guide linked here is in the installed package at `node_modules/@rig-c/core/docs/`,
|
|
149
149
|
which is the copy that matches the rigc you run; the links go to the repository's
|
|
150
150
|
`main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
|
|
151
|
-
Formats
|
|
152
|
-
|
|
151
|
+
Formats and the CLI reference: [AUTHORING §0](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md#0-the-loop),
|
|
152
|
+
installed at `node_modules/@rig-c/core/docs/AUTHORING.md`;
|
|
153
|
+
existing skeletons: [INGEST](https://github.com/rig-c/core/blob/main/docs/INGEST.md);
|
|
154
|
+
licensing: [NOTICE](https://github.com/rig-c/core/blob/main/NOTICE.md).
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -658,6 +658,14 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
658
658
|
const maxSide = readPositiveNumber(flags, 'max', 256, 16);
|
|
659
659
|
// A box given rather than fitted (issue #1414), read before a line is printed, so a malformed one is refused first.
|
|
660
660
|
const given = readGivenViewport(flags);
|
|
661
|
+
const extremesOnly = flags['extremes-only'] !== undefined;
|
|
662
|
+
if (extremesOnly) {
|
|
663
|
+
for (const flag of ['sheet', 'sheet-format', 'page-edges', 'geometry', 'folds', 'pass-through', 'seam', 'overlaps', 'angle-range']) {
|
|
664
|
+
if (flags[flag] !== undefined) {
|
|
665
|
+
throw new UsageError(`--extremes-only takes no --${flag}: it writes only extremes.json and, when readable, extremes.png. Drop --${flag}, or use --extremes for ordinary frames and additional outputs`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
661
669
|
const wantsSheet = flags.sheet !== undefined;
|
|
662
670
|
if (flags['sheet-format'] !== undefined && !wantsSheet) {
|
|
663
671
|
throw new UsageError('--sheet-format takes --sheet: pass --sheet alongside --sheet-format <format>, or drop --sheet-format');
|
|
@@ -672,7 +680,7 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
672
680
|
const sheetPageEdges = wantsSheet ? readPageEdges(flags) : undefined;
|
|
673
681
|
const outRoot = resolve(flags.out ?? 'render');
|
|
674
682
|
const geometry = flags.geometry !== undefined;
|
|
675
|
-
const extremes = flags.extremes !== undefined;
|
|
683
|
+
const extremes = extremesOnly || flags.extremes !== undefined;
|
|
676
684
|
// The fold reading (issue #1383) is its own flag rather than a part of --geometry, so a caller that asked for
|
|
677
685
|
// neither, or for --geometry alone, gets the bytes it always got: geometry.json is the pose, and a reading derived
|
|
678
686
|
// from it is a second file beside it, written only when asked for.
|
|
@@ -1063,6 +1071,22 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
1063
1071
|
}
|
|
1064
1072
|
|
|
1065
1073
|
mkdirSync(outRoot, { recursive: true });
|
|
1074
|
+
if (extremesOnly) {
|
|
1075
|
+
// Reuse the same posed grid and plates; selecting extremes needs no ordinary pixels.
|
|
1076
|
+
// Clear only the selected sets, just as the ordinary render does. No frames.json
|
|
1077
|
+
// is written: this output is an observation, not a frame set consumable by check.
|
|
1078
|
+
for (const [name, extreme] of extremeReadings) {
|
|
1079
|
+
const dir = join(outRoot, frameSetDir(name, fps));
|
|
1080
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true });
|
|
1081
|
+
mkdirSync(dir, { recursive: true });
|
|
1082
|
+
writeFileSync(join(dir, 'extremes.json'), extremesText(extreme.reading));
|
|
1083
|
+
if (extreme.plate !== null) extreme.plate.writePng(join(dir, 'extremes.png'));
|
|
1084
|
+
console.log(` .. ${extremesLine(extreme.reading)}`);
|
|
1085
|
+
}
|
|
1086
|
+
// A prior ordinary render's index would falsely advertise now-removed frames.
|
|
1087
|
+
rmSync(join(outRoot, FRAMES_SIDECAR), { force: true });
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1066
1090
|
const sets: FrameSet[] = [];
|
|
1067
1091
|
// A mesh's setup faults are a property of the mesh, not of an animation: said once, the first time a set shows it.
|
|
1068
1092
|
const faultsSaid = new Set<string>();
|
package/src/cli/shared.ts
CHANGED
|
@@ -152,7 +152,7 @@ export { PACKAGE_ROOT, readPackageMeta, readVersion };
|
|
|
152
152
|
* needs a value` (issue #328). `CLI10`/`CLI11` in `selftest.ts` now hold the two
|
|
153
153
|
* halves together by reading `--help` rather than by naming a flag.
|
|
154
154
|
*/
|
|
155
|
-
const BOOLEAN_FLAGS = new Set(['all', 'all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy', 'geometry', 'folds', 'extremes', 'overlaps', 'sheet', 'accept-skeleton-differences', 'keep-pages', 'dim']);
|
|
155
|
+
const BOOLEAN_FLAGS = new Set(['all', 'all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy', 'geometry', 'folds', 'extremes', 'extremes-only', 'overlaps', 'sheet', 'accept-skeleton-differences', 'keep-pages', 'dim']);
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* The flags a command is allowed to spell more than once.
|
|
@@ -1066,6 +1066,7 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
1066
1066
|
'Each frame with a moving pair also gets an outline plate, overlaps-f<NNNN>.png: every slot in a moving pair as its contour in its own ' +
|
|
1067
1067
|
'colour, dashed where a slot drawn later covers it, named at its top. The render reports what was drawn; who should be in front is the ' +
|
|
1068
1068
|
"author's, and the draw-order key in the motion spec is the remedy. A reading, not a gate. Not with --slot/--hide",
|
|
1069
|
+
'extremes-only': 'write only extremes.json and readable extremes.png per set, with the same posed grid and bytes as --extremes; no ordinary frame PNGs, contact.png or frames.json. Implies --extremes; takes no --sheet, --sheet-format, --page-edges, --geometry, --folds, --pass-through, --seam, --overlaps or --angle-range',
|
|
1069
1070
|
extremes: 'also write extremes.json and a native six-panel extremes.png per set: setup, geometry-pair min/max and premultiplied RGBA diffs, with a legend. Per-channel extrema are recorded separately and valid channels survive geometry/image SKIPs; channel refusals are named separately. Still geometry reports SKIP; a reading, not a gate. Attachment identity/count changes record a named SKIP without changing render exit; supports --slot/--hide on both posers',
|
|
1070
1071
|
seam:
|
|
1071
1072
|
`also read, for each <slot>:<slot> pair, where the two drawings stop meeting, and write ${SEAM_FILE} into each frame directory: ` +
|
|
@@ -1757,7 +1758,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1757
1758
|
runtime: false,
|
|
1758
1759
|
spineFormat: false,
|
|
1759
1760
|
usage: [
|
|
1760
|
-
'rigc render --candidate <dir | skeleton.json> [--animation <name>] [--skin <name>] [--fps 12] [--max 256] [--geometry] [--folds] [--extremes] [--poser core|spine] [--out render/]',
|
|
1761
|
+
'rigc render --candidate <dir | skeleton.json> [--animation <name>] [--skin <name>] [--fps 12] [--max 256] [--geometry] [--folds] [--extremes | --extremes-only] [--poser core|spine] [--out render/]',
|
|
1761
1762
|
'rigc render … --slot <name[,name…]> | --hide <name[,name…]> (a subset of the slots, on the whole rig\'s grid)',
|
|
1762
1763
|
'rigc render … --environment <schedule.json> (the wind and gravity the physics answers, in time)',
|
|
1763
1764
|
'rigc render … --pass-through <cloth>:<leg>[,…] (the frames where a cloth drawn over a leg passes through it)',
|
|
@@ -1767,7 +1768,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1767
1768
|
'rigc render … --viewport <x,y,w,h> | --viewport-from <frames.json> (a box given, not fitted: two renders on one pixel grid)',
|
|
1768
1769
|
'rigc render … --sheet [--sheet-format json-hash] [--page-edges pot|free] (deterministic sprite sheets + JSON hash table + sheet-manifest.json per set)',
|
|
1769
1770
|
],
|
|
1770
|
-
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'viewport', 'viewport-from', 'geometry', 'folds', 'extremes', 'pass-through', 'seam', 'overlaps', 'angle-range', 'sheet', 'sheet-format', 'page-edges', 'environment', 'poser', 'out'],
|
|
1771
|
+
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'viewport', 'viewport-from', 'geometry', 'folds', 'extremes', 'extremes-only', 'pass-through', 'seam', 'overlaps', 'angle-range', 'sheet', 'sheet-format', 'page-edges', 'environment', 'poser', 'out'],
|
|
1771
1772
|
overrides: {
|
|
1772
1773
|
'page-edges': {
|
|
1773
1774
|
meaning:
|