spine-rigc 1.4.0 → 1.5.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/cli.ts CHANGED
@@ -103,7 +103,6 @@ import {
103
103
  DEFAULT_PADDING,
104
104
  DEFAULT_PAGE_EDGES,
105
105
  DEFAULT_PAGE_SIZE,
106
- FREE_EDGE_STEP,
107
106
  PAGE_EDGES,
108
107
  packAtlas,
109
108
  type PageEdges,
@@ -2054,6 +2053,9 @@ function cmdRender(flags: Record<string, string>): void {
2054
2053
  if (skin !== undefined) console.log(` .. skin ${skin}`);
2055
2054
  if (subset !== undefined) console.log(` .. ${subset.mode.padEnd(8)} ${subset.names.join(', ')}`);
2056
2055
 
2056
+ // `null` is a skeleton that posed no vertex at all. One that posed a vertex
2057
+ // it cannot frame — Infinity or NaN — is thrown from the framing as a
2058
+ // `GeometryError` naming the number (issue #873), and never reaches this.
2057
2059
  const viewport = framingViewport(data, maxSide, pose);
2058
2060
  if (!viewport) {
2059
2061
  throw new UsageError(
@@ -3825,7 +3827,7 @@ const FLAG_MEANINGS: Record<string, string> = {
3825
3827
  padding: `gutter each region reserves on every side, --pack only (default ${DEFAULT_PADDING}); it is filled by ` +
3826
3828
  "extending the region's own edge pixels outwards, which is what stops a neighbour bleeding in",
3827
3829
  'page-edges': `what a page's edges may be, --pack only (default ${DEFAULT_PAGE_EDGES}): pot is a power of two on ` +
3828
- `both; free tries every width on a ${FREE_EDGE_STEP}px step, takes the height the placement needs and keeps ` +
3830
+ 'both; free tries every width from the widest part up, takes the height the placement needs and keeps ' +
3829
3831
  'the least area — a smaller page, at the cost of region attachments sampling within 1 LSB of the loose ' +
3830
3832
  'build rather than exactly',
3831
3833
  'atlas-in':
@@ -4539,9 +4541,10 @@ try {
4539
4541
  // the one reader's and already names the file, what it is and what rigc
4540
4542
  // reads; a stack under it is the tool describing its own internals instead.
4541
4543
  // Exit 1, like a compile error: the invocation was fine, a file was not.
4542
- // A pose the geometry export cannot write as numbers (issue #864): the
4543
- // invocation was fine and the skeleton posed a NaN or an infinity, so exit 1
4544
- // like a file that is not a PNG. Raised before the first file is written.
4544
+ // A pose that is not finite (issues #864, #873): the invocation was fine and
4545
+ // the skeleton posed a NaN or an infinity, so exit 1 like a file that is not
4546
+ // a PNG. Raised by the geometry export and by the framing — the one sentence
4547
+ // naming the bone or vertex and its value — before the first file is written.
4545
4548
  if (err instanceof GeometryError) {
4546
4549
  console.error(`rigc render: ${err.message}`);
4547
4550
  process.exit(1);
package/docs/AUTHORING.md CHANGED
@@ -341,9 +341,12 @@ a gap:
341
341
 
342
342
  Under `pot` turning shrank no page. Under `free` the largest gain, 13.37 %, is
343
343
  inside the search's own noise: the neighbouring width on the 32-pixel grid
344
- moves that set's unturned page by 18.13 %, and trying every width instead
345
- finds an unturned page 10.22 % smaller. With every width tried, no set gains
346
- more than 5.05 % from turning — so the packer still never turns a region.
344
+ `free` then searched moves that set's unturned page by 18.13 %, and trying
345
+ every width instead finds an unturned page 10.22 % smaller. With every width
346
+ tried, no set gains more than 5.05 % from turning — so the packer still never
347
+ turns a region. (The `free` column is that day's 32-pixel grid. `free` has
348
+ tried every width since issue #872 — below — so the 5.05 % is the turn gain on
349
+ the search that ships.)
347
350
  - **no re-ordering of anything the skeleton says.** `skeleton.json` from a packed
348
351
  build is **byte-identical** to the unpacked one, because sizes are still
349
352
  measured from the loose PNGs and packing is an output arrangement.
@@ -383,7 +386,7 @@ every power-of-two pair up to `--page-size` in order of area and writes the firs
383
386
  the pack fits — it never doubles past a smaller page that would do. So when the
384
387
  parts' own area already exceeds the next power-of-two page down, no `pot` packer
385
388
  can do better, and `free` is the lever left. Under `free` the candidate widths
386
- are the multiples of 32 from the widest padded part up to `--page-size`; at each
389
+ are **every width** from the widest padded part up to `--page-size`; at each
387
390
  width the parts are placed by the same MaxRects pass on a page `--page-size`
388
391
  tall, and the height is the bottom edge of the lowest part. The page with the
389
392
  least area wins, then the squarer one (smaller |width − height|), then the
@@ -391,13 +394,65 @@ narrower one — every candidate has a different width, so the order is total an
391
394
  two builds of the same parts write the same bytes. Spilling, the packing order
392
395
  and `--page-size` as the ceiling are the same as under `pot`.
393
396
 
397
+ Every width is affordable because of two bounds, and neither can change the
398
+ page: a width is not tried when the parts' area cannot fit it or when its
399
+ tallest part alone already makes it larger than the best page so far, and a
400
+ placement stops as soon as its lowest part makes it larger than that best page.
401
+ Both are strict, so a width that could tie the best is always placed to the end.
402
+
403
+ **Why every width, measured (issue #872).** Until then the widths were the
404
+ multiples of 32. On twelve region sets — the two painting rigs below and ten
405
+ production rigs of 20 to 22 parts, the sets of the rotation table above — and,
406
+ as a held-out check, on the ten atlases of the fetched `examples/` corpus, each
407
+ search was run against the every-width page (padding 2, `--page-size` 2048).
408
+ Area change against the 32-pixel grid:
409
+
410
+ | Set | 32 px (before) | step 8 | step 4 | coarse 32 → ±31 px of the best 2 | coarse 12 → ±11 px of the best | every width (now) |
411
+ | --- | --- | --- | --- | --- | --- | --- |
412
+ | 22-part painting | 1888x697 | −1.58 % | −1.58 % | −1.68 % | −1.68 % | 967x1338, −1.68 % |
413
+ | 20-part painting | 480x1166 | 0.00 % | 0.00 % | −0.21 % | −0.21 % | 479x1166, −0.21 % |
414
+ | P1 | 416x1633 | −1.92 % | −2.88 % | −2.88 % | −2.88 % | 404x1633, −2.88 % |
415
+ | P2 | 1184x810 | 0.00 % | 0.00 % | −0.08 % | −0.08 % | 1183x810, −0.08 % |
416
+ | P3 | 480x1000 | 0.00 % | 0.00 % | 0.00 % | 0.00 % | 480x1000, 0.00 % |
417
+ | P4 | 864x1346 | −3.07 % | −3.07 % | −2.66 % | −3.15 % | 1231x915, −3.15 % |
418
+ | P5 | 1344x1768 | −9.90 % | −10.16 % | −10.22 % | −10.22 % | 1427x1495, −10.22 % |
419
+ | P6 | 800x1934 | −1.00 % | −1.50 % | −1.50 % | −1.50 % | 788x1934, −1.50 % |
420
+ | P7 | 1024x928 | −0.78 % | −0.78 % | −0.88 % | −0.88 % | 1015x928, −0.88 % |
421
+ | P8 | 608x1250 | 0.00 % | 0.00 % | −0.16 % | −0.16 % | 607x1250, −0.16 % |
422
+ | P9 | 1024x2037 | −2.34 % | −2.73 % | −2.93 % | −2.93 % | 994x2037, −2.93 % |
423
+ | P10 | 352x1433 | 0.00 % | −1.14 % | −1.70 % | −1.70 % | 346x1433, −1.70 % |
424
+ | worst miss of the every-width page, twelve sets | 11.38 % | 1.73 % | 0.58 % | 0.50 % | 0.00 % | 0.00 % |
425
+ | worst miss, the ten `examples/` atlases | 4.12 % | 2.67 % | 0.77 % | 0.82 % | 0.82 % | 0.00 % |
426
+ | cell placements, twelve sets | 12,361 | 49,063 | 98,021 | 40,230 | 38,371 | 47,978 |
427
+
428
+ The rule was the cheapest search that lands on the every-width page. Flipping
429
+ the packer's own last tie-break (leftmost before topmost) changes no page on any
430
+ of the 22 sets, so there is no tie-break noise to land "within": the target is
431
+ the page itself. The coarse-to-fine search that met it on all twelve sets missed
432
+ it on the first held-out atlas, and its failures do not follow its grid — on
433
+ the twelve sets, refining around the best width of a 9-pixel grid misses where
434
+ 10 to 12 do not, and a 14-pixel grid misses again —
435
+ because the area as a function of the width has minima one pixel wide (a 4-pixel
436
+ grid refined around its best four widths still misses one). Every width is exact
437
+ by construction. Its placement count overstates its cost, because an abandoned
438
+ placement stops early: all twelve sets take 53 ms in the measuring harness,
439
+ against 22 ms for the 32-pixel grid and 810 ms for every width unbounded, and a
440
+ whole `build --pack --page-edges free` of the 22-part rig takes the same wall
441
+ time either way. ⚠️ The cost grows with the part count faster than the grid's:
442
+ on generated sets of 100 and 300 small parts, where no bound prunes much, the
443
+ search places 15 times the grid's cells.
444
+
394
445
  The default stays `pot` because every runtime accepts a power-of-two page and
395
446
  the editor's own packer writes one by default: 9 of the 10 atlases in the
396
447
  fetched `examples/` corpus are power-of-two on both edges. What `free` costs is
397
448
  the exactness above. `x / pageWidth` is no longer exact, so a **region**
398
449
  attachment's samples join a mesh's at one least significant bit against the
399
450
  loose build instead of 0. No region's bytes change: every one lifts back byte for
400
- byte, as under `pot`. Observed on two painting rigs of 22 and 20 parts:
451
+ byte, as under `pot`. Observed on two painting rigs of 22 and 20 parts, on the
452
+ 32-pixel grid `free` shipped with (issue #860); every width now packs them at
453
+ 967x1338 = 1,293,846 (−38.3 %, 91.2 % covered, 59.7 % opaque) and 479x1166 =
454
+ 558,514 (−46.7 %, 93.4 % covered, 53.9 % opaque), and the render columns are
455
+ held to their bound of one by `PK72` on every build rather than re-measured here:
401
456
 
402
457
  | Rig | `pot` page | `free` page | Area | Covered | Opaque (alpha > 0) | Render vs loose, region attachments only | Render vs loose, whole rig |
403
458
  | --- | --- | --- | --- | --- | --- | --- | --- |
@@ -1243,6 +1298,14 @@ emitted as written, in the default skin or anywhere else.
1243
1298
  `spec` must be exactly `"rigc-rig/1"`. `name` must be a non-empty string. `bones`
1244
1299
  must be non-empty. `slots` must be present (it may be empty).
1245
1300
 
1301
+ 🔢 **Every number in a rig spec is finite at float32 precision** — at most
1302
+ ±3.4028234663852886e38 — whatever field it is in, so the ranges the tables below
1303
+ state are ranges *within* that. The skeleton is written as float32, and a number
1304
+ past that line would be emitted as Infinity, which JSON writes as `null`: `1e309`
1305
+ (which JSON reads as Infinity) and `1e308` (a finite double with no float32) are
1306
+ both refused by the field they are in, before anything is compiled (§5.1). The cut
1307
+ manifest and the motion spec are held to the same line.
1308
+
1246
1309
  🚫 **Every example value below is invented.** Names, coordinates, vertex lists and
1247
1310
  payloads in this guide are written to illustrate a field, never copied out of a
1248
1311
  reference export — an example lifted from one would be handing an authoring agent an
@@ -1443,6 +1506,20 @@ renderers and reports its step 5 as a **SKIP** naming that, and the round trip
1443
1506
  comes back green on the strength of `validate` and `diff`. A skin only **one** side
1444
1507
  can draw is the other case entirely, and stays red.
1445
1508
 
1509
+ 🔢 **"Nothing to draw" is said only of a skeleton that posed no vertex at all.** One
1510
+ whose attachment is there but posed to `Infinity` or `NaN` is refused by the bone or
1511
+ the vertex that holds it and the value — `rigc render: the setup pose: bone "head" has
1512
+ worldX Infinity; a world transform is finite`, or `… animation "turn" frame 31 at 60
1513
+ fps (t=0.5167s): bone "head" has a NaN; …` for a key, or `… slot "x" attachment "y"
1514
+ vertex 3 has x NaN; a posed vertex is finite` — exit 1, before anything is written.
1515
+ It is the sentence `render --geometry` refuses on (§8.2), from the same code, and
1516
+ `rigc check` prints it after `the candidate is posed to a number that is not finite,
1517
+ so no frame of it can be compared:`. The bone is named before its vertices because
1518
+ it is the cause; look for its value in the skeleton file — a setup `x`/`y`/`rotation`,
1519
+ or a timeline key at the time given — and, for a rig spec you wrote, in `rigc explain`,
1520
+ which prints every bone's local transform. A frame's time is the framing pass's own
1521
+ (every animation at 60 fps); the key that overflowed is at or just before it.
1522
+
1446
1523
  **Region attachment** ([Spine: region attachments](http://esotericsoftware.com/spine-regions)),
1447
1524
  the default `type`:
1448
1525
 
@@ -5359,6 +5436,16 @@ rather than linked, like the references at the top of this page: that page cites
5359
5436
  corpus inventory, and a ladder run reads this guide without following its references
5360
5437
  out of it.)
5361
5438
 
5439
+ 🔢 **A fourth surface is a pose that is not finite**, which `render` and `check`
5440
+ refuse before drawing anything, naming the bone or vertex and its value.
5441
+ `A10_NO_NAN_AFTER_STEPPING` reads the same scan, so on a skeleton with an animation
5442
+ `validate` gets there first and in the same words: a bone `rotation` of `1e309`, in
5443
+ the setup pose or in a key, leaves the bone's world position finite and its matrix
5444
+ `NaN`, and A10 names the bone and the term. What it does not see is a skeleton with
5445
+ no animation (A10 SKIPs, so `render` is where that one is named) and a skin other
5446
+ than the default, which A10 does not pose. The sentence, and where to look for the
5447
+ number, is under *nothing to draw* in §3.4.
5448
+
5362
5449
  ### 5.1 Compile errors — before the gate
5363
5450
 
5364
5451
  A `CompileError` names the object and the field, and nothing is written.
@@ -5482,6 +5569,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
5482
5569
  | `a rig spec needs a non-empty "bones" array` | §3.2 — a rig with no bones has nothing to hang a slot on; every one has at least a root |
5483
5570
  | `a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)` | §3.3 — write `[]` for a rig that draws nothing. ⚠️ These three arrive only when the key is really absent: **misspelt**, it is the unknown-key refusal above, naming what you wrote |
5484
5571
  | `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
5572
+ | `<file>: <where> is Infinity; a number in this file is finite at float32 precision, at most ±3.4028234663852886e+38, because the skeleton is written as float32 — …` (also `is -Infinity`, `is 1e+308`, and `is NaN` from a library caller) | §3 — state the number you meant, inside float32's range. One walk over the whole file, run right after the key scan and before any other rig-spec check, so a range rule never prints `NaN`. `<where>` is every number in the rig spec — a bone (`bone "hip" x`), a slot, a skin's attachment down to its arrays (`skin "default" slot "s" attachment "a" weights[3][1].x`), a constraint (`constraint "aim" mixRotate`), an event, `skeleton`, `invariants.meshTriangles` — and the dotted path in the cut manifest (`crop.h`) and in the motion spec (`` `animations.idle.tracks[0].keys[1].v[0]` ``, after the motion spec's own field checks, which keep their sentences). ⚠️ It sees numbers only: `"x": "NaN"` is a string and is not refused by this row — write a number as a JSON number |
5485
5573
  | `two bones are called "X"` | bone names are the join key; rename one |
5486
5574
  | `two ik constraints are called "X" — a constraint resolves by name AND type (\`SkeletonData.findConstraint\`), so names are unique PER KIND: an ik and a transform constraint may share one, two of a kind may not` | §3.5 — rename one of the two. The kind in the sentence is the pair's own, so `two transform constraints are called "X"` is the same refusal on another kind; a name shared **across** kinds is not this error |
5487
5575
  | `physics constraint "X" is declared in both the rig spec and the motion spec's physics table` | §4.6 — the rig spec declares a physics constraint's structure and the motion spec's `physics` table declares one outright; pick the file it belongs in. Per kind, like every other constraint name: an `ik` "X" in the rig spec beside a `physics` "X" here is two constraints and is not this error |
@@ -5707,7 +5795,7 @@ Fix A00 and run it again.
5707
5795
  | `A07_ATLAS_TEXT_SHAPE` | both | atlas text: a region name with stray whitespace, or a blank line splitting a page block. rigc writes the atlas, so this means a hand-edited file. Three sentences are about blank lines and name three different repairs: `line 1: the file begins with a blank line` (or `with N blank lines` for a run, one finding) is a blank before the first page name — delete it; `line N: the file ends with a blank line` (or `with N blank lines`, one finding) is a blank after the last page block's final line — delete it, since rigc ends the file on one newline; `line N: consecutive blank lines` is two blank lines side by side between the first page and the last — delete one. None of the three is `the last page block declares no region`, which means a page name and its fields with no region after them. rigc writes none of the three: under `--atlas-in` the pack's blank lines are put in rigc's shape on re-emission (§0.2), so any of them means a file rigc did not write, handed to `validate`. ⚠️ An atlas with **no page block at all** — no non-blank line — is not one of those: its subject is absent, so this reports **SKIP** naming the byte count it read, and so do the four rules below whose subject is a page ([#608](https://github.com/firejune/rigc/issues/608)). A rig whose skins need no art writes exactly that file (§3.4), and before #608 this row refused it with two findings naming a page block that was not there. What an empty atlas does **not** excuse is an attachment that wants a region out of it — that is `A08` |
5708
5796
  | `A08_REGION_NAMES_MATCH_ATTACHMENTS` | both | three things, and the message says which: an attachment whose `path` names **no region** of this atlas; a `path` carrying **stray whitespace**, printed quoted so you can see it; an **atlas region name** carrying stray whitespace (`A07` names that same line with its line number). The first two are read off the raw file **before** the loader is asked, so the miss is named here with the skin, the slot, the placeholder and the attachment's own name — the four things `AtlasAttachmentLoader`'s own `Region not found in atlas: <path> (attachment: <name>)` does not carry. There is no `spine-html` clause here — a placeholder is free to differ from the region its `path` names **SKIP** when no attachment names a region *and* the atlas declares none — both of its subjects at once |
5709
5797
  | `A09_ANIMATION_DURATION_MATCHES_SPEC` | both | the loaded duration ≠ the declared one, or the two sides disagree about which animations exist (R7). Asymmetric by design: a frame of slack for an animation that ends early, and none worth the name for a key *past* the declared end, which is the same rule §4.5 states at compile time — held here against a skeleton the compiler never saw. **SKIP** when neither side has an animation at all — a static rig has no duration |
5710
- | `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a `NaN` pose. Look for a degenerate curve or a zero scale. 🦴 **It also poses every bone `inherit` key at its own time**: the runtime resolves a mode by folding the case of its first letter and nothing else, and a spelling that misses is stored as **NaN** — the world position stays finite, `updateWorldTransform` matches no mode, and the bone keeps the rotation, scale and shear it had. The detail names the animation, the bone, the key's time and its spelling, beside the five. A bone whose *setup* spelling misses poses no mode at all, and is named the same way from the stepping loop. Neither is reachable from a rig spec — `build` refuses both spellings by name (§5.1) — so on a green build this clause is about files rigc did not write. Which mode a correct spelling poses is not judged here, because the lookup that resolved it is the one that would be checked **SKIP** when the skeleton carries no animation: the NaN is produced by stepping, and a static rig is never stepped — the same subject `A09` skips on |
5798
+ | `A10_NO_NAN_AFTER_STEPPING` | both | stepping the animation produced a pose that is not finite. It reads the **whole world transform** — all six terms `a`, `b`, `c`, `d`, `worldX`, `worldY` of every bone — then every vertex of every region and mesh the slots show, at the **setup pose** and at every stepped frame of every animation, through the same scan `render` refuses on, so the two cannot disagree. The detail names where (`the setup pose`, or `animation "x" frame i of 120 (t=…s)`), then the bone and the term (`bone "x" has a NaN`), or the slot, attachment and vertex (`vertex 3 has x Infinity`) when every bone is finite and a vertex is not — a `scaleX` chain whose product stays under the largest double does that. A bone at `rotation: 1e309` has a finite position and a `NaN` matrix, which is why the position alone is not read. Look for a number past the largest double, a degenerate curve or a zero scale. 🦴 **It also poses every bone `inherit` key at its own time**: the runtime resolves a mode by folding the case of its first letter and nothing else, and a spelling that misses is stored as **NaN** — the world position stays finite, `updateWorldTransform` matches no mode, and the bone keeps the rotation, scale and shear it had. The detail names the animation, the bone, the key's time and its spelling, beside the five. A bone whose *setup* spelling misses poses no mode at all, and is named the same way from the stepping loop. Neither is reachable from a rig spec — `build` refuses both spellings by name (§5.1) — so on a green build this clause is about files rigc did not write. Which mode a correct spelling poses is not judged here, because the lookup that resolved it is the one that would be checked **SKIP** when the skeleton carries no animation: the NaN is produced by stepping, and a static rig is never stepped — the same subject `A09` skips on |
5711
5799
  | `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
5712
5800
  | `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
5713
5801
  | `A13_MESH_BUDGET` | renderer | more mesh slots than the rig's `invariants.meshSlots`, or a mesh over its `invariants.meshTriangles`. Thin the mesh, or raise the budget in the rig spec. **SKIP** when the rig declares neither — which means *unmeasured*, not that the budget is inert: the same `meshSlots` is a **compile-time** refusal for rigc's own generators, before the gate (§3.7) **SKIP** also when the rig budgets **only** triangles and the skeleton carries no mesh. A declared slot budget still PASSes there, because zero mesh slots is a count measured against a ceiling |
@@ -6366,8 +6454,13 @@ show what the clip left of them. Bounding boxes, points, paths and clipping poly
6366
6454
  draw nothing and are not listed. Numbers are printed the way every JSON file this
6367
6455
  tool writes prints them — the shortest decimal that reads back as the same double —
6368
6456
  so a vertex in the file *is* the runtime's vertex, not a rounding of it; a pose that
6369
- is not finite is refused naming the frame, the slot, the attachment and the vertex,
6370
- rather than written as `null`.
6457
+ is not finite is refused rather than written as `null`, naming where — the setup pose,
6458
+ `animation "a" frame i at F fps (t=…s)`, or the rest table — and then the bone and
6459
+ its world-transform field, or the slot, the attachment and the vertex, with the
6460
+ value. Bones come first, so a bone that overflowed is named as itself rather than as
6461
+ its first vertex. Since issue #873 plain `render` refuses the same pose in the same
6462
+ sentence while framing it (§3.4's note under *nothing to draw*), so the flag is not
6463
+ needed to find out which number it was.
6371
6464
 
6372
6465
  **The two judgements the file was designed for**, written once so a consumer
6373
6466
  implements what the file promises:
@@ -8732,7 +8825,7 @@ ambiguous one ends with a sentence saying which reading its figures support:
8732
8825
 
8733
8826
  - **below the floor** — a 40x28 part of one flat colour, cut from a plain block it fits anywhere inside:
8734
8827
 
8735
- > part is 40x28 px (span 40 frame px, opaque 1) with texture 0, detail 0; 14 candidate(s), best 0.0000, next 0.0000, spread 0.0000 — below the measured floor (AUTHORING §11.5: detail 0.5, measured from 24 px up): pose placed 8% of the measured parts this plain, so this part is too plain for pose to tell its placements apart; that says nothing about whether the cut is right
8828
+ > part is 40x28 px (span 40 frame px, opaque 1) with texture 0, detail 0; 17 candidate(s), best 0.0000, next 0.0000, spread 0.0000 — below the measured floor (AUTHORING §11.5: detail 0.5, measured from 24 px up): pose placed 7% of the measured parts this plain, so this part is too plain for pose to tell its placements apart; that says nothing about whether the cut is right
8736
8829
 
8737
8830
  - **above the floor** — a 200x150 black-and-white checker the frame does not hold:
8738
8831
 
@@ -8759,9 +8852,9 @@ Found within 2 px of the truth, out of 15 trials per cell (free search, default
8759
8852
 
8760
8853
  | texture level (mean) | 24 px | 32 px | 48 px | 64 px | 96 px | 128 px | 192 px |
8761
8854
  | --- | --- | --- | --- | --- | --- | --- | --- |
8762
- | `native` (detail 3.49) | 12/15 (3 amb) | 14/15 (1 amb) | 13/15 (2 amb) | 15/15 | 12/15 (2 amb, 1 wrong) | 14/15 (1 wrong) | 14/15 (1 wrong) |
8763
- | `blur1` (detail 1.58) | 10/15 (5 amb) | 12/15 (2 amb, 1 wrong) | 13/15 (2 amb) | 13/15 (2 amb) | 12/15 (1 amb, 2 wrong) | 11/15 (1 amb, 3 wrong) | 11/15 (2 amb, 2 wrong) |
8764
- | `blur2` (detail 0.59) | 7/15 (7 amb, 1 wrong) | 8/15 (5 amb, 2 wrong) | 9/15 (4 amb, 2 wrong) | 8/15 (3 amb, 4 wrong) | 7/15 (3 amb, 5 wrong) | 6/15 (4 amb, 5 wrong) | 6/15 (2 amb, 7 wrong) |
8855
+ | `native` (detail 3.49) | 13/15 (2 amb) | 15/15 | 15/15 | 15/15 | 14/15 (1 wrong) | 14/15 (1 wrong) | 14/15 (1 wrong) |
8856
+ | `blur1` (detail 1.58) | 12/15 (3 amb) | 12/15 (3 amb) | 13/15 (2 amb) | 12/15 (3 amb) | 12/15 (2 amb, 1 wrong) | 11/15 (1 amb, 3 wrong) | 11/15 (2 amb, 2 wrong) |
8857
+ | `blur2` (detail 0.59) | 9/15 (5 amb, 1 wrong) | 9/15 (4 amb, 2 wrong) | 9/15 (4 amb, 2 wrong) | 8/15 (3 amb, 4 wrong) | 7/15 (3 amb, 5 wrong) | 6/15 (4 amb, 5 wrong) | 7/15 (3 amb, 5 wrong) |
8765
8858
  | `flat` (detail 0.00) | 0/15 (14 amb, 1 wrong) | 0/15 (14 amb, 1 wrong) | 0/15 (13 amb, 2 wrong) | 0/15 (11 amb, 4 wrong) | 0/15 (13 amb, 2 wrong) | 0/15 (12 amb, 3 wrong) | 0/15 (12 amb, 3 wrong) |
8766
8859
 
8767
8860
  The same trials, grouped by `detail`:
@@ -8769,14 +8862,14 @@ The same trials, grouped by `detail`:
8769
8862
  | detail | found | ambiguous | wrong | refused |
8770
8863
  | --- | --- | --- | --- | --- |
8771
8864
  | under 0.25 | 8/119 | 89 | 22 | 0 |
8772
- | 0.25–0.5 | 2/3 | 0 | 1 | 0 |
8773
- | 0.5–0.75 | 44/81 | 16 | 21 | 0 |
8865
+ | 0.25–0.5 | 0/3 | 2 | 1 | 0 |
8866
+ | 0.5–0.75 | 49/81 | 13 | 19 | 0 |
8774
8867
  | 0.75–1 | 17/34 | 14 | 3 | 0 |
8775
- | 1–1.5 | 24/25 | 1 | 0 | 0 |
8776
- | 1.5–2 | 34/48 | 11 | 3 | 0 |
8777
- | 2–3 | 36/44 | 5 | 3 | 0 |
8778
- | 3–4 | 33/34 | 1 | 0 | 0 |
8779
- | 4 and over | 29/32 | 3 | 0 | 0 |
8868
+ | 1–1.5 | 25/25 | 0 | 0 | 0 |
8869
+ | 1.5–2 | 34/48 | 13 | 1 | 0 |
8870
+ | 2–3 | 40/44 | 1 | 3 | 0 |
8871
+ | 3–4 | 34/34 | 0 | 0 | 0 |
8872
+ | 4 and over | 31/32 | 1 | 0 | 0 |
8780
8873
 
8781
8874
  **Method.** The truth set is `examples/spineboy`: its setup pose and one frame of
8782
8875
  `walk` (a third of the way in). For every trial the figure is rendered twice with one
@@ -8798,8 +8891,8 @@ part has a low per-pixel gradient and still places — and size alone has no
8798
8891
  measurable effect across 24–192 px at any level.
8799
8892
 
8800
8893
  **The edge**, derived by `deriveFloor` as the largest rung of a fixed ladder under
8801
- which at most a tenth of the trials were found: **`detail` 0.5**. Under it, **8.2%**
8802
- of the trials were found (10/122); at or over it, **72.8%** (217/298). A part is below
8894
+ which at most a tenth of the trials were found: **`detail` 0.5**. Under it, **6.6%**
8895
+ of the trials were found (8/122); at or over it, **77.2%** (230/298). A part is below
8803
8896
  the floor when its `detail` is under 0.5, or when its own longest side is under 24 px,
8804
8897
  the smallest size measured (there, nothing measured says `pose` can place it).
8805
8898
 
@@ -8813,17 +8906,48 @@ two anchors — and only 3 had it ranked under the old cutoff of three. The fine
8813
8906
  took the grid from 185 to 227 trials found and those misses from 65 to 14, for about
8814
8907
  10–14% more CPU over the grid (615 s of user time before it, 678 s and 704 s on two runs after).
8815
8908
 
8816
- ⚠️ **What this is and is not.** The grid is not a reliability line: four native cells
8817
- place 14 or 15 of 15 and the trials at `detail` 3 and over place 62 of 66, but the
8818
- goggles still place 10 of 42, and a threshold read off two dozen parts of one figure
8909
+ The refinement then polishes each place down the pyramid, and at full resolution a
8910
+ polish that has stopped tries its scale **up by half a coarse rung** (2^(1/6)), re-fits
8911
+ the position there, and restarts if that scores better; fifteen places survive each
8912
+ level. Both were set on this grid too (issue #877): of the 14 misses left after #865,
8913
+ 12 reached full resolution with the truth's place ranked first or second and the
8914
+ full-resolution objective preferring the truth, and stopped 2–7 px off it at a shrunk
8915
+ scale — the objective couples scale to position along a diagonal, so every
8916
+ one-axis step from there is worse while a joint one is better. None of them had
8917
+ accepted a worse residual. One more was the 24 px shin, its truth's place ranked
8918
+ 15th at the 2x level and cut at twelve. Up only because the objective charges part
8919
+ pixels that land off the figure and nothing for figure left uncovered, so the
8920
+ reduced levels err small: every stuck point sat below scale 1. It took the grid from
8921
+ 227 to 237 found and the misses from 14 to 8 (12 trials gained, 2 lost — each a
8922
+ blurred part now reported with a second placement at the same spot and another
8923
+ scale, one of them with the truth itself as its best answer), for 43% more
8924
+ refinement samples and the coarse pass untouched.
8925
+
8926
+ Since issue #886 the stopped polish tries **a quarter rung** (2^(1/12)) beside the
8927
+ half and keeps the better re-fit. The case was not a grid trial but §6's stationary
8928
+ post in [MOTION.md](MOTION.md): under `--scale 0.85,1.2` it came back at scale
8929
+ 0.917, residual 0.0649, where the old placement at 0.975 still scores 0.0589. The
8930
+ half rung stepped from 0.917 to 1.029, where the best position scores 0.0980 — a part
8931
+ that fills its image pays for every pixel pushed past the figure, so above its truth
8932
+ the objective is a cliff, and half a rung crossed the basin onto it. With both, the
8933
+ post places at 0.971 (0.0593, 0.1 px from the old placement), and the grid goes from
8934
+ 237 to 238 found and 8 to 6 misses (2 gained, 1 lost) for 16% more user CPU. The
8935
+ quarter alone found 234, losing the native arms #877 was made for. The one lost is a
8936
+ margin reading again: the blurred 32 px front shin (walk) now reports its truth as its
8937
+ best (0.07 px, 0.00968), with its old 0.51 px answer 0.0045 above it.
8938
+
8939
+ ⚠️ **What this is and is not.** The grid is not a reliability line: six native cells
8940
+ place 14 or 15 of 15 and the trials at `detail` 3 and over place 65 of 66, but the
8941
+ goggles still place 13 of 42, and a threshold read off two dozen parts of one figure
8819
8942
  would be a claim about that figure. What the grid can separate is **whose** failure a
8820
8943
  failure is. `bun tools/pose_floor.ts` prints the residual at the truth for every
8821
8944
  failed trial and marks it **missed** when the truth scores better than everything
8822
8945
  reported and nothing reported is on it — a failure the search could fix. The other
8823
8946
  failures are the objective preferring another placement (a blurred part shrunk into
8824
8947
  its own region scores better than its truth) or the truth reported and tied, and no
8825
- search can fix those. After the change, 14 of the 193 failed trials are misses, and 13
8826
- of those sit within 7 px of the truth rather than on another hill. All this was
8948
+ search can fix those. After #886, 6 of the 182 failed trials are misses, and 5 of
8949
+ those sit within 3.1 px of the truth rather than on another hill; the `--from`
8950
+ listing prints, for each, where the refinement left the truth level by level. All this was
8827
8951
  measured on **ideal cuts**, the frame's own pixels, where a residual at the truth is
8828
8952
  near zero. A generated cut also disagrees with its picture, which can only make it
8829
8953
  harder to place. So the edge is where `pose` fails even on ideal cuts, and never a
@@ -8833,7 +8957,11 @@ Re-run it with `bun tools/pose_floor.ts --textures <level> --json <out>` (one le
8833
8957
  process parallelises it), then `bun tools/pose_floor.ts --from <a.json>,<b.json>,…` to
8834
8958
  print the table, derive the edge and list the misses per cell. `PO22` in
8835
8959
  `bun run selftest` re-measures three of its cells, `PO24` holds those three cells to
8836
- no miss at all, and `PO23` places the 48 px fist this change was made for.
8960
+ no miss at all, and `PO23` places the 48 px fist #865 was made for; `PO28` and
8961
+ `PO29` place the four trials #877 was made for and read where the shin survived the
8962
+ cutoff, and `PO26` holds every polish to strictly descending residuals, read through
8963
+ the same trace. `PO31` places §6's post at the old placement from the page's own
8964
+ input bytes, and `PO32` holds it to taking the quarter-rung escape and reading the same twice.
8837
8965
 
8838
8966
  ## 12. Reading the half of that picture `pose` refuses — `rigc chainfit`
8839
8967
 
package/docs/INGEST.md CHANGED
@@ -370,6 +370,8 @@ rigc check --candidate examples/3-timing-and-spacing/export/3-timing-and-spacing
370
370
  --frames ref3/light
371
371
  ```
372
372
 
373
+ **No run reproduces this:** abridged — the head's `atlas`, `frames`, `skin`, `scope`, `reference`, `content` and `in units` lines and its two ⚠️ notes are cut, so the `⤷ fit` line the run prints under `content` stands under `framed to`; the section's `frames` line and every `⤷` note are cut; and so is everything the run prints after `per-frame`
374
+
373
375
  ```
374
376
  rigc check
375
377
  candidate …/examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json
@@ -378,14 +380,14 @@ rigc check
378
380
  declared frames.json's own box: TAKEN, coincident — a fit there asks for 0.00 px, under the 1 px that separates a candidate in the frames' coordinates from one in its own, over 21 frame(s).
379
381
 
380
382
  ── light — candidate animation "light", 12 fps ──
381
- MAE mean 0.00 worst 0.00 at f00-1 (0..255 over the union alpha; over the whole frame, mean 0.00)
382
- slot drift worst 0.5 px "pendulum" at f0003
383
+ MAE mean 0.00 worst 0.00 (exact: none of the 21 compared frame(s) differs from the reference) (0..255 over the union alpha; over the whole frame, mean 0.00)
384
+ slot drift worst 0.4 px "pendulum" at f0012
383
385
  per-frame all 20 adjacent pair(s) change by as much as the reference's own frames do
384
386
  ```
385
387
 
386
388
  Two things to take from the control, both of which you need before reading any real
387
389
  number: the **framing** resolved to the frames' own box at 0.00 px, so the MAE is a
388
- comparison of pictures rather than of framings; and **slot drift still reads 0.5 px at
390
+ comparison of pictures rather than of framings; and **slot drift still reads 0.4 px at
389
391
  MAE 0.00**, which is that instrument's own floor rather than a difference.
390
392
 
391
393
  ⚠️ **`--texture-from` is not optional on ingest work, and the reason is structural.**
@@ -1339,7 +1341,9 @@ against one picture. A foreign export hands you a packed page instead, and point
1339
1341
  `pose` at one is worse than useless — it treats the whole page as a single part and
1340
1342
  answers confidently:
1341
1343
 
1342
- ```bash
1344
+ ```bash setup
1345
+ rigc render --candidate examples/3-timing-and-spacing/export/3-timing-and-spacing-ess.json \
1346
+ --fps 4 --max 900 --out ref-big
1343
1347
  mkdir -p packed-only
1344
1348
  cp examples/3-timing-and-spacing/export/3-timing-and-spacing.png packed-only/
1345
1349
  rigc pose --images packed-only --frame 'ref-big/light@4fps/f0002.png'
@@ -1350,12 +1354,12 @@ rigc pose
1350
1354
  .. frame …/ref-big/light@4fps/f0002.png (900x409)
1351
1355
  .. ground rgb(232, 232, 232) over 100% of the border ring
1352
1356
  .. parts …/packed-only (1 png)
1353
- .. search scale 0.5–2 in 7 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
1354
- PLACE 3-timing-and-spacing.png x= 324.3 y= 188.2 rot= -91.4° scale=0.629 residual=0.2083 unexplained= 30%
1355
- found on a 7x3 anchor grid, step 4 at 32x reduction
1357
+ .. search scale 0.5–2 in 7 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
1358
+ PLACE 3-timing-and-spacing.png x= 324.3 y= 188.3 rot= -91.6° scale=0.630 residual=0.2085 unexplained= 30%
1359
+ found on a 14x7 anchor grid, step 4 at 16x reduction
1356
1360
  ```
1357
1361
 
1358
- ⚠️ **`residual=0.2083` is *under* the default 0.25 refusal bar**, so nothing refused
1362
+ ⚠️ **`residual=0.2085` is *under* the default 0.25 refusal bar**, so nothing refused
1359
1363
  it, and `PLACE` rather than `AMBIG` means nothing flagged it either. With the same frame
1360
1364
  and the two real loose parts, the answer is what it should be:
1361
1365
 
@@ -1365,12 +1369,15 @@ rigc pose --images examples/3-timing-and-spacing/images \
1365
1369
  ```
1366
1370
 
1367
1371
  ```
1372
+ rigc pose
1373
+ .. frame …/ref-big/light@4fps/f0002.png (900x409)
1374
+ .. ground rgb(232, 232, 232) over 100% of the border ring
1368
1375
  .. parts …/examples/3-timing-and-spacing/images (2 png)
1369
- .. search scale 0.3–0.6 in 4 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
1370
- PLACE pendulum.png x= 319.3 y= 214.5 rot= -88.9° scale=0.412 residual=0.0425 unexplained= 7%
1371
- found on a 12x5 anchor grid, step 5 at 16x reduction
1372
- PLACE square.png x= 437.2 y= 343.1 rot= -0.1° scale=0.410 residual=0.0331 unexplained= 4%
1373
- found on a 57x26 anchor grid, step 4 at 4x reduction
1376
+ .. search scale 0.3–0.6 in 4 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
1377
+ PLACE pendulum.png x= 319.3 y= 214.6 rot= -88.9° scale=0.411 residual=0.0428 unexplained= 7%
1378
+ found on a 28x13 anchor grid, step 2 at 16x reduction
1379
+ PLACE square.png x= 437.1 y= 343.1 rot= 0.0° scale=0.410 residual=0.0334 unexplained= 4%
1380
+ found on a 113x51 anchor grid, step 2 at 4x reduction
1374
1381
  ```
1375
1382
 
1376
1383
  ⇒ **Check what is in `--images` before trusting a `pose` report on ingest work.** One
package/docs/MOTION.md CHANGED
@@ -767,7 +767,7 @@ late, §3.8's overshoot is live; animation name → **`raise`**.
767
767
 
768
768
  ### 1. The art, and the two pictures
769
769
 
770
- ```bash
770
+ ```bash setup
771
771
  mkdir -p semaphore/parts && cd semaphore
772
772
  bun -e '
773
773
  const files = {
@@ -806,15 +806,15 @@ rigc pose
806
806
  .. frame …/semaphore/poseA.png (160x200)
807
807
  .. ground rgb(238, 238, 234) over 100% of the border ring
808
808
  .. parts …/semaphore/parts (3 png)
809
- .. search scale 0.5–2 in 7 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
810
- PLACE arm.png x= 91.9 y= 122.8 rot= 61.9° scale=0.968 residual=0.0320 unexplained= 4%
811
- found on a 10x13 anchor grid, step 4 at 4x reduction
812
- PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
809
+ .. search scale 0.5–2 in 7 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
810
+ PLACE arm.png x= 92.0 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
813
811
  found on a 20x25 anchor grid, step 4 at 2x reduction
814
- AMBIG post.png x= 79.0 y= 141.7 rot= -0.4° scale=0.690 residual=0.0687 unexplained= 25%
815
- found on a 7x9 anchor grid, step 3 at 8x reduction
816
- alt 2: x= 78.9 y= 140.7 rot= -0.4° scale=0.650 residual=0.0692 unexplained= 26%
817
- alt 3: x= 78.4 y= 135.5 rot= -0.5° scale=0.500 residual=0.0702 unexplained= 23%
812
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
813
+ found on a 40x50 anchor grid, step 2 at 2x reduction
814
+ AMBIG post.png x= 79.9 y= 149.0 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
815
+ found on a 14x17 anchor grid, step 6 at 2x reduction
816
+ alt 2: x= 79.0 y= 141.7 rot= -0.5° scale=0.691 residual=0.0687 unexplained= 25%
817
+ ambiguous: part is 14x96 px (span 93.2 frame px, opaque 1) with texture 0.0146, detail 1.406; 43 candidate(s), best 0.0593, next 0.0687, spread 0.0095 — above the measured floor (AUTHORING §11.5: detail 0.5, measured from 24 px up), so size and texture do not explain this; either the frame holds more than one place this part fits, or every candidate missed the true one — a narrower --scale or --rotation window around what you know of the part tells the two apart
818
818
  ```
819
819
 
820
820
  ⚠️ **The post came back at scale 0.690 with two alternates trailing it down to 0.500 —
@@ -830,17 +830,31 @@ rigc pose --images parts --frame poseB.png --scale 0.85,1.2 --out poseB.json
830
830
  ```
831
831
 
832
832
  ```
833
- .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
834
- PLACE arm.png x= 91.9 y= 122.8 rot= 61.9° scale=0.968 residual=0.0320 unexplained= 4%
835
- PLACE flag.png x= 104.7 y= 156.1 rot= 84.3° scale=0.955 residual=0.0135 unexplained= 0%
836
- PLACE post.png x= 79.9 y= 148.6 rot= -0.1° scale=0.975 residual=0.0589 unexplained= 16%
833
+ rigc pose
834
+ .. frame …/semaphore/poseA.png (160x200)
835
+ .. ground rgb(238, 238, 234) over 100% of the border ring
836
+ .. parts …/semaphore/parts (3 png)
837
+ .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
838
+ PLACE arm.png x= 91.9 y= 122.8 rot= 61.9° scale=0.970 residual=0.0320 unexplained= 4%
839
+ found on a 20x25 anchor grid, step 4 at 2x reduction
840
+ PLACE flag.png x= 104.7 y= 156.1 rot= 84.1° scale=0.956 residual=0.0135 unexplained= 0%
841
+ found on a 40x50 anchor grid, step 2 at 2x reduction
842
+ PLACE post.png x= 79.9 y= 148.5 rot= -0.1° scale=0.971 residual=0.0593 unexplained= 16%
843
+ found on a 14x17 anchor grid, step 6 at 2x reduction
837
844
  ```
838
845
 
839
846
  ```
840
- .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° step 15° · refuse above residual 0.25
841
- PLACE arm.png x= 102.6 y= 94.8 rot= -18.2° scale=0.973 residual=0.0305 unexplained= 4%
842
- PLACE flag.png x= 137.4 y= 86.0 rot= -6.6° scale=0.956 residual=0.0134 unexplained= 1%
847
+ rigc pose
848
+ .. frame …/semaphore/poseB.png (160x200)
849
+ .. ground rgb(238, 238, 234) over 100% of the border ring
850
+ .. parts …/semaphore/parts (3 png)
851
+ .. search scale 0.85–1.2 in 2 step(s) · rotation -180°–180° in 24 step(s) of 15° · refuse above residual 0.25
852
+ PLACE arm.png x= 102.5 y= 94.8 rot= -18.3° scale=0.974 residual=0.0305 unexplained= 4%
853
+ found on a 20x25 anchor grid, step 4 at 2x reduction
854
+ PLACE flag.png x= 137.4 y= 86.0 rot= -6.6° scale=0.956 residual=0.0134 unexplained= 0%
855
+ found on a 40x50 anchor grid, step 2 at 2x reduction
843
856
  PLACE post.png x= 80.0 y= 148.0 rot= 0.0° scale=0.995 residual=0.0394 unexplained= 9%
857
+ found on a 14x17 anchor grid, step 6 at 2x reduction
844
858
  ```
845
859
 
846
860
  Three things to read out of that pair, none of which is a score:
@@ -1168,7 +1182,7 @@ skeleton — AUTHORING §10.3 and PROMPTING clause 4 both price it. Keys are str
1168
1182
  The bytes of `poseA.png` and `poseB.png`, so §6 runs end to end. Both are 160×200 on a
1169
1183
  flat ground; both are invented.
1170
1184
 
1171
- ```bash
1185
+ ```bash setup
1172
1186
  bun -e '
1173
1187
  const frames = {
1174
1188
  "poseA.png": "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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-rigc",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, round-trips it through spine-core and named assertions, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {