spine-rigc 0.3.0 โ 0.4.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/README.md +27 -3
- package/cli.ts +19 -2
- package/docs/AUTHORING.md +416 -47
- package/docs/SPEC_COVERAGE.md +5 -4
- package/package.json +1 -1
- package/src/check.ts +560 -24
- package/src/framing.ts +280 -0
- package/src/render.ts +21 -0
- package/src/slots.ts +102 -4
package/docs/AUTHORING.md
CHANGED
|
@@ -18,7 +18,10 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
18
18
|
- Formats and CLI reference: [README.md](../README.md)
|
|
19
19
|
- The rig spec's own source-level documentation: [`src/rig.ts`](../src/rig.ts)
|
|
20
20
|
- The motion spec and emitted shapes: [`src/types.ts`](../src/types.ts)
|
|
21
|
-
- What the format holds and rigc covers:
|
|
21
|
+
- What the format holds and rigc covers: `docs/SPEC_COVERAGE.md` โ ๐ซ **not an
|
|
22
|
+
authoring input, and deliberately unlinked**: it inventories the benchmark corpus
|
|
23
|
+
skeleton by skeleton, so it is on the ladder run's forbidden list. Named here for a
|
|
24
|
+
maintainer, not offered to a run
|
|
22
25
|
- Reproducing a shot you were given as pictures: **ยง8**, and read it *before* you
|
|
23
26
|
start measuring rather than after; **ยง8.1** if the figure has more joints than you
|
|
24
27
|
can measure one at a time; then **ยง9** for the loop that closes it
|
|
@@ -26,6 +29,22 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
26
29
|
attachment, keying practice, curve kind, draw order โ sourced from Spine's own
|
|
27
30
|
public documentation: **ยง10**
|
|
28
31
|
|
|
32
|
+
๐ **A ladder run reads this guide in full and does not follow its references out of
|
|
33
|
+
it.** The guide is allowed reading; not everything it cites is. Citations here are
|
|
34
|
+
provenance for a reader of record โ the loop that hit a trap, the issue that closed it โ
|
|
35
|
+
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 open. So: read the
|
|
37
|
+
document, take its numbered sections as the input, and leave its footprints to whoever
|
|
38
|
+
is maintaining it. The rule this states is that an **allowed-reading surface has to be
|
|
39
|
+
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
|
|
41
|
+
`bench/runs/README.md`, *What a run may read* โ the prompt that starts a run quotes them
|
|
42
|
+
outright, which is the copy that binds.
|
|
43
|
+
|
|
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
|
+
|
|
29
48
|
## The vocabulary is Spine's
|
|
30
49
|
|
|
31
50
|
Wherever rigc has no better abstraction it uses **Spine 4.3's own concept, its own
|
|
@@ -60,6 +79,7 @@ bun cli.ts check \
|
|
|
60
79
|
--frames path/to/reference/frames
|
|
61
80
|
|
|
62
81
|
# read the table โ fix the spec โ build again โ check again
|
|
82
|
+
# โณ read its per-frame column before its MAE โ ยง9.2
|
|
63
83
|
```
|
|
64
84
|
|
|
65
85
|
`build` compiles, round-trips the result through `@esotericsoftware/spine-core`,
|
|
@@ -73,6 +93,18 @@ the animation is the one in the frames, and there is no assertion that could โ
|
|
|
73
93
|
ยง9. The two run in that order because `check` needs artifacts on disk and `build`
|
|
74
94
|
only writes them when the gate is green.
|
|
75
95
|
|
|
96
|
+
๐จ **Read `check`'s per-frame column before its MAE.** The table's headline figures
|
|
97
|
+
are the MAE and the slot drift, and a reader who came for those will skip the
|
|
98
|
+
`per-frame` line printed under them โ but that line is the only thing in this
|
|
99
|
+
toolchain that can see a **hold**, a **loop seam** or a **one-frame event**. Those
|
|
100
|
+
defects are cheap in every single frame and wrong only in the relation between two,
|
|
101
|
+
so an aggregate MAE, `diff` and the gate are all silent on them: a candidate can
|
|
102
|
+
slope a line through a frame pair the reference holds perfectly still across, or
|
|
103
|
+
end a cycle on a pose that is not the pose it began on, without moving a decimal
|
|
104
|
+
anywhere else in the loop. **ยง9.2** documents the column. It is named here because
|
|
105
|
+
ยง0 is where the loop is learned, and a run that opens a report for its chain table
|
|
106
|
+
can come away with the column unread.
|
|
107
|
+
|
|
76
108
|
What the flags mean:
|
|
77
109
|
|
|
78
110
|
| Flag | Meaning |
|
|
@@ -263,8 +295,8 @@ payloads in this guide are written to illustrate a field, never copied out of a
|
|
|
263
295
|
reference export โ an example lifted from one would be handing an authoring agent an
|
|
264
296
|
answer to the rung it is standing on, which is the rule ยง10.6 states and the honesty
|
|
265
297
|
rule in [LADDER.md](LADDER.md) turns on. If a snippet here matches a reference file,
|
|
266
|
-
that is a defect in this guide: report it. (It has happened โ 2026-08-23
|
|
267
|
-
|
|
298
|
+
that is a defect in this guide: report it. (It has happened โ 2026-08-23; the incident
|
|
299
|
+
is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
268
300
|
|
|
269
301
|
### 3.1 `skeleton` โ the header
|
|
270
302
|
|
|
@@ -603,6 +635,26 @@ stepped.
|
|
|
603
635
|
written for. โ ๏ธ What this does **not** protect you from is rounding your own
|
|
604
636
|
times before you write them โ write `2/12`, not `0.1667`, and let the compiler
|
|
605
637
|
do the quantising.
|
|
638
|
+
- ๐จ **Nor does it protect a stepped key whose time is ALREADY on the 1e-6 grid.**
|
|
639
|
+
Rounding down leaves such a time exactly where you wrote it, and the sampler does
|
|
640
|
+
not arrive there: a player โ and `sampleAnimation`, and therefore `check` โ reaches
|
|
641
|
+
sample *i* by accumulating `1/fps` *i* times, which for many *i* lands a few ULPs
|
|
642
|
+
**below** `i/fps`. `2/12` is saved by the rule above precisely because it is *not*
|
|
643
|
+
on the grid; `0.25`, `0.5`, `0.75`, `1` and every other multiple of `0.25 s` is, and
|
|
644
|
+
a stepped key there sits above the sample that was meant to see it. On an
|
|
645
|
+
interpolated timeline that costs a few ULPs of value and nothing else. On a
|
|
646
|
+
**stepped** one it is the whole frame โ and on the last sample it is the whole
|
|
647
|
+
event, because there is no later sample to catch it. Measured on rung 5's 6.5 s
|
|
648
|
+
shot at 12 fps: **13 of its 78 sample times are affected** (f6, f15, f18, f21, f24,
|
|
649
|
+
f27, f60, f63, f66, f69, f72, f75, f78), and an attachment key written at the
|
|
650
|
+
declared duration `6.5` never fired at all against an accumulated
|
|
651
|
+
`6.499999999999994` โ which read as a frame-change disagreement the pose series had
|
|
652
|
+
already fixed, and cost that run three builds
|
|
653
|
+
([`2026-08-26-rung5-1`](../bench/runs/2026-08-26-rung5-1/LOOP.md), ยง8). โ **For a
|
|
654
|
+
stepped timeline, write `T โ 1e-6` rather than `T`.** One grid step early cannot
|
|
655
|
+
reach the previous sample โ 83,333 ยตs away at 12 fps โ and is always seen by the
|
|
656
|
+
sample it was written for; one ULP late loses the frame. This is the same asymmetry
|
|
657
|
+
the rule above turns on, one grid step further in.
|
|
606
658
|
- **No key may land past the animation's `duration`.** Nothing that plays the
|
|
607
659
|
animation for the duration it declares ever reaches such a key, so it is a
|
|
608
660
|
compile error โ checked on **every timeline**, not just on the latest key in the
|
|
@@ -956,17 +1008,83 @@ frames did not show the rest, and rendering like-for-like settled three more of
|
|
|
956
1008
|
โ worth a measurable drop in window MAE, a convention the gate cannot see and the
|
|
957
1009
|
measures can.
|
|
958
1010
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
and
|
|
1011
|
+
**Score that comparison over the pixels where the two builds differ. A whole-shot
|
|
1012
|
+
figure is the wrong feature.** Two builds that differ only in slot order are
|
|
1013
|
+
**bit-identical everywhere the two slots do not overlap**, so a whole-shot MAE
|
|
1014
|
+
divides the evidence by the whole figure and by every frame that carries none of
|
|
1015
|
+
it. What survives that division sits inside the objective's own scatter โ real
|
|
1016
|
+
hypotheses and a deliberately reversed control alike land in there, pointing
|
|
1017
|
+
whichever way the noise does, and what has been condemned is the statistic and not
|
|
1018
|
+
the edge. โ Take the pixels where the two renders differ **at all** and score both
|
|
1019
|
+
builds over exactly that set. Nothing outside it can contribute, so the dilution is
|
|
1020
|
+
gone by construction, and the reading needs no knowledge of which parts are involved
|
|
1021
|
+
โ it is the same mechanical test on any structural pair. Read a frame-by-frame tally
|
|
1022
|
+
beside the figure too, because an edge the frames really decide wins shot after shot
|
|
1023
|
+
rather than on a couple of them.
|
|
1024
|
+
|
|
1025
|
+
**Calibrate the band with a control on an edge the brief has already settled by
|
|
1026
|
+
measurement.** Run the same test on that edge, read how far apart the two builds
|
|
1027
|
+
come out over the pixels that decide it, and treat that separation as the scale a
|
|
1028
|
+
real answer is measured against. On the deciding pixels a settled edge separates by
|
|
1029
|
+
a wide margin where the whole-shot figure had it inside its own noise โ which is
|
|
1030
|
+
what lets an edge the frames show no interior detail on stop being unanswerable and
|
|
1031
|
+
start being an edge the null-result rule below has no business firing on.
|
|
1032
|
+
|
|
1033
|
+
โ ๏ธ **A control that fails may be a wrong control โ read the per-frame rows before
|
|
1034
|
+
you condemn the hypothesis.** A control is a **build**, and a build differs from
|
|
1035
|
+
base in everything the change implies, not only in the thing you meant to change:
|
|
1036
|
+
send one part behind another and it goes behind everything drawn between them too,
|
|
1037
|
+
so what you actually ran is one reversed edge plus several asserted ones. The
|
|
1038
|
+
aggregate will not say so, and it can favour the variant while the per-frame rows
|
|
1039
|
+
give base *every one* of the frames that carry most of the deciding pixels. That
|
|
1040
|
+
split โ an aggregate one way, a consistent per-frame tally the other โ is the
|
|
1041
|
+
signature of a control that asserts more than one thing, and reading the aggregate
|
|
1042
|
+
alone condemns an edge the brief settles by measurement. The rule the run protocol
|
|
1043
|
+
carries from the other side is the same one: a control that returns an impossible
|
|
1044
|
+
number has told you something, so read the number rather than the pass or fail.
|
|
1045
|
+
|
|
1046
|
+
โ ๏ธ **A render-back sweep whose spread is inside the objective's own scatter is
|
|
1047
|
+
*no answer*, not a weak one.** Rendering candidates back and keeping the best
|
|
1048
|
+
number is not a draw-order trick โ it is how any structural choice the frames
|
|
1049
|
+
might decide gets decided, a scale, an offset, an attachment kind, an order โ and
|
|
1050
|
+
every one of those sweeps can come back null. **Estimate the objective's own
|
|
1051
|
+
scatter first, or the sweep is not readable at all** โ a spread smaller than that
|
|
1052
|
+
is noise wearing a decimal point. Two orders on one ladder shot came out **0.8 %
|
|
1053
|
+
apart over the whole shot and pointing opposite ways**, and a later run swept
|
|
1054
|
+
**three** structural choices and landed inside that scatter on every one of them.
|
|
1055
|
+
A difference that small is not a quiet vote for the winner; it means the frames do
|
|
1056
|
+
not decide this, and there are two honest ways on:
|
|
1057
|
+
|
|
1058
|
+
- **find a second, independent way to get the number** โ often by measuring the
|
|
1059
|
+
*art* instead of the render, which needs no build at all. Two of those three
|
|
1060
|
+
sweeps were settled that way.
|
|
1061
|
+
- **or ship it on reasoning, and say in the log that is what you did.** The third
|
|
1062
|
+
one was. What makes that honest is the record โ a number that arrived as an
|
|
1063
|
+
argument must not later be read as a measurement.
|
|
965
1064
|
|
|
966
1065
|
And the general form of all three: **when a reading implies a key, look for a second
|
|
967
1066
|
way to get the same number before you author it.** A wrong measurement costs one
|
|
968
1067
|
spurious key; a wrong measurement you believed costs the shape of the whole shot.
|
|
969
1068
|
|
|
1069
|
+
โ ๏ธ **A part that grows about a pivot that is not its own centre reads as a part
|
|
1070
|
+
that moves.** Fit a scale about the region centre when the reference scales it about
|
|
1071
|
+
somewhere else, and the residual stays *plausible* while the fitted centre walks
|
|
1072
|
+
along with the fitted scale โ so the series looks like a translate you measured
|
|
1073
|
+
rather than a pivot you did not model. The tell is that shape and nothing else: **a
|
|
1074
|
+
per-frame centre that moves monotonically with the fitted scale is an unmodelled
|
|
1075
|
+
pivot.** Read it as motion and you author a translate timeline the reference does
|
|
1076
|
+
not have; read it as a pivot and it is an attachment offset (ยง3.4's `x`/`y`) with
|
|
1077
|
+
the bone's own scale carrying both the size and the centre drift โ *one* keyed
|
|
1078
|
+
property, which is also what an editor rig has. Recover the offset by sweeping it
|
|
1079
|
+
against the frames where the part is unoccluded and taking the minimum; the minimum
|
|
1080
|
+
is sharp, and it is a structural constant rather than a per-frame value, so a
|
|
1081
|
+
handful of frames settle it. Measured on a shot with four such parts: MAE **3.13 โ
|
|
1082
|
+
1.95** with not one key value re-measured, and those parts' chains from 10โ13
|
|
1083
|
+
`MAE in it` down to 1.3โ8.8. What made it believable rather than a lucky fit is the
|
|
1084
|
+
second trap's own cross-check โ the four offsets came out the same **fraction of each
|
|
1085
|
+
part's own height**, from four independent fits, which is the quantity that had to
|
|
1086
|
+
agree between them and did.
|
|
1087
|
+
|
|
970
1088
|
**A value is easier to get right than a curve.** The three traps above are all
|
|
971
1089
|
about measuring a *value*, and both ladder runs so far found that the values came
|
|
972
1090
|
out right early: rung 1's key values were exact at every keyframe on the second
|
|
@@ -1060,6 +1178,38 @@ the other two. Where a chain ends in something whose position you can actually s
|
|
|
1060
1178
|
a hand, a foot, a held prop โ scan the two links above it as a **pair**, over the
|
|
1061
1179
|
grid. That is the product of two ranges on a handful of chains, not on every bone.
|
|
1062
1180
|
|
|
1181
|
+
โ ๏ธ **Two whole chains can share a minimum, and no paired scan reaches that one.**
|
|
1182
|
+
The case above is two knobs in one chain. The harder one is two *chains* sitting in
|
|
1183
|
+
the same pixels: an arm and the prop it holds lying across the part of the frame the
|
|
1184
|
+
reference fills with the legs. Every leg knob that would carry a leg there finds the
|
|
1185
|
+
pixels already inked and reports no improvement โ correctly, on the objective it was
|
|
1186
|
+
given โ and the frame keeps its limbs in a **different configuration** rather than a
|
|
1187
|
+
slightly wrong one. Pairing cannot help here, because the two knobs are in different
|
|
1188
|
+
chains and pairing every chain with every other is the whole product.
|
|
1189
|
+
|
|
1190
|
+
**What reaches it is cheap: more than one start, screened coarsely.** Assemble a
|
|
1191
|
+
handful of candidate poses for the frame โ the incumbent, the two neighbouring
|
|
1192
|
+
frames' solutions, a few poses spread across the shot, the setup pose โ run all of
|
|
1193
|
+
them through the **coarse levels only**, and take the best two through the full
|
|
1194
|
+
schedule. **Keep the incumbent among the candidates**, so a frame can only improve
|
|
1195
|
+
on what it already had. The cost is a multiple of the coarse pass rather than of the
|
|
1196
|
+
fit, and the neighbour seed below is one start out of that set rather than a rule of
|
|
1197
|
+
its own. โ ๏ธ It also measures how far from converged a single-start fit can be while
|
|
1198
|
+
reporting success: repeat the *identical* search on one frame from different starts
|
|
1199
|
+
and the numbers walk down, step after step. That is not a tolerance being tightened,
|
|
1200
|
+
it is a different basin each time โ so a search that stopped improving is evidence
|
|
1201
|
+
about the start it was given and about nothing else.
|
|
1202
|
+
|
|
1203
|
+
**Cross-shot starts, for a configuration a shot cannot reach from its own frames.**
|
|
1204
|
+
Where every pose in one shot holds the prop low and the reference holds it out
|
|
1205
|
+
level, every start drawn from that shot's own frames is on the wrong side of the
|
|
1206
|
+
same two-chain minimum, and multi-start inside the shot barely moves the number.
|
|
1207
|
+
These shots are states of one character, so a configuration this shot never visits
|
|
1208
|
+
may be sitting in another one โ take the start from there. โ Borrow **only the bones
|
|
1209
|
+
of the chain in question**, never a whole foreign pose: a foreign pose puts the legs
|
|
1210
|
+
where this shot never goes, and the rest of the search then spends itself fighting
|
|
1211
|
+
what the borrow brought with it.
|
|
1212
|
+
|
|
1063
1213
|
**Re-fit the setup pose against frames drawn from every shot, not against one.** Every
|
|
1064
1214
|
animation is measured from the setup pose, so an error in it is an error in all of
|
|
1065
1215
|
them โ and it is exactly the error one frame cannot show you. Fit an attachment's
|
|
@@ -1153,19 +1303,21 @@ to fit one framing across every set instead of one each, `--all-frames` to list
|
|
|
1153
1303
|
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
1154
1304
|
per-slot report.
|
|
1155
1305
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
not
|
|
1167
|
-
|
|
1168
|
-
|
|
1306
|
+
โญ **A frame set may ship a contact sheet instead of every frame, and the sheet is
|
|
1307
|
+
compared too.** A long shot does not commit 311 near-duplicate PNGs: rung 2's sets
|
|
1308
|
+
ship `f0000.png` and `f0310.png` plus a `contact.png` holding all 311 sampled
|
|
1309
|
+
frames, and spineboy's `@30fps` sets do the same. The frame table still says
|
|
1310
|
+
`frames 2 on disk, candidate samples 311, 2 compared` โ those are the files โ and a
|
|
1311
|
+
**`sheet` line under it** carries the other 309: your candidate sampled at the set's
|
|
1312
|
+
own rate, rendered into the same box the frames above were at the sheet's own scale,
|
|
1313
|
+
and compared tile by tile (issue #36 โ the gap a rung-2 run had already prototyped a
|
|
1314
|
+
comparator for, in its own working directory, because the tool could not see its shot).
|
|
1315
|
+
|
|
1316
|
+
โ ๏ธ **Read it as a series, not as one number** โ ยง9.2. And note what it does not
|
|
1317
|
+
carry: MAE only. The `ฮpx` / `ref ฮ` thresholds are calibrated at frame scale and a
|
|
1318
|
+
tile has a fraction of a frame's pixels, so the per-frame change measure stays on
|
|
1319
|
+
the committed stills, where it reports `no two compared frames are adjacent` and
|
|
1320
|
+
means it.
|
|
1169
1321
|
|
|
1170
1322
|
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
1171
1323
|
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
@@ -1222,8 +1374,10 @@ question first: *do this set's own drawn pixels land in the box `frames.json`
|
|
|
1222
1374
|
records?* The sets that do are measured in that box, which is exact โ it is not an
|
|
1223
1375
|
estimate of where the frames were drawn, it is where they were drawn โ and nothing
|
|
1224
1376
|
another set does can move them. The sets that do not are measured in **one shared
|
|
1225
|
-
framing** fitted across every set, printed as the header's `shared box` line
|
|
1226
|
-
|
|
1377
|
+
framing** fitted across every set, printed as the header's `shared box` line, plus
|
|
1378
|
+
their own whole-pixel MAE refinement off it (ยง9.2) โ the fit is shared because more
|
|
1379
|
+
frames condition it better; the constant offset it still leaves is per set, and
|
|
1380
|
+
measured per set. Each set says which it got on its own `framed to` line.
|
|
1227
1381
|
|
|
1228
1382
|
Why the split falls there, both halves measured on an 8-shot character (147 frames):
|
|
1229
1383
|
|
|
@@ -1278,6 +1432,13 @@ your run ceasing to be an honest authoring run. It is a loop, in the way `build`
|
|
|
1278
1432
|
a loop. `bench` and `diff` against a rung's export are not โ they read the answer,
|
|
1279
1433
|
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
1280
1434
|
|
|
1435
|
+
๐ **That is also why the MAE figures quoted through this section stay.** Every one of
|
|
1436
|
+
them is a candidate's own reading against rendered frames โ the exam question, not the
|
|
1437
|
+
answer key โ so none of them narrows a reference-side measure, and a guide that censored
|
|
1438
|
+
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
|
|
1440
|
+
over a reference's own count, and no such figure appears here.
|
|
1441
|
+
|
|
1281
1442
|
๐จ **If you drive the runtime yourself, a bone's local transform lives on
|
|
1282
1443
|
`bone.pose`.** A shot whose poses have to be *fitted* rather than read sends you
|
|
1283
1444
|
past `check` and into your own render loop over
|
|
@@ -1303,8 +1464,8 @@ file, the next build reads those as zero, `validate` is green and `check` runs.
|
|
|
1303
1464
|
โ **A `null` in your own placements dump is the signature of having read
|
|
1304
1465
|
`bone.data` directly** โ nothing in this format is ever legitimately null. On
|
|
1305
1466
|
spineboy it cost the candidate MAE 13.0 โ 114.6 with a green gate throughout
|
|
1306
|
-
(
|
|
1307
|
-
|
|
1467
|
+
(measured in a spineboy attempt's own loop log, ยง4.1). Note that the two names are not
|
|
1468
|
+
the same thing: `bone.data.setupPose` is
|
|
1308
1469
|
the setup transform, while `bone.setupPose()` on a `Bone` is the method that
|
|
1309
1470
|
resets `bone.pose` back to it.
|
|
1310
1471
|
|
|
@@ -1315,8 +1476,7 @@ stores; what gets drawn is that stored quad โ `computeWorldVertices` reads
|
|
|
1315
1476
|
error nor a move. **Call `attachment.updateSequence()` after every write**, or
|
|
1316
1477
|
every frame renders the quad it was loaded with. A setup fit on spineboy ran
|
|
1317
1478
|
4,500 renders and reported the same number for all of them before this surfaced
|
|
1318
|
-
(
|
|
1319
|
-
ยง4.2).
|
|
1479
|
+
(the same loop log, ยง4.2).
|
|
1320
1480
|
|
|
1321
1481
|
โ **An MAE that is identical across every pose, and that does not move for any
|
|
1322
1482
|
parameter you sweep โ a bone's local transform, an attachment's offsets โ is one
|
|
@@ -1331,6 +1491,7 @@ cannot.
|
|
|
1331
1491
|
reference 256x116px 0.117628 px/unit world x[-573.3 .. 1603.0] y[-81.2 .. 908.9] (frames.json)
|
|
1332
1492
|
content candidate 234.6x95.5px at (11.3, 11.5) reference 234.7x95.3px at (11.2, 11.7) (union over 86 frame(s))
|
|
1333
1493
|
โคท fit x0.999256 offset +0.05, -0.02 px rms 0.42 px over 344 edge(s) union residual -0.27 x +0.17 px aspect -0.30% (derived, 4 pass(es), settled)
|
|
1494
|
+
โญ MAE-refined by -1, +1 px: 54.31 โ 48.47 over the reference's own pixels (10.7% of the figure). โฆ
|
|
1334
1495
|
in units candidate 1995.3 x 809.7 reference 1995.3 x 809.9 x0.9999
|
|
1335
1496
|
|
|
1336
1497
|
โโ heavy โ candidate animation "heavy", 12 fps โโ
|
|
@@ -1340,6 +1501,8 @@ cannot.
|
|
|
1340
1501
|
slot drift worst 2.1 px "pendulum" at f0029
|
|
1341
1502
|
per-frame 1 of 64 adjacent pair(s) change by a different amount than the reference does; worst
|
|
1342
1503
|
f0018, yours moved 0 px where the reference moved 374
|
|
1504
|
+
sheet 311 of 311 tile(s) of contact.png at 64x57px in 8 column(s) MAE mean 4.30 worst 4.76 at f0047
|
|
1505
|
+
โคท worst 8: f0047=4.8 f0048=4.7 f0045=4.7 f0039=4.7 f0149=4.7 f0044=4.6 f0046=4.6 f0043=4.6
|
|
1343
1506
|
|
|
1344
1507
|
the 9 frames worth reading โ worst by MAE, plus every frame whose own change disagrees, in index order
|
|
1345
1508
|
frame MAE union px ฮpx ref ฮ worst slot drift how slots note
|
|
@@ -1401,6 +1564,12 @@ The lines, in order:
|
|
|
1401
1564
|
does not, after the fit. This is the number that says *"something reaches
|
|
1402
1565
|
somewhere nothing in the frames does, or is a different size"*, and a warning
|
|
1403
1566
|
spells it out past a pixel.
|
|
1567
|
+
- the **MAE-refined** line, which is the last thing that happens to the box and the
|
|
1568
|
+
paragraph below is what it is for. On a **fitted** framing it says what constant
|
|
1569
|
+
whole-pixel offset was taken out and what that was worth (`โญ`), or that the
|
|
1570
|
+
search ran and the identity won. On a box that is not an estimate โ the frames'
|
|
1571
|
+
own, or one you pinned โ it never moves anything, and if it finds a constant there
|
|
1572
|
+
it says so as a **finding about your rig** rather than about the framing.
|
|
1404
1573
|
- `in units` โ the same two boxes in world units. The framing absorbs a pure scale
|
|
1405
1574
|
on purpose, so this is the only place one shows; it compares only if you measured
|
|
1406
1575
|
the shot in the frames' own units.
|
|
@@ -1411,10 +1580,31 @@ that is a little large โ the best fit of the two extents is not quite the best
|
|
|
1411
1580
|
alignment of the two pictures, and the fit spends a fraction of a pixel absorbing
|
|
1412
1581
|
a difference that would have been cheaper to leave alone. Measured floor: about a
|
|
1413
1582
|
third of a pixel on the ladder's shots. On most that is invisible; on a small
|
|
1414
|
-
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1583
|
+
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five, and
|
|
1584
|
+
on the spineboy sets a **constant** one or two pixels was worth 10โ30 % of the
|
|
1585
|
+
figure (issue #146). This is the floor the frames' own box has no share in, which is
|
|
1586
|
+
why `check` prefers that box whenever your pixels are measured to land in it;
|
|
1587
|
+
`--viewport` is how you stop it in the cases that box does not cover.
|
|
1588
|
+
|
|
1589
|
+
โญ **What a fitted framing now does about it: one final whole-pixel pass.** After
|
|
1590
|
+
the fit settles (or cycles), `check` searches every whole-pixel offset within ยฑ2 px
|
|
1591
|
+
for the lowest MAE over the reference's own drawn pixels and moves the box to the
|
|
1592
|
+
best one, when that is worth at least 1 % of the figure. So a fitted set's numbers
|
|
1593
|
+
are what is left **after** the best constant offset has been removed, rather than a
|
|
1594
|
+
constant offset read as motion โ and the line says which offset and what it bought,
|
|
1595
|
+
in both directions, so nothing is quietly absorbed.
|
|
1596
|
+
|
|
1597
|
+
Two things to know when you read it:
|
|
1598
|
+
|
|
1599
|
+
- โ ๏ธ **A large refinement on a set whose drift is also large is not necessarily
|
|
1600
|
+
framing.** The pass removes the best *constant*, and when one part carries much of
|
|
1601
|
+
the shot's ink a constant can absorb part of that part's own displacement. Read
|
|
1602
|
+
the offset beside the chain table: a big offset with a flat drift table is the
|
|
1603
|
+
fit's floor; a big offset with one limb far out is that limb.
|
|
1604
|
+
- On a box that is not an estimate the pass declines and says why. `frames.json`'s
|
|
1605
|
+
own box is where the frames were drawn, so a constant pixel *there* is your
|
|
1606
|
+
figure sitting a pixel off inside the right box โ a thing to fix, and the report
|
|
1607
|
+
refuses to frame it away. A pinned box is your claim, and nothing overrules it.
|
|
1418
1608
|
|
|
1419
1609
|
**MAE** is the mean absolute RGB difference, 0..255, over the pixels either side
|
|
1420
1610
|
covers โ the *union alpha*. It is not scored against a threshold, any more than a
|
|
@@ -1441,6 +1631,35 @@ much ink as the reference does gets `โ ๏ธ overdraw` beside those two numbers,
|
|
|
1441
1631
|
both pixel counts, because at that point the first figure is cheap for a reason
|
|
1442
1632
|
that has nothing to do with your keys.
|
|
1443
1633
|
|
|
1634
|
+
๐จ **Part of your MAE is the texture, not the animation, and nothing in the report
|
|
1635
|
+
says so.** The reference frames are rendered through the example's **own packed
|
|
1636
|
+
atlas**, and a packed atlas may carry a `scale:` line โ the ladder has one at
|
|
1637
|
+
`scale: 0.5`, whose 745x212 part is packed at 373x106. rigc has no packer (**ยง6**), so
|
|
1638
|
+
a candidate built from the loose PNGs samples a texture at twice that resolution and
|
|
1639
|
+
resamples every edge differently. The pixels are the same shape in the same place; they
|
|
1640
|
+
are filtered from a different source, and the difference lands on the outline of every
|
|
1641
|
+
part in every frame. It is a constant, it is invisible to `content`, `rms` and the
|
|
1642
|
+
`ยฑ2 px` refinement โ a resampling difference is not an offset โ and **no key you write
|
|
1643
|
+
can move it**.
|
|
1644
|
+
|
|
1645
|
+
โ **When the MAE is flat across the whole set and the drift is already at the floor,
|
|
1646
|
+
check the atlas before you look for keys.** The example's `.atlas` is an allowed input
|
|
1647
|
+
in its own right (`bench/runs/README.md`, *What a run may read*, item 4), and one line
|
|
1648
|
+
of it answers the question. To size the floor, re-run `check` once with
|
|
1649
|
+
`--atlas <the example's own .atlas>`: same skeleton, same keys, the reference's own
|
|
1650
|
+
texture. Measured on rung 3, MAE **6.13 / 6.01** with the candidate's own
|
|
1651
|
+
full-resolution atlas against **2.25 / 2.30** with the supplied one โ **two thirds of
|
|
1652
|
+
the figure was the texture**, and the run that did not know it would have spent its
|
|
1653
|
+
whole budget hunting a rig that was already right.
|
|
1654
|
+
|
|
1655
|
+
โ ๏ธ Two things about that diagnostic. It is a **diagnostic and not a better number**:
|
|
1656
|
+
the artifact `bench` validates ships its own atlas, so the first figure is the one
|
|
1657
|
+
that belongs in a run's record and the second is the explanation of where it went.
|
|
1658
|
+
And the coarser texture **loses** resolution the finer one has โ on the same rung a
|
|
1659
|
+
pair the reference moves *one pixel* across stopped being visible at half scale, so
|
|
1660
|
+
the diagnostic run reported a frame-change disagreement the graded run does not have.
|
|
1661
|
+
Read it for the floor, never as the verdict.
|
|
1662
|
+
|
|
1444
1663
|
**`ฮpx` and `ref ฮ`** are the two columns that do **not** compare you against the
|
|
1445
1664
|
reference. They compare each side against **itself one frame earlier**: how many
|
|
1446
1665
|
pixels of your own frame moved since your own previous frame, and the same for the
|
|
@@ -1466,12 +1685,46 @@ line whenever the MAE is flat and something still looks wrong: a flat MAE says t
|
|
|
1466
1685
|
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
1467
1686
|
and blinks where the reference does.
|
|
1468
1687
|
|
|
1688
|
+
โ ๏ธ **Each set is compared against ITSELF, so on a shot committed at two rates a hold
|
|
1689
|
+
can exist in one set and not the other.** The coarse set samples every other frame of
|
|
1690
|
+
the fine one, so a pair the coarse set holds across is a constraint between samples
|
|
1691
|
+
**2k and 2k+2** of the fine series โ and 2k+1 between them is free to move, and does.
|
|
1692
|
+
One ladder shot has a pair whose whole-frame change at the coarse rate is **exactly
|
|
1693
|
+
0** while both fine-rate pairs inside that same span change by 48 px: the subject
|
|
1694
|
+
shifts under three world units and comes back, so the two coarse samples land on the
|
|
1695
|
+
same pose either side of it. So ยง10.3's *key both ends of the hold* has to be applied
|
|
1696
|
+
**at each committed rate separately**, and equal values are not enough โ the two
|
|
1697
|
+
samples have to be **keys**, or a planner reduces through one of them and an
|
|
1698
|
+
interpolant inside its own tolerance is not equality. That cost two builds on that
|
|
1699
|
+
run, the second of them for exactly that reason. โ Measure the frames' own
|
|
1700
|
+
frame-to-frame change **once per committed rate**, and where a rate holds, pin both
|
|
1701
|
+
ends as keys whenever a finer rate moves between them.
|
|
1702
|
+
|
|
1469
1703
|
โ ๏ธ Only between **adjacent** frames. A set that ships stills rather than every frame
|
|
1470
1704
|
โ rung 2's contact-sheet sets โ reports `no two compared frames are adjacent`, and
|
|
1471
1705
|
means it: the difference between two frames 310 apart is not a frame-to-frame delta.
|
|
1472
1706
|
A disagreement needs one side to hold *exactly* still while the other moves, or one
|
|
1473
1707
|
side to move four times the other and at least two dozen pixels more; below that the
|
|
1474
|
-
two rasterisations differ by their own last bit and the column says nothing.
|
|
1708
|
+
two rasterisations differ by their own last bit and the column says nothing. Such a
|
|
1709
|
+
set gets the `sheet` line instead, which is MAE over every sampled frame and not a
|
|
1710
|
+
change measure โ the two thresholds above are pixel counts at frame scale, and a
|
|
1711
|
+
tile has a fraction of a frame's pixels.
|
|
1712
|
+
|
|
1713
|
+
**The `sheet` line is the whole shot**, on the sets that commit a couple of stills
|
|
1714
|
+
and fold every sampled frame into one `contact.png`. It says how many tiles were
|
|
1715
|
+
compared out of how many the sheet holds, the grid it measured off the sheet itself,
|
|
1716
|
+
the mean and worst tile, and the worst eight by MAE. Read the **series** rather than
|
|
1717
|
+
the mean, exactly as with the frame table: flat across the shot is framing or art,
|
|
1718
|
+
a spike is timing at that moment โ rung 2's four shots read 4.30โ4.41 flat over
|
|
1719
|
+
1,244 tiles, which is what says their trajectories, ring rates and attachment swaps
|
|
1720
|
+
land where and when they should. Two things to know:
|
|
1721
|
+
|
|
1722
|
+
- it is measured in the **same box** the frame table was, at the sheet's scale. For
|
|
1723
|
+
a stills-plus-sheet set that box was decided on the stills, so a set whose framing
|
|
1724
|
+
is a fit carries that fit into these numbers as well;
|
|
1725
|
+
- a sheet whose dimensions are not a grid of this set's frame count at these frames'
|
|
1726
|
+
aspect is **refused by name** rather than read wrong โ the note names the file, and
|
|
1727
|
+
the answer is to re-render the set.
|
|
1475
1728
|
|
|
1476
1729
|
**Slot drift** is what you act on. For each of your slots, `check` measures where
|
|
1477
1730
|
it landed and how far that is from where the reference put it. That names the part,
|
|
@@ -1481,8 +1734,15 @@ sentence you can take straight back to a key.
|
|
|
1481
1734
|
There are two matchers and the `how` column says which one answered:
|
|
1482
1735
|
|
|
1483
1736
|
- `component` โ your slot sits on a connected component of the reference frame that
|
|
1484
|
-
is its own size
|
|
1485
|
-
the strongest answer available.
|
|
1737
|
+
is its own size **and holds nothing else you drew**. The drift is the distance
|
|
1738
|
+
between the two centroids, and it is the strongest answer available. All three
|
|
1739
|
+
conditions are checked: a blob may not be much bigger than the slot, may not be
|
|
1740
|
+
much wider than its box, and may not contain another of your parts' ink. The last
|
|
1741
|
+
is the one a dominant part slips through otherwise โ rung 2's course is 81 % of a
|
|
1742
|
+
blob that also holds the water, the panel and both rings, so the blob is only
|
|
1743
|
+
1.24x its ink and no wider than its box, and the reported *"course drift 11.2 px"*
|
|
1744
|
+
was the distance to a five-part centroid (issue #37). It now falls to the matcher
|
|
1745
|
+
below and reads 0.0 px.
|
|
1486
1746
|
- `tmpl 0.62` โ the reference merged your slot into a neighbour (they touch, or one
|
|
1487
1747
|
is drawn over the other), so the fallback rendered **your slot on its own** and
|
|
1488
1748
|
correlated it against the reference around where you drew it. The number is the
|
|
@@ -1508,19 +1768,19 @@ a part you have not authored, or one you have put somewhere else entirely.
|
|
|
1508
1768
|
```
|
|
1509
1769
|
chains 6 from the candidate's own bone tree โ the roster is at the foot of the report
|
|
1510
1770
|
chain slots worst slot drift mean MAE in it share
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1771
|
+
crest 5/5 3.0 px "lantern" f0006 2.4 px 28.40 31.5%
|
|
1772
|
+
prop-arm 6/6 2.0 px "prop" f0002 1.5 px 44.90 33.0%
|
|
1773
|
+
near-strut 3/3 15.0 px "strut-tip" f0004 7.0 px 51.30 8.5%
|
|
1514
1774
|
```
|
|
1515
1775
|
|
|
1516
1776
|
`check` cuts **your own** bone tree at every branch point โ a chain runs from a
|
|
1517
1777
|
root or a fork down to the next fork; a single-bone chain that is itself a fork
|
|
1518
|
-
folds into its parent, so a `
|
|
1778
|
+
folds into its parent, so a `hub` that branches three ways joins the trunk rather
|
|
1519
1779
|
than becoming a row of its own; and each chain is named after the first bone in it
|
|
1520
|
-
that carries a slot. On a biped that lands on the parts you would name (`
|
|
1521
|
-
`
|
|
1522
|
-
the slot rows under it still say which link moved. Which bones and slots went
|
|
1523
|
-
is printed as a roster at the foot of the report, so it is never a guess. `MAE
|
|
1780
|
+
that carries a slot. On a biped that lands on the parts you would name (`crest`,
|
|
1781
|
+
`near-strut` and `prop-arm` above); on a serial figure with no fork it is one chain,
|
|
1782
|
+
and the slot rows under it still say which link moved. Which bones and slots went
|
|
1783
|
+
where is printed as a roster at the foot of the report, so it is never a guess. `MAE
|
|
1524
1784
|
share` divides the difference over the **reference's** own drawn pixels โ the
|
|
1525
1785
|
denominator from the line above, which nothing you draw can grow โ and splits it by
|
|
1526
1786
|
giving each of those pixels to the chain whose ink is nearest, so the shares
|
|
@@ -1566,10 +1826,22 @@ re-fitted.
|
|
|
1566
1826
|
3 px low at one frame have the same drift and opposite causes. The table gives
|
|
1567
1827
|
you the frame index; ยง8's rule still applies โ look for a second way to get the
|
|
1568
1828
|
number before you author the key.
|
|
1569
|
-
- **What happens between two committed frames
|
|
1570
|
-
a set that ships stills has none, so a shot
|
|
1571
|
-
and wrong between them reads clean
|
|
1572
|
-
contact-sheet set already has, and it
|
|
1829
|
+
- **What happens between two committed frames** โ *unless the set ships a sheet.*
|
|
1830
|
+
`ฮpx` compares adjacent frames and a set that ships stills has none, so a shot
|
|
1831
|
+
that is right at every committed frame and wrong between them reads clean in that
|
|
1832
|
+
column. That is the same gap `--frames` on a contact-sheet set already has, and it
|
|
1833
|
+
is why the frame-count line is printed. โญ What closes it is the **`sheet` line**:
|
|
1834
|
+
on a set rendered at a higher rate than the frames on disk, every sampled frame is
|
|
1835
|
+
compared, so the samples between two committed ones are measured there and
|
|
1836
|
+
nowhere else.
|
|
1837
|
+
๐จ **And when you go looking for one of them, do not assume it lies between its
|
|
1838
|
+
neighbours.** A half-frame is not an interpolation โ it is where the shot actually
|
|
1839
|
+
was, and the interesting ones are interesting because it left the interval. Rung
|
|
1840
|
+
4's ball makes contact entirely inside one twelfth of a second: the sheet puts it
|
|
1841
|
+
**40 px below both 12 fps frames that bracket it**, while its x sits within their
|
|
1842
|
+
own range. A search whose reach was scaled from those two frames' own step
|
|
1843
|
+
therefore looked ยฑ9 px for something 40 px away and reported the interpolation
|
|
1844
|
+
back, twice, before the tile was read rather than inferred.
|
|
1573
1845
|
|
|
1574
1846
|
---
|
|
1575
1847
|
|
|
@@ -1714,6 +1986,66 @@ that is not held"* from the other side, and the same place catches it โ a slop
|
|
|
1714
1986
|
hold shows up in `check`'s per-frame column and nowhere else, because it is cheap in
|
|
1715
1987
|
every single frame and wrong only in the relation between two.
|
|
1716
1988
|
|
|
1989
|
+
โ ๏ธ **The key reducer has to key the plateau, because a plateau is neither an end nor
|
|
1990
|
+
a turn.** A planner that forces the series ends and every change of direction โ
|
|
1991
|
+
which is exactly what the rule above asks for โ will still author a slope straight
|
|
1992
|
+
through a run of still frames: not one sample in the middle of a hold is an end or
|
|
1993
|
+
a turning point, and a greedy span stays inside its own per-bone tolerance the whole
|
|
1994
|
+
way across. **A tolerance is not a hold.** Slow motion inside the tolerance is a
|
|
1995
|
+
tolerance question; stillness is a thing the shot *does*, and it survives the
|
|
1996
|
+
reduction only if the reduction is told to keep it. โ Force **both ends of every run
|
|
1997
|
+
of equal values** as keys in their own right โ a third kind of forced index beside
|
|
1998
|
+
the series ends and the turning points โ and test on **exact** equality, so that a
|
|
1999
|
+
merely near-still span is deliberately not swept up with it. This is worth doing
|
|
2000
|
+
before you have any evidence you need it: a run whose *poses* are all right can fail
|
|
2001
|
+
this and see nothing wrong anywhere else, because `validate` has no opinion on it,
|
|
2002
|
+
`diff` never looks at a rendered frame, and an aggregate MAE is cheap in every
|
|
2003
|
+
single frame and wrong only in the relation between two.
|
|
2004
|
+
|
|
2005
|
+
โ ๏ธ **And a tolerance is not a *slow span* either โ a key tolerance needs a relative
|
|
2006
|
+
floor beside its absolute one.** The rule above rescues exact stillness by forcing it,
|
|
2007
|
+
and deliberately leaves near-stillness to the tolerance. But **ยง9.2's per-frame column
|
|
2008
|
+
compares *changes*, not positions**, so on a span the shot barely moves across, an
|
|
2009
|
+
error well inside an absolute tolerance is most of the motion. Measured, on rung 3: the
|
|
2010
|
+
reference moves **0.109 px** between two frames; a greedy span deviated **0.098 px**
|
|
2011
|
+
there, legal under a 0.30 px tolerance and legal again under 0.15 px โ and the column
|
|
2012
|
+
read **259 px against the reference's 40**, a six-fold disagreement authored by a span
|
|
2013
|
+
that was, at every keyframe, exactly right. The same tolerance that is generous on the
|
|
2014
|
+
fast part of the shot is a 90 % error on the slow part, because one figure in pixels
|
|
2015
|
+
cannot be both.
|
|
2016
|
+
|
|
2017
|
+
โ Cap each span's deviation at **the smaller of the absolute tolerance and the
|
|
2018
|
+
smallest single-frame move inside that span**. It is one line in the planner, it costs
|
|
2019
|
+
a handful of keys, and it is the difference between a reduction that is accurate and
|
|
2020
|
+
one that is accurate *in proportion to what is happening*.
|
|
2021
|
+
|
|
2022
|
+
โญ **Then stop trusting the floor and close the loop on the frames, because a floor is
|
|
2023
|
+
a heuristic and the column is a measurement.** The floor above cut rung 3's
|
|
2024
|
+
disagreements from three to one and could not reach the last: **sample your own planned
|
|
2025
|
+
curves at the frames' own rate, render them, compare every adjacent pair against the
|
|
2026
|
+
reference's own change, force the offending frames as keys, and re-plan** โ repeating
|
|
2027
|
+
until no pair is out of band. It terminates quickly (that shot needed one extra round
|
|
2028
|
+
on one animation and none on the other), it needs no build, and it is the only part of
|
|
2029
|
+
key planning that is verified rather than argued. โ ๏ธ The band is worth reading before
|
|
2030
|
+
you aim at it: `check` calls a pair a disagreement when one side is **exactly** still
|
|
2031
|
+
and the other is not, or when one side moves **four times** the other **and** at least
|
|
2032
|
+
**24 px** more (`src/check.ts`). So the wide middle of a shot is nearly free and the
|
|
2033
|
+
whole difficulty is the pairs where the reference barely moves โ including, on that
|
|
2034
|
+
rung, one pair it moves a single pixel across, which no MAE and no drift figure in this
|
|
2035
|
+
toolchain can see.
|
|
2036
|
+
|
|
2037
|
+
๐จ **But check that the shot holds at all first, because applying this to one that
|
|
2038
|
+
never does manufactures the defect it prevents.** The rule is about a *shot*, and a
|
|
2039
|
+
snap-to-still step in a planner does not cost nothing when there is nothing to snap:
|
|
2040
|
+
it will find some run of samples inside the fitter's own resolution and flatten it.
|
|
2041
|
+
Rung 4's shot has **no** still span โ not one adjacent pair of its 155 reference
|
|
2042
|
+
frames is pixel-identical, and even its last two differ, because the chain is still
|
|
2043
|
+
settling at the final frame โ and the snap duly flattened the tail and put a
|
|
2044
|
+
*"yours moved 0 px where the reference moved 28"* into the per-frame column: ยง9.2's
|
|
2045
|
+
held-pose defect arriving from the opposite direction. โ Difference every adjacent
|
|
2046
|
+
pair of frames once, before the planner runs. It is one pass over the set, and it
|
|
2047
|
+
tells you whether this paragraph applies to you at all.
|
|
2048
|
+
|
|
1717
2049
|
๐ **Add a key when a curve cannot carry the shape.** *"If a curve is not smooth
|
|
1718
2050
|
enough, it is easily remedied by adding another key"*, and the **Bounce** handle
|
|
1719
2051
|
preset exists for *"changing directions abruptly, such as when a ball bounces"* โ
|
|
@@ -1746,6 +2078,43 @@ by that bone's lever arm. With that, the whole trade reads as one curve and you
|
|
|
1746
2078
|
pick a point on it deliberately: that shot measured 0.6 px โ 259 keys โ 1.619 window
|
|
1747
2079
|
MAE, 0.3 px โ 300 โ 1.402, 0.15 px โ 377 โ 1.305.
|
|
1748
2080
|
|
|
2081
|
+
โ ๏ธ **Compute what a skipped sample costs before you declare that tolerance โ on a
|
|
2082
|
+
fast subject the shot's own speed has already pinned it.** Skipping one sample means
|
|
2083
|
+
spanning it linearly, and the chord through its two neighbours sits at their mean, so
|
|
2084
|
+
the deviation at the sample skipped is **half** the series' second difference there:
|
|
2085
|
+
`|f(nโ1) โ 2ยทf(n) + f(n+1)| / 2`. That is an identity, not an approximation โ check
|
|
2086
|
+
it on `f(n) = nยฒ`, whose second difference is 2: the chord reads `nยฒ + 1` where the
|
|
2087
|
+
curve reads `nยฒ`, and the deviation is 1. So second-difference the fitted series,
|
|
2088
|
+
halve it, and read that number against the tolerance you were about to declare. If it
|
|
2089
|
+
is the larger of the two, the fitter keys nearly every frame, and no tolerance below
|
|
2090
|
+
it changes that โ the key density is then a fact about the subject, not a choice you
|
|
2091
|
+
made. One ladder run is that case: its subject's median frame-to-frame second
|
|
2092
|
+
difference measured 6.4 px, so a span that skips one sample deviates about 3.2 px,
|
|
2093
|
+
ten times the 0.3 px tolerance the run declared, and the tolerance would have to be
|
|
2094
|
+
loosened past that 3.2 px before a span could afford to skip anything at all. What
|
|
2095
|
+
the trade bought there was accuracy and never sparsity โ 0.6 px โ 439 keys โ 18.22
|
|
2096
|
+
MAE at 12 fps, 0.45 px โ 482 โ 17.54, 0.3 px โ 521 โ 17.26. โ Do the arithmetic
|
|
2097
|
+
first. It tells you which of the two situations you are in: picking a point on the
|
|
2098
|
+
curve above, or discovering the point the shot has already put you on โ and the
|
|
2099
|
+
second one is not a failure to reach the density this section asks for, it is what
|
|
2100
|
+
that density is here.
|
|
2101
|
+
|
|
2102
|
+
๐จ **There is a third situation, and it is the one where nothing in the loop can
|
|
2103
|
+
help you: a tolerance under the accuracy of whatever produced the series.** The
|
|
2104
|
+
arithmetic above is about the *subject*; this is about the *estimator*. A fitted
|
|
2105
|
+
series is only as good as its objective's basin, and a tolerance below that width
|
|
2106
|
+
buys keys that encode the fitter's wander โ and **`check` cannot see that it
|
|
2107
|
+
happened**, because two candidates that are both inside tolerance render the same
|
|
2108
|
+
pixels. `diff`'s `key_counts` sees it and `diff` is the finish line, so a run gets
|
|
2109
|
+
one shot at the number. Rung 4's is the recorded case: it declared 0.28 px, and its
|
|
2110
|
+
own objective's basin on the shortest lever in the chain measured **ยฑ1.5ยฐ**, which
|
|
2111
|
+
is ยฑ0.5 px there โ so the declared figure was under the noise, and it shipped about
|
|
2112
|
+
three times the reference's key count (`key_counts` 421/1339) with every other
|
|
2113
|
+
animation measure at or near 1.000. โ **Measure the basin before you declare the
|
|
2114
|
+
tolerance**, which costs nothing and needs no reference: scan each knob around its
|
|
2115
|
+
converged value and read how far it moves before the objective does. Then declare a
|
|
2116
|
+
tolerance at or above the widest of them, and record both numbers.
|
|
2117
|
+
|
|
1749
2118
|
**A rig's parameters are not identified by its pixels โ remove the gauges before you
|
|
1750
2119
|
key.** A bone that carries no attachment is an exact gauge: turn it by ฮด, turn its
|
|
1751
2120
|
children back by ฮด, and **not one pixel changes**. Anything optimising against pixels
|