spine-rigc 0.20.0 → 0.20.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/README.md +2 -1
- package/docs/AUTHORING.md +83 -25
- package/docs/FACE.md +244 -43
- package/docs/INGEST.md +11 -15
- package/docs/MOTION.md +52 -0
- package/package.json +1 -1
- package/src/diff.ts +14 -4
package/README.md
CHANGED
|
@@ -443,7 +443,7 @@ limits: [AUTHORING.md §11](docs/AUTHORING.md). The parts it refuses because
|
|
|
443
443
|
something is drawn over them are `rigc chainfit`'s, once a candidate exists —
|
|
444
444
|
[§12](docs/AUTHORING.md).
|
|
445
445
|
|
|
446
|
-
## The gallery —
|
|
446
|
+
## The gallery — seven complete rigs over art that ships with them
|
|
447
447
|
|
|
448
448
|
Each directory in [`gallery/`](https://github.com/firejune/rigc/tree/main/gallery) is
|
|
449
449
|
one rig spec, one motion spec and the PNGs they name, small enough to read in one
|
|
@@ -460,6 +460,7 @@ was verified, and what writing it cost. Repository material: a clone and
|
|
|
460
460
|
| [`gallery/ride`](https://github.com/firejune/rigc/tree/main/gallery/ride) | `path` attachments + **path constraints** | A trolley coasting down a drawn rail and rolling back, driven by a `position` timeline, with `groups` + `stagger` keying the wheels and the ears |
|
|
461
461
|
| [`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait) | **deform `transform`** + `derive` group tracks | A 2.5D head turn: two meshes and six feature bones all keyed from one stated expression, `dx = x(cos t − 1) − z·sin t`, with the depths in the spec rather than a README |
|
|
462
462
|
| [`gallery/nod`](https://github.com/firejune/rigc/tree/main/gallery/nod) | the **`pitch`** and **`wave`** transform kinds | A head bowing and two lop ears rippling, on three meshes each laid out for the closed form that moves it — a fold angle solved for before authoring, and a shear whose winding no amplitude can reverse |
|
|
463
|
+
| [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) | **`slider` constraints** | A head that turns because a **value** says so: two dials drive two sliders, and the rendered animation moves the needles rather than the face — with a depth map under the face mesh, a soft mask on the cowlick, and a slider range derived from the turn ceiling `build` reports rather than picked by eye |
|
|
463
464
|
|
|
464
465
|
<p align="center">
|
|
465
466
|
<img src="https://raw.githubusercontent.com/firejune/rigc/main/assets/rigc-scene.gif" alt="A portrait rig breathing, glancing aside, then turning its head in 2.5D — hair and features sliding at different depths" width="600" />
|
package/docs/AUTHORING.md
CHANGED
|
@@ -773,8 +773,13 @@ between two things it has in front of it: the emitted triangles, and the PNG the
|
|
|
773
773
|
attachment names with `image`. So any mesh that names one gets the figure on its
|
|
774
774
|
`MESH` line, authored or generated:
|
|
775
775
|
|
|
776
|
+
```bash
|
|
777
|
+
bun cli.ts build --rig gallery/squash/rig.json \
|
|
778
|
+
--motion gallery/squash/motion.json --out /tmp/squash
|
|
779
|
+
```
|
|
780
|
+
|
|
776
781
|
```
|
|
777
|
-
MESH ball authored 9 vertices / 8 triangles (budget 8) bones=[ball] attachments=[ball] covers
|
|
782
|
+
MESH ball authored 9 vertices / 8 triangles (budget 8) bones=[ball] attachments=[ball] covers 100.00% of the art, reaching 15.00px past it
|
|
778
783
|
```
|
|
779
784
|
|
|
780
785
|
**A number, not a bar.** A `contour` under 99.5% is *refused* because rigc
|
|
@@ -782,11 +787,17 @@ generated that geometry as a claim about the art; an authored mesh that sits ins
|
|
|
782
787
|
its art is a legitimate thing to draw — a soft feather, a trimmed hull, a mesh
|
|
783
788
|
meant to bend a core while its edges stretch — so the figure informs and the
|
|
784
789
|
decision stays with the author. A mesh with no `image` reports nothing, because
|
|
785
|
-
there is nothing to measure it against.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
+
there is nothing to measure it against.
|
|
791
|
+
|
|
792
|
+
**The silence was worth closing, and that example is where it was found.** The
|
|
793
|
+
ball is a centre vertex plus 8 rim vertices, and the first version placed them
|
|
794
|
+
*on* the silhouette — but an octagon's sides pass `R · cos(π/8)` from its centre,
|
|
795
|
+
so its whole ink outline between the spokes was not going to be drawn, and every
|
|
796
|
+
assertion passed (issue #277). That is why the command above prints 100.00%
|
|
797
|
+
rather than the figure it was filed over:
|
|
798
|
+
[`gallery/squash`](https://github.com/firejune/rigc/tree/main/gallery/squash)'s
|
|
799
|
+
README carries the inradius arithmetic, both coverage readings, and the rim move
|
|
800
|
+
that settled it.
|
|
790
801
|
|
|
791
802
|
The generators are `ring`, `ribbon`, `contour` and `grid` (see
|
|
792
803
|
[`src/mesh.ts`](../src/mesh.ts)); the first two encode a deformation model rather
|
|
@@ -1032,10 +1043,14 @@ triangle does not, and nothing on the first line says so.
|
|
|
1032
1043
|
| `which is 0.049 of the range this mesh sampled` — the **same step, over the range this mesh sampled** | how much of everything the sheet said across the whole part it said across that one triangle. A form's slope is bounded, so this **halves every time you double the lattice** while the angle settles. Near 1 it is a **cliff**: a step with no slope in it, whose angle halves with the lattice instead and describes nothing at any density. Measured: `gallery/look` reads 0.112 and 0.468, a synthetic raised cosine 0.394 falling to 0.027 under refinement, the same cosine with one planted cliff a flat 0.50, and estimated depth sheets over cut-out art **0.92–0.99** |
|
|
1033
1044
|
| `+none` | on the ceiling line, nothing folds on that side at all, at any angle. On the percentile line it is the same statement — there is no population, because there is nothing to take a percentile of |
|
|
1034
1045
|
|
|
1035
|
-
A real one rather than the illustration above — `
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1046
|
+
A real one rather than the illustration above — `gallery/look`, whose two meshes
|
|
1047
|
+
happen to print two of the three spellings:
|
|
1048
|
+
|
|
1049
|
+
```bash
|
|
1050
|
+
bun cli.ts build --rig gallery/look/rig.json \
|
|
1051
|
+
--motion gallery/look/motion.json \
|
|
1052
|
+
--images gallery/look/parts --out /tmp/look
|
|
1053
|
+
```
|
|
1039
1054
|
|
|
1040
1055
|
```
|
|
1041
1056
|
MESH head grid 189 vertices / 320 triangles (budget 320) bones=[head] attachments=[head]
|
|
@@ -1653,6 +1668,13 @@ rather than 1 s. With `loop: false` that is the last frame and harmless, with
|
|
|
1653
1668
|
one — pick `to`/`scale` so the endpoint lands **inside** the duration rather than
|
|
1654
1669
|
exactly on it.
|
|
1655
1670
|
|
|
1671
|
+
🖼️ **Worked example: [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look)** — a face
|
|
1672
|
+
whose yaw and pitch are two sliders sharing one bone, both `local: true` and both
|
|
1673
|
+
`additive: true`, with each range derived from the turn ceiling `build` reports
|
|
1674
|
+
for that mesh (§3.4) rather than chosen. [`docs/FACE.md`](FACE.md) §8's *The turn
|
|
1675
|
+
as a value rather than a time* states that derivation as a rule, and its §7 is
|
|
1676
|
+
what a slider does to a face's channel allocation.
|
|
1677
|
+
|
|
1656
1678
|
### 3.6 `events` — names the animation can fire
|
|
1657
1679
|
|
|
1658
1680
|
**When you need one:** something outside the skeleton has to happen on a
|
|
@@ -2082,6 +2104,7 @@ is not the arrangement the format has. Spine keys one bone per timeline, so the
|
|
|
2082
2104
|
six numbers of a head turn are eighty lines apart in the artifact and nobody can
|
|
2083
2105
|
see a wrong sign in them.
|
|
2084
2106
|
|
|
2107
|
+
**No run reproduces this:** the legend and one group's record lifted out of one `explain` run, which prints the two `bone "faceshift"` records between them
|
|
2085
2108
|
```
|
|
2086
2109
|
group members (the per-member values of one track, side by side — issue #295)
|
|
2087
2110
|
.. a row per member and a block per key, because a wrong sign is visible in a column of six and
|
|
@@ -2661,10 +2684,15 @@ makes against them.
|
|
|
2661
2684
|
|
|
2662
2685
|
**It is auditable.** `explain` prints the model, the scalars the closed form
|
|
2663
2686
|
derived from it, and every offset it produced — the emitted ones, not a second
|
|
2664
|
-
evaluation:
|
|
2687
|
+
evaluation. This is the `t=0.62` key of the spec above, whole:
|
|
2665
2688
|
|
|
2689
|
+
```bash
|
|
2690
|
+
bun cli.ts explain --rig gallery/portrait/rig.json \
|
|
2691
|
+
--motion gallery/portrait/motion.json --out /tmp/explain
|
|
2666
2692
|
```
|
|
2667
|
-
|
|
2693
|
+
|
|
2694
|
+
```
|
|
2695
|
+
t=0.62 deform[0..50] 25 pair(s) stepped
|
|
2668
2696
|
transform yaw radius=170 degrees=12
|
|
2669
2697
|
dx = (x−about)·(cos t − 1) − z·sin t, z = √(radius² − (x−about)²)
|
|
2670
2698
|
t = 0.20944 rad
|
|
@@ -2673,9 +2701,19 @@ evaluation:
|
|
|
2673
2701
|
centre shift = −radius·sin t = -35.344987
|
|
2674
2702
|
25 vertices, largest offset 35.344987px at vertex 2
|
|
2675
2703
|
v 0 (-7.17493, 0) v 1 (-22.413595, 0) v 2 (-35.344987, 0) v 3 (-27.658171, 0)
|
|
2676
|
-
|
|
2704
|
+
v 4 (-14.255108, 0) v 5 (-14.255108, 0) v 6 (-14.255108, 0) v 7 (-14.255108, 0)
|
|
2705
|
+
v 8 (-14.255108, 0) v 9 (-27.658171, 0) v 10 (-35.344987, 0) v 11 (-22.413595, 0)
|
|
2706
|
+
v 12 (-7.17493, 0) v 13 (-7.17493, 0) v 14 (-7.17493, 0) v 15 (-7.17493, 0)
|
|
2707
|
+
v 16 (-22.413595, 0) v 17 (-35.344987, 0) v 18 (-27.658171, 0) v 19 (-22.413595, 0)
|
|
2708
|
+
v 20 (-35.344987, 0) v 21 (-27.658171, 0) v 22 (-22.413595, 0) v 23 (-35.344987, 0)
|
|
2709
|
+
v 24 (-27.658171, 0)
|
|
2677
2710
|
```
|
|
2678
2711
|
|
|
2712
|
+
The curve reads `stepped` where the spec says `"ease": "swell"`, and that is
|
|
2713
|
+
§4.5's hold rule rather than a discrepancy: the next key emits these same 25
|
|
2714
|
+
offsets, so the segment between them would draw nothing and is written the way
|
|
2715
|
+
the editor writes it.
|
|
2716
|
+
|
|
2679
2717
|
📌 **Float behaviour, stated.** The closed forms are evaluated in float64 and
|
|
2680
2718
|
quantised to six decimals like every other emitted number, so the same spec emits
|
|
2681
2719
|
the same bytes and `A18_DETERMINISTIC_EMIT` proves it on a second compile. The
|
|
@@ -2701,6 +2739,7 @@ triangles. It is a report and it never gates: `explain` takes no `--profile` and
|
|
|
2701
2739
|
exits 0 on a rig `build` would refuse, so the figures are readable on the build
|
|
2702
2740
|
that is failing.
|
|
2703
2741
|
|
|
2742
|
+
**No run reproduces this:** abridged — the `WORST` rollup follows key 1 here, where the run prints `head/head`'s other two keys and all four of `hair_bang/hair_bang` between them
|
|
2704
2743
|
```
|
|
2705
2744
|
deform (what each key does to the geometry — figures with names, never a bar; issue #316)
|
|
2706
2745
|
.. every key measured at its OWN time against the same pose with the deform CLEARED, so the
|
|
@@ -2797,8 +2836,12 @@ clothes. The quantity that does move — how much art each drawn pixel now carri
|
|
|
2797
2836
|
📘 **[FACE.md](FACE.md) §9.2** is this block on real art, as three builds of
|
|
2798
2837
|
`gallery/portrait`: the good one, one with a band inverted, and one folded. The
|
|
2799
2838
|
inverted build is the case worth reading — `A39` passes it (correctly: nothing
|
|
2800
|
-
reverses), and the block is what says `x1.362834`
|
|
2801
|
-
|
|
2839
|
+
reverses), and the block is what says `x1.362834` on the band the model's own key
|
|
2840
|
+
reports as `x0.637174`, with no reference render anywhere. ⭐ The comparison is
|
|
2841
|
+
per **triangle** and that is what makes it a reading: the block names one beside
|
|
2842
|
+
every ratio, so the two blocks can be lined up band against band instead of worst
|
|
2843
|
+
against worst — which on this build would have paired the inverted band with an
|
|
2844
|
+
untouched one and called the difference mild.
|
|
2802
2845
|
|
|
2803
2846
|
📘 **[`gallery/nod`](https://github.com/firejune/rigc/tree/main/gallery/nod)'s
|
|
2804
2847
|
README is a second reading of the same block** (repository material, hence the
|
|
@@ -2823,15 +2866,23 @@ the frames just before it are drawn, nearly folded, and land on no key at all. O
|
|
|
2823
2866
|
the turn probe that is **8 reversed triangles at alpha 0.20, gating green**.
|
|
2824
2867
|
|
|
2825
2868
|
⇒ `A39` now scans every interval between two consecutive deform keys as well, and
|
|
2826
|
-
refuses one with its own sentence
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2869
|
+
refuses one with its own sentence. No spec this repository ships produces one — the
|
|
2870
|
+
rig it was written from is a probe `selftest.ts` generates and nothing else can
|
|
2871
|
+
invoke — so the sentence is described here rather than transcribed.
|
|
2872
|
+
|
|
2873
|
+
**What it carries**, in the order it says it: `BETWEEN key <i> (t=…s) and key <j>
|
|
2874
|
+
(t=…s)` where a key refusal puts one index; the time the closed form solved for, and
|
|
2875
|
+
how far along the segment that is — or `(a stepped segment)` instead, which
|
|
2876
|
+
interpolates nothing and holds the earlier key's geometry across the span; the
|
|
2877
|
+
reversed count out of the triangle total, with the first four named and each one's
|
|
2878
|
+
vertex ids and its signed area before and after; `NO KEY LANDS THERE` in those words,
|
|
2879
|
+
then whether the runtime interpolates across the span or holds it; the alpha read at
|
|
2880
|
+
that same instant, present only where it is not 1; and the ways out — for an
|
|
2881
|
+
interpolating span the four the table below gives, the fade one among them only
|
|
2882
|
+
where the alpha is not 1, and for a stepped one the key it holds instead, with
|
|
2883
|
+
`invariants.deformMayFold` the last resort either way.
|
|
2884
|
+
[`src/validate.ts`](../src/validate.ts) builds it, beside the key sentence §4.11.2
|
|
2885
|
+
quotes.
|
|
2835
2886
|
|
|
2836
2887
|
**What to change when you see it**, in the order worth trying:
|
|
2837
2888
|
|
|
@@ -2899,7 +2950,13 @@ value = from + (time − to) / scale what A39 sets the dial to
|
|
|
2899
2950
|
|
|
2900
2951
|
🔒 **The `DEFORM` block prints the frame on every key**, because the derivation
|
|
2901
2952
|
changed and a block that went on printing the same figures under a changed meaning
|
|
2902
|
-
would be worse than the red it replaced
|
|
2953
|
+
would be worse than the red it replaced. `gallery/look`'s `turn` is the animation
|
|
2954
|
+
a slider applies, and this is one of its keys:
|
|
2955
|
+
|
|
2956
|
+
```bash
|
|
2957
|
+
bun cli.ts explain --rig gallery/look/rig.json \
|
|
2958
|
+
--motion gallery/look/motion.json --out /tmp/explain-look
|
|
2959
|
+
```
|
|
2903
2960
|
|
|
2904
2961
|
```
|
|
2905
2962
|
DEFORM turn default/head/head key 6 t=1.900000 transform yaw depth=true degrees=19
|
|
@@ -3221,6 +3278,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
3221
3278
|
|
|
3222
3279
|
The report prints one line per assertion:
|
|
3223
3280
|
|
|
3281
|
+
**No run reproduces this:** assembled — one line of each verdict kind; the `PASS` and the `PROF` are verbatim from a `build` this page states, the `SKIP` is cut at the ellipsis and only `--profile spine-html` prints it, and the `FAIL` is invented, because a green build prints none
|
|
3224
3282
|
```
|
|
3225
3283
|
PASS A08_REGION_NAMES_MATCH_ATTACHMENTS
|
|
3226
3284
|
SKIP A21_MESH_RIM_PINNED: the skeleton has no weighted mesh attachment, …
|
package/docs/FACE.md
CHANGED
|
@@ -40,16 +40,25 @@ hold its results.
|
|
|
40
40
|
measures §5's foreshortening at **0.863–1.176** against the yaw's 0.892–1.064
|
|
41
41
|
below — a wider span at the same 12°, because a face is taller than it is deep.
|
|
42
42
|
Read it after this page, not instead of it
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
- **The turn driven by a value instead of played as a time** — the same keys, on
|
|
44
|
+
an axis: §8's *The turn as a value rather than a time* derives the range,
|
|
45
|
+
**AUTHORING §3.5.2** is the `slider` constraint that does it, and
|
|
46
|
+
[`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) is
|
|
47
|
+
the worked case. Read it when the angle has to follow something outside the
|
|
48
|
+
animation — a pointer, a gaze target, a game value
|
|
49
|
+
|
|
50
|
+
🚨 **A `deform` key's winding is gated; how far it moved the geometry is not, and
|
|
51
|
+
that half is the one you have to author around.** Since 2026-09-03
|
|
52
|
+
`A39_DEFORM_KEEPS_TRIANGLE_WINDING` refuses a key that turns the mesh inside out —
|
|
53
|
+
by name, by key and by triangle — and the build writes nothing. What no assertion
|
|
54
|
+
has an opinion about is **magnitude**: a band that stretches where the projection
|
|
55
|
+
says it should compress keeps every triangle's winding, so it gates green, beside
|
|
56
|
+
the same `MESH` coverage line as the good build, because coverage reports the
|
|
57
|
+
*setup* pose. §9.2 is that pair as three builds of one rig — the good one, one
|
|
58
|
+
wrong and green, one refused. For the half still ungated, `explain`'s `DEFORM`
|
|
59
|
+
block prints each key's area and stretch ratios per triangle with no reference
|
|
60
|
+
render (§9.2, AUTHORING §4.11.2), and §9.3 is the differential audit, the three
|
|
61
|
+
things it cannot do, and the procedure that survives them.
|
|
53
62
|
|
|
54
63
|
📐 **Where the numbers on this page come from.** Every figure marked **derived**
|
|
55
64
|
is re-computed from the closed form in §1 and reproduces to the digits printed.
|
|
@@ -69,6 +78,7 @@ internal shape:
|
|
|
69
78
|
| a face that **blinks**, **breathes**, **looks around** | ordinary MOTION.md tracks. A lid, a chest, an iris. Nothing on this page is needed |
|
|
70
79
|
| a face that **turns** | a list of `(x, z)` — every part's position across the screen and its **depth** — plus one line of arithmetic evaluated at each of them |
|
|
71
80
|
| a face that turns **far** (a three-quarter view) | ⛔ a different rig, and §8 says where the line is. Not a format problem: a parts-and-labour problem |
|
|
81
|
+
| a face that turns **by however much something outside it says** — a pointer, a gaze target, a game value | the same list and the same arithmetic, reached by a **value** instead of by a playhead: the turn animation becomes a `slider`'s lookup table (§8's last subsection, AUTHORING §3.5.2). What changes is not the geometry, it is the range — which stops being a choice and becomes a measurement |
|
|
72
82
|
|
|
73
83
|
⭐ **The turn is the only part of a face that is not already MOTION.md's job**,
|
|
74
84
|
and it is 90% of this page. A blink is a translating plate (§6); a gaze is
|
|
@@ -524,6 +534,7 @@ asked for.** The `MEMBER` block (AUTHORING §4.5.2) is a row per member — the
|
|
|
524
534
|
emitted value, the `x` it read off the rig, and the depth the spec stated — so the
|
|
525
535
|
nose diagnostic below is now a line you read rather than arithmetic you redo:
|
|
526
536
|
|
|
537
|
+
**No run reproduces this:** abridged — the five derivation lines the run prints between this record's head and its rows are cut; AUTHORING §4.5.2 quotes them
|
|
527
538
|
```
|
|
528
539
|
MEMBER turn group "features".translatex t=0.620000 6 member(s) derive yaw degrees=12 carried=170 -> the displacement
|
|
529
540
|
eye_l 5.513083 <- -62 at depth 150
|
|
@@ -746,8 +757,7 @@ shows the listing beside the order.
|
|
|
746
757
|
over an oval face has transparent corners, and the line says so:
|
|
747
758
|
|
|
748
759
|
```
|
|
749
|
-
MESH head
|
|
750
|
-
attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
760
|
+
MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
751
761
|
```
|
|
752
762
|
|
|
753
763
|
**`covers 100.00%` is what matters** — nothing of the drawing is outside the
|
|
@@ -923,6 +933,22 @@ stack (`headroll_idle` under `headroll_layer`) — but both are runtime or rig
|
|
|
923
933
|
decisions the motion spec cannot express, so nothing warns an author that two of
|
|
924
934
|
their animations will fight.**
|
|
925
935
|
|
|
936
|
+
🚨 **A `slider` is a third way for two animations to meet on one property, and it
|
|
937
|
+
is an overwrite rather than a blend — so allocate it in this table too.** An
|
|
938
|
+
animation a slider applies (AUTHORING §3.5.2) is never on a track: the constraint
|
|
939
|
+
applies it every frame, at whatever time its own bone currently points at. At
|
|
940
|
+
`mix: 1` with `additive` left at its default that apply **writes the property
|
|
941
|
+
outright**, which erases both any earlier slider on the same property and the
|
|
942
|
+
**playing** animation on the bones its animation keys — including at its own
|
|
943
|
+
neutral, where it looks switched off. ⇒ Every face axis that shares a target
|
|
944
|
+
declares `"additive": true`, and unlike the two collisions above this one **is**
|
|
945
|
+
gated: `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` names the bone, the property,
|
|
946
|
+
every slider keying it in array order and which one wins today. ⛔ The one case
|
|
947
|
+
`additive` cannot rescue is a **slot colour, an attachment swap, a draw order or
|
|
948
|
+
a sequence**: those timelines ignore the flag entirely, so a fade — §8's way of
|
|
949
|
+
taking a part off the screen before its own ceiling — belongs *inside* the single
|
|
950
|
+
animation one slider applies, never in a second slider beside it.
|
|
951
|
+
|
|
926
952
|
⛔ **And the cost is real, so name it rather than discovering it by shipping.** In
|
|
927
953
|
the worked example `idle` keys **nothing** on the iris, on purpose, even though a
|
|
928
954
|
completely still eye reads as a mannequin. The iris is `gaze`'s channel; an
|
|
@@ -1057,6 +1083,111 @@ authoring concept**: the on-axis pair's shared `cos t` now falls out of the same
|
|
|
1057
1083
|
closed form as everybody else's value, so `axis` is gone from the spec while
|
|
1058
1084
|
`look_l`/`look_r` stay — because those two really are one shared number (§5).
|
|
1059
1085
|
|
|
1086
|
+
### The turn as a value rather than a time
|
|
1087
|
+
|
|
1088
|
+
Everything above prices a turn as an animation somebody plays. The same geometry
|
|
1089
|
+
also runs on an **axis**: a `slider` constraint reads a driving bone, maps that
|
|
1090
|
+
bone's rotation to a time inside the turn animation, and applies the animation
|
|
1091
|
+
there (AUTHORING §3.5.2). Nothing in §1–§5 changes — the keys are still §1's line
|
|
1092
|
+
evaluated at each angle — but what selects among them is a **value** rather than a
|
|
1093
|
+
playhead, so the face follows a number somebody else is holding: a pointer, a gaze
|
|
1094
|
+
target, a game state.
|
|
1095
|
+
|
|
1096
|
+
⭐ **The object offers a dial and does not decide when it turns.** That is the
|
|
1097
|
+
whole of the claim and it is deliberately not a larger one: what the rig
|
|
1098
|
+
guarantees is the axis — its range, its arithmetic, and that every angle on it is
|
|
1099
|
+
sound — and what moves the dial belongs to whoever is using the face. The
|
|
1100
|
+
paragraphs below are the part that is ours.
|
|
1101
|
+
|
|
1102
|
+
#### The range stops being a choice and becomes a measurement
|
|
1103
|
+
|
|
1104
|
+
This is the half an author has no other way to get right. §4.2's fold angle is not
|
|
1105
|
+
a rule of thumb once the angle is a dial position: it is the **top of the dial**,
|
|
1106
|
+
because past it a triangle turns inside out and `A39` refuses the build by name.
|
|
1107
|
+
`build` prints that angle for every depth mesh it compiles (AUTHORING §3.4), so
|
|
1108
|
+
the whole mapping falls out of one reading:
|
|
1109
|
+
|
|
1110
|
+
```
|
|
1111
|
+
ceiling the largest turn this depth mesh admits — printed by `build`, not guessed
|
|
1112
|
+
range the largest whole degree strictly INSIDE the ceiling
|
|
1113
|
+
from -range max +range local true (AUTHORING §3.5.2's circle)
|
|
1114
|
+
scale seconds per degree — the one number here you choose
|
|
1115
|
+
duration 2 x range x scale
|
|
1116
|
+
time to + (degrees - from) x scale the slider's own mapping
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
📐 **Worked, on [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look)**,
|
|
1120
|
+
whose face is a 21 × 9 `grid` over one depth sheet:
|
|
1121
|
+
|
|
1122
|
+
```bash
|
|
1123
|
+
bun cli.ts build --rig gallery/look/rig.json \
|
|
1124
|
+
--motion gallery/look/motion.json \
|
|
1125
|
+
--out gallery/look/build
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
```
|
|
1129
|
+
MESH head grid 189 vertices / 320 triangles (budget 320) bones=[head] attachments=[head]
|
|
1130
|
+
depth "face_depth.png" bf156ea0cfc970a3 near=white zScale=194 z=[0, 194]
|
|
1131
|
+
80 of 189 vertices sample a texel the part image does not draw — their z is the sheet's reading of somewhere the part is not
|
|
1132
|
+
turn ceiling yaw +19.32° / -19.32° pitch +22.92° / -26.94°
|
|
1133
|
+
1st pct yaw +19.32° x1.000 of 80 / -19.32° x1.000 of 80 pitch +22.92° x1.000 of 102 / -26.94° x1.000 of 130
|
|
1134
|
+
first to fold: yaw + at 19.32°, triangle 174 [119,138,139], the sheet steps 28.50 level(s) across it, which is 0.112 of the range this mesh sampled
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
⇒ the ceiling is **±19.32°**, so the range is **19** — `floor(19.32)`, and
|
|
1138
|
+
*strictly inside* is the whole of the rule. At the **0.05 s per degree** that rig
|
|
1139
|
+
chooses, `turn` runs `2 × 19 × 0.05` = **1.9 s** and the map is
|
|
1140
|
+
`time = 0 + (degrees + 19) × 0.05`, which is the constraint as its rig spec
|
|
1141
|
+
declares it: `"from": -19, "to": 0, "scale": 0.05, "max": 19, "local": true,
|
|
1142
|
+
"additive": true`. ⭐ **The only two numbers there that anybody chose are `scale`
|
|
1143
|
+
and `to`** — and `to: 0` says nothing more than *the bottom of the range is the
|
|
1144
|
+
animation's first frame*. `from`, `max` and the duration are all the ceiling.
|
|
1145
|
+
|
|
1146
|
+
🔸 **And `scale` is chosen for the endpoint.** rigc rounds every number it emits
|
|
1147
|
+
to six decimals, so a `scale` that is not exact there moves the top of the dial:
|
|
1148
|
+
`1/60` ships as `0.016667`, and a 60° turn then applies at 1.00002 s rather than
|
|
1149
|
+
1 s — the last frame under `loop: false`, the *first* under `loop: true`
|
|
1150
|
+
(AUTHORING §3.5.2). `0.05` is exact at six decimals, which is the only reason the
|
|
1151
|
+
example can put its endpoint exactly on the duration. Pick a `scale` that is not,
|
|
1152
|
+
and land the endpoint inside the duration instead.
|
|
1153
|
+
|
|
1154
|
+
⚠️ **The ceiling is per mesh, and the face's is not the smallest one on the
|
|
1155
|
+
face.** The same run prints one for every depth mesh, and in this example each
|
|
1156
|
+
sidelock reads `yaw +17.04° / -45.80°`: it folds at **17.04°** on one side, which
|
|
1157
|
+
is *inside* the ±19° the face itself admits, and not until 45.80° on the other.
|
|
1158
|
+
The asymmetry is the sheet's, not a coincidence — each of those sheets is
|
|
1159
|
+
steepest at the edge where the strand curves away, and a yaw folds a pair of
|
|
1160
|
+
vertices only in the direction their depth is rising.
|
|
1161
|
+
|
|
1162
|
+
⇒ **A part whose ceiling is lower than the range has to be gone before the turn
|
|
1163
|
+
reaches it.** That is AUTHORING §3.4's third way to live with a ceiling: fade the
|
|
1164
|
+
slot to alpha 0 **inside the animation the slider applies**, landing the alpha-0
|
|
1165
|
+
key *before* the key that folds rather than on it. §7's paragraph on sliders is
|
|
1166
|
+
why that fade cannot be a second slider.
|
|
1167
|
+
|
|
1168
|
+
⭐ **A lookup table wants linear keys, and that is not a style note.** The slider
|
|
1169
|
+
makes the pose a function of the dial, so an easing curve between two keys makes
|
|
1170
|
+
it a **non-linear** function of a number the consumer may be holding perfectly
|
|
1171
|
+
still — the face would drift and settle while the value sits where it was put.
|
|
1172
|
+
Anticipation and follow-through fail for the same reason, not a weaker one
|
|
1173
|
+
(MOTION §3.6, §3.7): both are functions of time, and there is no time on this
|
|
1174
|
+
axis. [MOTION §0.1](MOTION.md) is that split written out, and it is also where the
|
|
1175
|
+
shaping *does* belong — in whatever animation moves the dial.
|
|
1176
|
+
|
|
1177
|
+
⚠️ **Two axes on one face need `"additive": true` on both.** A `pitch` dial
|
|
1178
|
+
beside the `yaw` is the ordinary case and it is the one the format's default
|
|
1179
|
+
breaks the moment the two share a target — in the worked example both `turn` and
|
|
1180
|
+
`tilt` key `headroll`. §7's paragraph on sliders is the mechanism and
|
|
1181
|
+
`A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` is the refusal.
|
|
1182
|
+
|
|
1183
|
+
⚠️ **What none of this measures: the Spine editor.** No editor export in this
|
|
1184
|
+
repository carries a slider, so whether the editor preserves two of them, their
|
|
1185
|
+
`additive` and `local` flags, and their order in the constraints array is
|
|
1186
|
+
**unknown**. `tools/editor_roundtrip.ts` on a machine with a licensed editor is
|
|
1187
|
+
what would answer it, and until somebody runs it the editor half of a parameter
|
|
1188
|
+
axis is untested. The runtime half is not: every figure above came back through
|
|
1189
|
+
`spine-core`.
|
|
1190
|
+
|
|
1060
1191
|
---
|
|
1061
1192
|
|
|
1062
1193
|
## 9. Looking at it, and the audit gap
|
|
@@ -1105,7 +1236,7 @@ problem — two eyes whose upper lash is heaviest at the **inner** corner read a
|
|
|
1105
1236
|
angry whatever the brow above them does. Worth knowing before spending a pass on
|
|
1106
1237
|
the wrong part.
|
|
1107
1238
|
|
|
1108
|
-
### 9.2 🚨
|
|
1239
|
+
### 9.2 🚨 The half nothing measures — three builds, one of them refused
|
|
1109
1240
|
|
|
1110
1241
|
**The setup geometry is measured; the deformed geometry was not, and one half of
|
|
1111
1242
|
it still is not.** Here is that
|
|
@@ -1123,11 +1254,15 @@ bun cli.ts render --candidate gallery/portrait/build --fps 25 --max 640 \
|
|
|
1123
1254
|
```
|
|
1124
1255
|
|
|
1125
1256
|
```
|
|
1126
|
-
MESH head
|
|
1127
|
-
attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
1128
|
-
… 26 PASS, 13 SKIP
|
|
1257
|
+
MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
|
|
1129
1258
|
```
|
|
1130
1259
|
|
|
1260
|
+
Green, and the tally is left to the tool: `bun run selftest` prints this build's
|
|
1261
|
+
live assertion and skip counts on its `GALLERY_EXAMPLE_IS_GREEN[portrait/spine-html]`
|
|
1262
|
+
line. One written here would be a figure nothing in the tree compares against a
|
|
1263
|
+
run, sitting inside a fence that reads as a transcript — and rigc prints no tally
|
|
1264
|
+
line, so it never was one.
|
|
1265
|
+
|
|
1131
1266
|
Now break the projection two ways. Both scripts write a variant motion spec
|
|
1132
1267
|
beside the originals and touch nothing in the repository:
|
|
1133
1268
|
|
|
@@ -1137,14 +1272,21 @@ beside the originals and touch nothing in the repository:
|
|
|
1137
1272
|
# the head reads as turning the other way at its own edge. This one has to
|
|
1138
1273
|
# REPLACE the transform with a table: an inverted band is not the closed
|
|
1139
1274
|
# form at any angle, and §1.1 refuses a `transform` beside a `vertices` run.
|
|
1275
|
+
# Each vertex takes the shift for THE COLUMN IT IS IN, looked up in the rig:
|
|
1276
|
+
# a `vertices` run is positional, so a script that assumes the list's order
|
|
1277
|
+
# rather than reading it breaks in silence the day the list is renumbered.
|
|
1140
1278
|
bun -e '
|
|
1279
|
+
const r = await Bun.file("gallery/portrait/rig.json").json();
|
|
1141
1280
|
const m = await Bun.file("gallery/portrait/motion.json").json();
|
|
1281
|
+
const v = r.skins.default.head.head.vertices;
|
|
1142
1282
|
const d = m.animations.turn.deform.find(x => x.slot === "head");
|
|
1143
|
-
const
|
|
1283
|
+
const shift = {"-162": -22.414, "-120": -7.175, "0": -35.345, "120": -27.658, "162": -14.255};
|
|
1284
|
+
const run = [];
|
|
1285
|
+
for (let i = 0; i < v.length / 2; i++) run.push(shift[v[i * 2]], 0);
|
|
1144
1286
|
for (const k of d.keys) if (k.transform) {
|
|
1145
1287
|
delete k.transform;
|
|
1146
1288
|
k.fromVertex = 0;
|
|
1147
|
-
k.vertices =
|
|
1289
|
+
k.vertices = run;
|
|
1148
1290
|
}
|
|
1149
1291
|
await Bun.write("/tmp/swapped.motion.json", JSON.stringify(m, null, 2));
|
|
1150
1292
|
'
|
|
@@ -1172,15 +1314,33 @@ is a doc command silently passing rather than silently failing, which is the
|
|
|
1172
1314
|
worse of the two: build (b) reported `A39 PASS` and the table below said `FAIL`.
|
|
1173
1315
|
Both are re-run above.
|
|
1174
1316
|
|
|
1317
|
+
⚠️ **And script (a) broke a second time, the same way, on 2026-09-04.** It wrote
|
|
1318
|
+
one row of five shifts and repeated it five times, which was the mesh's own
|
|
1319
|
+
column order while the vertex list was row-major.
|
|
1320
|
+
[#375](https://github.com/firejune/rigc/issues/375) renumbered both gallery
|
|
1321
|
+
grids along their outline walk — perimeter first, then the interior, the order
|
|
1322
|
+
Spine's `hull` needs — and the row went on landing at the same *positions* in a
|
|
1323
|
+
list that no longer meant columns. The build stayed green and stayed wrong, so
|
|
1324
|
+
nothing on the page moved; what it stopped being was **an inverted band**, which
|
|
1325
|
+
is the one thing the table below reads it as. The figures in that table were
|
|
1326
|
+
right the whole time and the command under them had stopped producing
|
|
1327
|
+
them — a stale figure shows up at one site, and a broken command shows up at
|
|
1328
|
+
every figure it feeds, which is how the two are told apart: this one also
|
|
1329
|
+
contradicted §9.3's `check` row, `0.33 / 0.61` against a table saying
|
|
1330
|
+
`0.20 / 0.38`. ⭐ **The repair is the doctrine's own**: the script resolves each
|
|
1331
|
+
shift through the vertex's coordinate instead of its index, so the next
|
|
1332
|
+
renumbering cannot move it. A `vertices` run is positional by format — that is
|
|
1333
|
+
`fromVertex`'s whole job — and a *generator* of one has no reason to be.
|
|
1334
|
+
|
|
1175
1335
|
**What comes back from both:**
|
|
1176
1336
|
|
|
1177
1337
|
| | good | (a) one band inverted | (b) mesh folded |
|
|
1178
1338
|
| --- | --- | --- | --- |
|
|
1179
|
-
| `--profile spine-html`, **before `A39`** |
|
|
1339
|
+
| `--profile spine-html`, **before `A39`** | green | **green, and the same counts** | **green, and the same counts** |
|
|
1180
1340
|
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | PASS | **PASS** | **PASS** |
|
|
1181
1341
|
| the `MESH` coverage line | 100.00%, 95.90px past | **byte-identical** | **byte-identical** |
|
|
1182
1342
|
| 🆕 `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | PASS | PASS | **FAIL, both keys, 8 of 32 triangles** |
|
|
1183
|
-
| `--profile spine-html`, **today** |
|
|
1343
|
+
| `--profile spine-html`, **today** | green | green | **refused, and nothing written** |
|
|
1184
1344
|
| 🆕 the `DEFORM` block, `head` key 1 `area` | x0.637174 … x1.319122 | **x0.765250 … x1.362834** | **x−0.288121 … x1.820211** |
|
|
1185
1345
|
| 🆕 … and its `winding` | 32 of 32 kept | 32 of 32 kept | **24 of 32 kept** |
|
|
1186
1346
|
|
|
@@ -1193,22 +1353,46 @@ it mean anything.
|
|
|
1193
1353
|
|
|
1194
1354
|
⭐ **The two `DEFORM` rows are the ones that separate (a) from the good build**,
|
|
1195
1355
|
and nothing else in the toolchain does that without a reference render. `A39` is
|
|
1196
|
-
right to pass (a) — no triangle reverses —
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
compress to 0.637"*, as a figure the tool produces.
|
|
1356
|
+
right to pass (a) — no triangle reverses — so the whole of the difference is a
|
|
1357
|
+
pair of ratios, and the way to read them is to ask the tool rather than to copy
|
|
1358
|
+
them down:
|
|
1200
1359
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1360
|
+
```bash
|
|
1361
|
+
bun cli.ts explain --rig gallery/portrait/rig.json \
|
|
1362
|
+
--motion /tmp/swapped.motion.json --out /tmp/explain-swapped
|
|
1363
|
+
```
|
|
1203
1364
|
|
|
1204
1365
|
```
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1366
|
+
DEFORM turn default/head/head key 1 t=0.620000 authored table
|
|
1367
|
+
frame played on a track
|
|
1368
|
+
moved 25 of 25 vertices, worst 35.3450px at v10
|
|
1369
|
+
area min x0.765250 tri 19 max x1.362834 tri 1 (32 triangles, 0 with no area at the cleared pose, band 0.146694px²)
|
|
1370
|
+
stretch max x1.362834 tri 1 min x0.765250 tri 26
|
|
1371
|
+
winding 32 of 32 kept, 0 collapsed
|
|
1210
1372
|
```
|
|
1211
1373
|
|
|
1374
|
+
🚨 **Read it band by band, because the extremes have swapped ends and comparing
|
|
1375
|
+
worst to worst hides that.** The block names the triangle beside every ratio, and
|
|
1376
|
+
§4.1's table says what each band should be. `tri 1` spans **−162 → −120**, the
|
|
1377
|
+
band §4.1 puts at **0.637** and the good build's own block reports as
|
|
1378
|
+
`x0.637174 tri 17` — the same band, and here it comes back at **x1.362834**. That
|
|
1379
|
+
is this section's prose, *"stretches 1.363 where it should compress to 0.637"*, as
|
|
1380
|
+
a figure the tool produces, and it is the far edge turning the wrong way. `tri 19`
|
|
1381
|
+
spans **−120 → 0**, tabled at 0.892 and measured at **x0.765250**: the swapped
|
|
1382
|
+
column is the boundary between those two bands, so the compression the far band
|
|
1383
|
+
gave up lands next door. ⚠️ Read as min-against-min and max-against-max instead,
|
|
1384
|
+
the very same four figures say 1.319 → 1.363 and 0.637 → 0.765 — two comparisons
|
|
1385
|
+
across *different* bands, both of them mild, neither of them what happened. Only
|
|
1386
|
+
the two bands the swapped columns bound move at all: the 0 → 120 and 120 → 162
|
|
1387
|
+
bands still read **1.064** and **1.319**, exactly as §4.1 tables them. `authored table` on the key line is the other half of the diagnosis:
|
|
1388
|
+
the model is gone, so nothing is left to check the ratios against but the ratios.
|
|
1389
|
+
|
|
1390
|
+
`rigc explain` is the instrument that prints every other timeline's actual values,
|
|
1391
|
+
and on a deform it used to print the shape of the run rather than the run: the
|
|
1392
|
+
head's four keys came back as four lines, each giving a time, a curve kind, and
|
|
1393
|
+
either `back to the setup pose` or `deform[0..50] 25 pair(s)` — the extent of the
|
|
1394
|
+
run and how many pairs are in it, and not one of the numbers.
|
|
1395
|
+
|
|
1212
1396
|
⇒ **`25 pair(s)` was the whole of what `explain` would tell you about a face
|
|
1213
1397
|
turn**, against a scalar track two lines up in the same report printing
|
|
1214
1398
|
`value=-35.345`. That asymmetry is
|
|
@@ -1224,6 +1408,7 @@ bun cli.ts explain --rig gallery/portrait/rig.json \
|
|
|
1224
1408
|
|
|
1225
1409
|
```
|
|
1226
1410
|
DEFORM turn default/head/head key 1 t=0.620000 transform yaw radius=170 degrees=12
|
|
1411
|
+
frame played on a track
|
|
1227
1412
|
moved 25 of 25 vertices, worst 35.3450px at v2
|
|
1228
1413
|
area min x0.637174 tri 17 max x1.319122 tri 31 (32 triangles, 0 with no area at the cleared pose, band 0.146694px²)
|
|
1229
1414
|
stretch max x1.319121 tri 22 min x0.637175 tri 8
|
|
@@ -1299,13 +1484,13 @@ area is a *quadratic in the interpolation fraction* — the fold is a root of it
|
|
|
1299
1484
|
solved for rather than searched, with no sample spacing anybody would have to
|
|
1300
1485
|
defend. What that arithmetic names is then posed and measured by the same code
|
|
1301
1486
|
that measures a key, **alpha read at that same instant**, so the fade a correct
|
|
1302
|
-
rig relies on is not refused and the frames it does not cover are
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1487
|
+
rig relies on is not refused and the frames it does not cover are. The sentence names
|
|
1488
|
+
the two keys the fold lies between rather than one key index, the time it solved for
|
|
1489
|
+
and how far along the segment that is, the reversed triangles with their signed areas,
|
|
1490
|
+
`NO KEY LANDS THERE` in those words, and the alpha read at that same instant — so what
|
|
1491
|
+
it refuses is legible as a frame rather than as a key. AUTHORING §4.11.3 reads it field
|
|
1492
|
+
by field and [`src/validate.ts`](../src/validate.ts) builds it; no spec this repository
|
|
1493
|
+
ships produces one, the turn probe being `selftest.ts`'s own.
|
|
1309
1494
|
|
|
1310
1495
|
⇒ The rule this section gave — *fade out over the run up to the angle you cannot
|
|
1311
1496
|
take, so that every key past the ceiling is one that draws nothing* — is
|
|
@@ -1371,9 +1556,9 @@ to call an audit:**
|
|
|
1371
1556
|
|
|
1372
1557
|
📌 **`explain`'s `DEFORM` block (§9.2, AUTHORING §4.11.2) takes half of limits 1
|
|
1373
1558
|
and 2 away, and none of limit 3.** It is reference-free, so it says something
|
|
1374
|
-
about a first authoring; and it is *per key*, so the band
|
|
1375
|
-
as `x1.362834`
|
|
1376
|
-
aggregate. What it still cannot say is whether **12° was the angle the shot
|
|
1559
|
+
about a first authoring; and it is *per key and per triangle*, so the band
|
|
1560
|
+
inversion above reads as `x1.362834` on `tri 1` where the same band is `x0.637174`
|
|
1561
|
+
in the model, rather than as 0.20 of 255 in an aggregate. What it still cannot say is whether **12° was the angle the shot
|
|
1377
1562
|
wanted** — that needs the picture, which is why the procedure below survives the
|
|
1378
1563
|
block as it survived `A39`.
|
|
1379
1564
|
|
|
@@ -1458,8 +1643,8 @@ with no `build/`, `render/` or `preview.html` in that directory:
|
|
|
1458
1643
|
|
|
1459
1644
|
| Command | What came back |
|
|
1460
1645
|
| --- | --- |
|
|
1461
|
-
| `build --profile spine` | green
|
|
1462
|
-
| `build --profile spine-html` | green —
|
|
1646
|
+
| `build --profile spine` | green, and the report names what the profile leaves out rather than this page counting it: `profile spine — 7 renderer-policy and 8 archetype assertion(s) do not apply` |
|
|
1647
|
+
| `build --profile spine-html` | green — `profile spine-html — every assertion applies`, `A13_MESH_BUDGET` and `A15_IDLE_NO_MESH_BONE_KEYS` among the ones the row above excludes |
|
|
1463
1648
|
| both `MESH` lines | `head` **100.00%** covered, reaching 95.90px past the art; `hair_bang` 100.00%, 55.22px |
|
|
1464
1649
|
| `render --fps 25 --max 640` | **81 + 39 + 56 frames**, 478×640, three contact sheets |
|
|
1465
1650
|
| `loop_seam.ts` ×3 | **0 / 255**, **0 of 305 920 pixels** differing, for all three |
|
|
@@ -1523,6 +1708,22 @@ cost off the keyboard and onto the **parts**: per-eye meshes, a meshed neck, a
|
|
|
1523
1708
|
second art layer for the far cheek (§8). Whether to pay *that* is a project's
|
|
1524
1709
|
decision and this page does not make it.
|
|
1525
1710
|
|
|
1711
|
+
🚫 **No Live2D file is read or written, and none ever will be — a boundary
|
|
1712
|
+
rather than an unbuilt feature, and it runs in both directions.** rigc's inputs
|
|
1713
|
+
are a rig spec and a motion spec; its outputs are Spine 4.3 skeleton data and an
|
|
1714
|
+
atlas. There is no importer, no exporter and no converter for `.moc3`, `.cmo3`,
|
|
1715
|
+
`.model3.json` or anything else in that family, and nothing in this repository
|
|
1716
|
+
claims compatibility with that format in either direction
|
|
1717
|
+
([#399](https://github.com/firejune/rigc/issues/399) is where that was settled).
|
|
1718
|
+
⭐ **What is in scope is an authoring idea, stated on its own terms rather than
|
|
1719
|
+
as anybody's feature: that a face angle can be a value rather than a time.**
|
|
1720
|
+
§8's *The turn as a value rather than a time* is that idea on Spine's own
|
|
1721
|
+
`slider` constraint, and every mechanism under it is Spine's — the arithmetic,
|
|
1722
|
+
the flags, the readers and the failure modes are all in AUTHORING §3.5.2 and all
|
|
1723
|
+
measured against `spine-core`. ⚠️ Nothing on this page is a statement about how
|
|
1724
|
+
any other tool works inside, and nothing above implies one: what this repository
|
|
1725
|
+
has measured is its own format.
|
|
1726
|
+
|
|
1526
1727
|
🚫 **No per-eye mesh recipe.** §8 says the eyes need their own deform meshes past
|
|
1527
1728
|
about 26°, and nobody has built that here. The column-placement arithmetic in
|
|
1528
1729
|
§4.2 applies to any grid over any curved patch, so the tangent limit is the part
|
package/docs/INGEST.md
CHANGED
|
@@ -607,14 +607,11 @@ rigc validate examples/spineboy/export/spineboy-pro.json \
|
|
|
607
607
|
rigc: green
|
|
608
608
|
```
|
|
609
609
|
|
|
610
|
-
**Why it is worth a section anyway.** That line used to be two `FAIL`s
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
and nothing was wrong with the data. `hoverboard-board` is an unweighted mesh with 148
|
|
610
|
+
**Why it is worth a section anyway.** That line used to be two `FAIL`s on the same key —
|
|
611
|
+
one for an **odd `offset`**, on the reading that the run's x values would land on y slots
|
|
612
|
+
and back again, and one for an **odd-length run**, on the reading that the deform array is
|
|
613
|
+
x, y pairs — and nothing was wrong with the data, so neither sentence exists in the tool
|
|
614
|
+
any more. `hoverboard-board` is an unweighted mesh with 148
|
|
618
615
|
floats; the key carries `offset: 1` and 147 values, covering `1..148` — the whole array
|
|
619
616
|
minus a leading zero the editor trimmed. A trim can land on a y component, so an odd
|
|
620
617
|
offset is what a trimmed run looks like, and Spine's own parser copies the run in at the
|
|
@@ -846,13 +843,12 @@ rig spec; the discipline is in what you check afterwards.
|
|
|
846
843
|
`parent`, a slot's `bone`, a constraint's `bones` and `target`, a draw-order key's
|
|
847
844
|
`slot`, an authored mesh's vertex `weights` — and, across the two files, the motion
|
|
848
845
|
spec's `archetype` against the rig spec's `name`. That last one is the first refusal a
|
|
849
|
-
rename produces, before anything else has a chance to go wrong:
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
```
|
|
846
|
+
rename produces, before anything else has a chance to go wrong: a `rigc compile error`
|
|
847
|
+
naming the motion spec's path, the `archetype` that spec states, the path of the rig spec
|
|
848
|
+
it was handed, and the `name` that rig actually carries — both sides of the mismatch in
|
|
849
|
+
one sentence. [`src/compile.ts`](../src/compile.ts) builds it; the renamed copies this
|
|
850
|
+
section works on are not committed, for the reason the Appendix gives, so the figure is
|
|
851
|
+
described here rather than transcribed off one.
|
|
856
852
|
|
|
857
853
|
⭐ **A rename is therefore mostly safe by construction, and its failures arrive as
|
|
858
854
|
sentences naming both sides.** That is the reason to do it in the specs rather than in
|
package/docs/MOTION.md
CHANGED
|
@@ -80,6 +80,52 @@ and the last hop is the one that matters: a `both-unacceptable` tie means **prop
|
|
|
80
80
|
again from a different axis** (§4), not *nudge the same candidate*. §5 has the
|
|
81
81
|
detail.
|
|
82
82
|
|
|
83
|
+
### 0.1 When the axis is not time — an animation a `slider` applies
|
|
84
|
+
|
|
85
|
+
⛔ **One shape of request does not normalise to the table above, and carrying it
|
|
86
|
+
through §3 anyway produces a defect nothing on this page can measure.** A `slider`
|
|
87
|
+
constraint (AUTHORING §3.5.2) applies an animation as a function of a **value**: it
|
|
88
|
+
reads a driving bone's transform property, maps it with
|
|
89
|
+
`time = to + (value − from) × scale`, and applies the animation at that time on
|
|
90
|
+
every frame. What it applies is an ordinary animation in the motion spec — same
|
|
91
|
+
tracks, same keys, the same `duration` — but **nothing plays it**. It is a lookup
|
|
92
|
+
table, and `t` in it is a coordinate on the axis rather than a moment.
|
|
93
|
+
|
|
94
|
+
⇒ **So the constructs in §3 that are functions of time are not available to it,
|
|
95
|
+
and each fails in its own way rather than merely reading oddly:**
|
|
96
|
+
|
|
97
|
+
| §3 construct | On an animation a slider applies |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| §3.4 slow in and slow out | an easing curve makes the pose a **non-linear** function of the dial. The consumer moves the value at one rate and the face moves at another, and at a value held still the pose is still whatever the curve says there |
|
|
100
|
+
| §3.6 anticipation | places a counter-pose at a dial *position*, so the pose runs backwards while the value runs forwards. Nothing anticipates a number |
|
|
101
|
+
| §3.8 overshoot and settle | there is no settle: at a held value the pose is what the table says at that value, indefinitely. An overshoot keyed past the extreme is just a wrong pose at the top of the dial |
|
|
102
|
+
| §3.7 follow-through and the offset table | wants parts to arrive at different **times**. Here they differ by **amount** at every value, which is §3.7.1's construct and not this one |
|
|
103
|
+
| §3.3 timing | the key times are the axis's own coordinates — one per angle, position or level the table states — so spacing them is choosing where to sample, not choosing a rhythm |
|
|
104
|
+
|
|
105
|
+
⭐ **What is still this page's job is whatever moves the dial**, and that is an
|
|
106
|
+
ordinary animation with §3 applying to it unchanged. The split is visible in the
|
|
107
|
+
worked example: in
|
|
108
|
+
[`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) the two
|
|
109
|
+
lookup tables `turn` and `tilt` carry **no easing at all**, and `sweep` — the one
|
|
110
|
+
animation there meant to be played — carries every `ease` in the file.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bun -e 'const m = JSON.parse(await Bun.file("gallery/look/motion.json").text());
|
|
114
|
+
for (const [name, a] of Object.entries(m.animations))
|
|
115
|
+
console.log(name, (JSON.stringify(a).match(/"ease"/g) ?? []).length);'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
⚠️ **And two of §4's candidate axes stop being axes.** *Anticipation* and
|
|
119
|
+
*Termination* are both readings of how a movement is placed in time, so a ballot
|
|
120
|
+
spread on either of them over a slider-applied animation is asking a person to
|
|
121
|
+
choose between two wrong answers. The axes that survive are the ones about
|
|
122
|
+
**amount** — *Part amount*, *Path*, *Key density* — because those are still
|
|
123
|
+
readings of the value.
|
|
124
|
+
|
|
125
|
+
📘 The face case is worked end to end in [FACE.md](FACE.md): its §8 derives the
|
|
126
|
+
slider's range from the turn ceiling `build` reports, which is the one number on
|
|
127
|
+
that axis an author cannot guess.
|
|
128
|
+
|
|
83
129
|
---
|
|
84
130
|
|
|
85
131
|
## 1. Prompt grammar — what a request is made of
|
|
@@ -259,6 +305,12 @@ would not exist even if the user had more pictures of the same two poses. Everyt
|
|
|
259
305
|
below is therefore authored knowledge, and it is sourced the way AUTHORING §10 sourced
|
|
260
306
|
the editor's conventions.
|
|
261
307
|
|
|
308
|
+
⚠️ **All of it assumes the axis is time.** If what you are authoring is an
|
|
309
|
+
animation a `slider` applies — a face angle, a dial, a suspension that compresses
|
|
310
|
+
as the wheel rises — §0.1 is the exception, and it is not a small one: the easing,
|
|
311
|
+
the anticipation and the overshoot below each produce a specific defect there
|
|
312
|
+
rather than merely reading oddly.
|
|
313
|
+
|
|
262
314
|
### 3.1 Where these come from, and how each line is marked
|
|
263
315
|
|
|
264
316
|
Two public bodies of material, and nothing else: **the twelve basic principles of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/diff.ts
CHANGED
|
@@ -685,10 +685,20 @@ function diffAttachments(c: Json, r: Json): DiffSection {
|
|
|
685
685
|
// The issue's diagnosis was that Spine's exporter omits `width`/`height`
|
|
686
686
|
// when they match the atlas region, so a rigc rig — which always states
|
|
687
687
|
// them (AUTHORING R1/R5) — could never agree. That is not what the corpus
|
|
688
|
-
// says: all twelve reference exports state a size on every one of their
|
|
689
|
-
// regions, so there is nothing for an
|
|
690
|
-
// `--atlas` plumbing the issue proposed
|
|
691
|
-
// rung on the ladder.
|
|
688
|
+
// says: all twelve reference exports state a size on every one of their
|
|
689
|
+
// regions, and no region omits either field — so there is nothing for an
|
|
690
|
+
// atlas lookup to resolve and the `--atlas` plumbing the issue proposed
|
|
691
|
+
// would be dead code against every rung on the ladder.
|
|
692
|
+
//
|
|
693
|
+
// ⛔ No count on that claim, deliberately, and this was the fourth copy of
|
|
694
|
+
// the one issue #490 struck off `docs/LADDER.md`: nothing derives the
|
|
695
|
+
// figure — not the plainest reading, every `region` attachment under
|
|
696
|
+
// `skins` in the twelve `export/*.json` skeletons, and not counting by
|
|
697
|
+
// skin, by slot, by attachment name, by atlas path or by atlas region. The
|
|
698
|
+
// claim carries the argument without one, because it is universal and a
|
|
699
|
+
// single counterexample refutes it: after `bun run fetch-examples`, read
|
|
700
|
+
// the region attachments of those twelve files and check that each states
|
|
701
|
+
// both `width` and `height`.
|
|
692
702
|
//
|
|
693
703
|
// What the measure actually reported was the naming gap, a third time. It
|
|
694
704
|
// was keyed by `skin/slot/attachment`, so it could never exceed the name
|