spine-rigc 0.20.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/AUTHORING.md CHANGED
@@ -773,8 +773,13 @@ between two things it has in front of it: the emitted triangles, and the PNG the
773
773
  attachment names with `image`. So any mesh that names one gets the figure on its
774
774
  `MESH` line, authored or generated:
775
775
 
776
+ ```bash
777
+ bun cli.ts build --rig gallery/squash/rig.json \
778
+ --motion gallery/squash/motion.json --out /tmp/squash
779
+ ```
780
+
776
781
  ```
777
- MESH ball authored 9 vertices / 8 triangles (budget 8) bones=[ball] attachments=[ball] covers 94.31% of the art, reaching 2.50px past it
782
+ MESH ball authored 9 vertices / 8 triangles (budget 8) bones=[ball] attachments=[ball] covers 100.00% of the art, reaching 15.00px past it
778
783
  ```
779
784
 
780
785
  **A number, not a bar.** A `contour` under 99.5% is *refused* because rigc
@@ -782,11 +787,17 @@ generated that geometry as a claim about the art; an authored mesh that sits ins
782
787
  its art is a legitimate thing to draw — a soft feather, a trimmed hull, a mesh
783
788
  meant to bend a core while its edges stretch — so the figure informs and the
784
789
  decision stays with the author. A mesh with no `image` reports nothing, because
785
- there is nothing to measure it against. The silence was worth closing: the line
786
- above is a round part meshed as a centre vertex plus 8 rim vertices placed on the
787
- silhouette, and an octagon's sides pass `R · cos(π/8)` from its centre, so 5.7% of
788
- the drawing — its whole ink outline, between the spokes — was not going to be
789
- drawn, and every assertion passed (issue #277).
790
+ there is nothing to measure it against.
791
+
792
+ **The silence was worth closing, and that example is where it was found.** The
793
+ ball is a centre vertex plus 8 rim vertices, and the first version placed them
794
+ *on* the silhouette — but an octagon's sides pass `R · cos(π/8)` from its centre,
795
+ so its whole ink outline between the spokes was not going to be drawn, and every
796
+ assertion passed (issue #277). That is why the command above prints 100.00%
797
+ rather than the figure it was filed over:
798
+ [`gallery/squash`](https://github.com/firejune/rigc/tree/main/gallery/squash)'s
799
+ README carries the inradius arithmetic, both coverage readings, and the rim move
800
+ that settled it.
790
801
 
791
802
  The generators are `ring`, `ribbon`, `contour` and `grid` (see
792
803
  [`src/mesh.ts`](../src/mesh.ts)); the first two encode a deformation model rather
@@ -1032,10 +1043,14 @@ triangle does not, and nothing on the first line says so.
1032
1043
  | `which is 0.049 of the range this mesh sampled` — the **same step, over the range this mesh sampled** | how much of everything the sheet said across the whole part it said across that one triangle. A form's slope is bounded, so this **halves every time you double the lattice** while the angle settles. Near 1 it is a **cliff**: a step with no slope in it, whose angle halves with the lattice instead and describes nothing at any density. Measured: `gallery/look` reads 0.112 and 0.468, a synthetic raised cosine 0.394 falling to 0.027 under refinement, the same cosine with one planted cliff a flat 0.50, and estimated depth sheets over cut-out art **0.92–0.99** |
1033
1044
  | `+none` | on the ceiling line, nothing folds on that side at all, at any angle. On the percentile line it is the same statement — there is no population, because there is nothing to take a percentile of |
1034
1045
 
1035
- A real one rather than the illustration above — `bun cli.ts build --rig
1036
- gallery/look/rig.json --motion gallery/look/motion.json --images
1037
- gallery/look/parts --out <dir>`, whose two meshes happen to print two of the
1038
- three spellings:
1046
+ A real one rather than the illustration above — `gallery/look`, whose two meshes
1047
+ happen to print two of the three spellings:
1048
+
1049
+ ```bash
1050
+ bun cli.ts build --rig gallery/look/rig.json \
1051
+ --motion gallery/look/motion.json \
1052
+ --images gallery/look/parts --out /tmp/look
1053
+ ```
1039
1054
 
1040
1055
  ```
1041
1056
  MESH head grid 189 vertices / 320 triangles (budget 320) bones=[head] attachments=[head]
@@ -2082,6 +2097,7 @@ is not the arrangement the format has. Spine keys one bone per timeline, so the
2082
2097
  six numbers of a head turn are eighty lines apart in the artifact and nobody can
2083
2098
  see a wrong sign in them.
2084
2099
 
2100
+ **No run reproduces this:** the legend and one group's record lifted out of one `explain` run, which prints the two `bone "faceshift"` records between them
2085
2101
  ```
2086
2102
  group members (the per-member values of one track, side by side — issue #295)
2087
2103
  .. a row per member and a block per key, because a wrong sign is visible in a column of six and
@@ -2661,10 +2677,15 @@ makes against them.
2661
2677
 
2662
2678
  **It is auditable.** `explain` prints the model, the scalars the closed form
2663
2679
  derived from it, and every offset it produced — the emitted ones, not a second
2664
- evaluation:
2680
+ evaluation. This is the `t=0.62` key of the spec above, whole:
2681
+
2682
+ ```bash
2683
+ bun cli.ts explain --rig gallery/portrait/rig.json \
2684
+ --motion gallery/portrait/motion.json --out /tmp/explain
2685
+ ```
2665
2686
 
2666
2687
  ```
2667
- t=0.62 deform[0..50] 25 pair(s) bezier[4]
2688
+ t=0.62 deform[0..50] 25 pair(s) stepped
2668
2689
  transform yaw radius=170 degrees=12
2669
2690
  dx = (x−about)·(cos t − 1) − z·sin t, z = √(radius² − (x−about)²)
2670
2691
  t = 0.20944 rad
@@ -2673,9 +2694,19 @@ evaluation:
2673
2694
  centre shift = −radius·sin t = -35.344987
2674
2695
  25 vertices, largest offset 35.344987px at vertex 2
2675
2696
  v 0 (-7.17493, 0) v 1 (-22.413595, 0) v 2 (-35.344987, 0) v 3 (-27.658171, 0)
2676
- …five more lines
2697
+ v 4 (-14.255108, 0) v 5 (-14.255108, 0) v 6 (-14.255108, 0) v 7 (-14.255108, 0)
2698
+ v 8 (-14.255108, 0) v 9 (-27.658171, 0) v 10 (-35.344987, 0) v 11 (-22.413595, 0)
2699
+ v 12 (-7.17493, 0) v 13 (-7.17493, 0) v 14 (-7.17493, 0) v 15 (-7.17493, 0)
2700
+ v 16 (-22.413595, 0) v 17 (-35.344987, 0) v 18 (-27.658171, 0) v 19 (-22.413595, 0)
2701
+ v 20 (-35.344987, 0) v 21 (-27.658171, 0) v 22 (-22.413595, 0) v 23 (-35.344987, 0)
2702
+ v 24 (-27.658171, 0)
2677
2703
  ```
2678
2704
 
2705
+ The curve reads `stepped` where the spec says `"ease": "swell"`, and that is
2706
+ §4.5's hold rule rather than a discrepancy: the next key emits these same 25
2707
+ offsets, so the segment between them would draw nothing and is written the way
2708
+ the editor writes it.
2709
+
2679
2710
  📌 **Float behaviour, stated.** The closed forms are evaluated in float64 and
2680
2711
  quantised to six decimals like every other emitted number, so the same spec emits
2681
2712
  the same bytes and `A18_DETERMINISTIC_EMIT` proves it on a second compile. The
@@ -2701,6 +2732,7 @@ triangles. It is a report and it never gates: `explain` takes no `--profile` and
2701
2732
  exits 0 on a rig `build` would refuse, so the figures are readable on the build
2702
2733
  that is failing.
2703
2734
 
2735
+ **No run reproduces this:** abridged — the `WORST` rollup follows key 1 here, where the run prints `head/head`'s other two keys and all four of `hair_bang/hair_bang` between them
2704
2736
  ```
2705
2737
  deform (what each key does to the geometry — figures with names, never a bar; issue #316)
2706
2738
  .. every key measured at its OWN time against the same pose with the deform CLEARED, so the
@@ -2797,8 +2829,12 @@ clothes. The quantity that does move — how much art each drawn pixel now carri
2797
2829
  📘 **[FACE.md](FACE.md) §9.2** is this block on real art, as three builds of
2798
2830
  `gallery/portrait`: the good one, one with a band inverted, and one folded. The
2799
2831
  inverted build is the case worth reading — `A39` passes it (correctly: nothing
2800
- reverses), and the block is what says `x1.362834` where the model's own table
2801
- says `x1.319121`, with no reference render anywhere.
2832
+ reverses), and the block is what says `x1.362834` on the band the model's own key
2833
+ reports as `x0.637174`, with no reference render anywhere. ⭐ The comparison is
2834
+ per **triangle** and that is what makes it a reading: the block names one beside
2835
+ every ratio, so the two blocks can be lined up band against band instead of worst
2836
+ against worst — which on this build would have paired the inverted band with an
2837
+ untouched one and called the difference mild.
2802
2838
 
2803
2839
  📘 **[`gallery/nod`](https://github.com/firejune/rigc/tree/main/gallery/nod)'s
2804
2840
  README is a second reading of the same block** (repository material, hence the
@@ -2823,15 +2859,23 @@ the frames just before it are drawn, nearly folded, and land on no key at all. O
2823
2859
  the turn probe that is **8 reversed triangles at alpha 0.20, gating green**.
2824
2860
 
2825
2861
  ⇒ `A39` now scans every interval between two consecutive deform keys as well, and
2826
- refuses one with its own sentence:
2827
-
2828
- ```
2829
- FAIL A39_DEFORM_KEEPS_TRIANGLE_WINDING: animation "turn" deform head/head BETWEEN key 0
2830
- (t=0s) and key 1 (t=0.5s), at t=0.444089s — 88.8% of the way from one to the other:
2831
- 8 of 32 triangle(s) reverse winding — triangle 0 [0,15,16] 1890.001 -> -272.314px²; …
2832
- NO KEY LANDS THERE: the runtime interpolates between the two keys, and the mesh is
2833
- inside out for part of the way, drawing its texture backwards at alpha 0.1118 …
2834
- ```
2862
+ refuses one with its own sentence. No spec this repository ships produces one — the
2863
+ rig it was written from is a probe `selftest.ts` generates and nothing else can
2864
+ invoke — so the sentence is described here rather than transcribed.
2865
+
2866
+ **What it carries**, in the order it says it: `BETWEEN key <i> (t=…s) and key <j>
2867
+ (t=…s)` where a key refusal puts one index; the time the closed form solved for, and
2868
+ how far along the segment that is — or `(a stepped segment)` instead, which
2869
+ interpolates nothing and holds the earlier key's geometry across the span; the
2870
+ reversed count out of the triangle total, with the first four named and each one's
2871
+ vertex ids and its signed area before and after; `NO KEY LANDS THERE` in those words,
2872
+ then whether the runtime interpolates across the span or holds it; the alpha read at
2873
+ that same instant, present only where it is not 1; and the ways out — for an
2874
+ interpolating span the four the table below gives, the fade one among them only
2875
+ where the alpha is not 1, and for a stepped one the key it holds instead, with
2876
+ `invariants.deformMayFold` the last resort either way.
2877
+ [`src/validate.ts`](../src/validate.ts) builds it, beside the key sentence §4.11.2
2878
+ quotes.
2835
2879
 
2836
2880
  **What to change when you see it**, in the order worth trying:
2837
2881
 
@@ -2899,7 +2943,13 @@ value = from + (time − to) / scale what A39 sets the dial to
2899
2943
 
2900
2944
  🔒 **The `DEFORM` block prints the frame on every key**, because the derivation
2901
2945
  changed and a block that went on printing the same figures under a changed meaning
2902
- would be worse than the red it replaced:
2946
+ would be worse than the red it replaced. `gallery/look`'s `turn` is the animation
2947
+ a slider applies, and this is one of its keys:
2948
+
2949
+ ```bash
2950
+ bun cli.ts explain --rig gallery/look/rig.json \
2951
+ --motion gallery/look/motion.json --out /tmp/explain-look
2952
+ ```
2903
2953
 
2904
2954
  ```
2905
2955
  DEFORM turn default/head/head key 6 t=1.900000 transform yaw depth=true degrees=19
@@ -3221,6 +3271,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
3221
3271
 
3222
3272
  The report prints one line per assertion:
3223
3273
 
3274
+ **No run reproduces this:** assembled — one line of each verdict kind; the `PASS` and the `PROF` are verbatim from a `build` this page states, the `SKIP` is cut at the ellipsis and only `--profile spine-html` prints it, and the `FAIL` is invented, because a green build prints none
3224
3275
  ```
3225
3276
  PASS A08_REGION_NAMES_MATCH_ATTACHMENTS
3226
3277
  SKIP A21_MESH_RIM_PINNED: the skeleton has no weighted mesh attachment, …
package/docs/FACE.md CHANGED
@@ -41,15 +41,18 @@ hold its results.
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
43
 
44
- 🚨 **Nothing in this toolchain measures what a `deform` key does, and that is the
45
- one gap you have to author around.** The setup geometry is measured and printed —
46
- coverage, overshoot, hole. The *deformed* geometry is not. A key that turns a
47
- mesh inside out gates green — **26 PASS on `--profile spine-html`, the same as
48
- the good build, and the same coverage line reporting the setup pose at
49
- 100.00%.** §9.2 demonstrates that with three builds and §9.3 gives you the
50
- differential audit that works today;
51
- [issue #296](https://github.com/firejune/rigc/issues/296) is the instrument that
52
- would close it. Reference it, do not wait for it.
44
+ 🚨 **A `deform` key's winding is gated; how far it moved the geometry is not, and
45
+ that half is the one you have to author around.** Since 2026-09-03
46
+ `A39_DEFORM_KEEPS_TRIANGLE_WINDING` refuses a key that turns the mesh inside out —
47
+ by name, by key and by triangle — and the build writes nothing. What no assertion
48
+ has an opinion about is **magnitude**: a band that stretches where the projection
49
+ says it should compress keeps every triangle's winding, so it gates green, beside
50
+ the same `MESH` coverage line as the good build, because coverage reports the
51
+ *setup* pose. §9.2 is that pair as three builds of one rig — the good one, one
52
+ wrong and green, one refused. For the half still ungated, `explain`'s `DEFORM`
53
+ block prints each key's area and stretch ratios per triangle with no reference
54
+ render (§9.2, AUTHORING §4.11.2), and §9.3 is the differential audit, the three
55
+ things it cannot do, and the procedure that survives them.
53
56
 
54
57
  📐 **Where the numbers on this page come from.** Every figure marked **derived**
55
58
  is re-computed from the closed form in §1 and reproduces to the digits printed.
@@ -524,6 +527,7 @@ asked for.** The `MEMBER` block (AUTHORING §4.5.2) is a row per member — the
524
527
  emitted value, the `x` it read off the rig, and the depth the spec stated — so the
525
528
  nose diagnostic below is now a line you read rather than arithmetic you redo:
526
529
 
530
+ **No run reproduces this:** abridged — the five derivation lines the run prints between this record's head and its rows are cut; AUTHORING §4.5.2 quotes them
527
531
  ```
528
532
  MEMBER turn group "features".translatex t=0.620000 6 member(s) derive yaw degrees=12 carried=170 -> the displacement
529
533
  eye_l 5.513083 <- -62 at depth 150
@@ -746,8 +750,7 @@ shows the listing beside the order.
746
750
  over an oval face has transparent corners, and the line says so:
747
751
 
748
752
  ```
749
- MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head]
750
- attachments=[head] covers 100.00% of the art, reaching 95.90px past it
753
+ MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
751
754
  ```
752
755
 
753
756
  **`covers 100.00%` is what matters** — nothing of the drawing is outside the
@@ -1105,7 +1108,7 @@ problem — two eyes whose upper lash is heaviest at the **inner** corner read a
1105
1108
  angry whatever the brow above them does. Worth knowing before spending a pass on
1106
1109
  the wrong part.
1107
1110
 
1108
- ### 9.2 🚨 What nothing measures — three builds, all green
1111
+ ### 9.2 🚨 The half nothing measures — three builds, one of them refused
1109
1112
 
1110
1113
  **The setup geometry is measured; the deformed geometry was not, and one half of
1111
1114
  it still is not.** Here is that
@@ -1123,11 +1126,15 @@ bun cli.ts render --candidate gallery/portrait/build --fps 25 --max 640 \
1123
1126
  ```
1124
1127
 
1125
1128
  ```
1126
- MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head]
1127
- attachments=[head] covers 100.00% of the art, reaching 95.90px past it
1128
- … 26 PASS, 13 SKIP
1129
+ MESH head authored 25 vertices / 32 triangles (budget 32) bones=[head] attachments=[head] covers 100.00% of the art, reaching 95.90px past it
1129
1130
  ```
1130
1131
 
1132
+ Green, and the tally is left to the tool: `bun run selftest` prints this build's
1133
+ live assertion and skip counts on its `GALLERY_EXAMPLE_IS_GREEN[portrait/spine-html]`
1134
+ line. One written here would be a figure nothing in the tree compares against a
1135
+ run, sitting inside a fence that reads as a transcript — and rigc prints no tally
1136
+ line, so it never was one.
1137
+
1131
1138
  Now break the projection two ways. Both scripts write a variant motion spec
1132
1139
  beside the originals and touch nothing in the repository:
1133
1140
 
@@ -1137,14 +1144,21 @@ beside the originals and touch nothing in the repository:
1137
1144
  # the head reads as turning the other way at its own edge. This one has to
1138
1145
  # REPLACE the transform with a table: an inverted band is not the closed
1139
1146
  # form at any angle, and §1.1 refuses a `transform` beside a `vertices` run.
1147
+ # Each vertex takes the shift for THE COLUMN IT IS IN, looked up in the rig:
1148
+ # a `vertices` run is positional, so a script that assumes the list's order
1149
+ # rather than reading it breaks in silence the day the list is renumbered.
1140
1150
  bun -e '
1151
+ const r = await Bun.file("gallery/portrait/rig.json").json();
1141
1152
  const m = await Bun.file("gallery/portrait/motion.json").json();
1153
+ const v = r.skins.default.head.head.vertices;
1142
1154
  const d = m.animations.turn.deform.find(x => x.slot === "head");
1143
- const row = [-22.414, 0, -7.175, 0, -35.345, 0, -27.658, 0, -14.255, 0];
1155
+ const shift = {"-162": -22.414, "-120": -7.175, "0": -35.345, "120": -27.658, "162": -14.255};
1156
+ const run = [];
1157
+ for (let i = 0; i < v.length / 2; i++) run.push(shift[v[i * 2]], 0);
1144
1158
  for (const k of d.keys) if (k.transform) {
1145
1159
  delete k.transform;
1146
1160
  k.fromVertex = 0;
1147
- k.vertices = [...row, ...row, ...row, ...row, ...row];
1161
+ k.vertices = run;
1148
1162
  }
1149
1163
  await Bun.write("/tmp/swapped.motion.json", JSON.stringify(m, null, 2));
1150
1164
  '
@@ -1172,15 +1186,33 @@ is a doc command silently passing rather than silently failing, which is the
1172
1186
  worse of the two: build (b) reported `A39 PASS` and the table below said `FAIL`.
1173
1187
  Both are re-run above.
1174
1188
 
1189
+ ⚠️ **And script (a) broke a second time, the same way, on 2026-09-04.** It wrote
1190
+ one row of five shifts and repeated it five times, which was the mesh's own
1191
+ column order while the vertex list was row-major.
1192
+ [#375](https://github.com/firejune/rigc/issues/375) renumbered both gallery
1193
+ grids along their outline walk — perimeter first, then the interior, the order
1194
+ Spine's `hull` needs — and the row went on landing at the same *positions* in a
1195
+ list that no longer meant columns. The build stayed green and stayed wrong, so
1196
+ nothing on the page moved; what it stopped being was **an inverted band**, which
1197
+ is the one thing the table below reads it as. The figures in that table were
1198
+ right the whole time and the command under them had stopped producing
1199
+ them — a stale figure shows up at one site, and a broken command shows up at
1200
+ every figure it feeds, which is how the two are told apart: this one also
1201
+ contradicted §9.3's `check` row, `0.33 / 0.61` against a table saying
1202
+ `0.20 / 0.38`. ⭐ **The repair is the doctrine's own**: the script resolves each
1203
+ shift through the vertex's coordinate instead of its index, so the next
1204
+ renumbering cannot move it. A `vertices` run is positional by format — that is
1205
+ `fromVertex`'s whole job — and a *generator* of one has no reason to be.
1206
+
1175
1207
  **What comes back from both:**
1176
1208
 
1177
1209
  | | good | (a) one band inverted | (b) mesh folded |
1178
1210
  | --- | --- | --- | --- |
1179
- | `--profile spine-html`, **before `A39`** | 26 PASS / 13 SKIP | **26 PASS / 13 SKIP** | **26 PASS / 13 SKIP** |
1211
+ | `--profile spine-html`, **before `A39`** | green | **green, and the same counts** | **green, and the same counts** |
1180
1212
  | `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | PASS | **PASS** | **PASS** |
1181
1213
  | the `MESH` coverage line | 100.00%, 95.90px past | **byte-identical** | **byte-identical** |
1182
1214
  | 🆕 `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | PASS | PASS | **FAIL, both keys, 8 of 32 triangles** |
1183
- | `--profile spine-html`, **today** | 27 PASS / 13 SKIP | 27 PASS / 13 SKIP | **26 PASS / 13 SKIP / 2 FAIL** |
1215
+ | `--profile spine-html`, **today** | green | green | **refused, and nothing written** |
1184
1216
  | 🆕 the `DEFORM` block, `head` key 1 `area` | x0.637174 … x1.319122 | **x0.765250 … x1.362834** | **x−0.288121 … x1.820211** |
1185
1217
  | 🆕 … and its `winding` | 32 of 32 kept | 32 of 32 kept | **24 of 32 kept** |
1186
1218
 
@@ -1193,22 +1225,46 @@ it mean anything.
1193
1225
 
1194
1226
  ⭐ **The two `DEFORM` rows are the ones that separate (a) from the good build**,
1195
1227
  and nothing else in the toolchain does that without a reference render. `A39` is
1196
- right to pass (a) — no triangle reverses — and the block prints **x1.362834**
1197
- where the model's own table says x1.319121, and **x0.765250** where it says
1198
- x0.637174. That is this section's own prose, *"stretches 1.363 where it should
1199
- compress to 0.637"*, as a figure the tool produces.
1228
+ right to pass (a) — no triangle reverses — so the whole of the difference is a
1229
+ pair of ratios, and the way to read them is to ask the tool rather than to copy
1230
+ them down:
1200
1231
 
1201
- `rigc explain` is the instrument that prints every other timeline's actual values,
1202
- and on a deform it used to print the shape of the run rather than the run:
1232
+ ```bash
1233
+ bun cli.ts explain --rig gallery/portrait/rig.json \
1234
+ --motion /tmp/swapped.motion.json --out /tmp/explain-swapped
1235
+ ```
1203
1236
 
1204
1237
  ```
1205
- default/head/head.deform 4 key(s)
1206
- t=0 back to the setup pose bezier[4]
1207
- t=0.62 deform[0..50] 25 pair(s) bezier[4]
1208
- t=1.5 deform[0..50] 25 pair(s) bezier[4]
1209
- t=2.2 back to the setup pose linear
1238
+ DEFORM turn default/head/head key 1 t=0.620000 authored table
1239
+ frame played on a track
1240
+ moved 25 of 25 vertices, worst 35.3450px at v10
1241
+ area min x0.765250 tri 19 max x1.362834 tri 1 (32 triangles, 0 with no area at the cleared pose, band 0.146694px²)
1242
+ stretch max x1.362834 tri 1 min x0.765250 tri 26
1243
+ winding 32 of 32 kept, 0 collapsed
1210
1244
  ```
1211
1245
 
1246
+ 🚨 **Read it band by band, because the extremes have swapped ends and comparing
1247
+ worst to worst hides that.** The block names the triangle beside every ratio, and
1248
+ §4.1's table says what each band should be. `tri 1` spans **−162 → −120**, the
1249
+ band §4.1 puts at **0.637** and the good build's own block reports as
1250
+ `x0.637174 tri 17` — the same band, and here it comes back at **x1.362834**. That
1251
+ is this section's prose, *"stretches 1.363 where it should compress to 0.637"*, as
1252
+ a figure the tool produces, and it is the far edge turning the wrong way. `tri 19`
1253
+ spans **−120 → 0**, tabled at 0.892 and measured at **x0.765250**: the swapped
1254
+ column is the boundary between those two bands, so the compression the far band
1255
+ gave up lands next door. ⚠️ Read as min-against-min and max-against-max instead,
1256
+ the very same four figures say 1.319 → 1.363 and 0.637 → 0.765 — two comparisons
1257
+ across *different* bands, both of them mild, neither of them what happened. Only
1258
+ the two bands the swapped columns bound move at all: the 0 → 120 and 120 → 162
1259
+ 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:
1260
+ the model is gone, so nothing is left to check the ratios against but the ratios.
1261
+
1262
+ `rigc explain` is the instrument that prints every other timeline's actual values,
1263
+ and on a deform it used to print the shape of the run rather than the run: the
1264
+ head's four keys came back as four lines, each giving a time, a curve kind, and
1265
+ either `back to the setup pose` or `deform[0..50] 25 pair(s)` — the extent of the
1266
+ run and how many pairs are in it, and not one of the numbers.
1267
+
1212
1268
  ⇒ **`25 pair(s)` was the whole of what `explain` would tell you about a face
1213
1269
  turn**, against a scalar track two lines up in the same report printing
1214
1270
  `value=-35.345`. That asymmetry is
@@ -1224,6 +1280,7 @@ bun cli.ts explain --rig gallery/portrait/rig.json \
1224
1280
 
1225
1281
  ```
1226
1282
  DEFORM turn default/head/head key 1 t=0.620000 transform yaw radius=170 degrees=12
1283
+ frame played on a track
1227
1284
  moved 25 of 25 vertices, worst 35.3450px at v2
1228
1285
  area min x0.637174 tri 17 max x1.319122 tri 31 (32 triangles, 0 with no area at the cleared pose, band 0.146694px²)
1229
1286
  stretch max x1.319121 tri 22 min x0.637175 tri 8
@@ -1299,13 +1356,13 @@ area is a *quadratic in the interpolation fraction* — the fold is a root of it
1299
1356
  solved for rather than searched, with no sample spacing anybody would have to
1300
1357
  defend. What that arithmetic names is then posed and measured by the same code
1301
1358
  that measures a key, **alpha read at that same instant**, so the fade a correct
1302
- rig relies on is not refused and the frames it does not cover are:
1303
-
1304
- ```
1305
- FAIL A39_DEFORM_KEEPS_TRIANGLE_WINDING: animation "turn" deform head/head BETWEEN key 0
1306
- (t=0s) and key 1 (t=0.5s), at t=0.444089s — 88.8% of the way from one to the other:
1307
- 8 of 32 triangle(s) reverse winding … NO KEY LANDS THERE … at alpha 0.1118
1308
- ```
1359
+ rig relies on is not refused and the frames it does not cover are. The sentence names
1360
+ the two keys the fold lies between rather than one key index, the time it solved for
1361
+ and how far along the segment that is, the reversed triangles with their signed areas,
1362
+ `NO KEY LANDS THERE` in those words, and the alpha read at that same instant — so what
1363
+ it refuses is legible as a frame rather than as a key. AUTHORING §4.11.3 reads it field
1364
+ by field and [`src/validate.ts`](../src/validate.ts) builds it; no spec this repository
1365
+ ships produces one, the turn probe being `selftest.ts`'s own.
1309
1366
 
1310
1367
  ⇒ The rule this section gave — *fade out over the run up to the angle you cannot
1311
1368
  take, so that every key past the ceiling is one that draws nothing* — is
@@ -1371,9 +1428,9 @@ to call an audit:**
1371
1428
 
1372
1429
  📌 **`explain`'s `DEFORM` block (§9.2, AUTHORING §4.11.2) takes half of limits 1
1373
1430
  and 2 away, and none of limit 3.** It is reference-free, so it says something
1374
- about a first authoring; and it is *per key*, so the band inversion above reads
1375
- as `x1.362834` beside a model stating x1.319121 rather than as 0.20 of 255 in an
1376
- aggregate. What it still cannot say is whether **12° was the angle the shot
1431
+ about a first authoring; and it is *per key and per triangle*, so the band
1432
+ inversion above reads as `x1.362834` on `tri 1` where the same band is `x0.637174`
1433
+ in the model, rather than as 0.20 of 255 in an aggregate. What it still cannot say is whether **12° was the angle the shot
1377
1434
  wanted** — that needs the picture, which is why the procedure below survives the
1378
1435
  block as it survived `A39`.
1379
1436
 
@@ -1458,8 +1515,8 @@ with no `build/`, `render/` or `preview.html` in that directory:
1458
1515
 
1459
1516
  | Command | What came back |
1460
1517
  | --- | --- |
1461
- | `build --profile spine` | green — **18 PASS, 7 SKIP**, 14 excluded by profile |
1462
- | `build --profile spine-html` | green — **26 PASS, 13 SKIP**, including `A13_MESH_BUDGET` and `A15_IDLE_NO_MESH_BONE_KEYS` |
1518
+ | `build --profile spine` | green, and the report names what the profile leaves out rather than this page counting it: `profile spine — 7 renderer-policy and 8 archetype assertion(s) do not apply` |
1519
+ | `build --profile spine-html` | green — `profile spine-html — every assertion applies`, `A13_MESH_BUDGET` and `A15_IDLE_NO_MESH_BONE_KEYS` among the ones the row above excludes |
1463
1520
  | both `MESH` lines | `head` **100.00%** covered, reaching 95.90px past the art; `hair_bang` 100.00%, 55.22px |
1464
1521
  | `render --fps 25 --max 640` | **81 + 39 + 56 frames**, 478×640, three contact sheets |
1465
1522
  | `loop_seam.ts` ×3 | **0 / 255**, **0 of 305 920 pixels** differing, for all three |
package/docs/INGEST.md CHANGED
@@ -607,14 +607,11 @@ rigc validate examples/spineboy/export/spineboy-pro.json \
607
607
  rigc: green
608
608
  ```
609
609
 
610
- **Why it is worth a section anyway.** That line used to be two `FAIL`s:
611
-
612
- ```
613
- FAIL A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: … key 1: offset 1 is odd, so the run's x values land on y slots and back again
614
- FAIL A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: … key 1: the run holds 147 numbers and the deform array is x, y pairs
615
- ```
616
-
617
- and nothing was wrong with the data. `hoverboard-board` is an unweighted mesh with 148
610
+ **Why it is worth a section anyway.** That line used to be two `FAIL`s on the same key —
611
+ one for an **odd `offset`**, on the reading that the run's x values would land on y slots
612
+ and back again, and one for an **odd-length run**, on the reading that the deform array is
613
+ x, y pairs — and nothing was wrong with the data, so neither sentence exists in the tool
614
+ any more. `hoverboard-board` is an unweighted mesh with 148
618
615
  floats; the key carries `offset: 1` and 147 values, covering `1..148` — the whole array
619
616
  minus a leading zero the editor trimmed. A trim can land on a y component, so an odd
620
617
  offset is what a trimmed run looks like, and Spine's own parser copies the run in at the
@@ -846,13 +843,12 @@ rig spec; the discipline is in what you check afterwards.
846
843
  `parent`, a slot's `bone`, a constraint's `bones` and `target`, a draw-order key's
847
844
  `slot`, an authored mesh's vertex `weights` — and, across the two files, the motion
848
845
  spec's `archetype` against the rig spec's `name`. That last one is the first refusal a
849
- rename produces, before anything else has a chance to go wrong:
850
-
851
- ```
852
- rigc compile error: …/work/renamed/pendulum-rig.motion.json: motion spec names
853
- archetype "3-timing-and-spacing-ess" but the rig spec at
854
- …/work/renamed/pendulum-rig.rig.json is called "pendulum-rig"
855
- ```
846
+ rename produces, before anything else has a chance to go wrong: a `rigc compile error`
847
+ naming the motion spec's path, the `archetype` that spec states, the path of the rig spec
848
+ it was handed, and the `name` that rig actually carries — both sides of the mismatch in
849
+ one sentence. [`src/compile.ts`](../src/compile.ts) builds it; the renamed copies this
850
+ section works on are not committed, for the reason the Appendix gives, so the figure is
851
+ described here rather than transcribed off one.
856
852
 
857
853
  ⭐ **A rename is therefore mostly safe by construction, and its failures arrive as
858
854
  sentences naming both sides.** That is the reason to do it in the specs rather than in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-rigc",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/diff.ts CHANGED
@@ -685,10 +685,20 @@ function diffAttachments(c: Json, r: Json): DiffSection {
685
685
  // The issue's diagnosis was that Spine's exporter omits `width`/`height`
686
686
  // when they match the atlas region, so a rigc rig — which always states
687
687
  // them (AUTHORING R1/R5) — could never agree. That is not what the corpus
688
- // says: all twelve reference exports state a size on every one of their 168
689
- // regions, so there is nothing for an atlas lookup to resolve and the
690
- // `--atlas` plumbing the issue proposed would be dead code against every
691
- // rung on the ladder.
688
+ // says: all twelve reference exports state a size on every one of their
689
+ // regions, and no region omits either field — so there is nothing for an
690
+ // atlas lookup to resolve and the `--atlas` plumbing the issue proposed
691
+ // would be dead code against every rung on the ladder.
692
+ //
693
+ // ⛔ No count on that claim, deliberately, and this was the fourth copy of
694
+ // the one issue #490 struck off `docs/LADDER.md`: nothing derives the
695
+ // figure — not the plainest reading, every `region` attachment under
696
+ // `skins` in the twelve `export/*.json` skeletons, and not counting by
697
+ // skin, by slot, by attachment name, by atlas path or by atlas region. The
698
+ // claim carries the argument without one, because it is universal and a
699
+ // single counterexample refutes it: after `bun run fetch-examples`, read
700
+ // the region attachments of those twelve files and check that each states
701
+ // both `width` and `height`.
692
702
  //
693
703
  // What the measure actually reported was the naming gap, a third time. It
694
704
  // was keyed by `skin/slot/attachment`, so it could never exceed the name