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 +30 -9
- package/cli.ts +74 -15
- package/docs/AUTHORING.md +242 -38
- package/docs/INGEST.md +117 -14
- package/docs/SPEC_COVERAGE.md +2 -3
- package/package.json +1 -1
- package/skills/ingest/SKILL.md +4 -3
- package/src/atlas.ts +78 -3
- package/src/compile.ts +118 -16
- package/src/deformmeasure.ts +322 -151
- package/src/diff.ts +265 -6
- package/src/ingest.ts +72 -13
- package/src/render.ts +9 -7
- package/src/timelines.ts +163 -0
- package/src/types.ts +35 -2
- package/src/validate.ts +868 -105
- package/tools/editor_roundtrip.ts +260 -22
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
|
|
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 --
|
|
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
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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.
|
|
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
|
-
`${
|
|
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
|
-
|
|
2926
|
-
'<pack>` to
|
|
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
|
-
|
|
2929
|
-
'
|
|
2930
|
-
"caller's value and
|
|
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.
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
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
|
|
3262
|
-
'(--art).
|
|
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
|
'',
|