spine-rigc 0.6.0 β†’ 0.7.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,59 @@ 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
+ ### Letting someone choose β€” `rigc vote`
535
+
536
+ Sometimes looking is not enough on its own, because there is more than one
537
+ candidate and no instrument that can separate them: a pose fit with two local
538
+ optima that measure the same, a key density that is a matter of taste, a first
539
+ draft with no reference to compare against. `vote` is the deliberate human gate
540
+ for exactly that residue, and only for that residue.
541
+
542
+ ```bash
543
+ rigc vote --candidate spine-a --candidate spine-b [--animation <name>] [--out ballot.html]
544
+ rigc vote --record vote-<id>.json [--ballot ballot.html] [--ledger votes.jsonl] [--again]
545
+ ```
546
+
547
+ The first form writes one self-contained `ballot.html`: two to four compiled
548
+ candidates side by side, each in its own official player, looping, with one
549
+ button that restarts them together. The panes are labelled `A`, `B`, `C`, `D` and
550
+ show **no paths** β€” a voter who can see that `B` came out of `experiments/` is not
551
+ comparing pictures any more — so the path→label mapping lives in a manifest
552
+ embedded in the same file and is never rendered. A voter picks a winner or says
553
+ "tie / no preference", optionally writes a sentence, and copies or downloads a
554
+ small JSON result the page prints the filename for.
555
+
556
+ The second form checks that result against the ballot's own manifest and appends
557
+ it to an append-only JSONL ledger. Nothing is trusted: the result carries a
558
+ content **digest** per candidate, and a result whose digests are not this
559
+ ballot's, whose choice is not on it, or whose reason code contradicts its choice
560
+ is refused by a named rule (`V02_CANDIDATE_DIGESTS_ARE_THE_BALLOTS` and friends)
561
+ with nothing appended. A second vote on one ballot needs `--again`.
562
+
563
+ The loop it is built for, in one line: **the agent compiles N candidates that all
564
+ pass the gate β†’ `rigc vote` writes the ballot β†’ a human opens it, watches, and
565
+ votes β†’ `rigc vote --record` checks the answer into `votes.jsonl` β†’ the agent
566
+ reads the ledger and proceeds.** Compile first, vote last: a candidate reaches a
567
+ ballot only because it already validated green, so the human is never asked to
568
+ read JSON, a diff or a spec.
569
+
570
+ Three properties are worth stating because they are what make the ledger usable
571
+ by the next agent rather than by a reader:
572
+
573
+ - **A tie is a recorded outcome, not a missing one.** The ledger distinguishes a
574
+ ballot with a winner, a ballot the human called a tie, and a ballot nobody
575
+ opened. `both-unacceptable` is the tie that means *propose again*, and it is
576
+ unreachable if ties are not recordable.
577
+ - **The winner is a digest, not a label.** `B` means nothing outside one ballot;
578
+ the digest identifies the same pixels anywhere. Every line also carries its
579
+ `coverage` β€” which candidates the vote compared β€” so completeness is
580
+ computable rather than assumed.
581
+ - **Every line carries a reason code** from a closed enumeration, and the
582
+ enumeration is enforced: "tie, because this one is better" is refused.
583
+
584
+ Same player, same posture as `preview`: referenced from a CDN, never vendored,
585
+ and the file contains only your own art ([NOTICE.md](NOTICE.md)).
586
+
518
587
  ## Run viewer β€” watching a *run* instead of reading it
519
588
 
520
589
  πŸ”Ž **This is the ladder's instrument, not the way to look at your own rig** β€” that
@@ -586,8 +655,11 @@ the rest of the repository must not have) and is type-checked on its own with
586
655
  - A **motion spec** (`MotionSpec`, `spec: "rigc-motion/1"`) owns **time**: the rig
587
656
  it was authored against, named easing handles, setup overrides, a physics tuning
588
657
  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`).
658
+ tracks, and five timeline families that sit on the animation rather than in `tracks`:
659
+ `drawOrder` and `events`, which name no target at all, and `ik`, `transform`
660
+ and `deform`, whose keys carry named fields instead of one value (an IK mix and
661
+ softness, six transform mixes, a sparse run of vertex offsets) β€” which is also
662
+ where 4.3 writes each of them.
591
663
 
592
664
  **Outputs β€” two files per cut**, written to the cut's `out` directory:
593
665
 
@@ -613,7 +685,7 @@ model (what is pinned, what may move, how authority falls off), and the
613
685
  ### The validator
614
686
 
615
687
  [`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
688
+ and then runs 36 named assertions over the loaded skeleton. Each one exists because
617
689
  the failure it catches is **silent**: the file loads, animates, and lies.
618
690
 
619
691
  Assertions whose data is absent are reported as **SKIP**, never folded into the pass
@@ -621,7 +693,7 @@ count β€” an assertion with nothing to check has not checked anything.
621
693
 
622
694
  #### Profiles β€” "wrong" versus "not how we do it here"
623
695
 
624
- Not all 34 rules are about Spine. Some are about **spine-html**, the renderer this
696
+ Not all 36 rules are about Spine. Some are about **spine-html**, the renderer this
625
697
  compiler was built to feed, and about one project's frame budget; they fire on real,
626
698
  correct, editor-produced Spine data, because the official example projects carry
627
699
  clipping attachments, unweighted meshes, 116-triangle meshes and packed atlases β€”
@@ -634,12 +706,12 @@ So `validate` and `build` take a `--profile`:
634
706
 
635
707
  | Profile | Runs | For |
636
708
  | --- | --- | --- |
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? |
709
+ | `spine` | the 22 validity rules | **the default.** Is this valid Spine 4.3 that any runtime plays correctly? |
710
+ | `spine-html` | all 36 | Opt-in. Is this a rig *this* project can ship? |
639
711
 
640
712
  `spine` is the default because it is the question this package's output answers:
641
713
  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
714
+ that is what the 22 validity rules are about. The other 14 are somebody's policy
643
715
  β€” one renderer's, one canvas budget's, one compiler's own formations' β€” and a
644
716
  rig arriving from anywhere else has no stake in them. Ask for them with
645
717
  `--profile spine-html` when you want them.
@@ -688,6 +760,8 @@ the renderer policy*.
688
760
  | `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
761
  | `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
762
  | `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 |
763
+ | `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 |
764
+ | `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
765
 
692
766
  ## Usage
693
767
 
@@ -749,17 +823,21 @@ bun cli.ts check --candidate path/to/spine \
749
823
  bun cli.ts bench 3 --candidate path/to/spine # one rung of the ladder
750
824
  bun cli.ts render --candidate path/to/spine # PNG frames + a contact sheet
751
825
  bun cli.ts preview --candidate path/to/spine # one .html that plays it
826
+ bun cli.ts vote --candidate path/to/a --candidate path/to/b # one .html that asks which
827
+ bun cli.ts vote --record vote-<id>.json # check the answer into votes.jsonl
752
828
  ```
753
829
 
754
830
  `validate` on a bare directory checks what it can see. Adding `--cut`/`--cuts` lets
755
831
  it re-derive the declared durations and the structural expectations too, and the
756
832
  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
833
+ the 22 validity rules; `--profile spine-html` adds this project's renderer and
758
834
  archetype policy on top.
759
835
 
760
836
  `render` and `preview` are the two that need no reference at all β€” see
761
837
  [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.
838
+ straight after a green `build`, on the same directory `--out` wrote. `vote` is the
839
+ same idea with more than one candidate in the page and an answer coming back β€”
840
+ see [Letting someone choose](#letting-someone-choose--rigc-vote).
763
841
 
764
842
  ## Checks
765
843
 
@@ -856,18 +934,21 @@ nothing substantive executed exits 2 rather than printing green.
856
934
 
857
935
  ```
858
936
  tsconfig.json type-check config (noEmit); eslint.config.js β€” the no-any gate
859
- cli.ts build / validate / explain / diff / check / bench / render / preview
937
+ cli.ts build / validate / explain / diff / check / bench / render / preview / vote
860
938
  selftest.ts the validator's own negative controls, and diff's and check's
861
939
  fixtures/ public.ts β€” the three synthetic cuts the selftest breaks
862
940
  src/
863
941
  compile.ts rig + motion spec (+ manifest) -> skeleton JSON + atlas text (pure data assembly)
864
942
  rig.ts the rig spec β€” `spec: "rigc-rig/1"`, the skeleton as data
865
- validate.ts spine-core round trip + the 34 assertions
943
+ validate.ts spine-core round trip + the 36 assertions
866
944
  diff.ts structural comparison of two skeletons, one ratio per measure
867
945
  render.ts the rasteriser (regions + meshes), shared by the reference renderer,
868
946
  `rigc render` and check
869
947
  preview.ts the single-file HTML player page β€” the artifact embedded as data
870
948
  URIs, played by the official Spine Web Player (referenced, not vendored)
949
+ ballot.ts the same page with 2–4 candidates in it and a vote coming back β€”
950
+ candidate digests, the ballot manifest, and the refusals that
951
+ stand between a saved vote and the ledger
871
952
  check.ts a candidate against rendered frames β€” pixels and per-slot drift,
872
953
  and it never opens the reference skeleton
873
954
  ladder.ts which example is which rung, and which file in it is the reference