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 +147 -11
- package/cli.ts +482 -15
- package/docs/AUTHORING.md +426 -10
- package/docs/MOTION.md +990 -0
- package/package.json +3 -1
- package/src/ballot.ts +869 -0
- package/src/compile.ts +612 -0
- package/src/pose.ts +1386 -0
- package/src/preview.ts +2 -2
- package/src/types.ts +163 -0
- package/src/validate.ts +229 -1
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
|
|
590
|
-
|
|
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
|
|
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
|
|
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
|
|
638
|
-
| `spine-html` | all
|
|
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
|
|
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
|
|
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
|
|
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
|