spine-rigc 1.5.1 → 1.5.3
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 +43 -10
- package/docs/MOTION.md +125 -78
- package/package.json +1 -1
- package/src/compile.ts +36 -1
- package/src/keys.ts +286 -4
- package/src/motion.ts +137 -3
- package/src/pose.ts +53 -0
- package/src/rig.ts +270 -36
- package/src/types.ts +70 -0
- package/src/validate.ts +112 -56
package/docs/AUTHORING.md
CHANGED
|
@@ -1306,6 +1306,18 @@ past that line would be emitted as Infinity, which JSON writes as `null`: `1e309
|
|
|
1306
1306
|
both refused by the field they are in, before anything is compiled (§5.1). The cut
|
|
1307
1307
|
manifest and the motion spec are held to the same line.
|
|
1308
1308
|
|
|
1309
|
+
🔤 **Every field below holds one JSON type, and a value of another is refused.**
|
|
1310
|
+
Where a table says a field is a position, an angle, a scale, a mix, a count, a
|
|
1311
|
+
size or an index, it is a JSON **number** — `5`, never `"5"`; a flag is `true` or
|
|
1312
|
+
`false`, never `"true"` or `1`; a name or a path is a string; a list of vertices,
|
|
1313
|
+
uvs or triangles is an array of numbers, typed element by element. A JSON `null`
|
|
1314
|
+
is a value, not an absence, and is accepted only where a table below says what
|
|
1315
|
+
`null` means. The type of every key, shape by shape, is the table `RIG_TYPES` in
|
|
1316
|
+
[`src/rig.ts`](../src/rig.ts) (`MOTION_TYPES` in [`src/motion.ts`](../src/motion.ts),
|
|
1317
|
+
`MANIFEST_TYPES` in [`src/types.ts`](../src/types.ts)), and the refusal names the
|
|
1318
|
+
field, the type it found and the type it required (§5.1) — so that sentence, not a
|
|
1319
|
+
column restated beside each row here, is what to act on.
|
|
1320
|
+
|
|
1309
1321
|
🚫 **Every example value below is invented.** Names, coordinates, vertex lists and
|
|
1310
1322
|
payloads in this guide are written to illustrate a field, never copied out of a
|
|
1311
1323
|
reference export — an example lifted from one would be handing an authoring agent an
|
|
@@ -1411,7 +1423,7 @@ it would simply be a second root.
|
|
|
1411
1423
|
| `skin` | `BoneData.skinRequired` | `false` |
|
|
1412
1424
|
| `color` | `rrggbbaa`, editor affordance | — |
|
|
1413
1425
|
| `icon` | the editor's icon for this bone, e.g. `arrowsB`; editor affordance, no rendering effect. Copied through verbatim — no assertion checks the name, because the icon vocabulary is the editor's and an unknown one is not an error | — |
|
|
1414
|
-
| `from` | **rigc extension** — take `x`/`y`
|
|
1426
|
+
| `from` | **rigc extension** — take `x`/`y` from a cut manifest: `from.anchor`, `from.slotWindow` or `from.meshCenter`, exactly one. `from.rotation` is `"axis"` (the manifest's `axis.deg`, negated into y-up) or `"anchor"` (the third element of the named anchor, a screen-space facing angle); any other value is refused by name — it used to build as no rotation at all | — |
|
|
1415
1427
|
|
|
1416
1428
|
⚠️ Spine 4.0/4.1 called `inherit` **`transform`**. That old key still *loads* in 4.3
|
|
1417
1429
|
and the inheritance silently falls back to Normal — assertion `A02` refuses it.
|
|
@@ -1791,7 +1803,7 @@ region, a trigger volume — that moves with the skeleton and draws nothing.
|
|
|
1791
1803
|
| `vertexCount` | **required**, 3 or more. No default — the paragraph above is why |
|
|
1792
1804
|
| `vertices` | the unweighted `x, y` run, in the slot bone's local space; or the index-encoded weighted run, behind `boneIndexing` |
|
|
1793
1805
|
| `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` |
|
|
1794
|
-
| `boneIndexing` | `"name"` (the default) or `"raw"`, which opts a weighted `vertices` run into Spine's index encoding. `"raw"` beside `weights` is refused: `weights` always binds by name |
|
|
1806
|
+
| `boneIndexing` | `"name"` (the default) or `"raw"`, which opts a weighted `vertices` run into Spine's index encoding. `"raw"` beside `weights` is refused: `weights` always binds by name. Any other value — `"named"` included — is refused by name; it used to build as `"name"` |
|
|
1795
1807
|
| `color` | `rrggbbaa`. **No default in the file** — omitted, the parser never calls `setFromString` and the attachment keeps the runtime's own colour. It is an editor affordance, the colour the polygon is drawn in there; rigc emits it verbatim when stated and leaves the key out when not |
|
|
1796
1808
|
|
|
1797
1809
|
**Clipping polygon** ([Spine: clipping](http://esotericsoftware.com/spine-clipping)) —
|
|
@@ -1849,7 +1861,8 @@ is the wrap-around curve from `x = 180` back to `x = 0`, which nothing reads. Th
|
|
|
1849
1861
|
`worldX = 45.000000`.
|
|
1850
1862
|
|
|
1851
1863
|
The generators are `ring`, `ribbon`, `contour`, `grid` and `segments` (see
|
|
1852
|
-
[`src/mesh.ts`](../src/mesh.ts))
|
|
1864
|
+
[`src/mesh.ts`](../src/mesh.ts)), and a generator's `kind` is required: any other
|
|
1865
|
+
value, or none, is refused naming the five; the first two encode a deformation model rather
|
|
1853
1866
|
than a table of numbers, which is why they are code invoked by data. `contour`
|
|
1854
1867
|
and `grid` are geometry: they pin every vertex to the slot bone and exist to give
|
|
1855
1868
|
a `deform` timeline (§4.12) somewhere to push. `segments` is both at once — a
|
|
@@ -5438,11 +5451,12 @@ out of it.)
|
|
|
5438
5451
|
|
|
5439
5452
|
🔢 **A fourth surface is a pose that is not finite**, which `render` and `check`
|
|
5440
5453
|
refuse before drawing anything, naming the bone or vertex and its value.
|
|
5441
|
-
`A10_NO_NAN_AFTER_STEPPING` reads the same scan, so
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5454
|
+
`A10_NO_NAN_AFTER_STEPPING` reads the same scan, so `validate` gets there first and
|
|
5455
|
+
in the same words: a bone `rotation` of `1e309`, in the setup pose or in a key,
|
|
5456
|
+
leaves the bone's world position finite and its matrix `NaN`, and A10 names the bone
|
|
5457
|
+
and the term. That holds on a skeleton with no animation too — A10 reads its setup
|
|
5458
|
+
pose, which is the whole of what a static rig shows, and says on the stats line that
|
|
5459
|
+
it had nothing to step (`nanStepping=skipped`). What it does not see is a skin other
|
|
5446
5460
|
than the default, which A10 does not pose. The sentence, and where to look for the
|
|
5447
5461
|
number, is under *nothing to draw* in §3.4.
|
|
5448
5462
|
|
|
@@ -5569,7 +5583,9 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
5569
5583
|
| `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 |
|
|
5570
5584
|
| `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 |
|
|
5571
5585
|
| `bone "X" names parent "Y", which is not declared before it` | move `Y` earlier in `bones` |
|
|
5572
|
-
| `<file>: <where> is
|
|
5586
|
+
| `<file>: <where> is a string "5"; a number is required` (also `is a boolean true`, `is an array [1]`, `is null`, `is a number 7; a string is required`, `…; an array of numbers is required`, `…; a boolean (true or false) is required`, `…; a string or null is required`) | §3 / §4 — write the value as the JSON type its field holds: a number as a JSON number (`5`, not `"5"`), a flag as `true`/`false`, a name as a string. Every field of the three input files has one type, and a value of another is refused by the field before any reader does arithmetic on it — which it used to: `"x": "5"` built as `5`, `true` as `1` and `[1]` as `1`, and a `null` built too, all green. An array is typed element by element, so the refusal names the index (`weights[3][1].x`, `vertices[0]`). `null` is refused as `null` except where the field states it means something (`skeleton.width`/`height`, `skeleton.audio`, a slot's and a setup entry's `attachment`, a deform key's `vertices`, the manifest's `image`, `states`, `contact_depth` and `cap_containment_ceiling`). `<where>` is the rig spec's wording (`bone "hip" x`, `skin "default" slot "s" attachment "a" rotation`, `constraint "aim" mix`), the dotted path in the cut manifest (`crop.h`, `parts[2].offset[0]`), and the motion spec's own path (`` `animations."walk".ik[0].keys[0].mix` ``). In the rig spec and the manifest it runs right after the key scan and before every other check, so a field check that used to name a type (a mesh name that is not a string, a `lengths` entry spelt as a string) now reads as this row; in the motion spec it runs after that file's own field checks, which keep their sentences. A closed set of names is not a type, and is the next row's |
|
|
5587
|
+
| `<file>: <where> is "foo"; one of "name", "raw" — anything else was read as …` (also `bone "b" from.rotation is 5; one of "axis", "anchor"`, the manifest's `parts[1].mesh.kind is "foo"; one of "ring", "ribbon"`, and `skin "s" slot "x" attachment "a" generator.kind is "foo"; one of "ring", "ribbon", "contour", "grid", "segments"`, or `… generator.kind is absent; …`) | §3.2 / §3.4 — write one of the names listed. A field that holds one of a closed set of names is refused, with the set, when it holds anything else — a number too, since a number is not a name. Most such fields have a reader that refuses by name with its own sentence (`blend`, `inherit`, a constraint's `type`, `scaleY`); these four had none, and each built green or crashed: `boneIndexing` read as `"name"`, `from.rotation` as no rotation, the manifest's `mesh.kind` as a ribbon by one reader and a ring by two others, and a generator `kind` threw a `TypeError`. The sentence after the dash says what the value used to be read as, which is the build you had before this refusal. The first three run right after the type row's walk, before every other check; the generator's `kind` is refused earlier, by the key scan, because it chooses which keys the rest of the generator may hold |
|
|
5588
|
+
| `<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 the row above refuses it as one |
|
|
5573
5589
|
| `two bones are called "X"` | bone names are the join key; rename one |
|
|
5574
5590
|
| `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 |
|
|
5575
5591
|
| `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 |
|
|
@@ -5795,7 +5811,7 @@ Fix A00 and run it again.
|
|
|
5795
5811
|
| `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` |
|
|
5796
5812
|
| `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 |
|
|
5797
5813
|
| `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 |
|
|
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 **
|
|
5814
|
+
| `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. 🧍 **A skeleton with no animation is still read**: its setup pose is the whole of what it shows, so A10 reads it — the world transform and the vertices as above, and the bone's mode and the slot colours a stepped frame is read for, which on a static rig nothing else would reach — and names it `the setup pose` (`the setup pose: slot "x" colour is non-finite`). The stepping half had nothing, and a PASS row carries no detail, so the stats line says so: `nanStepping=skipped`, beside `animations=0`. `A09` does not follow — a declared duration is a fact about an animation and nothing else, so a static rig still gives it nothing. **SKIP** only when the skeleton carries **no bone and no animation**: there is then no pose to read and nothing to step |
|
|
5799
5815
|
| `A11_NO_CLIPPING_ATTACHMENTS` | renderer | a clipping attachment; the target renderer skips them silently |
|
|
5800
5816
|
| `A12_NO_DARK_COLOR` | renderer | a slot `dark` colour or an `rgba2`/`rgb2` timeline; parsed, then ignored |
|
|
5801
5817
|
| `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 |
|
|
@@ -8936,6 +8952,21 @@ quarter alone found 234, losing the native arms #877 was made for. The one lost
|
|
|
8936
8952
|
margin reading again: the blurred 32 px front shin (walk) now reports its truth as its
|
|
8937
8953
|
best (0.07 px, 0.00968), with its old 0.51 px answer 0.0045 above it.
|
|
8938
8954
|
|
|
8955
|
+
Issue #892 asked whether the coarse field, which keeps **one rotation per anchor
|
|
8956
|
+
cell**, loses the truth more often than on that post, and the answer is no. The
|
|
8957
|
+
tool now records, per trial, the coarse seed nearest the truth, the rotation the
|
|
8958
|
+
field kept for its cell and the field's own score there at the truth's rotation.
|
|
8959
|
+
Within 2.4 px of the truth that seed is a half turn off on 2 of the 238 found
|
|
8960
|
+
trials (a mouth at 48 px, both found anyway) and on none of the 6 misses, and
|
|
8961
|
+
every failed trial whose nearest seed is a quarter or half turn off but one is a
|
|
8962
|
+
flat part, none of them a miss. On the post itself, the upright rung scores 0.305
|
|
8963
|
+
at that cell against the half turn's 0.168, so a second rotation per cell would
|
|
8964
|
+
need a band 1.8 times wide to carry it. Re-gridding the rotation at the coarse level
|
|
8965
|
+
instead of the next one took the grid to 236 found (7 gained, 9 lost), and
|
|
8966
|
+
suppressing minima only within a rotation family took it to 231 (2 gained, 10
|
|
8967
|
+
lost). Both lose trials far from the truth, so neither shipped and the field is
|
|
8968
|
+
unchanged.
|
|
8969
|
+
|
|
8939
8970
|
⚠️ **What this is and is not.** The grid is not a reliability line: six native cells
|
|
8940
8971
|
place 14 or 15 of 15 and the trials at `detail` 3 and over place 65 of 66, but the
|
|
8941
8972
|
goggles still place 13 of 42, and a threshold read off two dozen parts of one figure
|
|
@@ -8962,6 +8993,8 @@ no miss at all, and `PO23` places the 48 px fist #865 was made for; `PO28` and
|
|
|
8962
8993
|
cutoff, and `PO26` holds every polish to strictly descending residuals, read through
|
|
8963
8994
|
the same trace. `PO31` places §6's post at the old placement from the page's own
|
|
8964
8995
|
input bytes, and `PO32` holds it to taking the quarter-rung escape and reading the same twice.
|
|
8996
|
+
`PO33` holds the coarse row the tool reads rotations from to the field each seed was
|
|
8997
|
+
picked from.
|
|
8965
8998
|
|
|
8966
8999
|
## 12. Reading the half of that picture `pose` refuses — `rigc chainfit`
|
|
8967
9000
|
|
package/docs/MOTION.md
CHANGED
|
@@ -614,7 +614,7 @@ quietly, and every in-between hung off it swings about the wrong centre.
|
|
|
614
614
|
further than you would accept as a bone position, treat it as the next case.
|
|
615
615
|
- **Δ < 20°** — ⛔ **do not use the solve.** Take the default below and **say in your
|
|
616
616
|
log that the pivot was defaulted and why** — the number, not the word: *"flag hinge
|
|
617
|
-
defaulted; relative rotation changed 10.
|
|
617
|
+
defaulted; relative rotation changed 10.5° between the two poses, amplification 5.5×"*.
|
|
618
618
|
- **Δ = 0** (a part that only translates, or a rigid pair) — the pivot is not a
|
|
619
619
|
quantity the pictures contain at all. Default it.
|
|
620
620
|
|
|
@@ -632,10 +632,13 @@ actually see, which is why they beat an ill-conditioned solve:
|
|
|
632
632
|
exactly why it gets said out loud.
|
|
633
633
|
|
|
634
634
|
📌 **Then check the default the cheap way: it should agree with itself across the
|
|
635
|
-
poses.** Take your chosen pivot point into the parent's
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
635
|
+
poses.** Take your chosen pivot point into the parent **bone's** frame once per pose —
|
|
636
|
+
origin at the parent's own pivot, not at the centre of its image. A real hinge is
|
|
637
|
+
*fixed* there, so the readings should differ by about your placement noise; if they
|
|
638
|
+
differ by several pixels, the point you picked is not the hinge. §6 runs this check on
|
|
639
|
+
a real pair and gets 0.25 px. ⚠️ The check cannot see the origin: measured from the
|
|
640
|
+
wrong one, the readings agree exactly as well, because a constant offset agrees with
|
|
641
|
+
itself. §6 shows both, and what the wrong one draws.
|
|
639
642
|
|
|
640
643
|
### 3.10 📗 Secondary action, and 🧩 what it costs here
|
|
641
644
|
|
|
@@ -817,12 +820,15 @@ rigc pose
|
|
|
817
820
|
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
821
|
```
|
|
819
822
|
|
|
820
|
-
⚠️ **The post came back at scale 0.
|
|
821
|
-
|
|
822
|
-
most of its area in one colour, so a shrunken copy sitting inside the
|
|
823
|
-
those pixels nearly as well, and
|
|
824
|
-
|
|
825
|
-
|
|
823
|
+
⚠️ **The post came back `AMBIG`: a best at scale 0.971 and an alternate at 0.691, 7.3 px
|
|
824
|
+
higher, trailing it by a spread of 0.0095.** That is §2.2's caveat in the open. The post
|
|
825
|
+
is a long part with most of its area in one colour, so a shrunken copy sitting inside the
|
|
826
|
+
real post explains those pixels nearly as well, and the report's own `ambiguous:` line
|
|
827
|
+
says size and texture do not account for how close the two come. The report cannot say
|
|
828
|
+
which one is true, and it says so: it names both and asks for a narrower window. The
|
|
829
|
+
picture can. The arm and the flag read 0.970 and 0.955, so the picture is at the art's
|
|
830
|
+
own resolution, and a post at 0.69 would be drawn at about 0.7 of the size of everything
|
|
831
|
+
bolted to it. ⇒ Narrow, and say so:
|
|
826
832
|
|
|
827
833
|
```bash
|
|
828
834
|
rigc pose --images parts --frame poseA.png --scale 0.85,1.2 --out poseA.json
|
|
@@ -859,18 +865,20 @@ rigc pose
|
|
|
859
865
|
|
|
860
866
|
Three things to read out of that pair, none of which is a score:
|
|
861
867
|
|
|
862
|
-
- **Scale.** All six readings sit in 0.
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
868
|
+
- **Scale.** All six readings sit in 0.956–0.995, a spread of about 4 %. The one part
|
|
869
|
+
read twice at one place, the post, reads 0.971 and 0.995 — 2.5 % apart on a part that
|
|
870
|
+
does not move — so a 4 % spread is the size of the method's own repeatability rather
|
|
871
|
+
than six different scales. ⇒ Take the pictures as being at the art's own resolution
|
|
872
|
+
and author the rig in **part pixels**, so no scaling appears in the spec at all. Say
|
|
873
|
+
that this is what the spread was read as.
|
|
866
874
|
- **Draw order, from `unexplained`.** The post reads **16 %** unexplained in pose A and
|
|
867
875
|
**9 %** in pose B, at placements that barely move — §2.2's occlusion signature. The arm
|
|
868
876
|
crosses more of the post in pose A, so the arm is **in front of** the post. The flag
|
|
869
|
-
reads 0 %
|
|
877
|
+
reads 0 % in both: nothing covers it, so it is **in front of** the arm. ⇒ Slots in
|
|
870
878
|
the order `post`, `arm`, `flag` (AUTHORING R4 — the slots array *is* the draw order,
|
|
871
879
|
and there is nowhere else in the file to say it).
|
|
872
880
|
- **The post does not move**, so its two readings are two measurements of one number:
|
|
873
|
-
x 79.9/80.0 and y 148.
|
|
881
|
+
x 79.9/80.0 and y 148.5/148.0. ⇒ Use the mean, **(79.95, 148.25)**, and treat the 0.5 px
|
|
874
882
|
disagreement as the noise floor for every other number on the page.
|
|
875
883
|
|
|
876
884
|
### 3. Convert, and derive the rig
|
|
@@ -880,33 +888,59 @@ Three things to read out of that pair, none of which is a score:
|
|
|
880
888
|
|
|
881
889
|
| | pose A, Spine world | pose B, Spine world |
|
|
882
890
|
| --- | --- | --- |
|
|
883
|
-
| `post` | x 79.9 · y 51.
|
|
884
|
-
| `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.
|
|
885
|
-
| `flag` | x 104.7 · y 43.9 · rot −84.
|
|
891
|
+
| `post` | x 79.9 · y 51.5 · rot 0.1° | x 80.0 · y 52.0 · rot 0.0° |
|
|
892
|
+
| `arm` | x 91.9 · y 77.2 · rot −61.9° | x 102.5 · y 105.2 · rot 18.3° |
|
|
893
|
+
| `flag` | x 104.7 · y 43.9 · rot −84.1° | x 137.4 · y 114.0 · rot 6.6° |
|
|
886
894
|
|
|
887
895
|
**The shoulder, by §3.9's solve.** The arm's screen rotation changes from 61.9° to
|
|
888
|
-
−18.
|
|
889
|
-
1.
|
|
890
|
-
102.
|
|
891
|
-
pixel **(6.
|
|
892
|
-
world that is (80.
|
|
896
|
+
−18.3°, so **Δ = 80.2°** — well inside the *solve it* band, `|det| = 4·sin²(40.1°) =
|
|
897
|
+
1.660`, amplification 0.78×. Solving the 2×2 puts the fixed point at frame **(80.57,
|
|
898
|
+
102.51)**, reconstructing identically from both poses, and the offset lands at arm-image
|
|
899
|
+
pixel **(6.76, 7.43)** — inside the arm's own hub, which is where a hub is for. In Spine
|
|
900
|
+
world that is (80.57, 97.49); in the post bone's local space, **(0.62, 45.74)**. That
|
|
901
|
+
point is the `arm` bone's origin, and it is the origin of everything measured in the
|
|
902
|
+
arm's frame below.
|
|
893
903
|
|
|
894
904
|
**The flag hinge, by §3.9's default — and this is the interesting one.** The relative
|
|
895
|
-
angle across that joint is 22.
|
|
896
|
-
`|det| = 0.
|
|
897
|
-
band. Run it anyway, to see what it would have cost — it
|
|
898
|
-
|
|
899
|
-
draws its hinge as a dark hoist strip down one edge, whose centre
|
|
900
|
-
**(2.5, 10)
|
|
901
|
-
**
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
+
angle across that joint is 22.2° in pose A and 11.7° in pose B, so **Δ = 10.5°**:
|
|
906
|
+
`|det| = 0.0335`, amplification **5.5×**, comfortably inside the *do not use the solve*
|
|
907
|
+
band. Run it anyway, to see what it would have cost — it puts the hinge at **(49.15,
|
|
908
|
+
−0.38)** in the arm bone's frame, exactly as confidently as the shoulder did. The default
|
|
909
|
+
instead: the flag's art draws its hinge as a dark hoist strip down one edge, whose centre
|
|
910
|
+
is flag-image **(2.5, 10)**. Carry that point through each pose's placement into the
|
|
911
|
+
**arm bone's frame** — origin at the shoulder pivot above, x along the arm, y up: take
|
|
912
|
+
the point's frame position, subtract the pivot's, turn it back through the arm's screen
|
|
913
|
+
rotation and flip y once. Carried unrounded, printed rounded:
|
|
914
|
+
|
|
915
|
+
| | pose A | pose B |
|
|
916
|
+
| --- | --- | --- |
|
|
917
|
+
| the hinge in the frame, through the flag's placement | (103.52, 144.66) | (125.98, 87.32) |
|
|
918
|
+
| minus the pivot, (80.57, 102.51) | (22.94, 42.15) | (45.40, −15.18) |
|
|
919
|
+
| turned by −61.9° and +18.3°, y flipped | **(47.99, 0.38)** | **(47.87, 0.16)** |
|
|
920
|
+
|
|
921
|
+
That is §3.9's self-agreement check, and the two poses agree to **0.25 px**. ⇒ Take the
|
|
922
|
+
mean, **(47.93, 0.27)**, write it as **(47.93, 0)** — the y is inside the 0.5 px noise
|
|
923
|
+
floor — and write in the log that the pivot was **defaulted**, with the number:
|
|
924
|
+
*relative rotation changed 10.5°, amplification 5.5×, ill-conditioned solve declined*.
|
|
925
|
+
|
|
926
|
+
⚠️ **Which origin the arm's frame has is the whole of this step, and the check above
|
|
927
|
+
cannot tell you.** A placement reports where the part image's **centre** lands, so the
|
|
928
|
+
nearest frame to hand is the one centred on the arm image — and measured from there the
|
|
929
|
+
same hinge reads (24.76, −0.05) and (24.64, −0.27). Those agree to 0.25 px just as well,
|
|
930
|
+
because a constant offset agrees with itself. They are short by 23.24, the arm
|
|
931
|
+
attachment's own offset below, and a flag hung at x 24.70 draws over the middle of the
|
|
932
|
+
arm: built and rendered on the pictures' grid, its flag sits 23.5 px from where
|
|
933
|
+
`poseA.png` draws it in frame 0 and 23.2 px from `poseB.png` at the end. With x 47.93 it
|
|
934
|
+
sits 0.4 px and 0.2 px off, inside the noise floor. A bone sits at its **pivot**, and its
|
|
935
|
+
children's offsets are measured from there.
|
|
905
936
|
|
|
906
937
|
⭐ Worth pausing on, because it is what §3.9 is for: **the two solves are
|
|
907
938
|
indistinguishable from the inside.** Both are exact, both reconstruct to a point that
|
|
908
|
-
agrees between the poses, and no residual anywhere in either pose report moves.
|
|
909
|
-
|
|
939
|
+
agrees between the poses, and no residual anywhere in either pose report moves. Here
|
|
940
|
+
the declined one lands 1.4 px from the drawn hinge — the 5.5× amplification applied to
|
|
941
|
+
about a quarter-pixel of placement noise — and nothing in either answer says which of the
|
|
942
|
+
two is the one to trust. The only thing separating them is Δ, which is arithmetic you
|
|
943
|
+
can do before you trust either.
|
|
910
944
|
|
|
911
945
|
`semaphore.rig.json` — complete, nothing trimmed:
|
|
912
946
|
|
|
@@ -918,9 +952,9 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
918
952
|
"skeleton": { "width": 160, "height": 200 },
|
|
919
953
|
"bones": [
|
|
920
954
|
{ "name": "root" },
|
|
921
|
-
{ "name": "post", "parent": "root", "x": 79.95, "y": 51.
|
|
922
|
-
{ "name": "arm", "parent": "post", "x": 0.
|
|
923
|
-
{ "name": "flag", "parent": "arm", "x":
|
|
955
|
+
{ "name": "post", "parent": "root", "x": 79.95, "y": 51.75 },
|
|
956
|
+
{ "name": "arm", "parent": "post", "x": 0.62, "y": 45.74 },
|
|
957
|
+
{ "name": "flag", "parent": "arm", "x": 47.93, "y": 0 }
|
|
924
958
|
],
|
|
925
959
|
"slots": [
|
|
926
960
|
{ "name": "post", "bone": "post", "attachment": "post" },
|
|
@@ -930,7 +964,7 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
930
964
|
"skins": {
|
|
931
965
|
"default": {
|
|
932
966
|
"post": { "post": { "image": "post.png" } },
|
|
933
|
-
"arm": { "arm": { "image": "arm.png", "x": 23.
|
|
967
|
+
"arm": { "arm": { "image": "arm.png", "x": 23.24 } },
|
|
934
968
|
"flag": { "flag": { "image": "flag.png", "x": 11.5 } }
|
|
935
969
|
}
|
|
936
970
|
}
|
|
@@ -940,17 +974,19 @@ thing separating them is Δ, which is arithmetic you can do before you trust eit
|
|
|
940
974
|
The two attachment offsets are the last of the arithmetic. A bone sits at its pivot and
|
|
941
975
|
the placement told you where the image's **centre** goes, so the offset is the gap
|
|
942
976
|
between them, in the bone's own axes with y flipped once: the arm's pivot is at image
|
|
943
|
-
(6.
|
|
944
|
-
the 0.
|
|
977
|
+
(6.76, 7.43) and its centre at (30, 7), giving **x 23.24** (the y term is 0.43, inside
|
|
978
|
+
the 0.5 px noise floor, so it is not written); the flag's hinge is at image (2.5, 10) and
|
|
945
979
|
its centre at (14, 10), giving **x 11.5** exactly. Bone rotations are left off, which
|
|
946
980
|
means *as drawn* — the arm plate is drawn horizontal and the post vertical, so the poses
|
|
947
981
|
are entirely the motion spec's business.
|
|
948
982
|
|
|
949
983
|
### 4. In-between it
|
|
950
984
|
|
|
951
|
-
Duration 0.55 s, proposed (§3.3). Three easings (§3.4). The arm is the driver
|
|
952
|
-
is the
|
|
953
|
-
the
|
|
985
|
+
Duration 0.55 s, proposed (§3.3). Three easings (§3.4). The arm is the driver. The flag
|
|
986
|
+
is the last thing on it and it is cloth, which is the row §3.7's table gives to *a
|
|
987
|
+
pennant* — not the *next link out* row, because nothing hangs off the flag — so its
|
|
988
|
+
extreme lands **+20–35 %** after the arm's, it overshoots, and it settles last with one
|
|
989
|
+
crossing. *"Snap"* buys an anticipation (§3.6) and an
|
|
954
990
|
overshoot (§3.8). The post is planted: ⛔ **no timeline**.
|
|
955
991
|
|
|
956
992
|
`semaphore.motion.json` — complete, nothing trimmed:
|
|
@@ -976,19 +1012,19 @@ overshoot (§3.8). The post is planted: ⛔ **no timeline**.
|
|
|
976
1012
|
"keys": [
|
|
977
1013
|
{ "t": 0, "v": [-61.9], "ease": "gather" },
|
|
978
1014
|
{ "t": 0.07, "v": [-66.4], "ease": "charge" },
|
|
979
|
-
{ "t": 0.32, "v": [24.
|
|
980
|
-
{ "t": 0.55, "v": [18.
|
|
1015
|
+
{ "t": 0.32, "v": [24.7], "ease": "settle" },
|
|
1016
|
+
{ "t": 0.55, "v": [18.3] }
|
|
981
1017
|
]
|
|
982
1018
|
},
|
|
983
1019
|
{
|
|
984
1020
|
"bone": "flag",
|
|
985
1021
|
"property": "rotate",
|
|
986
1022
|
"keys": [
|
|
987
|
-
{ "t": 0, "v": [-22.
|
|
1023
|
+
{ "t": 0, "v": [-22.2], "ease": "gather" },
|
|
988
1024
|
{ "t": 0.09, "v": [-30.1], "ease": "charge" },
|
|
989
|
-
{ "t": 0.
|
|
990
|
-
{ "t": 0.
|
|
991
|
-
{ "t": 0.55, "v": [-11.
|
|
1025
|
+
{ "t": 0.47, "v": [-3.8], "ease": "settle" },
|
|
1026
|
+
{ "t": 0.51, "v": [-15.4], "ease": "settle" },
|
|
1027
|
+
{ "t": 0.55, "v": [-11.7] }
|
|
992
1028
|
]
|
|
993
1029
|
}
|
|
994
1030
|
]
|
|
@@ -1002,12 +1038,12 @@ which is which is worth being able to point at:
|
|
|
1002
1038
|
|
|
1003
1039
|
| Key | Where it came from |
|
|
1004
1040
|
| --- | --- |
|
|
1005
|
-
| arm `t: 0` = −61.9, flag `t: 0` = −22.
|
|
1006
|
-
| arm `t: 0.55` = 18.
|
|
1041
|
+
| arm `t: 0` = −61.9, flag `t: 0` = −22.2 | **given** — pose A, converted. Untouched, per §3.6 |
|
|
1042
|
+
| arm `t: 0.55` = 18.3, flag `t: 0.55` = −11.7 | **given** — pose B. The flag's is `6.6 − 18.3`: both world rotations converted first, then differenced, because a child's track carries a **local** rotation under a rotated parent |
|
|
1007
1043
|
| arm `t: 0.07` = −66.4 | §3.6 — 4.5° against an 80° excursion (5.6 %), at 13 % of the duration |
|
|
1008
|
-
| arm `t: 0.32` = 24.
|
|
1009
|
-
| flag `t: 0.09`, `t: 0.
|
|
1010
|
-
| flag `t: 0.
|
|
1044
|
+
| arm `t: 0.32` = 24.7 | §3.8 — 6.4° past the end value (8 %), at 58 % of the duration |
|
|
1045
|
+
| flag `t: 0.09`, `t: 0.47` | §3.7 — the flag's extreme lands at 85 % of the duration against the arm's 58 %, an offset of **+27 %**, the middle of its row's band, and it drags the other way first. Not the top of the band: at 35 % (`t: 0.51`) the crossing and the settle below would share 0.04 s, and the 24 fps render would still show the flag at its extreme one frame before the last |
|
|
1046
|
+
| flag `t: 0.51` = −15.4 | §3.7's *one crossing* for a loose part: it comes back past its own end value before settling, keyed halfway between its extreme and the end |
|
|
1011
1047
|
| the three easings | §3.4 — one that gathers, one that arrives slowly, one symmetric. The **last key of each track carries no easing**, because there is nothing after it to ease towards (AUTHORING §4.5) |
|
|
1012
1048
|
| the post's absent track | §3.7 — a planted part gets no timeline |
|
|
1013
1049
|
|
|
@@ -1029,20 +1065,26 @@ rigc render --candidate spine --fps 24 --max 200
|
|
|
1029
1065
|
```
|
|
1030
1066
|
|
|
1031
1067
|
```
|
|
1032
|
-
..
|
|
1068
|
+
.. 134x200px at 24 fps, 1 set(s) -> …/semaphore/render
|
|
1033
1069
|
.. raise 14 frame(s), 0.542s + contact.png -> …/semaphore/render/raise@24fps
|
|
1034
1070
|
```
|
|
1035
1071
|
|
|
1036
1072
|
Open `render/raise@24fps/contact.png` **before anything else** — fourteen frames as one
|
|
1037
1073
|
grid, and spacing is a comparison across frames rather than a property of any one of
|
|
1038
|
-
them. What to check on it, and it is not a score: frame 0 is pose A
|
|
1039
|
-
|
|
1040
|
-
than the arm's
|
|
1074
|
+
them. What to check on it, and it is not a score: frame 0 is pose A — the flag hanging off
|
|
1075
|
+
the arm's collar, not over its middle — the last frame is pose B, the anticipation dips
|
|
1076
|
+
*after* frame 0, and the flag's extreme is visibly later than the arm's: the arm peaks at
|
|
1077
|
+
frame 8 while the flag is still closing on the arm's line, it keeps closing through
|
|
1078
|
+
frame 11 as the arm eases back, and at frame 12 it drops past its resting angle.
|
|
1041
1079
|
|
|
1042
1080
|
🚫 **Do not run `rigc check` against `poseA.png` and `poseB.png`.** Two pictures are not
|
|
1043
|
-
a frame set
|
|
1044
|
-
|
|
1045
|
-
movement.
|
|
1081
|
+
a frame set: `check` reads the second as the frame 1/12 s in, at its default rate. And
|
|
1082
|
+
the ends' **rotations** are given conditions the spec states by construction, so a
|
|
1083
|
+
number for how near the movement got to them is a number about the pose estimator. What
|
|
1084
|
+
the pictures can still catch is §3's arithmetic, because every **offset** in the rig was
|
|
1085
|
+
derived rather than given, and a wrong one moves an end pose off its picture while every
|
|
1086
|
+
key stays exact. That is what the frame-0 check above is for, and it is a look, not a
|
|
1087
|
+
score. §7.
|
|
1046
1088
|
|
|
1047
1089
|
### 6. Spread, and ask
|
|
1048
1090
|
|
|
@@ -1061,10 +1103,10 @@ with **these two fields replaced** and `spec`, `archetype` and `cut` unchanged:
|
|
|
1061
1103
|
"tracks": [
|
|
1062
1104
|
{ "bone": "arm", "property": "rotate", "keys": [
|
|
1063
1105
|
{ "t": 0, "v": [-61.9], "ease": "drive" },
|
|
1064
|
-
{ "t": 0.55, "v": [18.
|
|
1106
|
+
{ "t": 0.55, "v": [18.3] } ] },
|
|
1065
1107
|
{ "bone": "flag", "property": "rotate", "keys": [
|
|
1066
|
-
{ "t": 0, "v": [-22.
|
|
1067
|
-
{ "t": 0.55, "v": [-11.
|
|
1108
|
+
{ "t": 0, "v": [-22.2], "ease": "drive" },
|
|
1109
|
+
{ "t": 0.55, "v": [-11.7] } ] }
|
|
1068
1110
|
]
|
|
1069
1111
|
}
|
|
1070
1112
|
}
|
|
@@ -1077,20 +1119,21 @@ rigc vote --candidate spine --candidate spine-b
|
|
|
1077
1119
|
|
|
1078
1120
|
```
|
|
1079
1121
|
rigc vote
|
|
1080
|
-
.. ballot
|
|
1122
|
+
.. ballot 862f444c58d10e3e
|
|
1081
1123
|
.. animation raise
|
|
1082
|
-
.. A sha256:
|
|
1083
|
-
.. B sha256:
|
|
1124
|
+
.. A sha256:c49e458df44e… 3 page(s), 0.4 KiB <- …/semaphore/spine/skeleton.json
|
|
1125
|
+
.. B sha256:d1bda24d7c36… 3 page(s), 0.4 KiB <- …/semaphore/spine-b/skeleton.json
|
|
1084
1126
|
.. the page shows A/B and nothing else — the paths above are in its manifest, never on the screen
|
|
1085
|
-
|
|
1086
|
-
rigc:
|
|
1127
|
+
.. embedded every candidate's skeleton, atlas and page(s) as data URIs; the player itself loads from unpkg (@4.3.*), so the first open needs a network
|
|
1128
|
+
rigc: wrote …/semaphore/ballot.html (22.3 KiB — open it in a browser)
|
|
1129
|
+
rigc: then record the saved vote with rigc vote --record vote-862f444c58d10e3e.json --ballot …/semaphore/ballot.html
|
|
1087
1130
|
```
|
|
1088
1131
|
|
|
1089
1132
|
A person opens that page, watches two loops, picks one, and saves the small JSON it hands
|
|
1090
1133
|
them. Then:
|
|
1091
1134
|
|
|
1092
1135
|
```bash
|
|
1093
|
-
rigc vote --record vote-
|
|
1136
|
+
rigc vote --record vote-862f444c58d10e3e.json --ballot ballot.html
|
|
1094
1137
|
```
|
|
1095
1138
|
|
|
1096
1139
|
```
|
|
@@ -1101,8 +1144,8 @@ rigc vote --record vote-15b3f32bbbce77be.json --ballot ballot.html
|
|
|
1101
1144
|
PASS V04_CHOICE_IS_ON_THE_BALLOT
|
|
1102
1145
|
PASS V05_REASON_CODE_FITS_THE_CHOICE
|
|
1103
1146
|
PASS V06_NOT_ALREADY_RECORDED
|
|
1104
|
-
.. winner A = sha256:
|
|
1105
|
-
.. coverage 2 candidate(s): A=sha256:
|
|
1147
|
+
.. winner A = sha256:c49e458df44efd277aa6e50ff8fa1962c1211c7617079d63fbc4cf8008eaf0c5, reason code preferred
|
|
1148
|
+
.. coverage 2 candidate(s): A=sha256:c49e458df44e… B=sha256:d1bda24d7c36…
|
|
1106
1149
|
rigc: appended line 1 to …/semaphore/votes.jsonl
|
|
1107
1150
|
```
|
|
1108
1151
|
|
|
@@ -1130,9 +1173,13 @@ that the ends are stateable by construction (`pose`), that the file is checkable
|
|
|
1130
1173
|
(`vote`).
|
|
1131
1174
|
|
|
1132
1175
|
🚫 **No scoring of end-pose reach, and nothing here to add one to.** At L1 and L2 the
|
|
1133
|
-
end poses are given conditions
|
|
1134
|
-
|
|
1135
|
-
|
|
1176
|
+
end poses are given conditions, and what a picture gives is a **pose**: the rotations are
|
|
1177
|
+
`pose`'s readings, which the spec states by construction, so a number for how near the
|
|
1178
|
+
animation got to *them* is a number about the pose estimator. The **offsets** are the
|
|
1179
|
+
other half — derived from the pictures rather than given — and a rig can miss its
|
|
1180
|
+
picture by a wrong one with every key exact. Catching that is a look, not a score (§6.5's
|
|
1181
|
+
frame-0 check). This is why §6 does not run `check` on the two pictures and why no
|
|
1182
|
+
threshold, tolerance or pass bar appears anywhere in this document. The
|
|
1136
1183
|
residuals in a `pose` report are trust signals about *placements*, and AUTHORING §11.1
|
|
1137
1184
|
says the same from the instrument's side.
|
|
1138
1185
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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": {
|
package/src/compile.ts
CHANGED
|
@@ -38,7 +38,9 @@ import { parseJsonWithPosition } from './json-position.ts';
|
|
|
38
38
|
// The "did you mean" list on a missing atlas region, from the one implementation
|
|
39
39
|
// of it — the same search serves `refuseUnknownKeys`, and a second copy here with
|
|
40
40
|
// a threshold edited is how such a pair drifts apart.
|
|
41
|
-
import { dottedPath, nearMisses, refuseNumbersTheFileCannotCarry } from './keys.ts';
|
|
41
|
+
import { dottedPath, nearMisses, refuseNumbersTheFileCannotCarry, refuseValuesOfTheWrongType, refuseValuesOutsideTheirSet } from './keys.ts';
|
|
42
|
+
import type { ShapeVisit } from './keys.ts';
|
|
43
|
+
import { MANIFEST_ENUMS, MANIFEST_TYPES } from './types.ts';
|
|
42
44
|
import { inEditorKeyOrder, withoutParserDefaults } from './keyorder.ts';
|
|
43
45
|
import { EVERY_GLOBAL_PHYSICS, parseMotionSpec } from './motion.ts';
|
|
44
46
|
import {
|
|
@@ -1996,6 +1998,30 @@ export function droppedStateReason(dropped: DroppedState): string {
|
|
|
1996
1998
|
return dropped.why ?? `no PNG at ${dropped.path}`;
|
|
1997
1999
|
}
|
|
1998
2000
|
|
|
2001
|
+
/**
|
|
2002
|
+
* Every node of a cut manifest that `MANIFEST_TYPES` has a row for, in document
|
|
2003
|
+
* order, named by its dotted path (`parts[2].mesh.center[1]`). A node that is
|
|
2004
|
+
* not an object is skipped: whether it should have been one is the reader's
|
|
2005
|
+
* refusal, and the rows below it have nothing to type.
|
|
2006
|
+
*/
|
|
2007
|
+
function manifestShapeVisits(manifest: unknown): ShapeVisit[] {
|
|
2008
|
+
const visits: ShapeVisit[] = [];
|
|
2009
|
+
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
2010
|
+
const at = (node: unknown, shape: keyof typeof MANIFEST_TYPES, path: Array<string | number>): void => {
|
|
2011
|
+
if (isRecord(node)) visits.push({ node, shape, name: (tail) => dottedPath([...path, ...tail]) });
|
|
2012
|
+
};
|
|
2013
|
+
if (!isRecord(manifest)) return visits;
|
|
2014
|
+
at(manifest, 'FaceManifest', []);
|
|
2015
|
+
for (const field of ['crop', 'axis', 'stroke', 'roi'] as const) at(manifest[field], `FaceManifest.${field}`, [field]);
|
|
2016
|
+
for (const [i, part] of (Array.isArray(manifest.parts) ? manifest.parts : []).entries()) {
|
|
2017
|
+
at(part, 'FaceManifestPart', ['parts', i]);
|
|
2018
|
+
if (!isRecord(part)) continue;
|
|
2019
|
+
at(part.mesh, 'FaceManifestMesh', ['parts', i, 'mesh']);
|
|
2020
|
+
if (isRecord(part.mesh)) at(part.mesh.bias, 'FaceManifestMesh.bias', ['parts', i, 'mesh', 'bias']);
|
|
2021
|
+
}
|
|
2022
|
+
return visits;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1999
2025
|
/**
|
|
2000
2026
|
* Compile a rig spec and a motion spec into Spine 4.3 skeleton data.
|
|
2001
2027
|
*
|
|
@@ -2034,6 +2060,15 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2034
2060
|
// bone positions and the stage: a `crop.h` of 1e309 built as `"height": null`
|
|
2035
2061
|
// before issue #881. The rig spec's walk is `parseRigSpec`'s; this is the
|
|
2036
2062
|
// same rule over the other file whose numbers reach the skeleton.
|
|
2063
|
+
// Its values of the wrong type first (issue #890): `crop.h: "806"` built
|
|
2064
|
+
// green, the string coerced by whatever arithmetic read it.
|
|
2065
|
+
if (manifest !== null && manifestPath !== null) {
|
|
2066
|
+
const visits = manifestShapeVisits(manifest);
|
|
2067
|
+
refuseValuesOfTheWrongType(visits, MANIFEST_TYPES, manifestPath);
|
|
2068
|
+
// Then a name outside a stated set (issue #900): `mesh.kind: "foo"` was
|
|
2069
|
+
// read as a ribbon by one reader and a ring by two others.
|
|
2070
|
+
refuseValuesOutsideTheirSet(visits, MANIFEST_TYPES, MANIFEST_ENUMS, manifestPath);
|
|
2071
|
+
}
|
|
2037
2072
|
if (manifest !== null && manifestPath !== null) refuseNumbersTheFileCannotCarry(manifest, manifestPath, dottedPath);
|
|
2038
2073
|
|
|
2039
2074
|
// The rig spec names its own path in every message `parseRigSpec` throws (its
|