spine-rigc 0.24.0 → 0.25.1

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, 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/ --stage 0,0,1024,768 --images parts/
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. ⚠️ This said *"an editor export carries none"* until #594 measured
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.
@@ -613,7 +616,12 @@ export, every `diff` measure that moved, `check`'s mean MAE and worst drift per
613
616
  animation **for each skin the build declares** — one render-and-check block per
614
617
  skin, with a per-skin roll-up under them, because a rig's contested art lives in
615
618
  its named skins and a single un-skinned check draws none of it — and a
616
- field-by-field list of what the editor rewrote. On its first
619
+ field-by-field list of what the editor rewrote. Every step quotes what its child
620
+ said when that child did not do what it was for, the renderers included; a skin
621
+ **neither** side can draw — a hit-box rig, say — is a **SKIP** naming that, not a
622
+ red, because `check` had nothing to compare and `diff` and `validate` have
623
+ already measured the rig. One side drawing where the other does not is the
624
+ divergence the trip exists to find and stays a failure. On its first
617
625
  run it found three emitter defects — [#368](https://github.com/firejune/rigc/issues/368),
618
626
  [#369](https://github.com/firejune/rigc/issues/369),
619
627
  [#370](https://github.com/firejune/rigc/issues/370) — and then showed that a
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';
@@ -2961,9 +2961,13 @@ const FLAG_MEANINGS: Record<string, string> = {
2961
2961
  '--images <dir>` on every rebuild), `none` states width/height only for `build --atlas-in <pack>` to ' +
2962
2962
  'resolve (default: loose)',
2963
2963
  stage:
2964
- "the setup bounding box — `skeleton.x,y,width,height`. An editor export carries none and rigc refuses a " +
2965
- 'compile without one; posing the rig gives the ANIMATED extent, which is a different number, so this is the ' +
2966
- "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. ⛔ 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',
2967
2971
  help: "show this command's flags and exit",
2968
2972
  };
2969
2973
 
@@ -3308,13 +3312,15 @@ const USAGE = [
3308
3312
  'ingest runs build backwards: it reads a Spine 4.3 skeleton.json and writes the rig',
3309
3313
  'spec and motion spec that rebuild it, so an existing skeleton becomes a starting',
3310
3314
  'point instead of something to retype:',
3311
- ' rigc ingest hero.json --out specs/ --stage 0,0,1024,768 rig.json + motion.json',
3315
+ ' rigc ingest hero.json --out specs/ --images parts/ rig.json + motion.json',
3312
3316
  'The contract is an equality, not a rulebook: build(ingest(x)) is x, byte for byte.',
3313
3317
  'It reads the skeleton and nothing else — no .spine project, no binary .skel, no',
3314
3318
  'atlas — so two things are the caller\'s and are refused rather than guessed: the',
3315
- 'setup stage (--stage; an export carries none) and how the spec reaches the art',
3316
- '(--art). --images <dir> is the third and the only optional one: it WRITES the rig',
3317
- 'spec\'s own images directory, relative to --out, so the rebuild needs no flag.',
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',
3321
+ 'reaches the art (--art). --images <dir> is the third and the only optional one: it',
3322
+ 'WRITES the rig spec\'s own images directory, relative to --out, so the rebuild needs',
3323
+ 'no flag.',
3318
3324
  'Everything the spec format cannot hold is printed as a named finding and',
3319
3325
  'exits non-zero, with both files still written, because a spec plus a list of what',
3320
3326
  'is missing from it beats no spec at all.',
@@ -3428,5 +3434,15 @@ try {
3428
3434
  console.error(`rigc chainfit: ${err.message}`);
3429
3435
  process.exit(2);
3430
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
+ }
3431
3447
  throw err;
3432
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/ --stage 0,0,1024,768 --images parts/
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. **Required for an editor export**, which carries none |
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`
@@ -407,13 +407,36 @@ they name the same field — and `ingest` spells the value with the same functio
407
407
  `build` spells `skeleton.images` with, so a spec and the skeleton it came from say
408
408
  where the parts are in one convention.
409
409
 
410
- 🚨 **The stage is the one value `ingest` will not guess.** rigc always emits
411
- `skeleton.width`/`height` and an editor export never does, so a foreign file needs
412
- `--stage`; without it the missing box is a **blocker**, named. It is not derivable —
413
- posing the rig gives the *animated* extent, which is a different number from the
414
- editor's setup box — and it is the value that costs least to get wrong, because no
415
- measure `diff` reports reads the skeleton header at all. Supply it from the project
416
- the file came from, or from the editor's own canvas.
410
+ 🚨 **The stage is one of the two values `ingest` will not guess.** A skeleton JSON
411
+ *need not* carry `skeleton.width`/`height`, and when it does not rigc cannot derive
412
+ one — posing the rig gives the *animated* extent, which is a different number from the
413
+ editor's setup box. So a file that declares none is a **blocker**, named, unless
414
+ `--stage x,y,w,h` supplies it; supply it from the project the file came from, or from
415
+ the editor's own canvas.
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
+
427
+ ⚠️ **This said an editor export "never" carries one until
428
+ [#594](https://github.com/firejune/rigc/issues/594) measured the corpus.** All twelve
429
+ exports under `examples/` declare `x`, `y`, `width` and `height`, `ingest` takes the
430
+ early return on every one of them, and not one needs the flag. What an editor export
431
+ *may* do is carry none: a rigc build that declares no stage
432
+ ([#578](https://github.com/firejune/rigc/issues/578)) came back from a Spine 4.3.26
433
+ round trip with a header of `hash`, `spine`, `images`, `audio` and **no box at all** —
434
+ the editor preserves the absence rather than inventing a stage
435
+ ([#616](https://github.com/firejune/rigc/issues/616)). So `--stage` is for a file that
436
+ really has none, and this repository's corpus holds no example of one. It is still the
437
+ value that costs least to get wrong: `diff` reports the box as `stage_present` and
438
+ `stage_box` ([#578](https://github.com/firejune/rigc/issues/578)) and both are
439
+ `(reported)`, so nothing on the ladder consults them.
417
440
 
418
441
  ⚠️ **The duration is a convention, and it is recorded as one.** Skeleton JSON has no
419
442
  duration field. The largest key time is the only derivable answer and it is what a
@@ -430,7 +453,7 @@ the first:
430
453
  | --- | --- |
431
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 |
432
455
  | `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
433
- | `LOSS` | the skeleton says it and rigc re-derives it, on purpose. 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 |
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) |
434
457
 
435
458
  📝 **Do not delete the `note`.** Both written specs carry one saying the file is
436
459
  decompiled and naming the skeleton it came from. A decompiled spec is
@@ -822,7 +845,17 @@ Three readings stay apart, and the middle one is the point of the other two:
822
845
  reports **SKIP** on one, because there is no full frame for a mesh to span. And
823
846
  `rigc diff` reports it — `skeleton.stage_present` and `skeleton.stage_box`, in the
824
847
  header block at the top of the report — so a stage somebody invented now reads
825
- below 1.000 against a source that has none. Both are reported and gate nothing, for
848
+ below 1.000 against a source that has none.
849
+
850
+ ⭐ **A stage at `0,0` is not a stage-less one, and an editor export spells it by
851
+ saying nothing.** The editor omits a header field that is at its default, so a
852
+ skeleton whose box sits at the origin exports as a `width` and a `height` with no
853
+ `x`/`y`; `stage_box` reads that omission as the `0` it means, and its line says so
854
+ — *the extent as stated, an omitted origin as the 0 it means*. So `4/4` on a build
855
+ of yours against an export of that same build is the right answer rather than a
856
+ tolerance, and an origin that really did move still reads below 1.000. The extent
857
+ is the half that is read exactly as stated: omit a `width` and you have declared no
858
+ stage, which `stage_present` is the measure of. Both are reported and gate nothing, for
826
859
  the reason every reported measure is: no reading of the rendered frames could have
827
860
  decided a setup-pose bounding box. The measure inventory that says so lives in
828
861
  [BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md), which
@@ -927,7 +960,12 @@ pointing into it is not.
927
960
  ⚠️ **`rigc render` still refuses such a build**, by name and before it draws
928
961
  anything: `… posed no drawable attachment in any animation or in its setup pose —
929
962
  there is nothing to draw`. That is the honest division — the rig is valid Spine
930
- data, and there is no picture of it.
963
+ data, and there is no picture of it. `rigc check` says the same thing one step
964
+ on, since it has no frames to compare; `tools/editor_roundtrip.ts` quotes both
965
+ renderers and reports its step 5 as a **SKIP** naming that, and the round trip
966
+ comes back green on the strength of `validate` and `diff`
967
+ ([#621](https://github.com/firejune/rigc/issues/621)). A skin only **one** side
968
+ can draw is the other case entirely, and stays red.
931
969
 
932
970
  **Region attachment** ([Spine: region attachments](http://esotericsoftware.com/spine-regions)),
933
971
  the default `type`:
package/docs/FACE.md CHANGED
@@ -1403,6 +1403,15 @@ shift through the vertex's coordinate instead of its index, so the next
1403
1403
  renumbering cannot move it. A `vertices` run is positional by format — that is
1404
1404
  `fromVertex`'s whole job — and a *generator* of one has no reason to be.
1405
1405
 
1406
+ 🔒 **And that repair is now measured rather than asserted.** `bun run selftest`
1407
+ runs this script again against a rig whose vertices have been renumbered every
1408
+ way the compiler accepts — the outline rotated along its own walk, the outline
1409
+ reflected, the interior reordered — and requires the run it writes to come back
1410
+ as the same geometry once the renumbering is undone, while the run's own
1411
+ positions have visibly moved. Nothing here declares that property: the subject
1412
+ is any script on any page whose product is a `vertices` run, read off the
1413
+ product.
1414
+
1406
1415
  **What comes back from both:**
1407
1416
 
1408
1417
  | | good | (a) one band inverted | (b) mesh folded |
package/docs/INGEST.md CHANGED
@@ -253,7 +253,7 @@ rigc diff
253
253
 
254
254
  skeleton (reported) (no mean) over 2 measures — the stage, which no reading of the frames could decide
255
255
  1.000 stage_present 1/1 both sides declare a setup-pose stage, or neither does — …
256
- 1.000 stage_box 4/4 the stage is the same box (x, y, width, height, exactly as stated) — …
256
+ 1.000 stage_box 4/4 the stage is the same box (x, y, width, height — the extent as stated, an omitted origin as the 0 it means) — …
257
257
 
258
258
  bones mean 1.000 over 8 measures
259
259
  1.000 count 3/3 how many bones
@@ -288,12 +288,31 @@ units and every one of the 49 measures still reads **1.000**. ⇒ Never take a g
288
288
  `diff` as evidence that a geometric edit did not land, and never take it as evidence
289
289
  that one did.
290
290
 
291
+ ⚠️ **The corpus gate has a value-level measure and this command does not expose it**
292
+ (§2.3, and `docs/BENCHMARK.md`'s *The nine value measures*). The reason is an input
293
+ rather than a policy: comparing values means reading both files through `spine-core`,
294
+ and a skeleton whose attachments carry a `sequence` cannot be parsed without the atlas
295
+ that resolves it — so the measure takes two skeletons **and two packs**, which
296
+ `rigc diff <a.json> <b.json>` does not have. The sentence above is about this command
297
+ and stays true of it.
298
+
291
299
  ⚠️ **The one exception is the skeleton's own declared box**, and it is an exception to
292
300
  the sentence and not to the rule: `skeleton.stage_box` compares four world numbers,
293
- but they are numbers an exporter *wrote into the header* rather than a pose anything
301
+ but they are numbers an exporter *declared in the header* rather than a pose anything
294
302
  measured, and the block they sit in gates nothing. Moving a pivot does not move them
295
303
  either.
296
304
 
305
+ ⭐ **Declared is not the same as written down, and for the origin it is the
306
+ difference between a green round trip and a false finding**
307
+ ([#620](https://github.com/firejune/rigc/issues/620)). The editor omits a header
308
+ field at its default, so a stage sitting at `0,0` exports as a `width` and a
309
+ `height` and no `x`/`y` at all — there is no other spelling for it. The measure
310
+ reads that omission as the `0` it means, which is why a rigc build whose stage is at
311
+ the origin and its own export of that build read `stage_box` **4/4**; reading the
312
+ four "exactly as stated" scored the same box **2/4**. The extent is still read
313
+ exactly as stated: it is what decides whether there is a stage at all, so a missing
314
+ `width` is an absent stage rather than a stage of width zero.
315
+
297
316
  ⛔ **And its ratios are not a score.** [`src/diff.ts`](../src/diff.ts) says so in the
298
317
  type itself (*"Unweighted mean of the measures below. NOT a quality score"*), and the
299
318
  report repeats it at the foot. It measures *agreement with a particular reference*,
@@ -437,16 +456,21 @@ a file rigc did not write.
437
456
  them** ([#594](https://github.com/firejune/rigc/issues/594)). Every
438
457
  `examples/*/export/*.json` is ingested with `--art none`, rebuilt through the pack
439
458
  beside it, and `diff`ed against the file it was read from: **12 of 12 come back with 0
440
- blockers and 1.000 on all 49 ratio-bearing measures and all 5 reported ones.** Byte
459
+ blockers and 1.000 on all 49 ratio-bearing measures and all 5 reported ones** — and,
460
+ since [#615](https://github.com/firejune/rigc/issues/615), on all **nine value
461
+ measures** too, over **193,927** compared values. Byte
441
462
  identity is not the claim there and the reason is the input, not the round trip — §2.3
442
- has the three kinds of difference, measured. ⚠️ Which pack is "the one beside it" is
463
+ has the three kinds of difference, measured, and what the value measures do and do not
464
+ reach. ⚠️ Which pack is "the one beside it" is
443
465
  resolved rather than guessed, for §0.2's reason: `spineboy/export` holds two, and
444
466
  `spineboy-run.atlas` covers neither skeleton in it.
445
467
 
446
468
  **What it will not do is invent.** Everything the spec format cannot hold is a
447
469
  finding with a code — `BLOCK` for a construct the rebuild will be missing, `JUDGE`
448
- for the two values a skeleton does not carry, `LOSS` for the one number rigc
449
- re-derives on purpose. A blocker exits non-zero and still writes both files.
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.
450
474
 
451
475
  **Two values are not in a skeleton**, so `ingest` asks rather than guesses:
452
476
 
@@ -461,7 +485,11 @@ re-derives on purpose. A blocker exits non-zero and still writes both files.
461
485
  reports it as two measures of its own (`stage_present`, `stage_box`, since
462
486
  [#578](https://github.com/firejune/rigc/issues/578)) and they are `(reported)`, so
463
487
  nothing on the ladder reads them and an absurd box is green nearly everywhere. The
464
- 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;
465
493
  - **each animation's duration** — the format has no such field. The largest key time
466
494
  is used, stated in the motion spec's `note`, and recorded as a finding per
467
495
  animation. Edit it if you know the real number.
@@ -598,15 +626,46 @@ rebuild against its source produces differences of exactly three kinds, in every
598
626
  | Kind | Example, candidate vs reference | Why |
599
627
  | --- | --- | --- |
600
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` |
601
- | **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 |
602
630
  | **the emitted precision** | `…curve[0]: 0.066667 vs 0.06666667`, `uvs[0]: 0 vs 2.554152e-7` | rigc emits six decimals |
603
631
 
604
632
  ⇒ **So the corpus gate is `diff` at 1.000 rather than a byte comparison**, and it is
605
633
  worth being exact about what that does and does not cover. `diff` compares structure —
606
634
  counts, names, parentage, order, timeline kinds, key counts, curve kinds — and **not
607
635
  the values inside the keys**, which is why the precision row above is invisible to it.
608
- On rigc's own rigs byte identity covers both; on a foreign export the values are held
609
- by `check` (pixels) or by nothing, depending on what you render.
636
+ On rigc's own rigs byte identity covers both; on a foreign export it used to be
637
+ `check` (pixels) or nothing, depending on what you render.
638
+
639
+ ⭐ **The values are gated now, and by a second measure rather than by `diff`**
640
+ ([issue #615](https://github.com/firejune/rigc/issues/615)). Structure at 1.000 is
641
+ silent about the numbers inside it: a decompiler that halved every rotation, dropped
642
+ every bone's `length` or mirrored every vertex would read 1.000 on all 49 measures and
643
+ on every `(reported)` one. So the corpus round trip also compares **value by value**,
644
+ with the format's defaults taken from the parser rather than from a table — both files
645
+ are read through `spine-core` and the parsed forms are compared path by path, under a
646
+ tolerance that is the sum of rigc's own 1e-6 emitted grid and one float32 step of the
647
+ runtime's storage. Nine measures, printed on `IG16`'s own line and gated there — here
648
+ is the `6-arcs` export's, wrapped to fit this page:
649
+
650
+ ```
651
+ values: 9/9 measure(s) at 1.000 over 13865 compared value(s); skeleton 1.000 ·
652
+ bones 1.000 · slots 1.000 · attachments 1.000 · constraints 1.000 · events 1.000 ·
653
+ key_times 1.000 · key_values 1.000 · curves 1.000
654
+ ```
655
+
656
+ Over the whole corpus that is **193,927 values** compared, and the twelve read 1.000
657
+ on all nine.
658
+
659
+ `docs/BENCHMARK.md`'s *The nine value measures* is the full statement. What it still
660
+ does **not** cover, in the same breath:
661
+
662
+ | Still uncovered | Why |
663
+ | --- | --- |
664
+ | `version` and `hash` | the rig spec has no field for either, and `ingest` reports both as findings — the header row above, unchanged |
665
+ | anything below one float32 step | the parser stores frames, curves and vertices in a `Float32Array`, so a difference it cannot represent is invisible to any reading of the parsed form |
666
+ | a Bezier's handles *as written* | the parser samples them into the curve, so a moved handle arrives as moved samples rather than as the handle it was |
667
+ | how the file is **spelled** | field order, an omitted default written out, six decimals against eight — the second and third rows of the table above are values that agree, and this measure says so |
668
+ | how it **looks** | that is `check`, and `--texture-from` is how its figure is attributed |
610
669
 
611
670
  The geometric row needs a real number, because a naive reading of `check` makes an
612
671
  exact transcription look wrong. Here is the 3-timing transcription against frames
@@ -1052,7 +1052,7 @@ Nothing structurally new (attachment timeline, blend, inherit all arrived by run
1052
1052
  | (a) | **Transform constraints** — 🔴 first appearance (4). Full 4.3 `source` + `properties{from→to}` model (§1.4), which is the least-documented constraint in the format. ✅ Expressible and round-trips exactly; `RigTransformConstraint` already carried the 4.3 shape |
1053
1053
  | (a) | **Weighted meshes from authored geometry** — 🔴 first appearance. ~~rigc can emit weighted meshes, but only from `buildRingMesh`/`buildRibbonMesh`. An arbitrary 40-vertex/38-triangle mesh cannot be expressed~~ ⚠️ **This was wrong.** `RigMeshAttachment` takes authored `uvs`/`triangles`/`vertices`/`hull`, and `buildRigMesh` copies them verbatim. Both of 6-arcs' meshes round-trip to 1e-5 |
1054
1054
  | (a) | Mesh **`edges`** key ~~(rigc emits `hull` but not `edges`)~~ ✅ emitted from `RigMeshAttachment.edges` and byte-identical to the reference. 🚨 But **nothing measures it** — deleting `edges` from the rig still scores 1.000 on all nine attachment measures, so this rung's own gating feature is invisible to `bench` (issue #46) |
1055
- | (b) | Mesh geometry as data — vertices, triangles, uvs, per-vertex bone weights — instead of a generator name plus a polygon. ✅ Present. ~~🚨 But the weights bind bones by **index into the emitted bone array**, not by name — inserting a bone rebinds every vertex with the gate still green (issue #45)~~ ✅ **Fixed.** Weights bind **by name** (`weights: [[{ bone, x, y, weight }, …], …]`) and the compiler resolves them at emit; an unknown name is a `CompileError` (selftest `R08`). Spine's index run survives behind an explicit `"boneIndexing": "raw"`, whose cost — silence — `MR07` still measures |
1055
+ | (b) | Mesh geometry as data — vertices, triangles, uvs, per-vertex bone weights — instead of a generator name plus a polygon. ✅ Present. ~~🚨 But the weights bind bones by **index into the emitted bone array**, not by name — inserting a bone rebinds every vertex with the gate still green (issue #45)~~ ✅ **Fixed.** Weights bind **by name** (`weights: [[{ bone, x, y, weight }, …], …]`) and the compiler resolves them at emit; an unknown name is a `CompileError` (selftest `RF08`). Spine's index run survives behind an explicit `"boneIndexing": "raw"`, whose cost — silence — `MR07` still measures |
1056
1056
  | (c) | **A20** (unweighted forbidden) is satisfied here. ~~Under `--profile spine-html` its extra clause fires 11 times instead — the editor writes zero-weight bindings and that profile forbids them~~ ✅ **Fixed with #44**: both of A20's policy clauses are statements about what a rigc *generator* produces, so neither applies to authored geometry. Its coherence clauses — present, in range, summing to 1 — still do, in every profile |
1057
1057
  | (c) | **A21_MESH_RIM_PINNED** and **A28** encode ring/ribbon topology and will fire on an arbitrary mesh. ~~✅ **Confirmed**: A21 fires 40 times on the `tail` mesh under the default profile, because `meshKinds` has no entry for an authored mesh and the lookup falls back to `'ring'`~~ ✅ **Fixed** (issue #44): `meshKinds` has a third state, `authored`, and both assertions SKIP on one with that as the reason. The whole transcription is green under the default profile; selftest `MR08` holds it |
1058
1058
  | (c) | A13's mesh budget (≤4 slots, ≤80 tris) is **satisfied** at this rung (2 slots, max 38 tris) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-rigc",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
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": {
@@ -29,14 +29,16 @@ not do for you.
29
29
  ## Start here: `rigc ingest`
30
30
 
31
31
  ```bash
32
- rigc ingest hero.json --out specs/ --stage 0,0,1024,768
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
- spec that rebuild it, byte for byte. Two values are not in the file and it refuses
38
- rather than guessing them: the **stage** (`--stage`, an editor export carries none)
39
- and each animation's **duration** (the largest key time, recorded as a finding).
37
+ spec that rebuild it, byte for byte. Two values it refuses rather than guessing: the
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, and passing the flag at a
40
+ file that declares a box is refused rather than ignored) and each animation's
41
+ **duration** (the largest key time, recorded as a finding).
40
42
  Read `findings.json`: a `BLOCK` line means the rebuild will be missing something and
41
43
  the command exits non-zero. Keep the `note` both specs carry. INGEST §2.0.
42
44