spine-rigc 0.20.1 → 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 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 — six complete rigs over art that ships with them
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
@@ -1668,6 +1668,13 @@ rather than 1 s. With `loop: false` that is the last frame and harmless, with
1668
1668
  one — pick `to`/`scale` so the endpoint lands **inside** the duration rather than
1669
1669
  exactly on it.
1670
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
+
1671
1678
  ### 3.6 `events` — names the animation can fire
1672
1679
 
1673
1680
  **When you need one:** something outside the skeleton has to happen on a
package/docs/FACE.md CHANGED
@@ -40,6 +40,12 @@ 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
+ - **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
43
49
 
44
50
  🚨 **A `deform` key's winding is gated; how far it moved the geometry is not, and
45
51
  that half is the one you have to author around.** Since 2026-09-03
@@ -72,6 +78,7 @@ internal shape:
72
78
  | a face that **blinks**, **breathes**, **looks around** | ordinary MOTION.md tracks. A lid, a chest, an iris. Nothing on this page is needed |
73
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 |
74
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 |
75
82
 
76
83
  ⭐ **The turn is the only part of a face that is not already MOTION.md's job**,
77
84
  and it is 90% of this page. A blink is a translating plate (§6); a gaze is
@@ -926,6 +933,22 @@ stack (`headroll_idle` under `headroll_layer`) — but both are runtime or rig
926
933
  decisions the motion spec cannot express, so nothing warns an author that two of
927
934
  their animations will fight.**
928
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
+
929
952
  ⛔ **And the cost is real, so name it rather than discovering it by shipping.** In
930
953
  the worked example `idle` keys **nothing** on the iris, on purpose, even though a
931
954
  completely still eye reads as a mannequin. The iris is `gaze`'s channel; an
@@ -1060,6 +1083,111 @@ authoring concept**: the on-axis pair's shared `cos t` now falls out of the same
1060
1083
  closed form as everybody else's value, so `axis` is gone from the spec while
1061
1084
  `look_l`/`look_r` stay — because those two really are one shared number (§5).
1062
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
+
1063
1191
  ---
1064
1192
 
1065
1193
  ## 9. Looking at it, and the audit gap
@@ -1580,6 +1708,22 @@ cost off the keyboard and onto the **parts**: per-eye meshes, a meshed neck, a
1580
1708
  second art layer for the far cheek (§8). Whether to pay *that* is a project's
1581
1709
  decision and this page does not make it.
1582
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
+
1583
1727
  🚫 **No per-eye mesh recipe.** §8 says the eyes need their own deform meshes past
1584
1728
  about 26°, and nobody has built that here. The column-placement arithmetic in
1585
1729
  §4.2 applies to any grid over any curved patch, so the tangent limit is the part
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.1",
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": {