spine-rigc 0.6.0 β†’ 0.8.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
@@ -277,6 +277,22 @@ there has been played by Esoteric Software's own runtime rather than by ours.
277
277
  > Esoteric Software owns (see [NOTICE.md](NOTICE.md)). Everything the player
278
278
  > draws is inside your file.
279
279
 
280
+ **7. Let someone choose.** Sooner or later you will have two builds that both pass
281
+ the gate and no instrument that can separate them. `vote` puts them in one page
282
+ side by side, labelled `A` and `B` with no paths on screen, and takes an answer
283
+ back:
284
+
285
+ ```bash
286
+ rigc vote --candidate spine-a --candidate spine-b # -> ballot.html, open it and pick one
287
+ rigc vote --record vote-<id>.json # -> checks the answer into votes.jsonl
288
+ ```
289
+
290
+ The voter picks a winner or says "tie / no preference"; the page hands them a
291
+ small JSON file to save; `--record` checks that file against the ballot's own
292
+ hashes and appends one line to an append-only ledger, refusing by name anything
293
+ that does not belong to it. See
294
+ [Letting someone choose](#letting-someone-choose--rigc-vote).
295
+
280
296
  **Where to go next.**
281
297
 
282
298
  - πŸ“˜ **[docs/AUTHORING.md](docs/AUTHORING.md)** is the real guide β€” both files
@@ -515,6 +531,108 @@ They complement each other rather than overlap. `render` is offline, determinist
515
531
  and measurable β€” its pixels are the ones `check` reports on. `preview` is the
516
532
  interop proof: what plays there was played by Esoteric's own runtime, not by ours.
517
533
 
534
+ 🎞️ **Authoring the movement that these two show you** β€” key poses, in-betweening,
535
+ and how to spread candidates so a ballot informs β€” is
536
+ [docs/MOTION.md](docs/MOTION.md).
537
+
538
+ ### Letting someone choose β€” `rigc vote`
539
+
540
+ Sometimes looking is not enough on its own, because there is more than one
541
+ candidate and no instrument that can separate them: a pose fit with two local
542
+ optima that measure the same, a key density that is a matter of taste, a first
543
+ draft with no reference to compare against. `vote` is the deliberate human gate
544
+ for exactly that residue, and only for that residue.
545
+
546
+ ```bash
547
+ rigc vote --candidate spine-a --candidate spine-b [--animation <name>] [--out ballot.html]
548
+ rigc vote --record vote-<id>.json [--ballot ballot.html] [--ledger votes.jsonl] [--again]
549
+ ```
550
+
551
+ The first form writes one self-contained `ballot.html`: two to four compiled
552
+ candidates side by side, each in its own official player, looping, with one
553
+ button that restarts them together. The panes are labelled `A`, `B`, `C`, `D` and
554
+ show **no paths** β€” a voter who can see that `B` came out of `experiments/` is not
555
+ comparing pictures any more — so the path→label mapping lives in a manifest
556
+ embedded in the same file and is never rendered. A voter picks a winner or says
557
+ "tie / no preference", optionally writes a sentence, and copies or downloads a
558
+ small JSON result the page prints the filename for.
559
+
560
+ The second form checks that result against the ballot's own manifest and appends
561
+ it to an append-only JSONL ledger. Nothing is trusted: the result carries a
562
+ content **digest** per candidate, and a result whose digests are not this
563
+ ballot's, whose choice is not on it, or whose reason code contradicts its choice
564
+ is refused by a named rule (`V02_CANDIDATE_DIGESTS_ARE_THE_BALLOTS` and friends)
565
+ with nothing appended. A second vote on one ballot needs `--again`.
566
+
567
+ The loop it is built for, in one line: **the agent compiles N candidates that all
568
+ pass the gate β†’ `rigc vote` writes the ballot β†’ a human opens it, watches, and
569
+ votes β†’ `rigc vote --record` checks the answer into `votes.jsonl` β†’ the agent
570
+ reads the ledger and proceeds.** Compile first, vote last: a candidate reaches a
571
+ ballot only because it already validated green, so the human is never asked to
572
+ read JSON, a diff or a spec.
573
+
574
+ Three properties are worth stating because they are what make the ledger usable
575
+ by the next agent rather than by a reader:
576
+
577
+ - **A tie is a recorded outcome, not a missing one.** The ledger distinguishes a
578
+ ballot with a winner, a ballot the human called a tie, and a ballot nobody
579
+ opened. `both-unacceptable` is the tie that means *propose again*, and it is
580
+ unreachable if ties are not recordable.
581
+ - **The winner is a digest, not a label.** `B` means nothing outside one ballot;
582
+ the digest identifies the same pixels anywhere. Every line also carries its
583
+ `coverage` β€” which candidates the vote compared β€” so completeness is
584
+ computable rather than assumed.
585
+ - **Every line carries a reason code** from a closed enumeration, and the
586
+ enumeration is enforced: "tie, because this one is better" is refused.
587
+
588
+ Same player, same posture as `preview`: referenced from a CDN, never vendored,
589
+ and the file contains only your own art ([NOTICE.md](NOTICE.md)).
590
+
591
+ ## Reading a pose you were given β€” `rigc pose`
592
+
593
+ ```bash
594
+ rigc pose --images parts/ --frame poseA.png [--out pose.json]
595
+ ```
596
+
597
+ Every command above takes something you authored and tells you about it. This one
598
+ runs the other way: it takes a **picture the user already has** β€” one key pose β€”
599
+ and reports where each loose part PNG sits in it, so an agent can write those
600
+ coordinates into a rig and a motion **by construction** and spend its effort on the
601
+ part no instrument can measure, the movement between two poses.
602
+
603
+ ```
604
+ PLACE torso.png x= 44.4 y= 65.4 rot= 0.0Β° scale=1.118 residual=0.0770 unexplained= 19%
605
+ AMBIG arm.png x= 27.1 y= 56.3 rot= -35.2Β° scale=1.111 residual=0.0262 unexplained= 2%
606
+ alt 2: x= 61.5 y= 56.3 rot= 35.4Β° scale=1.116 residual=0.0279 unexplained= 2%
607
+ PLACE ball.png x= 44.3 y= 104.6 rot= 0.0Β° scale=1.144 residual=0.0203 unexplained= 3%
608
+ rotation is a FREE degree of freedom β€” the 0Β° above is a placeholder
609
+ REFUSE foreign.png no-match: the best placement found has residual 0.4245, above --max-residual 0.25
610
+ ```
611
+
612
+ 🚨 **Nothing here is a score, and no pass bar attaches to any of it.** `check` and
613
+ `bench` measure a build against a reference, so their numbers mean *how close*. A
614
+ pose frame is not a reference β€” it is a **given condition**, and once the spec
615
+ states those coordinates there is nothing left to be close to. The residual says
616
+ how far to trust a placement and where two answers are equally good, which is a
617
+ different job and needs the opposite defaults:
618
+
619
+ - a part that matches nowhere is **refused by name**, with its best guess still in
620
+ the JSON β€” a refusal tells you not to trust a number rather than hiding it;
621
+ - two near-equal optima are reported as **both**, flagged `ambiguous`, never
622
+ silently resolved. Two identical limbs look exactly like that;
623
+ - a part whose rotation genuinely does not matter β€” a ball β€” reports rotation as a
624
+ **free degree of freedom** rather than as a failure;
625
+ - a part the canvas cannot contain at any tested scale, and a part with no material
626
+ in it at all, each get their own named refusal.
627
+
628
+ ⚠️ **Residuals degrade under occlusion and there is no depth solver here.** A part
629
+ drawn behind another has the occluder's pixels where its own should be, so its
630
+ residual rises at the *correct* placement; `unexplained` is the share of the part
631
+ that disagrees, and a middling residual beside a high `unexplained` usually means
632
+ *right place, seen through something else*. The output carries its own `caveats`
633
+ block saying so. Fields, coordinate contract and the rest of the limits:
634
+ **[AUTHORING.md Β§11](docs/AUTHORING.md)**.
635
+
518
636
  ## Run viewer β€” watching a *run* instead of reading it
519
637
 
520
638
  πŸ”Ž **This is the ladder's instrument, not the way to look at your own rig** β€” that
@@ -586,8 +704,11 @@ the rest of the repository must not have) and is type-checked on its own with
586
704
  - A **motion spec** (`MotionSpec`, `spec: "rigc-motion/1"`) owns **time**: the rig
587
705
  it was authored against, named easing handles, setup overrides, a physics tuning
588
706
  table, and the animations β€” each with a declared duration, a loop flag, its
589
- tracks, and optionally a `drawOrder` timeline (the one timeline that names no
590
- target, so it sits on the animation rather than in `tracks`).
707
+ tracks, and five timeline families that sit on the animation rather than in `tracks`:
708
+ `drawOrder` and `events`, which name no target at all, and `ik`, `transform`
709
+ and `deform`, whose keys carry named fields instead of one value (an IK mix and
710
+ softness, six transform mixes, a sparse run of vertex offsets) β€” which is also
711
+ where 4.3 writes each of them.
591
712
 
592
713
  **Outputs β€” two files per cut**, written to the cut's `out` directory:
593
714
 
@@ -613,7 +734,7 @@ model (what is pinned, what may move, how authority falls off), and the
613
734
  ### The validator
614
735
 
615
736
  [`src/validate.ts`](src/validate.ts) parses the emitted artifacts with `spine-core`
616
- and then runs 34 named assertions over the loaded skeleton. Each one exists because
737
+ and then runs 36 named assertions over the loaded skeleton. Each one exists because
617
738
  the failure it catches is **silent**: the file loads, animates, and lies.
618
739
 
619
740
  Assertions whose data is absent are reported as **SKIP**, never folded into the pass
@@ -621,7 +742,7 @@ count β€” an assertion with nothing to check has not checked anything.
621
742
 
622
743
  #### Profiles β€” "wrong" versus "not how we do it here"
623
744
 
624
- Not all 34 rules are about Spine. Some are about **spine-html**, the renderer this
745
+ Not all 36 rules are about Spine. Some are about **spine-html**, the renderer this
625
746
  compiler was built to feed, and about one project's frame budget; they fire on real,
626
747
  correct, editor-produced Spine data, because the official example projects carry
627
748
  clipping attachments, unweighted meshes, 116-triangle meshes and packed atlases β€”
@@ -634,12 +755,12 @@ So `validate` and `build` take a `--profile`:
634
755
 
635
756
  | Profile | Runs | For |
636
757
  | --- | --- | --- |
637
- | `spine` | the 20 validity rules | **the default.** Is this valid Spine 4.3 that any runtime plays correctly? |
638
- | `spine-html` | all 34 | Opt-in. Is this a rig *this* project can ship? |
758
+ | `spine` | the 22 validity rules | **the default.** Is this valid Spine 4.3 that any runtime plays correctly? |
759
+ | `spine-html` | all 36 | Opt-in. Is this a rig *this* project can ship? |
639
760
 
640
761
  `spine` is the default because it is the question this package's output answers:
641
762
  the artifact imports into the Spine editor and plays in any 4.3 runtime, and
642
- that is what the 20 validity rules are about. The other 14 are somebody's policy
763
+ that is what the 22 validity rules are about. The other 14 are somebody's policy
643
764
  β€” one renderer's, one canvas budget's, one compiler's own formations' β€” and a
644
765
  rig arriving from anywhere else has no stake in them. Ask for them with
645
766
  `--profile spine-html` when you want them.
@@ -688,6 +809,8 @@ the renderer policy*.
688
809
  | `A31_DRAW_ORDER_OFFSETS_RESOLVE` | both | every draw-order key resolves to a real permutation: known slots, one entry per slot, each landing inside the slots array, offsets in ascending slot order. The **only assertion that runs before `A00`** β€” descending offsets make `readDrawOrder`'s forward-only cursor spin rather than return, so the round trip is refused by name instead of attempted |
689
810
  | `A32_EVENT_KEYS_RESOLVE` | both | every event key fires an event the skeleton declares, no key sits earlier in time than the one before it, and `volume`/`balance` appear only on an event with an `audio` path. Only the first of those is loud in the parser; the other two load clean and drop the firing or the value in silence. SKIPs when no animation carries an event timeline |
690
811
  | `A33_VERTEX_ATTACHMENT_GEOMETRY` | both | every bounding box and clipping polygon states a `vertexCount` that agrees with its vertex array, its weighted run decodes to that many vertices with bone indices in range, and a clipping `end` names a slot that exists. All three load clean: a missing count reads as zero and empties the polygon, and a missing end slot makes the clip run to the bottom of the draw order. SKIPs when the skeleton carries neither type |
812
+ | `A34_CONSTRAINT_TIMELINE_TARGETS` | both | every `ik` / `transform` timeline names a constraint of that type and carries at least one key. The name-and-type miss is loud in the parser (`IK Constraint not found`) and this one says which constraints the skeleton *does* have; the empty key array is silent β€” `readAnimation` reads key 0, finds nothing and skips the timeline without a word. SKIPs when no animation carries one |
813
+ | `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | both | every deform key's run lands inside the attachment's own deform array, starts on an even index, holds an even count of finite numbers, and names a skin/slot/attachment triple that resolves. The array is one `x, y` pair per **vertex** on an unweighted attachment and one per **bone influence** on a weighted one, so its length is measured from the attachment rather than assumed. An overlong run is the format's quietest defect: `Utils.arrayCopy` into a `Float32Array` drops everything past the end, so part of the mesh deforms and it looks nearly right. SKIPs when no animation carries a deform timeline |
691
814
 
692
815
  ## Usage
693
816
 
@@ -749,17 +872,24 @@ bun cli.ts check --candidate path/to/spine \
749
872
  bun cli.ts bench 3 --candidate path/to/spine # one rung of the ladder
750
873
  bun cli.ts render --candidate path/to/spine # PNG frames + a contact sheet
751
874
  bun cli.ts preview --candidate path/to/spine # one .html that plays it
875
+ bun cli.ts vote --candidate path/to/a --candidate path/to/b # one .html that asks which
876
+ bun cli.ts vote --record vote-<id>.json # check the answer into votes.jsonl
877
+ bun cli.ts pose --images path/to/parts --frame poseA.png # read a pose OUT of a picture
752
878
  ```
753
879
 
754
880
  `validate` on a bare directory checks what it can see. Adding `--cut`/`--cuts` lets
755
881
  it re-derive the declared durations and the structural expectations too, and the
756
882
  report says which it had. `build` and `validate` both default to `--profile spine`,
757
- the 20 validity rules; `--profile spine-html` adds this project's renderer and
883
+ the 22 validity rules; `--profile spine-html` adds this project's renderer and
758
884
  archetype policy on top.
759
885
 
760
886
  `render` and `preview` are the two that need no reference at all β€” see
761
887
  [Looking at a rig](#looking-at-a-rig--rigc-render-and-rigc-preview). Run either
762
- straight after a green `build`, on the same directory `--out` wrote.
888
+ straight after a green `build`, on the same directory `--out` wrote. `vote` is the
889
+ same idea with more than one candidate in the page and an answer coming back β€”
890
+ see [Letting someone choose](#letting-someone-choose--rigc-vote). `pose` is the
891
+ one command that runs *before* a spec exists rather than after β€” see
892
+ [Reading a pose you were given](#reading-a-pose-you-were-given--rigc-pose).
763
893
 
764
894
  ## Checks
765
895
 
@@ -856,20 +986,26 @@ nothing substantive executed exits 2 rather than printing green.
856
986
 
857
987
  ```
858
988
  tsconfig.json type-check config (noEmit); eslint.config.js β€” the no-any gate
859
- cli.ts build / validate / explain / diff / check / bench / render / preview
989
+ cli.ts build / validate / explain / diff / check / bench / render / preview / vote / pose
860
990
  selftest.ts the validator's own negative controls, and diff's and check's
861
991
  fixtures/ public.ts β€” the three synthetic cuts the selftest breaks
862
992
  src/
863
993
  compile.ts rig + motion spec (+ manifest) -> skeleton JSON + atlas text (pure data assembly)
864
994
  rig.ts the rig spec β€” `spec: "rigc-rig/1"`, the skeleton as data
865
- validate.ts spine-core round trip + the 34 assertions
995
+ validate.ts spine-core round trip + the 36 assertions
866
996
  diff.ts structural comparison of two skeletons, one ratio per measure
867
997
  render.ts the rasteriser (regions + meshes), shared by the reference renderer,
868
998
  `rigc render` and check
869
999
  preview.ts the single-file HTML player page β€” the artifact embedded as data
870
1000
  URIs, played by the official Spine Web Player (referenced, not vendored)
1001
+ ballot.ts the same page with 2–4 candidates in it and a vote coming back β€”
1002
+ candidate digests, the ballot manifest, and the refusals that
1003
+ stand between a saved vote and the ledger
871
1004
  check.ts a candidate against rendered frames β€” pixels and per-slot drift,
872
1005
  and it never opens the reference skeleton
1006
+ pose.ts the other direction: loose part PNGs against ONE pose frame, and
1007
+ where each part sits in it. An entry instrument β€” it reads a given
1008
+ condition into spec coordinates and grades nothing
873
1009
  ladder.ts which example is which rung, and which file in it is the reference
874
1010
  timelines.ts the 4.3 timeline catalogue and its walker (shared, pure JSON)
875
1011
  mesh.ts ring and ribbon mesh builders, weighted-vertex encoding