spine-rigc 1.5.1 → 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 +31 -1
- package/docs/MOTION.md +107 -68
- package/package.json +1 -1
- package/src/compile.ts +30 -1
- package/src/keys.ts +212 -4
- package/src/motion.ts +117 -3
- package/src/pose.ts +53 -0
- package/src/rig.ts +197 -31
- package/src/types.ts +42 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -1306,6 +1306,18 @@ past that line would be emitted as Infinity, which JSON writes as `null`: `1e309
|
|
|
1306
1306
|
both refused by the field they are in, before anything is compiled (§5.1). The cut
|
|
1307
1307
|
manifest and the motion spec are held to the same line.
|
|
1308
1308
|
|
|
1309
|
+
🔤 **Every field below holds one JSON type, and a value of another is refused.**
|
|
1310
|
+
Where a table says a field is a position, an angle, a scale, a mix, a count, a
|
|
1311
|
+
size or an index, it is a JSON **number** — `5`, never `"5"`; a flag is `true` or
|
|
1312
|
+
`false`, never `"true"` or `1`; a name or a path is a string; a list of vertices,
|
|
1313
|
+
uvs or triangles is an array of numbers, typed element by element. A JSON `null`
|
|
1314
|
+
is a value, not an absence, and is accepted only where a table below says what
|
|
1315
|
+
`null` means. The type of every key, shape by shape, is the table `RIG_TYPES` in
|
|
1316
|
+
[`src/rig.ts`](../src/rig.ts) (`MOTION_TYPES` in [`src/motion.ts`](../src/motion.ts),
|
|
1317
|
+
`MANIFEST_TYPES` in [`src/types.ts`](../src/types.ts)), and the refusal names the
|
|
1318
|
+
field, the type it found and the type it required (§5.1) — so that sentence, not a
|
|
1319
|
+
column restated beside each row here, is what to act on.
|
|
1320
|
+
|
|
1309
1321
|
🚫 **Every example value below is invented.** Names, coordinates, vertex lists and
|
|
1310
1322
|
payloads in this guide are written to illustrate a field, never copied out of a
|
|
1311
1323
|
reference export — an example lifted from one would be handing an authoring agent an
|
|
@@ -5569,7 +5581,8 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5569
5581
|
| `a rig spec needs a non-empty "bones" array` | §3.2 — a rig with no bones has nothing to hang a slot on; every one has at least a root |
|
|
5570
5582
|
| `a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)` | §3.3 — write `[]` for a rig that draws nothing. ⚠️ These three arrive only when the key is really absent: **misspelt**, it is the unknown-key refusal above, naming what you wrote |
|
|
5571
5583
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
5572
|
-
| `<file>: <where> is
|
|
5584
|
+
| `<file>: <where> is a string "5"; a number is required` (also `is a boolean true`, `is an array [1]`, `is null`, `is a number 7; a string is required`, `…; an array of numbers is required`, `…; a boolean (true or false) is required`, `…; a string or null is required`) | §3 / §4 — write the value as the JSON type its field holds: a number as a JSON number (`5`, not `"5"`), a flag as `true`/`false`, a name as a string. Every field of the three input files has one type, and a value of another is refused by the field before any reader does arithmetic on it — which it used to: `"x": "5"` built as `5`, `true` as `1` and `[1]` as `1`, and a `null` built too, all green. An array is typed element by element, so the refusal names the index (`weights[3][1].x`, `vertices[0]`). `null` is refused as `null` except where the field states it means something (`skeleton.width`/`height`, `skeleton.audio`, a slot's and a setup entry's `attachment`, a deform key's `vertices`, the manifest's `image`, `states`, `contact_depth` and `cap_containment_ceiling`). `<where>` is the rig spec's wording (`bone "hip" x`, `skin "default" slot "s" attachment "a" rotation`, `constraint "aim" mix`), the dotted path in the cut manifest (`crop.h`, `parts[2].offset[0]`), and the motion spec's own path (`` `animations."walk".ik[0].keys[0].mix` ``). In the rig spec and the manifest it runs right after the key scan and before every other check, so a field check that used to name a type (a mesh name that is not a string, a `lengths` entry spelt as a string) now reads as this row; in the motion spec it runs after that file's own field checks, which keep their sentences. A closed set of names (`blend`, `inherit`, a constraint's `type`, a generator's `kind`) keeps its own refusal, which lists the names that exist |
|
|
5585
|
+
| `<file>: <where> is Infinity; a number in this file is finite at float32 precision, at most ±3.4028234663852886e+38, because the skeleton is written as float32 — …` (also `is -Infinity`, `is 1e+308`, and `is NaN` from a library caller) | §3 — state the number you meant, inside float32's range. One walk over the whole file, run right after the key scan and before any other rig-spec check, so a range rule never prints `NaN`. `<where>` is every number in the rig spec — a bone (`bone "hip" x`), a slot, a skin's attachment down to its arrays (`skin "default" slot "s" attachment "a" weights[3][1].x`), a constraint (`constraint "aim" mixRotate`), an event, `skeleton`, `invariants.meshTriangles` — and the dotted path in the cut manifest (`crop.h`) and in the motion spec (`` `animations.idle.tracks[0].keys[1].v[0]` ``, after the motion spec's own field checks, which keep their sentences). ⚠️ It sees numbers only: `"x": "NaN"` is a string, and the row above refuses it as one |
|
|
5573
5586
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
5574
5587
|
| `two ik constraints are called "X" — a constraint resolves by name AND type (\`SkeletonData.findConstraint\`), so names are unique PER KIND: an ik and a transform constraint may share one, two of a kind may not` | §3.5 — rename one of the two. The kind in the sentence is the pair's own, so `two transform constraints are called "X"` is the same refusal on another kind; a name shared **across** kinds is not this error |
|
|
5575
5588
|
| `physics constraint "X" is declared in both the rig spec and the motion spec's physics table` | §4.6 — the rig spec declares a physics constraint's structure and the motion spec's `physics` table declares one outright; pick the file it belongs in. Per kind, like every other constraint name: an `ik` "X" in the rig spec beside a `physics` "X" here is two constraints and is not this error |
|
|
@@ -8936,6 +8949,21 @@ quarter alone found 234, losing the native arms #877 was made for. The one lost
|
|
|
8936
8949
|
margin reading again: the blurred 32 px front shin (walk) now reports its truth as its
|
|
8937
8950
|
best (0.07 px, 0.00968), with its old 0.51 px answer 0.0045 above it.
|
|
8938
8951
|
|
|
8952
|
+
Issue #892 asked whether the coarse field, which keeps **one rotation per anchor
|
|
8953
|
+
cell**, loses the truth more often than on that post, and the answer is no. The
|
|
8954
|
+
tool now records, per trial, the coarse seed nearest the truth, the rotation the
|
|
8955
|
+
field kept for its cell and the field's own score there at the truth's rotation.
|
|
8956
|
+
Within 2.4 px of the truth that seed is a half turn off on 2 of the 238 found
|
|
8957
|
+
trials (a mouth at 48 px, both found anyway) and on none of the 6 misses, and
|
|
8958
|
+
every failed trial whose nearest seed is a quarter or half turn off but one is a
|
|
8959
|
+
flat part, none of them a miss. On the post itself, the upright rung scores 0.305
|
|
8960
|
+
at that cell against the half turn's 0.168, so a second rotation per cell would
|
|
8961
|
+
need a band 1.8 times wide to carry it. Re-gridding the rotation at the coarse level
|
|
8962
|
+
instead of the next one took the grid to 236 found (7 gained, 9 lost), and
|
|
8963
|
+
suppressing minima only within a rotation family took it to 231 (2 gained, 10
|
|
8964
|
+
lost). Both lose trials far from the truth, so neither shipped and the field is
|
|
8965
|
+
unchanged.
|
|
8966
|
+
|
|
8939
8967
|
⚠️ **What this is and is not.** The grid is not a reliability line: six native cells
|
|
8940
8968
|
place 14 or 15 of 15 and the trials at `detail` 3 and over place 65 of 66, but the
|
|
8941
8969
|
goggles still place 13 of 42, and a threshold read off two dozen parts of one figure
|
|
@@ -8962,6 +8990,8 @@ no miss at all, and `PO23` places the 48 px fist #865 was made for; `PO28` and
|
|
|
8962
8990
|
cutoff, and `PO26` holds every polish to strictly descending residuals, read through
|
|
8963
8991
|
the same trace. `PO31` places §6's post at the old placement from the page's own
|
|
8964
8992
|
input bytes, and `PO32` holds it to taking the quarter-rung escape and reading the same twice.
|
|
8993
|
+
`PO33` holds the coarse row the tool reads rotations from to the field each seed was
|
|
8994
|
+
picked from.
|
|
8965
8995
|
|
|
8966
8996
|
## 12. Reading the half of that picture `pose` refuses — `rigc chainfit`
|
|
8967
8997
|
|
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
|
|
|
@@ -817,12 +820,15 @@ rigc pose
|
|
|
817
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
|
|
@@ -859,18 +865,20 @@ rigc pose
|
|
|
859
865
|
|
|
860
866
|
Three things to read out of that pair, none of which is a score:
|
|
861
867
|
|
|
862
|
-
- **Scale.** All six readings sit in 0.
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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.
|
|
866
874
|
- **Draw order, from `unexplained`.** The post reads **16 %** unexplained in pose A and
|
|
867
875
|
**9 %** in pose B, at placements that barely move — §2.2's occlusion signature. The arm
|
|
868
876
|
crosses more of the post in pose A, so the arm is **in front of** the post. The flag
|
|
869
|
-
reads 0 %
|
|
877
|
+
reads 0 % in both: nothing covers it, so it is **in front of** the arm. ⇒ Slots in
|
|
870
878
|
the order `post`, `arm`, `flag` (AUTHORING R4 — the slots array *is* the draw order,
|
|
871
879
|
and there is nowhere else in the file to say it).
|
|
872
880
|
- **The post does not move**, so its two readings are two measurements of one number:
|
|
873
|
-
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
|
|
874
882
|
disagreement as the noise floor for every other number on the page.
|
|
875
883
|
|
|
876
884
|
### 3. Convert, and derive the rig
|
|
@@ -880,33 +888,59 @@ Three things to read out of that pair, none of which is a score:
|
|
|
880
888
|
|
|
881
889
|
| | pose A, Spine world | pose B, Spine world |
|
|
882
890
|
| --- | --- | --- |
|
|
883
|
-
| `post` | x 79.9 · y 51.
|
|
884
|
-
| `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.
|
|
885
|
-
| `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° |
|
|
886
894
|
|
|
887
895
|
**The shoulder, by §3.9's solve.** The arm's screen rotation changes from 61.9° to
|
|
888
|
-
−18.
|
|
889
|
-
1.
|
|
890
|
-
102.
|
|
891
|
-
pixel **(6.
|
|
892
|
-
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.
|
|
893
903
|
|
|
894
904
|
**The flag hinge, by §3.9's default — and this is the interesting one.** The relative
|
|
895
|
-
angle across that joint is 22.
|
|
896
|
-
`|det| = 0.
|
|
897
|
-
band. Run it anyway, to see what it would have cost — it
|
|
898
|
-
|
|
899
|
-
draws its hinge as a dark hoist strip down one edge, whose centre
|
|
900
|
-
**(2.5, 10)
|
|
901
|
-
**
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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.
|
|
905
936
|
|
|
906
937
|
⭐ Worth pausing on, because it is what §3.9 is for: **the two solves are
|
|
907
938
|
indistinguishable from the inside.** Both are exact, both reconstruct to a point that
|
|
908
|
-
agrees between the poses, and no residual anywhere in either pose report moves.
|
|
909
|
-
|
|
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.
|
|
910
944
|
|
|
911
945
|
`semaphore.rig.json` — complete, nothing trimmed:
|
|
912
946
|
|
|
@@ -918,9 +952,9 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
918
952
|
"skeleton": { "width": 160, "height": 200 },
|
|
919
953
|
"bones": [
|
|
920
954
|
{ "name": "root" },
|
|
921
|
-
{ "name": "post", "parent": "root", "x": 79.95, "y": 51.
|
|
922
|
-
{ "name": "arm", "parent": "post", "x": 0.
|
|
923
|
-
{ "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 }
|
|
924
958
|
],
|
|
925
959
|
"slots": [
|
|
926
960
|
{ "name": "post", "bone": "post", "attachment": "post" },
|
|
@@ -930,7 +964,7 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
930
964
|
"skins": {
|
|
931
965
|
"default": {
|
|
932
966
|
"post": { "post": { "image": "post.png" } },
|
|
933
|
-
"arm": { "arm": { "image": "arm.png", "x": 23.
|
|
967
|
+
"arm": { "arm": { "image": "arm.png", "x": 23.24 } },
|
|
934
968
|
"flag": { "flag": { "image": "flag.png", "x": 11.5 } }
|
|
935
969
|
}
|
|
936
970
|
}
|
|
@@ -940,8 +974,8 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
940
974
|
The two attachment offsets are the last of the arithmetic. A bone sits at its pivot and
|
|
941
975
|
the placement told you where the image's **centre** goes, so the offset is the gap
|
|
942
976
|
between them, in the bone's own axes with y flipped once: the arm's pivot is at image
|
|
943
|
-
(6.
|
|
944
|
-
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
|
|
945
979
|
its centre at (14, 10), giving **x 11.5** exactly. Bone rotations are left off, which
|
|
946
980
|
means *as drawn* — the arm plate is drawn horizontal and the post vertical, so the poses
|
|
947
981
|
are entirely the motion spec's business.
|
|
@@ -976,19 +1010,19 @@ overshoot (§3.8). The post is planted: ⛔ **no timeline**.
|
|
|
976
1010
|
"keys": [
|
|
977
1011
|
{ "t": 0, "v": [-61.9], "ease": "gather" },
|
|
978
1012
|
{ "t": 0.07, "v": [-66.4], "ease": "charge" },
|
|
979
|
-
{ "t": 0.32, "v": [24.
|
|
980
|
-
{ "t": 0.55, "v": [18.
|
|
1013
|
+
{ "t": 0.32, "v": [24.7], "ease": "settle" },
|
|
1014
|
+
{ "t": 0.55, "v": [18.3] }
|
|
981
1015
|
]
|
|
982
1016
|
},
|
|
983
1017
|
{
|
|
984
1018
|
"bone": "flag",
|
|
985
1019
|
"property": "rotate",
|
|
986
1020
|
"keys": [
|
|
987
|
-
{ "t": 0, "v": [-22.
|
|
1021
|
+
{ "t": 0, "v": [-22.2], "ease": "gather" },
|
|
988
1022
|
{ "t": 0.09, "v": [-30.1], "ease": "charge" },
|
|
989
1023
|
{ "t": 0.38, "v": [-3.8], "ease": "settle" },
|
|
990
1024
|
{ "t": 0.48, "v": [-15.4], "ease": "settle" },
|
|
991
|
-
{ "t": 0.55, "v": [-11.
|
|
1025
|
+
{ "t": 0.55, "v": [-11.7] }
|
|
992
1026
|
]
|
|
993
1027
|
}
|
|
994
1028
|
]
|
|
@@ -1002,10 +1036,10 @@ which is which is worth being able to point at:
|
|
|
1002
1036
|
|
|
1003
1037
|
| Key | Where it came from |
|
|
1004
1038
|
| --- | --- |
|
|
1005
|
-
| arm `t: 0` = −61.9, flag `t: 0` = −22.
|
|
1006
|
-
| 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 |
|
|
1007
1041
|
| arm `t: 0.07` = −66.4 | §3.6 — 4.5° against an 80° excursion (5.6 %), at 13 % of the duration |
|
|
1008
|
-
| 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 |
|
|
1009
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 |
|
|
1010
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 |
|
|
1011
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) |
|
|
@@ -1029,20 +1063,24 @@ rigc render --candidate spine --fps 24 --max 200
|
|
|
1029
1063
|
```
|
|
1030
1064
|
|
|
1031
1065
|
```
|
|
1032
|
-
..
|
|
1066
|
+
.. 134x200px at 24 fps, 1 set(s) -> …/semaphore/render
|
|
1033
1067
|
.. raise 14 frame(s), 0.542s + contact.png -> …/semaphore/render/raise@24fps
|
|
1034
1068
|
```
|
|
1035
1069
|
|
|
1036
1070
|
Open `render/raise@24fps/contact.png` **before anything else** — fourteen frames as one
|
|
1037
1071
|
grid, and spacing is a comparison across frames rather than a property of any one of
|
|
1038
|
-
them. What to check on it, and it is not a score: frame 0 is pose A
|
|
1039
|
-
|
|
1040
|
-
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.
|
|
1041
1075
|
|
|
1042
1076
|
🚫 **Do not run `rigc check` against `poseA.png` and `poseB.png`.** Two pictures are not
|
|
1043
|
-
a frame set
|
|
1044
|
-
|
|
1045
|
-
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.
|
|
1046
1084
|
|
|
1047
1085
|
### 6. Spread, and ask
|
|
1048
1086
|
|
|
@@ -1061,10 +1099,10 @@ with **these two fields replaced** and `spec`, `archetype` and `cut` unchanged:
|
|
|
1061
1099
|
"tracks": [
|
|
1062
1100
|
{ "bone": "arm", "property": "rotate", "keys": [
|
|
1063
1101
|
{ "t": 0, "v": [-61.9], "ease": "drive" },
|
|
1064
|
-
{ "t": 0.55, "v": [18.
|
|
1102
|
+
{ "t": 0.55, "v": [18.3] } ] },
|
|
1065
1103
|
{ "bone": "flag", "property": "rotate", "keys": [
|
|
1066
|
-
{ "t": 0, "v": [-22.
|
|
1067
|
-
{ "t": 0.55, "v": [-11.
|
|
1104
|
+
{ "t": 0, "v": [-22.2], "ease": "drive" },
|
|
1105
|
+
{ "t": 0.55, "v": [-11.7] } ] }
|
|
1068
1106
|
]
|
|
1069
1107
|
}
|
|
1070
1108
|
}
|
|
@@ -1077,20 +1115,21 @@ rigc vote --candidate spine --candidate spine-b
|
|
|
1077
1115
|
|
|
1078
1116
|
```
|
|
1079
1117
|
rigc vote
|
|
1080
|
-
.. ballot
|
|
1118
|
+
.. ballot bce70ad82fe90941
|
|
1081
1119
|
.. animation raise
|
|
1082
|
-
.. A sha256:
|
|
1083
|
-
.. 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
|
|
1084
1122
|
.. the page shows A/B and nothing else — the paths above are in its manifest, never on the screen
|
|
1085
|
-
|
|
1086
|
-
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
|
|
1087
1126
|
```
|
|
1088
1127
|
|
|
1089
1128
|
A person opens that page, watches two loops, picks one, and saves the small JSON it hands
|
|
1090
1129
|
them. Then:
|
|
1091
1130
|
|
|
1092
1131
|
```bash
|
|
1093
|
-
rigc vote --record vote-
|
|
1132
|
+
rigc vote --record vote-bce70ad82fe90941.json --ballot ballot.html
|
|
1094
1133
|
```
|
|
1095
1134
|
|
|
1096
1135
|
```
|
|
@@ -1101,8 +1140,8 @@ rigc vote --record vote-15b3f32bbbce77be.json --ballot ballot.html
|
|
|
1101
1140
|
PASS V04_CHOICE_IS_ON_THE_BALLOT
|
|
1102
1141
|
PASS V05_REASON_CODE_FITS_THE_CHOICE
|
|
1103
1142
|
PASS V06_NOT_ALREADY_RECORDED
|
|
1104
|
-
.. winner A = sha256:
|
|
1105
|
-
.. 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…
|
|
1106
1145
|
rigc: appended line 1 to …/semaphore/votes.jsonl
|
|
1107
1146
|
```
|
|
1108
1147
|
|
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 { dottedPath, nearMisses, refuseNumbersTheFileCannotCarry } 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
|
*
|
|
@@ -2034,6 +2060,9 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2034
2060
|
// bone positions and the stage: a `crop.h` of 1e309 built as `"height": null`
|
|
2035
2061
|
// before issue #881. The rig spec's walk is `parseRigSpec`'s; this is the
|
|
2036
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);
|
|
2037
2066
|
if (manifest !== null && manifestPath !== null) refuseNumbersTheFileCannotCarry(manifest, manifestPath, dottedPath);
|
|
2038
2067
|
|
|
2039
2068
|
// The rig spec names its own path in every message `parseRigSpec` throws (its
|