spine-rigc 0.33.1 → 0.34.0
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/cli.ts +31 -7
- package/docs/AUTHORING.md +137 -65
- package/docs/FACE.md +8 -7
- package/docs/INGEST.md +17 -6
- package/package.json +1 -1
- package/src/chainfit.ts +64 -12
- package/src/compile.ts +432 -182
- package/src/deformgen.ts +14 -12
- package/src/diff.ts +24 -10
- package/src/mesh.ts +6 -1
- package/src/pose.ts +147 -35
- package/src/timelines.ts +37 -20
- package/src/trackgen.ts +3 -2
- package/src/transform.ts +18 -10
- package/src/types.ts +37 -2
- package/src/validate.ts +56 -44
package/cli.ts
CHANGED
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
type AtlasRegion,
|
|
87
87
|
} from './src/atlas.ts';
|
|
88
88
|
import { parseJsonWithPosition } from './src/json-position.ts';
|
|
89
|
-
import {
|
|
89
|
+
import { float32Step } from './src/timelines.ts';
|
|
90
90
|
import { findRung, RUNG_IDS, type RungSkeleton } from './src/ladder.ts';
|
|
91
91
|
import {
|
|
92
92
|
DEFAULT_MAX_RESIDUAL,
|
|
@@ -623,8 +623,30 @@ function meshFit(m: CompileResult['meshes'][number]): string {
|
|
|
623
623
|
// why, in the place the figures stood, and prints no figure.
|
|
624
624
|
if (m.fitWithheld !== undefined) return ` fit not measured: ${m.fitWithheld}. ${PAGE_GRID_UNLOCATED}`;
|
|
625
625
|
if (m.coverage === undefined) return '';
|
|
626
|
-
|
|
627
|
-
|
|
626
|
+
// A count of the plate's own cells, so on a `scale:` page it is texels and
|
|
627
|
+
// says so rather than borrowing the overshoot's unit beside it (issue #762).
|
|
628
|
+
const hole = m.holePixels ? `, enclosing ${m.holePixels}${m.pageScale === undefined ? 'px' : ' texel(s)'} of hole` : '';
|
|
629
|
+
return ` covers ${(m.coverage * 100).toFixed(2)}% of the art, reaching ${m.overshoot?.toFixed(2) ?? '?'}px past it${meshFitGrid(m)}${hole}`;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* The grid a fit was taken on, when it is not the drawing's own (issue #762).
|
|
634
|
+
*
|
|
635
|
+
* The overshoot is stated in the drawing's pixels on every route — the unit an
|
|
636
|
+
* attachment's size is in — and on a page that declares a `scale:` other than
|
|
637
|
+
* 1 it was measured on the page's texels and divided by that scale. So it
|
|
638
|
+
* carries the coarser grid's step: on `scale: 0.5` a figure moves in steps of
|
|
639
|
+
* 2.00px of the drawing, and it need not equal the figure the page it was
|
|
640
|
+
* packed from reads except where the distance falls on whole texels. Said
|
|
641
|
+
* beside the figure, and nothing at all on a loose part or a page at scale 1,
|
|
642
|
+
* where the line is the one it always was.
|
|
643
|
+
*/
|
|
644
|
+
function meshFitGrid(m: CompileResult['meshes'][number]): string {
|
|
645
|
+
if (m.pageScale === undefined) return '';
|
|
646
|
+
return (
|
|
647
|
+
` (the drawing's pixels, measured on the page's texels at scale: ${m.pageScale} — a texel is ` +
|
|
648
|
+
`${(1 / m.pageScale).toFixed(2)}px of the drawing)`
|
|
649
|
+
);
|
|
628
650
|
}
|
|
629
651
|
|
|
630
652
|
/**
|
|
@@ -739,12 +761,14 @@ function deformKeyName(key: DeformKeyMeasure): string {
|
|
|
739
761
|
* The report's is the spec's own `t`; the survey's came back through
|
|
740
762
|
* `Float32Array`, because that is what `spine-core` reads a timeline's frames
|
|
741
763
|
* into — a key written `0.62` arrives as `0.6200000047683716`. So the tolerance
|
|
742
|
-
* is
|
|
743
|
-
*
|
|
744
|
-
*
|
|
764
|
+
* is one float32 step at this magnitude (`float32Step`): the compiler emits a
|
|
765
|
+
* key time as a float's name — the spec's own time when it names one, the float
|
|
766
|
+
* below it when it does not (issue #716) — so the loaded float is within one
|
|
767
|
+
* step of the spec's `t` either way, which is narrower than any key spacing the
|
|
768
|
+
* format can hold.
|
|
745
769
|
*/
|
|
746
770
|
function sameKeyTime(specTime: number, loaded: number): boolean {
|
|
747
|
-
return Math.abs(specTime - loaded) <=
|
|
771
|
+
return Math.abs(specTime - loaded) <= float32Step(loaded);
|
|
748
772
|
}
|
|
749
773
|
|
|
750
774
|
/**
|
package/docs/AUTHORING.md
CHANGED
|
@@ -324,6 +324,14 @@ texel count beside it so both numbers are visible:
|
|
|
324
324
|
measures the PNG. Reach for `--atlas-in` when the pack is what you were handed, or
|
|
325
325
|
when drawing through the pack's own texels is the point.
|
|
326
326
|
|
|
327
|
+
📐 **What you make beside the art stays at the art's size**
|
|
328
|
+
([#762](https://github.com/firejune/rigc/issues/762)). A depth sheet or a soft
|
|
329
|
+
mask is read in the drawing's pixels on a `scale:` page as on loose parts — each
|
|
330
|
+
vertex's texel position over the stated scale — and a mesh fit's overshoot is
|
|
331
|
+
printed in them, with the texel it was measured on named beside it. What stays
|
|
332
|
+
in texels is what is taken off them: a `contour`'s trace, whose `margin` and
|
|
333
|
+
`tolerance` are applied on the texels there are (below).
|
|
334
|
+
|
|
327
335
|
🚨 **A page that declares a size it does not have is a different thing, and it is
|
|
328
336
|
refused** ([#715](https://github.com/firejune/rigc/issues/715)). The common shape
|
|
329
337
|
is a pack whose `4096x4096` pages ship as `2048x2048` PNGs with the atlas
|
|
@@ -387,11 +395,34 @@ that cannot be withheld, because its outline *is* its geometry: it is refused as
|
|
|
387
395
|
compile error carrying `A06`'s whole sentence — ratio and repair — on `explain`
|
|
388
396
|
and on `build` alike, where on `build` it arrives before the gate would have said
|
|
389
397
|
it. Carry out the repair and every figure comes back. ⚠️ They come back measured
|
|
390
|
-
on the **coarser** texels the page really has, so
|
|
391
|
-
|
|
392
|
-
restatement
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
on the **coarser** texels the page really has, so a figure that depends on the
|
|
399
|
+
grid need not equal the one the pack the page was halved from reads: on the same
|
|
400
|
+
fixture the `scale: 0.5` restatement traces the contour as 11 vertices where the
|
|
401
|
+
full-resolution page traced 15, because a trace runs on the texels there are and
|
|
402
|
+
its `margin` and `tolerance` are applied on them.
|
|
403
|
+
|
|
404
|
+
📐 **A fit's overshoot is stated in the drawing's pixels on every page**
|
|
405
|
+
([#762](https://github.com/firejune/rigc/issues/762)). It is a distance, and on a
|
|
406
|
+
page that declares a `scale:` it is taken on the page's texels — so it used to be
|
|
407
|
+
printed in them, under the same `px`: one mesh over one drawing read 16.00px on
|
|
408
|
+
the declared-size page, **8.00px** on its `scale: 0.5` restatement and **32.00px**
|
|
409
|
+
on a `scale: 2` one. It is now the texel distance over the scale the atlas states,
|
|
410
|
+
which is the unit an attachment's `width` is in and the one you drew in, and the
|
|
411
|
+
line says which grid it was taken on — a texel of a `scale: 0.5` page is 2.00px of
|
|
412
|
+
the drawing, so that is the step the figure moves in:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
# fan authored 9 vertices / 8 triangles (budget 200) bones=[fan] covers 100.00% of the art, reaching 16.00px
|
|
416
|
+
# past it (the drawing's pixels, measured on the page's texels at scale: 0.5 — a texel is 2.00px of the drawing)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
All three pages read the fan at 16.00px, because its rim lands on whole texels of
|
|
420
|
+
each. A figure that does not is exact only to that step — the contour above reads
|
|
421
|
+
6.00px on the `scale: 0.5` page (3.00 texels) against the full page's 3.16px, a
|
|
422
|
+
different outline measured on a coarser grid, and the line's clause is what says
|
|
423
|
+
so. On a loose part and a page at scale 1 the texels are the drawing, and the line
|
|
424
|
+
is the one it always was. A `contour`'s hole count is a count of those cells and
|
|
425
|
+
says `texel(s)` on such a page.
|
|
395
426
|
|
|
396
427
|
🚨 **A page that is not a PNG is refused by name, before anything is compiled
|
|
397
428
|
against it** ([#732](https://github.com/firejune/rigc/issues/732)). rigc reads PNG
|
|
@@ -707,7 +738,10 @@ bun cli.ts pose --images path/to/parts --frame poseA.png [--out pose.json]
|
|
|
707
738
|
count of vertices on undrawn texels — are replaced by `fit not measured: …` and
|
|
708
739
|
`… is not measured: …` naming the page and the ratio. A `contour` on such a page
|
|
709
740
|
is refused, since its outline is read off those texels. Why, the quoted lines,
|
|
710
|
-
and the repair: §0.2.
|
|
741
|
+
and the repair: §0.2. On a page that **does** declare a `scale:`, the overshoot
|
|
742
|
+
in that line is the drawing's pixels and the line names the texel it was taken
|
|
743
|
+
on (`… measured on the page's texels at scale: 0.5 — a texel is 2.00px of the
|
|
744
|
+
drawing`, [#762](https://github.com/firejune/rigc/issues/762)).
|
|
711
745
|
- **`diff`** compares two skeletons and reports **a ratio per measure** in six
|
|
712
746
|
sections (bones, slots, attachments, constraints, animations, events). It
|
|
713
747
|
deliberately does not combine them into a score: a rig with the right skeleton
|
|
@@ -922,7 +956,7 @@ one frame (1/60 s) is a compile error, and assertion `A09` re-checks it against
|
|
|
922
956
|
That frame of slack is for a duration declared *longer* than the motion — an
|
|
923
957
|
animation may hold its final pose. In the other direction there is no slack to give:
|
|
924
958
|
**no key may land past the declared duration**, and this is checked per timeline
|
|
925
|
-
rather than per animation, within
|
|
959
|
+
rather than per animation, within one float32 step of the duration. Both halves matter, and the second is not
|
|
926
960
|
the first with a smaller number — see §4.5.
|
|
927
961
|
|
|
928
962
|
**R8 — `from` needs a cut manifest.** `from.anchor` / `from.slotWindow` /
|
|
@@ -1092,7 +1126,7 @@ number where the box would be:
|
|
|
1092
1126
|
| --- | --- | --- |
|
|
1093
1127
|
| `build` | emits `x`/`y`/`width`/`height` | emits none of them |
|
|
1094
1128
|
| `A14_NO_FULL_FRAME_MESH` | fails a mesh as big as the stage | **SKIP**, by name |
|
|
1095
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | exempts the one image that covers the stage | exempts
|
|
1129
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | exempts the base plate a cut manifest names — the part whose window is the crop — and, on a build that names none, the one image that covers the stage | a manifest build exempts the plate it names, exactly as with one ([#770](https://github.com/firejune/rigc/issues/770)). A rig-spec build names no base plate, so an opaque part is refused with *"this skeleton declares no stage size to measure one against, and the build names no base plate"* and the two ways to decide it: a `skeleton` stage the plate covers, or *"build from a cut manifest, whose base plate is the part whose window is the crop"* |
|
|
1096
1130
|
| `diff` | `stage_present` and `stage_box` | `stage_present` 1/1 when neither side declares one (agreement), `stage_box` 0/0 |
|
|
1097
1131
|
| `explain` | `stage W x H` | `stage none declared` |
|
|
1098
1132
|
| `render` | frames the posed extent of every animation | the same frames, plus a line saying the viewport is the posed extent and no stage |
|
|
@@ -1869,7 +1903,7 @@ off a greyscale sheet in the part's own pixel grid:
|
|
|
1869
1903
|
|
|
1870
1904
|
| Field | Meaning |
|
|
1871
1905
|
| --- | --- |
|
|
1872
|
-
| `image` | **required.** The sheet, relative to the rig's `images` directory, and the **same pixel size as this attachment's `image`**. It is not packed into the atlas — it is a measurement rigc reads at compile time, not art anything draws |
|
|
1906
|
+
| `image` | **required.** The sheet, relative to the rig's `images` directory, and the **same pixel size as this attachment's `image`**. It is not packed into the atlas — it is a measurement rigc reads at compile time, not art anything draws. Under `--atlas-in` that is still the drawing's size, on a page that declares a `scale:` too: the sheet is read at each vertex's texel position over the scale the atlas states, so one sheet serves the loose parts and every pack of them ([#762](https://github.com/firejune/rigc/issues/762)) |
|
|
1873
1907
|
| `near` | **required.** `"white"` or `"black"` — which end of the range is closest to the viewer. Stated rather than defaulted: both conventions are in use, and a sheet read with the wrong one turns the part inside out with every gate still green |
|
|
1874
1908
|
| `zScale` | **required.** How many world units the map's full range spans, in the attachment's own units — the number `radius` used to carry. 8 bits of level say nothing about scale, so this is authored, never measured |
|
|
1875
1909
|
| `gamma`, `contrast`, `bias` | the tone curve applied to the nearness, defaults `1` / `1` / `0`. State them when a consumer's own renderer curves the same sheet, so the mesh and that renderer describe one surface |
|
|
@@ -1936,7 +1970,7 @@ bun cli.ts build --rig gallery/look/rig.json \
|
|
|
1936
1970
|
1st pct yaw +19.32° x1.000 of 80 / -19.32° x1.000 of 80 pitch +22.92° x1.000 of 102 / -26.94° x1.000 of 130
|
|
1937
1971
|
first to fold: yaw + at 19.32°, triangle 174 [119,138,139], the sheet steps 28.50 level(s) across it, which is 0.112 of the range this mesh sampled
|
|
1938
1972
|
MESH hair_lock_l grid 39 vertices / 48 triangles (budget 320) bones=[lock_l] attachments=[hair_lock_l]
|
|
1939
|
-
depth "lock_l_depth.png" 0c4eaeb36b7c5cac near=white zScale=64 z=[22.086275, 63.
|
|
1973
|
+
depth "lock_l_depth.png" 0c4eaeb36b7c5cac near=white zScale=64 z=[22.086275, 63.87451]
|
|
1940
1974
|
32 of 39 vertices sample a texel the part image does not draw — their z is the sheet's reading of somewhere the part is not
|
|
1941
1975
|
turn ceiling yaw +17.04° / -45.80° pitch +none / -none
|
|
1942
1976
|
1st pct yaw +unranked of 12 / -unranked of 36 pitch +none / -none
|
|
@@ -2034,6 +2068,8 @@ refuses it instead:
|
|
|
2034
2068
|
| a sheet cut to the art's alpha, on a **contour** | `does not cover 12 of the mesh's 12 vertices … A contour mesh puts every vertex ON the silhouette and pushes it out by the margin … Dilate the sheet past the mesh margin, or lower the margin.` |
|
|
2035
2069
|
| a sheet cut to the art's alpha, on a **grid** | `does not cover 36 of the mesh's 81 vertices … A grid spans the whole part window, corners included … Dilate the sheet to the window, or state "us"/"vs" that keep the lattice inside the art.` — the two topologies run out of sheet for different reasons, and the message says which |
|
|
2036
2070
|
| a sheet that is not the part's size | `the depth map … is 32x32 and the part is 64x64. A depth map is sampled in the part's own pixel grid` |
|
|
2071
|
+
| a sheet at the page's texel size, on a page that declares a `scale:` | `the depth map "lattice_texels.png" is 48x32 and the part is a 96x64 drawing — 48x32 texels on a page that declares scale: 0.5, which makes a texel 2px of the drawing. … so the sheet is 96x64, the size of the loose art, whatever the page holds.` — the part's own pixel grid is the drawing's on every route, and the message names the ratio and all three sizes, because the drawing's is in neither file |
|
|
2072
|
+
| a sheet cut to the art, on a `scale:` page | the coverage refusal above, with the vertex's position in the page's texels **and** `pixel (x, y) of the drawing-sized sheet` — the pixel of the file you made |
|
|
2037
2073
|
| a colour sheet | `the depth map … is not greyscale — pixel (0, 0) is rgb(10, 200, 10)` |
|
|
2038
2074
|
| `zScale` at or below 0 | `it is how many units the map's full range spans, so a positive number. To put the near end at the back, say "near": "black"` |
|
|
2039
2075
|
| `gamma` or `contrast` at or below 0 | `collapses the range onto the midpoint … so the map would describe a flat part` |
|
|
@@ -2101,7 +2137,7 @@ hanging sleeve.
|
|
|
2101
2137
|
| Field | Meaning |
|
|
2102
2138
|
| --- | --- |
|
|
2103
2139
|
| `bone` | **required.** The bone the region is carried by. It has to already exist — a bone a physics constraint targets is part of the skeleton, not a side effect of a mesh |
|
|
2104
|
-
| `mask` | **required.** A greyscale sheet in the part's own pixel grid: the level IS the weight, black still and white fully carried, sampled at each vertex. Alpha is not read — a transparent pixel is black |
|
|
2140
|
+
| `mask` | **required.** A greyscale sheet in the part's own pixel grid: the level IS the weight, black still and white fully carried, sampled at each vertex. Alpha is not read — a transparent pixel is black. The grid is the drawing's, as for a depth map: on a `scale:` page the mask is still the art's size ([#762](https://github.com/firejune/rigc/issues/762)) |
|
|
2105
2141
|
|
|
2106
2142
|
The remainder always stays on the slot bone, so every vertex closes at 1 by
|
|
2107
2143
|
construction rather than by `A20` catching it later. `build` and `explain` report
|
|
@@ -2131,7 +2167,7 @@ bone, must close at 1, and at least one must actually be carried.
|
|
|
2131
2167
|
| the slot's own bone | `moves nothing — a soft region needs a bone that can move independently` |
|
|
2132
2168
|
| a mask that is black everywhere | `carries no vertex of this mesh — every one of its 49 vertices samples black` |
|
|
2133
2169
|
| a colour mask | `is not greyscale — pixel (0, 0) is rgb(10, 200, 10)` |
|
|
2134
|
-
| a mask that is not the part's size | `is 48x32 and the part is 96x64` |
|
|
2170
|
+
| a mask that is not the part's size | `is 48x32 and the part is 96x64` — on a `scale:` page, `and the part is a 96x64 drawing — 48x32 texels on a page that declares scale: 0.5` |
|
|
2135
2171
|
| a mask that is not on disk | `the soft mask "x.png" is not at …` |
|
|
2136
2172
|
|
|
2137
2173
|
⭐ **One depth pass buys both, on one part.** A carried mesh has two bones on the
|
|
@@ -2987,10 +3023,11 @@ the mapping above and drives the bone to the value it names, rather than playing
|
|
|
2987
3023
|
the animation on a track while the slider sits at its neutral. §4.11.4 is what
|
|
2988
3024
|
that changes and why it matters at `mix: 1`.
|
|
2989
3025
|
|
|
2990
|
-
🔸 **`scale` is
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3026
|
+
🔸 **`scale` is emitted as its float32**, like every other number rigc writes, so
|
|
3027
|
+
`1/60` ships as `0.016666668` — 8e-8 relative (it was `0.016667`, 2e-5, on the
|
|
3028
|
+
six-decimal grid before issue #716). Invisible in the middle of the range; it
|
|
3029
|
+
shows at the top of it, where a 60° turn then applies at 1.00000008 s rather than
|
|
3030
|
+
1 s. With `loop: false` that is the last frame and harmless, with
|
|
2994
3031
|
`loop: true` it wraps to the start. When the range comes from a *measured* ceiling
|
|
2995
3032
|
— the turn ceiling `build` reports for a depth mesh (§3.4, `depth`) is the natural
|
|
2996
3033
|
one — pick `to`/`scale` so the endpoint lands **inside** the duration rather than
|
|
@@ -3561,26 +3598,33 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3561
3598
|
Seconds, not frames: nothing requires a key to land on any frame grid, and a
|
|
3562
3599
|
reference rendered at some rate says nothing about where its keys are. Put keys
|
|
3563
3600
|
where the motion changes.
|
|
3564
|
-
- **Key times are
|
|
3565
|
-
|
|
3601
|
+
- **Key times are emitted as float32s like every other number, and never stored
|
|
3602
|
+
LATER than you wrote them.** Every emitted number is the shortest decimal naming
|
|
3603
|
+
its float32 — the text the editor writes, and the precision the runtime keeps,
|
|
3604
|
+
because `spine-core` reads a timeline's frames into a `Float32Array` (issue #716).
|
|
3605
|
+
A time that already names a float — `0.5`, `0.2`, the editor's `1.4333333` — is
|
|
3606
|
+
written as you wrote it. A time that does not — `2/12` computed in doubles, a key
|
|
3607
|
+
moved by `lag` or `stagger` — steps to the **largest float not above it**, never
|
|
3608
|
+
to nearest. A key time is a position against the sample grid a player will step,
|
|
3566
3609
|
and the two directions of a half-step error are not the same size. `2/12 s` and
|
|
3567
|
-
`5/30 s` are both 0.16666666
|
|
3568
|
-
|
|
3569
|
-
a whole frame late, with nothing raised. On a
|
|
3570
|
-
timeline always is) that is the wrong picture
|
|
3571
|
-
the spineboy run's muzzle flare fired a frame
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3610
|
+
`5/30 s` are both 0.16666666…, and the nearest float to that, 0.1666666716…, is
|
|
3611
|
+
*larger* than either, so a key stored there is applied at sample **3** of a 12 fps
|
|
3612
|
+
playback and not sample 2 — a whole frame late, with nothing raised. On a
|
|
3613
|
+
**stepped** timeline (an attachment timeline always is) that is the wrong picture
|
|
3614
|
+
rather than a slightly wrong value: the spineboy run's muzzle flare fired a frame
|
|
3615
|
+
late for exactly this, on the six-decimal grid rigc emitted until #716, until the
|
|
3616
|
+
run's own frame check caught it (issue #99). Stepping down cannot do that; the
|
|
3617
|
+
worst it can do is store a key one float early — 1.5e-8 s at 1/6 s — on the sample
|
|
3618
|
+
it was written for. ⚠️ What this does **not** protect you from is rounding your
|
|
3619
|
+
own times before you write them — write `2/12`, not `0.1667`, and let the compiler
|
|
3576
3620
|
do the quantising.
|
|
3577
|
-
- 🚨 **Nor does it protect a stepped key whose time
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
a stepped key there sits above the sample that was meant to see it. On an
|
|
3621
|
+
- 🚨 **Nor does it protect a stepped key whose time ALREADY names a float.**
|
|
3622
|
+
Such a time is written exactly as you wrote it, and the sampler does not arrive
|
|
3623
|
+
there: a player — and `sampleAnimation`, and therefore `check` — reaches sample *i*
|
|
3624
|
+
by accumulating `1/fps` *i* times, which for many *i* lands a few ULPs **below**
|
|
3625
|
+
`i/fps`. `2/12` is saved by the rule above precisely because it is *not* a float;
|
|
3626
|
+
`0.25`, `0.5`, `0.75`, `1` and every other multiple of `0.25 s` is one exactly,
|
|
3627
|
+
and a stepped key there sits above the sample that was meant to see it. On an
|
|
3584
3628
|
interpolated timeline that costs a few ULPs of value and nothing else. On a
|
|
3585
3629
|
**stepped** one it is the whole frame — and on the last sample it is the whole
|
|
3586
3630
|
event, because there is no later sample to catch it. Measured on rung 5's 6.5 s
|
|
@@ -3590,16 +3634,22 @@ consumer's, decided by dressing the skeleton rather than by the animation.
|
|
|
3590
3634
|
`6.499999999999994` — which read as a frame-change disagreement the pose series had
|
|
3591
3635
|
already fixed, and cost that run three builds
|
|
3592
3636
|
([`2026-08-26-rung5-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-26-rung5-1/LOOP.md), §8). ⇒ **For a
|
|
3593
|
-
stepped timeline, write `T − 1e-6`
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3637
|
+
stepped timeline, write a time a little below `T` — `T − 1e-6` still works, at
|
|
3638
|
+
any magnitude — rather than `T`.** What makes it work is not the size of the step:
|
|
3639
|
+
any time below `T` is stored on a float below `T`, because a time that names a
|
|
3640
|
+
float is stored at that float and one that does not steps down, and the float
|
|
3641
|
+
below `T` is below the accumulated sample too (one float step is 3.0e-8 s at
|
|
3642
|
+
0.5 s and 4.8e-7 s at 6.5 s, against the few-ULP shortfall of the sampler). What
|
|
3643
|
+
bounds it from the other side is the frame: `T − 1e-6` cannot reach the previous
|
|
3644
|
+
sample, 83,333 µs away at 12 fps. One ULP late loses the frame. This is the same
|
|
3645
|
+
asymmetry the rule above turns on, one float further in.
|
|
3597
3646
|
- **No key may land past the animation's `duration`.** Nothing that plays the
|
|
3598
3647
|
animation for the duration it declares ever reaches such a key, so it is a
|
|
3599
3648
|
compile error — checked on **every timeline**, not just on the latest key in the
|
|
3600
|
-
animation
|
|
3601
|
-
|
|
3602
|
-
|
|
3649
|
+
animation, against the float the key is stored at. The tolerance is one float32
|
|
3650
|
+
step at the duration — 4.8e-7 s at 5 s, 3.8e-6 s at 32 s — which is the most a
|
|
3651
|
+
key you put exactly *on* a duration the float cannot hold is stored past it, so
|
|
3652
|
+
such a key is fine. R7's frame of slack does not apply in this direction and
|
|
3603
3653
|
would not see this: rung 6 rounded its key times to 4 dp somewhere upstream, its
|
|
3604
3654
|
one-frame reveal landed 0.000034 s past a 68/12 s duration, another track was
|
|
3605
3655
|
already sitting on the declared duration so the animation's *longest* key time
|
|
@@ -3787,7 +3837,7 @@ group members (the per-member values of one track, side by side — issue #295)
|
|
|
3787
3837
|
t = 0.20944 rad
|
|
3788
3838
|
cos t − 1 = -0.021852
|
|
3789
3839
|
sin t = 0.207912
|
|
3790
|
-
shift the parent carries = −carried·sin t = -35.
|
|
3840
|
+
shift the parent carries = −carried·sin t = -35.344986
|
|
3791
3841
|
eye_l 5.513083 <- -62 at depth 150
|
|
3792
3842
|
eye_r 2.803385 <- 62 at depth 150
|
|
3793
3843
|
brow_l 3.849789 <- -62 at depth 158
|
|
@@ -4443,15 +4493,15 @@ bun cli.ts explain --rig gallery/portrait/rig.json \
|
|
|
4443
4493
|
t = 0.20944 rad
|
|
4444
4494
|
cos t − 1 = -0.021852
|
|
4445
4495
|
sin t = 0.207912
|
|
4446
|
-
centre shift = −radius·sin t = -35.
|
|
4447
|
-
25 vertices, largest offset 35.
|
|
4448
|
-
v 0 (-7.17493, 0) v 1 (-22.413595, 0) v 2 (-35.
|
|
4496
|
+
centre shift = −radius·sin t = -35.344986
|
|
4497
|
+
25 vertices, largest offset 35.344986px at vertex 2
|
|
4498
|
+
v 0 (-7.17493, 0) v 1 (-22.413595, 0) v 2 (-35.344986, 0) v 3 (-27.65817, 0)
|
|
4449
4499
|
v 4 (-14.255108, 0) v 5 (-14.255108, 0) v 6 (-14.255108, 0) v 7 (-14.255108, 0)
|
|
4450
|
-
v 8 (-14.255108, 0) v 9 (-27.
|
|
4500
|
+
v 8 (-14.255108, 0) v 9 (-27.65817, 0) v 10 (-35.344986, 0) v 11 (-22.413595, 0)
|
|
4451
4501
|
v 12 (-7.17493, 0) v 13 (-7.17493, 0) v 14 (-7.17493, 0) v 15 (-7.17493, 0)
|
|
4452
|
-
v 16 (-22.413595, 0) v 17 (-35.
|
|
4453
|
-
v 20 (-35.
|
|
4454
|
-
v 24 (-27.
|
|
4502
|
+
v 16 (-22.413595, 0) v 17 (-35.344986, 0) v 18 (-27.65817, 0) v 19 (-22.413595, 0)
|
|
4503
|
+
v 20 (-35.344986, 0) v 21 (-27.65817, 0) v 22 (-22.413595, 0) v 23 (-35.344986, 0)
|
|
4504
|
+
v 24 (-27.65817, 0)
|
|
4455
4505
|
```
|
|
4456
4506
|
|
|
4457
4507
|
The curve reads `stepped` where the spec says `"ease": "swell"`, and that is
|
|
@@ -4459,12 +4509,17 @@ The curve reads `stepped` where the spec says `"ease": "swell"`, and that is
|
|
|
4459
4509
|
offsets, so the segment between them would draw nothing and is written the way
|
|
4460
4510
|
the editor writes it.
|
|
4461
4511
|
|
|
4462
|
-
📌 **Float behaviour, stated.** The closed forms are evaluated in float64
|
|
4463
|
-
quantised
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
a
|
|
4467
|
-
|
|
4512
|
+
📌 **Float behaviour, stated.** The closed forms are evaluated in float64,
|
|
4513
|
+
quantised onto the model's own 1e-6 grid and emitted as that value's float32 name
|
|
4514
|
+
like every other number, so the same spec emits the same bytes and
|
|
4515
|
+
`A18_DETERMINISTIC_EMIT` proves it on a second compile. The grid is absolute on
|
|
4516
|
+
purpose: a model's identities — a wave sampled on its zero crossings, a whole
|
|
4517
|
+
revolution — are exact zeros float64 misses by ~1e-16, and the refusal of a key
|
|
4518
|
+
that states a deformation and evaluates to nothing is decided on it (issue #350);
|
|
4519
|
+
a float32 alone is relative and has no zero to land on. The runtime then loads
|
|
4520
|
+
the numbers into a `Float32Array`, which is equally true of a hand-written table —
|
|
4521
|
+
the difference the generator makes is that the numbers now agree with a stated
|
|
4522
|
+
model instead of with a transcription.
|
|
4468
4523
|
|
|
4469
4524
|
🔭 **Both adjacent asks have since landed.**
|
|
4470
4525
|
[#295](https://github.com/firejune/rigc/issues/295) was the same complaint about
|
|
@@ -5444,7 +5499,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
5444
5499
|
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` label is not on the 4.3 line (`4.3`, `4.3.N`, `4.3.N-suffix`) |
|
|
5445
5500
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out`. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) — as it is for `A06`, `A19` and `A27`; see `A07` ([#608](https://github.com/firejune/rigc/issues/608)) |
|
|
5446
5501
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
5447
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the
|
|
5502
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the base plate may be opaque, and which image that is is decided once for both routes ([#770](https://github.com/firejune/rigc/issues/770)): the plate the build names — on a cut manifest, the part whose window is the crop — and, only when it names none, an image at least the stage's size. The rig's statement comes first because the two can disagree: a stage stated small enough for an overlay to cover would otherwise exempt that overlay. A rig spec cannot name a base plate, so a stageless rig-spec build, and `validate <dir>` on a stageless skeleton, has nothing to decide it; an opaque part there is refused with *"nothing here decides which image that is: this skeleton declares no stage size to measure one against"* and the two ways to decide it — a `skeleton` stage the plate covers, or a cut manifest (for `validate`, the specs it was built from: `--rig`, `--motion` and the `--manifest`). Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art. On a **shared** page the question is asked per REGION over the decoded page rather than per file, because a packed page's own file all but always declares transparency — its gutter is transparent — and the file-level question would then be answered by the packing rather than by the art ([#266](https://github.com/firejune/rigc/issues/266)). ⚠️ **That scan states its verdict over the texels it READ, and never over texels that are not on the page** ([#705](https://github.com/firejune/rigc/issues/705)): a rectangle partly on its page is judged over the part that is on it, and the message carries both counts — `opaque in every one of the 77 texels of its 12x8 rectangle at -1,-1 … the other 19 of the 96 it declares are not on the page and are not measured here`. A rectangle with **no** texel on the page is reported **not measured** by name — the region, its rectangle, the page image's size, and the pointer to `A06`, which is the rule that judges a region's rectangle — and no verdict about opacity is printed at all. ⚠️ **A page whose IMAGE is not the size the atlas declares for it is the same non-measurement for every region on it** ([#715](https://github.com/firejune/rigc/issues/715)), and #705's clause does not cover that case: a page rescaled after packing leaves most rectangles partly on it, at coordinates that address a different part of the picture, so the scan came back with a confident verdict over texels nobody had located — on a two-region pack at a uniform 0.5 an opaque part's failure **disappeared**, the scan having found a transparent texel 32 texels away from it. The row names the page's two sizes and points at `A06`, which judges the page grid and prints the header that repairs it (§0.2). It stays a failure rather than becoming a SKIP because a SKIP is per ASSERTION: it would delete the verdicts on every other part of the same page, and an assertion cannot be skipped and failed at once without the report counting it twice. Before #705 the walk was silent about its own reach, so a part nobody could read printed *opaque in every one of its 12x8 texels* over zero of them, which is a refusal pointing at the wrong file: the art it names may be transparent, and the repair is the rectangle in `A06`'s row above. ⚠️ **A page file that cannot be read as PNG at all is the same non-measurement for every part on it** ([#732](https://github.com/firejune/rigc/issues/732)): one row per page naming its parts and pointing at `A06`, which names what the file is — where it used to print `threw: cannot decode PNG …: unexpected end of file`, an inflate error about a file that was never a PNG. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5448
5503
|
| `A20_MESH_WEIGHTS_COHERENT` | both ◑ | a weighted vertex with no bone, a negative weight, a bone index out of range, or weights that do not sum to 1. Under `spine-html` also: an unweighted mesh, a binding at weight 0, or **a bone the mesh declares that no vertex binds** — `mesh "x" declares bone "grip_b" and none of its 25 vertices binds it; the weights reference "box", "grip_a"`. Those three are one sentence about rigc's own generators: the bone set a generated mesh declares is the bone set its weights reference, so a `controls` or `chain` name that moves nothing is a defect where a foreign mesh's is not ([#684](https://github.com/firejune/rigc/issues/684)). Fix the rig spec's `controls`/`chain`, or the manifest's `control_bones`. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
5449
5504
|
| `A21_MESH_RIM_PINNED` | archetype | a generated ring's rim, a ribbon's entry row, or a contour's outline (which is all of it) is not pinned to its anchor bone at weight 1 |
|
|
5450
5505
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count. **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
|
|
@@ -7844,11 +7899,14 @@ under 4.3.26 (`[152.7006, 305.4012, 458.1019, 610.8025]`) and on an open one und
|
|
|
7844
7899
|
⚠️ **That last reading settles the model and cannot settle the spelling.** A
|
|
7845
7900
|
4-sample chord sum agrees with the runtime's forward difference to about **nine
|
|
7846
7901
|
significant digits** — *below* what float32 can hold, which is why both spellings
|
|
7847
|
-
reproduce both exports exactly, and
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7902
|
+
reproduce both exports exactly, and since issue #716 below what rigc's own file
|
|
7903
|
+
holds too, because rigc now writes each number as its float32. Under the six fixed
|
|
7904
|
+
decimals it wrote until then the file could tell them apart: on both rigs above
|
|
7905
|
+
they rounded apart on the **last** curve, where the running total has accumulated
|
|
7906
|
+
most — `610.802519` against `610.802520`, `1127.735817` against `1127.735818`. So
|
|
7907
|
+
the editor is the evidence for *what* is computed, and only `PathConstraint`
|
|
7908
|
+
itself is evidence for *how* — `PS67`/`PS68` compare the transcription with it at
|
|
7909
|
+
double precision.
|
|
7852
7910
|
|
|
7853
7911
|
⭐ **rigc emits the forward difference itself** since
|
|
7854
7912
|
[#560](https://github.com/firejune/rigc/issues/560) — `pathCurveLengths` in
|
|
@@ -7989,6 +8047,7 @@ Per part:
|
|
|
7989
8047
|
| --- | --- |
|
|
7990
8048
|
| `part`, `path`, `width`, `height` | the PNG, by the name every message uses |
|
|
7991
8049
|
| `placement` | the best placement found — `null` **only** for `empty-part` and `larger-than-canvas`, where nothing was searched |
|
|
8050
|
+
| `walls` | the walls of the search window `placement` stands **on**, whatever the verdict: `[]` when it settled inside, otherwise one `{ axis, edge, window }` per axis — `axis` is `scale` or `rotation`, `edge` is `floor` or `ceiling`, `window` is the flag's own `min,max`. A value on a wall is where the search was held, not where it came to rest — see §11.4 |
|
|
7992
8051
|
| `alternates` | other optima worth reporting, best first. Non-empty means the answer was not unique |
|
|
7993
8052
|
| `ambiguous` | at least one alternate is inside the ambiguity margin. **Choose with something this instrument cannot see** — anatomy, the other frame, or `rigc vote` |
|
|
7994
8053
|
| `rotationFree` | the part is self-similar under rotation, so `rotationDeg` is a placeholder and the value is yours |
|
|
@@ -8043,9 +8102,22 @@ holds nothing because it already contains every angle.
|
|
|
8043
8102
|
That is the case where the window is the first thing to move rather than the
|
|
8044
8103
|
frame or the threshold — eleven parts of one frame came back refused at
|
|
8045
8104
|
`scale=0.500` against art rendered at `0.311` per part pixel, and the message
|
|
8046
|
-
said only that the residual was above `--max-residual`.
|
|
8047
|
-
|
|
8048
|
-
|
|
8105
|
+
said only that the residual was above `--max-residual`. The sentence — with its
|
|
8106
|
+
*"may lie below"* — is printed on a refusal only.
|
|
8107
|
+
- 🔒 **An accepted placement that stopped on a wall says which wall too**, beside
|
|
8108
|
+
the value it holds on the console line and in `walls`:
|
|
8109
|
+
`PLACE head.png x= 45.5 y= 37.5 rot= 0.0° scale=2.000 (the ceiling of --scale 0.5,2) residual=0.0394 unexplained= 10%`
|
|
8110
|
+
is a part drawn at twice the scale of a frame rendered at 1.15 px/unit, whose
|
|
8111
|
+
truth is **2.30** — outside the window — and whose residual at the ceiling still
|
|
8112
|
+
cleared `--max-residual`. *On* a wall is not *near* one: the refinement is clamped
|
|
8113
|
+
to the window, so a value the window held **is** the bound, while a correct
|
|
8114
|
+
placement a window brackets closely settles strictly inside it and carries no
|
|
8115
|
+
mark. A window with no interior (`min === max`) marks nothing — being at its only
|
|
8116
|
+
value says nothing about the answer. ⚠️ The mark names the wall and not the side
|
|
8117
|
+
the truth is on: over the rendered example corpus, a floor held both truths below
|
|
8118
|
+
the window and parts shrunk into their own region whose truth was inside it. An
|
|
8119
|
+
empty `walls` is not evidence either way — a window that excludes the truth can
|
|
8120
|
+
still settle inside on another optimum, which is the caveat two bullets up.
|
|
8049
8121
|
- ⚠️ **A frame whose border has no dominant colour reports `background.unknown`.**
|
|
8050
8122
|
Every pixel then counts as material, the silhouette signal is gone, and the
|
|
8051
8123
|
residual is colour agreement alone. The report says so rather than being quietly
|
|
@@ -8293,7 +8365,7 @@ simply unused; a name the directory lacks is refused `no-part-image` by name.
|
|
|
8293
8365
|
| --- | --- |
|
|
8294
8366
|
| `--anchor <pose.json>` | use this `rigc pose` report instead of running one. Refused together with `--scale` / `--rotation`, which size the internal pass that then does not happen |
|
|
8295
8367
|
| `--atlas` | **refused by name.** Every other `--candidate` command takes it, so trying it here is reasonable — but the part art comes from `--images` and the skeleton is all this needs of the candidate, so a flag that silently did nothing would be worse than one that says why |
|
|
8296
|
-
| `--hinge <min,max>` | the window each child's local rotation is searched over, in Spine degrees about its setup value. Default `-180,180` — **a full turn, on purpose**: one degree of freedom is cheap enough to sweep exhaustively, and §11.4's warning about a window that does not contain the truth applies here too |
|
|
8368
|
+
| `--hinge <min,max>` | the window each child's local rotation is searched over, in Spine degrees about its setup value. Default `-180,180` — **a full turn, on purpose**: one degree of freedom is cheap enough to sweep exhaustively, and §11.4's warning about a window that does not contain the truth applies here too. The hinge step `3°` is a **ceiling** on the step, not the step: a window it does not divide is divided into whole steps no coarser than it, and `search` states the step that division produced, so `--hinge -20,20` prints `hinge -20°–20° step 2.857° (15 rungs)` and each chain part's note says it was searched `in 2.857° steps`. The full turn divides it exactly, so the default walks the same rungs it always did |
|
|
8297
8369
|
| `--stretch <ratio>` | also search a uniform bone scale, this ratio either way. Without it, stretch is searched **only where your own animations key a `scale` timeline on that bone** — a rig that never scales a bone is a rig saying that bone does not stretch |
|
|
8298
8370
|
| `--min-visible <0..1>` | below this visible share a placement is refused `occluded` instead of reported flat (default `0.25`). A reporting threshold, not a pass bar; the placement is still in the JSON. ⚠️ **It is not inert, though**: a bone whose frozen share is under this floor gets one *unmasked* look before its visible set is fixed, so the flag also changes **where parts land** and not only which rows are refused. Two runs at different `--min-visible` are two fits, and their shares are not one column |
|
|
8299
8371
|
| `--max-residual <0..1>` | as §11, over the visible pixels (default `0.25`) |
|
package/docs/FACE.md
CHANGED
|
@@ -1187,13 +1187,14 @@ declares it: `"from": -19, "to": 0, "scale": 0.05, "max": 19, "local": true,
|
|
|
1187
1187
|
and `to`** — and `to: 0` says nothing more than *the bottom of the range is the
|
|
1188
1188
|
animation's first frame*. `from`, `max` and the duration are all the ceiling.
|
|
1189
1189
|
|
|
1190
|
-
🔸 **And `scale` is chosen for the endpoint.** rigc
|
|
1191
|
-
|
|
1192
|
-
`1/60` ships as `0.
|
|
1193
|
-
1 s — the last frame under `loop: false`, the *first* under
|
|
1194
|
-
(AUTHORING §3.5.2). `0.05` is
|
|
1195
|
-
|
|
1196
|
-
and land the endpoint inside the
|
|
1190
|
+
🔸 **And `scale` is chosen for the endpoint.** rigc emits every number as its
|
|
1191
|
+
float32's shortest name, so a `scale` the float cannot hold moves the top of the
|
|
1192
|
+
dial: `1/60` ships as `0.016666668`, and a 60° turn then applies at 1.00000008 s
|
|
1193
|
+
rather than 1 s — the last frame under `loop: false`, the *first* under
|
|
1194
|
+
`loop: true` (AUTHORING §3.5.2). `0.05` is its own float's name, so the file states
|
|
1195
|
+
exactly `0.05`, which is the only reason the example can put its endpoint exactly
|
|
1196
|
+
on the duration. Pick a `scale` that is not, and land the endpoint inside the
|
|
1197
|
+
duration instead.
|
|
1197
1198
|
|
|
1198
1199
|
⚠️ **The ceiling is per mesh, and the face's is not the smallest one on the
|
|
1199
1200
|
face.** The same run prints one for every depth mesh, and in this example each
|
package/docs/INGEST.md
CHANGED
|
@@ -762,18 +762,28 @@ State the ambition in the right units, because three different things get called
|
|
|
762
762
|
`build(ingest(x))` is byte-identical for a skeleton **rigc** emitted — that is the
|
|
763
763
|
contract `bun run selftest` gates on every run — and it is not, for a skeleton the
|
|
764
764
|
editor emitted. Measured over all twelve corpus exports, a field-by-field walk of the
|
|
765
|
-
rebuild against its source produces differences of exactly
|
|
765
|
+
rebuild against its source produces differences of exactly two kinds, in every file:
|
|
766
766
|
|
|
767
767
|
| Kind | Example, candidate vs reference | Why |
|
|
768
768
|
| --- | --- | --- |
|
|
769
769
|
| **header bookkeeping**, 3 per file | `skeleton.hash: undefined vs "VFWbaK2UoCM"`, `skeleton.audio: undefined vs null`, `skeleton.spine: "4.3.13" vs "4.3.75-beta"` | the rig spec has no field for `hash` or `audio`, and the version is the runtime rigc links. `ingest` reports all three as findings — `HEADER_BOOKKEEPING` and `HEADER_REDERIVED` |
|
|
770
770
|
| **an omitted default written out** | `…rotate[0].time: 0 vs undefined` | the editor omits a zero `time`; rigc writes it. AUTHORING §10.5's *do not imitate the exporter's omissions*, from the other side. The header has one of these too and it is the one `ingest` now names: a stage at the origin is written `width`/`height` with no `x`/`y`, and the rebuild spells both — `LOSS HEADER_ORIGIN`, with the box unchanged ([#622](https://github.com/firejune/rigc/issues/622)). No file in this corpus takes that branch: all twelve declare an origin away from 0 |
|
|
771
|
-
|
|
771
|
+
|
|
772
|
+
🔢 **A third row stood here until issue #716: the emitted precision** —
|
|
773
|
+
`…curve[0]: 0.066667 vs 0.06666667`, `uvs[0]: 0 vs 2.554152e-7` — because rigc wrote
|
|
774
|
+
six fixed decimals where the editor writes each number as the shortest decimal naming
|
|
775
|
+
its float32. It rewrote every number with more digits than that, by up to 7e-7, with
|
|
776
|
+
no `LOSS` line, and the `uvs[0]` case was that rounding taking a carried value to 0 —
|
|
777
|
+
`ingest` carries every number as the double it parsed, so no derivation was involved.
|
|
778
|
+
rigc now writes the editor's text, and `IG73` holds every number of the twelve
|
|
779
|
+
rebuilds to its source's spelling; `IG75` counts what still differs by kind, and none
|
|
780
|
+
of it is a number. The whitespace of an export is not compared at all — it is an
|
|
781
|
+
export setting, pretty-printed in the examples and one line from the command line.
|
|
772
782
|
|
|
773
783
|
⇒ **So the corpus gate is `diff` at 1.000 rather than a byte comparison**, and it is
|
|
774
784
|
worth being exact about what that does and does not cover. `diff` compares structure —
|
|
775
785
|
counts, names, parentage, order, timeline kinds, key counts, curve kinds — and **not
|
|
776
|
-
the values inside the keys**, which is why
|
|
786
|
+
the values inside the keys**, which is why a moved value is invisible to it.
|
|
777
787
|
On rigc's own rigs byte identity covers both; on a foreign export it used to be
|
|
778
788
|
`check` (pixels) or nothing, depending on what you render.
|
|
779
789
|
|
|
@@ -784,8 +794,9 @@ every bone's `length` or mirrored every vertex would read 1.000 on all 49 measur
|
|
|
784
794
|
on every `(reported)` one. So the corpus round trip also compares **value by value**,
|
|
785
795
|
with the format's defaults taken from the parser rather than from a table — both files
|
|
786
796
|
are read through `spine-core` and the parsed forms are compared path by path, under a
|
|
787
|
-
tolerance that is the sum of
|
|
788
|
-
|
|
797
|
+
tolerance that is the sum of the 1e-6 grid rigc's closed-form models are evaluated on
|
|
798
|
+
— the one absolute grid it still emits on — and one float32 step of the runtime's
|
|
799
|
+
storage. Nine measures, printed on `IG16`'s own line and gated there — here
|
|
789
800
|
is the `6-arcs` export's, wrapped to fit this page:
|
|
790
801
|
|
|
791
802
|
```
|
|
@@ -805,7 +816,7 @@ does **not** cover, in the same breath:
|
|
|
805
816
|
| `version` and `hash` | the rig spec has no field for either, and `ingest` reports both as findings — the header row above, unchanged |
|
|
806
817
|
| anything below one float32 step | the parser stores frames, curves and vertices in a `Float32Array`, so a difference it cannot represent is invisible to any reading of the parsed form |
|
|
807
818
|
| a Bezier's handles *as written* | the parser samples them into the curve, so a moved handle arrives as moved samples rather than as the handle it was |
|
|
808
|
-
| how the file is **spelled** | field order
|
|
819
|
+
| how the file is **spelled** | field order and an omitted default written out — the second row of the table above is values that agree, and this measure says so. A number's spelling is `IG73`'s, which reads the rebuild as text |
|
|
809
820
|
| how it **looks** | that is `check`, and `--texture-from` is how its figure is attributed |
|
|
810
821
|
|
|
811
822
|
The geometric row needs a real number, because a naive reading of `check` makes an
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
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": {
|