spine-rigc 0.3.0 โ 0.5.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 +41 -3
- package/cli.ts +19 -2
- package/docs/AUTHORING.md +894 -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,111 @@ 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
|
+
โญ **That dilution has a *temporal* cousin, and it bites inside a single shot's own
|
|
1026
|
+
per-frame fit.** The paragraph above is about two builds and a whole-shot figure; this is
|
|
1027
|
+
about one build and a whole-*figure* objective. Where a passage's motion is **a small
|
|
1028
|
+
part moving against a large, nearly still body** โ a hand, a head, a prop, while
|
|
1029
|
+
everything else holds โ the moving part is a tiny share of the ink, so a whole-figure
|
|
1030
|
+
score is dominated by the still majority. Every frame then reports a good number
|
|
1031
|
+
*individually*, the fit converges, and the passage comes out **static**: the mover was
|
|
1032
|
+
never worth enough of the objective to pull the search toward it.
|
|
1033
|
+
|
|
1034
|
+
โ ๏ธ **Nothing else in the loop catches this.** The MAE is fine, the drift is fine on every
|
|
1035
|
+
part that is not moving, and `validate` and `diff` never look at a rendered frame. What
|
|
1036
|
+
does see it is ยง10.3's change column, in its **under-change** direction โ and by the time
|
|
1037
|
+
it tells you, the poses are already wrong, because a key plan cannot add motion the poses
|
|
1038
|
+
do not have.
|
|
1039
|
+
|
|
1040
|
+
โ **Weight the objective by the reference's own frame-to-frame change.** Build a mask
|
|
1041
|
+
from where the reference *changes* between the two frames bracketing the one you are
|
|
1042
|
+
fitting, and weight the score by it โ so the pixels that carry the passage's motion carry
|
|
1043
|
+
the passage's objective. It costs one extra difference per frame, needs nothing but the
|
|
1044
|
+
frames, and it turns an untrackable passage into an ordinary one.
|
|
1045
|
+
|
|
1046
|
+
๐ **Read the mask itself before you trust the fit, because it also tells you what is
|
|
1047
|
+
actually moving** โ which is frequently not what the shot looks like it is about. A
|
|
1048
|
+
passage that reads as one limb waving can turn out to carry most of its change somewhere
|
|
1049
|
+
else entirely (a body-wide micro-rocking, a shadow, a trailing part), and a fitter aimed
|
|
1050
|
+
at the limb would have been chasing the minority of the evidence. The mask is the cheap
|
|
1051
|
+
way to find that out first.
|
|
1052
|
+
|
|
1053
|
+
**Calibrate the band with a control on an edge the brief has already settled by
|
|
1054
|
+
measurement.** Run the same test on that edge, read how far apart the two builds
|
|
1055
|
+
come out over the pixels that decide it, and treat that separation as the scale a
|
|
1056
|
+
real answer is measured against. On the deciding pixels a settled edge separates by
|
|
1057
|
+
a wide margin where the whole-shot figure had it inside its own noise โ which is
|
|
1058
|
+
what lets an edge the frames show no interior detail on stop being unanswerable and
|
|
1059
|
+
start being an edge the null-result rule below has no business firing on.
|
|
1060
|
+
|
|
1061
|
+
โ ๏ธ **A control that fails may be a wrong control โ read the per-frame rows before
|
|
1062
|
+
you condemn the hypothesis.** A control is a **build**, and a build differs from
|
|
1063
|
+
base in everything the change implies, not only in the thing you meant to change:
|
|
1064
|
+
send one part behind another and it goes behind everything drawn between them too,
|
|
1065
|
+
so what you actually ran is one reversed edge plus several asserted ones. The
|
|
1066
|
+
aggregate will not say so, and it can favour the variant while the per-frame rows
|
|
1067
|
+
give base *every one* of the frames that carry most of the deciding pixels. That
|
|
1068
|
+
split โ an aggregate one way, a consistent per-frame tally the other โ is the
|
|
1069
|
+
signature of a control that asserts more than one thing, and reading the aggregate
|
|
1070
|
+
alone condemns an edge the brief settles by measurement. The rule the run protocol
|
|
1071
|
+
carries from the other side is the same one: a control that returns an impossible
|
|
1072
|
+
number has told you something, so read the number rather than the pass or fail.
|
|
1073
|
+
|
|
1074
|
+
โ ๏ธ **A render-back sweep whose spread is inside the objective's own scatter is
|
|
1075
|
+
*no answer*, not a weak one.** Rendering candidates back and keeping the best
|
|
1076
|
+
number is not a draw-order trick โ it is how any structural choice the frames
|
|
1077
|
+
might decide gets decided, a scale, an offset, an attachment kind, an order โ and
|
|
1078
|
+
every one of those sweeps can come back null. **Estimate the objective's own
|
|
1079
|
+
scatter first, or the sweep is not readable at all** โ a spread smaller than that
|
|
1080
|
+
is noise wearing a decimal point. Two orders on one ladder shot came out **0.8 %
|
|
1081
|
+
apart over the whole shot and pointing opposite ways**, and a later run swept
|
|
1082
|
+
**three** structural choices and landed inside that scatter on every one of them.
|
|
1083
|
+
A difference that small is not a quiet vote for the winner; it means the frames do
|
|
1084
|
+
not decide this, and there are two honest ways on:
|
|
1085
|
+
|
|
1086
|
+
- **find a second, independent way to get the number** โ often by measuring the
|
|
1087
|
+
*art* instead of the render, which needs no build at all. Two of those three
|
|
1088
|
+
sweeps were settled that way.
|
|
1089
|
+
- **or ship it on reasoning, and say in the log that is what you did.** The third
|
|
1090
|
+
one was. What makes that honest is the record โ a number that arrived as an
|
|
1091
|
+
argument must not later be read as a measurement.
|
|
965
1092
|
|
|
966
1093
|
And the general form of all three: **when a reading implies a key, look for a second
|
|
967
1094
|
way to get the same number before you author it.** A wrong measurement costs one
|
|
968
1095
|
spurious key; a wrong measurement you believed costs the shape of the whole shot.
|
|
969
1096
|
|
|
1097
|
+
โ ๏ธ **A part that grows about a pivot that is not its own centre reads as a part
|
|
1098
|
+
that moves.** Fit a scale about the region centre when the reference scales it about
|
|
1099
|
+
somewhere else, and the residual stays *plausible* while the fitted centre walks
|
|
1100
|
+
along with the fitted scale โ so the series looks like a translate you measured
|
|
1101
|
+
rather than a pivot you did not model. The tell is that shape and nothing else: **a
|
|
1102
|
+
per-frame centre that moves monotonically with the fitted scale is an unmodelled
|
|
1103
|
+
pivot.** Read it as motion and you author a translate timeline the reference does
|
|
1104
|
+
not have; read it as a pivot and it is an attachment offset (ยง3.4's `x`/`y`) with
|
|
1105
|
+
the bone's own scale carrying both the size and the centre drift โ *one* keyed
|
|
1106
|
+
property, which is also what an editor rig has. Recover the offset by sweeping it
|
|
1107
|
+
against the frames where the part is unoccluded and taking the minimum; the minimum
|
|
1108
|
+
is sharp, and it is a structural constant rather than a per-frame value, so a
|
|
1109
|
+
handful of frames settle it. Measured on a shot with four such parts: MAE **3.13 โ
|
|
1110
|
+
1.95** with not one key value re-measured, and those parts' chains from 10โ13
|
|
1111
|
+
`MAE in it` down to 1.3โ8.8. What made it believable rather than a lucky fit is the
|
|
1112
|
+
second trap's own cross-check โ the four offsets came out the same **fraction of each
|
|
1113
|
+
part's own height**, from four independent fits, which is the quantity that had to
|
|
1114
|
+
agree between them and did.
|
|
1115
|
+
|
|
970
1116
|
**A value is easier to get right than a curve.** The three traps above are all
|
|
971
1117
|
about measuring a *value*, and both ladder runs so far found that the values came
|
|
972
1118
|
out right early: rung 1's key values were exact at every keyframe on the second
|
|
@@ -1060,6 +1206,58 @@ the other two. Where a chain ends in something whose position you can actually s
|
|
|
1060
1206
|
a hand, a foot, a held prop โ scan the two links above it as a **pair**, over the
|
|
1061
1207
|
grid. That is the product of two ranges on a handful of chains, not on every bone.
|
|
1062
1208
|
|
|
1209
|
+
โ ๏ธ **Two whole chains can share a minimum, and no paired scan reaches that one.**
|
|
1210
|
+
The case above is two knobs in one chain. The harder one is two *chains* sitting in
|
|
1211
|
+
the same pixels: an arm and the prop it holds lying across the part of the frame the
|
|
1212
|
+
reference fills with the legs. Every leg knob that would carry a leg there finds the
|
|
1213
|
+
pixels already inked and reports no improvement โ correctly, on the objective it was
|
|
1214
|
+
given โ and the frame keeps its limbs in a **different configuration** rather than a
|
|
1215
|
+
slightly wrong one. Pairing cannot help here, because the two knobs are in different
|
|
1216
|
+
chains and pairing every chain with every other is the whole product.
|
|
1217
|
+
|
|
1218
|
+
**What reaches it is cheap: more than one start, screened coarsely.** Assemble a
|
|
1219
|
+
handful of candidate poses for the frame โ the incumbent, the two neighbouring
|
|
1220
|
+
frames' solutions, a few poses spread across the shot, the setup pose โ run all of
|
|
1221
|
+
them through the **coarse levels only**, and take the best two through the full
|
|
1222
|
+
schedule. **Keep the incumbent among the candidates**, so a frame can only improve
|
|
1223
|
+
on what it already had. The cost is a multiple of the coarse pass rather than of the
|
|
1224
|
+
fit, and the neighbour seed below is one start out of that set rather than a rule of
|
|
1225
|
+
its own. โ ๏ธ It also measures how far from converged a single-start fit can be while
|
|
1226
|
+
reporting success: repeat the *identical* search on one frame from different starts
|
|
1227
|
+
and the numbers walk down, step after step. That is not a tolerance being tightened,
|
|
1228
|
+
it is a different basin each time โ so a search that stopped improving is evidence
|
|
1229
|
+
about the start it was given and about nothing else.
|
|
1230
|
+
|
|
1231
|
+
**Cross-shot starts, for a configuration a shot cannot reach from its own frames.**
|
|
1232
|
+
Where every pose in one shot holds the prop low and the reference holds it out
|
|
1233
|
+
level, every start drawn from that shot's own frames is on the wrong side of the
|
|
1234
|
+
same two-chain minimum, and multi-start inside the shot barely moves the number.
|
|
1235
|
+
These shots are states of one character, so a configuration this shot never visits
|
|
1236
|
+
may be sitting in another one โ take the start from there. โ Borrow **only the bones
|
|
1237
|
+
of the chain in question**, never a whole foreign pose: a foreign pose puts the legs
|
|
1238
|
+
where this shot never goes, and the rest of the search then spends itself fighting
|
|
1239
|
+
what the borrow brought with it.
|
|
1240
|
+
|
|
1241
|
+
๐จ **Before you fit a chain at all, check that it can *reach* the extremes the shot
|
|
1242
|
+
visits โ a reach deficit is invisible to every per-frame fit.** This is the precondition
|
|
1243
|
+
the borrow rule assumes and the loop does not check. If a chain's segment lengths are
|
|
1244
|
+
short โ read off a pose where the chain is **folded**, which is the easiest reading to
|
|
1245
|
+
take and the one most likely to be wrong โ then every frame where the chain is folded
|
|
1246
|
+
fits beautifully, and the fitter *silently absorbs* the deficit on every other frame by
|
|
1247
|
+
rotating the parts it does have. Nothing reports a failure. The number is merely a little
|
|
1248
|
+
worse everywhere, which reads like an ordinary residual, until a passage needs the full
|
|
1249
|
+
extension and then no start converges anywhere near it โ and multi-start does not help,
|
|
1250
|
+
because the pose being searched for is **outside the chain's reachable set**.
|
|
1251
|
+
|
|
1252
|
+
โ **The check is arithmetic and needs no fit.** Take the chain's total reach from your own
|
|
1253
|
+
rig; take the longest excursion the shot's own frames show that chain's end travelling โ
|
|
1254
|
+
a pendulum's full swing, a limb's extreme, a prop's sweep โ and compare. If the shot asks
|
|
1255
|
+
for markedly more than the chain has, the rig is wrong and no amount of searching will say
|
|
1256
|
+
so. โญ **A frames-side reading beats a rig-side one here**: the shot's own extremes are a
|
|
1257
|
+
measurement, while segment lengths taken off a folded pose are an estimate โ so when they
|
|
1258
|
+
disagree, suspect the estimate. And do this **per chain, before its first fit**, because
|
|
1259
|
+
the surgery to fix it invalidates every pose already fitted with the short chain.
|
|
1260
|
+
|
|
1063
1261
|
**Re-fit the setup pose against frames drawn from every shot, not against one.** Every
|
|
1064
1262
|
animation is measured from the setup pose, so an error in it is an error in all of
|
|
1065
1263
|
them โ and it is exactly the error one frame cannot show you. Fit an attachment's
|
|
@@ -1072,6 +1270,47 @@ against a handful of frames drawn from **every** animation at once, and hold it
|
|
|
1072
1270
|
while the per-frame poses are fitted. It is the spread that identifies it โ a
|
|
1073
1271
|
sequence of single-frame fits, one per shot, is not the same thing.
|
|
1074
1272
|
|
|
1273
|
+
๐จ **That rule is not sufficient for a *joint*, and the difference is not a matter of
|
|
1274
|
+
degree.** An attachment offset is identified by a spread of *rotations*; a **pivot** โ the
|
|
1275
|
+
point one bone turns about relative to its parent โ is identified only by frames whose
|
|
1276
|
+
**relative rotation across that joint actually differs**. So a spread can draw frames from
|
|
1277
|
+
every single shot, satisfy the paragraph above to the letter, and still be
|
|
1278
|
+
**ill-conditioned**: if every shot holds that joint at much the same relative angle, the
|
|
1279
|
+
pivot is barely constrained, and a wrong one re-solves far away *at equal residuals*. Equal
|
|
1280
|
+
residuals is the trap โ nothing in the fit reports a problem, because there genuinely is no
|
|
1281
|
+
better answer within the data you gave it.
|
|
1282
|
+
|
|
1283
|
+
๐ซ **And a structural descent that holds the fitted poses fixed cannot recover a
|
|
1284
|
+
mis-triangulated pivot at all.** This is the part worth internalising, because it looks
|
|
1285
|
+
like the obvious repair and it is inert: the per-frame poses were *fitted against the wrong
|
|
1286
|
+
pivot*, so they have already absorbed its error. Move the pivot with those poses held and
|
|
1287
|
+
every frame gets worse; hold the pivot and refit the poses and they re-absorb it. **The
|
|
1288
|
+
gradient at fixed poses points nowhere**, so the descent reports convergence on the wrong
|
|
1289
|
+
geometry โ and multi-start does not help either, because the defect is not a basin you
|
|
1290
|
+
failed to reach, it is a parameter the objective is no longer a function of.
|
|
1291
|
+
|
|
1292
|
+
โ **Triangulate a joint from part template matches across *configurations*, not from the
|
|
1293
|
+
whole-figure objective.** Match the two parts the joint connects โ each is its own art file
|
|
1294
|
+
and its own reading โ on frames that put the joint in **genuinely different relative
|
|
1295
|
+
angles**, and solve for the one point that is fixed in both parts' own coordinates. Then
|
|
1296
|
+
refit the poses against the corrected pivot. Two practical notes:
|
|
1297
|
+
|
|
1298
|
+
- โญ **"Different configurations" means what the shot list looks like, not how many frames
|
|
1299
|
+
you took.** A figure standing, walking and running may hold one joint at nearly the same
|
|
1300
|
+
angle throughout; a figure **lying down**, or inverted, or reaching across itself, is what
|
|
1301
|
+
makes that joint observable. Pick frames for *angular diversity across the joint*, and if
|
|
1302
|
+
the shot list has only one configuration, say in the log that the pivot is a prior.
|
|
1303
|
+
- ๐ **Check the conditioning rather than trusting the fit**: re-solve the joint from a
|
|
1304
|
+
subset that excludes the diverse configurations and see how far the answer moves. If it
|
|
1305
|
+
moves a long way at comparable residuals, the diverse frames were carrying the whole
|
|
1306
|
+
identification โ which is exactly the state in which an earlier triangulation goes wrong
|
|
1307
|
+
silently.
|
|
1308
|
+
- โ ๏ธ **Sequence matters, because the surgery invalidates work.** Correcting a pivot
|
|
1309
|
+
invalidates every pose fitted under the old one, so do it **before** the per-frame fitting
|
|
1310
|
+
budget is spent, not after. When it has to be done late, expect to re-settle every channel
|
|
1311
|
+
hung off that joint โ and freeze the ones that are not, so the two effects stay separable
|
|
1312
|
+
in the record.
|
|
1313
|
+
|
|
1075
1314
|
**Seed each frame's search from its neighbour's solution โ as one start among the
|
|
1076
1315
|
full-range scans, never instead of them.** Adjacent frames are adjacent poses, so the
|
|
1077
1316
|
answer next door is a better first guess than the middle of any range, and it costs one
|
|
@@ -1126,6 +1365,23 @@ not a quiet one.** The matcher refuses to name a distance past the part's own si
|
|
|
1126
1365
|
(ยง9.2), so a limb far enough out reports no match rather than a large number โ read
|
|
1127
1366
|
that beside a high figure per pixel as the strongest signal the table has.
|
|
1128
1367
|
|
|
1368
|
+
โ ๏ธ **Excess adjacency change has a second diagnosis, and the rule above assumes the
|
|
1369
|
+
first.** *A limb has left its place* is one cause โ a fit that teleported, which is what
|
|
1370
|
+
a blank drift and a high figure per pixel together point at. The other is **two
|
|
1371
|
+
independent per-frame residuals adding**: every pose inside its own accuracy, nothing
|
|
1372
|
+
lost, and the *difference* between two neighbours nonetheless several times the
|
|
1373
|
+
reference's. The fixes are opposite โ the first wants the search bounded or restarted,
|
|
1374
|
+
the second wants the neighbouring poses drawn toward each other (ยง10.3's own note on
|
|
1375
|
+
this) โ so guessing costs a round either way.
|
|
1376
|
+
|
|
1377
|
+
โ **Separate them by asking how much freedom the neighbour-mean step actually had.**
|
|
1378
|
+
Measure, over every neighbouring pair in the shot, how many of those steps your own
|
|
1379
|
+
constraints left **free** to move: if the answer is a percent or two of them, then the
|
|
1380
|
+
search was not free to teleport anything, and the excess is residuals adding rather than
|
|
1381
|
+
a lost limb. It is one count over data the
|
|
1382
|
+
fit already produced, and it is worth more than an afternoon of restarts aimed at the
|
|
1383
|
+
wrong cause.
|
|
1384
|
+
|
|
1129
1385
|
**What comes out is a pose per frame, and a pose per frame is not a key.** Two things
|
|
1130
1386
|
decide what survives the reduction, and **ยง10.3** states both: declare one tolerance
|
|
1131
1387
|
in pixels at the end of what each bone swings rather than a figure in degrees, and
|
|
@@ -1153,19 +1409,42 @@ to fit one framing across every set instead of one each, `--all-frames` to list
|
|
|
1153
1409
|
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
1154
1410
|
per-slot report.
|
|
1155
1411
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
not
|
|
1167
|
-
|
|
1168
|
-
|
|
1412
|
+
โญ **A frame set may ship a contact sheet instead of every frame, and the sheet is
|
|
1413
|
+
compared too.** A long shot does not commit 311 near-duplicate PNGs: rung 2's sets
|
|
1414
|
+
ship `f0000.png` and `f0310.png` plus a `contact.png` holding all 311 sampled
|
|
1415
|
+
frames, and spineboy's `@30fps` sets do the same. The frame table still says
|
|
1416
|
+
`frames 2 on disk, candidate samples 311, 2 compared` โ those are the files โ and a
|
|
1417
|
+
**`sheet` line under it** carries the other 309: your candidate sampled at the set's
|
|
1418
|
+
own rate, rendered into the same box the frames above were at the sheet's own scale,
|
|
1419
|
+
and compared tile by tile (issue #36 โ the gap a rung-2 run had already prototyped a
|
|
1420
|
+
comparator for, in its own working directory, because the tool could not see its shot).
|
|
1421
|
+
|
|
1422
|
+
โ ๏ธ **Read it as a series, not as one number** โ ยง9.2. And note what it does not
|
|
1423
|
+
carry: MAE only. The `ฮpx` / `ref ฮ` thresholds are calibrated at frame scale and a
|
|
1424
|
+
tile has a fraction of a frame's pixels, so the per-frame change measure stays on
|
|
1425
|
+
the committed stills, where it reports `no two compared frames are adjacent` and
|
|
1426
|
+
means it.
|
|
1427
|
+
|
|
1428
|
+
โญ **Do not treat such a set's two stills as bookends. They are full-resolution frames
|
|
1429
|
+
at their own rate, and one of them is routinely a pose no other set on disk carries.**
|
|
1430
|
+
The temptation is to read a strided set as *a sheet, plus two files that fix the
|
|
1431
|
+
framing* โ the sheet is where the shot is, so the stills look like plumbing. But the
|
|
1432
|
+
last still is the animation's **own last sample at that rate**, and a finer rate lands
|
|
1433
|
+
on a different instant: a shot whose length is not a multiple of the coarse interval
|
|
1434
|
+
ends *between* two coarse samples, so the coarse set's last frame is not the end of the
|
|
1435
|
+
shot and the finer set's is. If the shot is still moving there โ and an end pose usually
|
|
1436
|
+
is the part that moves most โ that pose exists in exactly one file, at full resolution,
|
|
1437
|
+
and it is worth fitting like any other frame.
|
|
1438
|
+
|
|
1439
|
+
โ **Two consequences for a run.** โ **Fit every committed still**, at every rate, and
|
|
1440
|
+
do not let a "sheets are for timing" habit skip them; a pose you never fitted is a pose
|
|
1441
|
+
you guessed, and a hold written across the gap because nothing on disk contradicted it
|
|
1442
|
+
is a **fabrication** rather than a simplification. โก This is the same fact a brief
|
|
1443
|
+
states from the timing side when it warns you against declaring the coarse set's
|
|
1444
|
+
rounded length: the rounding and the missing pose are one arithmetic, seen twice. If
|
|
1445
|
+
your shot's length is not a whole number of coarse intervals, expect **both** โ a
|
|
1446
|
+
duration the coarse sidecar understates, and a terminal pose only the finer set shows
|
|
1447
|
+
you.
|
|
1169
1448
|
|
|
1170
1449
|
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
1171
1450
|
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
@@ -1222,8 +1501,10 @@ question first: *do this set's own drawn pixels land in the box `frames.json`
|
|
|
1222
1501
|
records?* The sets that do are measured in that box, which is exact โ it is not an
|
|
1223
1502
|
estimate of where the frames were drawn, it is where they were drawn โ and nothing
|
|
1224
1503
|
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
|
-
|
|
1504
|
+
framing** fitted across every set, printed as the header's `shared box` line, plus
|
|
1505
|
+
their own whole-pixel MAE refinement off it (ยง9.2) โ the fit is shared because more
|
|
1506
|
+
frames condition it better; the constant offset it still leaves is per set, and
|
|
1507
|
+
measured per set. Each set says which it got on its own `framed to` line.
|
|
1227
1508
|
|
|
1228
1509
|
Why the split falls there, both halves measured on an 8-shot character (147 frames):
|
|
1229
1510
|
|
|
@@ -1278,6 +1559,13 @@ your run ceasing to be an honest authoring run. It is a loop, in the way `build`
|
|
|
1278
1559
|
a loop. `bench` and `diff` against a rung's export are not โ they read the answer,
|
|
1279
1560
|
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
1280
1561
|
|
|
1562
|
+
๐ **That is also why the MAE figures quoted through this section stay.** Every one of
|
|
1563
|
+
them is a candidate's own reading against rendered frames โ the exam question, not the
|
|
1564
|
+
answer key โ so none of them narrows a reference-side measure, and a guide that censored
|
|
1565
|
+
them would be teaching less for no gain in honesty. The criterion is under *The honesty
|
|
1566
|
+
rule* in [LADDER.md](LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1567
|
+
over a reference's own count, and no such figure appears here.
|
|
1568
|
+
|
|
1281
1569
|
๐จ **If you drive the runtime yourself, a bone's local transform lives on
|
|
1282
1570
|
`bone.pose`.** A shot whose poses have to be *fitted* rather than read sends you
|
|
1283
1571
|
past `check` and into your own render loop over
|
|
@@ -1303,8 +1591,8 @@ file, the next build reads those as zero, `validate` is green and `check` runs.
|
|
|
1303
1591
|
โ **A `null` in your own placements dump is the signature of having read
|
|
1304
1592
|
`bone.data` directly** โ nothing in this format is ever legitimately null. On
|
|
1305
1593
|
spineboy it cost the candidate MAE 13.0 โ 114.6 with a green gate throughout
|
|
1306
|
-
(
|
|
1307
|
-
|
|
1594
|
+
(measured in a spineboy attempt's own loop log, ยง4.1). Note that the two names are not
|
|
1595
|
+
the same thing: `bone.data.setupPose` is
|
|
1308
1596
|
the setup transform, while `bone.setupPose()` on a `Bone` is the method that
|
|
1309
1597
|
resets `bone.pose` back to it.
|
|
1310
1598
|
|
|
@@ -1315,8 +1603,7 @@ stores; what gets drawn is that stored quad โ `computeWorldVertices` reads
|
|
|
1315
1603
|
error nor a move. **Call `attachment.updateSequence()` after every write**, or
|
|
1316
1604
|
every frame renders the quad it was loaded with. A setup fit on spineboy ran
|
|
1317
1605
|
4,500 renders and reported the same number for all of them before this surfaced
|
|
1318
|
-
(
|
|
1319
|
-
ยง4.2).
|
|
1606
|
+
(the same loop log, ยง4.2).
|
|
1320
1607
|
|
|
1321
1608
|
โ **An MAE that is identical across every pose, and that does not move for any
|
|
1322
1609
|
parameter you sweep โ a bone's local transform, an attachment's offsets โ is one
|
|
@@ -1324,6 +1611,145 @@ of these inert writes and not a wrong animation.** The parameter you swept was
|
|
|
1324
1611
|
never read; a wrong rig moves the number, a write to a field nothing reads
|
|
1325
1612
|
cannot.
|
|
1326
1613
|
|
|
1614
|
+
๐จ **And the mirror image of all three: an objective that *improves by removing the
|
|
1615
|
+
subject*.** The traps above are each *"the number will not move"*, and they train you
|
|
1616
|
+
to distrust a still figure. The twin is a figure that moves, in the right direction,
|
|
1617
|
+
for the wrong reason โ and it is the more dangerous one, because progress is what it
|
|
1618
|
+
looks like.
|
|
1619
|
+
|
|
1620
|
+
The shape is arithmetic, not a bug. **Any symmetric error over two silhouettes charges
|
|
1621
|
+
a mismatch in both directions**, so it charges your ink that the reference has none
|
|
1622
|
+
under *and* the reference's ink you leave bare. Give it a candidate that draws
|
|
1623
|
+
**nothing** and only the second term survives: the score is the reference's own ink,
|
|
1624
|
+
once, and it is a *finite, respectable-looking number*. A part that is present but
|
|
1625
|
+
badly posed pays both terms and can score **worse than absence**. โ A search with a
|
|
1626
|
+
free enough range finds the cliff and walks off it, and every step of the walk reports
|
|
1627
|
+
as an improvement. Synthetic illustration of the whole failure in three rows โ one
|
|
1628
|
+
part, one objective, nothing else changed:
|
|
1629
|
+
|
|
1630
|
+
| what the candidate does | part error |
|
|
1631
|
+
| --- | --- |
|
|
1632
|
+
| posed roughly right | 2.15 |
|
|
1633
|
+
| posed badly โ overlapping the wrong reference ink | 2.48 |
|
|
1634
|
+
| **translated clean off the frame** | **1.00** |
|
|
1635
|
+
|
|
1636
|
+
The search reports **1.00 against 2.15** and calls it a 53 % gain. What it found was
|
|
1637
|
+
the absence of a subject. โ ๏ธ **1.00 is not a coincidence in that table, it is the
|
|
1638
|
+
construction**: absence pays the reference's ink exactly once, so on any objective
|
|
1639
|
+
normalised by that ink, *"draw nothing"* sits at 1.0 by arithmetic โ which is why it is
|
|
1640
|
+
worth evaluating deliberately rather than discovering.
|
|
1641
|
+
|
|
1642
|
+
โ **Four defences, and the first is the cheap one.**
|
|
1643
|
+
|
|
1644
|
+
- **Bound the search to the frame.** A part cannot legitimately leave the picture on a
|
|
1645
|
+
shot whose frames all draw it, so a translation range wide enough to exit the
|
|
1646
|
+
viewport is a range that contains a false optimum. Bound each parameter by what the
|
|
1647
|
+
frames can *show*, not by what the format permits.
|
|
1648
|
+
- โ ๏ธ **And its converse, which is the easier half to get wrong: a bound has to
|
|
1649
|
+
*reach* what the frames show, not merely stop where they stop.** The two failures
|
|
1650
|
+
look nothing alike โ a bound that is too wide loses a fit to the cliff, a bound
|
|
1651
|
+
that is too narrow loses one to a wall it never reports hitting. Bounding a
|
|
1652
|
+
vertical channel to the range the *rest* pose occupies is the classic case: a shot
|
|
1653
|
+
that drops its subject in from hundreds of pixels above the standing pose puts its
|
|
1654
|
+
own entry outside the search entirely, and the fitter returns the best pose *it was
|
|
1655
|
+
allowed*, which is the top of the box, silently. โ **Take each channel's range from
|
|
1656
|
+
the brief and the frames โ the extremes the shot actually visits โ and then check
|
|
1657
|
+
afterwards how many converged values are sitting on a bound.** A knob resting
|
|
1658
|
+
exactly on its limit is the signature, and it costs one line to print.
|
|
1659
|
+
- **Assert the part is drawn, every iteration.** Count your own ink for that part and
|
|
1660
|
+
reject any candidate whose count is zero or a small fraction of the reference's. This
|
|
1661
|
+
is one comparison and it makes the cliff unreachable rather than merely unattractive.
|
|
1662
|
+
- โ ๏ธ **Write it at the resolution the level is actually being evaluated at.** On a
|
|
1663
|
+
coarse-to-fine pyramid, a threshold expressed in full-resolution pixel counts
|
|
1664
|
+
refuses **every** coarse pose โ and a rejection that fires on everything is
|
|
1665
|
+
indistinguishable from an objective with no gradient. You get `Infinity`, or a
|
|
1666
|
+
figure far worse than the same search reaches with the assert switched off, and
|
|
1667
|
+
nothing in either says *"your guard is the problem"*. โ Express the count as a
|
|
1668
|
+
**fraction of the reference's ink at that same level**, so the test means the same
|
|
1669
|
+
thing at every rung of the pyramid.
|
|
1670
|
+
- **Charge ink that leaves the window, because the cliff has a second entrance.** If
|
|
1671
|
+
your objective is computed inside a window around the reference's own drawn box โ
|
|
1672
|
+
and it usually is, since that is what makes it cheap โ then ink outside the window
|
|
1673
|
+
costs **nothing**, and the three defences above do not reach that: the part is still
|
|
1674
|
+
drawn, its count is still healthy, and the score still falls. The fitter hangs a part
|
|
1675
|
+
a few hundred pixels below the frame and reports progress every step. โ **Count your
|
|
1676
|
+
own ink further than a small margin outside the reference's drawn box and charge it.**
|
|
1677
|
+
One line, in the same place as the ink count, and it closes the entrance the bound
|
|
1678
|
+
closes only when the bound happens to be tight enough.
|
|
1679
|
+
- **Read the objective's floor before you trust its direction.** Evaluate *"draw
|
|
1680
|
+
nothing"* once, deliberately, and keep the figure. Any score at or below it is the
|
|
1681
|
+
cliff, whatever the search says โ and if your best honest pose is *above* that floor,
|
|
1682
|
+
the objective is ranking absence over effort and needs an asymmetry (charge bare
|
|
1683
|
+
reference ink more than stray candidate ink) before it is safe to optimise against.
|
|
1684
|
+
|
|
1685
|
+
๐ **`check` itself is not exposed to this** โ its `MAE in it` and `share` columns
|
|
1686
|
+
divide over the **reference's** own drawn pixels and a chain that draws nothing reads
|
|
1687
|
+
0 % on 0 slots, which ยง9.2 says is the loudest row in the table and not the quietest.
|
|
1688
|
+
The trap lives in the objectives **you** write inside a fit, where the denominator is
|
|
1689
|
+
yours to choose.
|
|
1690
|
+
|
|
1691
|
+
๐จ **The cliff's nearest cousin, and the one that survives all four defences:
|
|
1692
|
+
*sacrificial cover*.** Every defence above protects a part from being **removed**. None
|
|
1693
|
+
protects a part from being **moved somewhere wrong on purpose**. A whole-figure objective
|
|
1694
|
+
scores one number over every pixel, so when part A is mis-placed and leaves reference ink
|
|
1695
|
+
bare, the cheapest available improvement is frequently to drag **part B off its own correct
|
|
1696
|
+
place to cover that ink**. Both parts are drawn, both counts are healthy, nothing leaves
|
|
1697
|
+
the window โ and the score genuinely falls, because covering bare ink is worth more to a
|
|
1698
|
+
blunt objective than B's own displacement costs it.
|
|
1699
|
+
|
|
1700
|
+
โ ๏ธ **What makes it expensive is that the objective is not lying.** The pose it prefers
|
|
1701
|
+
really is better *by that measure*. So the loop offers no signal at all: the fit converges,
|
|
1702
|
+
the number improves, and what you have is one part visibly out of place standing in for
|
|
1703
|
+
another. It surfaces later as a **drift** on the sacrificed part โ a slot several pixels
|
|
1704
|
+
from where the frames put it inside a pose whose overall figure looks fine โ which is the
|
|
1705
|
+
one measure that reads parts individually.
|
|
1706
|
+
|
|
1707
|
+
โ **Two ways to catch it, and the first is nearly free.**
|
|
1708
|
+
|
|
1709
|
+
- **Read a per-part residual beside the composite, never only the composite.** Score each
|
|
1710
|
+
part against its own template match as well, and flag any frame where the composite
|
|
1711
|
+
improves while a part's own residual worsens. That divergence *is* the signature; the
|
|
1712
|
+
composite alone cannot express it.
|
|
1713
|
+
- **Seed the parts analytically from their own measured features, then refine jointly with
|
|
1714
|
+
the sacrificed part pinned.** If a part's place is independently measurable โ a colour
|
|
1715
|
+
feature, a template peak, a contact row the brief gives you โ put it there first rather
|
|
1716
|
+
than letting the composite negotiate it, and hold the part that was being abused fixed
|
|
1717
|
+
while the rest re-settles.
|
|
1718
|
+
|
|
1719
|
+
โ๏ธ **Expect the corrected pose to score *worse* on the composite, and record that as a
|
|
1720
|
+
trade.** A few percent worse on your own objective while decisively better on every
|
|
1721
|
+
frame-derived placement instrument is the **expected** shape of this repair, not a
|
|
1722
|
+
regression โ the composite's preference was the defect. Declare an accept threshold before
|
|
1723
|
+
you need it, say how often you used it, and name the frames. โญ **And prefer the
|
|
1724
|
+
frame-derived instruments when they disagree with the composite about a single part's
|
|
1725
|
+
place**: the composite is one number over everything, while a template match on that part's
|
|
1726
|
+
own art is a measurement of the thing in question.
|
|
1727
|
+
|
|
1728
|
+
๐จ **One more inert-write trap, and it is on the way *out* of the fit rather than
|
|
1729
|
+
inside it: your compiled animation is not your pose series.** Everything above is about
|
|
1730
|
+
a search that reads the wrong thing; this is about a search that was right and an
|
|
1731
|
+
emission that was not. The formats differ in a way that is easy to miss โ **a translate
|
|
1732
|
+
key is an offset from the setup pose, while a fitter almost always drives the absolute
|
|
1733
|
+
local position** โ so writing the fitted numbers straight into keys applies the setup
|
|
1734
|
+
offset a second time and displaces the whole figure by it.
|
|
1735
|
+
|
|
1736
|
+
โ ๏ธ **What makes it expensive is how it presents.** `build` is green: the numbers are
|
|
1737
|
+
finite, the durations agree, nothing is degenerate. And `check` does not say *"your keys
|
|
1738
|
+
are offset"* โ it says the union box is a fifth larger than the reference's, the MAE is
|
|
1739
|
+
several times anything a wrong pose produces, and no slot is attributable anywhere.
|
|
1740
|
+
That reads like a **wrong rig**, so the hours go into the rig.
|
|
1741
|
+
|
|
1742
|
+
โ **Before reading a single measure, sample your own compiled animation and diff it
|
|
1743
|
+
against the pose series the fitter produced.** `sampleAnimation` in
|
|
1744
|
+
[`src/render.ts`](../src/render.ts) is the same stepper the frames were made with, so
|
|
1745
|
+
this is a handful of lines and it is exact: for every frame, for every bone, the local
|
|
1746
|
+
transform the file plays back against the local transform you fitted. A constant offset
|
|
1747
|
+
per channel is this bug; a constant *factor* is a unit or lever mistake; zeros
|
|
1748
|
+
everywhere are ยง9.1's `bone.pose` trap one level earlier. โญ **The general rule: a
|
|
1749
|
+
pipeline with a fit at one end and a file at the other needs one check that the file
|
|
1750
|
+
plays what the fit found**, and it belongs before the measures rather than after a day
|
|
1751
|
+
of them.
|
|
1752
|
+
|
|
1327
1753
|
### 9.2 Reading the table
|
|
1328
1754
|
|
|
1329
1755
|
```
|
|
@@ -1331,6 +1757,7 @@ cannot.
|
|
|
1331
1757
|
reference 256x116px 0.117628 px/unit world x[-573.3 .. 1603.0] y[-81.2 .. 908.9] (frames.json)
|
|
1332
1758
|
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
1759
|
โคท 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)
|
|
1760
|
+
โญ MAE-refined by -1, +1 px: 54.31 โ 48.47 over the reference's own pixels (10.7% of the figure). โฆ
|
|
1334
1761
|
in units candidate 1995.3 x 809.7 reference 1995.3 x 809.9 x0.9999
|
|
1335
1762
|
|
|
1336
1763
|
โโ heavy โ candidate animation "heavy", 12 fps โโ
|
|
@@ -1340,6 +1767,8 @@ cannot.
|
|
|
1340
1767
|
slot drift worst 2.1 px "pendulum" at f0029
|
|
1341
1768
|
per-frame 1 of 64 adjacent pair(s) change by a different amount than the reference does; worst
|
|
1342
1769
|
f0018, yours moved 0 px where the reference moved 374
|
|
1770
|
+
sheet 311 of 311 tile(s) of contact.png at 64x57px in 8 column(s) MAE mean 4.30 worst 4.76 at f0047
|
|
1771
|
+
โคท 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
1772
|
|
|
1344
1773
|
the 9 frames worth reading โ worst by MAE, plus every frame whose own change disagrees, in index order
|
|
1345
1774
|
frame MAE union px ฮpx ref ฮ worst slot drift how slots note
|
|
@@ -1401,6 +1830,12 @@ The lines, in order:
|
|
|
1401
1830
|
does not, after the fit. This is the number that says *"something reaches
|
|
1402
1831
|
somewhere nothing in the frames does, or is a different size"*, and a warning
|
|
1403
1832
|
spells it out past a pixel.
|
|
1833
|
+
- the **MAE-refined** line, which is the last thing that happens to the box and the
|
|
1834
|
+
paragraph below is what it is for. On a **fitted** framing it says what constant
|
|
1835
|
+
whole-pixel offset was taken out and what that was worth (`โญ`), or that the
|
|
1836
|
+
search ran and the identity won. On a box that is not an estimate โ the frames'
|
|
1837
|
+
own, or one you pinned โ it never moves anything, and if it finds a constant there
|
|
1838
|
+
it says so as a **finding about your rig** rather than about the framing.
|
|
1404
1839
|
- `in units` โ the same two boxes in world units. The framing absorbs a pure scale
|
|
1405
1840
|
on purpose, so this is the only place one shows; it compares only if you measured
|
|
1406
1841
|
the shot in the frames' own units.
|
|
@@ -1411,10 +1846,31 @@ that is a little large โ the best fit of the two extents is not quite the best
|
|
|
1411
1846
|
alignment of the two pictures, and the fit spends a fraction of a pixel absorbing
|
|
1412
1847
|
a difference that would have been cheaper to leave alone. Measured floor: about a
|
|
1413
1848
|
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
|
-
|
|
1849
|
+
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five, and
|
|
1850
|
+
on the spineboy sets a **constant** one or two pixels was worth 10โ30 % of the
|
|
1851
|
+
figure (issue #146). This is the floor the frames' own box has no share in, which is
|
|
1852
|
+
why `check` prefers that box whenever your pixels are measured to land in it;
|
|
1853
|
+
`--viewport` is how you stop it in the cases that box does not cover.
|
|
1854
|
+
|
|
1855
|
+
โญ **What a fitted framing now does about it: one final whole-pixel pass.** After
|
|
1856
|
+
the fit settles (or cycles), `check` searches every whole-pixel offset within ยฑ2 px
|
|
1857
|
+
for the lowest MAE over the reference's own drawn pixels and moves the box to the
|
|
1858
|
+
best one, when that is worth at least 1 % of the figure. So a fitted set's numbers
|
|
1859
|
+
are what is left **after** the best constant offset has been removed, rather than a
|
|
1860
|
+
constant offset read as motion โ and the line says which offset and what it bought,
|
|
1861
|
+
in both directions, so nothing is quietly absorbed.
|
|
1862
|
+
|
|
1863
|
+
Two things to know when you read it:
|
|
1864
|
+
|
|
1865
|
+
- โ ๏ธ **A large refinement on a set whose drift is also large is not necessarily
|
|
1866
|
+
framing.** The pass removes the best *constant*, and when one part carries much of
|
|
1867
|
+
the shot's ink a constant can absorb part of that part's own displacement. Read
|
|
1868
|
+
the offset beside the chain table: a big offset with a flat drift table is the
|
|
1869
|
+
fit's floor; a big offset with one limb far out is that limb.
|
|
1870
|
+
- On a box that is not an estimate the pass declines and says why. `frames.json`'s
|
|
1871
|
+
own box is where the frames were drawn, so a constant pixel *there* is your
|
|
1872
|
+
figure sitting a pixel off inside the right box โ a thing to fix, and the report
|
|
1873
|
+
refuses to frame it away. A pinned box is your claim, and nothing overrules it.
|
|
1418
1874
|
|
|
1419
1875
|
**MAE** is the mean absolute RGB difference, 0..255, over the pixels either side
|
|
1420
1876
|
covers โ the *union alpha*. It is not scored against a threshold, any more than a
|
|
@@ -1441,6 +1897,110 @@ much ink as the reference does gets `โ ๏ธ overdraw` beside those two numbers,
|
|
|
1441
1897
|
both pixel counts, because at that point the first figure is cheap for a reason
|
|
1442
1898
|
that has nothing to do with your keys.
|
|
1443
1899
|
|
|
1900
|
+
๐จ **Part of your MAE is the texture, not the animation, and nothing in the report
|
|
1901
|
+
says so.** The reference frames are rendered through the example's **own packed
|
|
1902
|
+
atlas**, and a packed atlas may carry a `scale:` line โ the ladder has one at
|
|
1903
|
+
`scale: 0.5`, whose 745x212 part is packed at 373x106. rigc has no packer (**ยง6**), so
|
|
1904
|
+
a candidate built from the loose PNGs samples a texture at twice that resolution and
|
|
1905
|
+
resamples every edge differently. The pixels are the same shape in the same place; they
|
|
1906
|
+
are filtered from a different source, and the difference lands on the outline of every
|
|
1907
|
+
part in every frame. It is a constant, it is invisible to `content`, `rms` and the
|
|
1908
|
+
`ยฑ2 px` refinement โ a resampling difference is not an offset โ and **no key you write
|
|
1909
|
+
can move it**.
|
|
1910
|
+
|
|
1911
|
+
โ **When the MAE is flat across the whole set and the drift is already at the floor,
|
|
1912
|
+
check the atlas before you look for keys.** The example's `.atlas` is an allowed input
|
|
1913
|
+
in its own right (`bench/runs/README.md`, *What a run may read*, item 4), and one line
|
|
1914
|
+
of it answers the question. To size the floor, re-run `check` once with
|
|
1915
|
+
`--atlas <the example's own .atlas>`: same skeleton, same keys, the reference's own
|
|
1916
|
+
texture. Measured on rung 3, MAE **6.13 / 6.01** with the candidate's own
|
|
1917
|
+
full-resolution atlas against **2.25 / 2.30** with the supplied one โ **two thirds of
|
|
1918
|
+
the figure was the texture**, and the run that did not know it would have spent its
|
|
1919
|
+
whole budget hunting a rig that was already right.
|
|
1920
|
+
|
|
1921
|
+
โ ๏ธ Two things about that diagnostic. It is a **diagnostic and not a better number**:
|
|
1922
|
+
the artifact `bench` validates ships its own atlas, so the first figure is the one
|
|
1923
|
+
that belongs in a run's record and the second is the explanation of where it went.
|
|
1924
|
+
And the coarser texture **loses** resolution the finer one has โ on the same rung a
|
|
1925
|
+
pair the reference moves *one pixel* across stopped being visible at half scale, so
|
|
1926
|
+
the diagnostic run reported a frame-change disagreement the graded run does not have.
|
|
1927
|
+
Read it for the floor, never as the verdict.
|
|
1928
|
+
|
|
1929
|
+
๐จ **And a third, which decides whether the recipe measures anything at all: `--atlas`
|
|
1930
|
+
substitutes region *geometry* as well as texture.** The diagnostic's logic is *"same
|
|
1931
|
+
skeleton, same keys, the reference's own texture"* โ but an atlas entry is not only a
|
|
1932
|
+
page and a rectangle. It also carries how the region was packed: **`rotate`**, and the
|
|
1933
|
+
trim offsets that say where the opaque part sits inside the original image. Swap the
|
|
1934
|
+
atlas and your attachments are re-seated on those, so the quads change too.
|
|
1935
|
+
|
|
1936
|
+
โ **The recipe measures a floor only when the substitution is *"same quads, coarser
|
|
1937
|
+
texture"*.** Where the supplied atlas packs its regions **rotated or trimmed** and your
|
|
1938
|
+
attachments were measured off the loose PNGs, it is not โ and the tell is unmistakable:
|
|
1939
|
+
**the number goes the wrong way.** A texture floor can only *explain* error, so a
|
|
1940
|
+
diagnostic that sends the MAE **up** on every set has substituted geometry, not just
|
|
1941
|
+
pixels, and the run's own atlas was the more faithful of the two.
|
|
1942
|
+
|
|
1943
|
+
โ ๏ธ **Then the honest verdict is *inconclusive*, not *no floor*.** Both readings stay
|
|
1944
|
+
open โ there may be a texture floor this diagnostic cannot isolate โ so record the
|
|
1945
|
+
figures, say the substitution changed the quads, and do **not** convert a failed
|
|
1946
|
+
diagnostic into a claim about the shot. โ Check the atlas's own entries for `rotate` and
|
|
1947
|
+
for trim before you run it; that is one look at a text file, and it tells you in advance
|
|
1948
|
+
whether the number you are about to take will mean anything.
|
|
1949
|
+
|
|
1950
|
+
๐จ **The precondition the advice above does not state: a floor measured with another
|
|
1951
|
+
part misplaced is not a floor.** Measuring at the rest pose is right โ it is the one
|
|
1952
|
+
pose you can often *prove*, because the setup pose is the art at its own scale and the
|
|
1953
|
+
frames state the standing dimensions โ but "the pose is provably right" is a claim
|
|
1954
|
+
about **one part**, and the floor you read is a whole-figure number. Any other part
|
|
1955
|
+
that can occlude the one you are measuring is inside that number too, and a part
|
|
1956
|
+
sitting tens of units off its place occludes the **wrong** pixels: the ones it hides
|
|
1957
|
+
count as yours-and-not-theirs, the ones it should have hidden count as
|
|
1958
|
+
theirs-and-not-yours, and both land on the part you thought you were isolating.
|
|
1959
|
+
|
|
1960
|
+
The damage is that you then hold a *plausible* floor and calibrate against it. A
|
|
1961
|
+
synthetic case with the same shape โ one part measured three ways, nothing about that
|
|
1962
|
+
part changed between the rows:
|
|
1963
|
+
|
|
1964
|
+
| what else is placed | silhouette IoU read for the measured part |
|
|
1965
|
+
| --- | --- |
|
|
1966
|
+
| a neighbour still tens of units out of place | 0.74 |
|
|
1967
|
+
| that neighbour placed | **0.95** |
|
|
1968
|
+
| (the difference) | 0.21, all of it the neighbour |
|
|
1969
|
+
|
|
1970
|
+
A fifth of an IoU is larger than most of what a fit is trying to buy, so two or three
|
|
1971
|
+
experiments get read against the wrong baseline before anything exposes it โ and what
|
|
1972
|
+
usually exposes it is the setup fit finishing, which is *after* you needed the number.
|
|
1973
|
+
|
|
1974
|
+
โ **Before believing a floor, check that every part which can occlude the one you are
|
|
1975
|
+
measuring is already placed** โ and prefer a frame where the parts are **far apart or
|
|
1976
|
+
only one is drawn** to one where they overlap, which is ยง8.1's rule for calibrating a
|
|
1977
|
+
two-part assignment applied to a floor. If no such frame exists, say in the log that
|
|
1978
|
+
the floor is an upper bound on the error rather than a floor under it. โ ๏ธ This is the
|
|
1979
|
+
same failure as capturing a guard's expected value from a screen that is already
|
|
1980
|
+
broken: the baseline records the defect, and then the *repair* is what looks wrong.
|
|
1981
|
+
|
|
1982
|
+
โ๏ธ **`frames.json`'s own box can be refused for a reason that is not a coordinate
|
|
1983
|
+
error, and there is an honest answer.** The test is on **extent**: a candidate authored
|
|
1984
|
+
in the frames' own world units โ one whose setup box lands on the reference's to the
|
|
1985
|
+
pixel โ still fails it if its union content box differs by a few pixels at the
|
|
1986
|
+
extremes, because one part reaching somewhere nothing in the frames reaches is enough.
|
|
1987
|
+
`check` then fits its own box, and on a multi-shot root the fitted framing costs every
|
|
1988
|
+
set some MAE against the declared one โ the same order as the shared-versus-per-set gap
|
|
1989
|
+
the `--framing` flag's own help quotes, and easily more than a round of fitting buys.
|
|
1990
|
+
That cost is real and it is **not** a sign you got the coordinates wrong.
|
|
1991
|
+
|
|
1992
|
+
โ **Report both, label which is which, and say what separates them.** Run `check`
|
|
1993
|
+
unaided โ that is the figure the artifact produces on its own and the one that belongs
|
|
1994
|
+
in a run's record โ then run it once more with `--viewport` on the declared box and keep
|
|
1995
|
+
that output as a **named diagnostic file** beside the first. The gap between them is the
|
|
1996
|
+
framing; what is left is the keys, which is the only reason to want the second number.
|
|
1997
|
+
๐ซ **The pinned run is never the record.** `--viewport` is a claim about your own
|
|
1998
|
+
coordinates and `check` says so above every figure it prints under one: *nothing checks
|
|
1999
|
+
it*. And do not chase the refusal by shrinking a part to fit the box โ that trades a
|
|
2000
|
+
framing cost for a wrong silhouette, which is worse in every column that matters.
|
|
2001
|
+
Instead read the `content` line's own advice: it names how much wider and shorter you
|
|
2002
|
+
cover, and **which part reaches too far is a drift question**, not a framing one.
|
|
2003
|
+
|
|
1444
2004
|
**`ฮpx` and `ref ฮ`** are the two columns that do **not** compare you against the
|
|
1445
2005
|
reference. They compare each side against **itself one frame earlier**: how many
|
|
1446
2006
|
pixels of your own frame moved since your own previous frame, and the same for the
|
|
@@ -1466,12 +2026,46 @@ line whenever the MAE is flat and something still looks wrong: a flat MAE says t
|
|
|
1466
2026
|
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
1467
2027
|
and blinks where the reference does.
|
|
1468
2028
|
|
|
2029
|
+
โ ๏ธ **Each set is compared against ITSELF, so on a shot committed at two rates a hold
|
|
2030
|
+
can exist in one set and not the other.** The coarse set samples every other frame of
|
|
2031
|
+
the fine one, so a pair the coarse set holds across is a constraint between samples
|
|
2032
|
+
**2k and 2k+2** of the fine series โ and 2k+1 between them is free to move, and does.
|
|
2033
|
+
One ladder shot has a pair whose whole-frame change at the coarse rate is **exactly
|
|
2034
|
+
0** while both fine-rate pairs inside that same span change by 48 px: the subject
|
|
2035
|
+
shifts under three world units and comes back, so the two coarse samples land on the
|
|
2036
|
+
same pose either side of it. So ยง10.3's *key both ends of the hold* has to be applied
|
|
2037
|
+
**at each committed rate separately**, and equal values are not enough โ the two
|
|
2038
|
+
samples have to be **keys**, or a planner reduces through one of them and an
|
|
2039
|
+
interpolant inside its own tolerance is not equality. That cost two builds on that
|
|
2040
|
+
run, the second of them for exactly that reason. โ Measure the frames' own
|
|
2041
|
+
frame-to-frame change **once per committed rate**, and where a rate holds, pin both
|
|
2042
|
+
ends as keys whenever a finer rate moves between them.
|
|
2043
|
+
|
|
1469
2044
|
โ ๏ธ Only between **adjacent** frames. A set that ships stills rather than every frame
|
|
1470
2045
|
โ rung 2's contact-sheet sets โ reports `no two compared frames are adjacent`, and
|
|
1471
2046
|
means it: the difference between two frames 310 apart is not a frame-to-frame delta.
|
|
1472
2047
|
A disagreement needs one side to hold *exactly* still while the other moves, or one
|
|
1473
2048
|
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.
|
|
2049
|
+
two rasterisations differ by their own last bit and the column says nothing. Such a
|
|
2050
|
+
set gets the `sheet` line instead, which is MAE over every sampled frame and not a
|
|
2051
|
+
change measure โ the two thresholds above are pixel counts at frame scale, and a
|
|
2052
|
+
tile has a fraction of a frame's pixels.
|
|
2053
|
+
|
|
2054
|
+
**The `sheet` line is the whole shot**, on the sets that commit a couple of stills
|
|
2055
|
+
and fold every sampled frame into one `contact.png`. It says how many tiles were
|
|
2056
|
+
compared out of how many the sheet holds, the grid it measured off the sheet itself,
|
|
2057
|
+
the mean and worst tile, and the worst eight by MAE. Read the **series** rather than
|
|
2058
|
+
the mean, exactly as with the frame table: flat across the shot is framing or art,
|
|
2059
|
+
a spike is timing at that moment โ rung 2's four shots read 4.30โ4.41 flat over
|
|
2060
|
+
1,244 tiles, which is what says their trajectories, ring rates and attachment swaps
|
|
2061
|
+
land where and when they should. Two things to know:
|
|
2062
|
+
|
|
2063
|
+
- it is measured in the **same box** the frame table was, at the sheet's scale. For
|
|
2064
|
+
a stills-plus-sheet set that box was decided on the stills, so a set whose framing
|
|
2065
|
+
is a fit carries that fit into these numbers as well;
|
|
2066
|
+
- a sheet whose dimensions are not a grid of this set's frame count at these frames'
|
|
2067
|
+
aspect is **refused by name** rather than read wrong โ the note names the file, and
|
|
2068
|
+
the answer is to re-render the set.
|
|
1475
2069
|
|
|
1476
2070
|
**Slot drift** is what you act on. For each of your slots, `check` measures where
|
|
1477
2071
|
it landed and how far that is from where the reference put it. That names the part,
|
|
@@ -1481,8 +2075,15 @@ sentence you can take straight back to a key.
|
|
|
1481
2075
|
There are two matchers and the `how` column says which one answered:
|
|
1482
2076
|
|
|
1483
2077
|
- `component` โ your slot sits on a connected component of the reference frame that
|
|
1484
|
-
is its own size
|
|
1485
|
-
the strongest answer available.
|
|
2078
|
+
is its own size **and holds nothing else you drew**. The drift is the distance
|
|
2079
|
+
between the two centroids, and it is the strongest answer available. All three
|
|
2080
|
+
conditions are checked: a blob may not be much bigger than the slot, may not be
|
|
2081
|
+
much wider than its box, and may not contain another of your parts' ink. The last
|
|
2082
|
+
is the one a dominant part slips through otherwise โ rung 2's course is 81 % of a
|
|
2083
|
+
blob that also holds the water, the panel and both rings, so the blob is only
|
|
2084
|
+
1.24x its ink and no wider than its box, and the reported *"course drift 11.2 px"*
|
|
2085
|
+
was the distance to a five-part centroid (issue #37). It now falls to the matcher
|
|
2086
|
+
below and reads 0.0 px.
|
|
1486
2087
|
- `tmpl 0.62` โ the reference merged your slot into a neighbour (they touch, or one
|
|
1487
2088
|
is drawn over the other), so the fallback rendered **your slot on its own** and
|
|
1488
2089
|
correlated it against the reference around where you drew it. The number is the
|
|
@@ -1508,19 +2109,19 @@ a part you have not authored, or one you have put somewhere else entirely.
|
|
|
1508
2109
|
```
|
|
1509
2110
|
chains 6 from the candidate's own bone tree โ the roster is at the foot of the report
|
|
1510
2111
|
chain slots worst slot drift mean MAE in it share
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
2112
|
+
crest 5/5 3.0 px "lantern" f0006 2.4 px 28.40 31.5%
|
|
2113
|
+
prop-arm 6/6 2.0 px "prop" f0002 1.5 px 44.90 33.0%
|
|
2114
|
+
near-strut 3/3 15.0 px "strut-tip" f0004 7.0 px 51.30 8.5%
|
|
1514
2115
|
```
|
|
1515
2116
|
|
|
1516
2117
|
`check` cuts **your own** bone tree at every branch point โ a chain runs from a
|
|
1517
2118
|
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 `
|
|
2119
|
+
folds into its parent, so a `hub` that branches three ways joins the trunk rather
|
|
1519
2120
|
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
|
|
2121
|
+
that carries a slot. On a biped that lands on the parts you would name (`crest`,
|
|
2122
|
+
`near-strut` and `prop-arm` above); on a serial figure with no fork it is one chain,
|
|
2123
|
+
and the slot rows under it still say which link moved. Which bones and slots went
|
|
2124
|
+
where is printed as a roster at the foot of the report, so it is never a guess. `MAE
|
|
1524
2125
|
share` divides the difference over the **reference's** own drawn pixels โ the
|
|
1525
2126
|
denominator from the line above, which nothing you draw can grow โ and splits it by
|
|
1526
2127
|
giving each of those pixels to the chain whose ink is nearest, so the shares
|
|
@@ -1536,6 +2137,19 @@ a per-shot list. **ยง8.1** is how to act on it: the next iteration goes to the w
|
|
|
1536
2137
|
chain by error per pixel, and a chain already at the floor is frozen rather than
|
|
1537
2138
|
re-fitted.
|
|
1538
2139
|
|
|
2140
|
+
โ ๏ธ **One exception to "0 % is the loudest row", and on a mesh rig it is the common
|
|
2141
|
+
case: a chain whose roster reads `(draws nothing)` rather than `0/n`.** Those are two
|
|
2142
|
+
different states and the table prints them differently. `0 %` on `0/3` **slots drawn**
|
|
2143
|
+
means three slots exist on that chain and none of them put ink on the frame โ that is
|
|
2144
|
+
the loud row, and it is a missing part. `(draws nothing)` in the **bones** roster means
|
|
2145
|
+
the chain carries **no slot at all**, and a mesh's control bones are exactly that: the
|
|
2146
|
+
mesh attachment lives on the slot of the bone the mesh hangs from, so the bones that
|
|
2147
|
+
*deform* it own nothing to draw. โ **On a mesh rig that row is normal and quiet.** Read
|
|
2148
|
+
the roster at the foot of the report before reacting to a chain's share: if the chain's
|
|
2149
|
+
slots column is a parenthesis rather than a fraction, the deformation it carries is
|
|
2150
|
+
already being scored inside the chain that owns the slot, and the row is telling you
|
|
2151
|
+
about your bone tree rather than about a hole in your figure.
|
|
2152
|
+
|
|
1539
2153
|
### 9.3 What it still cannot see
|
|
1540
2154
|
|
|
1541
2155
|
- **Anything a frame does not contain.** Bone `length`, the setup `inherit` mode,
|
|
@@ -1566,10 +2180,22 @@ re-fitted.
|
|
|
1566
2180
|
3 px low at one frame have the same drift and opposite causes. The table gives
|
|
1567
2181
|
you the frame index; ยง8's rule still applies โ look for a second way to get the
|
|
1568
2182
|
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
|
|
2183
|
+
- **What happens between two committed frames** โ *unless the set ships a sheet.*
|
|
2184
|
+
`ฮpx` compares adjacent frames and a set that ships stills has none, so a shot
|
|
2185
|
+
that is right at every committed frame and wrong between them reads clean in that
|
|
2186
|
+
column. That is the same gap `--frames` on a contact-sheet set already has, and it
|
|
2187
|
+
is why the frame-count line is printed. โญ What closes it is the **`sheet` line**:
|
|
2188
|
+
on a set rendered at a higher rate than the frames on disk, every sampled frame is
|
|
2189
|
+
compared, so the samples between two committed ones are measured there and
|
|
2190
|
+
nowhere else.
|
|
2191
|
+
๐จ **And when you go looking for one of them, do not assume it lies between its
|
|
2192
|
+
neighbours.** A half-frame is not an interpolation โ it is where the shot actually
|
|
2193
|
+
was, and the interesting ones are interesting because it left the interval. Rung
|
|
2194
|
+
4's ball makes contact entirely inside one twelfth of a second: the sheet puts it
|
|
2195
|
+
**40 px below both 12 fps frames that bracket it**, while its x sits within their
|
|
2196
|
+
own range. A search whose reach was scaled from those two frames' own step
|
|
2197
|
+
therefore looked ยฑ9 px for something 40 px away and reported the interpolation
|
|
2198
|
+
back, twice, before the tile was read rather than inferred.
|
|
1573
2199
|
|
|
1574
2200
|
---
|
|
1575
2201
|
|
|
@@ -1714,6 +2340,146 @@ that is not held"* from the other side, and the same place catches it โ a slop
|
|
|
1714
2340
|
hold shows up in `check`'s per-frame column and nowhere else, because it is cheap in
|
|
1715
2341
|
every single frame and wrong only in the relation between two.
|
|
1716
2342
|
|
|
2343
|
+
โ ๏ธ **The key reducer has to key the plateau, because a plateau is neither an end nor
|
|
2344
|
+
a turn.** A planner that forces the series ends and every change of direction โ
|
|
2345
|
+
which is exactly what the rule above asks for โ will still author a slope straight
|
|
2346
|
+
through a run of still frames: not one sample in the middle of a hold is an end or
|
|
2347
|
+
a turning point, and a greedy span stays inside its own per-bone tolerance the whole
|
|
2348
|
+
way across. **A tolerance is not a hold.** Slow motion inside the tolerance is a
|
|
2349
|
+
tolerance question; stillness is a thing the shot *does*, and it survives the
|
|
2350
|
+
reduction only if the reduction is told to keep it. โ Force **both ends of every run
|
|
2351
|
+
of equal values** as keys in their own right โ a third kind of forced index beside
|
|
2352
|
+
the series ends and the turning points โ and test on **exact** equality, so that a
|
|
2353
|
+
merely near-still span is deliberately not swept up with it. This is worth doing
|
|
2354
|
+
before you have any evidence you need it: a run whose *poses* are all right can fail
|
|
2355
|
+
this and see nothing wrong anywhere else, because `validate` has no opinion on it,
|
|
2356
|
+
`diff` never looks at a rendered frame, and an aggregate MAE is cheap in every
|
|
2357
|
+
single frame and wrong only in the relation between two.
|
|
2358
|
+
|
|
2359
|
+
โ ๏ธ **And a tolerance is not a *slow span* either โ a key tolerance needs a relative
|
|
2360
|
+
floor beside its absolute one.** The rule above rescues exact stillness by forcing it,
|
|
2361
|
+
and deliberately leaves near-stillness to the tolerance. But **ยง9.2's per-frame column
|
|
2362
|
+
compares *changes*, not positions**, so on a span the shot barely moves across, an
|
|
2363
|
+
error well inside an absolute tolerance is most of the motion. Measured, on rung 3: the
|
|
2364
|
+
reference moves **0.109 px** between two frames; a greedy span deviated **0.098 px**
|
|
2365
|
+
there, legal under a 0.30 px tolerance and legal again under 0.15 px โ and the column
|
|
2366
|
+
read **259 px against the reference's 40**, a six-fold disagreement authored by a span
|
|
2367
|
+
that was, at every keyframe, exactly right. The same tolerance that is generous on the
|
|
2368
|
+
fast part of the shot is a 90 % error on the slow part, because one figure in pixels
|
|
2369
|
+
cannot be both.
|
|
2370
|
+
|
|
2371
|
+
โ Cap each span's deviation at **the smaller of the absolute tolerance and the
|
|
2372
|
+
smallest single-frame move inside that span**. It is one line in the planner, it costs
|
|
2373
|
+
a handful of keys, and it is the difference between a reduction that is accurate and
|
|
2374
|
+
one that is accurate *in proportion to what is happening*.
|
|
2375
|
+
|
|
2376
|
+
โ ๏ธ **The opposite defect exists and forcing keys makes it worse.** Everything above is
|
|
2377
|
+
one direction โ *my curve slopes through a plateau the reference holds* โ and its fix is
|
|
2378
|
+
to force both ends as keys. The other direction is *my candidate moves several times
|
|
2379
|
+
what the reference does on a pair the reference barely moves across*, and if you reach
|
|
2380
|
+
for the same fix you will pin the excess in place instead of removing it. **The cause is
|
|
2381
|
+
different**: there the key plan was smoothing away motion the shot has; here the key
|
|
2382
|
+
plan is faithful and what disagrees is the **per-frame residual** โ two neighbouring
|
|
2383
|
+
poses each a little off, in opposite directions, so the *difference* between them is
|
|
2384
|
+
several times either error. Forcing both as keys asks the planner to reproduce exactly
|
|
2385
|
+
the two poses whose disagreement is the problem.
|
|
2386
|
+
|
|
2387
|
+
โญ **Diagnose it before you fix it, with one comparison.** Take the two frames the column
|
|
2388
|
+
flags and ask whether your **poses** at those two frames are each inside your own fitting
|
|
2389
|
+
accuracy. If they are โ and the pair still disagrees โ the defect is the residual and not
|
|
2390
|
+
the plan. Synthetic case, one pair:
|
|
2391
|
+
|
|
2392
|
+
| | reference moves | candidate moves | each pose's own error |
|
|
2393
|
+
| --- | --- | --- | --- |
|
|
2394
|
+
| a quiet pair | 0.8 px | 4.1 px | 1.6 px and 1.7 px, opposite signs |
|
|
2395
|
+
|
|
2396
|
+
Both poses are ordinary; the pair is a five-fold disagreement built out of them.
|
|
2397
|
+
|
|
2398
|
+
โ **The fix has the same shape as the relative floor above: make the smoothing slack
|
|
2399
|
+
relative to the reference's own local change.** Where the reference barely moves,
|
|
2400
|
+
contract your neighbouring poses toward each other until your own frame-to-frame change
|
|
2401
|
+
is inside the band โ accepting a small, *bounded* loss of fidelity on those frames in
|
|
2402
|
+
exchange for the one measure that can see a hold. โ ๏ธ **That is a trade and it is recorded
|
|
2403
|
+
as a trade**: name the frames, name the cost per frame, and say in the log that you took
|
|
2404
|
+
it. A contraction reported as a fit is the same dishonesty as a hold reported as a
|
|
2405
|
+
measurement, and the cost is real โ the frames you contracted are slightly less faithful
|
|
2406
|
+
than they were.
|
|
2407
|
+
|
|
2408
|
+
๐จ **Contract the *planned curves*, not the pose series โ the report never sees the pose
|
|
2409
|
+
series.** This is one sentence and it is worth two builds: the change column measures
|
|
2410
|
+
your **compiled animation sampled at the frames' own rate**, and between your poses and
|
|
2411
|
+
that lie the key reduction and the curves. Contract before the reduction and you have
|
|
2412
|
+
adjusted a series nothing downstream reads โ the planner then re-fits its spans through
|
|
2413
|
+
the adjusted poses, the interpolants land where the tolerance allows, and the pair you
|
|
2414
|
+
were aiming at comes back out of band having *moved*. โ **Apply the contraction where
|
|
2415
|
+
the measurement is taken**: plan the keys, sample the planned curves, find the offending
|
|
2416
|
+
pairs, and contract *those samples* by forcing or moving the keys that produce them โ
|
|
2417
|
+
then re-plan and re-sample. That is the closing loop below, and its subject is the curve
|
|
2418
|
+
series throughout.
|
|
2419
|
+
|
|
2420
|
+
โ ๏ธ **And aim *inside* the band, not at it.** `check`'s thresholds are exact and stated in
|
|
2421
|
+
[`src/check.ts`](../src/check.ts), so it is tempting to converge until every pair is
|
|
2422
|
+
just inside. But a run measuring its own change renders in **its own framing**, and the
|
|
2423
|
+
report renders in the one `check` chose โ and a fraction of a percent of scale is worth
|
|
2424
|
+
a few percent of a pixel count. A pair you cleared by a hair in your loop can sit the
|
|
2425
|
+
wrong side of the same threshold in the report, on a difference that is entirely
|
|
2426
|
+
framing. โ Converge to a **margin** โ clear the band by enough that a percent of scale
|
|
2427
|
+
cannot cross it โ and re-read the real report before believing the column.
|
|
2428
|
+
|
|
2429
|
+
๐จ **There is a third direction, and on a busy shot it is the binding one: your candidate
|
|
2430
|
+
moving too *little*.** The two cases above are both *you moved when you should not have*
|
|
2431
|
+
โ a hold that is not held, and excess change on a quiet pair. But `check`'s rule is
|
|
2432
|
+
**two-sided**: it faults a pair when **either** side moves several times the other by
|
|
2433
|
+
more than its pixel floor. So the mirror case is a reference that is genuinely busy and
|
|
2434
|
+
a candidate that reproduces a fraction of it, and nothing in the two paragraphs above
|
|
2435
|
+
names it.
|
|
2436
|
+
|
|
2437
|
+
โญ **The practical form is a floor rather than a ceiling: on every pair the reference
|
|
2438
|
+
moves, yours has to move at least about a quarter as much.** Read the exact multiple and
|
|
2439
|
+
the pixel floor out of [`src/check.ts`](../src/check.ts) rather than trusting the
|
|
2440
|
+
approximation โ but plan against the floor, because it behaves quite differently from
|
|
2441
|
+
the ceiling:
|
|
2442
|
+
|
|
2443
|
+
- **It is not fixed by keys.** Over-change is a planning artefact you can force or
|
|
2444
|
+
contract away. Under-change means the *poses themselves* barely differ, so no key plan
|
|
2445
|
+
recovers it โ the fit has to find more motion before the planner sees any.
|
|
2446
|
+
- **It is where a whole-figure objective fails hardest**, which is why the item below
|
|
2447
|
+
belongs beside it: a passage whose motion is a small part against a large still body
|
|
2448
|
+
contributes almost nothing to a whole-shot score, so a fitter converges happily on a
|
|
2449
|
+
near-static series and every pose looks fine on its own.
|
|
2450
|
+
- โ ๏ธ **And the band will accept a shot that is visibly underplayed.** Clearing the floor
|
|
2451
|
+
at a quarter is not reproducing the motion; it is not *failing* it. A run whose busy
|
|
2452
|
+
passage sits near the floor should say so in the log as a known-weak passage rather than
|
|
2453
|
+
quote the column as if it were a fidelity result โ the column is a **band**, and a band
|
|
2454
|
+
is the widest thing that passes, not the thing you were aiming at.
|
|
2455
|
+
|
|
2456
|
+
โญ **Then stop trusting the floor and close the loop on the frames, because a floor is
|
|
2457
|
+
a heuristic and the column is a measurement.** The floor above cut rung 3's
|
|
2458
|
+
disagreements from three to one and could not reach the last: **sample your own planned
|
|
2459
|
+
curves at the frames' own rate, render them, compare every adjacent pair against the
|
|
2460
|
+
reference's own change, force the offending frames as keys, and re-plan** โ repeating
|
|
2461
|
+
until no pair is out of band. It terminates quickly (that shot needed one extra round
|
|
2462
|
+
on one animation and none on the other), it needs no build, and it is the only part of
|
|
2463
|
+
key planning that is verified rather than argued. โ ๏ธ The band is worth reading before
|
|
2464
|
+
you aim at it: `check` calls a pair a disagreement when one side is **exactly** still
|
|
2465
|
+
and the other is not, or when one side moves **four times** the other **and** at least
|
|
2466
|
+
**24 px** more (`src/check.ts`). So the wide middle of a shot is nearly free and the
|
|
2467
|
+
whole difficulty is the pairs where the reference barely moves โ including, on that
|
|
2468
|
+
rung, one pair it moves a single pixel across, which no MAE and no drift figure in this
|
|
2469
|
+
toolchain can see.
|
|
2470
|
+
|
|
2471
|
+
๐จ **But check that the shot holds at all first, because applying this to one that
|
|
2472
|
+
never does manufactures the defect it prevents.** The rule is about a *shot*, and a
|
|
2473
|
+
snap-to-still step in a planner does not cost nothing when there is nothing to snap:
|
|
2474
|
+
it will find some run of samples inside the fitter's own resolution and flatten it.
|
|
2475
|
+
Rung 4's shot has **no** still span โ not one adjacent pair of its 155 reference
|
|
2476
|
+
frames is pixel-identical, and even its last two differ, because the chain is still
|
|
2477
|
+
settling at the final frame โ and the snap duly flattened the tail and put a
|
|
2478
|
+
*"yours moved 0 px where the reference moved 28"* into the per-frame column: ยง9.2's
|
|
2479
|
+
held-pose defect arriving from the opposite direction. โ Difference every adjacent
|
|
2480
|
+
pair of frames once, before the planner runs. It is one pass over the set, and it
|
|
2481
|
+
tells you whether this paragraph applies to you at all.
|
|
2482
|
+
|
|
1717
2483
|
๐ **Add a key when a curve cannot carry the shape.** *"If a curve is not smooth
|
|
1718
2484
|
enough, it is easily remedied by adding another key"*, and the **Bounce** handle
|
|
1719
2485
|
preset exists for *"changing directions abruptly, such as when a ball bounces"* โ
|
|
@@ -1746,6 +2512,87 @@ by that bone's lever arm. With that, the whole trade reads as one curve and you
|
|
|
1746
2512
|
pick a point on it deliberately: that shot measured 0.6 px โ 259 keys โ 1.619 window
|
|
1747
2513
|
MAE, 0.3 px โ 300 โ 1.402, 0.15 px โ 377 โ 1.305.
|
|
1748
2514
|
|
|
2515
|
+
โ ๏ธ **Compute what a skipped sample costs before you declare that tolerance โ on a
|
|
2516
|
+
fast subject the shot's own speed has already pinned it.** Skipping one sample means
|
|
2517
|
+
spanning it linearly, and the chord through its two neighbours sits at their mean, so
|
|
2518
|
+
the deviation at the sample skipped is **half** the series' second difference there:
|
|
2519
|
+
`|f(nโ1) โ 2ยทf(n) + f(n+1)| / 2`. That is an identity, not an approximation โ check
|
|
2520
|
+
it on `f(n) = nยฒ`, whose second difference is 2: the chord reads `nยฒ + 1` where the
|
|
2521
|
+
curve reads `nยฒ`, and the deviation is 1. So second-difference the fitted series,
|
|
2522
|
+
halve it, and read that number against the tolerance you were about to declare. If it
|
|
2523
|
+
is the larger of the two, the fitter keys nearly every frame, and no tolerance below
|
|
2524
|
+
it changes that โ the key density is then a fact about the subject, not a choice you
|
|
2525
|
+
made. One ladder run is that case: its subject's median frame-to-frame second
|
|
2526
|
+
difference measured 6.4 px, so a span that skips one sample deviates about 3.2 px,
|
|
2527
|
+
ten times the 0.3 px tolerance the run declared, and the tolerance would have to be
|
|
2528
|
+
loosened past that 3.2 px before a span could afford to skip anything at all. What
|
|
2529
|
+
the trade bought there was accuracy and never sparsity โ 0.6 px โ 439 keys โ 18.22
|
|
2530
|
+
MAE at 12 fps, 0.45 px โ 482 โ 17.54, 0.3 px โ 521 โ 17.26. โ Do the arithmetic
|
|
2531
|
+
first. It tells you which of the two situations you are in: picking a point on the
|
|
2532
|
+
curve above, or discovering the point the shot has already put you on โ and the
|
|
2533
|
+
second one is not a failure to reach the density this section asks for, it is what
|
|
2534
|
+
that density is here.
|
|
2535
|
+
|
|
2536
|
+
๐จ **There is a third situation, and it is the one where nothing in the loop can
|
|
2537
|
+
help you: a tolerance under the accuracy of whatever produced the series.** The
|
|
2538
|
+
arithmetic above is about the *subject*; this is about the *estimator*. A fitted
|
|
2539
|
+
series is only as good as its objective's basin, and a tolerance below that width
|
|
2540
|
+
buys keys that encode the fitter's wander โ and **`check` cannot see that it
|
|
2541
|
+
happened**, because two candidates that are both inside tolerance render the same
|
|
2542
|
+
pixels. `diff`'s `key_counts` sees it and `diff` is the finish line, so a run gets
|
|
2543
|
+
one shot at the number. Rung 4's is the recorded case: it declared 0.28 px, and its
|
|
2544
|
+
own objective's basin on the shortest lever in the chain measured **ยฑ1.5ยฐ**, which
|
|
2545
|
+
is ยฑ0.5 px there โ so the declared figure was under the noise, and it shipped about
|
|
2546
|
+
three times the reference's key count (`key_counts` 421/1339) with every other
|
|
2547
|
+
animation measure at or near 1.000. โ **Measure the basin before you declare the
|
|
2548
|
+
tolerance**, which costs nothing and needs no reference: scan each knob around its
|
|
2549
|
+
converged value and read how far it moves before the objective does. Then declare a
|
|
2550
|
+
tolerance at or above the widest of them, and record both numbers.
|
|
2551
|
+
|
|
2552
|
+
โ๏ธ **Read that last sentence with the rule two paragraphs up, because taken literally
|
|
2553
|
+
the two pull apart โ and the resolution is that the basin is a *per-channel floor*,
|
|
2554
|
+
not a second global declaration.** The tension is real: *declare one tolerance* asks
|
|
2555
|
+
for a single figure in one unit so the density trade reads as one curve, while
|
|
2556
|
+
*declare at or above the widest basin* points at the worst-identified knob in the rig.
|
|
2557
|
+
Those can differ by **an order of magnitude** โ a well-levered channel the objective
|
|
2558
|
+
pins to a fraction of a pixel sitting in the same rig as a part the objective barely
|
|
2559
|
+
sees at all, whose basin is several pixels wide. Take the widest and every good channel
|
|
2560
|
+
is keyed to the worst one's ignorance; take the declared figure alone and the bad
|
|
2561
|
+
channel ships the fitter's wander as data.
|
|
2562
|
+
|
|
2563
|
+
โญ **What decides it: a basin belongs to the estimator that wrote the channel, and a
|
|
2564
|
+
run that fits poses has more than one.** The two rules are answering different
|
|
2565
|
+
questions. *One tolerance* is about the **unit and comparability** of the figure you
|
|
2566
|
+
declare โ that survives untouched. *The basin* is about the **noise under a particular
|
|
2567
|
+
series**, and noise is a property of the estimator on that channel, not of the rig. So:
|
|
2568
|
+
|
|
2569
|
+
> **Declare one tolerance, in pixels at the end of what each bone swings. Then floor it
|
|
2570
|
+
> per channel at that channel's own basin, capped.** Effective tolerance for a channel
|
|
2571
|
+
> = `max(declared, min(that channel's basin, cap))`.
|
|
2572
|
+
|
|
2573
|
+
- **Why per channel** โ the thing the floor protects against is encoding wander, and
|
|
2574
|
+
wander is per channel. A global maximum spends keys nowhere they were needed and
|
|
2575
|
+
removes them nowhere they were wrong.
|
|
2576
|
+
- **Why a cap, and this is the part worth understanding.** The basin bounds **what you
|
|
2577
|
+
know**; the tolerance also bounds **what you render**, and the rendered series is read
|
|
2578
|
+
by `check`'s change column at *zero* slack (ยง9.2). So an uncapped floor lets a
|
|
2579
|
+
badly-identified channel buy a reduction error large enough to show up as motion the
|
|
2580
|
+
reference does not have โ trading a measure nothing reads for one read at zero
|
|
2581
|
+
tolerance, which is the wrong direction. A cap of a pixel or two, declared and
|
|
2582
|
+
recorded, bounds the reduction error whatever the identifiability.
|
|
2583
|
+
- โ **And a channel whose basin exceeds the cap is telling you it is not identified,
|
|
2584
|
+
which is a different problem with a different fix.** The answer there is a **prior** โ
|
|
2585
|
+
regularise the channel toward a smooth trend and say in the log that you did โ not a
|
|
2586
|
+
tolerance wide enough to key it three times and call the result a measurement. โ ๏ธ Such
|
|
2587
|
+
a channel is *partly a prior rather than a measurement*, and a run that does this
|
|
2588
|
+
records which channels and over which passages, exactly as it records a contraction
|
|
2589
|
+
trade below.
|
|
2590
|
+
|
|
2591
|
+
๐ **Record all three numbers**: the declared tolerance, each floored channel's basin,
|
|
2592
|
+
and the cap. `diff`'s `key_counts` is the finish line and a run gets one shot at it, so
|
|
2593
|
+
the arithmetic that produced the density is the only thing that makes the figure
|
|
2594
|
+
readable afterwards.
|
|
2595
|
+
|
|
1749
2596
|
**A rig's parameters are not identified by its pixels โ remove the gauges before you
|
|
1750
2597
|
key.** A bone that carries no attachment is an exact gauge: turn it by ฮด, turn its
|
|
1751
2598
|
children back by ฮด, and **not one pixel changes**. Anything optimising against pixels
|