spine-rigc 0.25.0 → 0.25.2
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 +6 -3
- package/cli.ts +17 -4
- package/docs/AUTHORING.md +27 -3
- package/docs/FACE.md +34 -0
- package/docs/INGEST.md +10 -4
- package/package.json +1 -1
- package/skills/ingest/SKILL.md +3 -2
- package/src/ingest.ts +112 -8
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 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 |
|
|
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 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 |
|
|
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,7 +539,7 @@ 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/ --
|
|
542
|
+
rigc ingest hero.json --out specs/ --images parts/
|
|
543
543
|
rigc build --rig specs/rig.json --motion specs/motion.json --out build/
|
|
544
544
|
rigc diff build/skeleton.json hero.json
|
|
545
545
|
```
|
|
@@ -580,7 +580,10 @@ One thing it drops on purpose and says so: a path attachment's `lengths`, which
|
|
|
580
580
|
- **The stage.** `skeleton.width`/`height`: rigc always writes one, and a skeleton that
|
|
581
581
|
carries none is refused by name unless `--stage x,y,w,h` supplies it. It is not
|
|
582
582
|
derivable — posing the rig gives the *animated* extent, which is a different number
|
|
583
|
-
from the setup box.
|
|
583
|
+
from the setup box. ⛔ **And `--stage` beside a box the file already states is refused
|
|
584
|
+
too**, for the opposite reason: two sources for one value, where the file is the record
|
|
585
|
+
of what was measured. It used to be read after the box and therefore never
|
|
586
|
+
([#626](https://github.com/firejune/rigc/issues/626)). ⚠️ This said *"an editor export carries none"* until #594 measured
|
|
584
587
|
it: **all twelve exports in the example corpus carry a stage** and none of them needs
|
|
585
588
|
the flag. It is still the value that costs least to get wrong, because `diff` reports
|
|
586
589
|
the box and gates nothing on it.
|
package/cli.ts
CHANGED
|
@@ -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, type IngestFindingKind, type IngestStage } from './src/ingest.ts';
|
|
70
|
+
import { ingest, IngestError, type IngestFindingKind, 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';
|
|
@@ -2965,7 +2965,9 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
2965
2965
|
'derived: posing the rig gives the ANIMATED extent, which is a different number from the setup box, so this ' +
|
|
2966
2966
|
"is the caller's value, and without it the missing stage is reported as a blocker. ⚠️ An editor export MAY " +
|
|
2967
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'
|
|
2968
|
+
'so the flag is for a file that really has none rather than for editor exports as a class. ⛔ Beside a ' +
|
|
2969
|
+
'skeleton that already declares a box it is REFUSED rather than ignored: two sources for one value, and the ' +
|
|
2970
|
+
'file is the record of what was measured',
|
|
2969
2971
|
help: "show this command's flags and exit",
|
|
2970
2972
|
};
|
|
2971
2973
|
|
|
@@ -3310,11 +3312,12 @@ const USAGE = [
|
|
|
3310
3312
|
'ingest runs build backwards: it reads a Spine 4.3 skeleton.json and writes the rig',
|
|
3311
3313
|
'spec and motion spec that rebuild it, so an existing skeleton becomes a starting',
|
|
3312
3314
|
'point instead of something to retype:',
|
|
3313
|
-
' rigc ingest hero.json --out specs/ --
|
|
3315
|
+
' rigc ingest hero.json --out specs/ --images parts/ rig.json + motion.json',
|
|
3314
3316
|
'The contract is an equality, not a rulebook: build(ingest(x)) is x, byte for byte.',
|
|
3315
3317
|
'It reads the skeleton and nothing else — no .spine project, no binary .skel, no',
|
|
3316
3318
|
'atlas — so two things are the caller\'s and are refused rather than guessed: the',
|
|
3317
|
-
'setup stage (--stage, only when the skeleton itself declares none
|
|
3319
|
+
'setup stage (--stage, only when the skeleton itself declares none — beside a box the',
|
|
3320
|
+
'file states, the flag is refused rather than ignored) and how the spec',
|
|
3318
3321
|
'reaches the art (--art). --images <dir> is the third and the only optional one: it',
|
|
3319
3322
|
'WRITES the rig spec\'s own images directory, relative to --out, so the rebuild needs',
|
|
3320
3323
|
'no flag.',
|
|
@@ -3431,5 +3434,15 @@ try {
|
|
|
3431
3434
|
console.error(`rigc chainfit: ${err.message}`);
|
|
3432
3435
|
process.exit(2);
|
|
3433
3436
|
}
|
|
3437
|
+
// A usage error in kind — an option that contradicts the file it was given —
|
|
3438
|
+
// and exit 2 for that reason rather than 1: nothing was compiled and nothing
|
|
3439
|
+
// was written, so it is the invocation that has to change (issue #626). It is
|
|
3440
|
+
// raised in `src/ingest.ts` rather than here because the library caller who
|
|
3441
|
+
// passes the same contradiction deserves the same refusal, and one rule in one
|
|
3442
|
+
// place is what stops the two from drifting apart.
|
|
3443
|
+
if (err instanceof IngestError) {
|
|
3444
|
+
console.error(`rigc ingest: ${err.message}`);
|
|
3445
|
+
process.exit(2);
|
|
3446
|
+
}
|
|
3434
3447
|
throw err;
|
|
3435
3448
|
}
|
package/docs/AUTHORING.md
CHANGED
|
@@ -362,7 +362,7 @@ Everything above starts from two spec files you wrote. `rigc ingest` starts from
|
|
|
362
362
|
an existing rig is a starting point instead of 250 KB of arrays to retype.
|
|
363
363
|
|
|
364
364
|
```bash
|
|
365
|
-
bun cli.ts ingest hero.json --out specs/ --
|
|
365
|
+
bun cli.ts ingest hero.json --out specs/ --images parts/
|
|
366
366
|
# .. out /abs/path/specs
|
|
367
367
|
# .. art loose
|
|
368
368
|
# .. images ../parts/ (the rig spec's own, from /abs/path/specs)
|
|
@@ -397,7 +397,7 @@ repository builds on every run.
|
|
|
397
397
|
| `--art loose` (default) | name an `image` per attachment — `<path or placeholder>.png` — so the rebuild resolves loose PNGs and rigc measures them |
|
|
398
398
|
| `--art none` | state `width`/`height` only, so the rebuild is `build --atlas-in <pack.atlas>` and every part resolves out of the pack |
|
|
399
399
|
| `--images <dir>` | **write** the rig spec's own `images` directory, spelled relative to `--out`, so the rebuild is a plain `build --rig … --motion … --out …`. Without it the field is left out and every `image` resolves against `--out` itself, which holds the specs and no art — so every rebuild has to repeat `build --images <dir>`. Refused together with `--art none`, which writes no `image` for it to be the base of |
|
|
400
|
-
| `--stage x,y,w,h` | the setup bounding box, **for a skeleton that declares none**. An editor export *may* be one; every export under `examples/` carries a box and `ingest` reads it straight through |
|
|
400
|
+
| `--stage x,y,w,h` | the setup bounding box, **for a skeleton that declares none**. An editor export *may* be one; every export under `examples/` carries a box and `ingest` reads it straight through — so passing the flag at one of them is **refused**, naming both boxes, rather than silently doing nothing ([#626](https://github.com/firejune/rigc/issues/626)) |
|
|
401
401
|
| `--name <n>` | the rig spec's `name`, which the motion spec's `archetype` must equal (default: the file's basename) |
|
|
402
402
|
|
|
403
403
|
⚠️ **`ingest --images` and `build --images` point opposite ways.** `build --images`
|
|
@@ -414,6 +414,16 @@ editor's setup box. So a file that declares none is a **blocker**, named, unless
|
|
|
414
414
|
`--stage x,y,w,h` supplies it; supply it from the project the file came from, or from
|
|
415
415
|
the editor's own canvas.
|
|
416
416
|
|
|
417
|
+
⛔ **The flag is refused beside a box the file states.** Two sources for one value, and
|
|
418
|
+
the file is the one that was measured — so `ingest` names both boxes and stops rather
|
|
419
|
+
than writing one of them and saying nothing. Drop the flag, or correct `skeleton` in the
|
|
420
|
+
source if its box is wrong ([#626](https://github.com/firejune/rigc/issues/626)). What
|
|
421
|
+
it does **not** do is refuse an *omitted origin*: inside a declared extent an omitted
|
|
422
|
+
`x`/`y` is `0` — the reading `build` emits and `diff` compares
|
|
423
|
+
([#620](https://github.com/firejune/rigc/issues/620)) — so the written spec states it
|
|
424
|
+
and a `LOSS HEADER_ORIGIN` line says the source omitted it and that the rebuild will
|
|
425
|
+
spell it ([#622](https://github.com/firejune/rigc/issues/622)).
|
|
426
|
+
|
|
417
427
|
⚠️ **This said an editor export "never" carries one until
|
|
418
428
|
[#594](https://github.com/firejune/rigc/issues/594) measured the corpus.** All twelve
|
|
419
429
|
exports under `examples/` declare `x`, `y`, `width` and `height`, `ingest` takes the
|
|
@@ -443,7 +453,7 @@ the first:
|
|
|
443
453
|
| --- | --- |
|
|
444
454
|
| `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read — `linkedmesh`, `point`, an attachment `sequence`, an unknown field on a bone, slot or constraint, a timeline family the motion spec has no track for. The command exits non-zero **and still writes both specs**, because a spec plus a list of what is missing from it beats no spec |
|
|
445
455
|
| `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
|
|
446
|
-
| `LOSS` | the skeleton
|
|
456
|
+
| `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how. A path attachment's `lengths` is the one that matters — it is `PathConstraint`'s own four-sample measurement rather than an arc length (#560), so a transcribed one would freeze whatever produced the source. The header ones are cheaper: `HEADER_BOOKKEEPING` for a field the spec has no home for, `HEADER_REDERIVED` for the version string, `HEADER_ORIGIN` for an origin the source left to the format and the rebuild writes out (#622) |
|
|
447
457
|
|
|
448
458
|
📝 **Do not delete the `note`.** Both written specs carry one saying the file is
|
|
449
459
|
decompiled and naming the skeleton it came from. A decompiled spec is
|
|
@@ -1982,6 +1992,20 @@ one wins today. ⛔ rigc does **not** set the flag for you. The compiler never
|
|
|
1982
1992
|
invents a value that is not in the spec, and a rig whose composition was chosen by
|
|
1983
1993
|
the tool is one nobody can reason about.
|
|
1984
1994
|
|
|
1995
|
+
⚠️ **And below full authority it is a weighting rather than a quieter erasure**
|
|
1996
|
+
([#399](https://github.com/firejune/rigc/issues/399)). An additive slider scales
|
|
1997
|
+
its whole contribution by its own `mix`, so two of them at any pair of mixes are
|
|
1998
|
+
still the sum — which is why `A40` skipping below full authority is right: what
|
|
1999
|
+
happens there is a weighting, not the erasure it refuses. A **non-additive**
|
|
2000
|
+
slider at `mix` α applies `current + (value + setup − current) × α`, a lerp *from
|
|
2001
|
+
the pose it found*, so the earlier slider is not erased — it is attenuated by
|
|
2002
|
+
`1 − α`. `mix × contribution` is not the arithmetic there, so a later dial turned
|
|
2003
|
+
part of the way down takes that share of every earlier slider on the target with
|
|
2004
|
+
it, on every frame, with the gate green — the second reason to write
|
|
2005
|
+
`"additive": true` on **every** slider that shares a target and not only on the
|
|
2006
|
+
later one. `PS130` in `selftest.ts` poses both models rather than quoting the
|
|
2007
|
+
runtime, and [`docs/FACE.md`](FACE.md) §8 is the same rule on a face's two axes.
|
|
2008
|
+
|
|
1985
2009
|
⚠️ **And `"additive": true` is not always available.** Only some timelines support
|
|
1986
2010
|
additive application at all: bone, deform, transform-constraint, path `position`,
|
|
1987
2011
|
physics `wind`/`gravity`, and a slider's own `mix`. A **slot colour, an attachment
|
package/docs/FACE.md
CHANGED
|
@@ -1180,6 +1180,31 @@ breaks the moment the two share a target — in the worked example both `turn` a
|
|
|
1180
1180
|
`tilt` key `headroll`. §7's paragraph on sliders is the mechanism and
|
|
1181
1181
|
`A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` is the refusal.
|
|
1182
1182
|
|
|
1183
|
+
✅ **And the space between the two dials is measured rather than inferred**
|
|
1184
|
+
([#399](https://github.com/firejune/rigc/issues/399)). Two additive sliders posed
|
|
1185
|
+
at a grid of *both* values — and over a rotation and a translation, so the claim
|
|
1186
|
+
is not one property's — are the closed-form sum at every cell of it, to float64:
|
|
1187
|
+
each slider maps its own reading to a time by §3.5.2's rule, its animation is read
|
|
1188
|
+
there, and the two contributions add. ⭐ That is a property of the **interior**
|
|
1189
|
+
and not of the corners, which is the whole reason it needed a grid: `PS129` drives
|
|
1190
|
+
the same comparison with an `ease` on one of the two animations, and every corner
|
|
1191
|
+
of the space still reads as correct while most of the inside has moved. ⇒ **a
|
|
1192
|
+
lookup table wants linear keys for a second reason** — not only that the face
|
|
1193
|
+
would drift while the value sat still, but that a curve is invisible to any check
|
|
1194
|
+
that reads an axis at its ends.
|
|
1195
|
+
|
|
1196
|
+
⚠️ **And `mix` below 1 is not what it looks like when the later slider is not
|
|
1197
|
+
additive.** An additive slider scales its whole contribution by its own `mix`, so
|
|
1198
|
+
two of them at any pair of mixes are still the sum — which is why `A40` skipping
|
|
1199
|
+
below full authority is right: what happens there is a weighting, not the erasure
|
|
1200
|
+
it refuses. A **non-additive** slider at `mix` α applies
|
|
1201
|
+
`current + (value + setup − current) × α`, a lerp *from the pose it found*, so the
|
|
1202
|
+
earlier slider is not erased — it is attenuated by `1 − α`. A pitch dial turned
|
|
1203
|
+
halfway down takes that share of the yaw with it, on every frame, with the gate
|
|
1204
|
+
green. ⇒ write `"additive": true` on **every** slider that shares a target and
|
|
1205
|
+
not only on the later one, which is what the paragraph above already asks for and
|
|
1206
|
+
this is the second reason for.
|
|
1207
|
+
|
|
1183
1208
|
✅ **The editor half, measured.** This paragraph said *unknown* until the round
|
|
1184
1209
|
trip was taken with `tools/editor_roundtrip.ts` on a licensed editor (data
|
|
1185
1210
|
version 4.3.26) against a 4.3.13 build of
|
|
@@ -1403,6 +1428,15 @@ shift through the vertex's coordinate instead of its index, so the next
|
|
|
1403
1428
|
renumbering cannot move it. A `vertices` run is positional by format — that is
|
|
1404
1429
|
`fromVertex`'s whole job — and a *generator* of one has no reason to be.
|
|
1405
1430
|
|
|
1431
|
+
🔒 **And that repair is now measured rather than asserted.** `bun run selftest`
|
|
1432
|
+
runs this script again against a rig whose vertices have been renumbered every
|
|
1433
|
+
way the compiler accepts — the outline rotated along its own walk, the outline
|
|
1434
|
+
reflected, the interior reordered — and requires the run it writes to come back
|
|
1435
|
+
as the same geometry once the renumbering is undone, while the run's own
|
|
1436
|
+
positions have visibly moved. Nothing here declares that property: the subject
|
|
1437
|
+
is any script on any page whose product is a `vertices` run, read off the
|
|
1438
|
+
product.
|
|
1439
|
+
|
|
1406
1440
|
**What comes back from both:**
|
|
1407
1441
|
|
|
1408
1442
|
| | good | (a) one band inverted | (b) mesh folded |
|
package/docs/INGEST.md
CHANGED
|
@@ -467,8 +467,10 @@ resolved rather than guessed, for §0.2's reason: `spineboy/export` holds two, a
|
|
|
467
467
|
|
|
468
468
|
**What it will not do is invent.** Everything the spec format cannot hold is a
|
|
469
469
|
finding with a code — `BLOCK` for a construct the rebuild will be missing, `JUDGE`
|
|
470
|
-
for the two values a skeleton does not carry, `LOSS`
|
|
471
|
-
|
|
470
|
+
for the two values a skeleton does not carry, `LOSS` wherever the source's spelling and
|
|
471
|
+
rigc's differ on purpose (a number rigc re-derives, a field the spec has no home for, or
|
|
472
|
+
a default the source left to the format and the rebuild writes out). A blocker exits
|
|
473
|
+
non-zero and still writes both files.
|
|
472
474
|
|
|
473
475
|
**Two values are not in a skeleton**, so `ingest` asks rather than guesses:
|
|
474
476
|
|
|
@@ -483,7 +485,11 @@ re-derives on purpose. A blocker exits non-zero and still writes both files.
|
|
|
483
485
|
reports it as two measures of its own (`stage_present`, `stage_box`, since
|
|
484
486
|
[#578](https://github.com/firejune/rigc/issues/578)) and they are `(reported)`, so
|
|
485
487
|
nothing on the ladder reads them and an absurd box is green nearly everywhere. The
|
|
486
|
-
corpus half of the selftest's `IG` suite is the one gate that does read them
|
|
488
|
+
corpus half of the selftest's `IG` suite is the one gate that does read them. ⛔ **The
|
|
489
|
+
flag is refused beside a box the file states** — two sources for one value, both named,
|
|
490
|
+
and the file is the record of what was measured
|
|
491
|
+
([#626](https://github.com/firejune/rigc/issues/626)). It used to be read only *after*
|
|
492
|
+
the file's box, so `--stage` at any of the twelve did nothing and said nothing;
|
|
487
493
|
- **each animation's duration** — the format has no such field. The largest key time
|
|
488
494
|
is used, stated in the motion spec's `note`, and recorded as a finding per
|
|
489
495
|
animation. Edit it if you know the real number.
|
|
@@ -620,7 +626,7 @@ rebuild against its source produces differences of exactly three kinds, in every
|
|
|
620
626
|
| Kind | Example, candidate vs reference | Why |
|
|
621
627
|
| --- | --- | --- |
|
|
622
628
|
| **header bookkeeping**, 3 per file | `skeleton.hash: undefined vs "VFWbaK2UoCM"`, `skeleton.audio: undefined vs null`, `skeleton.spine: "4.3.13" vs "4.3.75-beta"` | the rig spec has no field for `hash` or `audio`, and the version is the runtime rigc links. `ingest` reports all three as findings — `HEADER_BOOKKEEPING` and `HEADER_REDERIVED` |
|
|
623
|
-
| **an omitted default written out** | `…rotate[0].time: 0 vs undefined` | the editor omits a zero `time`; rigc writes it. AUTHORING §10.5's *do not imitate the exporter's omissions*, from the other side |
|
|
629
|
+
| **an omitted default written out** | `…rotate[0].time: 0 vs undefined` | the editor omits a zero `time`; rigc writes it. AUTHORING §10.5's *do not imitate the exporter's omissions*, from the other side. The header has one of these too and it is the one `ingest` now names: a stage at the origin is written `width`/`height` with no `x`/`y`, and the rebuild spells both — `LOSS HEADER_ORIGIN`, with the box unchanged ([#622](https://github.com/firejune/rigc/issues/622)). No file in this corpus takes that branch: all twelve declare an origin away from 0 |
|
|
624
630
|
| **the emitted precision** | `…curve[0]: 0.066667 vs 0.06666667`, `uvs[0]: 0 vs 2.554152e-7` | rigc emits six decimals |
|
|
625
631
|
|
|
626
632
|
⇒ **So the corpus gate is `diff` at 1.000 rather than a byte comparison**, and it is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/ingest/SKILL.md
CHANGED
|
@@ -29,14 +29,15 @@ not do for you.
|
|
|
29
29
|
## Start here: `rigc ingest`
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
rigc ingest hero.json --out specs/
|
|
32
|
+
rigc ingest hero.json --out specs/
|
|
33
33
|
rigc build --rig specs/rig.json --motion specs/motion.json --images parts/ --out build/
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
It reads the skeleton — **only** the skeleton — and writes the rig spec and motion
|
|
37
37
|
spec that rebuild it, byte for byte. Two values it refuses rather than guessing: the
|
|
38
38
|
**stage** (`--stage`, for a skeleton that declares none — an editor export *may* be
|
|
39
|
-
one, though every one in the example corpus carries a box
|
|
39
|
+
one, though every one in the example corpus carries a box, and passing the flag at a
|
|
40
|
+
file that declares a box is refused rather than ignored) and each animation's
|
|
40
41
|
**duration** (the largest key time, recorded as a finding).
|
|
41
42
|
Read `findings.json`: a `BLOCK` line means the rebuild will be missing something and
|
|
42
43
|
the command exits non-zero. Keep the `note` both specs carry. INGEST §2.0.
|
package/src/ingest.ts
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* log. Exactly two values are not in a skeleton at all (the stage and an
|
|
27
27
|
* animation's duration) and both are `judgement` findings; every construct the
|
|
28
28
|
* spec format cannot hold is a `blocker`; everything rigc re-derives rather than
|
|
29
|
-
* carries is `lossy`.
|
|
29
|
+
* carries is `lossy`. The one thing it refuses outright rather than recording is
|
|
30
|
+
* an OPTION that contradicts the file — see `IngestError`.
|
|
30
31
|
*
|
|
31
32
|
* ## What it does not read
|
|
32
33
|
*
|
|
@@ -48,6 +49,24 @@ import { MOTION_SPEC_VERSION, parseMotionSpec } from './motion.ts';
|
|
|
48
49
|
import { parseRigSpec, RIG_KEYS, RIG_SPEC_VERSION, type RigSpec } from './rig.ts';
|
|
49
50
|
import type { MotionSpec } from './types.ts';
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The invocation this module refuses outright, rather than recording.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ **A finding is about the FILE; this is about the call.** Everything below
|
|
56
|
+
* that a skeleton cannot answer is a `finding` and the specs are still written,
|
|
57
|
+
* because a spec plus a list of what is missing from it beats no spec. An
|
|
58
|
+
* `IngestError` is the other thing: an option that contradicts the file it was
|
|
59
|
+
* given, where writing anything at all would be writing something the caller did
|
|
60
|
+
* not ask for. It is one re-run away from everything, which is what makes
|
|
61
|
+
* refusing cheaper than recording here (issue #626).
|
|
62
|
+
*/
|
|
63
|
+
export class IngestError extends Error {
|
|
64
|
+
constructor(message: string) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = 'IngestError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
// ---------------------------------------------------------------------------
|
|
52
71
|
// findings
|
|
53
72
|
// ---------------------------------------------------------------------------
|
|
@@ -59,8 +78,12 @@ import type { MotionSpec } from './types.ts';
|
|
|
59
78
|
* NOT be the one that was read. Non-zero exit.
|
|
60
79
|
* - `judgement` — the skeleton does not carry it and somebody has to decide.
|
|
61
80
|
* There are exactly two: the stage, and an animation's duration.
|
|
62
|
-
* - `lossy` — the skeleton
|
|
63
|
-
*
|
|
81
|
+
* - `lossy` — the skeleton's spelling and rigc's differ, on purpose, and the
|
|
82
|
+
* difference is named: a value rigc re-derives rather than takes (`lengths`,
|
|
83
|
+
* the `spine` version), a field the spec has no home for (`hash`, `audio`), or
|
|
84
|
+
* a default the source left to the format and the rebuild writes out
|
|
85
|
+
* (`HEADER_ORIGIN`, issue #622). The rebuilt file is a different file in that
|
|
86
|
+
* field; it is not a different rig.
|
|
64
87
|
*/
|
|
65
88
|
export type IngestFindingKind = 'blocker' | 'judgement' | 'lossy';
|
|
66
89
|
|
|
@@ -112,7 +135,17 @@ export interface IngestOptions {
|
|
|
112
135
|
* `cli.ts` refuses that pair rather than writing a field nothing reads.
|
|
113
136
|
*/
|
|
114
137
|
images?: string;
|
|
115
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Supplied stage, for a skeleton that declares none.
|
|
140
|
+
*
|
|
141
|
+
* ⛔ **Beside a skeleton that declares one this is an `IngestError`, not an
|
|
142
|
+
* override** (issue #626). It used to be read only after the early return in
|
|
143
|
+
* `ingestHeader`, so a caller who passed it alongside a declared box got the
|
|
144
|
+
* file's box, no finding and exit 0 — and the sharper case is the caller who
|
|
145
|
+
* meant to correct a wrong box and believed they had. The file is the record
|
|
146
|
+
* of what was measured; two sources for one value is a question, and rigc
|
|
147
|
+
* refuses it rather than answering it quietly.
|
|
148
|
+
*/
|
|
116
149
|
stage?: IngestStage;
|
|
117
150
|
/** The source file's basename, for the provenance note. No path: no leak. */
|
|
118
151
|
source: string;
|
|
@@ -573,6 +606,32 @@ export function ingest(skeleton: unknown, opts: IngestOptions): IngestResult {
|
|
|
573
606
|
|
|
574
607
|
type Note = (kind: IngestFindingKind, code: string, where: string, detail: string) => void;
|
|
575
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Does this header declare a stage?
|
|
611
|
+
*
|
|
612
|
+
* 🔒 One reading, three callers below — the refusal, the origin default and the
|
|
613
|
+
* early return — because they are three statements about the same header and two
|
|
614
|
+
* spellings of "declares a stage" would be two things that have to agree. The
|
|
615
|
+
* EXTENT is what declares one: an origin for a box that is not there is a shape
|
|
616
|
+
* no export carries, which is how `diff`'s `stageFacts` and `compile`'s stage
|
|
617
|
+
* guard already read it.
|
|
618
|
+
*/
|
|
619
|
+
function declaresStage(head: JsonObject): boolean {
|
|
620
|
+
return head.width !== undefined && head.height !== undefined;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* A stage as one string, for a message that has to put two of them side by side.
|
|
625
|
+
*
|
|
626
|
+
* The origin is spelled `0` where it is absent, for the reason `HEADER_ORIGIN`
|
|
627
|
+
* writes it: inside a declared extent that is what the omission means, so a
|
|
628
|
+
* refusal that printed the file's box as `undefined,undefined,…` would be
|
|
629
|
+
* quoting the file against the reading every other part of this tree holds.
|
|
630
|
+
*/
|
|
631
|
+
function spellStage(x: unknown, y: unknown, width: unknown, height: unknown): string {
|
|
632
|
+
return [x ?? 0, y ?? 0, width, height].map((value) => String(value)).join(',');
|
|
633
|
+
}
|
|
634
|
+
|
|
576
635
|
/**
|
|
577
636
|
* The rig spec's `skeleton` block — and the one judgement in this module.
|
|
578
637
|
*
|
|
@@ -585,16 +644,35 @@ type Note = (kind: IngestFindingKind, code: string, where: string, detail: strin
|
|
|
585
644
|
*
|
|
586
645
|
* ⚠️ This said an editor export's `skeleton` block is `hash`, `spine`, `images`,
|
|
587
646
|
* `audio` **and no box at all** until issue #594 measured the corpus: all twelve
|
|
588
|
-
* exports under `examples/` carry `x`/`y`/`width`/`height`, and the
|
|
589
|
-
* below is the
|
|
590
|
-
* and this module has no example of one.
|
|
647
|
+
* exports under `examples/` carry `x`/`y`/`width`/`height`, and the declared-stage
|
|
648
|
+
* branch below is the one they take. The blocker is for a file that really has
|
|
649
|
+
* none, and this module has no example of one.
|
|
591
650
|
*
|
|
592
651
|
* ⭐ It is still the judgement that costs least to get wrong. `diff` does report
|
|
593
652
|
* the box — `stage_present` and `stage_box`, since issue #578 — but they sit in
|
|
594
653
|
* the `(reported)` block that no rung consults, so a deliberately absurd unit box
|
|
595
654
|
* is green everywhere a candidate is scored.
|
|
655
|
+
*
|
|
656
|
+
* 🔇 **Both of its silences were here, and both were around the DECLARED branch
|
|
657
|
+
* rather than the missing one.** That branch used to be a bare early return, so
|
|
658
|
+
* an origin the source omitted left no trace at all (issue #622) and a `--stage`
|
|
659
|
+
* given beside a declared box was read after it and therefore never (issue #626).
|
|
660
|
+
* Neither was wrong — the rebuild carried the right numbers both times — which is
|
|
661
|
+
* exactly the shape this whole module exists to convert into something named:
|
|
662
|
+
* a decompiler that is right for a reason it never states is a decompiler nobody
|
|
663
|
+
* can check.
|
|
596
664
|
*/
|
|
597
665
|
function ingestHeader(head: JsonObject, opts: IngestOptions, note: Note): JsonObject {
|
|
666
|
+
// 🚨 Before a line of transcription, because a header the caller contradicted
|
|
667
|
+
// is not a header to start writing a spec from (issue #626).
|
|
668
|
+
if (declaresStage(head) && opts.stage !== undefined) {
|
|
669
|
+
throw new IngestError(
|
|
670
|
+
`the skeleton declares a stage of ${spellStage(head.x, head.y, head.width, head.height)} and --stage supplied ` +
|
|
671
|
+
`${spellStage(opts.stage.x, opts.stage.y, opts.stage.width, opts.stage.height)}: two sources for one value. ` +
|
|
672
|
+
'rigc will not overwrite a box the file states — the file is the record of what was measured, and the flag ' +
|
|
673
|
+
'is for a skeleton that declares none. Drop --stage, or correct `skeleton` in the source if its box is wrong',
|
|
674
|
+
);
|
|
675
|
+
}
|
|
598
676
|
const out: JsonObject = {};
|
|
599
677
|
for (const field of RIG_KEYS.RigSkeletonHeader) if (head[field] !== undefined) out[field] = head[field];
|
|
600
678
|
for (const key of Object.keys(head)) {
|
|
@@ -621,7 +699,33 @@ function ingestHeader(head: JsonObject, opts: IngestOptions, note: Note): JsonOb
|
|
|
621
699
|
`the editor writes "${key}" and the rig spec has no field for it; it is dropped and nothing reads it back`,
|
|
622
700
|
);
|
|
623
701
|
}
|
|
624
|
-
if (
|
|
702
|
+
if (declaresStage(head)) {
|
|
703
|
+
// ⭐ **Inside a declared extent, an omitted origin IS `0`** (issue #620),
|
|
704
|
+
// which is a reading of the format rather than a value invented for a gap:
|
|
705
|
+
// `compile` assembles `header.x = rig.skeleton?.x ?? 0` under this same
|
|
706
|
+
// guard, `diff`'s `stageFacts` reads the omission the same way, and
|
|
707
|
+
// `stageFacts`'s own comment carries the four measurements behind it. So the
|
|
708
|
+
// spec states what the file meant instead of leaving the rebuild to a
|
|
709
|
+
// default in another module — and the half that has to be said out loud is
|
|
710
|
+
// the other one: the rebuilt header SPELLS a field the source omitted
|
|
711
|
+
// (issue #622).
|
|
712
|
+
const omitted = ['x', 'y'].filter((field) => head[field] === undefined);
|
|
713
|
+
if (omitted.length > 0) {
|
|
714
|
+
out.x = head.x ?? 0;
|
|
715
|
+
out.y = head.y ?? 0;
|
|
716
|
+
note(
|
|
717
|
+
'lossy',
|
|
718
|
+
'HEADER_ORIGIN',
|
|
719
|
+
`skeleton.${omitted.join('/')}`,
|
|
720
|
+
`the source declares a ${String(head.width)}x${String(head.height)} stage and omits ` +
|
|
721
|
+
`${omitted.map((field) => `"${field}"`).join(' and ')}; inside a declared extent an omitted origin is 0, ` +
|
|
722
|
+
'which is what `compile` emits and what `diff` compares (#620), so the rig spec states x=' +
|
|
723
|
+
`${String(out.x)}, y=${String(out.y)} rather than leaving the rebuild to a default in another module. ` +
|
|
724
|
+
`⚠️ The rebuild WILL spell ${omitted.length > 1 ? 'those fields' : 'that field'}: same box, different bytes`,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
625
729
|
if (opts.stage === undefined) {
|
|
626
730
|
note(
|
|
627
731
|
'blocker',
|