spine-rigc 0.35.1 โ†’ 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/AUTHORING.md CHANGED
@@ -505,6 +505,18 @@ because the text passes through by line; regions the rig does not use stay in th
505
505
  file, because a real pack is shared between cuts and an importer that quietly
506
506
  dropped half of one would make `--out` disagree with the pack it was built from.
507
507
 
508
+ ๐Ÿ”ธ **The one thing not passed through is the pack's blank lines**
509
+ ([#803](https://github.com/firejune/rigc/issues/803)). They are written in the
510
+ shape rigc writes its own packs in: none before the first page, exactly one between
511
+ two page blocks, one trailing newline. Every non-blank line keeps its bytes and its
512
+ order, so a pack already in that shape โ€” every editor export in the example corpus,
513
+ and every pack `--pack` writes โ€” is emitted byte for byte as before. What this
514
+ changes is a pack from a 3.8-era packer, which begins with a blank line: the
515
+ runtime reads a run of blank lines before the first page as nothing, so the pack
516
+ loads, and until #803 `build` re-emitted the blank and refused its own emission as
517
+ `A07_ATLAS_TEXT_SHAPE: line 1: consecutive blank lines`. A run of blank lines
518
+ between two page blocks reads as one to the runtime, and is written as one.
519
+
508
520
  Five things are refused rather than warned about, because each of them otherwise
509
521
  **loads clean and draws wrong** โ€” or, the last, cannot be read back at all:
510
522
 
@@ -607,7 +619,6 @@ bun cli.ts ingest hero.json --out specs/ --images parts/
607
619
  # .. art loose
608
620
  # .. images ../parts/ (the rig spec's own, from /abs/path/specs)
609
621
  # JUDGE DURATION: animation "idle" โ€” skeleton JSON carries no duration; the largest key time (2.667) is used โ€ฆ
610
- # LOSS PATH_LENGTHS: skin "default" slot "track" attachment "track" โ€” the source states `lengths`; rigc RE-MEASURES it โ€ฆ
611
622
  # rigc: wrote /abs/path/specs/rig.json
612
623
  # rigc: wrote /abs/path/specs/motion.json
613
624
  # rigc: wrote /abs/path/specs/findings.json
@@ -698,7 +709,7 @@ the first:
698
709
  | --- | --- |
699
710
  | `BLOCK` | the spec format cannot say it, so the rebuild will **not** be the file that was read โ€” `point`, a `sequence` block the parser would read as some other series, an unknown field on a bone, slot or constraint, a timeline family the motion spec has no track for. The command exits non-zero **and still writes both specs**, because a spec plus a list of what is missing from it beats no spec |
700
711
  | `JUDGE` | the skeleton cannot answer and somebody has to: the stage, and each animation's duration |
701
- | `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how. A path attachment's `lengths` is the one that matters โ€” it is `PathConstraint`'s own four-sample measurement rather than an arc length (#560), so a transcribed one would freeze whatever produced the source. The header ones are cheaper: `HEADER_BOOKKEEPING` for a field the spec has no home for, `HEADER_REDERIVED` for the version string, `HEADER_ORIGIN` for an origin the source left to the format and the rebuild writes out (#622) |
712
+ | `LOSS` | the skeleton's spelling and rigc's differ, on purpose, and the line says how: `HEADER_BOOKKEEPING` for a field the spec has no home for, `HEADER_REDERIVED` for the version string, `HEADER_ORIGIN` for an origin the source left to the format and the rebuild writes out (#622) |
702
713
 
703
714
  โ›” **It reads one generation of the format, and a file from another one ends loud.**
704
715
  Spine data is locked to the generation that exported it and a mismatch does not
@@ -1103,12 +1114,14 @@ names the same comparator puts ahead of it โ€” `default` before `2`, `default`
1103
1114
  before `A` โ€” so renaming a skin away from `default` makes it an ordinary name that
1104
1115
  sorts like one.
1105
1116
 
1106
- **R12 โ€” A placeholder that more than one skin fills gets a per-skin attachment
1107
- `name`, and the `default` skin may not be one of those skins.** rigc writes
1108
- `"name": "<skin>/<placeholder>"` on each of those entries and restates `path`
1109
- beside it so the texture still resolves where it did; you do not author that and
1110
- there is nothing to do about it, but it is visible in the emitted file, so ยง3.4.2
1111
- says what it is and why. What you *do* author is where the shared art lives: a
1117
+ **R12 โ€” An attachment's `name` is written exactly when the spec states it, and
1118
+ the `default` skin may not share a placeholder with a named skin.** rigc derives
1119
+ no name. A placeholder several skins fill is emitted under that placeholder in
1120
+ each skin, carrying the `name` each entry states and none where it states none โ€”
1121
+ which is the shape the editor itself exports (ยง3.4.2). Until
1122
+ [#796](https://github.com/firejune/rigc/issues/796) rigc composed
1123
+ `"<skin>/<placeholder>"` there, which renamed every such attachment at runtime.
1124
+ What you *do* author is where the shared art lives: a
1112
1125
  placeholder the `default` skin shares with a named skin is a **compile error**,
1113
1126
  because the Spine editor has no representation for it in either spelling (#567,
1114
1127
  measured on 4.3.26 โ€” named, the export re-keys it and the default skin draws
@@ -1279,8 +1292,8 @@ setup pose as `null`.
1279
1292
 
1280
1293
  ### 3.4 `skins` โ€” placeholder โ†’ attachment maps
1281
1294
 
1282
- `skins` is `skinName โ†’ slotName โ†’ placeholderName โ†’ attachment`. Give at least
1283
- `default`; it becomes the skeleton's default skin. (No rung of the benchmark ladder
1295
+ `skins` is `skinName โ†’ slotName โ†’ placeholderName โ†’ attachment`. A skin called
1296
+ `default` becomes the skeleton's default skin. (No rung of the benchmark ladder
1284
1297
  uses a named skin โ€” all twelve official example skeletons have exactly one skin,
1285
1298
  called `default`.)
1286
1299
 
@@ -1288,11 +1301,25 @@ A skin can also say which bones and constraints it **switches on**, and that nee
1288
1301
  one more level, so a skin entry has a second spelling โ€” see ยง3.4.1. The short one
1289
1302
  above is unchanged and is what almost every rig wants.
1290
1303
 
1291
- ๐Ÿ”ธ **`default` is a name, not a requirement.** A rig may put every attachment in
1292
- named skins and declare no `default` at all, which is what an editor export of a
1293
- multi-skin character gives back; rigc still emits a `default` skin, empty, because
1294
- it always does. An animation keying such a slot resolves its attachment names
1295
- against every skin there is โ€” ยง4.4 states that rule and ยง5.1 the one refusal left.
1304
+ ๐Ÿ”ธ **`default` is a name, not a requirement, and rigc writes one exactly when the
1305
+ spec has one** ([#801](https://github.com/firejune/rigc/issues/801)). A rig may put
1306
+ every attachment in named skins and declare no `default` at all, which is what an
1307
+ editor export of a multi-skin character gives back: the 4.3.26 editor took a build
1308
+ declaring `skins: [default, alt, base]` with `default` **empty** and exported
1309
+ `[alt, base]`. So the emitted file carries a `default` skin when `skins` has the
1310
+ key โ€” `"default": {}` included, which is written back empty โ€” or when a manifest
1311
+ part files its states under it, and **none otherwise**. spine-core loads both
1312
+ spellings alike (`SkeletonData.defaultSkin` is null or an empty skin, and neither
1313
+ fills a slot), and the binary format has no way to hold an empty default skin at
1314
+ all. Until #801 rigc wrote an empty `default` into every build whether the spec
1315
+ had one or not, so a rebuild of such an export read `attachments.skins 1/2`
1316
+ against it. Two things change for a rig with no default skin, and both are
1317
+ named: a linked mesh that states no `skin` looks for its source in the default
1318
+ skin and is refused (the error table in ยง5.1), and `rigc explain` says the
1319
+ skeleton declares no default skin above its slot list instead of printing
1320
+ `attachments=[]` on every slot. An animation keying such a slot resolves its
1321
+ attachment names against every skin there is โ€” ยง4.4 states that rule and ยง5.1 the
1322
+ one refusal left.
1296
1323
 
1297
1324
  ๐Ÿ”ธ **A skin may fill no slot with anything that needs art, and that build is
1298
1325
  green.** The atlas is built out of what the skins reference, so a rig whose skins
@@ -1332,25 +1359,26 @@ the default `type`:
1332
1359
  | Field | Meaning |
1333
1360
  | --- | --- |
1334
1361
  | `type` | `"region"`, or omit |
1362
+ | `name` | the attachment's **own name**, when it is not the placeholder: what the runtime calls it (`Attachment.name`, which is what `slot.attachment.name` returns) and what `path` defaults to. Optional โ€” absent, the runtime names the attachment by its placeholder. Written **verbatim**, as the attachment's first key, exactly when you state it; rigc never makes one up (R12, ยง3.4.2). Nothing resolves an attachment *by* it: a slot's setup `attachment`, every timeline and a linked mesh's `source` use the placeholder. Every type takes it. Not a string is refused โ€” `"name" is 7, which is not a string` |
1335
1363
  | `image` | **rigc extension.** A PNG relative to the rig's `images` directory; rigc measures it (R5) |
1336
1364
  | `width`, `height` | required by the format โ€” give them, or give an `image` |
1337
- | `path` | the atlas region to resolve; defaults to the attachment's own name. rigc sets it for you when the PNG basename differs from the placeholder, and whenever it composes a `name` because more than one skin fills this placeholder (ยง3.4.2). **One rule, both kinds** โ€” see the note under *Mesh attachment* |
1365
+ | `path` | the atlas region to resolve; defaults to the attachment's name โ€” its `name`, else the placeholder. rigc sets it for you when the PNG basename differs from that name. **One rule, both kinds** โ€” see the note under *Mesh attachment* |
1338
1366
  | `x`, `y` | offset from the bone, in the bone's local space |
1339
1367
  | `rotation` | degrees; cancels a rotated bone for a plate authored screen-upright |
1340
1368
  | `scaleX`, `scaleY`, `color` | as Spine |
1341
- | `sequence` | a **numbered image series** instead of one region: `{ "count", "start"?, "digits"?, "setup"? }`, and `path` (or the placeholder) is the series' stem. No `image` beside it โ€” the frames are the images. ยง3.4.3 |
1369
+ | `sequence` | a **numbered image series** instead of one region: `{ "count", "start"?, "digits"?, "setup"? }`, and `path` (or, with none, the `name`, or the placeholder) is the series' stem. No `image` beside it โ€” the frames are the images. ยง3.4.3 |
1342
1370
 
1343
1371
  **Mesh attachment** ([Spine: meshes](http://esotericsoftware.com/spine-meshes)) โ€”
1344
1372
  either authored geometry (`uvs` + `triangles` + geometry) **or** a `generator`,
1345
1373
  never both. `hull`, `edges`, `width` and `height` may be stated; whichever is
1346
1374
  omitted, rigc derives โ€” `hull` and `edges` from the triangles, the size from the
1347
- PNG โ€” and the rules are a few paragraphs down. `type`, `image`, `path`, `color` and
1348
- `sequence` mean exactly what they mean on a region (a sequence mesh takes authored
1375
+ PNG โ€” and the rules are a few paragraphs down. `type`, `name`, `image`, `path`,
1376
+ `color` and `sequence` mean exactly what they mean on a region (a sequence mesh takes authored
1349
1377
  geometry, never a `generator` โ€” ยง3.4.3).
1350
1378
 
1351
1379
  ๐Ÿ”‘ **`path` is one rule for both kinds.** A mesh derives it from `image` the way a
1352
1380
  region does: stated wins, otherwise the PNG's basename when that differs from the
1353
- placeholder, otherwise nothing. The parser reads `path` off both with the same line
1381
+ attachment's name โ€” its `name`, else the placeholder โ€” otherwise nothing. The parser reads `path` off both with the same line
1354
1382
  (`getValue(map, "path", name)`, `SkeletonJson.ts:541` and `:570`), and `path`
1355
1383
  defaults to the attachment's **name** rather than to the placeholder โ€” so a mesh
1356
1384
  with `image: hair_short.png` under a placeholder called `hair` resolves the region
@@ -1515,11 +1543,11 @@ them (`SkeletonJson.ts:568-569`, `:582`), so rigc reads them the same way.
1515
1543
 
1516
1544
  | Field | Meaning |
1517
1545
  | --- | --- |
1518
- | `source` | **required.** The **placeholder** of the mesh whose geometry this one draws โ€” the key it is filed under in its skin, not its `name`. A miss is refused naming the skin, the slot and what that slot holds |
1546
+ | `source` | **required.** The **placeholder** of the mesh whose geometry this one draws โ€” the key it is filed under in its skin, not its `name`. A miss is refused naming the skin, the slot and what that slot holds; a `source` that spells the source's `name` instead is that miss, because the runtime looks the source up by skin, slot and key and never by name โ€” measured, it throws `Source mesh not found` |
1519
1547
  | `slot` | the slot the source lives in. Default: **this attachment's own slot**. Resolved by name against the rig's slots |
1520
1548
  | `skin` | the skin the source lives in. Default: **`default`** โ€” the default skin, *not* the skin this link is written in. Resolved by name |
1521
1549
  | `timelines` | default **`true`**: the link plays the source's `deform` keys. `false` makes it its own timeline target, so only keys written against the link move it |
1522
- | `image`, `path`, `width`, `height`, `color` | exactly as on a mesh โ€” the link resolves **its own** region, which is the point of the type |
1550
+ | `name`, `image`, `path`, `width`, `height`, `color` | exactly as on a mesh โ€” the link resolves **its own** region, which is the point of the type |
1523
1551
 
1524
1552
  ```json
1525
1553
  "skins": {
@@ -1605,6 +1633,7 @@ region, a trigger volume โ€” that moves with the skeleton and draws nothing.
1605
1633
  | Field | Meaning |
1606
1634
  | --- | --- |
1607
1635
  | `type` | `"boundingbox"`. **Required**: an omitted `type` is `"region"`, and a region has nowhere to put these keys โ€” measured, the refusal reads `attachment "mask" (region) has 2 keys this compiler does not read: "vertexCount", "vertices"` |
1636
+ | `name` | as on a region: the runtime's name for the attachment, written only when stated. A polygon resolves no region, so here it is the name and nothing else |
1608
1637
  | `vertexCount` | **required**, 3 or more. No default โ€” the paragraph above is why |
1609
1638
  | `vertices` | the unweighted `x, y` run, in the slot bone's local space; or the index-encoded weighted run, behind `boneIndexing` |
1610
1639
  | `weights` | the by-name form: one entry per vertex, each a list of `{ "bone": โ€ฆ, "x": โ€ฆ, "y": โ€ฆ, "weight": โ€ฆ }`, each pair in that bone's local space. Never beside `vertices` |
@@ -1619,7 +1648,7 @@ attachment rather than a second set of art.
1619
1648
  | Field | Meaning |
1620
1649
  | --- | --- |
1621
1650
  | `type` | `"clipping"`. **Required** |
1622
- | `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | exactly as on a bounding box |
1651
+ | `name`, `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | exactly as on a bounding box |
1623
1652
  | `end` | the last slot the clip applies to, **by name**. Absent is the parser's own encoding for *clip everything after this one*, which is why a typo cannot be told from an omission once the file is loaded: `findSlot` returns null on a miss and the parser assigns that null without a word, so the clip runs to the bottom of the draw order and takes every slot below it with it. rigc refuses a name the rig does not declare โ€” `end names slot "X", which this rig does not declare` โ€” and `A33` refuses it again on a skeleton rigc did not write |
1624
1653
  | `convex` | default **false**. True tells the runtime the polygon is convex so it can clip without triangulating it, and a polygon that deforms concave is clipped by its convex hull instead (`ClippingAttachment.convex`). Nothing here checks that the polygon is in fact convex |
1625
1654
  | `inverse` | default **false**. True makes everything **outside** the polygon visible instead of everything inside, and inverse clipping is always treated as convex (`ClippingAttachment.inverse`) |
@@ -1637,10 +1666,10 @@ other vertex attachment.
1637
1666
  | Field | Meaning |
1638
1667
  | --- | --- |
1639
1668
  | `type` | `"path"`. **Required** |
1640
- | `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | as on a bounding box โ€” except that these vertices are knots **and** their handles, which the count rule below is about |
1669
+ | `name`, `vertexCount`, `vertices`, `weights`, `boneIndexing`, `color` | as on a bounding box โ€” except that these vertices are knots **and** their handles, which the count rule below is about |
1641
1670
  | `closed` | default **false**. True joins the last knot back to the first |
1642
1671
  | `constantSpeed` | default **true** โ€” note the direction. Leaving it out asks for the expensive-and-correct traversal, in which the runtime re-measures the path every frame and `lengths` is never read. `false` makes the runtime trust the emitted `lengths` instead: cheaper, exact only while the path holds its setup shape, and the reason a deformed path wants the default |
1643
- | `lengths` | ๐Ÿšซ **refused by name.** rigc measures the setup length of each curve off the geometry and emits it, the way it measures a region's size off its PNG: `"lengths" is not authored โ€” rigc measures the setup arc length of each curveโ€ฆ`. The field is declared only so the refusal can say that rather than report a misspelt key. What the numbers are โ€” and why *arc length* is the wrong name for them โ€” is ยง10.6 |
1672
+ | `lengths` | **stated or measured** ([#804](https://github.com/firejune/rigc/issues/804)). The cumulative length at the end of each curve, and exactly **`vertexCount / 3`** entries on an open path and a closed one alike โ€” the parser's own allocation, which on an open path is one more than its curves: the last entry is the wrap-around curve's cumulative, which the editor writes and nothing reads. **Stated**, it is emitted as stated (float32, like every number in the file) โ€” which is what `ingest` writes from an export, and the only way to carry the editor's number, because the editor measures the pose the first update gives the path constraint, *constraints applied*, and rigc does not pose. A stated array of the wrong count is refused with both counts, `"lengths" has N entry(ies) where the parser sizes M`, and a non-finite entry or one below its predecessor by index. **Left out**, rigc measures it off the geometry: weighted vertices through their bones' setup world transforms, scale, shear and `inherit` included, on the **unconstrained** setup pose โ€” so a path whose bones a constraint moves at rest gets a different figure from the editor's. Only `constantSpeed: false` reads the field at all. What the numbers are โ€” and why *arc length* is the wrong name for them โ€” is ยง10.6 |
1644
1673
 
1645
1674
  ๐Ÿšจ **`vertexCount` counts knots AND handles, and it has to be a multiple of 3.**
1646
1675
  The parser hands `vertexCount << 1` to `readVertices` and then walks the result in
@@ -1660,7 +1689,8 @@ and `vertexCount is N and an open path needs at least 6`.
1660
1689
 
1661
1690
  Nine points are two curves. The outer handles at `x = -30` and `x = 210` are
1662
1691
  dropped, so the chain runs from `x = 0` to `x = 180` and rigc emits
1663
- `"lengths": [90, 180]` beside it โ€” measured, not stated. That is the path
1692
+ `"lengths": [90, 180, 360]` beside it โ€” measured, not stated, and the third entry
1693
+ is the wrap-around curve from `x = 180` back to `x = 0`, which nothing reads. That is the path
1664
1694
  ยง3.5.1's constraint example rides: with `position: 0.25` its `cart` bone poses at
1665
1695
  `worldX = 45.000000`.
1666
1696
 
@@ -2312,41 +2342,64 @@ beside them is refused rather than ignored (an ignored slot is an attachment tha
2312
2342
  vanishes), and a rig with a *slot* of one of those names is refused too, because
2313
2343
  there the two forms are genuinely ambiguous. Rename the slot.
2314
2344
 
2315
- #### 3.4.2 Two skins, one placeholder โ€” the `name` rigc writes for you
2316
-
2317
- Two skins putting different art under one placeholder is what a skin is *for*, and
2318
- it is the one shape rigc emitted wrongly until
2319
- [#541](https://github.com/firejune/rigc/issues/541). Without a `name` field an
2320
- attachment's name **is** its placeholder (`SkeletonJson.ts:526`), so four skins
2321
- filling `patch` are four different attachments all called `patch`. spine-core never
2322
- notices โ€” its skin table is keyed by placeholder, so the two never meet โ€” and the
2323
- whole gate is green. The Spine editor refuses the import outright:
2345
+ #### 3.4.2 Two skins, one placeholder โ€” and the `name` you may state
2324
2346
 
2325
- ```
2326
- ERROR: Unable to import skeleton.
2327
- [error] Error reading skeleton: skins
2328
- Cause: [error] Error reading attachment: patch (MOw)
2329
- Cause: [error] Multiple attachments have the same name: patch patch
2330
- ```
2331
-
2332
- โ‡’ rigc now writes each of those entries a name of its own, composed from the two
2333
- names you already gave it:
2347
+ Two skins putting different art under one placeholder is what a skin is *for*. Each
2348
+ entry is its own attachment, filed under that placeholder in its own skin, and with
2349
+ no `name` field the runtime names every one of them by the placeholder
2350
+ (`getValue(map, "name", placeholder)`, `SkeletonJson.js:526`). That is legal Spine
2351
+ data, and it is the shape the editor exports: rigc emits it exactly as you wrote it.
2334
2352
 
2335
2353
  ```json
2336
2354
  "skins": {
2337
2355
  "default": { "block": { "block": { "image": "block.png" } } },
2338
2356
  "base": { "patch": { "patch": { "image": "patch_a.png" } } },
2339
- "zulu": { "patch": { "patch": { "image": "patch_a.png", "x": 4 } } }
2357
+ "zulu": { "patch": { "patch": { "image": "patch_b.png", "x": 4 } } }
2340
2358
  }
2341
2359
  ```
2342
2360
 
2343
- emits, for slot `patch`
2361
+ emits two `patch` attachments in slot `patch`, one per skin, each with the `path` its
2362
+ image gives it (`patch_a`, `patch_b`) and **no `name`** โ€” both answer to `patch` at
2363
+ runtime, and each is the one its skin shows.
2364
+
2365
+ ๐Ÿ”‘ **State a `name` when the attachment's own name is not its placeholder.** A
2366
+ transcription of an export that carries one โ€” `ingest` writes it for you โ€” or any
2367
+ rig whose consumer reads `slot.attachment.name`. It is written verbatim, and `path`
2368
+ defaults to it, so an image named after it needs no `path`:
2344
2369
 
2345
2370
  ```json
2346
- { "name": "base/patch", "path": "patch", "width": 64, "height": 64 }
2347
- { "name": "zulu/patch", "path": "patch", "width": 64, "height": 64, "x": 4 }
2371
+ "base": { "patch": { "patch": { "name": "patch-base", "image": "patch-base.png" } } }
2348
2372
  ```
2349
2373
 
2374
+ emits `"name": "patch-base"` first and no `path`, and the runtime draws region
2375
+ `patch-base` for an attachment it calls `patch-base`, filed under `patch`.
2376
+
2377
+ ๐Ÿ”ฌ **Until [#796](https://github.com/firejune/rigc/issues/796) rigc composed a name
2378
+ here, and the reason it did was measured wrong.**
2379
+ [#541](https://github.com/firejune/rigc/issues/541) read the editor's refusal of a
2380
+ four-skin rig โ€” `Multiple attachments have the same name: patch patch` โ€” as "one
2381
+ name over several attachments, because a linked mesh resolves its parent by name",
2382
+ and [#552](https://github.com/firejune/rigc/issues/552) wrote
2383
+ `"<skin>/<placeholder>"` on every contested entry. Both halves fail a measurement:
2384
+
2385
+ - **A linked mesh finds its source by skin, slot and KEY.** Through spine-core
2386
+ 4.3.13, two skins each fill `C` with a different mesh and no `name`, both load
2387
+ named `C`, and a link with `source: "C"` binds the mesh of the skin its own
2388
+ `skin` names. A `source` that spells a source's `name` instead throws
2389
+ `Source mesh not found` โ€” which rigc refuses by name before that (ยง3.4, *Linked
2390
+ mesh*).
2391
+ - **The editor imports the uncomposed shape.** A production rig whose placeholders
2392
+ are each filled by two named skins, meshes and linked meshes among them, imported
2393
+ through Spine 4.3.26 with every composed name stripped. It is the file that editor
2394
+ exported.
2395
+
2396
+ What #541 had bisected was a rig whose **default** skin filled the contested
2397
+ placeholder beside the named ones โ€” its smallest refusing variant was `default` plus
2398
+ one named skin โ€” and that shape is refused on measurements of its own, below. The
2399
+ composition renamed every contested attachment of every multi-skin export at
2400
+ runtime, which `slot.attachment.name` shows a consumer and no gate could see; `diff`
2401
+ now reads it as `attachments.runtime_name`.
2402
+
2350
2403
  ๐Ÿšจ **Every skin that shares a placeholder has to be a named one โ€” the default
2351
2404
  skin may not be among them, and rigc refuses the rig if it is.** That is not a
2352
2405
  style rule; it is the editor's model, and two round trips through Spine
@@ -2377,18 +2430,9 @@ style rule; it is the editor's model, and two round trips through Spine
2377
2430
  are the same namespace, and both are `patch`.
2378
2431
 
2379
2432
  โ‡’ **The rule: move the shared art into a named skin.** Call it `base`. Every
2380
- filler of that placeholder is then a named skin, rigc composes all of them, and
2381
- the names are unique within the slot โ€” which is all
2382
- [#541](https://github.com/firejune/rigc/issues/541) needed: `base/patch`,
2383
- `zulu/patch` and `mike/patch` are three names. That shape is the one the editor
2384
- does hold: the same three fillers in named skins imported, exported and measured
2385
- **0.00 mean MAE** with names and paths intact.
2386
-
2387
- ๐Ÿ“Ž **The earlier reading, kept because it was reasonable and wrong.** Between the
2388
- two trips this guide said *compose off the default skin only* โ€” keep the default
2389
- skin's entry as its placeholder and name the others. Trip 7 supported it and trip
2390
- 8 refuted it: that is the spelling the editor refuses at the door. There is no
2391
- third spelling, which is why this is a refusal rather than a naming scheme.
2433
+ filler of that placeholder is then a named skin, which is the shape the editor
2434
+ holds. Stating a `name` on the default skin's entry does not lift the refusal: that
2435
+ is the first of the two spellings above.
2392
2436
 
2393
2437
  What to know about it, and nothing to author:
2394
2438
 
@@ -2408,39 +2452,27 @@ What to know about it, and nothing to author:
2408
2452
  restated as the join the renderer actually performs it was a tautology over
2409
2453
  the resolve check beside it
2410
2454
  ([#574](https://github.com/firejune/rigc/issues/574)).
2411
- - **`path` is restated, and it has to be.** `path` defaults to the attachment's
2412
- **name**, not to its placeholder, so an entry given a name and no path would
2413
- resolve its texture at `zulu/patch` and find no such region.
2414
- `A08_REGION_NAMES_MATCH_ATTACHMENTS` says so if it is ever dropped, naming the
2415
- skin, the slot, the placeholder and the path
2416
- ([#589](https://github.com/firejune/rigc/issues/589)); `A00_ROUNDTRIP_PARSE`
2417
- reported it in the parser's own words until then, and now defers to A08.
2418
- - **Only contested placeholders are touched.** One skin filling a placeholder, or
2419
- two skins filling a slot under *different* placeholders, emit exactly what they
2420
- always did โ€” every rig in this repository is byte-identical across the change.
2421
- - **A composed name that collides is a compile error, not a surprise.** If some
2422
- other placeholder in the same slot is literally called `zulu/patch`, rigc refuses
2423
- and names both sites rather than emitting two attachments with one name again.
2424
- The walk covers every *uncontested* entry's plain name too, the default skin's
2425
- included โ€” a name that composed nothing can still be the one another skin
2426
- composes.
2427
- (`/` is the separator because it appears in **0** of the 160 placeholder names and
2428
- 159 atlas region names in `examples/` and `gallery/`, where `-` appears in 85 and
2429
- `_` in 37.)
2455
+ - **A stated `name` with no `path` resolves the region the name spells.** `path`
2456
+ defaults to the attachment's **name**, not to its placeholder, so an entry naming
2457
+ `patch-base` and drawing `patch_a.png` needs a `path` โ€” and gets one, because the
2458
+ image basename differs from the name. `A08_REGION_NAMES_MATCH_ATTACHMENTS` names
2459
+ the skin, the slot, the placeholder and the path if the region is not there
2460
+ ([#589](https://github.com/firejune/rigc/issues/589)).
2430
2461
  - **Each skin's art is measured and atlased on its own.** The example above points
2431
- both skins at one PNG, so there is one region; point them at two and there are
2432
- two, each attachment's `path` resolving to the file that attachment named and its
2433
- `width`/`height` measured off that file. Until
2434
- [#555](https://github.com/firejune/rigc/issues/555) only the first skin's PNG was
2435
- ever opened, and the second skin's art reached neither the atlas nor the
2436
- measurement โ€” so name the two files **distinctly**, because the region name is
2437
- the basename and `a/patch.png` beside `b/patch.png` is refused (R5).
2438
-
2439
- โš ๏ธ **The uniqueness scope is the slot, not the skeleton.** `spineboy-pro.json`,
2440
- which the editor wrote, gives the name `head` to a region in slot `head` and to a
2441
- bounding box in slot `head-bb`, and reuses `hoverglow-small` across eight slots. So
2442
- a name shared between slots is normal and rigc leaves it alone; what #541 refused
2443
- was one slot holding two.
2462
+ the two skins at two PNGs, so there are two regions, each attachment's `path`
2463
+ resolving to the file that attachment named and its `width`/`height` measured off
2464
+ that file. Until [#555](https://github.com/firejune/rigc/issues/555) only the
2465
+ first skin's PNG was ever opened, and the second skin's art reached neither the
2466
+ atlas nor the measurement โ€” so name the two files **distinctly**, because the
2467
+ region name is the basename and `a/patch.png` beside `b/patch.png` is refused
2468
+ (R5).
2469
+
2470
+ โš ๏ธ **Nothing measured says the editor wants names unique within a slot.**
2471
+ `spineboy-pro.json`, which the editor wrote, gives the name `head` to a region in
2472
+ slot `head` and to a bounding box in slot `head-bb`, and reuses `hoverglow-small`
2473
+ across eight slots; the production rig above holds same-named attachments in one
2474
+ slot across its named skins. The one refusal the editor was measured making is the
2475
+ default-skin share, and that is the rule rigc enforces.
2444
2476
 
2445
2477
  ๐Ÿšจ **Once a rig has named skins, no instrument here can see them until you say
2446
2478
  which one** ([#571](https://github.com/firejune/rigc/issues/571)). `render` and
@@ -2449,7 +2481,10 @@ skin alone and the art you just moved into `base`, `zulu` and `mike` draws
2449
2481
  nothing at all. `check` then compares blank against blank and reports a perfect
2450
2482
  `0.0000` โ€” about the very placeholder this subsection is about. Pass
2451
2483
  `--skin <name>` to both, once per skin (**ยง9**); `tools/editor_roundtrip.ts`
2452
- loops over every skin the build declares for the same reason.
2484
+ loops over every skin the build and the export both declare for the same reason,
2485
+ and names a skin only one of them declares as **lost** (or **added**) **by the
2486
+ export**, quoting `diff`'s `attachments.skins`, without rendering it on either side
2487
+ ([#801](https://github.com/firejune/rigc/issues/801)).
2453
2488
 
2454
2489
  #### 3.4.3 `sequence` โ€” a numbered image series on one attachment
2455
2490
 
@@ -2506,11 +2541,18 @@ other than what was written (measured on spine-core 4.3.13,
2506
2541
  only on the three kinds that draw a region, so it would be dropped in silence. The
2507
2542
  refusal names region, mesh and linkedmesh.
2508
2543
 
2509
- `width` and `height` are the attachment's one size, every frame drawn into it. Omit
2510
- them and rigc takes the frames' size โ€” **only when every frame measures the same**;
2511
- frames of different sizes are refused until you state the size, because picking one
2512
- of them would be the compiler choosing a value. Under `--atlas-in` a stated size that
2513
- disagrees with a packed frame is refused, as it is for one region.
2544
+ `width` and `height` are the attachment's one size, every frame drawn into it. The
2545
+ frames **may differ in size**: the runtime scales each frame's region into the
2546
+ attachment's size (`RegionAttachment.computeUVs`, `width / region.originalWidth`), so a
2547
+ stated size is emitted as stated and compared with no frame, on the loose route and
2548
+ under `--atlas-in` alike. That is what an editor exports for a series that mixes image
2549
+ sizes โ€” the setup frame's size, the other frames as they are
2550
+ ([#795](https://github.com/firejune/rigc/issues/795)). Omit them and rigc takes the
2551
+ frames' size โ€” **only when every frame measures the same**; frames of different sizes
2552
+ are refused until you state the size, because picking one of them would be the
2553
+ compiler choosing a value. โš ๏ธ One region is different: its `width` is its image's size
2554
+ in every editor export, so under `--atlas-in` a stated size that disagrees with its
2555
+ packed region is still refused.
2514
2556
  `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` (ยง5.2) holds the block and
2515
2557
  every frame the timelines show against the file.
2516
2558
 
@@ -3548,14 +3590,48 @@ a delta from the constraint's own setting.
3548
3590
  somebody else's file means **0**.
3549
3591
  - โš ๏ธ `mass` is the one whose keyed number is not what the runtime stores. The
3550
3592
  key states a mass and the pose holds `1 / mass`, so a `mass` key of `0` is an
3551
- infinite inverse mass โ€” the constraint stops moving.
3593
+ infinite inverse mass โ€” every velocity and offset is NaN from the first step.
3552
3594
  - **Four of the seven are bounded, and a key outside its bound is a compile
3553
3595
  error** ([#610](https://github.com/firejune/rigc/issues/610)). `mass` must be
3554
- `> 0`, `damping` must be strictly inside `(0, 1)`, and `mix` and `strength`
3596
+ `> 0`, `damping` must be inside the closed `[0, 1]`, and `mix` and `strength`
3555
3597
  must be `0` or more. `A23_PHYSICS_CONSTRAINT_EFFECTIVE` applies the same four to
3556
3598
  a file rigc did not write, naming the animation, the constraint, the key time
3557
3599
  and the value โ€” so the compiler is where a spec you wrote is refused, and the
3558
3600
  assertion is where an import is.
3601
+ - ๐Ÿ”Ž **Each way out of a bound has a basis, and the refusal says which**
3602
+ ([#798](https://github.com/firejune/rigc/issues/798)). **arithmetic** means the
3603
+ runtime cannot compute the value โ€” an expression in the integrator is
3604
+ non-finite at it โ€” and the sentence names the expression and its lines;
3605
+ **behavioural** means the runtime runs it, finitely, and the rig runs wrongly,
3606
+ so refusing it is rigc's call and the sentence says so and says what the value
3607
+ does. Two of the eight ways out are arithmetic. The table is the
3608
+ `PHYSICS_POSE_RULES` rows' own `basis`, verbatim, and `CUR100` holds it to them;
3609
+ every basis is re-measured through spine-core by `T113` on every run.
3610
+
3611
+ | field | at rest | on a key | basis |
3612
+ | --- | --- | --- | --- |
3613
+ | `mix` | `> 0` | `>= 0` | behavioural: below 0 the jiggle is applied inverted, on `x` and `y` exactly the offset a positive mix of the same size applies, mirrored (`PhysicsConstraint.js:172,174`), and `PhysicsConstraintPose` documents mix as "a percentage (0+)" where a transform constraint documents its own as "unbounded"; behavioural: at 0 `update` returns before it does anything (`PhysicsConstraint.js:109-111`), so the constraint is muted |
3614
+ | `mass` | `> 0` | `> 0` | arithmetic: at 0 `massInverse = 1 / mass` is Infinity and `m = t * massInverse` multiplies every velocity update, so the first step takes every velocity and offset to NaN (`SkeletonJson.js:309, Animation.js:2140, PhysicsConstraint.js:149,156,211`); behavioural: below 0 `massInverse` is a finite negative, which flips the sign of every force the velocity update applies, so the restoring force pushes the offset away and it grows with every step |
3615
+ | `strength` | `> 0` | `>= 0` | behavioural: below 0 the restoring term is ADDED to the offset instead of taken out of it, so the offset is pushed away and grows with every step; behavioural: nothing pulls it back |
3616
+ | `damping` | `inside [0, 1]` | `inside [0, 1]` | behavioural: above 1 every velocity grows on every step and the offset diverges; arithmetic: at any fps where `60 / fps` is not whole it is a negative number raised to a fractional power, which is NaN (`PhysicsConstraint.js:148,210`) |
3617
+
3618
+ โš ๏ธ The line between the two is **non-finite within the walk that measures
3619
+ it**, not "non-finite eventually": [measured] on the generated physics
3620
+ fixture at 60 fps, a setup `damping` of 2 is finite for 1,064 steps before its
3621
+ velocity overflows, and a `mass` of โˆ’1 runs the offset to 2.7e6 in 120 steps
3622
+ finite on every one. Every run-away overflows at some horizon, which is why
3623
+ `damping` above 1 is behavioural although it diverges.
3624
+ - โš–๏ธ **A negative `mix` is refused on a physics constraint and accepted on a
3625
+ transform constraint, and neither is the runtime's arithmetic**
3626
+ ([#798](https://github.com/firejune/rigc/issues/798)). [measured] both are a
3627
+ finite, mirrored scale: a physics constraint resting at `mix` โˆ’0.5 moves its
3628
+ bone by exactly the opposite of +0.5 on every step, and a transform constraint
3629
+ at `mixRotate` โˆ’0.5 rotates its bone by the negative of +0.5's. What differs is
3630
+ the range the runtime documents for each field โ€” `PhysicsConstraintPose.mix` is
3631
+ "a percentage (0+)", `TransformConstraintPose.mixRotate` "a percentage
3632
+ (unbounded)" โ€” and each rule follows its own. `T114` holds the mirror and both
3633
+ documented ranges against the runtime; `T105` holds the transform side's
3634
+ acceptance.
3559
3635
  - โš ๏ธ **`damping`'s bound depends on the constraint's `fps`, and a rig played at
3560
3636
  60 fps hides it** ([#748](https://github.com/firejune/rigc/issues/748)). The
3561
3637
  decay is `damping ** (60 * step)` with `step` = `1 / fps`, so the exponent is
@@ -3564,9 +3640,19 @@ a delta from the constraint's own setting.
3564
3640
  whole number the same negative is raised to a fractional power, which is NaN.
3565
3641
  [measured] one planted `damping` key of โˆ’0.5 on the generated physics fixture
3566
3642
  stays finite at 60 fps and at 30 (exponent 2), and is NaN within three steps of
3567
- the key at 45 and at 120. `1` never decays and above `1` diverges at every
3568
- rate. The refusal names the exponent and the rate for this reason, and `T101`
3569
- holds it to the two-rate measurement.
3643
+ the key at 45 and at 120. Above `1` every velocity grows on every step and
3644
+ diverges at every rate. The refusal names the exponent and the rate for this
3645
+ reason, and `T101` holds it to the two-rate measurement.
3646
+ - โœ… **Both ends of `damping` are inside the bound**
3647
+ ([#794](https://github.com/firejune/rigc/issues/794)). `1 ** x` is 1 and
3648
+ `0 ** x` is 0 for every positive exponent, so neither depends on `fps`: at `1`
3649
+ the velocity never decays and the jiggle holds for as long as it runs, and at
3650
+ `0` every velocity is zeroed on every step, so the offset follows the bone with
3651
+ no overshoot. Both are finite, at rest and keyed โ€” [measured] 120 steps from
3652
+ `Physics.reset` at 60, 45 and 30 fps on the generated physics fixture, which
3653
+ `T109`โ€“`T111` hold. โš ๏ธ `1` is also what a 4.2 file means by saying nothing:
3654
+ 4.2's parser reads an omitted `damping` as `1` where 4.3's reads `0.85`
3655
+ (ยง4.6), so a rig migrated from 4.2 that keys "the default" keys `1`.
3570
3656
  - ๐Ÿšซ **`inertia`, `wind` and `gravity` are bounded nowhere, and neither is the top
3571
3657
  of `mix`.** The runtime documents no range for the first three, and
3572
3658
  `PhysicsConstraintPose` documents `mix` as "a percentage (0+)" โ€” so a negative
@@ -3971,11 +4057,16 @@ key times. One lag, one place.
3971
4057
  mass?, wind?, gravity?, mix?, fps?, limit? }`. These are emitted into the 4.3
3972
4058
  `constraints` array. Seven of them โ€” the six tuning numbers and `mix` โ€” can also
3973
4059
  be **keyed over time** as `tracks` entries naming this constraint (ยง4.4); this
3974
- table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and `damping โ‰ฅ 1`
4060
+ table is the value at rest, and a timeline overrides it while it plays. `mass: 0` becomes an infinite inverse mass and a `damping` above 1
3975
4061
  never settles โ€” both are `A23`, here and on every timeline key that states them
3976
- ([#610](https://github.com/firejune/rigc/issues/610)); a `damping` at or below 0
3977
- is refused as well, and below 0 whether it is NaN depends on `fps` โ€” ยง4.4
3978
- ([#748](https://github.com/firejune/rigc/issues/748)). โš ๏ธ `strength: 0` is `A23` **here and not on a key**:
4062
+ ([#610](https://github.com/firejune/rigc/issues/610)); a `damping` below 0
4063
+ is refused as well, and whether it is NaN depends on `fps` โ€” ยง4.4
4064
+ ([#748](https://github.com/firejune/rigc/issues/748)). `damping` is `[0, 1]`
4065
+ with both ends in: `1` never decays and `0` zeroes the velocity on every step,
4066
+ both finite at every rate ([#794](https://github.com/firejune/rigc/issues/794)).
4067
+ โš ๏ธ An omitted `damping` is `0.85` to 4.3's parser (`SkeletonJson.js:308` in
4068
+ 4.3.13) and `1` to 4.2's (`SkeletonJson.js:242` in 4.2.120), so a constraint
4069
+ migrated from 4.2 that stated nothing may arrive stating `1` โ€” which builds. โš ๏ธ `strength: 0` is `A23` **here and not on a key**:
3979
4070
  at rest it is a constraint nothing pulls back, and on a key it is a release somebody
3980
4071
  asked for, which ยง4.4 states with the measurement behind it
3981
4072
  ([#727](https://github.com/firejune/rigc/issues/727)). A negative `strength` is
@@ -5414,6 +5505,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
5414
5505
  | `a linked mesh states "uvs", "triangles", โ€ฆ, and a linked mesh has no geometry of its own` | ยง3.4 โ€” remove them, or remove `source` and author this as a mesh. The parser returns before `readVertices`, so those keys are read by nothing at all |
5415
5506
  | `"source" is "X", and skin "S" โ€ฆ slot "L" โ€ฆ holds 2: "a", "b"` | ยง3.4 โ€” `source` is the PLACEHOLDER the source is filed under, not its `name`. A clause after the skin and after the slot says whether each was stated or taken from the parser's default โ€” **the default skin** and **this attachment's own slot**, which is the pair that surprises |
5416
5507
  | `"slot" is "X", which the rig does not declare as a slot` / `"skin" is "X", โ€ฆ the rig declares no such skin` | ยง3.4 โ€” a link resolves both by name. Left to the round trip these are the runtime's `Source mesh slot not found` and `Skin not found`, which name neither the attachment nor where it looked |
5508
+ | `no "skin" is stated, so the parser looks for the source "X" in the default skin โ€” and this rig declares no default skin` | ยง3.4 โ€” state the `skin` the source is filed under. A link with no `skin` resolves through `SkeletonData.defaultSkin`, which is null in a file with no default skin, and the runtime reads `getAttachment` off it with a `TypeError` naming nothing. Before [#801](https://github.com/firejune/rigc/issues/801) every build carried an empty `default`, so the same link failed at the source instead |
5417
5509
  | `"source" is "X", which is itself a linked mesh, and a chain of them is refused` | ยง3.4 โ€” point `source` at the mesh. A chain resolves in file order and loads nothing at all in one of the two orders, silently |
5418
5510
  | `"source" is "X", which is a "region" attachment and not a mesh` | ยง3.4 โ€” a link takes another MESH's geometry; off any other type the runtime reads `undefined` and says nothing |
5419
5511
  | `โ€ฆ is a boundingbox and states a "sequence". A sequence is a numbered series of atlas regions, and only the 3 kinds that draw a region carry one โ€” region, mesh, linkedmesh โ€ฆ` | ยง3.4.3 โ€” put the series on a region or a mesh, or remove it |
@@ -5450,7 +5542,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
5450
5542
  | `physics constraint "*": the name is reserved โ€” โ€ฆ` / `` `physics."*"` names a physics constraint "*", and that name is reserved โ€ฆ `` | ยง3.5 โ€” `"*"` is the target of the timeline that names no constraint; rename the constraint |
5451
5543
  | `animation "A": group "G" lists "*", which is not a constraint but the target that names none โ€ฆ` | ยง4.4 โ€” write `"*"` as the track's `physics` field |
5452
5544
  | `animation "A" bone "X" translatex: key value must be an array of 1 number(s)` | the value shape must match the property (ยง4.4) |
5453
- | `animation "A" physics constraint "C" mass key at t=โ€ฆ is 0 (massInverse Infinity); must be > 0 โ€” โ€ฆ` | ยง4.4 โ€” a keyed physics value the runtime cannot use. The message names the bound and the `PhysicsConstraint.js` lines that make it one: `mass` is `> 0`, `damping` is inside `(0, 1)`, `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)). โš ๏ธ Those are the bounds a **key** is held to. A setup `strength` of `0` is refused too, but by `A23` rather than here, and with its own sentence โ€” `physics "C" has strength 0; nothing pulls it back` ([#727](https://github.com/firejune/rigc/issues/727)); a setup `strength` below 0 says the offset is pushed away and grows, the same arm this message quotes for a negative key ([#748](https://github.com/firejune/rigc/issues/748)). A `damping` key's sentence names the exponent `60 * step` and why a negative is NaN at any `fps` where `60 / fps` is not whole (ยง4.4) |
5545
+ | `animation "A" physics constraint "C" mass key at t=โ€ฆ is 0 (massInverse Infinity); must be > 0 โ€” โ€ฆ` | ยง4.4 โ€” a keyed physics value outside its row's bound. The message names the bound and then its basis ([#798](https://github.com/firejune/rigc/issues/798)): where the runtime's arithmetic fails, the expression and its lines (`mass` at 0 is the one here, and `damping` below 0); where the value runs and runs wrongly, that refusing it is rigc's call and what the value does. The bounds: `mass` is `> 0`, `damping` is inside `[0, 1]`, both ends included ([#794](https://github.com/firejune/rigc/issues/794)), `mix` and `strength` are `0` or more, and `inertia`/`wind`/`gravity` are bounded nowhere ([#610](https://github.com/firejune/rigc/issues/610)). โš ๏ธ Those are the bounds a **key** is held to. A setup `strength` of `0` is refused too, but by `A23` rather than here, and with its own sentence โ€” `physics "C" has strength 0; nothing pulls it back` ([#727](https://github.com/firejune/rigc/issues/727)); a setup `strength` below 0 says the offset is pushed away and grows, the same arm this message quotes for a negative key ([#748](https://github.com/firejune/rigc/issues/748)). A `damping` key's sentence names the exponent `60 * step` and why a negative is NaN at any `fps` where `60 / fps` is not whole (ยง4.4) |
5454
5546
  | `a key carries both a named easing and a raw curve; pick one` | R6 |
5455
5547
  | `last key carries an easing but has nothing to ease to` | drop `ease`/`curve` from the final key |
5456
5548
  | `key times must strictly increase (at t=โ€ฆ)` | including after `lag` and `stagger` |
@@ -5494,7 +5586,8 @@ or the key's position in its own track. These are the frequent ones, verbatim:
5494
5586
  | `deform โ€ฆ (t=โ€ฆ): transform <kind> states โ€ฆ, and every one of this attachment's N vertices evaluates to an offset of 0` | ยง4.11.1 โ€” the parameters state a deformation and the geometry sampled it to nothing; the message names the measured cause. A key that means the setup pose states the identity in its parameters, or carries no run |
5495
5587
  | `vertexCount is N, which is not a multiple of 3` | ยง3.4 โ€” a path's vertices are knots and handles read in groups of three: `3(K + 1)` open, `3K` closed |
5496
5588
  | `vertexCount is N and an open path needs at least 6` | ยง3.4 โ€” an open path drops its first and last point, so it needs six for one curve |
5497
- | `"lengths" is not authored โ€” rigc measures the setup arc length of each curve` | ยง3.4 โ€” delete the array; it is a measurement of the vertices above it |
5589
+ | `"lengths" has N entry(ies) where the parser sizes M โ€” vertexCount / 3 on โ€ฆ` | ยง3.4 โ€” `vertexCount / 3` entries on either shape; give the array the source states, or leave the field out and rigc measures it |
5590
+ | `"lengths"[i] is V, below โ€ฆ` / `"lengths"[i] is "โ€ฆ"; every entry is a finite cumulative length` | ยง3.4 โ€” the array is cumulative, so each entry is at least the one before it |
5498
5591
  | `rig constraint "X": slot "Y" has no path attachment in any skin` | ยง3.5.1 โ€” give that slot a `"type": "path"` attachment, or aim the constraint at the slot that has one |
5499
5592
  | `rig constraint "X": rotateMode is "CHAINSCALE"; known: Tangent, Chain, ChainScale` | ยง3.5.1 โ€” only the first letter's case is free; anything else resolves to `undefined` in the parser |
5500
5593
  | `rig constraint "X": applies animation "Y", which the motion spec does not declare (it declares: โ€ฆ)` | ยง3.5.2 โ€” fix the slider's `animation`, or add it to the motion spec |
@@ -5525,7 +5618,7 @@ or the key's position in its own track. These are the frequent ones, verbatim:
5525
5618
  | `N pair(s) of animation names have no one order: โ€ฆ "Fx/a" / "fx/b" (folder) โ€” "Fx/a" and "fx/b" sit in the sibling folders "Fx" and "fx", which the comparator leaves in one place โ€ฆ; rename one of the two folders so they differ by more than letter case, spacing or a leading zero` | **R10** โ€” rename until no pair is left. The kind in brackets says which of the three things the five stored round trips leave open decides the pair: `number` (two digit runs that are each one number written twice, pointing opposite ways), `separator` (a whitespace character that is not a space) or `folder` (two sibling folders the comparator cannot separate). rigc keys `animations` in the editor's own comparator, read off `fixtures/editor-order/probe{1..5}.{in,out}.json` ([#728](https://github.com/firejune/rigc/issues/728)) โ€” so a pair those files settle is emitted rather than refused, **including a pair that differs only in case**, whose order is then the one your spec declared. On the three that are left, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
5526
5619
  | `N pair(s) of skin names have no one order: โ€ฆ "Fx/a" / "fx/b" (folder) โ€” โ€ฆ` | **R11** โ€” rename until no pair is left. The same shape and the same three kinds as the row above, because it is the same comparator: two of the five round trips carried one name list as both collections and both came back in one order ([#728](https://github.com/firejune/rigc/issues/728)). โš ๏ธ This row was **wider** than R10's until then โ€” `Zulu`/`mike` and `mike10`/`mike2` built as animation names and were refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) โ€” and both build now |
5527
5620
  | `slot "patch": placeholder "patch" is filled by the "default" skin AND by skins "zulu", "mike", and the Spine editor has no way to hold that โ€ฆ Move the default skin's entry for this slot into a named skin โ€” call it "base"` | **R12** โ€” do what it says: move that entry out of `default` into a named skin. The editor has no representation for a placeholder the default skin shares with a named one, in either spelling, and ยง3.4.2 has both measurements. Renaming the placeholder does not help; the shape is what is refused |
5528
- | `N attachment name collision(s): a placeholder that more than one skin fills is emitted with the name "<skin>/<placeholder>" โ€ฆ slot "patch": skin "base" placeholder "zulu/patch" and skin "zulu" placeholder "patch" would both be named "zulu/patch"` | **R12** โ€” rename the placeholder or the skin. rigc composes an attachment name for every placeholder more than one skin fills (ยง3.4.2), and this fires when a composed name is one another entry in the same slot already answers to โ€” including a plain name in the default skin, which composed nothing. Both sites are named; either rename ends it |
5621
+ | `skin "S" slot "X" attachment "P": "name" is V, which is not a string. An attachment's name is the runtime's `Attachment.name` โ€ฆ` | ยง3.4 โ€” write the name as a string, or leave the key out and the attachment is named by its placeholder. It is written verbatim and `path` defaults to it, so a number there would reach the atlas as a region name nobody spelled |
5529
5622
 
5530
5623
  โš ๏ธ **One refusal in this section is not a `CompileError`, and it is `explain`'s.**
5531
5624
  `explain` prints the `DEFORM` block by **posing** the rig, and a pose resolves every
@@ -5623,7 +5716,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
5623
5716
  | `A04_MESH_TRIANGLES_AND_ENCODING` | both | authored mesh geometry: triangle count not a multiple of 3, an index out of range, or a `vertices` length that disagrees with `uvs` (the weighted/unweighted trap) **SKIP** when the skeleton carries no mesh attachment ([#580](https://github.com/firejune/rigc/issues/580)) |
5624
5717
  | `A05_CURVE_ARRAY_LENGTH` | both | a raw `curve` with the wrong number of values, a non-finite number in one, or a curve on a timeline that cannot take one. Four numbers **per value channel**. **SKIP** when no animation carries a timeline at all ([#580](https://github.com/firejune/rigc/issues/580)). Timelines with no `curve` on any key still PASS: every timeline name is checked against the channel table whether or not a curve sits on one |
5625
5718
  | `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | both โ—‘ | the atlas `size:` disagrees with the PNG on disk โ€” the message names the ratio it measured on **each** axis and the header that states the same art truthfully, and ยง0.2 has the whole of it ([#715](https://github.com/firejune/rigc/issues/715)): a runtime maps a region as a fraction of the DECLARED size and never reads the texture's own, so such a page draws, and what it breaks is every reader that addresses the page in texels. A uniform ratio is a `size:`/`scale:` pair away from honest and the message prints it; two ratios are not, because `scale:` carries one number, and the message says so rather than offering a header that would not work. **Or** a region's rectangle is not inside the page it names โ€” rotation honoured, so a region at `rotate: 90` or `270` occupies `height x width` of the page and a region that fits only because it is turned is inside it. The message names the region, the rectangle it occupies, the page and the page's size. That clause is **validity** and runs under both profiles ([#694](https://github.com/firejune/rigc/issues/694)): a rectangle outside its page makes `u2 > 1` and samples whatever the wrap mode returns, and `--atlas-in` already refuses the same rectangle at compile time (ยง0.2). Under `spine-html` also: `pma`, rotation, and two regions on one page over the same texels โ€” a packed page must be **one part covering it exactly** (the unpacked convention) or a **tiling** ([#266](https://github.com/firejune/rigc/issues/266)), and what that message names is the pair that shares texels. **Or** the page file is not a PNG at all, and then nothing about its size is measured: the message carries the page's path and the size the atlas declares, and names what the file is by its first bytes โ€” one of WebP, JPEG, GIF, KTX, KTX2 by its signature, or no image format rigc recognises, the bytes in hex either way โ€” or calls it **truncated** when it begins as a PNG and runs out before its `IEND` (ยง0.2, [#732](https://github.com/firejune/rigc/issues/732)). That clause is validity too: nothing in rigc can read such a page back. **SKIP** when the atlas declares no page ([#580](https://github.com/firejune/rigc/issues/580)) |
5626
- | `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. โš ๏ธ 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` |
5719
+ | `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. Two sentences are about blank lines and name two different repairs ([#803](https://github.com/firejune/rigc/issues/803)): `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: consecutive blank lines` is two blank lines side by side after the first page โ€” delete one. rigc writes neither: under `--atlas-in` the pack's blank lines are put in rigc's shape on re-emission (ยง0.2), so either sentence 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` |
5627
5720
  | `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. Until [#589](https://github.com/firejune/rigc/issues/589) they were unreachable: the loader threw first and the miss arrived as `A00_ROUNDTRIP_PARSE`. There is no `spine-html` clause here any more โ€” a placeholder is free to differ from the region its `path` names ([#574](https://github.com/firejune/rigc/issues/574)) **SKIP** when no attachment names a region *and* the atlas declares none โ€” both of its subjects at once ([#580](https://github.com/firejune/rigc/issues/580)) |
5628
5721
  | `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 |
5629
5722
  | `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** ([#733](https://github.com/firejune/rigc/issues/733)): 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 ([#580](https://github.com/firejune/rigc/issues/580)): the NaN is produced by stepping, and a static rig is never stepped โ€” the same subject `A09` skips on |
@@ -5639,7 +5732,7 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
5639
5732
  | `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)) |
5640
5733
  | `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 |
5641
5734
  | `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)) |
5642
- | `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, rests at `mix: 0` with **no timeline in any animation keying that `mix` above 0**, has `mass: 0`, has `strength` at or below 0 โ€” `0` says `nothing pulls it back`, below 0 says the offset `is pushed away and grows with every step`, both read off the row's `outside` arms, which the key's refusal quotes too ([#748](https://github.com/firejune/rigc/issues/748)) โ€” or has `damping` outside `(0, 1)` so it never settles โ€” **at rest, and on every physics timeline key** ([#610](https://github.com/firejune/rigc/issues/610), [#743](https://github.com/firejune/rigc/issues/743)). The timeline arm reads each key through the runtime's own `PhysicsConstraint*Timeline.set`, so a keyed `mass` is judged as the `massInverse` it becomes, and the detail names the animation, the constraint, the key time, the value and the bound. Two differences between the two arms, and the runtime is the reason for both: a **key** of `mix: 0` is accepted, because `update` opens with `if (mix === 0) return;` and muting a constraint for a stretch is what a mix timeline is for โ€” the editor's own `sack-pro` example keys it there on 24 of its 36 mix keys โ€” and a **key** of `strength: 0` is accepted, because it releases the constraint for the span with `damping` and `inertia` still applied and the next key pulls the offset back, measured through spine-core at no NaN, a coast to a limit and a return in 54 steps ([#727](https://github.com/firejune/rigc/issues/727)). As a **setup** value `strength: 0` is still refused by the arm above, and `mix: 0` is refused only when nothing keys it above 0. The `mix` branch above is why `mix` is the one setup value a key can answer for: at rest the constraint is **inert** rather than broken, so a rig that rests muted and is keyed above 0 is refused by nothing, while a rig resting at `mass: 0` is `massInverse` Infinity before anything plays and no key reaches back into that. The detail of the refusal says both halves and how many animations were searched: `physics "C" has mix 0 and none of the 3 animations keys its mix above 0; it is muted โ€” rest it above 0, or key its mix above 0 in an animation`. The search counts the unnamed global timeline for every constraint whose own `mixGlobal` is set, reads each key through the runtime's accessor, counts every sample of a Bezier between two keys as a value the timeline poses โ€” so two keys of 0 joined by a curve lifted above 0 are a rescue, measured to move the bone โ€” and takes an animation a slider applies like any other. It is the one reading `A36` and `A37` use as well ([#752](https://github.com/firejune/rigc/issues/752)). `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. `ingest` does not carry a constraint that drives no component into the spec it writes: it omits it with its timelines and reports `PHYSICS_DRIVES_NOTHING` ([INGEST ยง2.0](INGEST.md), [#731](https://github.com/firejune/rigc/issues/731)), so this sentence is met on a file, never on a decompiled rebuild. **SKIP** when the skeleton declares no physics constraint ([#580](https://github.com/firejune/rigc/issues/580)) โ€” the same sentence `A36` and `A37` have always printed for their own constraint types |
5735
+ | `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | both | a physics constraint that drives no component, rests at `mix: 0` with **no timeline in any animation keying that `mix` above 0**, has `mass: 0`, has `strength` at or below 0 โ€” `0` says `nothing pulls it back`, below 0 says the offset `is pushed away and grows with every step`, both read off the row's `outside` arms, which the key's refusal quotes too ([#748](https://github.com/firejune/rigc/issues/748)) โ€” or has `damping` outside `[0, 1]`, which says `` physics "C" has damping 1.5; must be inside [0, 1] โ€” the per-step decay is `damping ** (60 * step)`, and the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: above 1 every velocity grows on every step and the offset diverges ``, the bound read off the row the key's refusal reads; `1` and `0` are inside, since both are finite at every rate ([#794](https://github.com/firejune/rigc/issues/794)) โ€” **at rest, and on every physics timeline key** ([#610](https://github.com/firejune/rigc/issues/610), [#743](https://github.com/firejune/rigc/issues/743)). The timeline arm reads each key through the runtime's own `PhysicsConstraint*Timeline.set`, so a keyed `mass` is judged as the `massInverse` it becomes, and the detail names the animation, the constraint, the key time, the value and the bound. Two differences between the two arms, and the runtime is the reason for both: a **key** of `mix: 0` is accepted, because `update` opens with `if (mix === 0) return;` and muting a constraint for a stretch is what a mix timeline is for โ€” the editor's own `sack-pro` example keys it there on 24 of its 36 mix keys โ€” and a **key** of `strength: 0` is accepted, because it releases the constraint for the span with `damping` and `inertia` still applied and the next key pulls the offset back, measured through spine-core at no NaN, a coast to a limit and a return in 54 steps ([#727](https://github.com/firejune/rigc/issues/727)). As a **setup** value `strength: 0` is still refused by the arm above, and `mix: 0` is refused only when nothing keys it above 0. The `mix` branch above is why `mix` is the one setup value a key can answer for: at rest the constraint is **inert** rather than broken, so a rig that rests muted and is keyed above 0 is refused by nothing, while a rig resting at `mass: 0` is `massInverse` Infinity before anything plays and no key reaches back into that. The detail of the refusal says both halves and how many animations were searched: `` physics "C" has mix 0 and none of the 3 animations keys its mix above 0; the runtime runs this value finitely, so refusing it is rigc's call rather than the runtime's: at 0 `update` returns before it does anything (`PhysicsConstraint.js:109-111`), so the constraint is muted โ€” rest it above 0, or key its mix above 0 in an animation ``. โš ๏ธ Every reason A23 prints for a setup value is that value's row `basis` arm (ยง4.4's table, [#798](https://github.com/firejune/rigc/issues/798)) โ€” an arithmetic arm names its expression (`physics "C" has massInverse Infinity; mass must be > 0 โ€” at 0 โ€ฆ`), a behavioural one says it is rigc's call and what the value does. A setup `mix` **below** 0 is told the jiggle is applied inverted, not that it is muted: until #798 it read "it is muted", of a constraint measured moving its bone by exactly the opposite of a positive mix (`T115`). The search counts the unnamed global timeline for every constraint whose own `mixGlobal` is set, reads each key through the runtime's accessor, counts every sample of a Bezier between two keys as a value the timeline poses โ€” so two keys of 0 joined by a curve lifted above 0 are a rescue, measured to move the bone โ€” and takes an animation a slider applies like any other. It is the one reading `A36` and `A37` use as well ([#752](https://github.com/firejune/rigc/issues/752)). `inertia`, `wind`, `gravity` and the top of `mix` are bounded nowhere, at rest or keyed. `ingest` does not carry a constraint that drives no component into the spec it writes: it omits it with its timelines and reports `PHYSICS_DRIVES_NOTHING` ([INGEST ยง2.0](INGEST.md), [#731](https://github.com/firejune/rigc/issues/731)), so this sentence is met on a file, never on a decompiled rebuild. **SKIP** when the skeleton declares no physics constraint ([#580](https://github.com/firejune/rigc/issues/580)) โ€” the same sentence `A36` and `A37` have always printed for their own constraint types |
5643
5736
  | `A24_AXIS_SPACE_STROKE` | archetype | a bone under the rig's `axisBone` was keyed with a screen-space Y component, or the axis bone itself was keyed. **SKIP** when the rig declares no axis bone, and also when no animation keys that bone or anything under it ([#580](https://github.com/firejune/rigc/issues/580)) |
5644
5737
  | `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares `detached` is a descendant of the bone it must never hang under |
5645
5738
  | `A26_SLOT_DRAW_ORDER` | archetype | the emitted slots are not the rig's slot table โ€” a slot is out of order, is not in the table at all, or is in the table and missing from the skeleton (ยง3.3). **SKIP** when the rig declares no canonical slot order. โš ๏ธ A skeleton with **no** slot beside a rig that declares some is **not** a skip, and it is the one rule in this family where an empty loop is not a vacuous pass ([#580](https://github.com/firejune/rigc/issues/580)): the completeness clause reads it as every declared slot lost and names them, which is the maximal case of what [#575](https://github.com/firejune/rigc/issues/575) filed |
@@ -5700,7 +5793,6 @@ are `CompileError`s, and they name what the format actually defines
5700
5793
  | attachment `type` of anything else โ€” `sequence`, a typo | `attachment type "X" is not one of the 7 the Spine 4.3 format defines (region, mesh, linkedmesh, boundingbox, path, point, clipping). โ€ฆ the attachment is dropped from the skeleton without a word` โ€” a **`CompileError`**, not a deferral: rigc is not going to implement a name the format does not have. (`sequence` is a key on a region or a mesh, not a type of its own.) |
5701
5794
  | `"type": null` | `"type" is null, which is not a name. โ€ฆ PRESENT-and-null is not absent: getValue(map, "type", "region") takes the default only when the key is missing, so this map matches no case, readAttachment returns null, and the attachment is dropped from the skeleton without a word. Remove the key, or name a type.` Leaving the key **out** is legal and reads as `region`; writing it as `null` is not the same thing ([#577](https://github.com/firejune/rigc/issues/577)) |
5702
5795
  | constraint `type` of anything else | `constraint type "X" is not one Spine 4.3 knows. The five are: ik, transform, path, physics, slider.` โ€” all five are emitted, so this is a typo, and a typo is what the parser drops in silence |
5703
- | a path attachment's `lengths` | `"lengths" is not authored โ€” rigc measures the setup arc length of each curve off the geometry` (ยง3.4). Not a deferral: a second copy of a number the vertices already fix |
5704
5796
  | any key neither format has, anywhere in either file | `<object> has a key this compiler does not read: "x" (did you mean "y"?) โ€ฆ Known here: โ€ฆ` (ยง5.1). Not a deferral either: a key nothing reads is a value you wrote and the emitted skeleton does not contain |
5705
5797
 
5706
5798
  Two more limits that are not errors but will shape what you can attempt:
@@ -8025,8 +8117,10 @@ evidence that the format will carry what you write.
8025
8117
  so do its `position`, `spacing` and three-channel `mix` timelines โ€” all three
8026
8118
  channels of every `mix` key, and all twelve curve numbers on each key that
8027
8119
  carries a curve. โš ๏ธ Its `lengths` did **not**, which is the last bullet โ€” and
8028
- since [#560](https://github.com/firejune/rigc/issues/560) they do, because rigc
8029
- now emits the numbers the editor recomputes rather than numbers near them.
8120
+ since [#560](https://github.com/firejune/rigc/issues/560) they do on that rig,
8121
+ because rigc emits the computation the editor runs rather than one near it.
8122
+ What they are computed ON is a second question, which the round trip could not
8123
+ ask and #804 did: see the end of this section.
8030
8124
  - ๐Ÿ”ฌ **`physics.mix` and `physics.reset` timelines survive**, the `reset` key
8031
8125
  included โ€” a key that carries a time and no value at all โ€” and so does the
8032
8126
  physics constraint's setup `mix`.
@@ -8079,9 +8173,33 @@ out of the artifact.
8079
8173
 
8080
8174
  ๐Ÿ”ฌ **And the editor always writes `vertexCount / 3` entries, computing the
8081
8175
  wrap-around curve even on an open path** โ€” that open path's fourth entry,
8082
- `2136.228`, is the closed-chain cumulative. โš ๏ธ Neither array is wrong: the parser
8083
- allocates `vertexCount / 3` and copies whatever is there, and `PathConstraint`
8084
- reads at most `lengths[curveCount]`, so the trailing entry is never read.
8176
+ `2136.228`, is the closed-chain cumulative. The parser allocates `vertexCount / 3`
8177
+ and copies whatever is there, and `PathConstraint` reads at most
8178
+ `lengths[curveCount]`, so the trailing entry is never read โ€” and since
8179
+ [#804](https://github.com/firejune/rigc/issues/804) rigc writes it too, measured
8180
+ over the closed chain, so the `gallery/ride` build ends on the same `2136.228`.
8181
+
8182
+ ๐Ÿšจ **"Every digit the editor printed" was true of two rigs and false in
8183
+ production** ([#804](https://github.com/firejune/rigc/issues/804)). Both rigs above
8184
+ are unweighted, unscaled and unconstrained, and the computation is only half of
8185
+ the number โ€” the other half is the geometry it is fed, and there the editor and
8186
+ rigc differed twice:
8187
+
8188
+ - **Scale.** rigc blended weighted vertices through setup matrices that ignored
8189
+ bone scale, shear and `inherit`: a 50/50-weighted probe path over a bone at
8190
+ scale 2 measured 0.752ร— the runtime's own `curves`. The matrices are the
8191
+ runtime's now, and an omitted `lengths` measures within 1e-4 of `curves`
8192
+ (`PS186`).
8193
+ - **Constraints.** The editor measures the pose the first update hands the path
8194
+ constraint, with every constraint ordered before it applied; a transform
8195
+ constraint on the path's slot bone ordered after it moves nothing. rigc does
8196
+ not pose, so it cannot reproduce that โ€” an omitted `lengths` is the
8197
+ **unconstrained** setup figure (`PS190`), and on a path a constraint moves at
8198
+ rest that is not the editor's number.
8199
+
8200
+ โ‡’ **A stated `lengths` is carried as stated**, and `ingest` writes the source's
8201
+ array into the spec, so a rebuilt export is its export on this field. What rigc
8202
+ still measures is an array you left out of a spec you wrote.
8085
8203
 
8086
8204
  โ‡’ **What this means for you.** `lengths` is the one number in a path rig you
8087
8205
  cannot check by looking: `diff` does not compare it, and `A33` asks only that it