spine-rigc 0.25.3 → 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 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
- Two commands are repository workflows rather than package ones: `bench` and
150
- `check` measure against Spine's official example projects — fetched, never
151
- committed — and against reference frames this project renders from them, which
152
- **are** committed, each example's own `license.txt` beside them under the
153
- redistribution grant those files carry; the images stay **non-commercial only**.
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). Both commands need a clone and `bun run
157
- fetch-examples`, and say so by name when the corpus is absent.
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 first two
518
- work on any frames you have, and `bench` is a repository workflow that needs a clone
519
- and `bun run fetch-examples`. The reasoning behind all three is in
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
- `build` and `validate` both default to `--profile spine` — the 27 validity rules, which
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
- adds all 42: the other 15 are one renderer's policy and one canvas budget's, and they
525
- fire on perfectly correct editor-produced Spine data, so reach for that profile when
526
- you are shipping into *that* project rather than to be thorough. A report always names
535
+ adds all 43: the other 15 are one renderer's policy and one canvas budget's, and they
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
@@ -663,9 +678,10 @@ letting `A17` blame the editor for the harness's own doing.
663
678
  | 📥 **[docs/INGEST.md](docs/INGEST.md)** | **working with a skeleton you did not author.** What every command can and cannot do with a foreign `skeleton.json`, reading it with the toolchain, transcription as the route that makes it yours, what each validator complaint means on an export, and the re-pivot/rename/extend recipes. Ships in the package too |
664
679
  | 🤖 **[docs/PROMPTING.md](docs/PROMPTING.md)** | **handing the authoring to an AI agent** — the prompt clauses a measured pilot run paid for, and what you can leave unsaid. Ships in the package too |
665
680
  | 🔬 **[docs/SPEC_COVERAGE.md](docs/SPEC_COVERAGE.md)** | Spine 4.3's full export surface against what rigc emits and what the official examples measurably use, with the ordered gap list |
666
- | 🎓 **[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 42 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 |
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
 
@@ -719,7 +735,7 @@ quality."* All six, with their verdicts, are in
719
735
  [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
720
736
 
721
737
  The whole dossier — the yardstick, `diff` and `check` and what neither of them can
722
- see, every rung, the run viewer, the 42 assertions and the selftest behind them — is
738
+ see, every rung, the run viewer, the 43 assertions and the selftest behind them — is
723
739
  [docs/BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
724
740
  Live rung status is
725
741
  [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
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, type IngestFindingKind, type IngestStage } from './src/ingest.ts';
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
- console.log('\ndropped states (listed in the manifest, no PNG on disk)');
2724
- for (const d of result.droppedStates) console.log(` ${d.slot}/${d.state} ${d.path}`);
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
- const GUTTER: Record<IngestFindingKind, string> = { blocker: 'BLOCK', judgement: 'JUDGE', lossy: 'LOSS ' };
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(` ${GUTTER[kind]} ${finding.code}: ${finding.where} — ${finding.detail}`);
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 ['usage:', ...doc.usage.map((u) => ` ${u}`), '', 'flags:', ...keys.map((key, i) => ` ${labels[i].padEnd(width)}${meaning(key)}`)].join(
3271
- '\n',
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) {