spine-rigc 0.4.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 +15 -1
- package/docs/AUTHORING.md +478 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -198,11 +198,25 @@ exit code either command could produce.
|
|
|
198
198
|
|
|
199
199
|
### Benchmark ladder โ the rungs, and where they stand
|
|
200
200
|
|
|
201
|
+
๐ **The ladder is complete, 2026-08-28.** All eight numbered rungs and the
|
|
202
|
+
spineboy graduation exam are cleared under gate v2.1, every clause PASS or SKIP:
|
|
203
|
+
worst attributable slot drift **5.55 px** against a 6.0 px bar, and **0 of 124**
|
|
204
|
+
frame-change disagreements. Recompiling the same spec in a different session
|
|
205
|
+
reproduced every field of the measurement record **to the digit**. The rungs stay
|
|
206
|
+
in place as regression gates.
|
|
207
|
+
|
|
208
|
+
โ ๏ธ **What that certifies, stated exactly.** That **the tool, the guide and the
|
|
209
|
+
protocol reach the bar across a bounded series of honest attempts, each residual
|
|
210
|
+
diagnosed and fixed** โ spineboy took five, and the last inherited its
|
|
211
|
+
predecessor's specs under the run protocol's inheritance clause. It is **not**
|
|
212
|
+
that an agent authors a spineboy-scale rig from the brief alone in one run: the
|
|
213
|
+
ladder has not demonstrated that, and each row records which of the two it is.
|
|
214
|
+
|
|
201
215
|
**[docs/LADDER.md](docs/LADDER.md) is the live ledger**: the rung order
|
|
202
216
|
(blockers โ rung 3 first โ 1 ยท 2 ยท 4 ยท 5 โ 6 โ 8 โ 7 โ spineboy), what each
|
|
203
217
|
rung gates on, how a rung is scored, the honesty rule that keeps the reference
|
|
204
218
|
export away from the authoring agent, the operating rules โ what a pass is, and
|
|
205
|
-
the numbered thresholds of the current gate (**gate v2**) that decide one โ and a status table. Run
|
|
219
|
+
the numbered thresholds of the current gate (**gate v2.1**) that decide one โ and a status table. Run
|
|
206
220
|
one with:
|
|
207
221
|
|
|
208
222
|
```bash
|
package/docs/AUTHORING.md
CHANGED
|
@@ -1022,6 +1022,34 @@ gone by construction, and the reading needs no knowledge of which parts are invo
|
|
|
1022
1022
|
beside the figure too, because an edge the frames really decide wins shot after shot
|
|
1023
1023
|
rather than on a couple of them.
|
|
1024
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
|
+
|
|
1025
1053
|
**Calibrate the band with a control on an edge the brief has already settled by
|
|
1026
1054
|
measurement.** Run the same test on that edge, read how far apart the two builds
|
|
1027
1055
|
come out over the pixels that decide it, and treat that separation as the scale a
|
|
@@ -1210,6 +1238,26 @@ of the chain in question**, never a whole foreign pose: a foreign pose puts the
|
|
|
1210
1238
|
where this shot never goes, and the rest of the search then spends itself fighting
|
|
1211
1239
|
what the borrow brought with it.
|
|
1212
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
|
+
|
|
1213
1261
|
**Re-fit the setup pose against frames drawn from every shot, not against one.** Every
|
|
1214
1262
|
animation is measured from the setup pose, so an error in it is an error in all of
|
|
1215
1263
|
them โ and it is exactly the error one frame cannot show you. Fit an attachment's
|
|
@@ -1222,6 +1270,47 @@ against a handful of frames drawn from **every** animation at once, and hold it
|
|
|
1222
1270
|
while the per-frame poses are fitted. It is the spread that identifies it โ a
|
|
1223
1271
|
sequence of single-frame fits, one per shot, is not the same thing.
|
|
1224
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
|
+
|
|
1225
1314
|
**Seed each frame's search from its neighbour's solution โ as one start among the
|
|
1226
1315
|
full-range scans, never instead of them.** Adjacent frames are adjacent poses, so the
|
|
1227
1316
|
answer next door is a better first guess than the middle of any range, and it costs one
|
|
@@ -1276,6 +1365,23 @@ not a quiet one.** The matcher refuses to name a distance past the part's own si
|
|
|
1276
1365
|
(ยง9.2), so a limb far enough out reports no match rather than a large number โ read
|
|
1277
1366
|
that beside a high figure per pixel as the strongest signal the table has.
|
|
1278
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
|
+
|
|
1279
1385
|
**What comes out is a pose per frame, and a pose per frame is not a key.** Two things
|
|
1280
1386
|
decide what survives the reduction, and **ยง10.3** states both: declare one tolerance
|
|
1281
1387
|
in pixels at the end of what each bone swings rather than a figure in degrees, and
|
|
@@ -1319,6 +1425,27 @@ tile has a fraction of a frame's pixels, so the per-frame change measure stays o
|
|
|
1319
1425
|
the committed stills, where it reports `no two compared frames are adjacent` and
|
|
1320
1426
|
means it.
|
|
1321
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.
|
|
1448
|
+
|
|
1322
1449
|
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
1323
1450
|
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
1324
1451
|
sampled at, and without it the 12 fps protocol rate is assumed and the report says
|
|
@@ -1484,6 +1611,145 @@ of these inert writes and not a wrong animation.** The parameter you swept was
|
|
|
1484
1611
|
never read; a wrong rig moves the number, a write to a field nothing reads
|
|
1485
1612
|
cannot.
|
|
1486
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
|
+
|
|
1487
1753
|
### 9.2 Reading the table
|
|
1488
1754
|
|
|
1489
1755
|
```
|
|
@@ -1660,6 +1926,81 @@ pair the reference moves *one pixel* across stopped being visible at half scale,
|
|
|
1660
1926
|
the diagnostic run reported a frame-change disagreement the graded run does not have.
|
|
1661
1927
|
Read it for the floor, never as the verdict.
|
|
1662
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
|
+
|
|
1663
2004
|
**`ฮpx` and `ref ฮ`** are the two columns that do **not** compare you against the
|
|
1664
2005
|
reference. They compare each side against **itself one frame earlier**: how many
|
|
1665
2006
|
pixels of your own frame moved since your own previous frame, and the same for the
|
|
@@ -1796,6 +2137,19 @@ a per-shot list. **ยง8.1** is how to act on it: the next iteration goes to the w
|
|
|
1796
2137
|
chain by error per pixel, and a chain already at the floor is frozen rather than
|
|
1797
2138
|
re-fitted.
|
|
1798
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
|
+
|
|
1799
2153
|
### 9.3 What it still cannot see
|
|
1800
2154
|
|
|
1801
2155
|
- **Anything a frame does not contain.** Bone `length`, the setup `inherit` mode,
|
|
@@ -2019,6 +2373,86 @@ smallest single-frame move inside that span**. It is one line in the planner, it
|
|
|
2019
2373
|
a handful of keys, and it is the difference between a reduction that is accurate and
|
|
2020
2374
|
one that is accurate *in proportion to what is happening*.
|
|
2021
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
|
+
|
|
2022
2456
|
โญ **Then stop trusting the floor and close the loop on the frames, because a floor is
|
|
2023
2457
|
a heuristic and the column is a measurement.** The floor above cut rung 3's
|
|
2024
2458
|
disagreements from three to one and could not reach the last: **sample your own planned
|
|
@@ -2115,6 +2549,50 @@ tolerance**, which costs nothing and needs no reference: scan each knob around i
|
|
|
2115
2549
|
converged value and read how far it moves before the objective does. Then declare a
|
|
2116
2550
|
tolerance at or above the widest of them, and record both numbers.
|
|
2117
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
|
+
|
|
2118
2596
|
**A rig's parameters are not identified by its pixels โ remove the gauges before you
|
|
2119
2597
|
key.** A bone that carries no attachment is an exact gauge: turn it by ฮด, turn its
|
|
2120
2598
|
children back by ฮด, and **not one pixel changes**. Anything optimising against pixels
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Rig compiler for Spine โ declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|