spine-rigc 0.25.4 → 0.25.5
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 +29 -13
- package/cli.ts +68 -13
- package/docs/AUTHORING.md +302 -8
- package/docs/FACE.md +153 -37
- package/docs/INGEST.md +54 -3
- package/docs/MOTION.md +1 -1
- package/docs/PROMPTING.md +2 -2
- package/docs/RIGGING.md +3 -2
- package/docs/SPEC_COVERAGE.md +19 -0
- package/package.json +1 -1
- package/skills/face/SKILL.md +13 -4
- package/skills/ingest/SKILL.md +6 -3
- package/skills/rigc/SKILL.md +11 -4
- package/src/check.ts +23 -2
- package/src/compile.ts +84 -8
- package/src/errors.ts +32 -1
- package/src/ingest.ts +41 -3
- package/src/rig.ts +23 -5
- package/src/slots.ts +52 -3
- package/src/types.ts +25 -14
package/README.md
CHANGED
|
@@ -146,15 +146,17 @@ because they are written from a clone of this repository (`bun install`, then ru
|
|
|
146
146
|
the CLI in place); the two are interchangeable — `rigc build …` is
|
|
147
147
|
`bun cli.ts build …`.
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
The reasoning is in
|
|
149
|
+
One command is a repository workflow rather than a package one: `bench` measures
|
|
150
|
+
against Spine's official example projects — fetched, never committed — and against
|
|
151
|
+
reference frames this project renders from them, which **are** committed, each
|
|
152
|
+
example's own `license.txt` beside them under the redistribution grant those files
|
|
153
|
+
carry; the images stay **non-commercial only**. The reasoning is in
|
|
155
154
|
[`bench/reference/README.md`](https://github.com/firejune/rigc/blob/main/bench/reference/README.md)
|
|
156
|
-
and the terms in [NOTICE.md](NOTICE.md).
|
|
157
|
-
fetch-examples`, and
|
|
155
|
+
and the terms in [NOTICE.md](NOTICE.md). It needs a clone and `bun run
|
|
156
|
+
fetch-examples`, and says so by name when the corpus is absent. `check` is not one
|
|
157
|
+
of them: it reads whatever frames you point it at, so it runs from the installed
|
|
158
|
+
package on pictures of your own — which is what *Where to go next* below tells you
|
|
159
|
+
to do with it, and it is the one instrument here that can see a wrong animation.
|
|
158
160
|
|
|
159
161
|
### Install it into your agent
|
|
160
162
|
|
|
@@ -405,6 +407,14 @@ that does not belong to it. See
|
|
|
405
407
|
could. If you have reference pictures of the shot,
|
|
406
408
|
`rigc check --candidate spine --frames <dir>` is the half of the loop that can
|
|
407
409
|
see a wrong animation — AUTHORING.md §9.
|
|
410
|
+
- 🧭 **No reference pictures, because the rig is your own?** Then make them:
|
|
411
|
+
`rigc render` the first build you are happy with and keep those frames. Every
|
|
412
|
+
later build is checked against them, and the first such check — the same build
|
|
413
|
+
against frames of itself — is the floor the rest are read against, because
|
|
414
|
+
`check` grades nothing and has no pass mark. It is the same instrument and the
|
|
415
|
+
same commands; what changes is that the reference is a build of yours you have
|
|
416
|
+
already looked at, so what it measures is **what your edit did**. AUTHORING.md
|
|
417
|
+
§9.2 says what that floor reads and why it is not zero.
|
|
408
418
|
- [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) is the benchmark: the same job, from a brief
|
|
409
419
|
and rendered frames, scored. [docs/PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) is how to run an
|
|
410
420
|
agent through it and score what comes back.
|
|
@@ -511,19 +521,24 @@ commands take it and what its default is.
|
|
|
511
521
|
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
|
512
522
|
| `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 |
|
|
513
523
|
| `diff <candidate.json> <reference.json>` | structural comparison of two skeletons, one ratio per measure and deliberately no combined score |
|
|
524
|
+
| `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 |
|
|
514
525
|
| `check --candidate <dir> --frames <dir>` | the candidate against reference pictures — the only instrument here that can see a *wrong animation* |
|
|
515
526
|
| `bench <rung> --candidate <dir>` | one rung of the benchmark ladder |
|
|
516
527
|
|
|
517
|
-
`diff`, `check` and `bench` measure against something you were given; the
|
|
518
|
-
work on any
|
|
519
|
-
and `bun run fetch-examples`. The reasoning behind
|
|
528
|
+
`diff`, `bonedist`, `check` and `bench` measure against something you were given; the
|
|
529
|
+
first three work on any reference you have, and `bench` is a repository workflow that needs a clone
|
|
530
|
+
and `bun run fetch-examples`. The reasoning behind them is in
|
|
520
531
|
[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
521
532
|
|
|
522
533
|
`build` and `validate` both default to `--profile spine` — the 28 validity rules, which
|
|
523
534
|
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
|
|
524
535
|
adds all 43: the other 15 are one renderer's policy and one canvas budget's, and they
|
|
525
|
-
fire on perfectly correct editor-produced Spine data,
|
|
526
|
-
|
|
536
|
+
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
537
|
+
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
538
|
+
yourself: author under `--profile spine-html` and read the extra 15 as findings, and
|
|
539
|
+
gate the release under `--profile spine`.** A `deform` key that folds a mesh inside
|
|
540
|
+
out is written out under the default and refused by name under `spine-html`, which
|
|
541
|
+
is the shape of what that split buys you. A report always names
|
|
527
542
|
the profile it ran and lists what that profile left out.
|
|
528
543
|
|
|
529
544
|
Several cuts can also be registered in a `cuts.json` and built by name
|
|
@@ -666,6 +681,7 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
666
681
|
| 🎓 **[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the 43 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 |
|
|
667
682
|
| 📋 [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) · [GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md) · [PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) | the live rung ledger, the clause statements a candidate is graded against, and how to run an agent through the ladder and score what comes back |
|
|
668
683
|
| 🗺️ [ROADMAP.md](https://github.com/firejune/rigc/blob/main/ROADMAP.md) | where this is going, and where it has been. What 1.0 has to mean before the number is claimed — conditions rather than a feature list, because direction here comes from what users hit |
|
|
684
|
+
| 📐 [CLAUDE.md](https://github.com/firejune/rigc/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/firejune/rigc/blob/main/CONTRIBUTING.md) calls it worth ten minutes before a first patch. Repository material |
|
|
669
685
|
|
|
670
686
|
## Why you can trust the output
|
|
671
687
|
|
package/cli.ts
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
type BoneDistReport,
|
|
58
58
|
} from './src/bonedist.ts';
|
|
59
59
|
import { checkAgainstFrames, checkLines, CheckError, type CheckOptions, type CheckReport } from './src/check.ts';
|
|
60
|
-
import { compile, CompileError, relativeImagesPath, type CompileOptions } from './src/compile.ts';
|
|
60
|
+
import { compile, CompileError, droppedStateReason, relativeImagesPath, type CompileOptions } from './src/compile.ts';
|
|
61
61
|
import {
|
|
62
62
|
skeletonDataFromText,
|
|
63
63
|
surveyDeformKeys,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
type DeformSpan,
|
|
68
68
|
} from './src/deformmeasure.ts';
|
|
69
69
|
import { diffLines, diffSkeletons, reportedFigures, sectionFigures, type DiffReport } from './src/diff.ts';
|
|
70
|
-
import { ingest, IngestError,
|
|
70
|
+
import { ingest, IngestError, INGEST_GUTTERS, type IngestStage } from './src/ingest.ts';
|
|
71
71
|
import { copyAtlasImages } from './src/emit.ts';
|
|
72
72
|
import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, packAtlas } from './src/atlas.ts';
|
|
73
73
|
import { parseJsonWithPosition } from './src/json-position.ts';
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
} from './src/validate.ts';
|
|
126
126
|
import { parseMotionSpec } from './src/motion.ts';
|
|
127
127
|
import { depthStepLevels, type FoldLimit, type TurnCeiling } from './src/depth.ts';
|
|
128
|
-
import type { CompileResult } from './src/types.ts';
|
|
128
|
+
import type { CompileResult, DroppedState } from './src/types.ts';
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* One entry of a cuts.json, every path relative to the cuts.json file.
|
|
@@ -1133,6 +1133,19 @@ function readIntFlag(flags: Record<string, string>, name: string, fallback: numb
|
|
|
1133
1133
|
return Number(raw);
|
|
1134
1134
|
}
|
|
1135
1135
|
|
|
1136
|
+
/**
|
|
1137
|
+
* One `DROP` line, written once because two outcomes print it.
|
|
1138
|
+
*
|
|
1139
|
+
* A build that succeeds prints it in its report; a build that REFUSES prints it
|
|
1140
|
+
* under the refusal (issue #671), and the two have to be the same line or the
|
|
1141
|
+
* failing run would be quoting a different fact from the one the green run
|
|
1142
|
+
* shows. What it names — a file or a region — is `droppedStateReason`'s, in the
|
|
1143
|
+
* compiler, beside the code that decided which of the two was consulted.
|
|
1144
|
+
*/
|
|
1145
|
+
function dropLine(dropped: DroppedState): string {
|
|
1146
|
+
return ` DROP ${dropped.slot}/${dropped.state}: ${droppedStateReason(dropped)} (state not emitted)`;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1136
1149
|
function cmdBuild(flags: Record<string, string>): void {
|
|
1137
1150
|
const { label, opts } = resolveCut(flags);
|
|
1138
1151
|
const profile = readProfile(flags);
|
|
@@ -1195,9 +1208,7 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1195
1208
|
: ` scale ${img.atlasScale} (${img.atlas.originalWidth}x${img.atlas.originalHeight} texels)`);
|
|
1196
1209
|
console.log(` .. ${img.region.padEnd(24)} ${img.width}x${img.height} <- ${where}`);
|
|
1197
1210
|
}
|
|
1198
|
-
for (const d of result.droppedStates)
|
|
1199
|
-
console.log(` DROP ${d.slot}/${d.state}: ${d.why ?? `no PNG at ${d.path}`} (state not emitted)`);
|
|
1200
|
-
}
|
|
1211
|
+
for (const d of result.droppedStates) console.log(dropLine(d));
|
|
1201
1212
|
// "The optional slots are optional" is a claim about this code path, so this
|
|
1202
1213
|
// code path says which ones it left out rather than being silently right.
|
|
1203
1214
|
for (const a of result.absentParts) {
|
|
@@ -2720,8 +2731,12 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2720
2731
|
}
|
|
2721
2732
|
|
|
2722
2733
|
if (result.droppedStates.length) {
|
|
2723
|
-
|
|
2724
|
-
|
|
2734
|
+
// The heading said "no PNG on disk" and the line printed the path, on a
|
|
2735
|
+
// command that takes `--atlas-in` like `build` does — so an explain of a
|
|
2736
|
+
// pack build named a file it never opened. Same renderer as the other two
|
|
2737
|
+
// printers now, for the same reason they share one (issue #671).
|
|
2738
|
+
console.log('\ndropped states (listed in the manifest, no art behind them)');
|
|
2739
|
+
for (const d of result.droppedStates) console.log(` ${d.slot}/${d.state} ${droppedStateReason(d)}`);
|
|
2725
2740
|
}
|
|
2726
2741
|
|
|
2727
2742
|
console.log('\nmix table (player config, not skeleton JSON)');
|
|
@@ -2829,10 +2844,13 @@ function cmdIngest(flags: Record<string, string>, positional: string[]): void {
|
|
|
2829
2844
|
// Grouped by kind rather than printed in discovery order: a blocker is what
|
|
2830
2845
|
// decides the exit code, and a reader scanning for one should not have to
|
|
2831
2846
|
// read past a hundred DURATION lines to find it.
|
|
2832
|
-
|
|
2847
|
+
// The gutter words are `INGEST_GUTTERS`, which is also the column
|
|
2848
|
+
// `docs/INGEST.md` §2.0's finding-code table is keyed on (issue #675); the
|
|
2849
|
+
// pad to one width is this printer's, so the codes line up.
|
|
2850
|
+
const width = Math.max(...Object.values(INGEST_GUTTERS).map((gutter) => gutter.length));
|
|
2833
2851
|
for (const kind of ['blocker', 'judgement', 'lossy'] as const) {
|
|
2834
2852
|
for (const finding of result.findings.filter((f) => f.kind === kind)) {
|
|
2835
|
-
console.log(` ${
|
|
2853
|
+
console.log(` ${INGEST_GUTTERS[kind].padEnd(width)} ${finding.code}: ${finding.where} — ${finding.detail}`);
|
|
2836
2854
|
}
|
|
2837
2855
|
}
|
|
2838
2856
|
console.log(`rigc: wrote ${join(outDir, 'rig.json')}`);
|
|
@@ -3049,6 +3067,18 @@ interface CommandDoc {
|
|
|
3049
3067
|
* nine, and nothing had ever compared the two.
|
|
3050
3068
|
*/
|
|
3051
3069
|
overrides?: Record<string, { value?: string; meaning?: string }>;
|
|
3070
|
+
/**
|
|
3071
|
+
* Lines printed under the flag table: what this command's own figures mean.
|
|
3072
|
+
*
|
|
3073
|
+
* ⚠️ Not a second place to describe a flag. It exists for what is true of the
|
|
3074
|
+
* **command** and of no flag it takes — and the case that earned it is issue
|
|
3075
|
+
* #678: `pose` and `chainfit` each say *"it is a reporting threshold, not a
|
|
3076
|
+
* pass bar"* on the flag that carries their threshold, and `check` has no such
|
|
3077
|
+
* flag, so its page said nothing at all about whether any of its figures is a
|
|
3078
|
+
* bar to beat. An agent reading `slot drift worst 3.7 px` off a correct rig had
|
|
3079
|
+
* no page to consult and no exit code to read it in.
|
|
3080
|
+
*/
|
|
3081
|
+
notes?: string[];
|
|
3052
3082
|
}
|
|
3053
3083
|
|
|
3054
3084
|
const COMMANDS: CommandDoc[] = [
|
|
@@ -3126,6 +3156,19 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3126
3156
|
'REFUSED by name, and one that records none says so in the report rather than pretending to agree',
|
|
3127
3157
|
},
|
|
3128
3158
|
},
|
|
3159
|
+
notes: [
|
|
3160
|
+
'every figure here is a reporting threshold, not a pass bar. Nothing in this report',
|
|
3161
|
+
'grades, no number has to beat anything, and the exit code says only whether the',
|
|
3162
|
+
'comparison could be MADE: 0 when it ran — including the build with every easing',
|
|
3163
|
+
'reversed, which is the defect this command exists for — 1 when it could not (frames',
|
|
3164
|
+
'that are not there, a skin the frames do not record, a candidate that will not load),',
|
|
3165
|
+
'2 on the flags.',
|
|
3166
|
+
'',
|
|
3167
|
+
'So read a figure against a floor you measured yourself: render the first green build',
|
|
3168
|
+
'and keep its frames, then check every later build against them. The identity run of',
|
|
3169
|
+
'that pair is the floor, and it is not zero — docs/AUTHORING.md §9.2 states it, what',
|
|
3170
|
+
'it comes from, and which column separates a wrong curve from a moved key.',
|
|
3171
|
+
],
|
|
3129
3172
|
},
|
|
3130
3173
|
{
|
|
3131
3174
|
name: 'bench',
|
|
@@ -3267,9 +3310,14 @@ function commandHelp(name: string): string {
|
|
|
3267
3310
|
const meaning = (key: string): string => doc.overrides?.[key]?.meaning ?? FLAG_MEANINGS[key];
|
|
3268
3311
|
const labels = keys.map((key) => `--${key}${value(key) ? ` ${value(key)}` : ''}`);
|
|
3269
3312
|
const width = Math.max(...labels.map((l) => l.length)) + 2;
|
|
3270
|
-
return [
|
|
3271
|
-
'
|
|
3272
|
-
)
|
|
3313
|
+
return [
|
|
3314
|
+
'usage:',
|
|
3315
|
+
...doc.usage.map((u) => ` ${u}`),
|
|
3316
|
+
'',
|
|
3317
|
+
'flags:',
|
|
3318
|
+
...keys.map((key, i) => ` ${labels[i].padEnd(width)}${meaning(key)}`),
|
|
3319
|
+
...(doc.notes === undefined ? [] : ['', ...doc.notes]),
|
|
3320
|
+
].join('\n');
|
|
3273
3321
|
}
|
|
3274
3322
|
|
|
3275
3323
|
const USAGE = [
|
|
@@ -3410,6 +3458,13 @@ try {
|
|
|
3410
3458
|
}
|
|
3411
3459
|
if (err instanceof CompileError) {
|
|
3412
3460
|
console.error(`rigc compile error: ${err.message}`);
|
|
3461
|
+
// The drops the compile recorded before it stopped, in the same line the
|
|
3462
|
+
// green build prints (issue #671). They were reported from the compile
|
|
3463
|
+
// RESULT alone, so the run that failed BECAUSE a file was missing was the
|
|
3464
|
+
// one run that never named the file. On stderr with the refusal rather than
|
|
3465
|
+
// on stdout, so redirecting one stream does not separate a fact from the
|
|
3466
|
+
// sentence it explains.
|
|
3467
|
+
for (const dropped of err.droppedStates ?? []) console.error(dropLine(dropped));
|
|
3413
3468
|
process.exit(1);
|
|
3414
3469
|
}
|
|
3415
3470
|
if (err instanceof CheckError) {
|