spine-rigc 1.0.0 → 1.0.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/FACE.md CHANGED
@@ -36,7 +36,7 @@ hold its results.
36
36
  [`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait),
37
37
  and its measurement half,
38
38
  [`FINDINGS.md`](https://github.com/firejune/rigc/tree/main/gallery/portrait/FINDINGS.md)
39
- - **The same closed forms on the other axis**, since 2026-09-03:
39
+ - **The same closed forms on the other axis**:
40
40
  [`gallery/nod`](https://github.com/firejune/rigc/tree/main/gallery/nod) is a
41
41
  worked `pitch`, and this page stays written for a **yaw**. It re-derives §4.2's
42
42
  fold angle on uneven *rows* and brackets it against `A39` at 33°/34°, and it
@@ -51,7 +51,7 @@ hold its results.
51
51
  animation — a pointer, a gaze target, a game value
52
52
 
53
53
  🚨 **A `deform` key's winding is gated; how far it moved the geometry is not, and
54
- that half is the one you have to author around.** Since 2026-09-03
54
+ that half is the one you have to author around.**
55
55
  `A39_DEFORM_KEEPS_TRIANGLE_WINDING` refuses a key that turns the mesh inside out —
56
56
  by name, by key and by triangle — and the build writes nothing. What no assertion
57
57
  has an opinion about is **magnitude**: a band that stretches where the projection
@@ -135,8 +135,7 @@ agrees with the line, or it does not.
135
135
  ### 1.1 ⭐ And now the spec holds the line, not its results
136
136
 
137
137
  **A `deform` key states this expression** (AUTHORING §4.11.1). The worked
138
- example's held 12° yaw used to be **160 hand-transcribed floats across 8 keys**,
139
- and it is now four lines of which two are the angle:
138
+ example's held 12° yaw is four lines of which two are the angle:
140
139
 
141
140
  ```json
142
141
  { "t": 0.62, "transform": { "kind": "yaw", "radius": 170, "degrees": 12 }, "ease": "swell" },
@@ -146,79 +145,53 @@ and it is now four lines of which two are the angle:
146
145
  ⇒ **`radius` is `R` off the depth table (§2) and `degrees` is the angle.** The
147
146
  compiler evaluates `dx = x·(cos t − 1) − z·sin t` at every vertex with
148
147
  `z = √(R² − x²)`, writes the millimetre-level results into the artifact, and
149
- `explain` prints both the model and the offsets it produced. What was
150
- transcription is a measurement of the shape the drawing implies, and nothing
151
- else.
152
-
153
- Three things that changes on this page, and they are the reasons the construct
154
- exists rather than side effects:
155
-
156
- - **A second angle is a second number.** §8's cliff sweep — 8, 12, 16, 20, 24,
157
- 28 and 32 degrees — was seven tables of 160 floats, produced by a throwaway
158
- script that never made it into the repository: *the measurement existed and the
159
- reproduction did not*. It is now `"degrees": 20` and a rebuild.
160
- - **The anticipation and the head-follow §3.6 and §7 priced out** were priced out
161
- by the transcription, not by the format. 80 numbers for a tenth of a second is
162
- now one key with a smaller `degrees`.
163
- - **The audit changed shape.** A transcription can only be checked against the
148
+ `explain` prints both the model and the offsets it produced. What the key
149
+ states is a measurement of the shape the drawing implies, and nothing else.
150
+
151
+ Three things follow, and they are the reasons the construct exists rather than
152
+ side effects:
153
+
154
+ - **A second angle is a second number.** Each angle of §8's cliff sweep — 8, 12,
155
+ 16, 20, 24, 28 and 32 degrees — is `"degrees": <n>` and a rebuild.
156
+ - **A small yaw is one key.** The anticipation MOTION §3.6 asks for and the
157
+ head-follow §7 prices are each one key with a smaller `degrees`.
158
+ - **The audit is two parameters.** A transcription can only be checked against the
164
159
  line by hand, which is §9.3's gap; a stated model is checked by reading two
165
160
  parameters. What is still unmeasured is the *consequence* — whether that angle
166
161
  folds the mesh — and §4.2 and A39 are that half.
167
162
 
168
163
  ⚠️ **What it does not do.** It evaluates; it never chooses. The radius, the
169
164
  angle, the depth table and whether 12° reads are all yours, and a wrong `radius`
170
- now produces 160 consistent wrong numbers as fast as it used to produce one — see
171
- §4's warning, which the construct makes cheaper to get wrong rather than harder.
165
+ produces 160 consistent wrong numbers at once — see §4's warning, which the
166
+ construct makes cheaper to get wrong rather than harder.
172
167
 
173
168
  📌 **Author `t` in degrees, in the spec.** Radians appear nowhere: the key holds
174
- the angle and the compiler holds the conversion. That is the sentence this
175
- section replaced — the angle used to live only in a comment beside the part list,
176
- because the spec could not hold it, and §9 was what happened when a later reader
177
- could not find it.
169
+ the angle and the compiler holds the conversion.
178
170
 
179
171
  ---
180
172
 
181
173
  ## 2. Depth is the parameter you are actually authoring
182
174
 
183
175
  🚨 **A part list for a face is not a list of drawings. It is a list of
184
- `(x, z)`.** And here is the sharp edge this section was written about:
185
-
186
- > **`x` was in the file and `z` was not.** Neither spec had a depth field at all,
187
- > and on the worked example as it first shipped
188
- >
189
- > ```bash
190
- > grep -c '"z"' gallery/portrait/rig.json gallery/portrait/motion.json # 0 and 0
191
- > ```
192
- >
193
- > The bones carried `x` (`eye_l` at `−62`, `nose` at `0`), the rig carried the mesh
194
- > columns' `x`, and every `z` that produced every number lived **only in the
195
- > README beside them**.
196
-
197
- ⚠️ **Read that as the reason the constructs below exist, not as the state of the
198
- tool.** The `z` above never had a home because a track carries a value and not a
199
- model — so a depth was consumed, arithmetic was done by hand, and the input
200
- vanished. Both halves have since been given one (next paragraph), and `"z"` is
201
- still `0` in both files because the field is spelled `radius` on a mesh key and
202
- `depth` on a bone track.
203
-
204
- ⇒ **The instruction survives anyway: write the depth table down somewhere a
205
- reader will find it.** The specs now hold the depths the turn *uses*; a project's
206
- own reasoning about them — why the fringe stands off 26 and not 15 — still belongs
207
- beside the rig, and in the worked example that is a table in its README.
208
-
209
- ⭐ **Every depth is now in the file, and both halves of that arrived as their own
210
- construct.** Since [#294](https://github.com/firejune/rigc/issues/294) a `yaw`
176
+ `(x, z)`.** The bones carry `x` (`eye_l` at `−62`, `nose` at `0`) and the rig
177
+ carries the mesh columns' `x`; the `z` is in no drawing.
178
+
179
+ ⭐ **Every depth goes in the file, and each half has its own construct.** A `yaw`
211
180
  deform key states its `radius` (§1.1), which is the `R` of the cylinder that plate
212
- is painted on — so the head's 170 and the fringe's 196 are in `motion.json`. Since
213
- [#295](https://github.com/firejune/rigc/issues/295) a **bone** track's key can
214
- state a `derive` model over a group, whose `depth` is one number **per member** —
215
- so the feature depths, the hair depths and the sign flip below are in the file too
216
- (§3, AUTHORING §4.5.1).
181
+ is painted on — so the head's 170 and the fringe's 196 are in `motion.json`. A
182
+ **bone** track's key can state a `derive` model over a group, whose `depth` is one
183
+ number **per member** — so the feature depths, the hair depths and the sign flip
184
+ below are in the file too (§3, AUTHORING §4.5.1). Neither is spelled `"z"`: the
185
+ field is `radius` on a mesh key and `depth` on a bone track.
217
186
 
218
- ⚠️ **What has not changed is that a depth is a decision.** The table below is
219
- still the thing to argue about before authoring anything; all that moved is where
220
- it is written, and the point of moving it is that a reader of the numbers can now
221
- see the table that produced them:
187
+ ⇒ **Write the depth table down somewhere a reader will find it.** The specs hold
188
+ the depths the turn *uses*; a project's own reasoning about them — why the fringe
189
+ stands off 26 and not 15 — belongs beside the rig, and in the worked example that
190
+ is a table in its README.
191
+
192
+ ⚠️ **A depth is a decision.** The table below is the thing to argue about before
193
+ authoring anything, and the point of writing it into the spec is that a reader of
194
+ the numbers can see the table that produced them:
222
195
 
223
196
  ```bash
224
197
  grep -c '"depth"' gallery/portrait/motion.json # 8, one per derive key
@@ -272,12 +245,11 @@ not better — the columns are sampling a cylinder that was never the shape of a
272
245
  face. A cylinder is one number pretending to be a surface, and past a certain
273
246
  density the pretence is what fails.
274
247
 
275
- ⭐ **Two things had to arrive together, and both are now generated rather than
276
- written.** The lattice itself was hand-numbered — this example's 25 vertex pairs,
277
- 32 triangles and hull walk were a person's arithmetic — and
248
+ ⭐ **Two things arrive together, and both are generated rather than written.**
278
249
  [AUTHORING §3.4](AUTHORING.md#grid--a-lattice-over-the-part-window)'s `grid`
279
- generator now builds it from the column positions alone, reproducing this one
280
- exactly. That is the prerequisite: a turn needs interior vertices to move, and
250
+ generator builds the lattice from the column positions alone, and reproduces this
251
+ example's hand-numbered 25 vertex pairs, 32 triangles and hull walk exactly. That
252
+ is the prerequisite: a turn needs interior vertices to move, and
281
253
  §4.3's contour has none.
282
254
 
283
255
  ⭐ **And a depth map is the number above becoming a surface.** A greyscale sheet in the
@@ -295,7 +267,7 @@ PIXEL by its own depth; a mesh displaces vertices and interpolates across
295
267
  triangles. On a dome (a ramp would prove nothing — linear interpolation is exact
296
268
  on a linear field) at 18°, the two evaluations converge as the lattice refines:
297
269
 
298
- **No run reproduces this:** the 2026-09-05 density study's figures, printed by `bench/studies/2026-09-05-density/tools/densprobe.ts` and kept in its `evidence/`; no command this page states re-takes them, and `CS01`–`CS03` in `selftest.ts` hold the claim rather than the cells
270
+ **No run reproduces this:** the 2026-09-05 density study's figures, printed by `bench/studies/2026-09-05-density/tools/densprobe.ts` and kept in its `evidence/`; no command this page states re-takes them
299
271
 
300
272
  | lattice | 3×3 | 5×5 | 9×9 | 17×17 | 33×33 |
301
273
  | --- | --- | --- | --- | --- | --- |
@@ -306,7 +278,7 @@ on a linear field) at 18°, the two evaluations converge as the lattice refines:
306
278
  does not converge — it settles at ~3.4px however dense it gets — and at 3×3 it
307
279
  reads *better* than the right one. So one measurement at one density cannot tell
308
280
  the two models apart, and would have picked the wrong one. The claim is the
309
- convergence, never a single number. (`CS01`–`CS03` in `selftest.ts`; the worst
281
+ convergence, never a single number. (The worst
310
282
  case falls more slowly than the mean and is expected to, because the dome's rim
311
283
  has an unbounded depth gradient.)
312
284
 
@@ -323,7 +295,7 @@ is free, get it wrong and nothing complains. What the map removes is the
323
295
 
324
296
  ### 2.2 The angle belongs to the map, not to the mesh
325
297
 
326
- §2.1 leaves an open question and it has now been measured. §4.2 finds that
298
+ §2.1 leaves an open question, and it is measured here. §4.2 finds that
327
299
  refining the lattice makes the fold **worse**, and read that as the cylinder's
328
300
  error surfacing — so per-vertex depth ought to have bought the angle back. ⛔ **It
329
301
  does not, and it never could have.**
@@ -373,7 +345,7 @@ are two different quantities and both are true — §2.1 measures the first, `A3
373
345
  refuses the second. What is new here is that the second is a fact about the
374
346
  sheet, and can be fixed there.
375
347
 
376
- ⭐ **And you no longer have to find the ceiling by building into it.** `build`
348
+ ⭐ **And you do not have to find the ceiling by building into it.** `build`
377
349
  and `explain` print it for any mesh with a depth map — per axis, per direction,
378
350
  naming the triangle that goes first — from `tan t = A₀/A_axis` on the mesh's own
379
351
  geometry ([AUTHORING §3.4](AUTHORING.md)):
@@ -426,8 +398,7 @@ share pins at **0.92–0.99** where rigc's own gallery reads 0.112 and 0.468.
426
398
  slope. That is the input this whole section is about.
427
399
 
428
400
  ⛔ rigc will not decide that your sheet is the wrong kind of thing. It has every
429
- authority to say what it measured, and the step share is that
430
- ([#448](https://github.com/firejune/rigc/issues/448)).
401
+ authority to say what it measured, and the step share is that.
431
402
 
432
403
  📐 Method, harness and the full ladders live in the repository rather than in
433
404
  this package, as
@@ -438,7 +409,7 @@ silhouette so it samples **2 %** of the depth range, and its ear-clipped interio
438
409
  holds triangles three orders of magnitude apart in area, the smallest of which
439
410
  reverse under a fraction of a pixel.
440
411
 
441
- **No run reproduces this:** the ladder in the paragraph below is the 2026-09-05 noise study's, taken with `bench/studies/2026-09-05-noise/tools/noiseprobe.ts` against the repository at `7ebebc97`, one evidence file per experiment; no command this page states re-takes it
412
+ **No run reproduces this:** the ladder in the paragraph below is the 2026-09-05 noise study's, taken with `bench/studies/2026-09-05-noise/tools/noiseprobe.ts`, one evidence file per experiment; no command this page states re-takes it
442
413
 
443
414
  🚨 **The sheet's grain is a slope too, and the ceiling reads it.** Because
444
415
  `max|dz/du|` is a maximum over sampled gradients, there is no averaging anywhere
@@ -456,9 +427,7 @@ ladders:
456
427
  [`bench/studies/2026-09-05-noise`](https://github.com/firejune/rigc/tree/main/bench/studies/2026-09-05-noise).
457
428
 
458
429
  ⭐ **And you do not have to guess which of the three you are looking at.** The
459
- lines under the ceiling say it
460
- ([#412](https://github.com/firejune/rigc/issues/412),
461
- [#448](https://github.com/firejune/rigc/issues/448)): the **1st percentile over
430
+ lines under the ceiling say it: the **1st percentile over
462
431
  the ceiling** is near 1 when a band of the mesh reaches the limit together and
463
432
  near 10 when one triangle does, which is a texel — 1.003 against 10.652 for the
464
433
  two sheets above. The **depth step across the triangle that folds first**, in
@@ -514,7 +483,7 @@ rigging one**, and given §9 that is the whole argument for it.
514
483
 
515
484
  ### 3.1 ⭐ And now the spec holds this split, not its results
516
485
 
517
- Since [#295](https://github.com/firejune/rigc/issues/295) the pattern above is a
486
+ The pattern above is a
518
487
  named construct rather than a page of advice: **one group track whose key states
519
488
  the model, with a depth per member** (AUTHORING §4.5.1). The worked example's six
520
489
  residuals and six scale factors are two tracks:
@@ -533,15 +502,14 @@ residuals and six scale factors are two tracks:
533
502
  `faceshift` bone already applies, so what each member keys is exactly the residual
534
503
  this section derives. Drop it — write `carried: 0` or leave it out — and the same
535
504
  kind emits the **full** `dx` instead, which is what the parts hanging off `head`
536
- rather than off `faceshift` need. ⇒ **The split the toolchain used to know nothing
537
- about is now the one parameter, and the seam falls where the parent chain already
505
+ rather than off `faceshift` need. ⇒ **The split is the one parameter, and the seam falls where the parent chain already
538
506
  put it** (AUTHORING §4.5.1 refuses a model over members under different parents,
539
507
  for exactly that reason).
540
508
 
541
509
  ⭐ **`explain` then prints the column of six, which is what the argument above
542
510
  asked for.** The `MEMBER` block (AUTHORING §4.5.2) is a row per member — the
543
511
  emitted value, the `x` it read off the rig, and the depth the spec stated — so the
544
- nose diagnostic below is now a line you read rather than arithmetic you redo:
512
+ nose diagnostic below is a line you read rather than arithmetic you redo:
545
513
 
546
514
  **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
547
515
  ```
@@ -735,22 +703,21 @@ that has to go past it fades the far cheek or ear out — `rgba` to alpha 0 —
735
703
  swaps the attachment away, and another part takes over. `A39` measures that: a
736
704
  deform key whose slot draws **no pixels at that key's own time** is passed over by
737
705
  name rather than refused, with the reason on the stats line and beside the key's
738
- own figures in the `DEFORM` block
739
- ([#401](https://github.com/firejune/rigc/issues/401)). Two things it is not — the
706
+ own figures in the `DEFORM` block. Two things it is not — the
740
707
  bar is alpha **exactly 0**, so a part faded halfway is still refused with the
741
708
  alpha in the message; and it is per key and per time, so the same slot folding at
742
- full alpha anywhere else is refused as before. ⛔ It is also not
709
+ full alpha anywhere else is refused. ⛔ It is also not
743
710
  `invariants.deformMayFold`: that field turns the check off for the slot at every
744
711
  angle, including the ones where the part is fully visible.
745
712
 
746
- 🚨 **Fade out *up to* the angle you cannot take, never *at* it — and the gate now
713
+ 🚨 **Fade out *up to* the angle you cannot take, never *at* it — and the gate
747
714
  keeps that rule rather than asking you to.** The runtime interpolates between
748
715
  keys, so an alpha-0 key landing exactly on the folding key leaves the frames just
749
716
  before it drawn and nearly folded (§9.2 measures it: 8 reversed triangles at
750
717
  alpha 0.20). `A39` scans the spans between consecutive keys as well and refuses
751
718
  one by name, at a time solved for in closed form and then posed and measured
752
- ([#403](https://github.com/firejune/rigc/issues/403), AUTHORING §4.11.3). ⇒ The
753
- practical shape is unchanged and now checkable: **key the fade to 0 at or before
719
+ (AUTHORING §4.11.3). ⇒ The
720
+ practical shape is checkable: **key the fade to 0 at or before
754
721
  the last angle that gates green, and let the fold happen after it.**
755
722
 
756
723
  📘 **The identity above, used forwards on a `pitch`.**
@@ -766,19 +733,17 @@ That is this table checked from the other end, on the other axis.
766
733
  ⚠️ **A grid's perimeter is 16 of its 25 vertices, and in row-major order they are
767
734
  interleaved with the interior.** Spine's `hull` is the first `hull` vertices of
768
735
  the list, in order — the editor draws the outline by joining them in sequence — so
769
- a row-major grid cannot declare one. And an undeclared hull is not neutral: rigc
770
- used to write `hull: 0` there, and the editor's import repairs that by making
736
+ a row-major grid cannot declare one. And an undeclared hull is not neutral: the
737
+ editor's import repairs a `hull: 0` by making
771
738
  **every** vertex a hull vertex in list order, which is a self-intersecting outline
772
- on the mesh the person refining the draft sees
773
- ([#368](https://github.com/firejune/rigc/issues/368) records the round trip that
774
- found it). So the list is written the way the editor writes one: **the perimeter
739
+ on the mesh the person refining the draft sees. So the list is written the way the editor writes one: **the perimeter
775
740
  first, walked around — top row, right column, bottom row, left column — then the
776
741
  interior, row-major.** rigc derives `hull` from the triangles (AUTHORING §3.4) and
777
742
  refuses any other order with the walk to renumber along, so getting this wrong
778
743
  costs one loop rather than a silent file.
779
744
 
780
745
  **What it costs the reader.** `explain` prints one offset per vertex in list
781
- order, so the run no longer reads as one row of five values repeated down the
746
+ order, so the run does not read as one row of five values repeated down the
782
747
  grid: entries 0–15 walk the perimeter and 16–24 are the interior. The column
783
748
  table is still what to check it against — a vertex's offset depends on its column
784
749
  alone — and the right column's five entries (4–8) carrying one value is the
@@ -809,9 +774,7 @@ scaleX = cos(α − t) / cos α where α = atan2(x, z)
809
774
  ```
810
775
 
811
776
  The far eye narrows to 89%, the near eye widens to 106% (**derived**: 0.8922 and
812
- 1.0641). Parts on the axis get `cos t = 0.9781` and could therefore share one
813
- track — the nose and mouth did, through a `groups` entry, and it was the only
814
- place in the worked example's turn where two parts could (§7).
777
+ 1.0641). Parts on the axis get `cos t = 0.9781`.
815
778
 
816
779
  📘 **How much this is worth depends on the axis, and there is a worked case for
817
780
  the other one.** The same closed form on the `pitch` of
@@ -822,12 +785,12 @@ the **0.892 … 1.064** above — a wider span at the *identical* angle, because
822
785
  than it is deep. ⇒ **The foreshortening buys more on a nod than on a turn**, and
823
786
  that is arithmetic rather than a judgement about the art.
824
787
 
825
- 📌 **That entry is gone, and the reason is worth a line.** `scalex` is the
788
+ 📌 **The on-axis pair needs no `groups` entry.** `scalex` is the
826
789
  foreshortening projection of the same `derive` kind §3.1 uses (AUTHORING §4.5.1),
827
790
  so all six features are one track and the on-axis pair's shared value **falls out
828
791
  of the arithmetic** — `α = atan2(0, z)` is 0, so `cos(α − t)/cos α` is `cos t`.
829
- A coincidence between two members has stopped being something an author has to
830
- notice and spend a group on.
792
+ A coincidence between two members is not something an author has to notice and
793
+ spend a group on.
831
794
 
832
795
  ### 🚨 The iris does not foreshorten, and this is the finding
833
796
 
@@ -849,14 +812,14 @@ look_r: [iris_r, spark_r] scalex = 1 / 1.0641 = 0.9398
849
812
  ```
850
813
 
851
814
  ⭐ **These two stay a shared value on a group, and they are the case where the
852
- per-member construct is the wrong tool** — stated here because "we have a model
853
- now" is exactly the reasoning that would spoil them. A counter-scale belongs to
815
+ per-member construct is the wrong tool** — stated here because "there is a
816
+ model" is exactly the reasoning that would spoil them. A counter-scale belongs to
854
817
  the **socket**, not to the part: `spark_l` sits at local `x = −11` and takes the
855
818
  same `1.1208` as `iris_l` at `0`, because what is being cancelled is the socket's
856
819
  foreshortening and not the highlight's own. ⇒ **The value is precisely NOT a
857
820
  function of the member's position**, so a `groups` entry with one number is the
858
821
  true statement and a `derive` over `iris_l`/`spark_l` would be a fiction that
859
- happened to use the new field.
822
+ happened to use the `derive` field.
860
823
 
861
824
  ⭐ **Their positions still ride the socket, and that is the part that makes it
862
825
  correct rather than a hack.** A bone's scale moves its children's local
@@ -864,7 +827,7 @@ translation, so the highlight at local `(−11, +11)` under the far socket's 0.8
864
827
  lands at `−9.81` — it slides 1.19 units inward, toward the surface it reflects
865
828
  off (**derived**). Only the *shape* is held.
866
829
 
867
- **No run reproduces this:** a person's judgement over seven renders, kept in the `gallery/portrait` record that landed 2026-09-03 (`FINDINGS.md`, *The measured sweep*); no instrument in this repository grades a turn, so nothing re-takes it and nothing can
830
+ **No run reproduces this:** a person's judgement over seven renders, kept in the `gallery/portrait` record (`FINDINGS.md`, *The measured sweep*); no instrument in this repository grades a turn, so nothing re-takes it and nothing can
868
831
 
869
832
  📊 **Measured effect on the cliff, by the worked example's own sweep — a
870
833
  looked-at judgement over seven renders, not a computed figure:** without the
@@ -909,28 +872,18 @@ are all timing:
909
872
  blink key sits at `0.399999` (AUTHORING §4.5). **A continuous channel does not
910
873
  care where the samples land.**
911
874
 
912
- ⚠️ **Two constraints on the lid's own drawing, and both are art decisions the rig
875
+ ⚠️ **One constraint on the lid's own drawing, and it is an art decision the rig
913
876
  cannot express.**
914
877
 
915
- 1. **Fade its top edge out, and size the fade in pixels.** A flat plate of skin
916
- translating down a forehead has a visible edge; fading its top 30 pixels
917
- removes it. ⛔ **Do not write that fade proportionally.** In the worked example
918
- it was first authored as `offset 0.34` in `objectBoundingBox` units — 34% of
919
- *whatever height the plate happened to be* — and when the plate grew from 88 to
920
- 112 to cover the eye, the faded band grew with it and stopped covering the top
921
- of the shut eye. In `userSpaceOnUse` with `y2="30"` the height becomes a free
922
- variable and the coverage becomes a stated one: `106 − 30 = 76` opaque pixels
923
- above the lash, against a 70-unit eye.
924
- 2. ~~**Run its sides flush to the window edge.**~~ ✅ **No longer needed.** This
925
- was a workaround for [#292](https://github.com/firejune/rigc/issues/292):
926
- `rigc render`'s bilinear filter sampled straight alpha, so **every
927
- atlas-region edge drew a one-pixel dark rim** over what was behind it, and
928
- running the art flush to its window gave the sampler nothing dark to reach
929
- into. The sampler now interpolates premultiplied and a transparent texel gets
930
- no vote in the colour, so **a lid plate may have whatever margin its drawing
931
- wants.** §9.1 keeps the record of how the rim was found, because the lesson
932
- there — that scene work exercises a renderer where game-part work does not —
933
- outlives this particular defect.
878
+ **Fade its top edge out, and size the fade in pixels.** A flat plate of skin
879
+ translating down a forehead has a visible edge; fading its top 30 pixels
880
+ removes it. ⛔ **Do not write that fade proportionally.** In the worked example
881
+ it was first authored as `offset 0.34` in `objectBoundingBox` units — 34% of
882
+ *whatever height the plate happened to be* — and when the plate grew from 88 to
883
+ 112 to cover the eye, the faded band grew with it and stopped covering the top
884
+ of the shut eye. In `userSpaceOnUse` with `y2="30"` the height becomes a free
885
+ variable and the coverage becomes a stated one: `106 − 30 = 76` opaque pixels
886
+ above the lash, against a 70-unit eye.
934
887
 
935
888
  📌 **Blink both lids on one `groups` track.** An L/R offset of one frame was
936
889
  tried in the worked example and rejected: at 25 fps it does not read as a soft
@@ -1004,20 +957,14 @@ face-specific part is which bone leads. The worked example's ordering
1004
957
  | `headroll` translatex + rotate | **0.40 s** | the head follows **+12% of the duration after the eyes**. A rigid 3.4-unit slide and a −1.2° roll |
1005
958
  | `lock_l` / `lock_r` / `ahoge` rotate | 0.72 / 0.76 / 0.82 s | **+21% / +24% / +28% after the head**, each with one overshoot crossing of opposite sign |
1006
959
 
1007
- 🩹 **The head's follow there is a rigid slide plus a roll, not a small yaw, and
1008
- that was the transcription cost showing through.** A head following a gaze really
1009
- does yaw a few degrees; when this was authored, three degrees of yaw meant a
1010
- third pair of `deform` keys — 80 more hand-written numbers — for a motion a
1011
- viewer reads as *"her head moved a little"*. ✅ **§1.1 took that price away**: it
1012
- is one `transform` key with a smaller `degrees`, the same edit §8's seven-angle
1013
- sweep is now made of. ⇒ What is left to weigh is not the typing, it is §7's
960
+ 🩹 **The head's follow there is a rigid slide plus a roll, not a small yaw.** A
961
+ head following a gaze really does yaw a few degrees, and that is one `transform`
962
+ key with a smaller `degrees` (§1.1). ⇒ What there is to weigh is §7's
1014
963
  table — a yaw on the `gaze` channel is a second animation keying the head mesh's
1015
964
  deform, which `turn` already owns, and that collision is the thing the format
1016
965
  cannot express. Same trade in the turn's anticipation: MOTION §3.6 asks
1017
966
  for a counter-move, and the worked example's is a **−0.9° counter-roll on the
1018
- neck bone rather than a counter-yaw**, priced at the 50-float head key plus 30 on
1019
- the fringe it cost then and at one more `transform` key now. ⚠️ Neither has been
1020
- re-authored, so what this page measures is still the motion the example ships.
967
+ neck bone rather than a counter-yaw**, which would be one more `transform` key.
1021
968
 
1022
969
  ⭐ **A roll channel is worth having for a second reason: it is where a turn's arc
1023
970
  comes from.** A yaw shift is a straight horizontal line and `translatex` draws
@@ -1037,7 +984,7 @@ expensive thing to change.
1037
984
  rendered and looked at. The geometry columns are **derived**; the last column is
1038
985
  the record's looked-at judgement:
1039
986
 
1040
- **No run reproduces this:** the geometry columns are §1's line evaluated by hand and the last column is a person's, from the sweep in the 2026-09-03 `gallery/portrait` record; since §1.1 the seven builds are `"degrees": <n>` and a rebuild, but no command this page states takes them
987
+ **No run reproduces this:** the geometry columns are §1's line evaluated by hand and the last column is a person's, from the sweep in the `gallery/portrait` record; each of the seven builds is `"degrees": <n>` and a rebuild, but no command this page states takes them
1041
988
 
1042
989
  | yaw | centre shift | far band 42 → | ratio | 9-unit ink → | reads as a turn? |
1043
990
  | --- | --- | --- | --- | --- | --- |
@@ -1094,38 +1041,28 @@ not a format problem, it is a parts-and-labour problem.
1094
1041
  | `gaze` | 1.5 s | 8 | 32 | 0 | 0 | 0 |
1095
1042
  | **`turn`** | 2.2 s | **8** | **33** | 2 | 8 | **0** |
1096
1043
 
1097
- ⇒ **`idle` and `gaze` cost what an ordinary MOTION.md shot costs, and the turn's
1098
- transcription is gone — both halves of it.** That last cell was **160** until
1099
- [#294](https://github.com/firejune/rigc/issues/294) shipped, and what those 160
1100
- floats were is worth keeping: **five distinct values** (one per column, repeated
1101
- down five rows), with **25 of the 50 slots in a head key structurally `0`**
1102
- because a yaw has no vertical component. They are now four `transform` keys
1103
- (§1.1) and the compiler writes the run.
1104
-
1105
- ⇒ The **tracks** column was **20** until
1106
- [#295](https://github.com/firejune/rigc/issues/295) shipped, with 19 of the 20
1107
- carrying one value each. It is 8, because the sixteen sibling tracks became two
1108
- group tracks whose keys state a model (§3, AUTHORING §4.5.1) and the four hair
1109
- tracks became one.
1110
-
1111
- 🚨 **And here is the number that matters more than either count: the hand-written
1112
- figures barely moved. What they ARE changed completely.** The 20-track turn stated
1113
- **32 residuals and scale factors** across its held keys; the 8-track one states
1114
- **34 depths** — of which **11 are distinct**, the rest being the same table
1115
- repeated on the second held key and on the `scalex` track. ⇒ The saving is not
1116
- arithmetic, it is **auditability**: a residual of `1.14` is a number a reader can
1117
- only take on trust, and `brow_r at depth 158` is a claim they can argue with. §3
1118
- is why that is the whole point, and §2 is why the depths had nowhere else to live.
1044
+ ⇒ **`idle` and `gaze` cost what an ordinary MOTION.md shot costs, and the turn
1045
+ has no transcription in it.** The deform is four `transform` keys (§1.1) and the
1046
+ compiler writes the run: **five distinct values** per head key (one per column,
1047
+ repeated down five rows), with **25 of its 50 slots structurally `0`** because a
1048
+ yaw has no vertical component. The **tracks** column is 8 because the features are
1049
+ two group tracks whose keys state a model (§3, AUTHORING §4.5.1) and the four hair
1050
+ parts are one.
1051
+
1052
+ 🚨 **And here is the number that matters more than either count: what the
1053
+ hand-written figures ARE.** The turn states **34 depths** — of which **11 are
1054
+ distinct**, the rest being the same table repeated on the second held key and on
1055
+ the `scalex` track. ⇒ The point is **auditability**: a residual of `1.14` is a
1056
+ number a reader can only take on trust, and `brow_r at depth 158` is a claim they
1057
+ can argue with. §3 is why that is the whole point, and §2 is why the depths had
1058
+ nowhere else to live.
1119
1059
 
1120
1060
  ⚠️ **A plain `groups` entry still buys almost nothing on a face, and that is
1121
1061
  structural rather than an oversight.** Keying several bones **identically** is
1122
1062
  right for a wheel pair, and **every part needing a different number is what
1123
- parallax means.** Of the 20 turn tracks exactly one was a group for that reason
1124
- (`axis`, the nose and mouth, both on the axis and therefore both `cos t`), plus
1125
- the two reciprocal groups §5 needs. ⭐ **That coincidence has stopped being an
1126
- authoring concept**: the on-axis pair's shared `cos t` now falls out of the same
1127
- closed form as everybody else's value, so `axis` is gone from the spec while
1128
- `look_l`/`look_r` stay — because those two really are one shared number (§5).
1063
+ parallax means.** The on-axis pair's shared `cos t` falls out of the same closed
1064
+ form as everybody else's value, so it needs no group, while `look_l`/`look_r` stay
1065
+ one — because those two really are one shared number (§5).
1129
1066
 
1130
1067
  ### The turn as a value rather than a time
1131
1068
 
@@ -1225,15 +1162,15 @@ breaks the moment the two share a target — in the worked example both `turn` a
1225
1162
  `tilt` key `headroll`. §7's paragraph on sliders is the mechanism and
1226
1163
  `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` is the refusal.
1227
1164
 
1228
- ✅ **And the space between the two dials is measured rather than inferred**
1229
- ([#399](https://github.com/firejune/rigc/issues/399)). Two additive sliders posed
1165
+ ✅ **And the space between the two dials is measured rather than inferred.**
1166
+ Two additive sliders posed
1230
1167
  at a grid of *both* values — and over a rotation and a translation, so the claim
1231
1168
  is not one property's — are the closed-form sum at every cell of it, to float64:
1232
1169
  each slider maps its own reading to a time by §3.5.2's rule, its animation is read
1233
1170
  there, and the two contributions add. ⭐ That is a property of the **interior**
1234
- and not of the corners, which is the whole reason it needed a grid: `PS129` drives
1235
- the same comparison with an `ease` on one of the two animations, and every corner
1236
- of the space still reads as correct while most of the inside has moved. ⇒ **a
1171
+ and not of the corners, which is the whole reason it needed a grid: with an `ease` on one of the two
1172
+ animations, every corner of the space still reads as correct while most of the
1173
+ inside has moved. ⇒ **a
1237
1174
  lookup table wants linear keys for a second reason** — not only that the face
1238
1175
  would drift while the value sat still, but that a curve is invisible to any check
1239
1176
  that reads an axis at its ends.
@@ -1263,16 +1200,13 @@ and the yaw dial also fades it, one of the two has to stop: key that property
1263
1200
  from **one** slider, or move both edits into the animation a single slider
1264
1201
  applies. `A40` refuses the rest by name, and AUTHORING §3.5.2 is the mechanism.
1265
1202
 
1266
- ✅ **And which of the two a timeline is, the gate now poses rather than asks.**
1267
- It read `Timeline.additive`, the runtime's own declaration, which two classes
1268
- state falsely about themselves — so a path constraint's `mix` and a slider's
1269
- `time`, both in the composing list above, were refused by name with a message
1270
- saying the flag would not compose them
1271
- ([#655](https://github.com/firejune/rigc/issues/655)). `A40` applies the shared
1272
- timeline twice with `add` set instead and reads whether the second application
1273
- accumulated. The same measurement retired a refusal no pose could have told
1274
- apart: two dials whose animations both fire **events** were refused, and a slider
1275
- fires no event at all — it applies its animation with `firedEvents` null.
1203
+ ✅ **And which of the two a timeline is, the gate poses rather than asks.**
1204
+ `A40` applies the shared timeline twice with `add` set and reads whether the second
1205
+ application accumulated, rather than reading `Timeline.additive`, the runtime's own
1206
+ declaration, which two classes state falsely about themselves — a path
1207
+ constraint's `mix` and a slider's `time`, both in the composing list above. Two
1208
+ dials whose animations both fire **events** are not refused either: a slider fires
1209
+ no event at all — it applies its animation with `firedEvents` null.
1276
1210
 
1277
1211
  ⭐ **Three dials are the same sum as two — as long as every one of them is
1278
1212
  additive.** The case worth knowing is a non-additive dial in the *middle* of
@@ -1318,13 +1252,10 @@ on this page that multiplies rather than adds. ⚠️ **It only works downward t
1318
1252
  the `constraints` array.** A slider reads its own authority when its turn comes
1319
1253
  and the array is the update order, so a dial that keys the `mix` of a slider
1320
1254
  *earlier* than itself writes a number that slider has already read past: the
1321
- driven axis is dead at every position, every frame. ✅ **The gate names that pair
1322
- now** — `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` refuses it with both
1323
- sliders, the property and both array indices, and says which way to move them
1324
- ([#658](https://github.com/firejune/rigc/issues/658)). Until then it was green,
1325
- for a reason worth knowing: a slider whose `mix` is keyed at all leaves `A40`'s
1326
- comparison, so nothing in the tool was looking at that pair. ⇒ put the driving
1327
- slider **first**, which is what the refusal tells you to do.
1255
+ driven axis is dead at every position, every frame. ✅ **The gate names that
1256
+ pair** — `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` refuses it with both
1257
+ sliders, the property and both array indices, and says which way to move them.
1258
+ ⇒ put the driving slider **first**, which is what the refusal tells you to do.
1328
1259
 
1329
1260
  🚨 **And a dial cannot turn itself on.** `Slider.update` reads its own `mix` as
1330
1261
  the alpha it applies the animation with, before that animation runs, so a slider
@@ -1343,8 +1274,7 @@ it when its own turn comes, and that turn is its place in `constraints`. A
1343
1274
  number in that constraint's pose and moves nothing at all: [measured] the bone a
1344
1275
  physics constraint drives travels `0.000e+0` across the dial with the constraint
1345
1276
  declared first and `4.256e+2` with it declared last, and a path constraint's
1346
- rider `0.000e+0` against `1.620e+2`
1347
- ([#665](https://github.com/firejune/rigc/issues/665)). ✅ `A42` names those pairs
1277
+ rider `0.000e+0` against `1.620e+2`. ✅ `A42` names those pairs
1348
1278
  too, with the constraint's kind and both array indices. ⇒ **every constraint a
1349
1279
  dial drives goes after that dial in `constraints`** — which, for a face, means
1350
1280
  the dials come first and the jiggles, paths and aim constraints they scale come
@@ -1356,8 +1286,7 @@ its SKIP rather than asking you to move anything.
1356
1286
  🔸 **The bone-less slider is the same story one field over.** A slider with no
1357
1287
  `bone` takes its time from `slider.<name>.time`, which any animation can key — and
1358
1288
  two dials keying it *add*, so a time-driven axis composes like everything else
1359
- here, and since [#655](https://github.com/firejune/rigc/issues/655) the gate
1360
- agrees: it used to refuse exactly this rig. The same array rule applies, for the
1289
+ here, and the gate agrees. The same array rule applies, for the
1361
1290
  same reason — and `A42` refuses it there too, naming `time` instead of `mix`.
1362
1291
  ⚠️ Two things the bone
1363
1292
  form does and this one does not: there is no `Math.max(0, time)` and no wrap, so a
@@ -1377,9 +1306,8 @@ about it because the pair genuinely never meets.
1377
1306
  axis: the arithmetic, the flag, and the non-additive-in-the-middle case all read
1378
1307
  exactly as they do above. Write `"additive": true` on all of them.
1379
1308
 
1380
- ✅ **The editor half, measured.** This paragraph said *unknown* until the round
1381
- trip was taken with `tools/editor_roundtrip.ts` on a licensed editor (data
1382
- version 4.3.26) against a 4.3.13 build of
1309
+ ✅ **The editor half, measured.** The round trip was taken with
1310
+ `tools/editor_roundtrip.ts` on a licensed editor (data version 4.3.26) against a 4.3.13 build of
1383
1311
  [`gallery/look`](https://github.com/firejune/rigc/tree/main/gallery/look) — this
1384
1312
  subsection's worked case, not the `gallery/portrait` this page names at the top,
1385
1313
  which declares no constraints at all and so can carry no slider. What it found:
@@ -1389,39 +1317,32 @@ which declares no constraints at all and so can carry no slider. What it found:
1389
1317
  field, and the two keep their places in the `constraints` array. `mix: 1` and
1390
1318
  `to: 0` are dropped, and those are the format's own defaults (`SkeletonJson`
1391
1319
  reads `mix` as 1 and `to` as 0 when absent) — an elision, not a loss.
1392
- - ⚠️ **The animation each slider *names* did not come back, until rigc changed
1393
- what it emits.** The editor re-sorts the `animations` object and a slider's
1394
- animation is an ordinal in the format, so `yaw -> "turn"` returned as
1395
- `yaw -> "sweep"` — the first animation of the sorted list
1396
- ([#535](https://github.com/firejune/rigc/issues/535)). rigc now emits
1397
- animations in the editor's own order — natural and case-insensitive
1398
- ([#539](https://github.com/firejune/rigc/issues/539),
1399
- [#543](https://github.com/firejune/rigc/issues/543)); this rig's names are ones
1400
- a codepoint sort orders identically, which is what it emitted at the time of
1401
- the round trip below. On the same rig through the same editor that
1402
- restored `yaw -> "turn"` and took the re-rendered mean absolute error from
1403
- 10.4655 / 8.4961 / 8.7140 (`sweep` / `tilt` / `turn`) to
1404
- 0.3035 / 0.0769 / 0.0588, worst drift 16.535 px to 3.947 px.
1320
+ - ⚠️ **The animation each slider *names* comes back because rigc emits
1321
+ animations in the editor's own order** — natural and case-insensitive. The
1322
+ editor re-sorts the `animations` object and a slider's animation is an ordinal
1323
+ in the format, so out of that order `yaw -> "turn"` returns as
1324
+ `yaw -> "sweep"` — the first animation of the sorted list. In order, on the
1325
+ same rig through the same editor, `yaw -> "turn"` comes back and the
1326
+ re-rendered mean absolute error is 0.3035 / 0.0769 / 0.0588
1327
+ (`sweep` / `tilt` / `turn`) against 10.4655 / 8.4961 / 8.7140 out of order,
1328
+ worst drift 3.947 px against 16.535 px.
1405
1329
  - 🚨 **The physics constraint on the cowlick comes back driving nothing.**
1406
1330
  `rotate: 1` is absent from the export, and an absent `rotate` parses as **0**
1407
1331
  (`SkeletonJson`), so the returned file states *drives nothing* rather than
1408
1332
  omitting a default — which is why `A23_PHYSICS_CONSTRAINT_EFFECTIVE` refuses it
1409
- by name. Independent of the ordering defect, and filed as
1410
- [#536](https://github.com/firejune/rigc/issues/536).
1333
+ by name. Independent of the ordering.
1411
1334
 
1412
- ✅ **Why, measured since.** It is not elision and not a defect in one field:
1335
+ ✅ **Why, measured.** It is not elision and not a defect in one field:
1413
1336
  the editor's physics model holds `x` and `y` and nothing else, with no limit on
1414
1337
  how many at once. Three rigs, twelve constraints, predictions written before the
1415
1338
  round trip — a lone `y` came back, `x` and `y` together came back, and a lone
1416
1339
  `rotate`, a lone `scaleX` and a lone `shearX` each came back as **no components
1417
1340
  at all**, with every constraint's fixed-point `strength` returning exactly so a
1418
- silent harness failure could not read as a finding
1419
- ([#540](https://github.com/firejune/rigc/issues/540)). ⇒ **A rotation-driven
1341
+ silent harness failure could not read as a finding. ⇒ **A rotation-driven
1420
1342
  jiggle does not survive the editor, and no `scaleY` mode substitutes for it.**
1421
1343
 
1422
- ✅ **And the locus is measured: the loss is at EXPORT** — #536 closed
1423
- 2026-09-17 on round trip 10, against Spine 4.3.26 and without decoding the
1424
- project format. `gallery/look`'s build and three variants of its
1344
+ ✅ **And the locus is measured: the loss is at EXPORT** — against Spine
1345
+ 4.3.26 and without decoding the project format. `gallery/look`'s build and three variants of its
1425
1346
  `skeleton.json` were each imported with the documented CLI, the project files
1426
1347
  inflated, and compared byte by byte. **Two imports of the same file differ
1427
1348
  only at bytes 13–28** — a timestamp or a hash — so that is the noise floor,
@@ -1435,14 +1356,14 @@ which declares no constraints at all and so can carry no slider. What it found:
1435
1356
  the exporter wrote nothing for it. It is the editor's **writer**, not its
1436
1357
  reader, and not rigc's emitter.
1437
1358
 
1438
- ✅ **And the gate now says so before the trip, not after.** A face rig that is
1359
+ ✅ **And the gate says so before the trip, not after.** A face rig that is
1439
1360
  authored to come back out of the editor declares
1440
1361
  `"invariants": { "editorRoundTrip": true }` (AUTHORING §3.7) and
1441
1362
  `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` refuses the constraint by name at build
1442
1363
  time, with the fix in the message: drive it in `x`/`y`, or drop the declaration.
1443
1364
  ⚠️ A rig that declares nothing is **not** silent either — A41 SKIPs and the skip
1444
1365
  names the constraint and the component, which is the whole reason the rule is
1445
- opt-in rather than default-off. rigc's own output was never wrong here: a
1366
+ opt-in rather than default-off. rigc's own output is not wrong here: a
1446
1367
  rotation jiggle is valid Spine 4.3 that every runtime plays, and refusing it for
1447
1368
  everybody would be refusing correct data on behalf of one consumer. ⇒ A23 and
1448
1369
  A41 are the same loss from opposite sides of the trip: A41 fires on what goes
@@ -1452,35 +1373,20 @@ which declares no constraints at all and so can carry no slider. What it found:
1452
1373
  the clean one included. Visually small once the ordering is fixed — but it is
1453
1374
  98% of the keys, and *small* is not *explained*. ⚠️ **[measured] by
1454
1375
  `tools/editor_roundtrip.ts` on the trips this subsection records, and no
1455
- command this page states re-takes it** — the trips reached ten by 2026-09-17
1456
- (#536's measurement above is the tenth), so read the figure as those runs' and
1376
+ command this page states re-takes it** — so read the figure as those runs' and
1457
1377
  not as a property the gate holds.
1458
1378
 
1459
- ✅ **The two things this paragraph said it still did not measure have since been
1460
- measured, and both came back safe** — [#544](https://github.com/firejune/rigc/issues/544)
1461
- is the card for having left the sentence standing:
1379
+ ✅ **Two more things are measured, and both came back safe:**
1462
1380
 
1463
1381
  - **More than one event is safe.** The editor re-keys `events` the way it re-keys
1464
1382
  `animations` — `zebra, mike, alpha` came back `alpha, mike, zebra` — but every
1465
- firing resolved **by name**, `0.3 -> mike` and `0.6 -> alpha`, payloads intact
1466
- ([#539](https://github.com/firejune/rigc/issues/539)). The ordinal shape does
1383
+ firing resolved **by name**, `0.3 -> mike` and `0.6 -> alpha`, payloads intact. The ordinal shape does
1467
1384
  *not* bite here, and rigc emits events in the order you declare them.
1468
- - **More than one skin imports**, and this bullet reported a harness defect as an
1469
- editor behaviour until [#541](https://github.com/firejune/rigc/issues/541)
1470
- closed on **2026-09-14**. The four-skin rig this page was written against
1471
- imports in **both build modes** — default and `--copy-images` — **exit 0,
1472
- project written**, with `default, alpha, mike, zulu` emitted and
1473
- `default/patch`, `alpha/patch`, `mike/patch`, `zulu/patch` as the attachment
1474
- names. ⚠️ **The editor was never silent**: it named the section, the attachment
1475
- and the rule, and `tools/editor_roundtrip.ts` had discarded its stdout and
1476
- stderr. What changed is [#552](https://github.com/firejune/rigc/issues/552) —
1477
- the composed `<skin>/<placeholder>` name, with `default` pinned first; the
1478
- composition was retired by [#796](https://github.com/firejune/rigc/issues/796),
1479
- which measured that it was never what the editor objected to — and what
1480
- still binds is [#567](https://github.com/firejune/rigc/issues/567): a
1481
- `CompileError` when the default skin shares a placeholder with a named one. ⇒
1482
- Skins are not the reason to stay at one, and every figure on this page was
1483
- still taken on a rig carrying exactly one (AUTHORING §10.1).
1385
+ - **More than one skin imports.** A four-skin rig imports in **both build
1386
+ modes** — default and `--copy-images` — **exit 0, project written**. What binds
1387
+ is a `CompileError` when the default skin shares a placeholder with a named
1388
+ one. ⇒ Skins are not the reason to stay at one, and every figure on this page
1389
+ was taken on a rig carrying exactly one (AUTHORING §10.1).
1484
1390
 
1485
1391
  Every figure above was read back through `spine-core`.
1486
1392
 
@@ -1497,16 +1403,14 @@ Every figure above was read back through `spine-core`.
1497
1403
  | **3–4× on the eyes** | because that is where a reader will look, and a face has no other equivalent |
1498
1404
 
1499
1405
  📊 **Four of the five art defects in the worked example were invisible at contact
1500
- sheet scale** and all four were found at 1:1 or better: the dark seam
1501
- ([#292](https://github.com/firejune/rigc/issues/292)), the lid's fade letting a
1406
+ sheet scale** and all four were found at 1:1 or better: the dark seam, the lid's fade letting a
1502
1407
  shut eye's lash show through as a grey smudge, the iris crossing its own lash at
1503
1408
  the gaze extreme, and a forehead highlight turning the lid's soft edge into a
1504
1409
  tonal step.
1505
1410
 
1506
- ⚠️ **A portrait is the first rig whose plates are *supposed* to overlap
1507
- invisibly**, which is why #292 stayed latent through four gallery examples: every
1508
- part in them carries an ink outline at its edge, and a dark rim on a dark line
1509
- cannot be seen. ⇒ **Scene work exercises a renderer where game-part work does
1411
+ ⚠️ **A portrait's plates are *supposed* to overlap invisibly**, and that is
1412
+ where a renderer's edge defect shows: a part that carries an ink outline at its
1413
+ edge hides one, because a dark rim on a dark line cannot be seen. ⇒ **Scene work exercises a renderer where game-part work does
1510
1414
  not.** Expect to find renderer defects on your first face, and check a suspicious
1511
1415
  edge against a **region build** before reading a single vertex — which is the
1512
1416
  next item.
@@ -1534,9 +1438,8 @@ the wrong part.
1534
1438
 
1535
1439
  ### 9.2 🚨 The half nothing measures — three builds, one of them refused
1536
1440
 
1537
- **The setup geometry is measured; the deformed geometry was not, and one half of
1538
- it still is not.** Here is that
1539
- claim as three builds of the same rig, each command runnable verbatim from a
1441
+ **The setup geometry is measured; one half of the deformed geometry is not.**
1442
+ Here is that claim as three builds of the same rig, each command runnable verbatim from a
1540
1443
  clean checkout. Start from the good one:
1541
1444
 
1542
1445
  ```bash
@@ -1556,12 +1459,7 @@ bun cli.ts render --candidate gallery/portrait/build --fps 25 --max 640 \
1556
1459
  .. profile spine-html — every assertion applies
1557
1460
  ```
1558
1461
 
1559
- Green — the profile line above says so in the tool's own words — and the tally is
1560
- left to the tool: `bun run selftest` prints this build's live assertion and skip
1561
- counts on its `GALLERY_EXAMPLE_IS_GREEN[portrait/spine-html]`
1562
- line. One written here would be a figure nothing in the tree compares against a
1563
- run, sitting inside a fence that reads as a transcript — and rigc prints no tally
1564
- line, so it never was one.
1462
+ Green — the profile line above says so in the tool's own words.
1565
1463
 
1566
1464
  Now break the projection two ways. Both scripts write a variant motion spec
1567
1465
  beside the originals and touch nothing in the repository:
@@ -1606,57 +1504,22 @@ bun cli.ts build --rig gallery/portrait/rig.json --motion /tmp/folded.motion.jso
1606
1504
  --out /tmp/folded --profile spine-html
1607
1505
  ```
1608
1506
 
1609
- ⚠️ **Both scripts were rewritten on 2026-09-03 and the old ones did nothing.**
1610
- They keyed off `if (k.vertices)`, which was true of every key until §1.1 put the
1611
- model on it — after that re-authoring the guard matched nothing, both scripts
1612
- wrote an unchanged motion spec, and builds (a) and (b) were the good build. That
1613
- is a doc command silently passing rather than silently failing, which is the
1614
- worse of the two: build (b) reported `A39 PASS` and the table below said `FAIL`.
1615
- Both are re-run above.
1616
-
1617
- ⚠️ **And script (a) broke a second time, the same way, on 2026-09-04.** It wrote
1618
- one row of five shifts and repeated it five times, which was the mesh's own
1619
- column order while the vertex list was row-major.
1620
- [#375](https://github.com/firejune/rigc/issues/375) renumbered both gallery
1621
- grids along their outline walk — perimeter first, then the interior, the order
1622
- Spine's `hull` needs — and the row went on landing at the same *positions* in a
1623
- list that no longer meant columns. The build stayed green and stayed wrong, so
1624
- nothing on the page moved; what it stopped being was **an inverted band**, which
1625
- is the one thing the table below reads it as. The figures in that table were
1626
- right the whole time and the command under them had stopped producing
1627
- them — a stale figure shows up at one site, and a broken command shows up at
1628
- every figure it feeds, which is how the two are told apart: this one also
1629
- contradicted §9.3's `check` row, `0.33 / 0.61` against a table saying
1630
- `0.20 / 0.38`. ⭐ **The repair is the doctrine's own**: the script resolves each
1631
- shift through the vertex's coordinate instead of its index, so the next
1632
- renumbering cannot move it. A `vertices` run is positional by format — that is
1633
- `fromVertex`'s whole job — and a *generator* of one has no reason to be.
1634
-
1635
- 🔒 **And that repair is now measured rather than asserted.** `bun run selftest`
1636
- runs this script again against a rig whose vertices have been renumbered every
1637
- way the compiler accepts — the outline rotated along its own walk, the outline
1638
- reflected, the interior reordered — and requires the run it writes to come back
1639
- as the same geometry once the renumbering is undone, while the run's own
1640
- positions have visibly moved. Nothing here declares that property: the subject
1641
- is any script on any page whose product is a `vertices` run, read off the
1642
- product.
1643
-
1644
1507
  **What comes back from both:**
1645
1508
 
1646
1509
  | | good | (a) one band inverted | (b) mesh folded |
1647
1510
  | --- | --- | --- | --- |
1648
- | `--profile spine-html`, **before `A39`** | green | **green, and the same counts** | **green, and the same counts** |
1511
+ | `--profile spine-html`, **without `A39`** | green | **green, and the same counts** | **green, and the same counts** |
1649
1512
  | `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | PASS | **PASS** | **PASS** |
1650
1513
  | the `MESH` coverage line | 100.00%, 95.90px past | **byte-identical** | **byte-identical** |
1651
- | 🆕 `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | PASS | PASS | **FAIL, both keys, 8 of 32 triangles** |
1652
- | `--profile spine-html`, **today** | green | green | **refused, and nothing written** |
1653
- | 🆕 the `DEFORM` block, `head` key 1 `area` | x0.637174 … x1.319122 | **x0.765250 … x1.362834** | **x−0.288121 … x1.820211** |
1654
- | 🆕 … and its `winding` | 32 of 32 kept | 32 of 32 kept | **24 of 32 kept** |
1514
+ | `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | PASS | PASS | **FAIL, both keys, 8 of 32 triangles** |
1515
+ | `--profile spine-html`, **with `A39`** | green | green | **refused, and nothing written** |
1516
+ | the `DEFORM` block, `head` key 1 `area` | x0.637174 … x1.319122 | **x0.765250 … x1.362834** | **x−0.288121 … x1.820211** |
1517
+ | … and its `winding` | 32 of 32 kept | 32 of 32 kept | **24 of 32 kept** |
1655
1518
 
1656
1519
  🚨 **All three were green, and the coverage line is the same string in all three,
1657
- because it reports the SETUP pose.** The `--profile spine-html` row is what
1658
- `A39` changed and the two rows above it are unchanged: both still measure only
1659
- the setup geometry, and `A35` is still silent about build (b). `A35` checks that a deform run *fits* its
1520
+ because it reports the SETUP pose.** The `--profile spine-html` row with `A39` is
1521
+ the only verdict that separates them, and the two rows above it measure only the
1522
+ setup geometry: `A35` is silent about build (b). `A35` checks that a deform run *fits* its
1660
1523
  attachment — an honest and useful check, and orthogonal to whether the numbers in
1661
1524
  it mean anything.
1662
1525
 
@@ -1696,19 +1559,9 @@ the two bands the swapped columns bound move at all: the 0 → 120 and 120 → 1
1696
1559
  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:
1697
1560
  the model is gone, so nothing is left to check the ratios against but the ratios.
1698
1561
 
1699
- `rigc explain` is the instrument that prints every other timeline's actual values,
1700
- and on a deform it used to print the shape of the run rather than the run: the
1701
- head's four keys came back as four lines, each giving a time, a curve kind, and
1702
- either `back to the setup pose` or `deform[0..50] 25 pair(s)` — the extent of the
1703
- run and how many pairs are in it, and not one of the numbers.
1704
-
1705
- ⇒ **`25 pair(s)` was the whole of what `explain` would tell you about a face
1706
- turn**, against a scalar track two lines up in the same report printing
1707
- `value=-35.345`. That asymmetry is
1708
- [#296](https://github.com/firejune/rigc/issues/296), and both of its halves have
1709
- now closed: §1.1's construct put the model and every offset it produced into the
1710
- same report (AUTHORING §4.11.1), and the `DEFORM` block put the geometry there
1711
- (AUTHORING §4.11.2) —
1562
+ `rigc explain` is the instrument that prints every timeline's actual values, and
1563
+ on a deform that is the model and every offset it produced (AUTHORING §4.11.1)
1564
+ and the `DEFORM` block's geometry (AUTHORING §4.11.2) —
1712
1565
 
1713
1566
  ```bash
1714
1567
  bun cli.ts explain --rig gallery/portrait/rig.json \
@@ -1724,13 +1577,13 @@ bun cli.ts explain --rig gallery/portrait/rig.json \
1724
1577
  winding 32 of 32 kept, 0 collapsed
1725
1578
  ```
1726
1579
 
1727
- ⇒ **`0.637` is now a figure the tool prints.** It is a *report* and not a bar —
1728
- `explain` takes no `--profile` and gates nothing, for #277's reason: a deliberate
1580
+ ⇒ **`0.637` is a figure the tool prints.** It is a *report* and not a bar —
1581
+ `explain` takes no `--profile` and gates nothing, because a deliberate
1729
1582
  3× stretch is a real thing to author, and the one deformed-geometry fault with no
1730
1583
  legitimate counter-example is the fold, which is `A39`'s.
1731
1584
 
1732
- 🆕 **`A39_DEFORM_KEEPS_TRIANGLE_WINDING` now closes the half of that gap the
1733
- fold lives in** — `--profile spine-html`, 2026-09-03. Build (b) above is refused
1585
+ **`A39_DEFORM_KEEPS_TRIANGLE_WINDING` closes the half of that gap the fold
1586
+ lives in** — `--profile spine-html`. Build (b) above is refused
1734
1587
  by name, on both its keys, with the triangles listed:
1735
1588
 
1736
1589
  ```
@@ -1741,11 +1594,10 @@ FAIL A39_DEFORM_KEEPS_TRIANGLE_WINDING: animation "turn" deform head/head key 1
1741
1594
 
1742
1595
  and builds (a) and the good one both still PASS it, because **an inverted band is
1743
1596
  not a fold**: its winding survives. The angle A39 first fires at agrees with
1744
- §4.2's `tan θ = Δx/Δz` to **0.0001°**, so the formula above is now checkable by
1597
+ §4.2's `tan θ = Δx/Δz` to **0.0001°**, so the formula above is checkable by
1745
1598
  running the gate instead of by rendering seven variants.
1746
1599
 
1747
- 🆕 **And one thing it stopped doing, on 2026-09-05
1748
- ([#401](https://github.com/firejune/rigc/issues/401)).** Build (b) is refused
1600
+ **And one thing it does not refuse.** Build (b) is refused
1749
1601
  because the head is *drawn* while it folds. Fade that slot to alpha exactly 0 over
1750
1602
  the same keys — §4.2's fourth way out, and what a face past its ceiling actually
1751
1603
  does — and the same build is green, with the key still measured and the reason
@@ -1760,14 +1612,13 @@ printed rather than passed over in silence:
1760
1612
  ```
1761
1613
 
1762
1614
  `A39`'s message says the mesh "draws its texture backwards there", and that
1763
- sentence is false when the slot draws nothing — the assertion was not too strict,
1764
- it was measuring something other than what it said. The bar is **alpha exactly
1765
- 0**; at 0.5 build (b) is refused as before, with the alpha in the message. The
1766
- same fold at full alpha in another animation is still refused, because the
1615
+ sentence is false when the slot draws nothing. The bar is **alpha exactly
1616
+ 0**; at 0.5 build (b) is refused, with the alpha in the message. The
1617
+ same fold at full alpha in another animation is refused, because the
1767
1618
  measurement is of one key at one time.
1768
1619
 
1769
- 🚨 **And the limit of that, measured rather than assumed: the gate read KEYS, and
1770
- the geometry between two keys is interpolated.** Put the alpha-0 key exactly on
1620
+ 🚨 **And a key is not the whole of it: the geometry between two keys is
1621
+ interpolated.** Put the alpha-0 key exactly on
1771
1622
  the 40° key and 8 triangles are already reversed at `t=0.4`, where the slot is
1772
1623
  still drawing at **alpha 0.20**:
1773
1624
 
@@ -1781,13 +1632,9 @@ still drawing at **alpha 0.20**:
1781
1632
 
1782
1633
  📌 The table and the refusal below are on the **turn probe** — this head's own
1783
1634
  five columns and 32 triangles on a one-second timeline, which is why the times
1784
- are not build (b)'s. It is `selftest.ts`'s fixture, so the rows are reproduced by
1785
- every run rather than transcribed once.
1635
+ are not build (b)'s.
1786
1636
 
1787
- 🆕 **Closed on 2026-09-05
1788
- ([#403](https://github.com/firejune/rigc/issues/403)), and the shape of the fix
1789
- is the reason it is here rather than in a paragraph of advice.** Every interval
1790
- between two consecutive deform keys is scanned. A deform interpolated between two
1637
+ **Every interval between two consecutive deform keys is scanned.** A deform interpolated between two
1791
1638
  keys travels a **straight line through offset space**, so a triangle's signed
1792
1639
  area is a *quadratic in the interpolation fraction* — the fold is a root of it,
1793
1640
  solved for rather than searched, with no sample spacing anybody would have to
@@ -1799,15 +1646,14 @@ and how far along the segment that is, the reversed triangles with their signed
1799
1646
  `NO KEY LANDS THERE` in those words, and the alpha read at that same instant — so what
1800
1647
  it refuses is legible as a frame rather than as a key. AUTHORING §4.11.3 reads it field
1801
1648
  by field and [`src/validate.ts`](../src/validate.ts) builds it; no spec this repository
1802
- ships produces one, the turn probe being `selftest.ts`'s own.
1649
+ ships produces one.
1803
1650
 
1804
- ⇒ The rule this section gave — *fade out over the run up to the angle you cannot
1805
- take, so that every key past the ceiling is one that draws nothing* — is
1806
- unchanged. What changed is that it is now a **measurement**: land the alpha-0 key
1651
+ ⇒ The rule — *fade out over the run up to the angle you cannot
1652
+ take, so that every key past the ceiling is one that draws nothing* — is a
1653
+ **measurement**: land the alpha-0 key
1807
1654
  on the fold and the build is refused, with the frame it is refused for.
1808
1655
 
1809
- ⚠️ **What that scan still cannot see**, stated because the limit moved rather
1810
- than vanished: the closed form holds the BONES still across the span. On an
1656
+ ⚠️ **What that scan cannot see**: the closed form holds the BONES still across the span. On an
1811
1657
  unweighted attachment that is exact — one matrix multiplies every vertex and its
1812
1658
  determinant cancels out of the sign comparison — but on a weighted mesh whose
1813
1659
  bones move across the span it is an approximation, and a prediction no
@@ -1817,7 +1663,7 @@ refused. A fold caused by the bones alone is not this rule's subject at all, and
1817
1663
 
1818
1664
  ⚠️ **One thing it deliberately does not do.** It is an **archetype** rule, so a
1819
1665
  `--profile spine` build reads `PROF` — the premise "a fold has no legitimate
1820
- counter-example" turned out to be false when it was measured, an official
1666
+ counter-example" is false: an official
1821
1667
  `spineboy-pro` export reverses one of `hoverboard-board`'s 101 triangles, and a
1822
1668
  `validity` rule would have told its author to change correct data. ⇒ `explain`'s
1823
1669
  `DEFORM` block is the surface with **no profile at all**, so the winding count is
@@ -1838,15 +1684,15 @@ bun cli.ts check --candidate /tmp/swapped --frames gallery/portrait/re
1838
1684
  bun cli.ts check --candidate /tmp/folded --frames gallery/portrait/render/turn@25fps
1839
1685
  ```
1840
1686
 
1841
- ⚠️ **The third of those needs a build §9.2's own command will not write.** Since
1842
- `A39` shipped, build (b) under `--profile spine-html` is refused and nothing
1687
+ ⚠️ **The third of those needs a build §9.2's own command will not write.** Build (b)
1688
+ under `--profile spine-html` is refused and nothing
1843
1689
  lands in `/tmp/folded` — which is the row above it in §9.2's table, working. Take
1844
1690
  that candidate from the **default** profile, where `A39` reads `PROF` and the
1845
1691
  artifact is written — a profile selects which assertions apply and not what is
1846
1692
  emitted, and on this rig the two profiles write a `skeleton.json` and a
1847
1693
  `skeleton.atlas` that are identical byte for byte.
1848
1694
 
1849
- **No run reproduces this:** the three commands above read a build directory and two `/tmp` paths this repository does not track, so no gate reaches them; re-taken by hand on 2026-09-18 against a render of the good build made the same day
1695
+ **No run reproduces this:** the three commands above read a build directory and two `/tmp` paths this repository does not track, so no gate reaches them; re-taken by hand against a render of the good build
1850
1696
 
1851
1697
  | Candidate | MAE mean | worst | at |
1852
1698
  | --- | --- | --- | --- |
@@ -1884,15 +1730,14 @@ block as it survived `A39`.
1884
1730
  📌 **`explain`'s `MEMBER` block (§3.1, AUTHORING §4.5.2) does the same for the
1885
1731
  bone half, and it takes the **nose test** off the procedure below.** §3 makes the
1886
1732
  nose the diagnostic — *if the nose's residual is not negative, the depths are
1887
- wrong* — and that used to be arithmetic a reader had to redo from a README's
1888
- depth table. The block prints the six residuals in a column with the depth that
1733
+ wrong* — and the block prints the six residuals in a column with the depth that
1889
1734
  produced each one, so the check is reading one sign. ⚠️ It still cannot say
1890
1735
  whether the **depth** was right: `nose at depth 192` evaluates as consistently
1891
1736
  wrong as it does right, and no reference frame separates a plausible depth table
1892
1737
  from the intended one.
1893
1738
 
1894
- ⇒ **So the honest procedure — thinner now that `A39` and the two report blocks
1895
- exist, and still a procedure, because none of the three limits above is one they
1739
+ ⇒ **So the honest procedure — thinner for `A39` and the two report blocks, and
1740
+ still a procedure, because none of the three limits above is one they
1896
1741
  lift:** state the model on the key rather than deriving a table (§1.1 for the
1897
1742
  mesh, §3.1 for the bones), so what a reviewer reads is a radius, an angle and a
1898
1743
  depth per part; read the **nose's sign** off the `MEMBER` block and check the
@@ -1901,13 +1746,11 @@ still yours, and a stated model evaluates a wrong radius as consistently as a
1901
1746
  right one — then render, **look at three scales**, and keep a render of the last
1902
1747
  build you trusted so `check` has something to be differential against.
1903
1748
 
1904
- 📌 **What §1.1 moved, what §9.2's block moved, and what neither did.** The
1905
- transcription is gone and `explain` prints the model beside the offsets it
1906
- produced, so *what a key claims* is readable; the `DEFORM` block prints what the
1749
+ 📌 **What §1.1 and §9.2's block make readable, and what neither does.**
1750
+ `explain` prints the model beside the offsets it produced, so *what a key claims* is readable; the `DEFORM` block prints what the
1907
1751
  key **did** — the area and stretch extremes, the displacement and the winding —
1908
1752
  so *what the claim came to* is readable too, per key and with no reference
1909
- ([#316](https://github.com/firejune/rigc/issues/316), landed 2026-09-03; `A39`
1910
- catches the fold inside it). *Whether the claim is right* is unchanged: **nothing
1753
+ (`A39` catches the fold inside it). *Whether the claim is right* is not: **nothing
1911
1754
  measures whether 12° was the angle the shot wanted**, and nothing above is a
1912
1755
  substitute for looking at three scales.
1913
1756
 
@@ -1975,24 +1818,22 @@ the report names what the profile leaves out rather than this page counting it.
1975
1818
  there; `A13_MESH_BUDGET` and `A15_IDLE_NO_MESH_BONE_KEYS` are among the rules
1976
1819
  the default profile excludes.
1977
1820
 
1978
- **No run reproduces this:** the three rows below come from commands that read the build directory this repository does not track, so no gate reaches them; re-taken by hand on 2026-09-18 from a clean `gallery/portrait`
1821
+ **No run reproduces this:** the three rows below come from commands that read the build directory this repository does not track, so no gate reaches them; re-taken by hand from a clean `gallery/portrait`
1979
1822
 
1980
1823
  | Command | What came back |
1981
1824
  | --- | --- |
1982
1825
  | `render --fps 25 --max 640` | **81 + 39 + 56 frames**, 478×640, three contact sheets |
1983
1826
  | `loop_seam.ts` ×3 | **0 / 255**, **0 of 305 920 pixels** differing, for all three |
1984
- | `preview` | one **414.5 KiB** HTML file, 22 pages embedded as data URIs — the figure the tool prints, where this row used to round it to 415 |
1827
+ | `preview` | one **414.5 KiB** HTML file, 22 pages embedded as data URIs — the figure the tool prints |
1985
1828
 
1986
1829
  📊 **Figures this page took from the record rather than re-deriving**, because
1987
1830
  they need the artifact's own pixels: the blink's occlusion (hiding the whole eye
1988
1831
  assembly at the shut hold changes **0 of 305 920** pixels; positive control at
1989
1832
  rest moves **8 183**), the per-edge narrowing displacements, the `spine-core`
1990
1833
  agreement of every posed column and scale with §1's line to **under 0.001 px**,
1991
- and the Web Player interop pass (**0 console errors, 0 page exceptions**). Note
1992
- what the first of those cost, and what it costs now:
1993
- [#293](https://github.com/firejune/rigc/issues/293) closed **2026-09-03**, so
1994
- `setup: { "slot": null }` no longer crashes with a raw `TypeError` — it is a
1995
- named `rigc compile error` that gives the spelling. Measured on a copy of this
1834
+ and the Web Player interop pass (**0 console errors, 0 page exceptions**).
1835
+ `setup: { "slot": null }`, the obvious way to hide one slot, is a named
1836
+ `rigc compile error` that gives the spelling. Measured on a copy of this
1996
1837
  example's `motion.json` carrying one such entry, with the path the run echoes
1997
1838
  shortened and the line wrapped:
1998
1839
 
@@ -2003,13 +1844,11 @@ nothing there write `"eye_l": { "attachment": null }`, and to show an attachment
2003
1844
  write `"eye_l": { "attachment": "<name>" }`
2004
1845
  ```
2005
1846
 
2006
- ⚠️ **The throwaway script did not become unnecessary here, and the second
2007
- refusal says why.** Writing the spelling that message names on a slot this rig
1847
+ ⚠️ **And the second refusal says where that edit goes.** Writing the spelling that message names on a slot this rig
2008
1848
  gives an attachment to is refused in turn — `slot "eye_l" has a setup attachment
2009
1849
  in the rig spec AND in the motion spec; the setup pose has one author` — so on
2010
- the worked example hiding one slot is still an edit to the **rig** spec rather
2011
- than a line in the motion spec. What changed is that both of those are now
2012
- refusals with a name and a fix in them, where one used to be a stack trace.
1850
+ the worked example hiding one slot is an edit to the **rig** spec rather
1851
+ than a line in the motion spec.
2013
1852
 
2014
1853
  ⭐ **Vela is a second cast member and that was deliberate**, against the gallery's
2015
1854
  own rule that its examples share one drawing. A 2.5D turn reads off four things: a
@@ -2025,16 +1864,14 @@ mesh is built**, and that is worth saying to the user before you build it.
2025
1864
 
2026
1865
  ## 11. Non-goals — stated, so nobody proposes them as gaps
2027
1866
 
2028
- 🚫 **No command generates a turn, and neither construct that shipped is one.**
1867
+ 🚫 **No command generates a turn, and neither model construct is one.**
2029
1868
  §1 is one line of arithmetic; a `rigc yaw --degrees 12` would be guessing at
2030
1869
  every depth in §2 on the user's behalf, and depth is the parameter the *author*
2031
- is choosing. [#294](https://github.com/firejune/rigc/issues/294) and
2032
- [#295](https://github.com/firejune/rigc/issues/295) both shipped as the other
2033
- thing — **a way to say the model in the spec** (§1.1 for the mesh, §3.1 for the
2034
- bones) — so the radius, the angle and **every depth** still arrive from the
2035
- author and the compiler only evaluates. Neither one generates an in-between
1870
+ is choosing. Both constructs are the other thing — **a way to say the model in
1871
+ the spec** (§1.1 for the mesh, §3.1 for the bones) — so the radius, the angle and
1872
+ **every depth** arrive from the author and the compiler only evaluates. Neither one generates an in-between
2036
1873
  either: a model is evaluated at one key, and sweeping an angle is editing one
2037
- number per key. What the toolchain owes is unchanged: that the file is checkable,
1874
+ number per key. What the toolchain owes is that the file is checkable,
2038
1875
  that you can look, and that a person can choose.
2039
1876
 
2040
1877
  🚫 **No pass bar for a face, and nothing here to hang one on.** MOTION.md's
@@ -2051,11 +1888,9 @@ the entire point of that section.
2051
1888
  ⚠️ **Not a Live2D comparison, and not a recommendation between formats.** What
2052
1889
  the worked example measured is that a portrait turn is authorable on plain Spine
2053
1890
  4.3 at draft quality — nothing outside the format, no plugin, no runtime patch —
2054
- and that the **split is authoring cost rather than runtime capability**. Both
2055
- halves of the transcription cost are now paid — the deform table via
2056
- [#294](https://github.com/firejune/rigc/issues/294) and the track table via
2057
- [#295](https://github.com/firejune/rigc/issues/295) — which moves the remaining
2058
- cost off the keyboard and onto the **parts**: per-eye meshes, a meshed neck, a
1891
+ and that the **split is authoring cost rather than runtime capability**. Neither
1892
+ the deform table nor the track table is transcribed (§1.1, §3.1), so the
1893
+ remaining cost is not on the keyboard but on the **parts**: per-eye meshes, a meshed neck, a
2059
1894
  second art layer for the far cheek (§8). Whether to pay *that* is a project's
2060
1895
  decision and this page does not make it.
2061
1896
 
@@ -2064,8 +1899,7 @@ rather than an unbuilt feature, and it runs in both directions.** rigc's inputs
2064
1899
  are a rig spec and a motion spec; its outputs are Spine 4.3 skeleton data and an
2065
1900
  atlas. There is no importer, no exporter and no converter for `.moc3`, `.cmo3`,
2066
1901
  `.model3.json` or anything else in that family, and nothing in this repository
2067
- claims compatibility with that format in either direction
2068
- ([#399](https://github.com/firejune/rigc/issues/399) is where that was settled).
1902
+ claims compatibility with that format in either direction.
2069
1903
  ⭐ **What is in scope is an authoring idea, stated on its own terms rather than
2070
1904
  as anybody's feature: that a face angle can be a value rather than a time.**
2071
1905
  §8's *The turn as a value rather than a time* is that idea on Spine's own