spine-rigc 0.14.0 → 0.14.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/README.md +14 -2
- package/cli.ts +24 -1
- package/docs/AUTHORING.md +161 -12
- package/docs/FACE.md +4 -0
- package/docs/INGEST.md +3 -0
- package/docs/MOTION.md +5 -0
- package/docs/RIGGING.md +1453 -0
- package/package.json +2 -1
- package/src/chainfit.ts +707 -35
package/README.md
CHANGED
|
@@ -386,7 +386,7 @@ limits: [AUTHORING.md §11](docs/AUTHORING.md). The parts it refuses because
|
|
|
386
386
|
something is drawn over them are `rigc chainfit`'s, once a candidate exists —
|
|
387
387
|
[§12](docs/AUTHORING.md).
|
|
388
388
|
|
|
389
|
-
## The gallery —
|
|
389
|
+
## The gallery — five complete rigs over art that ships with them
|
|
390
390
|
|
|
391
391
|
Each directory in [`gallery/`](https://github.com/firejune/rigc/tree/main/gallery) is
|
|
392
392
|
one rig spec, one motion spec and the PNGs they name, small enough to read in one
|
|
@@ -401,6 +401,17 @@ was verified, and what writing it cost. Repository material: a clone and
|
|
|
401
401
|
| [`gallery/squash`](https://github.com/firejune/rigc/tree/main/gallery/squash) | **`deform` timelines** | A ball squashed about its contact point and stretched along its travel, from two affine transforms the keys state rather than tabulate |
|
|
402
402
|
| [`gallery/flex`](https://github.com/firejune/rigc/tree/main/gallery/flex) | **`contour` meshes** | A swallow-tailed banner and a serrated leaf: four meshes traced off their own alpha, waved by bone timelines and rippled by a `deform` |
|
|
403
403
|
| [`gallery/ride`](https://github.com/firejune/rigc/tree/main/gallery/ride) | `path` attachments + **path constraints** | A trolley coasting down a drawn rail and rolling back, driven by a `position` timeline, with `groups` + `stagger` keying the wheels and the ears |
|
|
404
|
+
| [`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait) | **deform `transform`** + `derive` group tracks | A 2.5D head turn: two meshes and six feature bones all keyed from one stated expression, `dx = x(cos t − 1) − z·sin t`, with the depths in the spec rather than a README |
|
|
405
|
+
|
|
406
|
+
<p align="center">
|
|
407
|
+
<img src="https://raw.githubusercontent.com/firejune/rigc/main/assets/rigc-scene.gif" alt="A portrait rig breathing, glancing aside, then turning its head in 2.5D — hair and features sliding at different depths" width="600" />
|
|
408
|
+
</p>
|
|
409
|
+
|
|
410
|
+
<p align="center"><em>The portrait rig playing its three animations in one take — the turn is
|
|
411
|
+
the shot: both silhouette edges move apart, which a flat slide cannot do, because every
|
|
412
|
+
feature carries its own depth. Scene direction of this kind was Live2D's territory; the
|
|
413
|
+
split was authoring cost, and the cost is now one stated expression per key. Compiled and
|
|
414
|
+
rendered entirely by the published package.</em></p>
|
|
404
415
|
|
|
405
416
|
## Commands
|
|
406
417
|
|
|
@@ -419,7 +430,7 @@ commands take it and what its default is.
|
|
|
419
430
|
| `preview --candidate <dir>` | one self-contained `.html` that plays it |
|
|
420
431
|
| `vote --candidate a --candidate b` | one `.html` that asks a human which; `vote --record <file>` checks the answer into `votes.jsonl` |
|
|
421
432
|
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
|
422
|
-
| `chainfit --candidate <dir> --images <dir> --frame <png>` | reads the parts `pose` refuses, through the candidate's own draw order and hierarchy: masked residuals over **visible** pixels, one hinge per child instead of four degrees of freedom, and the `rotate` key value each answer implies |
|
|
433
|
+
| `chainfit --candidate <dir> --images <dir> --frame <png>` | reads the parts `pose` refuses, through the candidate's own draw order and hierarchy: masked residuals over **visible** pixels, one hinge per child instead of four degrees of freedom, and the `rotate` key value each answer implies. A bone with two or more anchored descendants is **determined** rather than searched, and the residual that over-determination leaves is reported |
|
|
423
434
|
| `diff <candidate.json> <reference.json>` | structural comparison of two skeletons, one ratio per measure and deliberately no combined score |
|
|
424
435
|
| `check --candidate <dir> --frames <dir>` | the candidate against reference pictures — the only instrument here that can see a *wrong animation* |
|
|
425
436
|
| `bench <rung> --candidate <dir>` | one rung of the benchmark ladder |
|
|
@@ -447,6 +458,7 @@ the art. Its shape is under
|
|
|
447
458
|
| Document | For |
|
|
448
459
|
| --- | --- |
|
|
449
460
|
| 📘 **[docs/AUTHORING.md](docs/AUTHORING.md)** | **the format guide, and the one to read before writing a spec.** Both input files field by field with a complete minimal example each, every field with its Spine meaning, the rules that decide what is emitted, the build → read the report → fix → repeat loop, the map from every named failure to the file that has to change, and the features rigc refuses by name so you do not spend a loop discovering them. It travels **inside the npm package**, at `node_modules/spine-rigc/docs/AUTHORING.md` |
|
|
461
|
+
| 🦴 **[docs/RIGGING.md](docs/RIGGING.md)** | **authoring the hierarchy.** Where a bone goes and why the art is pushed out on an offset, why a pivot in the wrong place looks like a search failure and what identifies one, moving a pivot and the child row that gets forgotten, gauges, siblings-not-a-chain, what a chain can reach and how many links it needs, why a local key is not a world key, duplicate art at mirrored pivots, and constraints as structure. Every section is a stumble the run records hold more than once, ranked by how often. Ships in the package too |
|
|
450
462
|
| 🎞️ **[docs/MOTION.md](docs/MOTION.md)** | **the key-pose recipe.** How to get two poses, what a pair of poses does and does not fix, the in-betweening rules and where each comes from, and how to spread candidates so a ballot informs. Ships in the package too |
|
|
451
463
|
| 🙂 **[docs/FACE.md](docs/FACE.md)** | **authoring a face.** A blink, a gaze and a 2.5D head turn on plain Spine data: the one line of yaw arithmetic every number in a turn comes from, depth as the parameter you are actually authoring, where to put a grid's columns and the angle at which any grid folds, what foreshortens and what does not, channel allocation before the first key, and the three cliffs with their angles. Also the deform audit gap, demonstrated — a folded mesh gates green — and the differential check that works today |
|
|
452
464
|
| 📥 **[docs/INGEST.md](docs/INGEST.md)** | **working with a skeleton you did not author.** What every command can and cannot do with a foreign `skeleton.json`, reading it with the toolchain, transcription as the route that makes it yours, what each validator complaint means on an export, and the re-pivot/rename/extend recipes. Ships in the package too |
|
package/cli.ts
CHANGED
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
ChainFitError,
|
|
87
87
|
DEFAULT_HINGE_MAX,
|
|
88
88
|
DEFAULT_HINGE_MIN,
|
|
89
|
+
DEFAULT_MIN_LEVER_PX,
|
|
89
90
|
DEFAULT_MIN_VISIBLE,
|
|
90
91
|
DEFAULT_PASSES,
|
|
91
92
|
estimateChainFit,
|
|
@@ -180,8 +181,19 @@ function repositoryUrl(): string {
|
|
|
180
181
|
* Listed by name rather than inferred from "the next argument looks like a
|
|
181
182
|
* flag": inferring it would turn `--out --json report.json` — a real typo, a
|
|
182
183
|
* missing value — into a silently accepted switch plus a stray positional.
|
|
184
|
+
*
|
|
185
|
+
* ⚠️ This set and `FLAG_VALUES` are two halves of one statement, and they are
|
|
186
|
+
* the halves a reader and the parser read separately: a flag absent from
|
|
187
|
+
* `FLAG_VALUES` is printed bare in every usage line and flag table, and a flag
|
|
188
|
+
* present here is the only kind the parser will accept bare. `all-bones` was in
|
|
189
|
+
* one half and not the other for two releases — documented bare in `bonedist`'s
|
|
190
|
+
* usage line, in the shared flag table, and in the hint `src/bonedist.ts` prints
|
|
191
|
+
* under a truncated bone table, while the parser fell through to the value
|
|
192
|
+
* branch and answered the caller who followed that hint with `rigc: --all-bones
|
|
193
|
+
* needs a value` (issue #328). `CLI10`/`CLI11` in `selftest.ts` now hold the two
|
|
194
|
+
* halves together by reading `--help` rather than by naming a flag.
|
|
183
195
|
*/
|
|
184
|
-
const BOOLEAN_FLAGS = new Set(['all-frames', 'help', 'copy-images', 'again', 'pack']);
|
|
196
|
+
const BOOLEAN_FLAGS = new Set(['all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack']);
|
|
185
197
|
|
|
186
198
|
/**
|
|
187
199
|
* The flags a command is allowed to spell more than once.
|
|
@@ -1407,6 +1419,11 @@ function cmdChainFit(flags: Record<string, string>): void {
|
|
|
1407
1419
|
if (!Number.isInteger(value) || value < 1 || value > 8) throw new UsageError('--passes must be a whole number in 1..8');
|
|
1408
1420
|
options.passes = value;
|
|
1409
1421
|
}
|
|
1422
|
+
if (flags['inward-lever'] !== undefined) {
|
|
1423
|
+
const value = Number(flags['inward-lever']);
|
|
1424
|
+
if (!Number.isFinite(value) || value < 0) throw new UsageError('--inward-lever must be a number of frame pixels, 0 or more');
|
|
1425
|
+
options.minLeverPx = value;
|
|
1426
|
+
}
|
|
1410
1427
|
if (flags['anchor-residual'] !== undefined) {
|
|
1411
1428
|
const value = Number(flags['anchor-residual']);
|
|
1412
1429
|
if (!Number.isFinite(value) || value <= 0 || value > 1) throw new UsageError('--anchor-residual must be a number in (0, 1]');
|
|
@@ -2300,6 +2317,10 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
2300
2317
|
'anchor-residual':
|
|
2301
2318
|
`the residual a \`pose\` placement must be within to anchor a chain (default ${ANCHOR_MAX_RESIDUAL}, with ` +
|
|
2302
2319
|
`unexplained ≤ ${ANCHOR_MAX_UNEXPLAINED} and unambiguous — the 2026-09-03 measurement run's own clean-frame criterion)`,
|
|
2320
|
+
'inward-lever':
|
|
2321
|
+
`how far apart, in frame pixels, two anchored descendants have to sit before the rotation they determine is ` +
|
|
2322
|
+
`printed (default ${DEFAULT_MIN_LEVER_PX}); below it the bone is refused \`no-bracket\` naming the measured ` +
|
|
2323
|
+
'lever, because an angle read across a short lever turns a half-pixel anchor error into several degrees',
|
|
2303
2324
|
animation: 'which animation to show; the default is every one for `render` and the first for `preview`',
|
|
2304
2325
|
max: 'longest side of a rendered frame, in pixels (default 256)',
|
|
2305
2326
|
record: 'a saved vote to check against its ballot and append to the ledger, instead of writing a ballot',
|
|
@@ -2344,6 +2365,7 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
2344
2365
|
'min-visible': '<0..1>',
|
|
2345
2366
|
passes: '<n>',
|
|
2346
2367
|
'anchor-residual': '<0..1>',
|
|
2368
|
+
'inward-lever': '<px>',
|
|
2347
2369
|
animation: '<name>',
|
|
2348
2370
|
max: '<px>',
|
|
2349
2371
|
record: '<result.json>',
|
|
@@ -2496,6 +2518,7 @@ const COMMANDS: CommandDoc[] = [
|
|
|
2496
2518
|
'max-residual',
|
|
2497
2519
|
'passes',
|
|
2498
2520
|
'anchor-residual',
|
|
2521
|
+
'inward-lever',
|
|
2499
2522
|
'scale',
|
|
2500
2523
|
'rotation',
|
|
2501
2524
|
'out',
|
package/docs/AUTHORING.md
CHANGED
|
@@ -30,6 +30,14 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
30
30
|
unlinked** for the same reason as the line above: it cites `SPEC_COVERAGE.md` and
|
|
31
31
|
the stored transcriptions, both of which are on the ladder run's forbidden list. Its
|
|
32
32
|
reader was handed a compiled skeleton rather than art and a brief
|
|
33
|
+
- Deciding the **hierarchy** itself — how many bones, where each pivot goes, what
|
|
34
|
+
hangs off what, what a chain can reach, and which of those the frames can check:
|
|
35
|
+
`docs/RIGGING.md`. 🚫 **Not an authoring input, and deliberately unlinked** for
|
|
36
|
+
the same reason as the two lines below: it is a mining of the recorded runs, so it
|
|
37
|
+
cites their `LOOP.md` files, and following a citation out of an allowed surface is
|
|
38
|
+
a leak by another route. Its rules are the ones **§8.1**, **§10.3** and
|
|
39
|
+
**MOTION §3.9** already state, which are allowed reading; what that page adds is
|
|
40
|
+
provenance and worked demonstrations, for a maintainer
|
|
33
41
|
- Reproducing a shot you were given as pictures: **§8**, and read it *before* you
|
|
34
42
|
start measuring rather than after; **§8.1** if the figure has more joints than you
|
|
35
43
|
can measure one at a time; then **§9** for the loop that closes it
|
|
@@ -4345,7 +4353,9 @@ themselves**, downward and by small amounts, most on the parts whose edges are i
|
|
|
4345
4353
|
the frame. ⇒ A residual measured before that date and one measured after are not
|
|
4346
4354
|
the same measurement; re-read a frame rather than comparing across it. The worked
|
|
4347
4355
|
figures live in [MOTION.md](MOTION.md) §6 and
|
|
4348
|
-
[BENCHMARK.md](BENCHMARK.md)
|
|
4356
|
+
[BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md) —
|
|
4357
|
+
repository material, not shipped in the package — each with its own re-baseline
|
|
4358
|
+
note.
|
|
4349
4359
|
|
|
4350
4360
|
### 11.1 It measures an input, so nothing here is a score
|
|
4351
4361
|
|
|
@@ -4460,7 +4470,11 @@ two things.
|
|
|
4460
4470
|
apologised for. The parts drawn after a part are what covers it, and the pixels
|
|
4461
4471
|
they cover are **excluded** from that part's objective rather than charged to it.
|
|
4462
4472
|
Every residual here is over the part's **visible** pixels, and every one comes
|
|
4463
|
-
with the `visibleShare` it was computed on.
|
|
4473
|
+
with the `visibleShare` it was computed on. ⚠️ **`visibleShare` is a per-frame
|
|
4474
|
+
diagnostic and not a summary statistic** — it is measured through the fitted
|
|
4475
|
+
placements, so a median or a mean of it is not comparable across fits. §12.3 says
|
|
4476
|
+
what that costs and [the 2026-09-03 study](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md)
|
|
4477
|
+
measures it — repository material, not shipped in the package.
|
|
4464
4478
|
- **Hierarchy and attachment geometry**, so the search collapses. A child bone
|
|
4465
4479
|
whose parent is already placed does not have four degrees of freedom: the rig
|
|
4466
4480
|
fixes its pivot, so what is left is **one hinge** about that pivot — plus a
|
|
@@ -4500,11 +4514,96 @@ the four a similarity has — and every descendant then follows from the rig. A
|
|
|
4500
4514
|
**above** an anchor does not: recovering it would need to know what the link
|
|
4501
4515
|
between them did, and that is precisely the unknown the anchor does not carry.
|
|
4502
4516
|
|
|
4503
|
-
⚠️ **So a limb with no trusted part on it or above it is refused
|
|
4504
|
-
|
|
4505
|
-
|
|
4517
|
+
⚠️ **So a limb with no trusted part on it or above it is refused**, not guessed at
|
|
4518
|
+
from a cousin. If a whole side of your figure comes back that way, the repair is
|
|
4519
|
+
upstream: give `pose` a better frame, pin its `--scale`, or loosen
|
|
4506
4520
|
`--anchor-residual` deliberately and read the consequences.
|
|
4507
4521
|
|
|
4522
|
+
### 12.2b The inward step — two anchors bracket the bone between them
|
|
4523
|
+
|
|
4524
|
+
⬆️ There is **one exception** to the paragraph above, and it is one shape.
|
|
4525
|
+
|
|
4526
|
+
One anchored descendant says nothing about the link above it — that is the
|
|
4527
|
+
sentence you just read, and it is still true. **Two** of them say something else
|
|
4528
|
+
entirely. A bone's world placement is four numbers, and a descendant's **pivot**
|
|
4529
|
+
depends on that bone and on your rig's own offsets and **not** on the
|
|
4530
|
+
descendant's own hinge. So each anchored descendant contributes two equations,
|
|
4531
|
+
two of them make four, and four equations fix four numbers. That is the whole
|
|
4532
|
+
geometry; there is no search and no window.
|
|
4533
|
+
|
|
4534
|
+
Such a bone comes back with **`role: "inward"`** and a `bone.inward` block, and
|
|
4535
|
+
the outward walk then **resumes from it** — so a subtree that was refused a moment
|
|
4536
|
+
ago is fitted normally, one hinge per link, with `bone.anchoredToRole` on every
|
|
4537
|
+
one of those placements reading `"inward"` to say what it rests on.
|
|
4538
|
+
|
|
4539
|
+
⚠️ **Which means the inward step reaches exactly the bones that BRANCH.** A bone
|
|
4540
|
+
whose children form a single sub-chain can never be determined, however good the
|
|
4541
|
+
anchor below it is: two equations, four unknowns. That is `no-bracket`, and it is
|
|
4542
|
+
a fact about your rig's topology rather than about the frame. On the 2026-09-03
|
|
4543
|
+
spineboy candidate `torso` is the only bone in the whole rig that branches — and
|
|
4544
|
+
it is the bone that recorded 30 `no-anchor` frames, which is why this exists.
|
|
4545
|
+
|
|
4546
|
+
| Field on `bone.inward` | Meaning |
|
|
4547
|
+
| --- | --- |
|
|
4548
|
+
| `form` | `descendants` — the only form there is. Named so a future one is readable beside it |
|
|
4549
|
+
| `determinants[]` | the anchored bones the four numbers were read from: each one's `bone`, the `part` its anchor came from, its `leverPx` from the determined bone, its `offsetPx`, and the art-less bones `carried` through to reach it |
|
|
4550
|
+
| `redundancy` | equations beyond the four a similarity needs, `2 × determinants − 4`. **Read `disagreementPx` next to this and never without it** |
|
|
4551
|
+
| `leverPx` | the widest frame-pixel span between two determinant pivots — what the rotation was read *across*. A short lever turns a half-pixel anchor error into several degrees |
|
|
4552
|
+
| `minLeverPx` | the floor that span had to clear (`--inward-lever`, default `8`) |
|
|
4553
|
+
| `disagreementPx` | the worst `offsetPx`: **the over-determination residual**, in frame pixels. `null` at `redundancy 0` |
|
|
4554
|
+
| `rejected[]` | anchored descendants that could **not** be used, each with the reason — named rather than silently dropped |
|
|
4555
|
+
|
|
4556
|
+
🚨 **At `redundancy 0`, `disagreementPx` is `null` rather than `0`, and the
|
|
4557
|
+
difference is the whole point.** Two determinants supply exactly four numbers, so
|
|
4558
|
+
the solve fits its own two points exactly *whether or not your rig is right*. A
|
|
4559
|
+
zero there would be a measurement of nothing. One more anchored descendant on a
|
|
4560
|
+
third sub-chain is what makes a determination checkable at all — and that is the
|
|
4561
|
+
same philosophy as `pivotDisagreementPx`: the residual is not an error bar, it is
|
|
4562
|
+
your rig's joint offsets and the frame disagreeing by that much.
|
|
4563
|
+
|
|
4564
|
+
🚨 **And `disagreementPx` says the determination disagrees with itself. It does
|
|
4565
|
+
NOT say which determinant is wrong.** The solve is least squares, so a
|
|
4566
|
+
displacement on one anchor spreads across every determinant near it. Measured on
|
|
4567
|
+
the chain-fit fixture, a deliberate 4 px error on one child came back as **1.95 px
|
|
4568
|
+
on a different child** and 1.74 px on the one that was moved, because those two
|
|
4569
|
+
sit 5 bone units apart while the third is 24 away — nothing in the arithmetic can
|
|
4570
|
+
tell a tight pair apart. Read the per-determinant `offsetPx` list as a *pattern*,
|
|
4571
|
+
and attribute with a second frame or with `pivotDisagreementPx` on the anchors
|
|
4572
|
+
themselves.
|
|
4573
|
+
|
|
4574
|
+
**What the step cannot see, stated as refusals it makes by name.**
|
|
4575
|
+
|
|
4576
|
+
- **`no-bracket`, one determinant.** Four numbers need four equations. The detail
|
|
4577
|
+
names the one it found.
|
|
4578
|
+
- **`no-bracket`, an unusable path.** A bone strictly between the two carries art
|
|
4579
|
+
(its hinge is a searched unknown), or your rig leaves its **scale** free (the
|
|
4580
|
+
*distance* across it is unknown), or its geometry is not a similarity at all.
|
|
4581
|
+
Each is named with the bone. ⚠️ `--stretch` frees every bone's scale, so it can
|
|
4582
|
+
turn a working bracket into a refused one — that is the flag telling the truth
|
|
4583
|
+
about what it made unknown.
|
|
4584
|
+
- **`no-bracket`, below the lever floor.** Two coincident pivots fix no direction.
|
|
4585
|
+
Nothing is printed here, unlike `occluded`: a rotation with no baseline is not a
|
|
4586
|
+
worse placement, it is not a placement.
|
|
4587
|
+
- **`no-anchor` still means what it meant** — nothing trusted on this limb, above
|
|
4588
|
+
it *or below it*. The split matters because the repair does: `no-bracket` wants
|
|
4589
|
+
one more anchor on a different sub-chain, `no-anchor` wants a better anchor pass.
|
|
4590
|
+
- **Determinants are ANCHORED bones and nothing else** (`inward.criterion.determinantsMustBeAnchored`).
|
|
4591
|
+
A bone the outward walk placed is sitting at whatever hinge your setup declares
|
|
4592
|
+
until it is fitted, and reading that as evidence would compound a guess into a
|
|
4593
|
+
placement.
|
|
4594
|
+
- **An anchored bone is never determined inward**, and a fitted hinge is never
|
|
4595
|
+
replaced by a geometric one. The step only reaches bones the outward walk left
|
|
4596
|
+
unplaced.
|
|
4597
|
+
|
|
4598
|
+
🚫 **An `inward` placement is not a measurement of the bone it places.** Its
|
|
4599
|
+
evidence lives on the anchors below it. The bone's own `residual` and
|
|
4600
|
+
`visibleShare` say how much of the answer *the frame can independently confirm* —
|
|
4601
|
+
and **the visibility floor still refuses it** when the answer is one nothing in
|
|
4602
|
+
the picture can check. That is deliberate, and it is the second time this call has
|
|
4603
|
+
been made in `chainfit`: exempting a placement nothing searched was tried for
|
|
4604
|
+
anchors and reverted, because it prints a part nobody can see as READ. The floor
|
|
4605
|
+
is about what the picture can confirm, not about how the number was arrived at.
|
|
4606
|
+
|
|
4508
4607
|
### 12.3 What the report adds to a `pose` report
|
|
4509
4608
|
|
|
4510
4609
|
The coordinate contract is **identical** to §11.2 — frame pixels, y down, origin
|
|
@@ -4515,27 +4614,64 @@ placement:
|
|
|
4515
4614
|
| Field | Meaning |
|
|
4516
4615
|
| --- | --- |
|
|
4517
4616
|
| `residual` | the same objective as §11, over the part's **visible** pixels only: covered pixels are dropped from both sums rather than charged. **Not the same number as `pose`'s on an occluded part**, and never to be read without the next field |
|
|
4518
|
-
| `visibleShare` | the share of the part's own alpha weight the residual was computed on. A low residual on a `0.08` share is a confident statement about a sliver |
|
|
4617
|
+
| `visibleShare` | the share of the part's own alpha weight the residual was computed on. A low residual on a `0.08` share is a confident statement about a sliver. ⚠️ **Per frame, not per corpus** — see the caution below the table |
|
|
4519
4618
|
| `scoredPixels` | how many part pixels that share actually is |
|
|
4520
|
-
| `visibleShareAtFit` | the share recomputed **where the answer landed**, rather than where the visible set was frozen. Far from `visibleShare` means the fit moved out of its own measurement; `--passes` is the repair |
|
|
4619
|
+
| `visibleShareAtFit` | the share recomputed **where the answer landed**, rather than where the visible set was frozen. Far from `visibleShare` means the fit moved out of its own measurement; `--passes` is the repair. ⚠️ Not the steadier of the two — measured, below |
|
|
4521
4620
|
| `hingeDeg` | ⭐ the searched degree of freedom, in **Spine** degrees relative to the bone's setup rotation — **the value a `rotate` key would carry**. `null` on an anchor whose own parent is unplaced, where the quantity does not exist |
|
|
4522
4621
|
| `localRotationDeg` | the bone's local rotation this implies, Spine degrees. The other half of the same answer |
|
|
4523
4622
|
| `stretch` | the uniform scale on the bone; `1` where that DOF was not free |
|
|
4524
4623
|
| `unexplained`, `offCanvas`, `footprint`, `bbox` | as §11.3, with `residual` and `unexplained` over the visible set and `offCanvas` over the whole part |
|
|
4525
4624
|
|
|
4625
|
+
🚨 **`visibleShare` is a per-frame diagnostic. Do not take a median or a mean of it
|
|
4626
|
+
across frames without naming the fit.** Both halves of the fraction are downstream
|
|
4627
|
+
of the fit — the numerator because the occluders are stamped from wherever the
|
|
4628
|
+
later-drawn parts *currently sit* — so the quantity is fit-relative by
|
|
4629
|
+
construction, and measurably so. Measured over all 147 committed `ess` frames of
|
|
4630
|
+
`bench/reference/spineboy`, perturbing the anchor placements **inside `pose`'s own
|
|
4631
|
+
convergence band** (`src/pose.ts`'s level-0 polish `floor`: 0.05 px, 0.1°, 0.1 %
|
|
4632
|
+
scale — below which the fitter stops looking, so it cannot tell the two fits apart):
|
|
4633
|
+
|
|
4634
|
+
| | |
|
|
4635
|
+
| --- | ---: |
|
|
4636
|
+
| median \|Δ`visibleShare`\| | 0.0005 |
|
|
4637
|
+
| **p99** | **0.5592** |
|
|
4638
|
+
| max | 0.9401 |
|
|
4639
|
+
| readings that move by more than 0.10 | **5.71 %** |
|
|
4640
|
+
| the **corpus median** of one part's share, worst case | **0.3055 ↔ 0.5228** (`rear-foot`) |
|
|
4641
|
+
|
|
4642
|
+
⚠️ **The distribution is bimodal, so the median of the swing is not a summary of
|
|
4643
|
+
it.** A reading is either exact to four decimals or somewhere else entirely, and
|
|
4644
|
+
which of the two a part is in **cannot be told by looking at the row**:
|
|
4645
|
+
`front-bracer` sits at a comfortable 0.73 and its corpus median moves 17 points,
|
|
4646
|
+
while `rear-bracer` sits at a 0.015 sliver and moves 0.0005.
|
|
4647
|
+
|
|
4648
|
+
📌 **It is not a definitional edge, and `visibleShareAtFit` is not a way out.** Of the
|
|
4649
|
+
cells that swing by more than 0.10, **63 %** are the part's own placement having
|
|
4650
|
+
travelled more than a pixel or turned more than `AMBIGUITY_HINGE_DEG`, **32 %** are
|
|
4651
|
+
an occluder having relocated (median 42.6 px), and **0.5 %** are the mask changing
|
|
4652
|
+
while everything on the frame stood still. `visibleShareAtFit` is measured where
|
|
4653
|
+
the answer landed and is *less* steady, not more (p99 0.6588 against 0.5592). ⇒
|
|
4654
|
+
**The field is reporting a bistable fit faithfully.** What is safe: reading it beside
|
|
4655
|
+
its own residual, on its own frame — which is what it exists for. What is safe as a
|
|
4656
|
+
corpus statistic: a **maximum**, which saturates. Full method and evidence:
|
|
4657
|
+
[`bench/studies/2026-09-03-visibleshare`](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md)
|
|
4658
|
+
([#323](https://github.com/firejune/rigc/issues/323)).
|
|
4659
|
+
|
|
4526
4660
|
And per part:
|
|
4527
4661
|
|
|
4528
4662
|
| Field | Meaning |
|
|
4529
4663
|
| --- | --- |
|
|
4530
|
-
| `role` | `anchor` (taken from the anchor pass, not re-fitted), `chain` (fitted through the rig), `unplaced` |
|
|
4531
|
-
| — | ⭐ **A refused ANCHOR is not a contradiction, and it is the most useful row in the table.** The anchor pass judged that placement over the part's *whole* footprint — all `pose` can see, and blind to what covers it — while this instrument has just measured how much of the part is visible at all. Both readings are true. A refused anchor means *the placement may well be right and the confirmation is missing*, and every part whose `anchoredTo` names that bone rests on it. Measured on the 2026-09-03 corpus, `rear-bracer` clears `pose`'s criterion on 81 of 147 frames at a median visible share of **0.1%** — suppressing the refusal there was tried and prints that as READ. ⚠️ **That pair of numbers is on the pre-[#306](https://github.com/firejune/rigc/issues/306) objective** and has not been re-derived: the study is the 2026-09-03 run's own, over its own candidate, and re-running it is a run-scale job rather than a docs edit. A four-frame spot check under #306 (`--min-visible 0`, committed `ess` frames `idle/f0000`, `run/f0002`, `walk/f0004`, `aim/f0000`) moved `rear-bracer`'s anchor residual *down* on all four — 0.1564→0.1555, 0.1519→0.1513, 0.1493→0.1488, 0.1537→0.1534 — and flipped its eligibility on none, while `visibleShare` moved materially on one of the four (0.36→0.89). ⇒ Read the **shape** of the row, not those two digits |
|
|
4664
|
+
| `role` | `anchor` (taken from the anchor pass, not re-fitted), `chain` (fitted through the rig), `inward` (**determined** from two or more anchored descendants, with nothing searched — §12.2b), `unplaced` |
|
|
4665
|
+
| — | ⭐ **A refused ANCHOR is not a contradiction, and it is the most useful row in the table.** The anchor pass judged that placement over the part's *whole* footprint — all `pose` can see, and blind to what covers it — while this instrument has just measured how much of the part is visible at all. Both readings are true. A refused anchor means *the placement may well be right and the confirmation is missing*, and every part whose `anchoredTo` names that bone rests on it. Measured on the 2026-09-03 corpus, `rear-bracer` clears `pose`'s criterion on 81 of 147 frames at a median visible share of **0.1%** — suppressing the refusal there was tried and prints that as READ. ⚠️ **That pair of numbers is on the pre-[#306](https://github.com/firejune/rigc/issues/306) objective** and has not been re-derived: the study is the 2026-09-03 run's own, over its own candidate, and re-running it is a run-scale job rather than a docs edit. A four-frame spot check under #306 (`--min-visible 0`, committed `ess` frames `idle/f0000`, `run/f0002`, `walk/f0004`, `aim/f0000`) moved `rear-bracer`'s anchor residual *down* on all four — 0.1564→0.1555, 0.1519→0.1513, 0.1493→0.1488, 0.1537→0.1534 — and flipped its eligibility on none, while `visibleShare` moved materially on one of the four (0.36→0.89). ⇒ Read the **shape** of the row, not those two digits. 🚨 **And that median in particular is one of the ones the 2026-09-03 study found unsafe**: `rear-bracer`'s share swings by **0.87** on `idle/f0001` inside `pose`'s own convergence band when the chain anchors on `pose`'s criterion — the basis this figure was taken on. The count and the shape are the reading; the 0.1 % is not a number |
|
|
4532
4666
|
| — | The **other** parts on an anchored bone are refused on their own numbers too, and there they mean something different again: their placement is the **rig's** prediction from that anchor, so their residual is a measurement of the rig (a goggle plate that will not sit on the head it is parented to shows up exactly here) |
|
|
4533
4667
|
| `bone` | the bone this hangs off: its `parent`, its `setupRotationDeg`, its `depth` in links from the anchor, `anchoredTo`, the `dof` searched, the `window` taken, the other parts `sharedWith` it on that bone, and `carriedBones` |
|
|
4534
4668
|
| `bone.dof.pivotFree` | your candidate keys a `translate` timeline on this bone, so the arc this answer sits on has a centre the rig itself moves. The placement is still read off pixels; `localRotationDeg` alone will not reproduce it |
|
|
4535
4669
|
| `bone.carriedBones` | bones between the anchor and here that carry nothing scoreable. Their hinge could not be fitted, their setup rotation was carried through, and every number below them inherits that |
|
|
4536
4670
|
| `bone.pivotDisagreementPx` | anchored bones only: how far the chain's own prediction of this bone's pivot is from where the anchor put it. **This is the one direct measurement of your rig against the picture** — a large value says the joint offset you declared is not the joint the frame shows |
|
|
4537
4671
|
| `anchorVerdict` | what the anchor pass made of this same part: `residual`, `unexplained`, `ambiguous`, `eligible`. ⭐ `eligible: false` beside a `chain` placement is **a part the chain bought** |
|
|
4538
|
-
| `
|
|
4672
|
+
| `bone.anchoredToRole` | whether the trunk this hangs off was `anchor` (read off the picture) or `inward` (determined from two anchors below it). ⭐ **The field to check before quoting anything hung off an inward trunk** — that subtree inherits the determination's own uncertainty, and `bone.inward.disagreementPx` on the trunk bone is where it is priced |
|
|
4673
|
+
| `bone.inward` | non-`null` only on a bone determined inward, and then it is the whole account of that determination — see §12.2b |
|
|
4674
|
+
| `refusal` | `{ reason, detail }` or `null`. Reasons: `occluded`, `no-match`, `no-anchor`, `no-bracket` (§12.2b), `empty-part`, `no-part-image`, `unsupported-geometry` |
|
|
4539
4675
|
|
|
4540
4676
|
`⚠️ --images is not a part list here.` For `pose` every `.png` in the directory is
|
|
4541
4677
|
a part; for `chainfit` **the candidate decides what the parts are** and the
|
|
@@ -4550,10 +4686,11 @@ simply unused; a name the directory lacks is refused `no-part-image` by name.
|
|
|
4550
4686
|
| `--atlas` | **refused by name.** Every other `--candidate` command takes it, so trying it here is reasonable — but the part art comes from `--images` and the skeleton is all this needs of the candidate, so a flag that silently did nothing would be worse than one that says why |
|
|
4551
4687
|
| `--hinge <min,max>` | the window each child's local rotation is searched over, in Spine degrees about its setup value. Default `-180,180` — **a full turn, on purpose**: one degree of freedom is cheap enough to sweep exhaustively, and §11.4's warning about a window that does not contain the truth applies here too |
|
|
4552
4688
|
| `--stretch <ratio>` | also search a uniform bone scale, this ratio either way. Without it, stretch is searched **only where your own animations key a `scale` timeline on that bone** — a rig that never scales a bone is a rig saying that bone does not stretch |
|
|
4553
|
-
| `--min-visible <0..1>` | below this visible share a placement is refused `occluded` instead of reported flat (default `0.25`). A reporting threshold, not a pass bar; the placement is still in the JSON |
|
|
4689
|
+
| `--min-visible <0..1>` | below this visible share a placement is refused `occluded` instead of reported flat (default `0.25`). A reporting threshold, not a pass bar; the placement is still in the JSON. ⚠️ **It is not inert, though**: a bone whose frozen share is under this floor gets one *unmasked* look before its visible set is fixed, so the flag also changes **where parts land** and not only which rows are refused. Two runs at different `--min-visible` are two fits, and their shares are not one column |
|
|
4554
4690
|
| `--max-residual <0..1>` | as §11, over the visible pixels (default `0.25`) |
|
|
4555
|
-
| `--passes <n>` | how many times the masks are rebuilt from the answers and the fit rerun (default `2`) |
|
|
4691
|
+
| `--passes <n>` | how many times the masks are rebuilt from the answers and the fit rerun (default `2`). ⚠️ **It buys convergence of the mask onto the answer, and it costs determinacy.** Each pass re-seeds the hinge search on the previous pass's answer, so two nearby inputs that fell into different basins on pass 1 are *further* apart after pass 2. Measured over the 147 committed `ess` frames, the share of readings whose `visibleShare` moves by more than 0.10 under a perturbation inside `pose`'s convergence band runs **0.78 % → 5.45 % → 15.17 %** at `--passes` 1 → 2 → 4. Raise it to settle a `visibleShareAtFit` drift on one frame; do not raise it expecting steadier numbers across runs |
|
|
4556
4692
|
| `--anchor-residual <0..1>` | the residual a `pose` placement must be within to anchor (default `0.16`) |
|
|
4693
|
+
| `--inward-lever <px>` | how far apart two anchored descendants must sit before the rotation they determine is printed (default `8`). Derived, not picked: a pivot error of ε px across a lever of L px is an angle error of about ε/L radians, so half a pixel inside 3° needs 9.5 px. Below it, `no-bracket` names the measured lever |
|
|
4557
4694
|
| `--scale`, `--rotation` | passed to the **internal anchor pass**, meaning exactly what they mean to `pose` |
|
|
4558
4695
|
|
|
4559
4696
|
### 12.5 What it cannot see — read this before using the numbers
|
|
@@ -4569,6 +4706,15 @@ simply unused; a name the directory lacks is refused `no-part-image` by name.
|
|
|
4569
4706
|
measurement on an occluded part**, by construction: one drops the covered pixels
|
|
4570
4707
|
and the other charges them. Do not put them in one column. What *is* comparable
|
|
4571
4708
|
is each against its own `visibleShare` / `unexplained`.
|
|
4709
|
+
- 🚨 **`visibleShare` cannot be averaged across frames or across fits.** It is
|
|
4710
|
+
measured through the fitted placements of the parts drawn over this one, so two
|
|
4711
|
+
fits `pose` itself cannot tell apart give it different values — measured: p99
|
|
4712
|
+
0.56 and a worst corpus-median move of 22 points inside `pose`'s own polish
|
|
4713
|
+
floor. Read it per frame beside its own residual. If a corpus statistic is
|
|
4714
|
+
needed, a **maximum** is the one that survives (it saturates), and it should be
|
|
4715
|
+
quoted with its spread. §12.3 carries the figures;
|
|
4716
|
+
[`bench/studies/2026-09-03-visibleshare`](https://github.com/firejune/rigc/blob/main/bench/studies/2026-09-03-visibleshare/README.md)
|
|
4717
|
+
carries the method.
|
|
4572
4718
|
- ⚠️ **Setup draw order, on one frame.** A `drawOrder` timeline reorders your slots
|
|
4573
4719
|
at runtime and this cannot know the time, so a candidate that has one is masked in
|
|
4574
4720
|
the order its setup pose declares. The report says so in `caveats` when it finds
|
|
@@ -4576,6 +4722,9 @@ simply unused; a name the directory lacks is refused `no-part-image` by name.
|
|
|
4576
4722
|
- ⚠️ **The hinge is searched; the pivot is not.** Nothing here searches a bone's
|
|
4577
4723
|
translation, so a bone you key `translate` on is reported `pivotFree` rather than
|
|
4578
4724
|
solved.
|
|
4725
|
+
- ⬆️ **The inward step determines a bone; it does not measure one.** §12.2b is the
|
|
4726
|
+
whole account. The two things it structurally cannot do: attribute a
|
|
4727
|
+
disagreement to one determinant, and reach a bone that does not branch.
|
|
4579
4728
|
- **A constraint moves bones after their local transforms compose.** With IK,
|
|
4580
4729
|
transform, path or physics constraints in the candidate, a fitted
|
|
4581
4730
|
`localRotationDeg` is still a placement but not necessarily a value you can key
|
package/docs/FACE.md
CHANGED
|
@@ -25,6 +25,10 @@ hold its results.
|
|
|
25
25
|
replaces a person's eye
|
|
26
26
|
- A skeleton somebody else authored, and moving a pivot inside it:
|
|
27
27
|
[INGEST.md](INGEST.md)
|
|
28
|
+
- The hierarchy underneath a face, as a general rule rather than this closed form:
|
|
29
|
+
[RIGGING.md](RIGGING.md) — §5 is why §3's `faceshift` is a bone at all and why a
|
|
30
|
+
breath's `chest` is a **sibling** of the plate it must not scale, and §4.4 is the
|
|
31
|
+
artless-parent pattern the shared-shift split is one instance of
|
|
28
32
|
- The worked example every number below comes from:
|
|
29
33
|
[`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait),
|
|
30
34
|
and its measurement half,
|
package/docs/INGEST.md
CHANGED
|
@@ -35,6 +35,9 @@ invent one.
|
|
|
35
35
|
- The coordinate contract, in one place: **AUTHORING §11.2** and
|
|
36
36
|
[`src/transform.ts`](../src/transform.ts)
|
|
37
37
|
- What the editor does when nobody tells it otherwise: **AUTHORING §10**
|
|
38
|
+
- Why the re-pivot in **§4.1** is shaped the way it is, and the child-bone row it
|
|
39
|
+
warns about worked on a bone that actually has one: [RIGGING.md](RIGGING.md) §3.
|
|
40
|
+
Its §2 is how to tell whether the pivot you are moving *to* is identified at all
|
|
38
41
|
- What the format holds and what rigc covers, skeleton by skeleton:
|
|
39
42
|
[SPEC_COVERAGE.md](SPEC_COVERAGE.md)
|
|
40
43
|
- If you are the *person operating* an agent rather than the agent:
|
package/docs/MOTION.md
CHANGED
|
@@ -25,6 +25,11 @@ toolchain and this page does not invent one.
|
|
|
25
25
|
- The parts of that picture `pose` refuses because something is drawn over them,
|
|
26
26
|
once a first candidate exists: **AUTHORING §12** (`rigc chainfit`). It reports the
|
|
27
27
|
`rotate` key value each answer implies, which is the form this recipe wants them in
|
|
28
|
+
- If the **skeleton** is what you have to decide rather than the movement — how many
|
|
29
|
+
bones, where each pivot goes, what hangs off what, and which of those the frames
|
|
30
|
+
can check: [RIGGING.md](RIGGING.md). §3.9's pivot solve is the one piece of that
|
|
31
|
+
page this one already carries, and RIGGING §2 is what a wrong answer to it looks
|
|
32
|
+
like from inside a fit
|
|
28
33
|
- If the movement is a **face** — a blink, a gaze, or a head turning off axis —
|
|
29
34
|
everything on this page still applies, and [FACE.md](FACE.md) is the geometry it
|
|
30
35
|
does not have: a turn is a projection rather than a pose, so its values are
|