spine-rigc 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -503,7 +503,7 @@ commands take it and what its default is.
503
503
  | `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` and `--padding` tune it |
504
504
  | `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 |
505
505
  | `validate <dir>` | re-gates artifacts already on disk |
506
- | `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. `--stage x,y,w,h` supplies the one value a skeleton does not hold |
506
+ | `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. `--stage x,y,w,h` supplies the stage for a skeleton that declares none, 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 |
507
507
  | `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 |
508
508
  | `render --candidate <dir>` | PNG frames plus a contact sheet, in `render/` |
509
509
  | `preview --candidate <dir>` | one self-contained `.html` that plays it |
@@ -539,11 +539,16 @@ rebuild it. It is the only command that runs against `build`'s direction, and th
539
539
  only one whose contract is an equality rather than a rulebook:
540
540
 
541
541
  ```bash
542
- rigc ingest hero.json --out specs/ --stage 0,0,1024,768
543
- rigc build --rig specs/rig.json --motion specs/motion.json --images parts/ --out build/
542
+ rigc ingest hero.json --out specs/ --stage 0,0,1024,768 --images parts/
543
+ rigc build --rig specs/rig.json --motion specs/motion.json --out build/
544
544
  rigc diff build/skeleton.json hero.json
545
545
  ```
546
546
 
547
+ `--images parts/` is what makes the second line carry no flag: it writes the spec's
548
+ own `images` directory, spelled from `--out`, so the specs are self-contained from
549
+ there on. Leave it off and the `image` names resolve against `specs/` itself, which
550
+ holds no art — every rebuild then has to repeat `build --images parts/`.
551
+
547
552
  **`build(ingest(x))` is `x`.** Over the eleven rigs this repository builds — the seven
548
553
  gallery examples, the three generated probes and a coverage probe written for the
549
554
  purpose — the rebuilt `skeleton.json` is byte for byte the file the decompiler read,
@@ -552,6 +557,15 @@ claim on purpose, and the weakening is measured rather than assumed: it comes ba
552
557
  equal as a **multiset of region blocks**, because the order the pages are collected in
553
558
  is in no field of the skeleton.
554
559
 
560
+ **And over twelve skeletons nobody here wrote.** The same run ingests every editor
561
+ export in the fetched example corpus, rebuilds it through the pack beside it and
562
+ `diff`s the result against the source: **12 of 12, no blockers, 1.000 on every measure
563
+ the report carries.** Byte identity is not the claim there and the reason is the input
564
+ rather than the round trip — an editor header carries `hash` and `audio`, which the rig
565
+ spec has no field for — so the contract is the structural one `diff` measures.
566
+ [INGEST.md §2.3](docs/INGEST.md) has the three kinds of difference that remain, with
567
+ what each is worth.
568
+
555
569
  **What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
556
570
  `.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
557
571
  the file are **findings** with codes rather than plausible values: a construct the
@@ -563,11 +577,13 @@ One thing it drops on purpose and says so: a path attachment's `lengths`, which
563
577
 
564
578
  ⚠️ **Two values are not in a skeleton at all.**
565
579
 
566
- - **The stage.** `skeleton.width`/`height`: rigc always writes one and an editor
567
- export carries none, so without `--stage x,y,w,h` the missing box is refused by
568
- name. It is not derivable — posing the rig gives the *animated* extent, which is a
569
- different number from the setup box — and it is the value that costs nothing to get
570
- wrong, because no measure `diff` reports reads the skeleton header at all.
580
+ - **The stage.** `skeleton.width`/`height`: rigc always writes one, and a skeleton that
581
+ carries none is refused by name unless `--stage x,y,w,h` supplies it. It is not
582
+ derivable — posing the rig gives the *animated* extent, which is a different number
583
+ from the setup box. ⚠️ This said *"an editor export carries none"* until #594 measured
584
+ it: **all twelve exports in the example corpus carry a stage** and none of them needs
585
+ the flag. It is still the value that costs least to get wrong, because `diff` reports
586
+ the box and gates nothing on it.
571
587
  - **An animation's duration.** The format has no such field. The largest key time is
572
588
  the only derivable answer and it is what a runtime plays to; it is wrong for an
573
589
  animation that holds its last pose past its last key, so it is recorded as a finding
@@ -597,7 +613,12 @@ export, every `diff` measure that moved, `check`'s mean MAE and worst drift per
597
613
  animation **for each skin the build declares** — one render-and-check block per
598
614
  skin, with a per-skin roll-up under them, because a rig's contested art lives in
599
615
  its named skins and a single un-skinned check draws none of it — and a
600
- field-by-field list of what the editor rewrote. On its first
616
+ field-by-field list of what the editor rewrote. Every step quotes what its child
617
+ said when that child did not do what it was for, the renderers included; a skin
618
+ **neither** side can draw — a hit-box rig, say — is a **SKIP** naming that, not a
619
+ red, because `check` had nothing to compare and `diff` and `validate` have
620
+ already measured the rig. One side drawing where the other does not is the
621
+ divergence the trip exists to find and stays a failure. On its first
601
622
  run it found three emitter defects — [#368](https://github.com/firejune/rigc/issues/368),
602
623
  [#369](https://github.com/firejune/rigc/issues/369),
603
624
  [#370](https://github.com/firejune/rigc/issues/370) — and then showed that a
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, type CompileOptions } from './src/compile.ts';
60
+ import { compile, CompileError, relativeImagesPath, type CompileOptions } from './src/compile.ts';
61
61
  import {
62
62
  skeletonDataFromText,
63
63
  surveyDeformKeys,
@@ -2776,13 +2776,44 @@ function cmdIngest(flags: Record<string, string>, positional: string[]): void {
2776
2776
  stage = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
2777
2777
  }
2778
2778
  const outDir = resolve(flags.out);
2779
+ /**
2780
+ * The rig spec's own `images`, spelled from `--out` the way `build` spells
2781
+ * `skeleton.images` from its own output directory — the SAME function, so the
2782
+ * two conventions cannot drift (issue #595). Without it the field is left out,
2783
+ * an `image` name resolves against the spec's own directory, and every rebuild
2784
+ * of the spec has to carry `build --images <dir>`.
2785
+ */
2786
+ let specImages: string | undefined;
2787
+ if (flags.images !== undefined) {
2788
+ // Refused rather than ignored, for the reason `chainfit` refuses `--atlas`:
2789
+ // a flag that silently does nothing is worse than one that says why it
2790
+ // cannot. Measured — an `--art none` spec rebuilt with `--images` naming a
2791
+ // directory that does not exist is byte-identical to one rebuilt without
2792
+ // it, because no attachment carries an `image` for that directory to be the
2793
+ // base of.
2794
+ if (art === 'none') {
2795
+ throw new UsageError(
2796
+ '--images <dir> and --art none contradict: `none` writes width/height and no `image` at all, so the rig ' +
2797
+ "spec's `images` directory would be the base of nothing and no rebuild would read it. Use --art loose to " +
2798
+ 'name an image per attachment, or drop --images — an `--art none` spec rebuilds with `build --atlas-in ' +
2799
+ '<pack.atlas>`',
2800
+ );
2801
+ }
2802
+ // Not checked for existence, deliberately: `ingest` reads the skeleton and
2803
+ // nothing else, so the parts may well be extracted AFTER the specs are
2804
+ // written, and refusing a directory this command never opens would refuse a
2805
+ // legitimate order of work. `build` is where a missing PNG is named.
2806
+ specImages = relativeImagesPath(outDir, resolve(flags.images));
2807
+ }
2779
2808
  console.log(`rigc ingest ${skeletonPath}`);
2780
2809
  console.log(` .. out ${outDir}`);
2781
2810
  console.log(` .. art ${art}`);
2811
+ if (specImages !== undefined) console.log(` .. images ${specImages} (the rig spec's own, from ${outDir})`);
2782
2812
 
2783
2813
  const result = ingest(readJsonFile(skeletonPath), {
2784
2814
  name: flags.name ?? basename(skeletonPath, '.json'),
2785
2815
  art,
2816
+ images: specImages,
2786
2817
  stage,
2787
2818
  source: basename(skeletonPath),
2788
2819
  version: readVersion(),
@@ -2807,9 +2838,13 @@ function cmdIngest(flags: Record<string, string>, positional: string[]): void {
2807
2838
  console.log(`rigc: wrote ${join(outDir, 'rig.json')}`);
2808
2839
  console.log(`rigc: wrote ${join(outDir, 'motion.json')}`);
2809
2840
  console.log(`rigc: wrote ${join(outDir, 'findings.json')}`);
2841
+ // The hint is the command the caller will actually run, so it drops `--images`
2842
+ // exactly when the spec now carries the directory itself — a hint that asks for
2843
+ // a flag the spec made unnecessary is the defect issue #595 is about, printed.
2844
+ const artFlag = art === 'none' ? ' --atlas-in <pack.atlas>' : specImages === undefined ? ' --images <dir>' : '';
2810
2845
  console.log(
2811
- `rigc: build it with rigc build --rig ${join(outDir, 'rig.json')} --motion ${join(outDir, 'motion.json')} ` +
2812
- `${art === 'loose' ? '--images <dir>' : '--atlas-in <pack.atlas>'} --out <dir>`,
2846
+ `rigc: build it with rigc build --rig ${join(outDir, 'rig.json')} --motion ${join(outDir, 'motion.json')}` +
2847
+ `${artFlag} --out <dir>`,
2813
2848
  );
2814
2849
 
2815
2850
  const blockers = result.findings.filter((f) => f.kind === 'blocker');
@@ -2922,12 +2957,15 @@ const FLAG_MEANINGS: Record<string, string> = {
2922
2957
  again: 'record a second vote on a ballot the ledger already has; without it, a repeat is refused rather than doubled',
2923
2958
  name: "the rig spec's own name, which the motion spec's archetype must match (default: the skeleton file's basename)",
2924
2959
  art: 'how the written spec reaches the art, which a skeleton does not encode: `loose` names an image per ' +
2925
- 'attachment for `build --images <dir>` to measure, `none` states width/height only for `build --atlas-in ' +
2926
- '<pack>` to resolve (default: loose)',
2960
+ "attachment, measured out of the rig spec's own images directory (--images writes it; without it, `build " +
2961
+ '--images <dir>` on every rebuild), `none` states width/height only for `build --atlas-in <pack>` to ' +
2962
+ 'resolve (default: loose)',
2927
2963
  stage:
2928
- "the setup bounding box — `skeleton.x,y,width,height`. An editor export carries none and rigc refuses a " +
2929
- 'compile without one; posing the rig gives the ANIMATED extent, which is a different number, so this is the ' +
2930
- "caller's value and is never derived. Without it the missing stage is reported as a blocker",
2964
+ 'the setup bounding box — `skeleton.x,y,width,height` — for a skeleton that declares none. It cannot be ' +
2965
+ 'derived: posing the rig gives the ANIMATED extent, which is a different number from the setup box, so this ' +
2966
+ "is the caller's value, and without it the missing stage is reported as a blocker. ⚠️ An editor export MAY " +
2967
+ 'carry none; every editor export measured for this project carries one and ingest reads it straight through, ' +
2968
+ 'so the flag is for a file that really has none rather than for editor exports as a class',
2931
2969
  help: "show this command's flags and exit",
2932
2970
  };
2933
2971
 
@@ -2988,15 +3026,25 @@ interface CommandDoc {
2988
3026
  * Per-command wording for a flag whose value or meaning genuinely differs here.
2989
3027
  *
2990
3028
  * ⚠️ The default above it — one meaning per flag name, everywhere — is the rule
2991
- * and this is the named exception to it, not a second table. Three flags earn it:
2992
- * `--out` is a directory of artifacts to `build`, a directory of specs to
2993
- * `ingest`, a directory of pictures to
3029
+ * and this is the named exception to it, not a second table. What earns an entry
3030
+ * is the criterion rather than a headcount: the flag is **shared with another
3031
+ * command**, and it means something different in this one. Examples, and not an
3032
+ * inventory: `--out` is a directory of artifacts to `build`, a directory of specs
3033
+ * to `ingest`, a directory of pictures to
2994
3034
  * `render` and one file to `preview` and `vote`; `--fps` is the rate a frame set
2995
3035
  * was RECORDED at to `check`, which reads it off a sidecar, and the rate to
2996
3036
  * SAMPLE at to `render`, which is choosing it; `--candidate` is one artifact
2997
3037
  * everywhere except `vote`, which is the one command that takes several and is
2998
3038
  * the reason there is a ballot at all. Writing any of them as one sentence
2999
3039
  * covering every command would leave every command's own help less true.
3040
+ *
3041
+ * ⛔ The other side of the criterion, which is the one that keeps this from
3042
+ * becoming the second table it says it is not: a flag no other command takes has
3043
+ * nothing to differ FROM, so its wording belongs in `FLAG_MEANINGS` /
3044
+ * `FLAG_VALUES` above and an entry here for it buys only a second place to look.
3045
+ * Both halves are read off `--help` by `CLI71` in `selftest.ts`, which is why
3046
+ * this sentence no longer counts anything: it said *"three"* where #605 counted
3047
+ * nine, and nothing had ever compared the two.
3000
3048
  */
3001
3049
  overrides?: Record<string, { value?: string; meaning?: string }>;
3002
3050
  }
@@ -3041,13 +3089,21 @@ const COMMANDS: CommandDoc[] = [
3041
3089
  },
3042
3090
  {
3043
3091
  name: 'ingest',
3044
- usage: ['rigc ingest <skeleton.json> --out <dir> [--name <n>] [--art loose|none] [--stage x,y,w,h]'],
3045
- flags: ['out', 'name', 'art', 'stage'],
3092
+ usage: ['rigc ingest <skeleton.json> --out <dir> [--name <n>] [--art loose|none] [--images <dir>] [--stage x,y,w,h]'],
3093
+ flags: ['out', 'name', 'art', 'images', 'stage'],
3046
3094
  overrides: {
3047
3095
  out: {
3048
3096
  value: '<dir>',
3049
3097
  meaning: 'directory to write rig.json, motion.json and findings.json into — the two specs that rebuild this skeleton',
3050
3098
  },
3099
+ images: {
3100
+ value: '<dir>',
3101
+ meaning:
3102
+ "WRITE the rig spec's own images directory, spelled relative to --out, so the rebuild is a plain `build " +
3103
+ '--rig … --motion … --out …` with no flag. ⚠️ The opposite direction from `build --images`, which ' +
3104
+ 'OVERRIDES that field: this one fills it in. Without it the field is left out and every `image` resolves ' +
3105
+ 'against --out itself. Refused together with --art none, which writes no `image` for it to be the base of',
3106
+ },
3051
3107
  },
3052
3108
  },
3053
3109
  {
@@ -3258,8 +3314,11 @@ const USAGE = [
3258
3314
  'The contract is an equality, not a rulebook: build(ingest(x)) is x, byte for byte.',
3259
3315
  'It reads the skeleton and nothing else — no .spine project, no binary .skel, no',
3260
3316
  'atlas — so two things are the caller\'s and are refused rather than guessed: the',
3261
- 'setup stage (--stage; an export carries none) and how the spec reaches the art',
3262
- '(--art). Everything the spec format cannot hold is printed as a named finding and',
3317
+ 'setup stage (--stage, only when the skeleton itself declares none) and how the spec',
3318
+ 'reaches the art (--art). --images <dir> is the third and the only optional one: it',
3319
+ 'WRITES the rig spec\'s own images directory, relative to --out, so the rebuild needs',
3320
+ 'no flag.',
3321
+ 'Everything the spec format cannot hold is printed as a named finding and',
3263
3322
  'exits non-zero, with both files still written, because a spec plus a list of what',
3264
3323
  'is missing from it beats no spec at all.',
3265
3324
  '',