spine-rigc 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +21 -0
- package/README.md +336 -223
- package/bin/rigc.cjs +40 -0
- package/cli.ts +558 -49
- package/docs/AUTHORING.md +538 -23
- package/docs/PROMPTING.md +106 -0
- package/docs/SPEC_COVERAGE.md +16 -14
- package/package.json +4 -2
- package/src/compile.ts +189 -142
- package/src/emit.ts +88 -0
- package/src/json-position.ts +253 -0
- package/src/png.ts +72 -7
- package/src/preview.ts +243 -0
- package/src/render.ts +58 -0
- package/src/types.ts +8 -0
- package/src/validate.ts +58 -13
- package/tools/plate.ts +164 -21
package/docs/AUTHORING.md
CHANGED
|
@@ -29,22 +29,27 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
29
29
|
attachment, keying practice, curve kind, draw order — sourced from Spine's own
|
|
30
30
|
public documentation: **§10**
|
|
31
31
|
|
|
32
|
+
If you were given no brief and no reference frames, this section does not apply to
|
|
33
|
+
you — skip to **§0**. You are rigging somebody's own art rather than reproducing a
|
|
34
|
+
measured shot, so none of what follows applies: it is the ladder's protocol, not a
|
|
35
|
+
property of the tool.
|
|
36
|
+
|
|
32
37
|
🔒 **A ladder run reads this guide in full and does not follow its references out of
|
|
33
38
|
it.** The guide is allowed reading; not everything it cites is. Citations here are
|
|
34
39
|
provenance for a reader of record — the loop that hit a trap, the issue that closed it —
|
|
35
40
|
and following one can arrive at a stored candidate's own spec, at the corpus inventory,
|
|
36
|
-
or at the gate a verdict is read against, none of which a run may
|
|
41
|
+
or at the **derivation** of the gate a verdict is read against, none of which a run may
|
|
42
|
+
open. ⭐ The gate's **clause statements** are a different matter and a run may read them:
|
|
43
|
+
they are in `docs/GATE.md`, item 11 of the allowed list (owner ruling 2026-08-29) — the
|
|
44
|
+
measure, the comparator, the number and the SKIP semantics, with no recorded figure in it.
|
|
45
|
+
So: read the
|
|
37
46
|
document, take its numbered sections as the input, and leave its footprints to whoever
|
|
38
47
|
is maintaining it. The rule this states is that an **allowed-reading surface has to be
|
|
39
48
|
closed under reading**; the criterion behind it is under *The honesty rule* in
|
|
40
|
-
[LADDER.md](LADDER.md), and the enumerated allowed and forbidden lists are in
|
|
49
|
+
[LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md), and the enumerated allowed and forbidden lists are in
|
|
41
50
|
`bench/runs/README.md`, *What a run may read* — the prompt that starts a run quotes them
|
|
42
51
|
outright, which is the copy that binds.
|
|
43
52
|
|
|
44
|
-
If you were given no brief and no frames — you are rigging somebody's own art rather
|
|
45
|
-
than reproducing a measured shot — none of this applies to you. It is the ladder's
|
|
46
|
-
protocol, not a property of the tool.
|
|
47
|
-
|
|
48
53
|
## The vocabulary is Spine's
|
|
49
54
|
|
|
50
55
|
Wherever rigc has no better abstraction it uses **Spine 4.3's own concept, its own
|
|
@@ -80,6 +85,10 @@ bun cli.ts check \
|
|
|
80
85
|
|
|
81
86
|
# read the table → fix the spec → build again → check again
|
|
82
87
|
# ↳ read its per-frame column before its MAE — §9.2
|
|
88
|
+
|
|
89
|
+
# …and if nobody gave you frames, LOOK at it instead — neither needs a reference:
|
|
90
|
+
bun cli.ts render --candidate path/to/spine # PNG frames + a contact sheet grid
|
|
91
|
+
bun cli.ts preview --candidate path/to/spine # one .html that plays it in Spine's own player
|
|
83
92
|
```
|
|
84
93
|
|
|
85
94
|
`build` compiles, round-trips the result through `@esotericsoftware/spine-core`,
|
|
@@ -112,16 +121,26 @@ What the flags mean:
|
|
|
112
121
|
| `--rig` | the rig spec — skeleton structure |
|
|
113
122
|
| `--motion` | the motion spec — time |
|
|
114
123
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths are written relative to it |
|
|
124
|
+
| `--copy-images` | `build` only: also copies every referenced page PNG into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own. Default is unchanged — page paths still point at the source art (issue #217) |
|
|
115
125
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory) |
|
|
116
126
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
117
|
-
| `--profile` | `spine` = the 20 validity rules · `spine-html` = all 34
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
| `--profile` | `spine` = the 20 validity rules (**the default**) · `spine-html` = all 34, opt-in |
|
|
128
|
+
| `--candidate` | `check`, `bench`, `render` and `preview` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton |
|
|
129
|
+
| `--animation` | `render` and `preview` only: which animation to show. The default is **every** one for `render` and the **first** for `preview`. A name the skeleton does not have is refused, with the ones it does have listed |
|
|
130
|
+
|
|
131
|
+
`render` also takes `--fps <n>` (the rate it samples at, default 12 — the same
|
|
132
|
+
protocol rate the reference frames use) and `--max <px>` (the long side of a
|
|
133
|
+
frame, default 256). Both commands take `--out`: a directory for `render`
|
|
134
|
+
(default `render/`), the `.html` file for `preview` (default `preview.html`).
|
|
135
|
+
|
|
136
|
+
Pick the profile deliberately, and know which one you got by saying nothing. The
|
|
137
|
+
default is `spine`: "is this valid Spine 4.3 that any runtime plays correctly",
|
|
138
|
+
which is the question a rig authored anywhere is asking. `--profile spine-html`
|
|
139
|
+
adds one renderer's policy and one project's canvas budget on top, and those
|
|
140
|
+
extra rules fire on perfectly correct Spine data (clipping attachments,
|
|
141
|
+
unweighted meshes, packed atlases) — reach for it when you are shipping into
|
|
142
|
+
*that* project, not to be thorough. A report always prints which profile ran and
|
|
143
|
+
lists what that profile left out, on `PROF` lines.
|
|
125
144
|
|
|
126
145
|
The other commands:
|
|
127
146
|
|
|
@@ -131,6 +150,8 @@ bun cli.ts validate path/to/spine # re-gate artifacts already on
|
|
|
131
150
|
bun cli.ts diff candidate.json reference.json
|
|
132
151
|
bun cli.ts check --candidate path/to/spine --frames path/to/frames
|
|
133
152
|
bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
153
|
+
bun cli.ts render --candidate path/to/spine [--animation …] [--fps 12] [--max 256]
|
|
154
|
+
bun cli.ts preview --candidate path/to/spine [--animation …] [--out preview.html]
|
|
134
155
|
```
|
|
135
156
|
|
|
136
157
|
- **`explain`** is the one to reach for when a rig compiles but looks wrong. It
|
|
@@ -144,12 +165,26 @@ bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
|
144
165
|
for opposite fixes. A measure with nothing to compare says `0/0` and says so.
|
|
145
166
|
- **`check`** renders your candidate into the reference frames' own pixel grid and
|
|
146
167
|
compares pixels — the only thing here that can see a wrong animation. **§9.**
|
|
147
|
-
- **`bench <rung>`** runs one rung of [the benchmark ladder](LADDER.md): validate
|
|
168
|
+
- **`bench <rung>`** runs one rung of [the benchmark ladder](https://github.com/firejune/rigc/blob/main/docs/LADDER.md): validate
|
|
148
169
|
under `--profile spine`, then diff against that rung's reference export, and with
|
|
149
170
|
`--frames` the `check` table as well. Unlike the three above it is a **finish
|
|
150
171
|
line, not a loop**: it opens the reference export, so a run that consults it and
|
|
151
172
|
then edits is no longer an authoring run. `check` carries no such restriction —
|
|
152
173
|
see §9.
|
|
174
|
+
- 🚨 **`render` and `preview` are how you LOOK at what you built**, and they are
|
|
175
|
+
the two that need no reference at all. Reach for them the moment a rig compiles
|
|
176
|
+
green, because green says nothing about the picture: a head that sits visibly
|
|
177
|
+
off its torso passes every assertion, loads in `spine-core` and steps
|
|
178
|
+
numerically clean — the offsets are the ones you asked for, and nothing in the
|
|
179
|
+
gate can know you did not mean them. `render` writes
|
|
180
|
+
`render/<animation>/f0000.png…` with a `contact.png` grid of **every** frame
|
|
181
|
+
beside them (open that one first — spacing is a comparison across frames) and a
|
|
182
|
+
`frames.json` sidecar naming the world box they are pictures of. `preview`
|
|
183
|
+
writes one self-contained `.html`: your skeleton, atlas and page PNGs are
|
|
184
|
+
embedded in it as data URIs and played by the official **Spine Web Player**, so
|
|
185
|
+
double-clicking it is also the interop proof — what plays there was played by
|
|
186
|
+
Esoteric Software's runtime, not by rigc's. The player is loaded from a CDN
|
|
187
|
+
rather than copied into the file, so the first open needs a network.
|
|
153
188
|
|
|
154
189
|
---
|
|
155
190
|
|
|
@@ -294,7 +329,7 @@ must be non-empty. `slots` must be present (it may be empty).
|
|
|
294
329
|
payloads in this guide are written to illustrate a field, never copied out of a
|
|
295
330
|
reference export — an example lifted from one would be handing an authoring agent an
|
|
296
331
|
answer to the rung it is standing on, which is the rule §10.6 states and the honesty
|
|
297
|
-
rule in [LADDER.md](LADDER.md) turns on. If a snippet here matches a reference file,
|
|
332
|
+
rule in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) turns on. If a snippet here matches a reference file,
|
|
298
333
|
that is a defect in this guide: report it. (It has happened — 2026-08-23; the incident
|
|
299
334
|
is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
300
335
|
|
|
@@ -482,10 +517,10 @@ and takes every slot below it out of the frame. rigc refuses a name the rig does
|
|
|
482
517
|
not declare. Omitting `end` entirely is the format's own way of saying "clip
|
|
483
518
|
everything after this one", and is left alone.
|
|
484
519
|
|
|
485
|
-
🚫 Under
|
|
520
|
+
🚫 Under `--profile spine-html` a clipping attachment is refused by
|
|
486
521
|
`A11_NO_CLIPPING_ATTACHMENTS` — that renderer skips them silently, so a mask that
|
|
487
|
-
was supposed to hide something would not. It is valid Spine and
|
|
488
|
-
accepts it; the refusal is policy, not validity.
|
|
522
|
+
was supposed to hide something would not. It is valid Spine and the default
|
|
523
|
+
`--profile spine` accepts it; the refusal is policy, not validity.
|
|
489
524
|
|
|
490
525
|
### 3.5 `constraints` — 4.3's single typed array
|
|
491
526
|
|
|
@@ -650,7 +685,7 @@ stepped.
|
|
|
650
685
|
declared duration `6.5` never fired at all against an accumulated
|
|
651
686
|
`6.499999999999994` — which read as a frame-change disagreement the pose series had
|
|
652
687
|
already fixed, and cost that run three builds
|
|
653
|
-
([`2026-08-26-rung5-1`](
|
|
688
|
+
([`2026-08-26-rung5-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-26-rung5-1/LOOP.md), §8). ⇒ **For a
|
|
654
689
|
stepped timeline, write `T − 1e-6` rather than `T`.** One grid step early cannot
|
|
655
690
|
reach the previous sample — 83,333 µs away at 12 fps — and is always seen by the
|
|
656
691
|
sample it was written for; one ULP late loses the frame. This is the same asymmetry
|
|
@@ -867,7 +902,7 @@ The report prints one line per assertion:
|
|
|
867
902
|
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` label is not on the 4.3 line (`4.3`, `4.3.N`, `4.3.N-suffix`) |
|
|
868
903
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out` |
|
|
869
904
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
870
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay
|
|
905
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art |
|
|
871
906
|
| `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, or a binding at weight 0 |
|
|
872
907
|
| `A21_MESH_RIM_PINNED` | archetype | a generated ring's rim, or a ribbon's entry row, is not pinned to its anchor bone at weight 1 |
|
|
873
908
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count |
|
|
@@ -922,6 +957,8 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
922
957
|
## 7. Before you call it done
|
|
923
958
|
|
|
924
959
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
960
|
+
Saying nothing means `spine`, so "the one you meant" is a decision either way —
|
|
961
|
+
the report's first line names the profile that judged it.
|
|
925
962
|
2. Read the `SKIP` lines. Each one is a check that did *not* run — make sure none of
|
|
926
963
|
them is a check you were relying on.
|
|
927
964
|
⚠️ Under `--profile spine` a foreign skeleton usually produces **no SKIP lines
|
|
@@ -1022,6 +1059,34 @@ gone by construction, and the reading needs no knowledge of which parts are invo
|
|
|
1022
1059
|
beside the figure too, because an edge the frames really decide wins shot after shot
|
|
1023
1060
|
rather than on a couple of them.
|
|
1024
1061
|
|
|
1062
|
+
⭐ **That dilution has a *temporal* cousin, and it bites inside a single shot's own
|
|
1063
|
+
per-frame fit.** The paragraph above is about two builds and a whole-shot figure; this is
|
|
1064
|
+
about one build and a whole-*figure* objective. Where a passage's motion is **a small
|
|
1065
|
+
part moving against a large, nearly still body** — a hand, a head, a prop, while
|
|
1066
|
+
everything else holds — the moving part is a tiny share of the ink, so a whole-figure
|
|
1067
|
+
score is dominated by the still majority. Every frame then reports a good number
|
|
1068
|
+
*individually*, the fit converges, and the passage comes out **static**: the mover was
|
|
1069
|
+
never worth enough of the objective to pull the search toward it.
|
|
1070
|
+
|
|
1071
|
+
⚠️ **Nothing else in the loop catches this.** The MAE is fine, the drift is fine on every
|
|
1072
|
+
part that is not moving, and `validate` and `diff` never look at a rendered frame. What
|
|
1073
|
+
does see it is §10.3's change column, in its **under-change** direction — and by the time
|
|
1074
|
+
it tells you, the poses are already wrong, because a key plan cannot add motion the poses
|
|
1075
|
+
do not have.
|
|
1076
|
+
|
|
1077
|
+
⇒ **Weight the objective by the reference's own frame-to-frame change.** Build a mask
|
|
1078
|
+
from where the reference *changes* between the two frames bracketing the one you are
|
|
1079
|
+
fitting, and weight the score by it — so the pixels that carry the passage's motion carry
|
|
1080
|
+
the passage's objective. It costs one extra difference per frame, needs nothing but the
|
|
1081
|
+
frames, and it turns an untrackable passage into an ordinary one.
|
|
1082
|
+
|
|
1083
|
+
📌 **Read the mask itself before you trust the fit, because it also tells you what is
|
|
1084
|
+
actually moving** — which is frequently not what the shot looks like it is about. A
|
|
1085
|
+
passage that reads as one limb waving can turn out to carry most of its change somewhere
|
|
1086
|
+
else entirely (a body-wide micro-rocking, a shadow, a trailing part), and a fitter aimed
|
|
1087
|
+
at the limb would have been chasing the minority of the evidence. The mask is the cheap
|
|
1088
|
+
way to find that out first.
|
|
1089
|
+
|
|
1025
1090
|
**Calibrate the band with a control on an edge the brief has already settled by
|
|
1026
1091
|
measurement.** Run the same test on that edge, read how far apart the two builds
|
|
1027
1092
|
come out over the pixels that decide it, and treat that separation as the scale a
|
|
@@ -1210,6 +1275,26 @@ of the chain in question**, never a whole foreign pose: a foreign pose puts the
|
|
|
1210
1275
|
where this shot never goes, and the rest of the search then spends itself fighting
|
|
1211
1276
|
what the borrow brought with it.
|
|
1212
1277
|
|
|
1278
|
+
🚨 **Before you fit a chain at all, check that it can *reach* the extremes the shot
|
|
1279
|
+
visits — a reach deficit is invisible to every per-frame fit.** This is the precondition
|
|
1280
|
+
the borrow rule assumes and the loop does not check. If a chain's segment lengths are
|
|
1281
|
+
short — read off a pose where the chain is **folded**, which is the easiest reading to
|
|
1282
|
+
take and the one most likely to be wrong — then every frame where the chain is folded
|
|
1283
|
+
fits beautifully, and the fitter *silently absorbs* the deficit on every other frame by
|
|
1284
|
+
rotating the parts it does have. Nothing reports a failure. The number is merely a little
|
|
1285
|
+
worse everywhere, which reads like an ordinary residual, until a passage needs the full
|
|
1286
|
+
extension and then no start converges anywhere near it — and multi-start does not help,
|
|
1287
|
+
because the pose being searched for is **outside the chain's reachable set**.
|
|
1288
|
+
|
|
1289
|
+
⇒ **The check is arithmetic and needs no fit.** Take the chain's total reach from your own
|
|
1290
|
+
rig; take the longest excursion the shot's own frames show that chain's end travelling —
|
|
1291
|
+
a pendulum's full swing, a limb's extreme, a prop's sweep — and compare. If the shot asks
|
|
1292
|
+
for markedly more than the chain has, the rig is wrong and no amount of searching will say
|
|
1293
|
+
so. ⭐ **A frames-side reading beats a rig-side one here**: the shot's own extremes are a
|
|
1294
|
+
measurement, while segment lengths taken off a folded pose are an estimate — so when they
|
|
1295
|
+
disagree, suspect the estimate. And do this **per chain, before its first fit**, because
|
|
1296
|
+
the surgery to fix it invalidates every pose already fitted with the short chain.
|
|
1297
|
+
|
|
1213
1298
|
**Re-fit the setup pose against frames drawn from every shot, not against one.** Every
|
|
1214
1299
|
animation is measured from the setup pose, so an error in it is an error in all of
|
|
1215
1300
|
them — and it is exactly the error one frame cannot show you. Fit an attachment's
|
|
@@ -1222,6 +1307,47 @@ against a handful of frames drawn from **every** animation at once, and hold it
|
|
|
1222
1307
|
while the per-frame poses are fitted. It is the spread that identifies it — a
|
|
1223
1308
|
sequence of single-frame fits, one per shot, is not the same thing.
|
|
1224
1309
|
|
|
1310
|
+
🚨 **That rule is not sufficient for a *joint*, and the difference is not a matter of
|
|
1311
|
+
degree.** An attachment offset is identified by a spread of *rotations*; a **pivot** — the
|
|
1312
|
+
point one bone turns about relative to its parent — is identified only by frames whose
|
|
1313
|
+
**relative rotation across that joint actually differs**. So a spread can draw frames from
|
|
1314
|
+
every single shot, satisfy the paragraph above to the letter, and still be
|
|
1315
|
+
**ill-conditioned**: if every shot holds that joint at much the same relative angle, the
|
|
1316
|
+
pivot is barely constrained, and a wrong one re-solves far away *at equal residuals*. Equal
|
|
1317
|
+
residuals is the trap — nothing in the fit reports a problem, because there genuinely is no
|
|
1318
|
+
better answer within the data you gave it.
|
|
1319
|
+
|
|
1320
|
+
🚫 **And a structural descent that holds the fitted poses fixed cannot recover a
|
|
1321
|
+
mis-triangulated pivot at all.** This is the part worth internalising, because it looks
|
|
1322
|
+
like the obvious repair and it is inert: the per-frame poses were *fitted against the wrong
|
|
1323
|
+
pivot*, so they have already absorbed its error. Move the pivot with those poses held and
|
|
1324
|
+
every frame gets worse; hold the pivot and refit the poses and they re-absorb it. **The
|
|
1325
|
+
gradient at fixed poses points nowhere**, so the descent reports convergence on the wrong
|
|
1326
|
+
geometry — and multi-start does not help either, because the defect is not a basin you
|
|
1327
|
+
failed to reach, it is a parameter the objective is no longer a function of.
|
|
1328
|
+
|
|
1329
|
+
⇒ **Triangulate a joint from part template matches across *configurations*, not from the
|
|
1330
|
+
whole-figure objective.** Match the two parts the joint connects — each is its own art file
|
|
1331
|
+
and its own reading — on frames that put the joint in **genuinely different relative
|
|
1332
|
+
angles**, and solve for the one point that is fixed in both parts' own coordinates. Then
|
|
1333
|
+
refit the poses against the corrected pivot. Two practical notes:
|
|
1334
|
+
|
|
1335
|
+
- ⭐ **"Different configurations" means what the shot list looks like, not how many frames
|
|
1336
|
+
you took.** A figure standing, walking and running may hold one joint at nearly the same
|
|
1337
|
+
angle throughout; a figure **lying down**, or inverted, or reaching across itself, is what
|
|
1338
|
+
makes that joint observable. Pick frames for *angular diversity across the joint*, and if
|
|
1339
|
+
the shot list has only one configuration, say in the log that the pivot is a prior.
|
|
1340
|
+
- 📌 **Check the conditioning rather than trusting the fit**: re-solve the joint from a
|
|
1341
|
+
subset that excludes the diverse configurations and see how far the answer moves. If it
|
|
1342
|
+
moves a long way at comparable residuals, the diverse frames were carrying the whole
|
|
1343
|
+
identification — which is exactly the state in which an earlier triangulation goes wrong
|
|
1344
|
+
silently.
|
|
1345
|
+
- ⚠️ **Sequence matters, because the surgery invalidates work.** Correcting a pivot
|
|
1346
|
+
invalidates every pose fitted under the old one, so do it **before** the per-frame fitting
|
|
1347
|
+
budget is spent, not after. When it has to be done late, expect to re-settle every channel
|
|
1348
|
+
hung off that joint — and freeze the ones that are not, so the two effects stay separable
|
|
1349
|
+
in the record.
|
|
1350
|
+
|
|
1225
1351
|
**Seed each frame's search from its neighbour's solution — as one start among the
|
|
1226
1352
|
full-range scans, never instead of them.** Adjacent frames are adjacent poses, so the
|
|
1227
1353
|
answer next door is a better first guess than the middle of any range, and it costs one
|
|
@@ -1276,6 +1402,23 @@ not a quiet one.** The matcher refuses to name a distance past the part's own si
|
|
|
1276
1402
|
(§9.2), so a limb far enough out reports no match rather than a large number — read
|
|
1277
1403
|
that beside a high figure per pixel as the strongest signal the table has.
|
|
1278
1404
|
|
|
1405
|
+
⚠️ **Excess adjacency change has a second diagnosis, and the rule above assumes the
|
|
1406
|
+
first.** *A limb has left its place* is one cause — a fit that teleported, which is what
|
|
1407
|
+
a blank drift and a high figure per pixel together point at. The other is **two
|
|
1408
|
+
independent per-frame residuals adding**: every pose inside its own accuracy, nothing
|
|
1409
|
+
lost, and the *difference* between two neighbours nonetheless several times the
|
|
1410
|
+
reference's. The fixes are opposite — the first wants the search bounded or restarted,
|
|
1411
|
+
the second wants the neighbouring poses drawn toward each other (§10.3's own note on
|
|
1412
|
+
this) — so guessing costs a round either way.
|
|
1413
|
+
|
|
1414
|
+
⇒ **Separate them by asking how much freedom the neighbour-mean step actually had.**
|
|
1415
|
+
Measure, over every neighbouring pair in the shot, how many of those steps your own
|
|
1416
|
+
constraints left **free** to move: if the answer is a percent or two of them, then the
|
|
1417
|
+
search was not free to teleport anything, and the excess is residuals adding rather than
|
|
1418
|
+
a lost limb. It is one count over data the
|
|
1419
|
+
fit already produced, and it is worth more than an afternoon of restarts aimed at the
|
|
1420
|
+
wrong cause.
|
|
1421
|
+
|
|
1279
1422
|
**What comes out is a pose per frame, and a pose per frame is not a key.** Two things
|
|
1280
1423
|
decide what survives the reduction, and **§10.3** states both: declare one tolerance
|
|
1281
1424
|
in pixels at the end of what each bone swings rather than a figure in degrees, and
|
|
@@ -1319,6 +1462,27 @@ tile has a fraction of a frame's pixels, so the per-frame change measure stays o
|
|
|
1319
1462
|
the committed stills, where it reports `no two compared frames are adjacent` and
|
|
1320
1463
|
means it.
|
|
1321
1464
|
|
|
1465
|
+
⭐ **Do not treat such a set's two stills as bookends. They are full-resolution frames
|
|
1466
|
+
at their own rate, and one of them is routinely a pose no other set on disk carries.**
|
|
1467
|
+
The temptation is to read a strided set as *a sheet, plus two files that fix the
|
|
1468
|
+
framing* — the sheet is where the shot is, so the stills look like plumbing. But the
|
|
1469
|
+
last still is the animation's **own last sample at that rate**, and a finer rate lands
|
|
1470
|
+
on a different instant: a shot whose length is not a multiple of the coarse interval
|
|
1471
|
+
ends *between* two coarse samples, so the coarse set's last frame is not the end of the
|
|
1472
|
+
shot and the finer set's is. If the shot is still moving there — and an end pose usually
|
|
1473
|
+
is the part that moves most — that pose exists in exactly one file, at full resolution,
|
|
1474
|
+
and it is worth fitting like any other frame.
|
|
1475
|
+
|
|
1476
|
+
⇒ **Two consequences for a run.** ① **Fit every committed still**, at every rate, and
|
|
1477
|
+
do not let a "sheets are for timing" habit skip them; a pose you never fitted is a pose
|
|
1478
|
+
you guessed, and a hold written across the gap because nothing on disk contradicted it
|
|
1479
|
+
is a **fabrication** rather than a simplification. ② This is the same fact a brief
|
|
1480
|
+
states from the timing side when it warns you against declaring the coarse set's
|
|
1481
|
+
rounded length: the rounding and the missing pose are one arithmetic, seen twice. If
|
|
1482
|
+
your shot's length is not a whole number of coarse intervals, expect **both** — a
|
|
1483
|
+
duration the coarse sidecar understates, and a terminal pose only the finer set shows
|
|
1484
|
+
you.
|
|
1485
|
+
|
|
1322
1486
|
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
1323
1487
|
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
1324
1488
|
sampled at, and without it the 12 fps protocol rate is assumed and the report says
|
|
@@ -1430,13 +1594,13 @@ that drew the reference frames, onto the same pixel grid, and reports what diffe
|
|
|
1430
1594
|
you specifically: it means **you may run `check` as often as you like** without
|
|
1431
1595
|
your run ceasing to be an honest authoring run. It is a loop, in the way `build` is
|
|
1432
1596
|
a loop. `bench` and `diff` against a rung's export are not — they read the answer,
|
|
1433
|
-
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
1597
|
+
and [the ladder's honesty rule](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) makes them a finish line you reach once.
|
|
1434
1598
|
|
|
1435
1599
|
📌 **That is also why the MAE figures quoted through this section stay.** Every one of
|
|
1436
1600
|
them is a candidate's own reading against rendered frames — the exam question, not the
|
|
1437
1601
|
answer key — so none of them narrows a reference-side measure, and a guide that censored
|
|
1438
1602
|
them would be teaching less for no gain in honesty. The criterion is under *The honesty
|
|
1439
|
-
rule* in [LADDER.md](LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1603
|
+
rule* in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1440
1604
|
over a reference's own count, and no such figure appears here.
|
|
1441
1605
|
|
|
1442
1606
|
🚨 **If you drive the runtime yourself, a bone's local transform lives on
|
|
@@ -1484,6 +1648,145 @@ of these inert writes and not a wrong animation.** The parameter you swept was
|
|
|
1484
1648
|
never read; a wrong rig moves the number, a write to a field nothing reads
|
|
1485
1649
|
cannot.
|
|
1486
1650
|
|
|
1651
|
+
🚨 **And the mirror image of all three: an objective that *improves by removing the
|
|
1652
|
+
subject*.** The traps above are each *"the number will not move"*, and they train you
|
|
1653
|
+
to distrust a still figure. The twin is a figure that moves, in the right direction,
|
|
1654
|
+
for the wrong reason — and it is the more dangerous one, because progress is what it
|
|
1655
|
+
looks like.
|
|
1656
|
+
|
|
1657
|
+
The shape is arithmetic, not a bug. **Any symmetric error over two silhouettes charges
|
|
1658
|
+
a mismatch in both directions**, so it charges your ink that the reference has none
|
|
1659
|
+
under *and* the reference's ink you leave bare. Give it a candidate that draws
|
|
1660
|
+
**nothing** and only the second term survives: the score is the reference's own ink,
|
|
1661
|
+
once, and it is a *finite, respectable-looking number*. A part that is present but
|
|
1662
|
+
badly posed pays both terms and can score **worse than absence**. ⇒ A search with a
|
|
1663
|
+
free enough range finds the cliff and walks off it, and every step of the walk reports
|
|
1664
|
+
as an improvement. Synthetic illustration of the whole failure in three rows — one
|
|
1665
|
+
part, one objective, nothing else changed:
|
|
1666
|
+
|
|
1667
|
+
| what the candidate does | part error |
|
|
1668
|
+
| --- | --- |
|
|
1669
|
+
| posed roughly right | 2.15 |
|
|
1670
|
+
| posed badly — overlapping the wrong reference ink | 2.48 |
|
|
1671
|
+
| **translated clean off the frame** | **1.00** |
|
|
1672
|
+
|
|
1673
|
+
The search reports **1.00 against 2.15** and calls it a 53 % gain. What it found was
|
|
1674
|
+
the absence of a subject. ⚠️ **1.00 is not a coincidence in that table, it is the
|
|
1675
|
+
construction**: absence pays the reference's ink exactly once, so on any objective
|
|
1676
|
+
normalised by that ink, *"draw nothing"* sits at 1.0 by arithmetic — which is why it is
|
|
1677
|
+
worth evaluating deliberately rather than discovering.
|
|
1678
|
+
|
|
1679
|
+
⇒ **Four defences, and the first is the cheap one.**
|
|
1680
|
+
|
|
1681
|
+
- **Bound the search to the frame.** A part cannot legitimately leave the picture on a
|
|
1682
|
+
shot whose frames all draw it, so a translation range wide enough to exit the
|
|
1683
|
+
viewport is a range that contains a false optimum. Bound each parameter by what the
|
|
1684
|
+
frames can *show*, not by what the format permits.
|
|
1685
|
+
- ⚠️ **And its converse, which is the easier half to get wrong: a bound has to
|
|
1686
|
+
*reach* what the frames show, not merely stop where they stop.** The two failures
|
|
1687
|
+
look nothing alike — a bound that is too wide loses a fit to the cliff, a bound
|
|
1688
|
+
that is too narrow loses one to a wall it never reports hitting. Bounding a
|
|
1689
|
+
vertical channel to the range the *rest* pose occupies is the classic case: a shot
|
|
1690
|
+
that drops its subject in from hundreds of pixels above the standing pose puts its
|
|
1691
|
+
own entry outside the search entirely, and the fitter returns the best pose *it was
|
|
1692
|
+
allowed*, which is the top of the box, silently. ⇒ **Take each channel's range from
|
|
1693
|
+
the brief and the frames — the extremes the shot actually visits — and then check
|
|
1694
|
+
afterwards how many converged values are sitting on a bound.** A knob resting
|
|
1695
|
+
exactly on its limit is the signature, and it costs one line to print.
|
|
1696
|
+
- **Assert the part is drawn, every iteration.** Count your own ink for that part and
|
|
1697
|
+
reject any candidate whose count is zero or a small fraction of the reference's. This
|
|
1698
|
+
is one comparison and it makes the cliff unreachable rather than merely unattractive.
|
|
1699
|
+
- ⚠️ **Write it at the resolution the level is actually being evaluated at.** On a
|
|
1700
|
+
coarse-to-fine pyramid, a threshold expressed in full-resolution pixel counts
|
|
1701
|
+
refuses **every** coarse pose — and a rejection that fires on everything is
|
|
1702
|
+
indistinguishable from an objective with no gradient. You get `Infinity`, or a
|
|
1703
|
+
figure far worse than the same search reaches with the assert switched off, and
|
|
1704
|
+
nothing in either says *"your guard is the problem"*. ⇒ Express the count as a
|
|
1705
|
+
**fraction of the reference's ink at that same level**, so the test means the same
|
|
1706
|
+
thing at every rung of the pyramid.
|
|
1707
|
+
- **Charge ink that leaves the window, because the cliff has a second entrance.** If
|
|
1708
|
+
your objective is computed inside a window around the reference's own drawn box —
|
|
1709
|
+
and it usually is, since that is what makes it cheap — then ink outside the window
|
|
1710
|
+
costs **nothing**, and the three defences above do not reach that: the part is still
|
|
1711
|
+
drawn, its count is still healthy, and the score still falls. The fitter hangs a part
|
|
1712
|
+
a few hundred pixels below the frame and reports progress every step. ⇒ **Count your
|
|
1713
|
+
own ink further than a small margin outside the reference's drawn box and charge it.**
|
|
1714
|
+
One line, in the same place as the ink count, and it closes the entrance the bound
|
|
1715
|
+
closes only when the bound happens to be tight enough.
|
|
1716
|
+
- **Read the objective's floor before you trust its direction.** Evaluate *"draw
|
|
1717
|
+
nothing"* once, deliberately, and keep the figure. Any score at or below it is the
|
|
1718
|
+
cliff, whatever the search says — and if your best honest pose is *above* that floor,
|
|
1719
|
+
the objective is ranking absence over effort and needs an asymmetry (charge bare
|
|
1720
|
+
reference ink more than stray candidate ink) before it is safe to optimise against.
|
|
1721
|
+
|
|
1722
|
+
📌 **`check` itself is not exposed to this** — its `MAE in it` and `share` columns
|
|
1723
|
+
divide over the **reference's** own drawn pixels and a chain that draws nothing reads
|
|
1724
|
+
0 % on 0 slots, which §9.2 says is the loudest row in the table and not the quietest.
|
|
1725
|
+
The trap lives in the objectives **you** write inside a fit, where the denominator is
|
|
1726
|
+
yours to choose.
|
|
1727
|
+
|
|
1728
|
+
🚨 **The cliff's nearest cousin, and the one that survives all four defences:
|
|
1729
|
+
*sacrificial cover*.** Every defence above protects a part from being **removed**. None
|
|
1730
|
+
protects a part from being **moved somewhere wrong on purpose**. A whole-figure objective
|
|
1731
|
+
scores one number over every pixel, so when part A is mis-placed and leaves reference ink
|
|
1732
|
+
bare, the cheapest available improvement is frequently to drag **part B off its own correct
|
|
1733
|
+
place to cover that ink**. Both parts are drawn, both counts are healthy, nothing leaves
|
|
1734
|
+
the window — and the score genuinely falls, because covering bare ink is worth more to a
|
|
1735
|
+
blunt objective than B's own displacement costs it.
|
|
1736
|
+
|
|
1737
|
+
⚠️ **What makes it expensive is that the objective is not lying.** The pose it prefers
|
|
1738
|
+
really is better *by that measure*. So the loop offers no signal at all: the fit converges,
|
|
1739
|
+
the number improves, and what you have is one part visibly out of place standing in for
|
|
1740
|
+
another. It surfaces later as a **drift** on the sacrificed part — a slot several pixels
|
|
1741
|
+
from where the frames put it inside a pose whose overall figure looks fine — which is the
|
|
1742
|
+
one measure that reads parts individually.
|
|
1743
|
+
|
|
1744
|
+
⇒ **Two ways to catch it, and the first is nearly free.**
|
|
1745
|
+
|
|
1746
|
+
- **Read a per-part residual beside the composite, never only the composite.** Score each
|
|
1747
|
+
part against its own template match as well, and flag any frame where the composite
|
|
1748
|
+
improves while a part's own residual worsens. That divergence *is* the signature; the
|
|
1749
|
+
composite alone cannot express it.
|
|
1750
|
+
- **Seed the parts analytically from their own measured features, then refine jointly with
|
|
1751
|
+
the sacrificed part pinned.** If a part's place is independently measurable — a colour
|
|
1752
|
+
feature, a template peak, a contact row the brief gives you — put it there first rather
|
|
1753
|
+
than letting the composite negotiate it, and hold the part that was being abused fixed
|
|
1754
|
+
while the rest re-settles.
|
|
1755
|
+
|
|
1756
|
+
⚖️ **Expect the corrected pose to score *worse* on the composite, and record that as a
|
|
1757
|
+
trade.** A few percent worse on your own objective while decisively better on every
|
|
1758
|
+
frame-derived placement instrument is the **expected** shape of this repair, not a
|
|
1759
|
+
regression — the composite's preference was the defect. Declare an accept threshold before
|
|
1760
|
+
you need it, say how often you used it, and name the frames. ⭐ **And prefer the
|
|
1761
|
+
frame-derived instruments when they disagree with the composite about a single part's
|
|
1762
|
+
place**: the composite is one number over everything, while a template match on that part's
|
|
1763
|
+
own art is a measurement of the thing in question.
|
|
1764
|
+
|
|
1765
|
+
🚨 **One more inert-write trap, and it is on the way *out* of the fit rather than
|
|
1766
|
+
inside it: your compiled animation is not your pose series.** Everything above is about
|
|
1767
|
+
a search that reads the wrong thing; this is about a search that was right and an
|
|
1768
|
+
emission that was not. The formats differ in a way that is easy to miss — **a translate
|
|
1769
|
+
key is an offset from the setup pose, while a fitter almost always drives the absolute
|
|
1770
|
+
local position** — so writing the fitted numbers straight into keys applies the setup
|
|
1771
|
+
offset a second time and displaces the whole figure by it.
|
|
1772
|
+
|
|
1773
|
+
⚠️ **What makes it expensive is how it presents.** `build` is green: the numbers are
|
|
1774
|
+
finite, the durations agree, nothing is degenerate. And `check` does not say *"your keys
|
|
1775
|
+
are offset"* — it says the union box is a fifth larger than the reference's, the MAE is
|
|
1776
|
+
several times anything a wrong pose produces, and no slot is attributable anywhere.
|
|
1777
|
+
That reads like a **wrong rig**, so the hours go into the rig.
|
|
1778
|
+
|
|
1779
|
+
⇒ **Before reading a single measure, sample your own compiled animation and diff it
|
|
1780
|
+
against the pose series the fitter produced.** `sampleAnimation` in
|
|
1781
|
+
[`src/render.ts`](../src/render.ts) is the same stepper the frames were made with, so
|
|
1782
|
+
this is a handful of lines and it is exact: for every frame, for every bone, the local
|
|
1783
|
+
transform the file plays back against the local transform you fitted. A constant offset
|
|
1784
|
+
per channel is this bug; a constant *factor* is a unit or lever mistake; zeros
|
|
1785
|
+
everywhere are §9.1's `bone.pose` trap one level earlier. ⭐ **The general rule: a
|
|
1786
|
+
pipeline with a fit at one end and a file at the other needs one check that the file
|
|
1787
|
+
plays what the fit found**, and it belongs before the measures rather than after a day
|
|
1788
|
+
of them.
|
|
1789
|
+
|
|
1487
1790
|
### 9.2 Reading the table
|
|
1488
1791
|
|
|
1489
1792
|
```
|
|
@@ -1660,6 +1963,81 @@ pair the reference moves *one pixel* across stopped being visible at half scale,
|
|
|
1660
1963
|
the diagnostic run reported a frame-change disagreement the graded run does not have.
|
|
1661
1964
|
Read it for the floor, never as the verdict.
|
|
1662
1965
|
|
|
1966
|
+
🚨 **And a third, which decides whether the recipe measures anything at all: `--atlas`
|
|
1967
|
+
substitutes region *geometry* as well as texture.** The diagnostic's logic is *"same
|
|
1968
|
+
skeleton, same keys, the reference's own texture"* — but an atlas entry is not only a
|
|
1969
|
+
page and a rectangle. It also carries how the region was packed: **`rotate`**, and the
|
|
1970
|
+
trim offsets that say where the opaque part sits inside the original image. Swap the
|
|
1971
|
+
atlas and your attachments are re-seated on those, so the quads change too.
|
|
1972
|
+
|
|
1973
|
+
⇒ **The recipe measures a floor only when the substitution is *"same quads, coarser
|
|
1974
|
+
texture"*.** Where the supplied atlas packs its regions **rotated or trimmed** and your
|
|
1975
|
+
attachments were measured off the loose PNGs, it is not — and the tell is unmistakable:
|
|
1976
|
+
**the number goes the wrong way.** A texture floor can only *explain* error, so a
|
|
1977
|
+
diagnostic that sends the MAE **up** on every set has substituted geometry, not just
|
|
1978
|
+
pixels, and the run's own atlas was the more faithful of the two.
|
|
1979
|
+
|
|
1980
|
+
⚠️ **Then the honest verdict is *inconclusive*, not *no floor*.** Both readings stay
|
|
1981
|
+
open — there may be a texture floor this diagnostic cannot isolate — so record the
|
|
1982
|
+
figures, say the substitution changed the quads, and do **not** convert a failed
|
|
1983
|
+
diagnostic into a claim about the shot. ⇒ Check the atlas's own entries for `rotate` and
|
|
1984
|
+
for trim before you run it; that is one look at a text file, and it tells you in advance
|
|
1985
|
+
whether the number you are about to take will mean anything.
|
|
1986
|
+
|
|
1987
|
+
🚨 **The precondition the advice above does not state: a floor measured with another
|
|
1988
|
+
part misplaced is not a floor.** Measuring at the rest pose is right — it is the one
|
|
1989
|
+
pose you can often *prove*, because the setup pose is the art at its own scale and the
|
|
1990
|
+
frames state the standing dimensions — but "the pose is provably right" is a claim
|
|
1991
|
+
about **one part**, and the floor you read is a whole-figure number. Any other part
|
|
1992
|
+
that can occlude the one you are measuring is inside that number too, and a part
|
|
1993
|
+
sitting tens of units off its place occludes the **wrong** pixels: the ones it hides
|
|
1994
|
+
count as yours-and-not-theirs, the ones it should have hidden count as
|
|
1995
|
+
theirs-and-not-yours, and both land on the part you thought you were isolating.
|
|
1996
|
+
|
|
1997
|
+
The damage is that you then hold a *plausible* floor and calibrate against it. A
|
|
1998
|
+
synthetic case with the same shape — one part measured three ways, nothing about that
|
|
1999
|
+
part changed between the rows:
|
|
2000
|
+
|
|
2001
|
+
| what else is placed | silhouette IoU read for the measured part |
|
|
2002
|
+
| --- | --- |
|
|
2003
|
+
| a neighbour still tens of units out of place | 0.74 |
|
|
2004
|
+
| that neighbour placed | **0.95** |
|
|
2005
|
+
| (the difference) | 0.21, all of it the neighbour |
|
|
2006
|
+
|
|
2007
|
+
A fifth of an IoU is larger than most of what a fit is trying to buy, so two or three
|
|
2008
|
+
experiments get read against the wrong baseline before anything exposes it — and what
|
|
2009
|
+
usually exposes it is the setup fit finishing, which is *after* you needed the number.
|
|
2010
|
+
|
|
2011
|
+
⇒ **Before believing a floor, check that every part which can occlude the one you are
|
|
2012
|
+
measuring is already placed** — and prefer a frame where the parts are **far apart or
|
|
2013
|
+
only one is drawn** to one where they overlap, which is §8.1's rule for calibrating a
|
|
2014
|
+
two-part assignment applied to a floor. If no such frame exists, say in the log that
|
|
2015
|
+
the floor is an upper bound on the error rather than a floor under it. ⚠️ This is the
|
|
2016
|
+
same failure as capturing a guard's expected value from a screen that is already
|
|
2017
|
+
broken: the baseline records the defect, and then the *repair* is what looks wrong.
|
|
2018
|
+
|
|
2019
|
+
⚖️ **`frames.json`'s own box can be refused for a reason that is not a coordinate
|
|
2020
|
+
error, and there is an honest answer.** The test is on **extent**: a candidate authored
|
|
2021
|
+
in the frames' own world units — one whose setup box lands on the reference's to the
|
|
2022
|
+
pixel — still fails it if its union content box differs by a few pixels at the
|
|
2023
|
+
extremes, because one part reaching somewhere nothing in the frames reaches is enough.
|
|
2024
|
+
`check` then fits its own box, and on a multi-shot root the fitted framing costs every
|
|
2025
|
+
set some MAE against the declared one — the same order as the shared-versus-per-set gap
|
|
2026
|
+
the `--framing` flag's own help quotes, and easily more than a round of fitting buys.
|
|
2027
|
+
That cost is real and it is **not** a sign you got the coordinates wrong.
|
|
2028
|
+
|
|
2029
|
+
⇒ **Report both, label which is which, and say what separates them.** Run `check`
|
|
2030
|
+
unaided — that is the figure the artifact produces on its own and the one that belongs
|
|
2031
|
+
in a run's record — then run it once more with `--viewport` on the declared box and keep
|
|
2032
|
+
that output as a **named diagnostic file** beside the first. The gap between them is the
|
|
2033
|
+
framing; what is left is the keys, which is the only reason to want the second number.
|
|
2034
|
+
🚫 **The pinned run is never the record.** `--viewport` is a claim about your own
|
|
2035
|
+
coordinates and `check` says so above every figure it prints under one: *nothing checks
|
|
2036
|
+
it*. And do not chase the refusal by shrinking a part to fit the box — that trades a
|
|
2037
|
+
framing cost for a wrong silhouette, which is worse in every column that matters.
|
|
2038
|
+
Instead read the `content` line's own advice: it names how much wider and shorter you
|
|
2039
|
+
cover, and **which part reaches too far is a drift question**, not a framing one.
|
|
2040
|
+
|
|
1663
2041
|
**`Δpx` and `ref Δ`** are the two columns that do **not** compare you against the
|
|
1664
2042
|
reference. They compare each side against **itself one frame earlier**: how many
|
|
1665
2043
|
pixels of your own frame moved since your own previous frame, and the same for the
|
|
@@ -1796,6 +2174,19 @@ a per-shot list. **§8.1** is how to act on it: the next iteration goes to the w
|
|
|
1796
2174
|
chain by error per pixel, and a chain already at the floor is frozen rather than
|
|
1797
2175
|
re-fitted.
|
|
1798
2176
|
|
|
2177
|
+
⚠️ **One exception to "0 % is the loudest row", and on a mesh rig it is the common
|
|
2178
|
+
case: a chain whose roster reads `(draws nothing)` rather than `0/n`.** Those are two
|
|
2179
|
+
different states and the table prints them differently. `0 %` on `0/3` **slots drawn**
|
|
2180
|
+
means three slots exist on that chain and none of them put ink on the frame — that is
|
|
2181
|
+
the loud row, and it is a missing part. `(draws nothing)` in the **bones** roster means
|
|
2182
|
+
the chain carries **no slot at all**, and a mesh's control bones are exactly that: the
|
|
2183
|
+
mesh attachment lives on the slot of the bone the mesh hangs from, so the bones that
|
|
2184
|
+
*deform* it own nothing to draw. ⇒ **On a mesh rig that row is normal and quiet.** Read
|
|
2185
|
+
the roster at the foot of the report before reacting to a chain's share: if the chain's
|
|
2186
|
+
slots column is a parenthesis rather than a fraction, the deformation it carries is
|
|
2187
|
+
already being scored inside the chain that owns the slot, and the row is telling you
|
|
2188
|
+
about your bone tree rather than about a hole in your figure.
|
|
2189
|
+
|
|
1799
2190
|
### 9.3 What it still cannot see
|
|
1800
2191
|
|
|
1801
2192
|
- **Anything a frame does not contain.** Bone `length`, the setup `inherit` mode,
|
|
@@ -2019,6 +2410,86 @@ smallest single-frame move inside that span**. It is one line in the planner, it
|
|
|
2019
2410
|
a handful of keys, and it is the difference between a reduction that is accurate and
|
|
2020
2411
|
one that is accurate *in proportion to what is happening*.
|
|
2021
2412
|
|
|
2413
|
+
⚠️ **The opposite defect exists and forcing keys makes it worse.** Everything above is
|
|
2414
|
+
one direction — *my curve slopes through a plateau the reference holds* — and its fix is
|
|
2415
|
+
to force both ends as keys. The other direction is *my candidate moves several times
|
|
2416
|
+
what the reference does on a pair the reference barely moves across*, and if you reach
|
|
2417
|
+
for the same fix you will pin the excess in place instead of removing it. **The cause is
|
|
2418
|
+
different**: there the key plan was smoothing away motion the shot has; here the key
|
|
2419
|
+
plan is faithful and what disagrees is the **per-frame residual** — two neighbouring
|
|
2420
|
+
poses each a little off, in opposite directions, so the *difference* between them is
|
|
2421
|
+
several times either error. Forcing both as keys asks the planner to reproduce exactly
|
|
2422
|
+
the two poses whose disagreement is the problem.
|
|
2423
|
+
|
|
2424
|
+
⭐ **Diagnose it before you fix it, with one comparison.** Take the two frames the column
|
|
2425
|
+
flags and ask whether your **poses** at those two frames are each inside your own fitting
|
|
2426
|
+
accuracy. If they are — and the pair still disagrees — the defect is the residual and not
|
|
2427
|
+
the plan. Synthetic case, one pair:
|
|
2428
|
+
|
|
2429
|
+
| | reference moves | candidate moves | each pose's own error |
|
|
2430
|
+
| --- | --- | --- | --- |
|
|
2431
|
+
| a quiet pair | 0.8 px | 4.1 px | 1.6 px and 1.7 px, opposite signs |
|
|
2432
|
+
|
|
2433
|
+
Both poses are ordinary; the pair is a five-fold disagreement built out of them.
|
|
2434
|
+
|
|
2435
|
+
⇒ **The fix has the same shape as the relative floor above: make the smoothing slack
|
|
2436
|
+
relative to the reference's own local change.** Where the reference barely moves,
|
|
2437
|
+
contract your neighbouring poses toward each other until your own frame-to-frame change
|
|
2438
|
+
is inside the band — accepting a small, *bounded* loss of fidelity on those frames in
|
|
2439
|
+
exchange for the one measure that can see a hold. ⚠️ **That is a trade and it is recorded
|
|
2440
|
+
as a trade**: name the frames, name the cost per frame, and say in the log that you took
|
|
2441
|
+
it. A contraction reported as a fit is the same dishonesty as a hold reported as a
|
|
2442
|
+
measurement, and the cost is real — the frames you contracted are slightly less faithful
|
|
2443
|
+
than they were.
|
|
2444
|
+
|
|
2445
|
+
🚨 **Contract the *planned curves*, not the pose series — the report never sees the pose
|
|
2446
|
+
series.** This is one sentence and it is worth two builds: the change column measures
|
|
2447
|
+
your **compiled animation sampled at the frames' own rate**, and between your poses and
|
|
2448
|
+
that lie the key reduction and the curves. Contract before the reduction and you have
|
|
2449
|
+
adjusted a series nothing downstream reads — the planner then re-fits its spans through
|
|
2450
|
+
the adjusted poses, the interpolants land where the tolerance allows, and the pair you
|
|
2451
|
+
were aiming at comes back out of band having *moved*. ⇒ **Apply the contraction where
|
|
2452
|
+
the measurement is taken**: plan the keys, sample the planned curves, find the offending
|
|
2453
|
+
pairs, and contract *those samples* by forcing or moving the keys that produce them —
|
|
2454
|
+
then re-plan and re-sample. That is the closing loop below, and its subject is the curve
|
|
2455
|
+
series throughout.
|
|
2456
|
+
|
|
2457
|
+
⚠️ **And aim *inside* the band, not at it.** `check`'s thresholds are exact and stated in
|
|
2458
|
+
[`src/check.ts`](../src/check.ts), so it is tempting to converge until every pair is
|
|
2459
|
+
just inside. But a run measuring its own change renders in **its own framing**, and the
|
|
2460
|
+
report renders in the one `check` chose — and a fraction of a percent of scale is worth
|
|
2461
|
+
a few percent of a pixel count. A pair you cleared by a hair in your loop can sit the
|
|
2462
|
+
wrong side of the same threshold in the report, on a difference that is entirely
|
|
2463
|
+
framing. ⇒ Converge to a **margin** — clear the band by enough that a percent of scale
|
|
2464
|
+
cannot cross it — and re-read the real report before believing the column.
|
|
2465
|
+
|
|
2466
|
+
🚨 **There is a third direction, and on a busy shot it is the binding one: your candidate
|
|
2467
|
+
moving too *little*.** The two cases above are both *you moved when you should not have*
|
|
2468
|
+
— a hold that is not held, and excess change on a quiet pair. But `check`'s rule is
|
|
2469
|
+
**two-sided**: it faults a pair when **either** side moves several times the other by
|
|
2470
|
+
more than its pixel floor. So the mirror case is a reference that is genuinely busy and
|
|
2471
|
+
a candidate that reproduces a fraction of it, and nothing in the two paragraphs above
|
|
2472
|
+
names it.
|
|
2473
|
+
|
|
2474
|
+
⭐ **The practical form is a floor rather than a ceiling: on every pair the reference
|
|
2475
|
+
moves, yours has to move at least about a quarter as much.** Read the exact multiple and
|
|
2476
|
+
the pixel floor out of [`src/check.ts`](../src/check.ts) rather than trusting the
|
|
2477
|
+
approximation — but plan against the floor, because it behaves quite differently from
|
|
2478
|
+
the ceiling:
|
|
2479
|
+
|
|
2480
|
+
- **It is not fixed by keys.** Over-change is a planning artefact you can force or
|
|
2481
|
+
contract away. Under-change means the *poses themselves* barely differ, so no key plan
|
|
2482
|
+
recovers it — the fit has to find more motion before the planner sees any.
|
|
2483
|
+
- **It is where a whole-figure objective fails hardest**, which is why the item below
|
|
2484
|
+
belongs beside it: a passage whose motion is a small part against a large still body
|
|
2485
|
+
contributes almost nothing to a whole-shot score, so a fitter converges happily on a
|
|
2486
|
+
near-static series and every pose looks fine on its own.
|
|
2487
|
+
- ⚠️ **And the band will accept a shot that is visibly underplayed.** Clearing the floor
|
|
2488
|
+
at a quarter is not reproducing the motion; it is not *failing* it. A run whose busy
|
|
2489
|
+
passage sits near the floor should say so in the log as a known-weak passage rather than
|
|
2490
|
+
quote the column as if it were a fidelity result — the column is a **band**, and a band
|
|
2491
|
+
is the widest thing that passes, not the thing you were aiming at.
|
|
2492
|
+
|
|
2022
2493
|
⭐ **Then stop trusting the floor and close the loop on the frames, because a floor is
|
|
2023
2494
|
a heuristic and the column is a measurement.** The floor above cut rung 3's
|
|
2024
2495
|
disagreements from three to one and could not reach the last: **sample your own planned
|
|
@@ -2115,6 +2586,50 @@ tolerance**, which costs nothing and needs no reference: scan each knob around i
|
|
|
2115
2586
|
converged value and read how far it moves before the objective does. Then declare a
|
|
2116
2587
|
tolerance at or above the widest of them, and record both numbers.
|
|
2117
2588
|
|
|
2589
|
+
⚖️ **Read that last sentence with the rule two paragraphs up, because taken literally
|
|
2590
|
+
the two pull apart — and the resolution is that the basin is a *per-channel floor*,
|
|
2591
|
+
not a second global declaration.** The tension is real: *declare one tolerance* asks
|
|
2592
|
+
for a single figure in one unit so the density trade reads as one curve, while
|
|
2593
|
+
*declare at or above the widest basin* points at the worst-identified knob in the rig.
|
|
2594
|
+
Those can differ by **an order of magnitude** — a well-levered channel the objective
|
|
2595
|
+
pins to a fraction of a pixel sitting in the same rig as a part the objective barely
|
|
2596
|
+
sees at all, whose basin is several pixels wide. Take the widest and every good channel
|
|
2597
|
+
is keyed to the worst one's ignorance; take the declared figure alone and the bad
|
|
2598
|
+
channel ships the fitter's wander as data.
|
|
2599
|
+
|
|
2600
|
+
⭐ **What decides it: a basin belongs to the estimator that wrote the channel, and a
|
|
2601
|
+
run that fits poses has more than one.** The two rules are answering different
|
|
2602
|
+
questions. *One tolerance* is about the **unit and comparability** of the figure you
|
|
2603
|
+
declare — that survives untouched. *The basin* is about the **noise under a particular
|
|
2604
|
+
series**, and noise is a property of the estimator on that channel, not of the rig. So:
|
|
2605
|
+
|
|
2606
|
+
> **Declare one tolerance, in pixels at the end of what each bone swings. Then floor it
|
|
2607
|
+
> per channel at that channel's own basin, capped.** Effective tolerance for a channel
|
|
2608
|
+
> = `max(declared, min(that channel's basin, cap))`.
|
|
2609
|
+
|
|
2610
|
+
- **Why per channel** — the thing the floor protects against is encoding wander, and
|
|
2611
|
+
wander is per channel. A global maximum spends keys nowhere they were needed and
|
|
2612
|
+
removes them nowhere they were wrong.
|
|
2613
|
+
- **Why a cap, and this is the part worth understanding.** The basin bounds **what you
|
|
2614
|
+
know**; the tolerance also bounds **what you render**, and the rendered series is read
|
|
2615
|
+
by `check`'s change column at *zero* slack (§9.2). So an uncapped floor lets a
|
|
2616
|
+
badly-identified channel buy a reduction error large enough to show up as motion the
|
|
2617
|
+
reference does not have — trading a measure nothing reads for one read at zero
|
|
2618
|
+
tolerance, which is the wrong direction. A cap of a pixel or two, declared and
|
|
2619
|
+
recorded, bounds the reduction error whatever the identifiability.
|
|
2620
|
+
- ⇒ **And a channel whose basin exceeds the cap is telling you it is not identified,
|
|
2621
|
+
which is a different problem with a different fix.** The answer there is a **prior** —
|
|
2622
|
+
regularise the channel toward a smooth trend and say in the log that you did — not a
|
|
2623
|
+
tolerance wide enough to key it three times and call the result a measurement. ⚠️ Such
|
|
2624
|
+
a channel is *partly a prior rather than a measurement*, and a run that does this
|
|
2625
|
+
records which channels and over which passages, exactly as it records a contraction
|
|
2626
|
+
trade below.
|
|
2627
|
+
|
|
2628
|
+
📌 **Record all three numbers**: the declared tolerance, each floored channel's basin,
|
|
2629
|
+
and the cap. `diff`'s `key_counts` is the finish line and a run gets one shot at it, so
|
|
2630
|
+
the arithmetic that produced the density is the only thing that makes the figure
|
|
2631
|
+
readable afterwards.
|
|
2632
|
+
|
|
2118
2633
|
**A rig's parameters are not identified by its pixels — remove the gauges before you
|
|
2119
2634
|
key.** A bone that carries no attachment is an exact gauge: turn it by δ, turn its
|
|
2120
2635
|
children back by δ, and **not one pixel changes**. Anything optimising against pixels
|