spine-rigc 1.5.0 → 1.5.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/docs/AUTHORING.md +75 -19
- package/docs/INGEST.md +20 -13
- package/docs/MOTION.md +138 -85
- package/package.json +1 -1
- package/src/compile.ts +35 -1
- package/src/keys.ts +297 -2
- package/src/motion.ts +123 -3
- package/src/pose.ts +84 -6
- package/src/render.ts +32 -0
- package/src/rig.ts +241 -31
- package/src/types.ts +42 -0
- package/src/validate.ts +34 -4
package/docs/MOTION.md
CHANGED
|
@@ -614,7 +614,7 @@ quietly, and every in-between hung off it swings about the wrong centre.
|
|
|
614
614
|
further than you would accept as a bone position, treat it as the next case.
|
|
615
615
|
- **Δ < 20°** — ⛔ **do not use the solve.** Take the default below and **say in your
|
|
616
616
|
log that the pivot was defaulted and why** — the number, not the word: *"flag hinge
|
|
617
|
-
defaulted; relative rotation changed 10.
|
|
617
|
+
defaulted; relative rotation changed 10.5° between the two poses, amplification 5.5×"*.
|
|
618
618
|
- **Δ = 0** (a part that only translates, or a rigid pair) — the pivot is not a
|
|
619
619
|
quantity the pictures contain at all. Default it.
|
|
620
620
|
|
|
@@ -632,10 +632,13 @@ actually see, which is why they beat an ill-conditioned solve:
|
|
|
632
632
|
exactly why it gets said out loud.
|
|
633
633
|
|
|
634
634
|
📌 **Then check the default the cheap way: it should agree with itself across the
|
|
635
|
-
poses.** Take your chosen pivot point into the parent's
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
635
|
+
poses.** Take your chosen pivot point into the parent **bone's** frame once per pose —
|
|
636
|
+
origin at the parent's own pivot, not at the centre of its image. A real hinge is
|
|
637
|
+
*fixed* there, so the readings should differ by about your placement noise; if they
|
|
638
|
+
differ by several pixels, the point you picked is not the hinge. §6 runs this check on
|
|
639
|
+
a real pair and gets 0.25 px. ⚠️ The check cannot see the origin: measured from the
|
|
640
|
+
wrong one, the readings agree exactly as well, because a constant offset agrees with
|
|
641
|
+
itself. §6 shows both, and what the wrong one draws.
|
|
639
642
|
|
|
640
643
|
### 3.10 📗 Secondary action, and 🧩 what it costs here
|
|
641
644
|
|
|
@@ -767,7 +770,7 @@ late, §3.8's overshoot is live; animation name → **`raise`**.
|
|
|
767
770
|
|
|
768
771
|
### 1. The art, and the two pictures
|
|
769
772
|
|
|
770
|
-
```bash
|
|
773
|
+
```bash setup
|
|
771
774
|
mkdir -p semaphore/parts && cd semaphore
|
|
772
775
|
bun -e '
|
|
773
776
|
const files = {
|
|
@@ -806,23 +809,26 @@ rigc pose
|
|
|
806
809
|
.. frame …/semaphore/poseA.png (160x200)
|
|
807
810
|
.. ground rgb(238, 238, 234) over 100% of the border ring
|
|
808
811
|
.. parts …/semaphore/parts (3 png)
|
|
809
|
-
.. search scale 0.5–2 in 7 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
|
|
810
|
-
PLACE arm.png x=
|
|
811
|
-
found on a 10x13 anchor grid, step 4 at 4x reduction
|
|
812
|
-
PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
|
|
812
|
+
.. search scale 0.5–2 in 7 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
|
|
813
|
+
PLACE arm.png x= 92.0 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
|
|
813
814
|
found on a 20x25 anchor grid, step 4 at 2x reduction
|
|
814
|
-
|
|
815
|
-
found on a
|
|
816
|
-
|
|
817
|
-
|
|
815
|
+
PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
|
|
816
|
+
found on a 40x50 anchor grid, step 2 at 2x reduction
|
|
817
|
+
AMBIG post.png x= 79.9 y= 149.0 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
|
|
818
|
+
found on a 14x17 anchor grid, step 6 at 2x reduction
|
|
819
|
+
alt 2: x= 79.0 y= 141.7 rot= -0.5° scale=0.691 residual=0.0687 unexplained= 25%
|
|
820
|
+
ambiguous: part is 14x96 px (span 93.2 frame px, opaque 1) with texture 0.0146, detail 1.406; 43 candidate(s), best 0.0593, next 0.0687, spread 0.0095 — above the measured floor (AUTHORING §11.5: detail 0.5, measured from 24 px up), so size and texture do not explain this; either the frame holds more than one place this part fits, or every candidate missed the true one — a narrower --scale or --rotation window around what you know of the part tells the two apart
|
|
818
821
|
```
|
|
819
822
|
|
|
820
|
-
⚠️ **The post came back at scale 0.
|
|
821
|
-
|
|
822
|
-
most of its area in one colour, so a shrunken copy sitting inside the
|
|
823
|
-
those pixels nearly as well, and
|
|
824
|
-
|
|
825
|
-
|
|
823
|
+
⚠️ **The post came back `AMBIG`: a best at scale 0.971 and an alternate at 0.691, 7.3 px
|
|
824
|
+
higher, trailing it by a spread of 0.0095.** That is §2.2's caveat in the open. The post
|
|
825
|
+
is a long part with most of its area in one colour, so a shrunken copy sitting inside the
|
|
826
|
+
real post explains those pixels nearly as well, and the report's own `ambiguous:` line
|
|
827
|
+
says size and texture do not account for how close the two come. The report cannot say
|
|
828
|
+
which one is true, and it says so: it names both and asks for a narrower window. The
|
|
829
|
+
picture can. The arm and the flag read 0.970 and 0.955, so the picture is at the art's
|
|
830
|
+
own resolution, and a post at 0.69 would be drawn at about 0.7 of the size of everything
|
|
831
|
+
bolted to it. ⇒ Narrow, and say so:
|
|
826
832
|
|
|
827
833
|
```bash
|
|
828
834
|
rigc pose --images parts --frame poseA.png --scale 0.85,1.2 --out poseA.json
|
|
@@ -830,33 +836,49 @@ rigc pose --images parts --frame poseB.png --scale 0.85,1.2 --out poseB.json
|
|
|
830
836
|
```
|
|
831
837
|
|
|
832
838
|
```
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
839
|
+
rigc pose
|
|
840
|
+
.. frame …/semaphore/poseA.png (160x200)
|
|
841
|
+
.. ground rgb(238, 238, 234) over 100% of the border ring
|
|
842
|
+
.. parts …/semaphore/parts (3 png)
|
|
843
|
+
.. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
|
|
844
|
+
PLACE arm.png x= 91.9 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
|
|
845
|
+
found on a 20x25 anchor grid, step 4 at 2x reduction
|
|
846
|
+
PLACE flag.png x= 104.7 y= 156.1 rot= 84.1° scale=0.956 residual=0.0135 unexplained= 0%
|
|
847
|
+
found on a 40x50 anchor grid, step 2 at 2x reduction
|
|
848
|
+
PLACE post.png x= 79.9 y= 148.5 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
|
|
849
|
+
found on a 14x17 anchor grid, step 6 at 2x reduction
|
|
837
850
|
```
|
|
838
851
|
|
|
839
852
|
```
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
853
|
+
rigc pose
|
|
854
|
+
.. frame …/semaphore/poseB.png (160x200)
|
|
855
|
+
.. ground rgb(238, 238, 234) over 100% of the border ring
|
|
856
|
+
.. parts …/semaphore/parts (3 png)
|
|
857
|
+
.. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
|
|
858
|
+
PLACE arm.png x= 102.5 y= 94.8 rot= -18.3° scale=0.974 residual=0.0305 unexplained= 4%
|
|
859
|
+
found on a 20x25 anchor grid, step 4 at 2x reduction
|
|
860
|
+
PLACE flag.png x= 137.4 y= 86.0 rot= -6.6° scale=0.956 residual=0.0134 unexplained= 0%
|
|
861
|
+
found on a 40x50 anchor grid, step 2 at 2x reduction
|
|
843
862
|
PLACE post.png x= 80.0 y= 148.0 rot= 0.0° scale=0.995 residual=0.0394 unexplained= 9%
|
|
863
|
+
found on a 14x17 anchor grid, step 6 at 2x reduction
|
|
844
864
|
```
|
|
845
865
|
|
|
846
866
|
Three things to read out of that pair, none of which is a score:
|
|
847
867
|
|
|
848
|
-
- **Scale.** All six readings sit in 0.
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
868
|
+
- **Scale.** All six readings sit in 0.956–0.995, a spread of about 4 %. The one part
|
|
869
|
+
read twice at one place, the post, reads 0.971 and 0.995 — 2.5 % apart on a part that
|
|
870
|
+
does not move — so a 4 % spread is the size of the method's own repeatability rather
|
|
871
|
+
than six different scales. ⇒ Take the pictures as being at the art's own resolution
|
|
872
|
+
and author the rig in **part pixels**, so no scaling appears in the spec at all. Say
|
|
873
|
+
that this is what the spread was read as.
|
|
852
874
|
- **Draw order, from `unexplained`.** The post reads **16 %** unexplained in pose A and
|
|
853
875
|
**9 %** in pose B, at placements that barely move — §2.2's occlusion signature. The arm
|
|
854
876
|
crosses more of the post in pose A, so the arm is **in front of** the post. The flag
|
|
855
|
-
reads 0 %
|
|
877
|
+
reads 0 % in both: nothing covers it, so it is **in front of** the arm. ⇒ Slots in
|
|
856
878
|
the order `post`, `arm`, `flag` (AUTHORING R4 — the slots array *is* the draw order,
|
|
857
879
|
and there is nowhere else in the file to say it).
|
|
858
880
|
- **The post does not move**, so its two readings are two measurements of one number:
|
|
859
|
-
x 79.9/80.0 and y 148.
|
|
881
|
+
x 79.9/80.0 and y 148.5/148.0. ⇒ Use the mean, **(79.95, 148.25)**, and treat the 0.5 px
|
|
860
882
|
disagreement as the noise floor for every other number on the page.
|
|
861
883
|
|
|
862
884
|
### 3. Convert, and derive the rig
|
|
@@ -866,33 +888,59 @@ Three things to read out of that pair, none of which is a score:
|
|
|
866
888
|
|
|
867
889
|
| | pose A, Spine world | pose B, Spine world |
|
|
868
890
|
| --- | --- | --- |
|
|
869
|
-
| `post` | x 79.9 · y 51.
|
|
870
|
-
| `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.
|
|
871
|
-
| `flag` | x 104.7 · y 43.9 · rot −84.
|
|
891
|
+
| `post` | x 79.9 · y 51.5 · rot 0.1° | x 80.0 · y 52.0 · rot 0.0° |
|
|
892
|
+
| `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.5 · y 105.2 · rot 18.3° |
|
|
893
|
+
| `flag` | x 104.7 · y 43.9 · rot −84.1° | x 137.4 · y 114.0 · rot 6.6° |
|
|
872
894
|
|
|
873
895
|
**The shoulder, by §3.9's solve.** The arm's screen rotation changes from 61.9° to
|
|
874
|
-
−18.
|
|
875
|
-
1.
|
|
876
|
-
102.
|
|
877
|
-
pixel **(6.
|
|
878
|
-
world that is (80.
|
|
896
|
+
−18.3°, so **Δ = 80.2°** — well inside the *solve it* band, `|det| = 4·sin²(40.1°) =
|
|
897
|
+
1.660`, amplification 0.78×. Solving the 2×2 puts the fixed point at frame **(80.57,
|
|
898
|
+
102.51)**, reconstructing identically from both poses, and the offset lands at arm-image
|
|
899
|
+
pixel **(6.76, 7.43)** — inside the arm's own hub, which is where a hub is for. In Spine
|
|
900
|
+
world that is (80.57, 97.49); in the post bone's local space, **(0.62, 45.74)**. That
|
|
901
|
+
point is the `arm` bone's origin, and it is the origin of everything measured in the
|
|
902
|
+
arm's frame below.
|
|
879
903
|
|
|
880
904
|
**The flag hinge, by §3.9's default — and this is the interesting one.** The relative
|
|
881
|
-
angle across that joint is 22.
|
|
882
|
-
`|det| = 0.
|
|
883
|
-
band. Run it anyway, to see what it would have cost — it
|
|
884
|
-
|
|
885
|
-
draws its hinge as a dark hoist strip down one edge, whose centre
|
|
886
|
-
**(2.5, 10)
|
|
887
|
-
**
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
905
|
+
angle across that joint is 22.2° in pose A and 11.7° in pose B, so **Δ = 10.5°**:
|
|
906
|
+
`|det| = 0.0335`, amplification **5.5×**, comfortably inside the *do not use the solve*
|
|
907
|
+
band. Run it anyway, to see what it would have cost — it puts the hinge at **(49.15,
|
|
908
|
+
−0.38)** in the arm bone's frame, exactly as confidently as the shoulder did. The default
|
|
909
|
+
instead: the flag's art draws its hinge as a dark hoist strip down one edge, whose centre
|
|
910
|
+
is flag-image **(2.5, 10)**. Carry that point through each pose's placement into the
|
|
911
|
+
**arm bone's frame** — origin at the shoulder pivot above, x along the arm, y up: take
|
|
912
|
+
the point's frame position, subtract the pivot's, turn it back through the arm's screen
|
|
913
|
+
rotation and flip y once. Carried unrounded, printed rounded:
|
|
914
|
+
|
|
915
|
+
| | pose A | pose B |
|
|
916
|
+
| --- | --- | --- |
|
|
917
|
+
| the hinge in the frame, through the flag's placement | (103.52, 144.66) | (125.98, 87.32) |
|
|
918
|
+
| minus the pivot, (80.57, 102.51) | (22.94, 42.15) | (45.40, −15.18) |
|
|
919
|
+
| turned by −61.9° and +18.3°, y flipped | **(47.99, 0.38)** | **(47.87, 0.16)** |
|
|
920
|
+
|
|
921
|
+
That is §3.9's self-agreement check, and the two poses agree to **0.25 px**. ⇒ Take the
|
|
922
|
+
mean, **(47.93, 0.27)**, write it as **(47.93, 0)** — the y is inside the 0.5 px noise
|
|
923
|
+
floor — and write in the log that the pivot was **defaulted**, with the number:
|
|
924
|
+
*relative rotation changed 10.5°, amplification 5.5×, ill-conditioned solve declined*.
|
|
925
|
+
|
|
926
|
+
⚠️ **Which origin the arm's frame has is the whole of this step, and the check above
|
|
927
|
+
cannot tell you.** A placement reports where the part image's **centre** lands, so the
|
|
928
|
+
nearest frame to hand is the one centred on the arm image — and measured from there the
|
|
929
|
+
same hinge reads (24.76, −0.05) and (24.64, −0.27). Those agree to 0.25 px just as well,
|
|
930
|
+
because a constant offset agrees with itself. They are short by 23.24, the arm
|
|
931
|
+
attachment's own offset below, and a flag hung at x 24.70 draws over the middle of the
|
|
932
|
+
arm: built and rendered on the pictures' grid, its flag sits 23.5 px from where
|
|
933
|
+
`poseA.png` draws it in frame 0 and 23.2 px from `poseB.png` at the end. With x 47.93 it
|
|
934
|
+
sits 0.4 px and 0.2 px off, inside the noise floor. A bone sits at its **pivot**, and its
|
|
935
|
+
children's offsets are measured from there.
|
|
891
936
|
|
|
892
937
|
⭐ Worth pausing on, because it is what §3.9 is for: **the two solves are
|
|
893
938
|
indistinguishable from the inside.** Both are exact, both reconstruct to a point that
|
|
894
|
-
agrees between the poses, and no residual anywhere in either pose report moves.
|
|
895
|
-
|
|
939
|
+
agrees between the poses, and no residual anywhere in either pose report moves. Here
|
|
940
|
+
the declined one lands 1.4 px from the drawn hinge — the 5.5× amplification applied to
|
|
941
|
+
about a quarter-pixel of placement noise — and nothing in either answer says which of the
|
|
942
|
+
two is the one to trust. The only thing separating them is Δ, which is arithmetic you
|
|
943
|
+
can do before you trust either.
|
|
896
944
|
|
|
897
945
|
`semaphore.rig.json` — complete, nothing trimmed:
|
|
898
946
|
|
|
@@ -904,9 +952,9 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
904
952
|
"skeleton": { "width": 160, "height": 200 },
|
|
905
953
|
"bones": [
|
|
906
954
|
{ "name": "root" },
|
|
907
|
-
{ "name": "post", "parent": "root", "x": 79.95, "y": 51.
|
|
908
|
-
{ "name": "arm", "parent": "post", "x": 0.
|
|
909
|
-
{ "name": "flag", "parent": "arm", "x":
|
|
955
|
+
{ "name": "post", "parent": "root", "x": 79.95, "y": 51.75 },
|
|
956
|
+
{ "name": "arm", "parent": "post", "x": 0.62, "y": 45.74 },
|
|
957
|
+
{ "name": "flag", "parent": "arm", "x": 47.93, "y": 0 }
|
|
910
958
|
],
|
|
911
959
|
"slots": [
|
|
912
960
|
{ "name": "post", "bone": "post", "attachment": "post" },
|
|
@@ -916,7 +964,7 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
916
964
|
"skins": {
|
|
917
965
|
"default": {
|
|
918
966
|
"post": { "post": { "image": "post.png" } },
|
|
919
|
-
"arm": { "arm": { "image": "arm.png", "x": 23.
|
|
967
|
+
"arm": { "arm": { "image": "arm.png", "x": 23.24 } },
|
|
920
968
|
"flag": { "flag": { "image": "flag.png", "x": 11.5 } }
|
|
921
969
|
}
|
|
922
970
|
}
|
|
@@ -926,8 +974,8 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
926
974
|
The two attachment offsets are the last of the arithmetic. A bone sits at its pivot and
|
|
927
975
|
the placement told you where the image's **centre** goes, so the offset is the gap
|
|
928
976
|
between them, in the bone's own axes with y flipped once: the arm's pivot is at image
|
|
929
|
-
(6.
|
|
930
|
-
the 0.
|
|
977
|
+
(6.76, 7.43) and its centre at (30, 7), giving **x 23.24** (the y term is 0.43, inside
|
|
978
|
+
the 0.5 px noise floor, so it is not written); the flag's hinge is at image (2.5, 10) and
|
|
931
979
|
its centre at (14, 10), giving **x 11.5** exactly. Bone rotations are left off, which
|
|
932
980
|
means *as drawn* — the arm plate is drawn horizontal and the post vertical, so the poses
|
|
933
981
|
are entirely the motion spec's business.
|
|
@@ -962,19 +1010,19 @@ overshoot (§3.8). The post is planted: ⛔ **no timeline**.
|
|
|
962
1010
|
"keys": [
|
|
963
1011
|
{ "t": 0, "v": [-61.9], "ease": "gather" },
|
|
964
1012
|
{ "t": 0.07, "v": [-66.4], "ease": "charge" },
|
|
965
|
-
{ "t": 0.32, "v": [24.
|
|
966
|
-
{ "t": 0.55, "v": [18.
|
|
1013
|
+
{ "t": 0.32, "v": [24.7], "ease": "settle" },
|
|
1014
|
+
{ "t": 0.55, "v": [18.3] }
|
|
967
1015
|
]
|
|
968
1016
|
},
|
|
969
1017
|
{
|
|
970
1018
|
"bone": "flag",
|
|
971
1019
|
"property": "rotate",
|
|
972
1020
|
"keys": [
|
|
973
|
-
{ "t": 0, "v": [-22.
|
|
1021
|
+
{ "t": 0, "v": [-22.2], "ease": "gather" },
|
|
974
1022
|
{ "t": 0.09, "v": [-30.1], "ease": "charge" },
|
|
975
1023
|
{ "t": 0.38, "v": [-3.8], "ease": "settle" },
|
|
976
1024
|
{ "t": 0.48, "v": [-15.4], "ease": "settle" },
|
|
977
|
-
{ "t": 0.55, "v": [-11.
|
|
1025
|
+
{ "t": 0.55, "v": [-11.7] }
|
|
978
1026
|
]
|
|
979
1027
|
}
|
|
980
1028
|
]
|
|
@@ -988,10 +1036,10 @@ which is which is worth being able to point at:
|
|
|
988
1036
|
|
|
989
1037
|
| Key | Where it came from |
|
|
990
1038
|
| --- | --- |
|
|
991
|
-
| arm `t: 0` = −61.9, flag `t: 0` = −22.
|
|
992
|
-
| arm `t: 0.55` = 18.
|
|
1039
|
+
| arm `t: 0` = −61.9, flag `t: 0` = −22.2 | **given** — pose A, converted. Untouched, per §3.6 |
|
|
1040
|
+
| arm `t: 0.55` = 18.3, flag `t: 0.55` = −11.7 | **given** — pose B. The flag's is `6.6 − 18.3`: both world rotations converted first, then differenced, because a child's track carries a **local** rotation under a rotated parent |
|
|
993
1041
|
| arm `t: 0.07` = −66.4 | §3.6 — 4.5° against an 80° excursion (5.6 %), at 13 % of the duration |
|
|
994
|
-
| arm `t: 0.32` = 24.
|
|
1042
|
+
| arm `t: 0.32` = 24.7 | §3.8 — 6.4° past the end value (8 %), at 58 % of the duration |
|
|
995
1043
|
| flag `t: 0.09`, `t: 0.38` | §3.7 — the flag's extreme lands at 69 % against the arm's 58 %, an offset of **+11 %**, and it drags the other way first |
|
|
996
1044
|
| flag `t: 0.48` = −15.4 | §3.7's *one crossing* for a loose part: it comes back past its own end value before settling |
|
|
997
1045
|
| the three easings | §3.4 — one that gathers, one that arrives slowly, one symmetric. The **last key of each track carries no easing**, because there is nothing after it to ease towards (AUTHORING §4.5) |
|
|
@@ -1015,20 +1063,24 @@ rigc render --candidate spine --fps 24 --max 200
|
|
|
1015
1063
|
```
|
|
1016
1064
|
|
|
1017
1065
|
```
|
|
1018
|
-
..
|
|
1066
|
+
.. 134x200px at 24 fps, 1 set(s) -> …/semaphore/render
|
|
1019
1067
|
.. raise 14 frame(s), 0.542s + contact.png -> …/semaphore/render/raise@24fps
|
|
1020
1068
|
```
|
|
1021
1069
|
|
|
1022
1070
|
Open `render/raise@24fps/contact.png` **before anything else** — fourteen frames as one
|
|
1023
1071
|
grid, and spacing is a comparison across frames rather than a property of any one of
|
|
1024
|
-
them. What to check on it, and it is not a score: frame 0 is pose A
|
|
1025
|
-
|
|
1026
|
-
than the arm's.
|
|
1072
|
+
them. What to check on it, and it is not a score: frame 0 is pose A — the flag hanging off
|
|
1073
|
+
the arm's collar, not over its middle — the last frame is pose B, the anticipation dips
|
|
1074
|
+
*after* frame 0, and the flag's extreme is visibly later than the arm's.
|
|
1027
1075
|
|
|
1028
1076
|
🚫 **Do not run `rigc check` against `poseA.png` and `poseB.png`.** Two pictures are not
|
|
1029
|
-
a frame set
|
|
1030
|
-
|
|
1031
|
-
movement.
|
|
1077
|
+
a frame set: `check` reads the second as the frame 1/12 s in, at its default rate. And
|
|
1078
|
+
the ends' **rotations** are given conditions the spec states by construction, so a
|
|
1079
|
+
number for how near the movement got to them is a number about the pose estimator. What
|
|
1080
|
+
the pictures can still catch is §3's arithmetic, because every **offset** in the rig was
|
|
1081
|
+
derived rather than given, and a wrong one moves an end pose off its picture while every
|
|
1082
|
+
key stays exact. That is what the frame-0 check above is for, and it is a look, not a
|
|
1083
|
+
score. §7.
|
|
1032
1084
|
|
|
1033
1085
|
### 6. Spread, and ask
|
|
1034
1086
|
|
|
@@ -1047,10 +1099,10 @@ with **these two fields replaced** and `spec`, `archetype` and `cut` unchanged:
|
|
|
1047
1099
|
"tracks": [
|
|
1048
1100
|
{ "bone": "arm", "property": "rotate", "keys": [
|
|
1049
1101
|
{ "t": 0, "v": [-61.9], "ease": "drive" },
|
|
1050
|
-
{ "t": 0.55, "v": [18.
|
|
1102
|
+
{ "t": 0.55, "v": [18.3] } ] },
|
|
1051
1103
|
{ "bone": "flag", "property": "rotate", "keys": [
|
|
1052
|
-
{ "t": 0, "v": [-22.
|
|
1053
|
-
{ "t": 0.55, "v": [-11.
|
|
1104
|
+
{ "t": 0, "v": [-22.2], "ease": "drive" },
|
|
1105
|
+
{ "t": 0.55, "v": [-11.7] } ] }
|
|
1054
1106
|
]
|
|
1055
1107
|
}
|
|
1056
1108
|
}
|
|
@@ -1063,20 +1115,21 @@ rigc vote --candidate spine --candidate spine-b
|
|
|
1063
1115
|
|
|
1064
1116
|
```
|
|
1065
1117
|
rigc vote
|
|
1066
|
-
.. ballot
|
|
1118
|
+
.. ballot bce70ad82fe90941
|
|
1067
1119
|
.. animation raise
|
|
1068
|
-
.. A sha256:
|
|
1069
|
-
.. B sha256:
|
|
1120
|
+
.. A sha256:6740d3dbf304… 3 page(s), 0.4 KiB <- …/semaphore/spine/skeleton.json
|
|
1121
|
+
.. B sha256:d1bda24d7c36… 3 page(s), 0.4 KiB <- …/semaphore/spine-b/skeleton.json
|
|
1070
1122
|
.. the page shows A/B and nothing else — the paths above are in its manifest, never on the screen
|
|
1071
|
-
|
|
1072
|
-
rigc:
|
|
1123
|
+
.. embedded every candidate's skeleton, atlas and page(s) as data URIs; the player itself loads from unpkg (@4.3.*), so the first open needs a network
|
|
1124
|
+
rigc: wrote …/semaphore/ballot.html (22.3 KiB — open it in a browser)
|
|
1125
|
+
rigc: then record the saved vote with rigc vote --record vote-bce70ad82fe90941.json --ballot …/semaphore/ballot.html
|
|
1073
1126
|
```
|
|
1074
1127
|
|
|
1075
1128
|
A person opens that page, watches two loops, picks one, and saves the small JSON it hands
|
|
1076
1129
|
them. Then:
|
|
1077
1130
|
|
|
1078
1131
|
```bash
|
|
1079
|
-
rigc vote --record vote-
|
|
1132
|
+
rigc vote --record vote-bce70ad82fe90941.json --ballot ballot.html
|
|
1080
1133
|
```
|
|
1081
1134
|
|
|
1082
1135
|
```
|
|
@@ -1087,8 +1140,8 @@ rigc vote --record vote-15b3f32bbbce77be.json --ballot ballot.html
|
|
|
1087
1140
|
PASS V04_CHOICE_IS_ON_THE_BALLOT
|
|
1088
1141
|
PASS V05_REASON_CODE_FITS_THE_CHOICE
|
|
1089
1142
|
PASS V06_NOT_ALREADY_RECORDED
|
|
1090
|
-
.. winner A = sha256:
|
|
1091
|
-
.. coverage 2 candidate(s): A=sha256:
|
|
1143
|
+
.. winner A = sha256:6740d3dbf3043ff39c5f90577407c381f6841ff6af921517be7a0cfd3d7eb9d3, reason code preferred
|
|
1144
|
+
.. coverage 2 candidate(s): A=sha256:6740d3dbf304… B=sha256:d1bda24d7c36…
|
|
1092
1145
|
rigc: appended line 1 to …/semaphore/votes.jsonl
|
|
1093
1146
|
```
|
|
1094
1147
|
|
|
@@ -1168,7 +1221,7 @@ skeleton — AUTHORING §10.3 and PROMPTING clause 4 both price it. Keys are str
|
|
|
1168
1221
|
The bytes of `poseA.png` and `poseB.png`, so §6 runs end to end. Both are 160×200 on a
|
|
1169
1222
|
flat ground; both are invented.
|
|
1170
1223
|
|
|
1171
|
-
```bash
|
|
1224
|
+
```bash setup
|
|
1172
1225
|
bun -e '
|
|
1173
1226
|
const frames = {
|
|
1174
1227
|
"poseA.png": "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",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, round-trips it through spine-core and named assertions, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/compile.ts
CHANGED
|
@@ -38,7 +38,9 @@ import { parseJsonWithPosition } from './json-position.ts';
|
|
|
38
38
|
// The "did you mean" list on a missing atlas region, from the one implementation
|
|
39
39
|
// of it — the same search serves `refuseUnknownKeys`, and a second copy here with
|
|
40
40
|
// a threshold edited is how such a pair drifts apart.
|
|
41
|
-
import { nearMisses } from './keys.ts';
|
|
41
|
+
import { dottedPath, nearMisses, refuseNumbersTheFileCannotCarry, refuseValuesOfTheWrongType } from './keys.ts';
|
|
42
|
+
import type { ShapeVisit } from './keys.ts';
|
|
43
|
+
import { MANIFEST_TYPES } from './types.ts';
|
|
42
44
|
import { inEditorKeyOrder, withoutParserDefaults } from './keyorder.ts';
|
|
43
45
|
import { EVERY_GLOBAL_PHYSICS, parseMotionSpec } from './motion.ts';
|
|
44
46
|
import {
|
|
@@ -1996,6 +1998,30 @@ export function droppedStateReason(dropped: DroppedState): string {
|
|
|
1996
1998
|
return dropped.why ?? `no PNG at ${dropped.path}`;
|
|
1997
1999
|
}
|
|
1998
2000
|
|
|
2001
|
+
/**
|
|
2002
|
+
* Every node of a cut manifest that `MANIFEST_TYPES` has a row for, in document
|
|
2003
|
+
* order, named by its dotted path (`parts[2].mesh.center[1]`). A node that is
|
|
2004
|
+
* not an object is skipped: whether it should have been one is the reader's
|
|
2005
|
+
* refusal, and the rows below it have nothing to type.
|
|
2006
|
+
*/
|
|
2007
|
+
function manifestShapeVisits(manifest: unknown): ShapeVisit[] {
|
|
2008
|
+
const visits: ShapeVisit[] = [];
|
|
2009
|
+
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
2010
|
+
const at = (node: unknown, shape: keyof typeof MANIFEST_TYPES, path: Array<string | number>): void => {
|
|
2011
|
+
if (isRecord(node)) visits.push({ node, shape, name: (tail) => dottedPath([...path, ...tail]) });
|
|
2012
|
+
};
|
|
2013
|
+
if (!isRecord(manifest)) return visits;
|
|
2014
|
+
at(manifest, 'FaceManifest', []);
|
|
2015
|
+
for (const field of ['crop', 'axis', 'stroke', 'roi'] as const) at(manifest[field], `FaceManifest.${field}`, [field]);
|
|
2016
|
+
for (const [i, part] of (Array.isArray(manifest.parts) ? manifest.parts : []).entries()) {
|
|
2017
|
+
at(part, 'FaceManifestPart', ['parts', i]);
|
|
2018
|
+
if (!isRecord(part)) continue;
|
|
2019
|
+
at(part.mesh, 'FaceManifestMesh', ['parts', i, 'mesh']);
|
|
2020
|
+
if (isRecord(part.mesh)) at(part.mesh.bias, 'FaceManifestMesh.bias', ['parts', i, 'mesh', 'bias']);
|
|
2021
|
+
}
|
|
2022
|
+
return visits;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1999
2025
|
/**
|
|
2000
2026
|
* Compile a rig spec and a motion spec into Spine 4.3 skeleton data.
|
|
2001
2027
|
*
|
|
@@ -2030,6 +2056,14 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2030
2056
|
// and which refusals it deliberately leaves in this file.
|
|
2031
2057
|
const motion = parseMotionSpec(readJson<unknown>(motionPath), motionPath);
|
|
2032
2058
|
const manifest = manifestPath === null ? null : readJson<FaceManifest>(manifestPath);
|
|
2059
|
+
// The manifest is the record of what was measured, and its numbers become
|
|
2060
|
+
// bone positions and the stage: a `crop.h` of 1e309 built as `"height": null`
|
|
2061
|
+
// before issue #881. The rig spec's walk is `parseRigSpec`'s; this is the
|
|
2062
|
+
// same rule over the other file whose numbers reach the skeleton.
|
|
2063
|
+
// Its values of the wrong type first (issue #890): `crop.h: "806"` built
|
|
2064
|
+
// green, the string coerced by whatever arithmetic read it.
|
|
2065
|
+
if (manifest !== null && manifestPath !== null) refuseValuesOfTheWrongType(manifestShapeVisits(manifest), MANIFEST_TYPES, manifestPath);
|
|
2066
|
+
if (manifest !== null && manifestPath !== null) refuseNumbersTheFileCannotCarry(manifest, manifestPath, dottedPath);
|
|
2033
2067
|
|
|
2034
2068
|
// The rig spec names its own path in every message `parseRigSpec` throws (its
|
|
2035
2069
|
// `where` argument, above). The motion spec gets no such treatment below —
|