spine-rigc 0.2.1 โ 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +258 -9
- package/cli.ts +57 -6
- package/docs/AUTHORING.md +1006 -50
- package/docs/SPEC_COVERAGE.md +21 -14
- package/package.json +5 -2
- package/src/chains.ts +170 -0
- package/src/check.ts +1555 -98
- package/src/compile.ts +325 -6
- package/src/framing.ts +280 -0
- package/src/ladder.ts +1 -1
- package/src/render.ts +43 -2
- package/src/rig.ts +169 -6
- package/src/slots.ts +102 -4
- package/src/timelines.ts +9 -5
- package/src/types.ts +80 -1
- package/src/validate.ts +192 -2
package/docs/AUTHORING.md
CHANGED
|
@@ -18,13 +18,33 @@ 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
|
-
start measuring rather than after;
|
|
26
|
+
start measuring rather than after; **ยง8.1** if the figure has more joints than you
|
|
27
|
+
can measure one at a time; then **ยง9** for the loop that closes it
|
|
24
28
|
- The conventions an editor user follows without being told โ one image per
|
|
25
29
|
attachment, keying practice, curve kind, draw order โ sourced from Spine's own
|
|
26
30
|
public documentation: **ยง10**
|
|
27
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
|
+
|
|
28
48
|
## The vocabulary is Spine's
|
|
29
49
|
|
|
30
50
|
Wherever rigc has no better abstraction it uses **Spine 4.3's own concept, its own
|
|
@@ -59,6 +79,7 @@ bun cli.ts check \
|
|
|
59
79
|
--frames path/to/reference/frames
|
|
60
80
|
|
|
61
81
|
# read the table โ fix the spec โ build again โ check again
|
|
82
|
+
# โณ read its per-frame column before its MAE โ ยง9.2
|
|
62
83
|
```
|
|
63
84
|
|
|
64
85
|
`build` compiles, round-trips the result through `@esotericsoftware/spine-core`,
|
|
@@ -72,6 +93,18 @@ the animation is the one in the frames, and there is no assertion that could โ
|
|
|
72
93
|
ยง9. The two run in that order because `check` needs artifacts on disk and `build`
|
|
73
94
|
only writes them when the gate is green.
|
|
74
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
|
+
|
|
75
108
|
What the flags mean:
|
|
76
109
|
|
|
77
110
|
| Flag | Meaning |
|
|
@@ -81,7 +114,7 @@ What the flags mean:
|
|
|
81
114
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths are written relative to it |
|
|
82
115
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory) |
|
|
83
116
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
84
|
-
| `--profile` | `spine` = the
|
|
117
|
+
| `--profile` | `spine` = the 20 validity rules ยท `spine-html` = all 34 (**the default**) |
|
|
85
118
|
|
|
86
119
|
Pick the profile deliberately. `spine-html` adds one renderer's policy and one
|
|
87
120
|
project's canvas budget, and those rules fire on perfectly correct Spine data
|
|
@@ -257,6 +290,14 @@ behind it writes literal `x`/`y` instead.
|
|
|
257
290
|
`spec` must be exactly `"rigc-rig/1"`. `name` must be a non-empty string. `bones`
|
|
258
291
|
must be non-empty. `slots` must be present (it may be empty).
|
|
259
292
|
|
|
293
|
+
๐ซ **Every example value below is invented.** Names, coordinates, vertex lists and
|
|
294
|
+
payloads in this guide are written to illustrate a field, never copied out of a
|
|
295
|
+
reference export โ an example lifted from one would be handing an authoring agent an
|
|
296
|
+
answer to the rung it is standing on, which is the rule ยง10.6 states and the honesty
|
|
297
|
+
rule in [LADDER.md](LADDER.md) turns on. If a snippet here matches a reference file,
|
|
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*.)
|
|
300
|
+
|
|
260
301
|
### 3.1 `skeleton` โ the header
|
|
261
302
|
|
|
262
303
|
| Field | Spine meaning | Default |
|
|
@@ -349,9 +390,9 @@ Geometry comes in one of two fields:
|
|
|
349
390
|
|
|
350
391
|
```json
|
|
351
392
|
"weights": [
|
|
352
|
-
[{ "bone": "
|
|
353
|
-
{ "bone": "
|
|
354
|
-
[{ "bone": "
|
|
393
|
+
[{ "bone": "link_a", "x": 40, "y": 0, "weight": 0.25 },
|
|
394
|
+
{ "bone": "link_b", "x": -20, "y": 0, "weight": 0.75 }],
|
|
395
|
+
[{ "bone": "link_b", "x": -60, "y": 12, "weight": 1 }]
|
|
355
396
|
]
|
|
356
397
|
```
|
|
357
398
|
|
|
@@ -393,6 +434,59 @@ they encode a deformation model rather than a table of numbers, which is why the
|
|
|
393
434
|
are code invoked by data. A generator is for a skeleton with **no** manifest; a cut
|
|
394
435
|
that has one invokes the same builders through the manifest's `mesh` block.
|
|
395
436
|
|
|
437
|
+
**Bounding box** ([Spine: bounding boxes](http://esotericsoftware.com/spine-bounding-boxes))
|
|
438
|
+
and **clipping** ([Spine: clipping](http://esotericsoftware.com/spine-clipping))
|
|
439
|
+
attachments โ a polygon, and nothing else.
|
|
440
|
+
|
|
441
|
+
**When you need one:** a *bounding box* is a shape the game hit-tests against โ a
|
|
442
|
+
hurt box, a pick region, a trigger volume โ that follows the skeleton and draws
|
|
443
|
+
nothing. A *clipping* attachment is a **mask**: everything drawn from the slot
|
|
444
|
+
carrying it up to and including `end` is clipped to the polygon, so a window, a
|
|
445
|
+
portal or a wipe is one attachment rather than a second set of art.
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
"hitbox_a": { "hitbox_a": { "type": "boundingbox", "vertexCount": 4,
|
|
449
|
+
"vertices": [-30, -10, 30, -10, 30, 50, -30, 50] } },
|
|
450
|
+
"mask_a": { "mask_a": { "type": "clipping", "end": "box", "vertexCount": 3,
|
|
451
|
+
"vertices": [0, 0, 200, 0, 0, 160],
|
|
452
|
+
"color": "ff00ffff" } }
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Both polygons above are invented โ an axis-aligned rectangle and a right triangle,
|
|
456
|
+
in round numbers, so that nothing here can be mistaken for a shape measured off a
|
|
457
|
+
reference. A real one is measured off your own art (ยง8) or drawn to the volume the
|
|
458
|
+
game needs.
|
|
459
|
+
|
|
460
|
+
| Field | Meaning |
|
|
461
|
+
| --- | --- |
|
|
462
|
+
| `vertexCount` | **required.** How many vertices the polygon has, stated outright โ see the warning below |
|
|
463
|
+
| `vertices` / `weights` | the same two encodings a mesh's geometry uses, with the same by-name default and the same `"boneIndexing": "raw"` opt-in |
|
|
464
|
+
| `color` | `rrggbbaa`; the colour the editor draws the outline in |
|
|
465
|
+
| `end` | clipping only. The **last** slot the clip applies to, by name |
|
|
466
|
+
| `convex`, `inverse` | clipping only, 4.3, both default false |
|
|
467
|
+
|
|
468
|
+
๐จ **`vertexCount` has no parser default and rigc will not infer one.** A mesh gets
|
|
469
|
+
its count from `uvs.length`; a polygon has no uvs, and the parser reads
|
|
470
|
+
`map.vertexCount << 1` as the number of coordinates to expect. With the field
|
|
471
|
+
absent that is `undefined << 1` = **0**, so the coordinate array is decoded as a
|
|
472
|
+
*weighted* run โ bone counts and weights read out of your x/y pairs โ and the
|
|
473
|
+
attachment ends up holding nothing. It loads. Neither type draws a pixel, so
|
|
474
|
+
nothing downstream notices. rigc requires the count and cross-checks it against
|
|
475
|
+
whichever encoding you used; `A33_VERTEX_ATTACHMENT_GEOMETRY` checks it again on
|
|
476
|
+
the artifact.
|
|
477
|
+
|
|
478
|
+
โ ๏ธ **A clipping `end` that names nothing is not an error to Spine.**
|
|
479
|
+
`skeletonData.findSlot` returns `null` on a miss and the parser assigns that null
|
|
480
|
+
without a word, so the clip never ends โ it runs to the bottom of the draw order
|
|
481
|
+
and takes every slot below it out of the frame. rigc refuses a name the rig does
|
|
482
|
+
not declare. Omitting `end` entirely is the format's own way of saying "clip
|
|
483
|
+
everything after this one", and is left alone.
|
|
484
|
+
|
|
485
|
+
๐ซ Under the default `spine-html` profile a clipping attachment is refused by
|
|
486
|
+
`A11_NO_CLIPPING_ATTACHMENTS` โ that renderer skips them silently, so a mask that
|
|
487
|
+
was supposed to hide something would not. It is valid Spine and `--profile spine`
|
|
488
|
+
accepts it; the refusal is policy, not validity.
|
|
489
|
+
|
|
396
490
|
### 3.5 `constraints` โ 4.3's single typed array
|
|
397
491
|
|
|
398
492
|
Spine 4.3 folds every constraint into one `constraints` array with a `type`
|
|
@@ -413,7 +507,38 @@ Field lists are in [`src/rig.ts`](../src/rig.ts); three traps worth carrying her
|
|
|
413
507
|
them parses cleanly and does nothing at all. rigc refuses it up front, and `A23`
|
|
414
508
|
catches it from the other side.
|
|
415
509
|
|
|
416
|
-
### 3.6 `
|
|
510
|
+
### 3.6 `events` โ names the animation can fire
|
|
511
|
+
|
|
512
|
+
**When you need one:** something outside the skeleton has to happen on a
|
|
513
|
+
particular frame โ a footstep sound, a spawn, a hit window opening. Spine
|
|
514
|
+
[events](http://esotericsoftware.com/spine-events) carry no rendering effect at
|
|
515
|
+
all; they are a named signal the game listens for, with an optional payload.
|
|
516
|
+
|
|
517
|
+
The **declaration** lives here, in the rig spec, because the name is structure.
|
|
518
|
+
The **firings** live in the motion spec (ยง4.8), because when they happen is time.
|
|
519
|
+
|
|
520
|
+
```json
|
|
521
|
+
"events": {
|
|
522
|
+
"cue_a": {},
|
|
523
|
+
"cue_b": { "audio": "cue_b.ogg", "volume": 0.8, "string": "line-01" }
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
An object keyed by event name โ the one top-level collection in the format that
|
|
528
|
+
is not an array. Every field is optional and each is the payload a firing
|
|
529
|
+
**inherits** when it does not override it: `int` (0), `float` (0), `string`
|
|
530
|
+
(`""`), `audio` (none), `volume` and `balance`.
|
|
531
|
+
|
|
532
|
+
- **An empty object is the normal case, not a stub.** Most events carry no payload:
|
|
533
|
+
the name *is* the signal, and the declaration exists so the firings in ยง4.8 have
|
|
534
|
+
something to resolve against. Write `{}` and move on.
|
|
535
|
+
- โ ๏ธ `volume` and `balance` are read **only when `audio` is set**. Without an
|
|
536
|
+
audio path the parser drops them without a word, so rigc refuses that pairing
|
|
537
|
+
rather than emitting two numbers no runtime will read.
|
|
538
|
+
- An event that nothing fires is legitimate: a skeleton may declare the vocabulary
|
|
539
|
+
its game listens for and key only some of it in any one animation.
|
|
540
|
+
|
|
541
|
+
### 3.7 `invariants` โ what the artifact cannot say about itself
|
|
417
542
|
|
|
418
543
|
Optional, and only meaningful for rigc's own formations: `meshSlots` and
|
|
419
544
|
`meshTriangles` (the two halves of the mesh budget `A13` measures against),
|
|
@@ -456,6 +581,7 @@ time puts it here.
|
|
|
456
581
|
| `note` | free text |
|
|
457
582
|
| `tracks` | the timelines |
|
|
458
583
|
| `drawOrder` | the draw-order timeline โ ยง4.7. Not a track: it names no target |
|
|
584
|
+
| `events` | the event timeline โ ยง4.8. Not a track, for the same reason |
|
|
459
585
|
|
|
460
586
|
`groups` (`name โ [member, โฆ]`) lets one track target several bones or slots at
|
|
461
587
|
once; `lag` shifts every key of a track, and `stagger` adds a per-member delay in
|
|
@@ -496,6 +622,39 @@ stepped.
|
|
|
496
622
|
Seconds, not frames: nothing requires a key to land on any frame grid, and a
|
|
497
623
|
reference rendered at some rate says nothing about where its keys are. Put keys
|
|
498
624
|
where the motion changes.
|
|
625
|
+
- **Key times are quantised onto a 1e-6 s grid by rounding DOWN, never to
|
|
626
|
+
nearest.** A key time is a position against the sample grid a player will step,
|
|
627
|
+
and the two directions of a half-step error are not the same size. `2/12 s` and
|
|
628
|
+
`5/30 s` are both 0.16666666โฆ; `0.166667` is *larger* than either, so a key
|
|
629
|
+
emitted there is applied at sample **3** of a 12 fps playback and not sample 2 โ
|
|
630
|
+
a whole frame late, with nothing raised. On a **stepped** timeline (an attachment
|
|
631
|
+
timeline always is) that is the wrong picture rather than a slightly wrong value:
|
|
632
|
+
the spineboy run's muzzle flare fired a frame late for exactly this until the
|
|
633
|
+
run's own frame check caught it (issue #99). Rounding down cannot do that; the
|
|
634
|
+
worst it can do is put a key a millionth of a second early, on the sample it was
|
|
635
|
+
written for. โ ๏ธ What this does **not** protect you from is rounding your own
|
|
636
|
+
times before you write them โ write `2/12`, not `0.1667`, and let the compiler
|
|
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.
|
|
499
658
|
- **No key may land past the animation's `duration`.** Nothing that plays the
|
|
500
659
|
animation for the duration it declares ever reaches such a key, so it is a
|
|
501
660
|
compile error โ checked on **every timeline**, not just on the latest key in the
|
|
@@ -578,6 +737,53 @@ the emitted file is the parser's requirement rather than a decision of yours, so
|
|
|
578
737
|
you state the set of moves and rigc writes them in the order the parser needs.
|
|
579
738
|
`A31_DRAW_ORDER_OFFSETS_RESOLVE` checks all four from the other side.
|
|
580
739
|
|
|
740
|
+
### 4.8 `events` โ firing a declared event
|
|
741
|
+
|
|
742
|
+
The second timeline that names no target, and it sits on the animation for the
|
|
743
|
+
same reason `drawOrder` does: 4.3 writes it as `animations.<a>.events`, beside
|
|
744
|
+
`bones` and `slots`.
|
|
745
|
+
|
|
746
|
+
```json
|
|
747
|
+
"animations": {
|
|
748
|
+
"walk": {
|
|
749
|
+
"duration": 1.0666666,
|
|
750
|
+
"loop": true,
|
|
751
|
+
"events": [
|
|
752
|
+
{ "t": 0, "name": "cue_a" },
|
|
753
|
+
{ "t": 0.5333333, "name": "cue_a", "int": 2 }
|
|
754
|
+
],
|
|
755
|
+
"tracks": []
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
- `name` must be a key of the rig spec's `events` block (ยง3.6). A miss is a
|
|
761
|
+
compile error here; in raw JSON the parser **throws** `Event not found` in the
|
|
762
|
+
consumer's process, which is late.
|
|
763
|
+
- `int`, `float` and `string` override the declaration's payload **for this firing
|
|
764
|
+
only**. Omit them and the firing inherits the declared defaults, which is what
|
|
765
|
+
the editor writes.
|
|
766
|
+
- `volume` and `balance` are accepted only on an event that declares `audio`, for
|
|
767
|
+
the same reason as ยง3.6.
|
|
768
|
+
- Event keys are **instantaneous** and carry no `ease` or `curve`.
|
|
769
|
+
- Its last key counts towards the declared duration like any other (R7).
|
|
770
|
+
|
|
771
|
+
rigc refuses three things here, and only the first is loud in the parser:
|
|
772
|
+
|
|
773
|
+
| You wrote | You get |
|
|
774
|
+
| --- | --- |
|
|
775
|
+
| a name the rig spec does not declare | `event "X" is not declared in the rig spec's "events" block; declared: โฆ` |
|
|
776
|
+
| a key time earlier than the key before it | `key times must not go backwards (at t=0.25, after t=0.5)` |
|
|
777
|
+
| `volume`/`balance` on an event with no `audio` | `volume is set but event "X" declares no "audio"` |
|
|
778
|
+
|
|
779
|
+
The ordering rule is **non-decreasing**, not strictly increasing: two different
|
|
780
|
+
events on one frame is an ordinary thing to want, and unlike a value track there
|
|
781
|
+
is no contradiction in it. What is refused is going *backwards* โ
|
|
782
|
+
`readAnimation` fills frame `i` from key `i` in array order and never sorts, so a
|
|
783
|
+
decreasing time builds an `EventTimeline` whose earlier firing is simply
|
|
784
|
+
unreachable, with a perfectly clean load.
|
|
785
|
+
`A32_EVENT_KEYS_RESOLVE` checks all three from the other side.
|
|
786
|
+
|
|
581
787
|
---
|
|
582
788
|
|
|
583
789
|
## 5. Reading a failure
|
|
@@ -609,6 +815,12 @@ the frequent ones, verbatim:
|
|
|
609
815
|
| `animation "A" has two tracks on X.property; merge them into one track` | one timeline per target property |
|
|
610
816
|
| `no stage size: give the rig spec a \`skeleton.width\`/\`skeleton.height\`` | ยง3.1 |
|
|
611
817
|
| `drawOrder at t=โฆ: slot "X" is not one this rig emits` / `is offset twice in one key` / `puts it at N, outside the โฆ emitted slots` | ยง4.7 |
|
|
818
|
+
| `events at t=โฆ: event "X" is not declared in the rig spec's "events" block` | declare it in the rig spec (ยง3.6), or fix the name |
|
|
819
|
+
| `events: key times must not go backwards` | put the firings in time order (ยง4.8) |
|
|
820
|
+
| `events at t=โฆ: volume is set but event "X" declares no "audio"` | drop `volume`/`balance`, or give the event an audio path |
|
|
821
|
+
| `vertexCount is undefined; a polygon needs at least 3 vertices, stated outright` | give the bounding box or clipping attachment a `vertexCount` (ยง3.4) |
|
|
822
|
+
| `vertexCount N wants M unweighted numbers and "vertices" holds K` | fix the count or the array; they decide the encoding between them |
|
|
823
|
+
| `end names slot "X", which this rig does not declare` | fix the clipping attachment's `end`, or add the slot |
|
|
612
824
|
| `bone "X" takes its position from โฆ, which needs a cut manifest` | R8 โ pass `--manifest`, or write literal `x`/`y` |
|
|
613
825
|
|
|
614
826
|
### 5.2 Assertions โ the gate
|
|
@@ -668,6 +880,8 @@ The report prints one line per assertion:
|
|
|
668
880
|
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | the animation drives deeper than the manifest's measured contact depth |
|
|
669
881
|
| `A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | the animation drives past the measured containment ceiling, or scales a bone in the axis subtree |
|
|
670
882
|
| `A31_DRAW_ORDER_OFFSETS_RESOLVE` | both | a draw-order key names a slot the skeleton does not have, offsets one slot twice, puts a slot outside the slots array, or lists its offsets out of slot order (ยง4.7). The only assertion that runs **before** `A00` โ the last of those shapes makes the loader spin rather than return, so the round trip is refused instead of attempted |
|
|
883
|
+
| `A32_EVENT_KEYS_RESOLVE` | both | an event key fires a name the skeleton's `events` block does not declare, sits earlier in time than the key before it, or sets `volume`/`balance` on an event with no `audio` (ยง4.8). **SKIP** when no animation carries an event timeline |
|
|
884
|
+
| `A33_VERTEX_ATTACHMENT_GEOMETRY` | both | a bounding box or clipping polygon whose `vertexCount` is missing or disagrees with its vertex array, a weighted run that decodes to the wrong number of vertices or an out-of-range bone index, or a clipping `end` naming a slot the skeleton does not have (ยง3.4). **SKIP** when the skeleton carries neither type |
|
|
671
885
|
|
|
672
886
|
`both โ` marks a mixed assertion: its validity half always runs and its policy
|
|
673
887
|
clauses are gated by profile.
|
|
@@ -681,9 +895,14 @@ a **`NotImplementedError` naming the field**, because the parser's own behaviour
|
|
|
681
895
|
worse: an unknown attachment `type` returns `null` and the attachment disappears,
|
|
682
896
|
and a constraint entry with an unrecognised `type` matches no case and vanishes.
|
|
683
897
|
|
|
898
|
+
Each is deferred for a stated reason, and the reason is the same one in every row:
|
|
899
|
+
**not one of these types appears anywhere in the benchmark corpus** (SPEC_COVERAGE
|
|
900
|
+
parts 3-1 and 4-2), so none of them is on the ladder's critical path. The message
|
|
901
|
+
says so, because a deferral without its reason is a wall rather than a work item.
|
|
902
|
+
|
|
684
903
|
| You wrote | You get |
|
|
685
904
|
| --- | --- |
|
|
686
|
-
| attachment `type` of `
|
|
905
|
+
| attachment `type` of `point`, `path`, `linkedmesh` | `attachment type "X" is in the Spine 4.3 format and rigc does not emit it yet. Implemented: region, mesh, boundingbox, clipping. point, path and linkedmesh are deliberately deferred: not one of them appears anywhere in the benchmark corpus โฆ` |
|
|
687
906
|
| constraint `type` of `path` or `slider` | `constraint type "X" โฆ Implemented: ik, transform, physics.` |
|
|
688
907
|
| mesh `generator.kind` of `contour` | `the "contour" generator would triangulate a part's own alpha mask, and src/mesh.ts has no triangulator` |
|
|
689
908
|
|
|
@@ -774,10 +993,98 @@ where one part's **interior detail** โ a marking, a highlight, anything not on
|
|
|
774
993
|
outline โ lies inside the other part's area, and see which survives. Then write the
|
|
775
994
|
slots in that order (R4), because there is no other place in the file to say it.
|
|
776
995
|
|
|
996
|
+
**That reads the reference only, so it settles the edges the reference happens to
|
|
997
|
+
show an interior detail on, and no more. There is a second test, and it decides more
|
|
998
|
+
of them: render your own candidate both ways and measure the same feature on both
|
|
999
|
+
sides.** Build the pair, render each back at the frames' own scale, and compare
|
|
1000
|
+
like with like. A part whose unoccluded size you can compute is a ruler โ composite
|
|
1001
|
+
it alone, then read how much of it survives in the reference and how much survives
|
|
1002
|
+
in each build. If a part that measures 110 px on its own reads 100 px in the
|
|
1003
|
+
reference, the build that also reads 100 px is covering it the way the reference
|
|
1004
|
+
does and the build that reads 108 px is not, and that is the order. It costs two
|
|
1005
|
+
builds and it reaches edges the frames never show a marking on: on the ladder a brief
|
|
1006
|
+
has settled a single edge of a chain from interior detail and said outright that the
|
|
1007
|
+
frames did not show the rest, and rendering like-for-like settled three more of them
|
|
1008
|
+
โ worth a measurable drop in window MAE, a convention the gate cannot see and the
|
|
1009
|
+
measures can.
|
|
1010
|
+
|
|
1011
|
+
**Score that comparison over the pixels where the two builds differ. A whole-shot
|
|
1012
|
+
figure is the wrong feature.** Two builds that differ only in slot order are
|
|
1013
|
+
**bit-identical everywhere the two slots do not overlap**, so a whole-shot MAE
|
|
1014
|
+
divides the evidence by the whole figure and by every frame that carries none of
|
|
1015
|
+
it. What survives that division sits inside the objective's own scatter โ real
|
|
1016
|
+
hypotheses and a deliberately reversed control alike land in there, pointing
|
|
1017
|
+
whichever way the noise does, and what has been condemned is the statistic and not
|
|
1018
|
+
the edge. โ Take the pixels where the two renders differ **at all** and score both
|
|
1019
|
+
builds over exactly that set. Nothing outside it can contribute, so the dilution is
|
|
1020
|
+
gone by construction, and the reading needs no knowledge of which parts are involved
|
|
1021
|
+
โ it is the same mechanical test on any structural pair. Read a frame-by-frame tally
|
|
1022
|
+
beside the figure too, because an edge the frames really decide wins shot after shot
|
|
1023
|
+
rather than on a couple of them.
|
|
1024
|
+
|
|
1025
|
+
**Calibrate the band with a control on an edge the brief has already settled by
|
|
1026
|
+
measurement.** Run the same test on that edge, read how far apart the two builds
|
|
1027
|
+
come out over the pixels that decide it, and treat that separation as the scale a
|
|
1028
|
+
real answer is measured against. On the deciding pixels a settled edge separates by
|
|
1029
|
+
a wide margin where the whole-shot figure had it inside its own noise โ which is
|
|
1030
|
+
what lets an edge the frames show no interior detail on stop being unanswerable and
|
|
1031
|
+
start being an edge the null-result rule below has no business firing on.
|
|
1032
|
+
|
|
1033
|
+
โ ๏ธ **A control that fails may be a wrong control โ read the per-frame rows before
|
|
1034
|
+
you condemn the hypothesis.** A control is a **build**, and a build differs from
|
|
1035
|
+
base in everything the change implies, not only in the thing you meant to change:
|
|
1036
|
+
send one part behind another and it goes behind everything drawn between them too,
|
|
1037
|
+
so what you actually ran is one reversed edge plus several asserted ones. The
|
|
1038
|
+
aggregate will not say so, and it can favour the variant while the per-frame rows
|
|
1039
|
+
give base *every one* of the frames that carry most of the deciding pixels. That
|
|
1040
|
+
split โ an aggregate one way, a consistent per-frame tally the other โ is the
|
|
1041
|
+
signature of a control that asserts more than one thing, and reading the aggregate
|
|
1042
|
+
alone condemns an edge the brief settles by measurement. The rule the run protocol
|
|
1043
|
+
carries from the other side is the same one: a control that returns an impossible
|
|
1044
|
+
number has told you something, so read the number rather than the pass or fail.
|
|
1045
|
+
|
|
1046
|
+
โ ๏ธ **A render-back sweep whose spread is inside the objective's own scatter is
|
|
1047
|
+
*no answer*, not a weak one.** Rendering candidates back and keeping the best
|
|
1048
|
+
number is not a draw-order trick โ it is how any structural choice the frames
|
|
1049
|
+
might decide gets decided, a scale, an offset, an attachment kind, an order โ and
|
|
1050
|
+
every one of those sweeps can come back null. **Estimate the objective's own
|
|
1051
|
+
scatter first, or the sweep is not readable at all** โ a spread smaller than that
|
|
1052
|
+
is noise wearing a decimal point. Two orders on one ladder shot came out **0.8 %
|
|
1053
|
+
apart over the whole shot and pointing opposite ways**, and a later run swept
|
|
1054
|
+
**three** structural choices and landed inside that scatter on every one of them.
|
|
1055
|
+
A difference that small is not a quiet vote for the winner; it means the frames do
|
|
1056
|
+
not decide this, and there are two honest ways on:
|
|
1057
|
+
|
|
1058
|
+
- **find a second, independent way to get the number** โ often by measuring the
|
|
1059
|
+
*art* instead of the render, which needs no build at all. Two of those three
|
|
1060
|
+
sweeps were settled that way.
|
|
1061
|
+
- **or ship it on reasoning, and say in the log that is what you did.** The third
|
|
1062
|
+
one was. What makes that honest is the record โ a number that arrived as an
|
|
1063
|
+
argument must not later be read as a measurement.
|
|
1064
|
+
|
|
777
1065
|
And the general form of all three: **when a reading implies a key, look for a second
|
|
778
1066
|
way to get the same number before you author it.** A wrong measurement costs one
|
|
779
1067
|
spurious key; a wrong measurement you believed costs the shape of the whole shot.
|
|
780
1068
|
|
|
1069
|
+
โ ๏ธ **A part that grows about a pivot that is not its own centre reads as a part
|
|
1070
|
+
that moves.** Fit a scale about the region centre when the reference scales it about
|
|
1071
|
+
somewhere else, and the residual stays *plausible* while the fitted centre walks
|
|
1072
|
+
along with the fitted scale โ so the series looks like a translate you measured
|
|
1073
|
+
rather than a pivot you did not model. The tell is that shape and nothing else: **a
|
|
1074
|
+
per-frame centre that moves monotonically with the fitted scale is an unmodelled
|
|
1075
|
+
pivot.** Read it as motion and you author a translate timeline the reference does
|
|
1076
|
+
not have; read it as a pivot and it is an attachment offset (ยง3.4's `x`/`y`) with
|
|
1077
|
+
the bone's own scale carrying both the size and the centre drift โ *one* keyed
|
|
1078
|
+
property, which is also what an editor rig has. Recover the offset by sweeping it
|
|
1079
|
+
against the frames where the part is unoccluded and taking the minimum; the minimum
|
|
1080
|
+
is sharp, and it is a structural constant rather than a per-frame value, so a
|
|
1081
|
+
handful of frames settle it. Measured on a shot with four such parts: MAE **3.13 โ
|
|
1082
|
+
1.95** with not one key value re-measured, and those parts' chains from 10โ13
|
|
1083
|
+
`MAE in it` down to 1.3โ8.8. What made it believable rather than a lucky fit is the
|
|
1084
|
+
second trap's own cross-check โ the four offsets came out the same **fraction of each
|
|
1085
|
+
part's own height**, from four independent fits, which is the quantity that had to
|
|
1086
|
+
agree between them and did.
|
|
1087
|
+
|
|
781
1088
|
**A value is easier to get right than a curve.** The three traps above are all
|
|
782
1089
|
about measuring a *value*, and both ladder runs so far found that the values came
|
|
783
1090
|
out right early: rung 1's key values were exact at every keyframe on the second
|
|
@@ -797,16 +1104,188 @@ linear, stepped or bezier and never compares two handle shapes; `check` measures
|
|
|
797
1104
|
rendered result, so it can tell you a curve is *wrong* without telling you what it
|
|
798
1105
|
should have been. What does not follow is that omitting `ease` abstains. It authors
|
|
799
1106
|
constant speed on every span โ the one shape a hand-animated reference almost never
|
|
800
|
-
has (ยง10.4) โ and the ladder has measured both sides of that bet.
|
|
801
|
-
everything linear for exactly the reasoning above
|
|
802
|
-
its single largest structural gap.
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1107
|
+
has (ยง10.4) โ and the ladder has measured both sides of that bet. A run that keyed
|
|
1108
|
+
everything linear for exactly the reasoning above scored under a **sixteenth** of its
|
|
1109
|
+
`curve_kinds` measure, and that was its single largest structural gap. A second
|
|
1110
|
+
attempt at another rung โ same brief, same frames, same model as its own first, with
|
|
1111
|
+
ยง10.4's rule added and nothing else โ lifted `curve_kinds` by a fifth, with
|
|
1112
|
+
`key_counts` rising beside it and every other section figure unchanged. โ Take the
|
|
1113
|
+
curve *kind* from what the motion does โ starts, stops, accelerates, settles,
|
|
1114
|
+
falls โ rather than from how far apart the keys are; take its
|
|
806
1115
|
shape from ยง10.4's automatic-handle advice and a small reused `easings` table; and
|
|
807
1116
|
leave `check` to catch the one thing no static reading can, an easing applied the
|
|
808
1117
|
wrong way round.
|
|
809
1118
|
|
|
1119
|
+
### 8.1 Getting a pose for a figure with a dozen joints
|
|
1120
|
+
|
|
1121
|
+
Everything above measures a *part*, and ยง9 checks a pose you already have. Between
|
|
1122
|
+
them sits the question neither answers โ where do a dozen bones go on this frame โ
|
|
1123
|
+
and on a character that gap is most of the run. What follows is not Spine's and no
|
|
1124
|
+
public page has an opinion about it, but it decides whether the search converges at
|
|
1125
|
+
all, so it sits here rather than being rediscovered once per figure.
|
|
1126
|
+
|
|
1127
|
+
**Fit the rendered composite, never a part on its own.** The first trap above tells
|
|
1128
|
+
you to measure each part on pixels that can only be that part. On a figure with limbs
|
|
1129
|
+
there are no such pixels: an arm crosses a torso, one leg crosses the other, a held
|
|
1130
|
+
prop is drawn over both, and **every frame is a frame where parts are touching** โ the
|
|
1131
|
+
trap with no way out of it. So the objective is the whole picture. Render your
|
|
1132
|
+
candidate through the same rasteriser that drew the reference, into the frames' own
|
|
1133
|
+
viewport, and minimise the difference over the bones' local transforms; that is ยง8's
|
|
1134
|
+
*"look for a second way to get the same number"* applied to a whole pose at once, and
|
|
1135
|
+
it is the render loop **ยง9.1** sends you into. Read ยง9.1's warning about where a
|
|
1136
|
+
bone's local transform lives *before* you write the first sweep โ a fitter that is
|
|
1137
|
+
posing nothing reports a flat number and looks like a bad objective.
|
|
1138
|
+
|
|
1139
|
+
โ ๏ธ **Fitting one part at a time in sequence is the same mistake wearing a
|
|
1140
|
+
schedule.** A near arm solved against the composite while the far arm is still
|
|
1141
|
+
wrong is being scored on a picture the other arm is spoiling, and the minimum it
|
|
1142
|
+
walks to is not its own. The knobs come down together, coarse first โ the next two
|
|
1143
|
+
rules are how.
|
|
1144
|
+
|
|
1145
|
+
**Compare at a reduced resolution first. At full resolution the objective is flat
|
|
1146
|
+
over the range a joint has to travel.** Sweep one bone alone across the width of the
|
|
1147
|
+
figure against a single frame and watch the number: at full resolution it can wander
|
|
1148
|
+
inside a few percent for the whole sweep with no slope anywhere in it, because a
|
|
1149
|
+
limb fifty pixels from where it belongs overlaps the reference no better than one a
|
|
1150
|
+
hundred pixels away โ both are *no overlap*, and the difference between them is
|
|
1151
|
+
aliasing. A coordinate search sees noise, reports no improvement, and leaves the frame
|
|
1152
|
+
at a pose that shares almost nothing with the picture. โ **Box-average both sides
|
|
1153
|
+
before comparing them and run the search coarse to fine.** The coarsest level places
|
|
1154
|
+
the body, the next the limbs, the last two the pixels. The same sweep at the coarse
|
|
1155
|
+
level has a slope on it, because at that block size the two figures still overlap and
|
|
1156
|
+
the number knows which way to go.
|
|
1157
|
+
|
|
1158
|
+
โ ๏ธ **The coarsest level is for the body and nothing else.** A block big enough to
|
|
1159
|
+
give the whole figure a gradient is a block a shin is one cell of, and one cell
|
|
1160
|
+
cannot say which way a shin points. Place the root and the torso there, then decide
|
|
1161
|
+
each limb at a level whose cells are smaller than the part that level is moving.
|
|
1162
|
+
|
|
1163
|
+
**Scan each knob's whole plausible range. Do not line-search out from where it sits.**
|
|
1164
|
+
A search that steps out from the current value cannot bring an arm 60ยฐ round, because
|
|
1165
|
+
the first step overlaps the reference no better than standing still did โ so it
|
|
1166
|
+
reports no improvement and stops, correctly, on the objective it was given. A figure
|
|
1167
|
+
whose legs have folded under it is that failure with a picture attached: a real local
|
|
1168
|
+
minimum, sat in for as long as you care to iterate. โ For each knob, evaluate the
|
|
1169
|
+
whole range that bone can plausibly take โ a few dozen samples across it, which is
|
|
1170
|
+
nothing beside the frames you are fitting โ take the best, and refine only after
|
|
1171
|
+
that. The cost is linear in the number of knobs, and it is the difference between a
|
|
1172
|
+
fit that converges and a fit that reports success on a folded figure.
|
|
1173
|
+
|
|
1174
|
+
โ ๏ธ **Some knobs only decide together.** A part hanging three rotations below a
|
|
1175
|
+
shoulder is placed by none of them alone: each single-knob scan finds its own best
|
|
1176
|
+
while the part is still nowhere near, because every value of that knob is wrong given
|
|
1177
|
+
the other two. Where a chain ends in something whose position you can actually see โ
|
|
1178
|
+
a hand, a foot, a held prop โ scan the two links above it as a **pair**, over the
|
|
1179
|
+
grid. That is the product of two ranges on a handful of chains, not on every bone.
|
|
1180
|
+
|
|
1181
|
+
โ ๏ธ **Two whole chains can share a minimum, and no paired scan reaches that one.**
|
|
1182
|
+
The case above is two knobs in one chain. The harder one is two *chains* sitting in
|
|
1183
|
+
the same pixels: an arm and the prop it holds lying across the part of the frame the
|
|
1184
|
+
reference fills with the legs. Every leg knob that would carry a leg there finds the
|
|
1185
|
+
pixels already inked and reports no improvement โ correctly, on the objective it was
|
|
1186
|
+
given โ and the frame keeps its limbs in a **different configuration** rather than a
|
|
1187
|
+
slightly wrong one. Pairing cannot help here, because the two knobs are in different
|
|
1188
|
+
chains and pairing every chain with every other is the whole product.
|
|
1189
|
+
|
|
1190
|
+
**What reaches it is cheap: more than one start, screened coarsely.** Assemble a
|
|
1191
|
+
handful of candidate poses for the frame โ the incumbent, the two neighbouring
|
|
1192
|
+
frames' solutions, a few poses spread across the shot, the setup pose โ run all of
|
|
1193
|
+
them through the **coarse levels only**, and take the best two through the full
|
|
1194
|
+
schedule. **Keep the incumbent among the candidates**, so a frame can only improve
|
|
1195
|
+
on what it already had. The cost is a multiple of the coarse pass rather than of the
|
|
1196
|
+
fit, and the neighbour seed below is one start out of that set rather than a rule of
|
|
1197
|
+
its own. โ ๏ธ It also measures how far from converged a single-start fit can be while
|
|
1198
|
+
reporting success: repeat the *identical* search on one frame from different starts
|
|
1199
|
+
and the numbers walk down, step after step. That is not a tolerance being tightened,
|
|
1200
|
+
it is a different basin each time โ so a search that stopped improving is evidence
|
|
1201
|
+
about the start it was given and about nothing else.
|
|
1202
|
+
|
|
1203
|
+
**Cross-shot starts, for a configuration a shot cannot reach from its own frames.**
|
|
1204
|
+
Where every pose in one shot holds the prop low and the reference holds it out
|
|
1205
|
+
level, every start drawn from that shot's own frames is on the wrong side of the
|
|
1206
|
+
same two-chain minimum, and multi-start inside the shot barely moves the number.
|
|
1207
|
+
These shots are states of one character, so a configuration this shot never visits
|
|
1208
|
+
may be sitting in another one โ take the start from there. โ Borrow **only the bones
|
|
1209
|
+
of the chain in question**, never a whole foreign pose: a foreign pose puts the legs
|
|
1210
|
+
where this shot never goes, and the rest of the search then spends itself fighting
|
|
1211
|
+
what the borrow brought with it.
|
|
1212
|
+
|
|
1213
|
+
**Re-fit the setup pose against frames drawn from every shot, not against one.** Every
|
|
1214
|
+
animation is measured from the setup pose, so an error in it is an error in all of
|
|
1215
|
+
them โ and it is exactly the error one frame cannot show you. Fit an attachment's
|
|
1216
|
+
offset against a single frame and that frame's own rotations absorb whatever you got
|
|
1217
|
+
wrong: the picture comes out right, the offset is wrong, and every other shot pays
|
|
1218
|
+
for it. Across a spread it cannot hide, because a wrong offset would have to be
|
|
1219
|
+
absorbed by a *different* rotation in each frame and no one value of the offset does
|
|
1220
|
+
that. โ Fit the setup pose against one clear frame to get near, then re-fit it
|
|
1221
|
+
against a handful of frames drawn from **every** animation at once, and hold it fixed
|
|
1222
|
+
while the per-frame poses are fitted. It is the spread that identifies it โ a
|
|
1223
|
+
sequence of single-frame fits, one per shot, is not the same thing.
|
|
1224
|
+
|
|
1225
|
+
**Seed each frame's search from its neighbour's solution โ as one start among the
|
|
1226
|
+
full-range scans, never instead of them.** Adjacent frames are adjacent poses, so the
|
|
1227
|
+
answer next door is a better first guess than the middle of any range, and it costs one
|
|
1228
|
+
extra evaluation per knob to try it. What it must not do is *replace* the scans: the
|
|
1229
|
+
neighbouring frame's pose is precisely where a line search would have started, and a
|
|
1230
|
+
limb 60ยฐ out in one frame stays 60ยฐ out in the next for the reason the rule above
|
|
1231
|
+
gives โ stepping away from it overlaps no better, so the whole series inherits one
|
|
1232
|
+
frame's local minimum and looks stable while it does. โ Scan the whole range, add the
|
|
1233
|
+
neighbour's value to the starts, take the best of them. Fit outward from a frame you
|
|
1234
|
+
trust in both directions rather than only forward, so a bad frame seeds its neighbour
|
|
1235
|
+
and not every frame behind it.
|
|
1236
|
+
|
|
1237
|
+
โ ๏ธ **Then measure the adjacency drift, because a fit that lost a limb teleports.**
|
|
1238
|
+
Fitted frame by frame with nothing tying the frames together, a leg has two answers
|
|
1239
|
+
wherever the other leg is near it, and no single frame's number prefers the right one:
|
|
1240
|
+
some frames land on the wrong leg, every one of them cheap, and the series jumps back
|
|
1241
|
+
and forth between the two. That defect is invisible in any per-frame figure and loud in
|
|
1242
|
+
the relation between two โ the reading ยง9.2's `ฮpx` and `ref ฮ` columns already make
|
|
1243
|
+
for the whole figure, and the chain table localises: a chain whose worst slot drift on
|
|
1244
|
+
one frame runs many times its own mean across the set did not travel that far, it was
|
|
1245
|
+
lost and refound somewhere else. **A limb that moves much further between two adjacent
|
|
1246
|
+
frames than the reference's own frame-to-frame change is a fit that lost it, not a limb
|
|
1247
|
+
that moved.** Read it per chain rather than per figure โ one leg swapped for its twin
|
|
1248
|
+
is a small share of a whole-frame delta and vanishes into it.
|
|
1249
|
+
|
|
1250
|
+
**Two near-identical parts need one calibrated separator, decided once and pinned.** A
|
|
1251
|
+
front limb and a rear one are often the same drawing twice, differing by a tint or by
|
|
1252
|
+
nothing at all; a search scoring a whole composite cannot tell which of the two it just
|
|
1253
|
+
placed, because exchanging them costs almost nothing on the frames where they overlap.
|
|
1254
|
+
Left to the per-frame fit, that assignment is re-decided on every frame โ the teleport
|
|
1255
|
+
above, arriving by a second route. โ Settle it the way ยง8 settles a draw-order edge:
|
|
1256
|
+
build both hypotheses, render each at the frames' own scale, and compare like with like
|
|
1257
|
+
โ but calibrate on the frames where the two parts are *unambiguous*, the ones where
|
|
1258
|
+
they are far apart or only one of them is drawn, and read the separation there, where
|
|
1259
|
+
it is a real gap rather than a rounding difference. Then **pin the assignment for the
|
|
1260
|
+
run** and let no per-frame search reopen it. โ ๏ธ The same test knows when to stay silent
|
|
1261
|
+
here too: two hypotheses that come out inside the objective's own scatter mean the
|
|
1262
|
+
frames do not decide this, and you ship it on reasoning and say in the log that is what
|
|
1263
|
+
you did.
|
|
1264
|
+
|
|
1265
|
+
**Spend each iteration on the worst chain, and stop re-fitting the ones already at the
|
|
1266
|
+
floor.** ยง9.2's chain table is this loop's work queue: it gives every limb a worst slot
|
|
1267
|
+
drift, an error per pixel, and a share of the set's error. โ Take the next iteration to
|
|
1268
|
+
the worst **per-pixel** chain rather than the largest share โ the share confounds
|
|
1269
|
+
*wrong* with *big*, as ยง9.2 says beside the table, and the chain holding most of a
|
|
1270
|
+
run's error is routinely the one that simply covers most of the figure. Then freeze the
|
|
1271
|
+
chains that have converged. Re-fitting them spends the budget the broken chain needed,
|
|
1272
|
+
and it is not merely wasteful: chains share parents, so a search free to move a
|
|
1273
|
+
converged limb's ancestors will walk it back off the floor to buy a fraction of a point
|
|
1274
|
+
somewhere else. โ ๏ธ **A blank where a drift should be is the loudest row in the table,
|
|
1275
|
+
not a quiet one.** The matcher refuses to name a distance past the part's own size
|
|
1276
|
+
(ยง9.2), so a limb far enough out reports no match rather than a large number โ read
|
|
1277
|
+
that beside a high figure per pixel as the strongest signal the table has.
|
|
1278
|
+
|
|
1279
|
+
**What comes out is a pose per frame, and a pose per frame is not a key.** Two things
|
|
1280
|
+
decide what survives the reduction, and **ยง10.3** states both: declare one tolerance
|
|
1281
|
+
in pixels at the end of what each bone swings rather than a figure in degrees, and
|
|
1282
|
+
deal with the gauges โ the directions the pixels cannot see โ *before* the series
|
|
1283
|
+
becomes keys, because a fitter will have wandered along every one of them; fold the
|
|
1284
|
+
exact ones out and penalise the rest. Then close
|
|
1285
|
+
the loop with **ยง9**: the fit's own number says how near this pose is to this frame,
|
|
1286
|
+
and only `check` says whether the shot is the shot. **ยง9.3** is the list of what even
|
|
1287
|
+
that cannot see.
|
|
1288
|
+
|
|
810
1289
|
---
|
|
811
1290
|
|
|
812
1291
|
## 9. Checking against the frames โ `rigc check`
|
|
@@ -816,25 +1295,29 @@ bun cli.ts check --candidate path/to/spine --frames path/to/reference/frames
|
|
|
816
1295
|
```
|
|
817
1296
|
|
|
818
1297
|
`--frames` takes either a **skeleton root** (the directory holding `frames.json`,
|
|
819
|
-
which checks every animation of that shot
|
|
820
|
-
it. Everything else is optional:
|
|
821
|
-
its skeleton, `--as <name>` when
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
not
|
|
836
|
-
|
|
837
|
-
|
|
1298
|
+
which checks every animation of that shot, framed per set โ see the scope note
|
|
1299
|
+
below) or **one animation directory** inside it. Everything else is optional:
|
|
1300
|
+
`--atlas` when the candidate's atlas is not beside its skeleton, `--as <name>` when
|
|
1301
|
+
your animation is called something the frame directory is not, `--framing shared`
|
|
1302
|
+
to fit one framing across every set instead of one each, `--all-frames` to list
|
|
1303
|
+
every frame instead of the worst by MAE, `--json <out>` for the whole per-frame,
|
|
1304
|
+
per-slot report.
|
|
1305
|
+
|
|
1306
|
+
โญ **A frame set may ship a contact sheet instead of every frame, and the sheet is
|
|
1307
|
+
compared too.** A long shot does not commit 311 near-duplicate PNGs: rung 2's sets
|
|
1308
|
+
ship `f0000.png` and `f0310.png` plus a `contact.png` holding all 311 sampled
|
|
1309
|
+
frames, and spineboy's `@30fps` sets do the same. The frame table still says
|
|
1310
|
+
`frames 2 on disk, candidate samples 311, 2 compared` โ those are the files โ and a
|
|
1311
|
+
**`sheet` line under it** carries the other 309: your candidate sampled at the set's
|
|
1312
|
+
own rate, rendered into the same box the frames above were at the sheet's own scale,
|
|
1313
|
+
and compared tile by tile (issue #36 โ the gap a rung-2 run had already prototyped a
|
|
1314
|
+
comparator for, in its own working directory, because the tool could not see its shot).
|
|
1315
|
+
|
|
1316
|
+
โ ๏ธ **Read it as a series, not as one number** โ ยง9.2. And note what it does not
|
|
1317
|
+
carry: MAE only. The `ฮpx` / `ref ฮ` thresholds are calibrated at frame scale and a
|
|
1318
|
+
tile has a fraction of a frame's pixels, so the per-frame change measure stays on
|
|
1319
|
+
the committed stills, where it reports `no two compared frames are adjacent` and
|
|
1320
|
+
means it.
|
|
838
1321
|
|
|
839
1322
|
`--fps <n>` exists for frame sets that have no `frames.json` beside them, which are
|
|
840
1323
|
sets rendered before the sidecar existed: it gives the rate those frames were
|
|
@@ -885,10 +1368,49 @@ pixels rather than quad corners (issue #34, closed by #39; see ยง9.2). Pin to a
|
|
|
885
1368
|
you can name a reason for, and read the unpinned framing line first when you are not
|
|
886
1369
|
sure whether you have one.
|
|
887
1370
|
|
|
888
|
-
โ ๏ธ **The framing is over the frames you compare
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1371
|
+
โ ๏ธ **The framing is over the frames you compare, and `check` decides it PER SET.**
|
|
1372
|
+
Point it at a skeleton root and each animation directory under it is asked its own
|
|
1373
|
+
question first: *do this set's own drawn pixels land in the box `frames.json`
|
|
1374
|
+
records?* The sets that do are measured in that box, which is exact โ it is not an
|
|
1375
|
+
estimate of where the frames were drawn, it is where they were drawn โ and nothing
|
|
1376
|
+
another set does can move them. The sets that do not are measured in **one shared
|
|
1377
|
+
framing** fitted across every set, printed as the header's `shared box` line, plus
|
|
1378
|
+
their own whole-pixel MAE refinement off it (ยง9.2) โ the fit is shared because more
|
|
1379
|
+
frames condition it better; the constant offset it still leaves is per set, and
|
|
1380
|
+
measured per set. Each set says which it got on its own `framed to` line.
|
|
1381
|
+
|
|
1382
|
+
Why the split falls there, both halves measured on an 8-shot character (147 frames):
|
|
1383
|
+
|
|
1384
|
+
- **Deciding the declared box per set is worth 15โ25 MAE.** Over the union, one shot
|
|
1385
|
+
that is not in the frames' coordinates puts the pooled correction over the
|
|
1386
|
+
one-pixel threshold and the *whole root* falls back to a fit. Per set, the shots
|
|
1387
|
+
that qualify read what pinning by hand reads: `idle` **18.77** where a whole-root
|
|
1388
|
+
run read 41.59, with not one key different (issue #100).
|
|
1389
|
+
- **Fitting per set is worse, so `check` does not.** `fitFraming` registers extent,
|
|
1390
|
+
and extent is not alignment (see the โ ๏ธ in ยง9.2), so on a shot whose silhouette
|
|
1391
|
+
genuinely differs one shot's frames do not constrain the fit enough: `hit` reads
|
|
1392
|
+
**92.36** fitted on its own against 60.59 in the shared fit, and a two-frame stills
|
|
1393
|
+
set reads 101.94 against 42.98. More frames is a better-conditioned fit, so the
|
|
1394
|
+
fallback is deliberately the shared one.
|
|
1395
|
+
|
|
1396
|
+
`--framing shared` measures **every** set in the shared framing, which is what a
|
|
1397
|
+
whole-root run used to do. It answers one question and it is a good one โ *does a
|
|
1398
|
+
single box serve every set?* โ and it is the wrong number to read as one shot's
|
|
1399
|
+
fidelity.
|
|
1400
|
+
|
|
1401
|
+
โ ๏ธ The two are different measurements and their absolute numbers are not comparable.
|
|
1402
|
+
The `scope` line at the top of the report says which one you got. `--viewport` pins
|
|
1403
|
+
one box for the whole run whichever scope you ask for, because a pin is a claim
|
|
1404
|
+
about your candidate's own coordinates and those do not change between shots.
|
|
1405
|
+
|
|
1406
|
+
โ ๏ธ **`--frames <root>/<one-set>` is a third number for a set that cannot take the
|
|
1407
|
+
declared box**, and it is the least constrained of the three: the fit has only that
|
|
1408
|
+
set's frames to work from, where a root run's shared fit has every frame in the
|
|
1409
|
+
skeleton. Measured on the same character, `hit` reads 60.50 at the root and 92.41
|
|
1410
|
+
pointed at its own directory. A set that DOES take the declared box reads the same
|
|
1411
|
+
either way โ that box is not fitted to anything โ so pointing at one directory is
|
|
1412
|
+
exact for a shot you authored in the frames' coordinates and a rough estimate for
|
|
1413
|
+
one you did not.
|
|
892
1414
|
|
|
893
1415
|
### 9.1 Why this exists
|
|
894
1416
|
|
|
@@ -910,6 +1432,58 @@ your run ceasing to be an honest authoring run. It is a loop, in the way `build`
|
|
|
910
1432
|
a loop. `bench` and `diff` against a rung's export are not โ they read the answer,
|
|
911
1433
|
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
912
1434
|
|
|
1435
|
+
๐ **That is also why the MAE figures quoted through this section stay.** Every one of
|
|
1436
|
+
them is a candidate's own reading against rendered frames โ the exam question, not the
|
|
1437
|
+
answer key โ so none of them narrows a reference-side measure, and a guide that censored
|
|
1438
|
+
them would be teaching less for no gain in honesty. The criterion is under *The honesty
|
|
1439
|
+
rule* in [LADDER.md](LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1440
|
+
over a reference's own count, and no such figure appears here.
|
|
1441
|
+
|
|
1442
|
+
๐จ **If you drive the runtime yourself, a bone's local transform lives on
|
|
1443
|
+
`bone.pose`.** A shot whose poses have to be *fitted* rather than read sends you
|
|
1444
|
+
past `check` and into your own render loop over
|
|
1445
|
+
`@esotericsoftware/spine-core` โ that is ยง8's *"look for a second way to get the
|
|
1446
|
+
same number"* applied to a whole pose, and it is a legitimate thing to build โ ยง8.1
|
|
1447
|
+
is how that search is set up so it converges. The
|
|
1448
|
+
first thing it hits is not a subtlety. **spine-core 4.3 keeps a bone's local
|
|
1449
|
+
transform on `bone.pose`, not on the bone**, so `bone.rotation = โฆ` โ or `.x`,
|
|
1450
|
+
`.y`, `.scaleX` โ is neither an error nor a rotation: it adds a property nothing
|
|
1451
|
+
reads, and every frame renders as the setup pose. Write `bone.pose.rotation`.
|
|
1452
|
+
Rung 8 lost a loop to it at a flat 17.3; driven through `bone.pose`, the same
|
|
1453
|
+
poses measured **2.76**.
|
|
1454
|
+
|
|
1455
|
+
๐จ **That trap has a twin one level up: the *setup* transform lives on
|
|
1456
|
+
`bone.data.setupPose`, not on `bone.data`.** `BoneData` extends `PosedData`,
|
|
1457
|
+
which carries the whole setup transform on a `setupPose` object, so
|
|
1458
|
+
`bone.data.rotation` โ or `.x`, `.y`, `.scaleX` โ is `undefined`, and
|
|
1459
|
+
`bone.data.rotation + delta` is `NaN`. Read and write it as
|
|
1460
|
+
`bone.data.setupPose.rotation`. This one is worse to spot than the `bone.pose`
|
|
1461
|
+
trap, because nothing on the path raises: `undefined` propagates to `NaN`, and
|
|
1462
|
+
`NaN` serialises to `null`, so a fit writes `"px": null` into its own placements
|
|
1463
|
+
file, the next build reads those as zero, `validate` is green and `check` runs.
|
|
1464
|
+
โ **A `null` in your own placements dump is the signature of having read
|
|
1465
|
+
`bone.data` directly** โ nothing in this format is ever legitimately null. On
|
|
1466
|
+
spineboy it cost the candidate MAE 13.0 โ 114.6 with a green gate throughout
|
|
1467
|
+
(measured in a spineboy attempt's own loop log, ยง4.1). Note that the two names are not
|
|
1468
|
+
the same thing: `bone.data.setupPose` is
|
|
1469
|
+
the setup transform, while `bone.setupPose()` on a `Bone` is the method that
|
|
1470
|
+
resets `bone.pose` back to it.
|
|
1471
|
+
|
|
1472
|
+
๐จ **A region attachment's own offsets are cached.** `attachment.x`, `.y`,
|
|
1473
|
+
`.rotation`, `.scaleX/.scaleY` are inputs to a quad spine-core computes once and
|
|
1474
|
+
stores; what gets drawn is that stored quad โ `computeWorldVertices` reads
|
|
1475
|
+
`getOffsets(pose)`, never the fields โ so writing them is, again, neither an
|
|
1476
|
+
error nor a move. **Call `attachment.updateSequence()` after every write**, or
|
|
1477
|
+
every frame renders the quad it was loaded with. A setup fit on spineboy ran
|
|
1478
|
+
4,500 renders and reported the same number for all of them before this surfaced
|
|
1479
|
+
(the same loop log, ยง4.2).
|
|
1480
|
+
|
|
1481
|
+
โ **An MAE that is identical across every pose, and that does not move for any
|
|
1482
|
+
parameter you sweep โ a bone's local transform, an attachment's offsets โ is one
|
|
1483
|
+
of these inert writes and not a wrong animation.** The parameter you swept was
|
|
1484
|
+
never read; a wrong rig moves the number, a write to a field nothing reads
|
|
1485
|
+
cannot.
|
|
1486
|
+
|
|
913
1487
|
### 9.2 Reading the table
|
|
914
1488
|
|
|
915
1489
|
```
|
|
@@ -917,14 +1491,18 @@ and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach on
|
|
|
917
1491
|
reference 256x116px 0.117628 px/unit world x[-573.3 .. 1603.0] y[-81.2 .. 908.9] (frames.json)
|
|
918
1492
|
content candidate 234.6x95.5px at (11.3, 11.5) reference 234.7x95.3px at (11.2, 11.7) (union over 86 frame(s))
|
|
919
1493
|
โคท fit x0.999256 offset +0.05, -0.02 px rms 0.42 px over 344 edge(s) union residual -0.27 x +0.17 px aspect -0.30% (derived, 4 pass(es), settled)
|
|
1494
|
+
โญ MAE-refined by -1, +1 px: 54.31 โ 48.47 over the reference's own pixels (10.7% of the figure). โฆ
|
|
920
1495
|
in units candidate 1995.3 x 809.7 reference 1995.3 x 809.9 x0.9999
|
|
921
1496
|
|
|
922
1497
|
โโ heavy โ candidate animation "heavy", 12 fps โโ
|
|
923
1498
|
frames 65 on disk, candidate samples 65, 65 compared
|
|
924
1499
|
MAE mean 23.10 worst 43.36 at f0029 (0..255 over the union alpha; โฆ)
|
|
1500
|
+
โคท over the REFERENCE's own drawn pixels, mean 23.90 โ the union figure compares two builds โฆ
|
|
925
1501
|
slot drift worst 2.1 px "pendulum" at f0029
|
|
926
1502
|
per-frame 1 of 64 adjacent pair(s) change by a different amount than the reference does; worst
|
|
927
1503
|
f0018, yours moved 0 px where the reference moved 374
|
|
1504
|
+
sheet 311 of 311 tile(s) of contact.png at 64x57px in 8 column(s) MAE mean 4.30 worst 4.76 at f0047
|
|
1505
|
+
โคท worst 8: f0047=4.8 f0048=4.7 f0045=4.7 f0039=4.7 f0149=4.7 f0044=4.6 f0046=4.6 f0043=4.6
|
|
928
1506
|
|
|
929
1507
|
the 9 frames worth reading โ worst by MAE, plus every frame whose own change disagrees, in index order
|
|
930
1508
|
frame MAE union px ฮpx ref ฮ worst slot drift how slots note
|
|
@@ -986,6 +1564,12 @@ The lines, in order:
|
|
|
986
1564
|
does not, after the fit. This is the number that says *"something reaches
|
|
987
1565
|
somewhere nothing in the frames does, or is a different size"*, and a warning
|
|
988
1566
|
spells it out past a pixel.
|
|
1567
|
+
- the **MAE-refined** line, which is the last thing that happens to the box and the
|
|
1568
|
+
paragraph below is what it is for. On a **fitted** framing it says what constant
|
|
1569
|
+
whole-pixel offset was taken out and what that was worth (`โญ`), or that the
|
|
1570
|
+
search ran and the identity won. On a box that is not an estimate โ the frames'
|
|
1571
|
+
own, or one you pinned โ it never moves anything, and if it finds a constant there
|
|
1572
|
+
it says so as a **finding about your rig** rather than about the framing.
|
|
989
1573
|
- `in units` โ the same two boxes in world units. The framing absorbs a pure scale
|
|
990
1574
|
on purpose, so this is the only place one shows; it compares only if you measured
|
|
991
1575
|
the shot in the frames' own units.
|
|
@@ -996,10 +1580,31 @@ that is a little large โ the best fit of the two extents is not quite the best
|
|
|
996
1580
|
alignment of the two pictures, and the fit spends a fraction of a pixel absorbing
|
|
997
1581
|
a difference that would have been cheaper to leave alone. Measured floor: about a
|
|
998
1582
|
third of a pixel on the ladder's shots. On most that is invisible; on a small
|
|
999
|
-
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1583
|
+
high-contrast frame it is worth a point or two of MAE โ rung 6 measured five, and
|
|
1584
|
+
on the spineboy sets a **constant** one or two pixels was worth 10โ30 % of the
|
|
1585
|
+
figure (issue #146). This is the floor the frames' own box has no share in, which is
|
|
1586
|
+
why `check` prefers that box whenever your pixels are measured to land in it;
|
|
1587
|
+
`--viewport` is how you stop it in the cases that box does not cover.
|
|
1588
|
+
|
|
1589
|
+
โญ **What a fitted framing now does about it: one final whole-pixel pass.** After
|
|
1590
|
+
the fit settles (or cycles), `check` searches every whole-pixel offset within ยฑ2 px
|
|
1591
|
+
for the lowest MAE over the reference's own drawn pixels and moves the box to the
|
|
1592
|
+
best one, when that is worth at least 1 % of the figure. So a fitted set's numbers
|
|
1593
|
+
are what is left **after** the best constant offset has been removed, rather than a
|
|
1594
|
+
constant offset read as motion โ and the line says which offset and what it bought,
|
|
1595
|
+
in both directions, so nothing is quietly absorbed.
|
|
1596
|
+
|
|
1597
|
+
Two things to know when you read it:
|
|
1598
|
+
|
|
1599
|
+
- โ ๏ธ **A large refinement on a set whose drift is also large is not necessarily
|
|
1600
|
+
framing.** The pass removes the best *constant*, and when one part carries much of
|
|
1601
|
+
the shot's ink a constant can absorb part of that part's own displacement. Read
|
|
1602
|
+
the offset beside the chain table: a big offset with a flat drift table is the
|
|
1603
|
+
fit's floor; a big offset with one limb far out is that limb.
|
|
1604
|
+
- On a box that is not an estimate the pass declines and says why. `frames.json`'s
|
|
1605
|
+
own box is where the frames were drawn, so a constant pixel *there* is your
|
|
1606
|
+
figure sitting a pixel off inside the right box โ a thing to fix, and the report
|
|
1607
|
+
refuses to frame it away. A pinned box is your claim, and nothing overrules it.
|
|
1003
1608
|
|
|
1004
1609
|
**MAE** is the mean absolute RGB difference, 0..255, over the pixels either side
|
|
1005
1610
|
covers โ the *union alpha*. It is not scored against a threshold, any more than a
|
|
@@ -1012,6 +1617,49 @@ because an ad-hoc re-render check naturally computes that one, and on every set
|
|
|
1012
1617
|
measured so far it comes out ten to twenty-five times smaller and correspondingly
|
|
1013
1618
|
blunter.
|
|
1014
1619
|
|
|
1620
|
+
โ ๏ธ **Half of that denominator is yours, so do not optimise against it.** The union
|
|
1621
|
+
is the pixels *either* side drew, and a large, mostly transparent sprite adds many
|
|
1622
|
+
cheap pixels to it โ so the *mean falls* on a candidate that got worse. That is not
|
|
1623
|
+
hypothetical: spineboy-2's muzzle flare walked its own scale to 13x under a fitting
|
|
1624
|
+
loop doing exactly this, and cost every set in that run its framing (issue #119).
|
|
1625
|
+
So the line under the MAE divides the same difference by the pixels the
|
|
1626
|
+
**reference** drew, a denominator nothing you do can grow. Read the union figure to
|
|
1627
|
+
compare two builds of your own rig, where both sides cover about the same ground,
|
|
1628
|
+
and the reference-denominator figure when you are deciding whether a change made
|
|
1629
|
+
the shot better; it is not bounded by 255. A set that draws more than half again as
|
|
1630
|
+
much ink as the reference does gets `โ ๏ธ overdraw` beside those two numbers, with
|
|
1631
|
+
both pixel counts, because at that point the first figure is cheap for a reason
|
|
1632
|
+
that has nothing to do with your keys.
|
|
1633
|
+
|
|
1634
|
+
๐จ **Part of your MAE is the texture, not the animation, and nothing in the report
|
|
1635
|
+
says so.** The reference frames are rendered through the example's **own packed
|
|
1636
|
+
atlas**, and a packed atlas may carry a `scale:` line โ the ladder has one at
|
|
1637
|
+
`scale: 0.5`, whose 745x212 part is packed at 373x106. rigc has no packer (**ยง6**), so
|
|
1638
|
+
a candidate built from the loose PNGs samples a texture at twice that resolution and
|
|
1639
|
+
resamples every edge differently. The pixels are the same shape in the same place; they
|
|
1640
|
+
are filtered from a different source, and the difference lands on the outline of every
|
|
1641
|
+
part in every frame. It is a constant, it is invisible to `content`, `rms` and the
|
|
1642
|
+
`ยฑ2 px` refinement โ a resampling difference is not an offset โ and **no key you write
|
|
1643
|
+
can move it**.
|
|
1644
|
+
|
|
1645
|
+
โ **When the MAE is flat across the whole set and the drift is already at the floor,
|
|
1646
|
+
check the atlas before you look for keys.** The example's `.atlas` is an allowed input
|
|
1647
|
+
in its own right (`bench/runs/README.md`, *What a run may read*, item 4), and one line
|
|
1648
|
+
of it answers the question. To size the floor, re-run `check` once with
|
|
1649
|
+
`--atlas <the example's own .atlas>`: same skeleton, same keys, the reference's own
|
|
1650
|
+
texture. Measured on rung 3, MAE **6.13 / 6.01** with the candidate's own
|
|
1651
|
+
full-resolution atlas against **2.25 / 2.30** with the supplied one โ **two thirds of
|
|
1652
|
+
the figure was the texture**, and the run that did not know it would have spent its
|
|
1653
|
+
whole budget hunting a rig that was already right.
|
|
1654
|
+
|
|
1655
|
+
โ ๏ธ Two things about that diagnostic. It is a **diagnostic and not a better number**:
|
|
1656
|
+
the artifact `bench` validates ships its own atlas, so the first figure is the one
|
|
1657
|
+
that belongs in a run's record and the second is the explanation of where it went.
|
|
1658
|
+
And the coarser texture **loses** resolution the finer one has โ on the same rung a
|
|
1659
|
+
pair the reference moves *one pixel* across stopped being visible at half scale, so
|
|
1660
|
+
the diagnostic run reported a frame-change disagreement the graded run does not have.
|
|
1661
|
+
Read it for the floor, never as the verdict.
|
|
1662
|
+
|
|
1015
1663
|
**`ฮpx` and `ref ฮ`** are the two columns that do **not** compare you against the
|
|
1016
1664
|
reference. They compare each side against **itself one frame earlier**: how many
|
|
1017
1665
|
pixels of your own frame moved since your own previous frame, and the same for the
|
|
@@ -1037,12 +1685,46 @@ line whenever the MAE is flat and something still looks wrong: a flat MAE says t
|
|
|
1037
1685
|
framing and the art agree, and it says nothing at all about whether your shot holds
|
|
1038
1686
|
and blinks where the reference does.
|
|
1039
1687
|
|
|
1688
|
+
โ ๏ธ **Each set is compared against ITSELF, so on a shot committed at two rates a hold
|
|
1689
|
+
can exist in one set and not the other.** The coarse set samples every other frame of
|
|
1690
|
+
the fine one, so a pair the coarse set holds across is a constraint between samples
|
|
1691
|
+
**2k and 2k+2** of the fine series โ and 2k+1 between them is free to move, and does.
|
|
1692
|
+
One ladder shot has a pair whose whole-frame change at the coarse rate is **exactly
|
|
1693
|
+
0** while both fine-rate pairs inside that same span change by 48 px: the subject
|
|
1694
|
+
shifts under three world units and comes back, so the two coarse samples land on the
|
|
1695
|
+
same pose either side of it. So ยง10.3's *key both ends of the hold* has to be applied
|
|
1696
|
+
**at each committed rate separately**, and equal values are not enough โ the two
|
|
1697
|
+
samples have to be **keys**, or a planner reduces through one of them and an
|
|
1698
|
+
interpolant inside its own tolerance is not equality. That cost two builds on that
|
|
1699
|
+
run, the second of them for exactly that reason. โ Measure the frames' own
|
|
1700
|
+
frame-to-frame change **once per committed rate**, and where a rate holds, pin both
|
|
1701
|
+
ends as keys whenever a finer rate moves between them.
|
|
1702
|
+
|
|
1040
1703
|
โ ๏ธ Only between **adjacent** frames. A set that ships stills rather than every frame
|
|
1041
1704
|
โ rung 2's contact-sheet sets โ reports `no two compared frames are adjacent`, and
|
|
1042
1705
|
means it: the difference between two frames 310 apart is not a frame-to-frame delta.
|
|
1043
1706
|
A disagreement needs one side to hold *exactly* still while the other moves, or one
|
|
1044
1707
|
side to move four times the other and at least two dozen pixels more; below that the
|
|
1045
|
-
two rasterisations differ by their own last bit and the column says nothing.
|
|
1708
|
+
two rasterisations differ by their own last bit and the column says nothing. Such a
|
|
1709
|
+
set gets the `sheet` line instead, which is MAE over every sampled frame and not a
|
|
1710
|
+
change measure โ the two thresholds above are pixel counts at frame scale, and a
|
|
1711
|
+
tile has a fraction of a frame's pixels.
|
|
1712
|
+
|
|
1713
|
+
**The `sheet` line is the whole shot**, on the sets that commit a couple of stills
|
|
1714
|
+
and fold every sampled frame into one `contact.png`. It says how many tiles were
|
|
1715
|
+
compared out of how many the sheet holds, the grid it measured off the sheet itself,
|
|
1716
|
+
the mean and worst tile, and the worst eight by MAE. Read the **series** rather than
|
|
1717
|
+
the mean, exactly as with the frame table: flat across the shot is framing or art,
|
|
1718
|
+
a spike is timing at that moment โ rung 2's four shots read 4.30โ4.41 flat over
|
|
1719
|
+
1,244 tiles, which is what says their trajectories, ring rates and attachment swaps
|
|
1720
|
+
land where and when they should. Two things to know:
|
|
1721
|
+
|
|
1722
|
+
- it is measured in the **same box** the frame table was, at the sheet's scale. For
|
|
1723
|
+
a stills-plus-sheet set that box was decided on the stills, so a set whose framing
|
|
1724
|
+
is a fit carries that fit into these numbers as well;
|
|
1725
|
+
- a sheet whose dimensions are not a grid of this set's frame count at these frames'
|
|
1726
|
+
aspect is **refused by name** rather than read wrong โ the note names the file, and
|
|
1727
|
+
the answer is to re-render the set.
|
|
1046
1728
|
|
|
1047
1729
|
**Slot drift** is what you act on. For each of your slots, `check` measures where
|
|
1048
1730
|
it landed and how far that is from where the reference put it. That names the part,
|
|
@@ -1052,8 +1734,15 @@ sentence you can take straight back to a key.
|
|
|
1052
1734
|
There are two matchers and the `how` column says which one answered:
|
|
1053
1735
|
|
|
1054
1736
|
- `component` โ your slot sits on a connected component of the reference frame that
|
|
1055
|
-
is its own size
|
|
1056
|
-
the strongest answer available.
|
|
1737
|
+
is its own size **and holds nothing else you drew**. The drift is the distance
|
|
1738
|
+
between the two centroids, and it is the strongest answer available. All three
|
|
1739
|
+
conditions are checked: a blob may not be much bigger than the slot, may not be
|
|
1740
|
+
much wider than its box, and may not contain another of your parts' ink. The last
|
|
1741
|
+
is the one a dominant part slips through otherwise โ rung 2's course is 81 % of a
|
|
1742
|
+
blob that also holds the water, the panel and both rings, so the blob is only
|
|
1743
|
+
1.24x its ink and no wider than its box, and the reported *"course drift 11.2 px"*
|
|
1744
|
+
was the distance to a five-part centroid (issue #37). It now falls to the matcher
|
|
1745
|
+
below and reads 0.0 px.
|
|
1057
1746
|
- `tmpl 0.62` โ the reference merged your slot into a neighbour (they touch, or one
|
|
1058
1747
|
is drawn over the other), so the fallback rendered **your slot on its own** and
|
|
1059
1748
|
correlated it against the reference around where you drew it. The number is the
|
|
@@ -1074,6 +1763,39 @@ summary line carries the same denominator. `N reference component(s) no slot
|
|
|
1074
1763
|
reaches` means the reference frame contains something none of your slots overlaps:
|
|
1075
1764
|
a part you have not authored, or one you have put somewhere else entirely.
|
|
1076
1765
|
|
|
1766
|
+
**The `chains` block is the same two measures on the unit you actually repair.**
|
|
1767
|
+
|
|
1768
|
+
```
|
|
1769
|
+
chains 6 from the candidate's own bone tree โ the roster is at the foot of the report
|
|
1770
|
+
chain slots worst slot drift mean MAE in it share
|
|
1771
|
+
crest 5/5 3.0 px "lantern" f0006 2.4 px 28.40 31.5%
|
|
1772
|
+
prop-arm 6/6 2.0 px "prop" f0002 1.5 px 44.90 33.0%
|
|
1773
|
+
near-strut 3/3 15.0 px "strut-tip" f0004 7.0 px 51.30 8.5%
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
`check` cuts **your own** bone tree at every branch point โ a chain runs from a
|
|
1777
|
+
root or a fork down to the next fork; a single-bone chain that is itself a fork
|
|
1778
|
+
folds into its parent, so a `hub` that branches three ways joins the trunk rather
|
|
1779
|
+
than becoming a row of its own; and each chain is named after the first bone in it
|
|
1780
|
+
that carries a slot. On a biped that lands on the parts you would name (`crest`,
|
|
1781
|
+
`near-strut` and `prop-arm` above); on a serial figure with no fork it is one chain,
|
|
1782
|
+
and the slot rows under it still say which link moved. Which bones and slots went
|
|
1783
|
+
where is printed as a roster at the foot of the report, so it is never a guess. `MAE
|
|
1784
|
+
share` divides the difference over the **reference's** own drawn pixels โ the
|
|
1785
|
+
denominator from the line above, which nothing you draw can grow โ and splits it by
|
|
1786
|
+
giving each of those pixels to the chain whose ink is nearest, so the shares
|
|
1787
|
+
partition the set and no chain can look better by drawing more: growing its ink
|
|
1788
|
+
only pulls more of the reference's pixels, and their error, into it. Read `MAE in
|
|
1789
|
+
it` beside the share, because the share alone confounds *wrong* with *big* โ a head
|
|
1790
|
+
and its features cover a lot of a figure and can carry a third of the error at a
|
|
1791
|
+
below-average figure per pixel. Reference ink further from your ink than the part's
|
|
1792
|
+
own size is left `(unattributed)` rather than charged to a neighbour, and a chain
|
|
1793
|
+
reading 0 % on `0/3` slots drawn is missing, not clean. The rollup at the foot gives
|
|
1794
|
+
each chain one line across every set โ the sentence a run's README quotes instead of
|
|
1795
|
+
a per-shot list. **ยง8.1** is how to act on it: the next iteration goes to the worst
|
|
1796
|
+
chain by error per pixel, and a chain already at the floor is frozen rather than
|
|
1797
|
+
re-fitted.
|
|
1798
|
+
|
|
1077
1799
|
### 9.3 What it still cannot see
|
|
1078
1800
|
|
|
1079
1801
|
- **Anything a frame does not contain.** Bone `length`, the setup `inherit` mode,
|
|
@@ -1086,15 +1808,40 @@ a part you have not authored, or one you have put somewhere else entirely.
|
|
|
1086
1808
|
still cannot tell you is *how* a silhouette got its shape: a hull moved by a
|
|
1087
1809
|
bone chain and the same hull moved by deform keys render to the same pixels, and
|
|
1088
1810
|
the frames cannot separate them. Choose on what the rig has to do next, not on
|
|
1089
|
-
what the frames appear to say.
|
|
1811
|
+
what the frames appear to say. โ ๏ธ **But `bench` does see it.** Take a disc that
|
|
1812
|
+
squashes as it travels: a region on a bone that scales, and a grid mesh weighted
|
|
1813
|
+
1.0 to that same bone, draw the same pixels on every frame โ and they are
|
|
1814
|
+
different files. One has a region and one bone; the other has a hull, a
|
|
1815
|
+
triangulation and a weight per vertex, and a rig that meshes one part usually
|
|
1816
|
+
meshes its neighbour and carries the bones to drive them. `attachments.type_counts`,
|
|
1817
|
+
`attachments.mesh_weighted`, `attachments.region_size` and the bone count that
|
|
1818
|
+
comes with the choice all move on that decision, while `animations.deform` moves
|
|
1819
|
+
for neither, because neither one deforms with keys โ the whole difference is which
|
|
1820
|
+
machinery renders identical pixels. That is not an argument for guessing: the
|
|
1821
|
+
frames cannot choose, and this guide will not tell you which way any reference
|
|
1822
|
+
went. It is an argument for **writing down which way you went and why at the
|
|
1823
|
+
moment you decide it**, rather than meeting the decision again in the measures
|
|
1824
|
+
after the run is over.
|
|
1090
1825
|
- **Which of two explanations is right.** A slot 3 px low every frame and a slot
|
|
1091
1826
|
3 px low at one frame have the same drift and opposite causes. The table gives
|
|
1092
1827
|
you the frame index; ยง8's rule still applies โ look for a second way to get the
|
|
1093
1828
|
number before you author the key.
|
|
1094
|
-
- **What happens between two committed frames
|
|
1095
|
-
a set that ships stills has none, so a shot
|
|
1096
|
-
and wrong between them reads clean
|
|
1097
|
-
contact-sheet set already has, and it
|
|
1829
|
+
- **What happens between two committed frames** โ *unless the set ships a sheet.*
|
|
1830
|
+
`ฮpx` compares adjacent frames and a set that ships stills has none, so a shot
|
|
1831
|
+
that is right at every committed frame and wrong between them reads clean in that
|
|
1832
|
+
column. That is the same gap `--frames` on a contact-sheet set already has, and it
|
|
1833
|
+
is why the frame-count line is printed. โญ What closes it is the **`sheet` line**:
|
|
1834
|
+
on a set rendered at a higher rate than the frames on disk, every sampled frame is
|
|
1835
|
+
compared, so the samples between two committed ones are measured there and
|
|
1836
|
+
nowhere else.
|
|
1837
|
+
๐จ **And when you go looking for one of them, do not assume it lies between its
|
|
1838
|
+
neighbours.** A half-frame is not an interpolation โ it is where the shot actually
|
|
1839
|
+
was, and the interesting ones are interesting because it left the interval. Rung
|
|
1840
|
+
4's ball makes contact entirely inside one twelfth of a second: the sheet puts it
|
|
1841
|
+
**40 px below both 12 fps frames that bracket it**, while its x sits within their
|
|
1842
|
+
own range. A search whose reach was scaled from those two frames' own step
|
|
1843
|
+
therefore looked ยฑ9 px for something 40 px away and reported the interpolation
|
|
1844
|
+
back, twice, before the tile was read rather than inferred.
|
|
1098
1845
|
|
|
1099
1846
|
---
|
|
1100
1847
|
|
|
@@ -1143,6 +1890,31 @@ attachment name"* โ [Images](http://esotericsoftware.com/spine-images). โ in
|
|
|
1143
1890
|
keep the placeholder equal to the PNG's basename and no `path` is written (R5). A
|
|
1144
1891
|
`path` in the emitted file means the two disagreed.
|
|
1145
1892
|
|
|
1893
|
+
๐งฉ **โ When the art is named after the parts, the art's names are the rig's names โ
|
|
1894
|
+
and this is the largest lever ยง10 has.** *One image โ one slot โ one attachment,
|
|
1895
|
+
named after the image* reads as a structural rule. It is also, and mostly, a
|
|
1896
|
+
**naming** rule, and naming is what whole sections of the measures are made of:
|
|
1897
|
+
five of `bones`'s eight name-matched measures (`names`, `parent_by_name`, `order`,
|
|
1898
|
+
`length_present`, `inherit_present`) and every `slots` measure but the count are
|
|
1899
|
+
scored over the names the two sides **share** ([`src/diff.ts`](../src/diff.ts)), so
|
|
1900
|
+
a rig whose names miss reads near zero on all of them however well it is built. โ
|
|
1901
|
+
carry each part's own name straight through โ PNG basename โ slot โ attachment, and
|
|
1902
|
+
the bone that moves it โ instead of inventing a scheme of your own.
|
|
1903
|
+
|
|
1904
|
+
**Both directions are measured.** The one run whose art shipped a separate PNG per
|
|
1905
|
+
body part, each named for the part, applied this deliberately and posted `names`
|
|
1906
|
+
measures **an order of magnitude** above anything on the ladder before it โ without
|
|
1907
|
+
either side seeing the other. Every honest run before it read near zero on the same
|
|
1908
|
+
measures. It is the largest single thing any run has got out of a convention.
|
|
1909
|
+
|
|
1910
|
+
โ ๏ธ **And the other half, which matters just as much: when the art is *not* named
|
|
1911
|
+
after the parts, no naming strategy beats any other and the measure is noise.** On a
|
|
1912
|
+
shot whose two PNGs are called things like `square` and `pendulum`, the names carry
|
|
1913
|
+
nothing a rig could inherit, every candidate name is as good as every other, and the
|
|
1914
|
+
name measures are reporting the honesty rule's own price rather than a defect in
|
|
1915
|
+
your rig. Do not spend a loop hunting for better names there, and do not read the
|
|
1916
|
+
low figure as a miss โ say in the log that the art did not carry them.
|
|
1917
|
+
|
|
1146
1918
|
๐ **Housekeeping the format fixes for you.** The default skin *"always has the name
|
|
1147
1919
|
`default`"* and *"bones are ordered so that the parent always comes before a child
|
|
1148
1920
|
bone"* โ [JSON format](http://esotericsoftware.com/spine-json-format). ยง3.4.
|
|
@@ -1200,7 +1972,79 @@ all the time"*, and Clean Up *"deletes all unnecessary keys โฆ keying the same
|
|
|
1200
1972
|
multiple times in a row, keying the same values as the setup pose"*, because *"often
|
|
1201
1973
|
it is convenient to set keys liberally when designing an animation, then use Clean
|
|
1202
1974
|
Up afterward"* โ [Keys](http://esotericsoftware.com/spine-keys). โ a shipped export
|
|
1203
|
-
is dense,
|
|
1975
|
+
is dense, and what it does not carry is a key its own neighbours already imply.
|
|
1976
|
+
|
|
1977
|
+
๐งฉ **โ A hold still needs a key at both ends, and two equal values are not a
|
|
1978
|
+
repeat.** Clean Up's *"keying the same value multiple times in a row"* is about
|
|
1979
|
+
**three or more** โ a run of keys whose interior ones their neighbours imply. Two
|
|
1980
|
+
keys of equal value imply nothing: they are the only way to say *nothing moves here*
|
|
1981
|
+
on an interpolated timeline, and deleting either one ramps the value through the
|
|
1982
|
+
hold. Stillness is a thing a shot does, sometimes for a twelfth of a second and
|
|
1983
|
+
sometimes for nine, and it is authored, not omitted. โ Key the start of a hold and
|
|
1984
|
+
key its end, at the same value; drop the ones in between. This is ยง9.2's *"held pose
|
|
1985
|
+
that is not held"* from the other side, and the same place catches it โ a sloped
|
|
1986
|
+
hold shows up in `check`'s per-frame column and nowhere else, because it is cheap in
|
|
1987
|
+
every single frame and wrong only in the relation between two.
|
|
1988
|
+
|
|
1989
|
+
โ ๏ธ **The key reducer has to key the plateau, because a plateau is neither an end nor
|
|
1990
|
+
a turn.** A planner that forces the series ends and every change of direction โ
|
|
1991
|
+
which is exactly what the rule above asks for โ will still author a slope straight
|
|
1992
|
+
through a run of still frames: not one sample in the middle of a hold is an end or
|
|
1993
|
+
a turning point, and a greedy span stays inside its own per-bone tolerance the whole
|
|
1994
|
+
way across. **A tolerance is not a hold.** Slow motion inside the tolerance is a
|
|
1995
|
+
tolerance question; stillness is a thing the shot *does*, and it survives the
|
|
1996
|
+
reduction only if the reduction is told to keep it. โ Force **both ends of every run
|
|
1997
|
+
of equal values** as keys in their own right โ a third kind of forced index beside
|
|
1998
|
+
the series ends and the turning points โ and test on **exact** equality, so that a
|
|
1999
|
+
merely near-still span is deliberately not swept up with it. This is worth doing
|
|
2000
|
+
before you have any evidence you need it: a run whose *poses* are all right can fail
|
|
2001
|
+
this and see nothing wrong anywhere else, because `validate` has no opinion on it,
|
|
2002
|
+
`diff` never looks at a rendered frame, and an aggregate MAE is cheap in every
|
|
2003
|
+
single frame and wrong only in the relation between two.
|
|
2004
|
+
|
|
2005
|
+
โ ๏ธ **And a tolerance is not a *slow span* either โ a key tolerance needs a relative
|
|
2006
|
+
floor beside its absolute one.** The rule above rescues exact stillness by forcing it,
|
|
2007
|
+
and deliberately leaves near-stillness to the tolerance. But **ยง9.2's per-frame column
|
|
2008
|
+
compares *changes*, not positions**, so on a span the shot barely moves across, an
|
|
2009
|
+
error well inside an absolute tolerance is most of the motion. Measured, on rung 3: the
|
|
2010
|
+
reference moves **0.109 px** between two frames; a greedy span deviated **0.098 px**
|
|
2011
|
+
there, legal under a 0.30 px tolerance and legal again under 0.15 px โ and the column
|
|
2012
|
+
read **259 px against the reference's 40**, a six-fold disagreement authored by a span
|
|
2013
|
+
that was, at every keyframe, exactly right. The same tolerance that is generous on the
|
|
2014
|
+
fast part of the shot is a 90 % error on the slow part, because one figure in pixels
|
|
2015
|
+
cannot be both.
|
|
2016
|
+
|
|
2017
|
+
โ Cap each span's deviation at **the smaller of the absolute tolerance and the
|
|
2018
|
+
smallest single-frame move inside that span**. It is one line in the planner, it costs
|
|
2019
|
+
a handful of keys, and it is the difference between a reduction that is accurate and
|
|
2020
|
+
one that is accurate *in proportion to what is happening*.
|
|
2021
|
+
|
|
2022
|
+
โญ **Then stop trusting the floor and close the loop on the frames, because a floor is
|
|
2023
|
+
a heuristic and the column is a measurement.** The floor above cut rung 3's
|
|
2024
|
+
disagreements from three to one and could not reach the last: **sample your own planned
|
|
2025
|
+
curves at the frames' own rate, render them, compare every adjacent pair against the
|
|
2026
|
+
reference's own change, force the offending frames as keys, and re-plan** โ repeating
|
|
2027
|
+
until no pair is out of band. It terminates quickly (that shot needed one extra round
|
|
2028
|
+
on one animation and none on the other), it needs no build, and it is the only part of
|
|
2029
|
+
key planning that is verified rather than argued. โ ๏ธ The band is worth reading before
|
|
2030
|
+
you aim at it: `check` calls a pair a disagreement when one side is **exactly** still
|
|
2031
|
+
and the other is not, or when one side moves **four times** the other **and** at least
|
|
2032
|
+
**24 px** more (`src/check.ts`). So the wide middle of a shot is nearly free and the
|
|
2033
|
+
whole difficulty is the pairs where the reference barely moves โ including, on that
|
|
2034
|
+
rung, one pair it moves a single pixel across, which no MAE and no drift figure in this
|
|
2035
|
+
toolchain can see.
|
|
2036
|
+
|
|
2037
|
+
๐จ **But check that the shot holds at all first, because applying this to one that
|
|
2038
|
+
never does manufactures the defect it prevents.** The rule is about a *shot*, and a
|
|
2039
|
+
snap-to-still step in a planner does not cost nothing when there is nothing to snap:
|
|
2040
|
+
it will find some run of samples inside the fitter's own resolution and flatten it.
|
|
2041
|
+
Rung 4's shot has **no** still span โ not one adjacent pair of its 155 reference
|
|
2042
|
+
frames is pixel-identical, and even its last two differ, because the chain is still
|
|
2043
|
+
settling at the final frame โ and the snap duly flattened the tail and put a
|
|
2044
|
+
*"yours moved 0 px where the reference moved 28"* into the per-frame column: ยง9.2's
|
|
2045
|
+
held-pose defect arriving from the opposite direction. โ Difference every adjacent
|
|
2046
|
+
pair of frames once, before the planner runs. It is one pass over the set, and it
|
|
2047
|
+
tells you whether this paragraph applies to you at all.
|
|
1204
2048
|
|
|
1205
2049
|
๐ **Add a key when a curve cannot carry the shape.** *"If a curve is not smooth
|
|
1206
2050
|
enough, it is easily remedied by adding another key"*, and the **Bounce** handle
|
|
@@ -1217,6 +2061,89 @@ minimal one-key-per-pose spec produces.
|
|
|
1217
2061
|
one.** Reaching for a target density is guessing. The frames are the only thing that
|
|
1218
2062
|
can say where the motion turns; ยง8 is how to read them.
|
|
1219
2063
|
|
|
2064
|
+
โ ๏ธ **Two rules for a run that *fits* a pose series rather than reading it off the
|
|
2065
|
+
frames.** Neither is Spine's โ no public page has an opinion about a fitter โ but
|
|
2066
|
+
both decide where the keys above actually land, so they sit beside them. Both were
|
|
2067
|
+
paid for on the ladder. (**ยง8.1** is how the series gets fitted in the first place.)
|
|
2068
|
+
|
|
2069
|
+
**A key tolerance on a rotation is not a number of degrees.** The same angular error
|
|
2070
|
+
costs a different number of pixels at every level of a hierarchy, because everything
|
|
2071
|
+
below the bone comes with it. Measured on rung 8's four-link chain: **a quarter of a
|
|
2072
|
+
degree on the last link moves the chain's end 0.15 px, and the same quarter degree
|
|
2073
|
+
on the plate the chain hangs from moves it 0.69 px.** One figure in degrees applied
|
|
2074
|
+
per property therefore keys the far end of the chain roughly four times too loosely
|
|
2075
|
+
while over-keying the near end โ it is not one tolerance at all. โ Declare **one**
|
|
2076
|
+
tolerance, **in pixels at the end of what the bone swings**, and convert it per bone
|
|
2077
|
+
by that bone's lever arm. With that, the whole trade reads as one curve and you can
|
|
2078
|
+
pick a point on it deliberately: that shot measured 0.6 px โ 259 keys โ 1.619 window
|
|
2079
|
+
MAE, 0.3 px โ 300 โ 1.402, 0.15 px โ 377 โ 1.305.
|
|
2080
|
+
|
|
2081
|
+
โ ๏ธ **Compute what a skipped sample costs before you declare that tolerance โ on a
|
|
2082
|
+
fast subject the shot's own speed has already pinned it.** Skipping one sample means
|
|
2083
|
+
spanning it linearly, and the chord through its two neighbours sits at their mean, so
|
|
2084
|
+
the deviation at the sample skipped is **half** the series' second difference there:
|
|
2085
|
+
`|f(nโ1) โ 2ยทf(n) + f(n+1)| / 2`. That is an identity, not an approximation โ check
|
|
2086
|
+
it on `f(n) = nยฒ`, whose second difference is 2: the chord reads `nยฒ + 1` where the
|
|
2087
|
+
curve reads `nยฒ`, and the deviation is 1. So second-difference the fitted series,
|
|
2088
|
+
halve it, and read that number against the tolerance you were about to declare. If it
|
|
2089
|
+
is the larger of the two, the fitter keys nearly every frame, and no tolerance below
|
|
2090
|
+
it changes that โ the key density is then a fact about the subject, not a choice you
|
|
2091
|
+
made. One ladder run is that case: its subject's median frame-to-frame second
|
|
2092
|
+
difference measured 6.4 px, so a span that skips one sample deviates about 3.2 px,
|
|
2093
|
+
ten times the 0.3 px tolerance the run declared, and the tolerance would have to be
|
|
2094
|
+
loosened past that 3.2 px before a span could afford to skip anything at all. What
|
|
2095
|
+
the trade bought there was accuracy and never sparsity โ 0.6 px โ 439 keys โ 18.22
|
|
2096
|
+
MAE at 12 fps, 0.45 px โ 482 โ 17.54, 0.3 px โ 521 โ 17.26. โ Do the arithmetic
|
|
2097
|
+
first. It tells you which of the two situations you are in: picking a point on the
|
|
2098
|
+
curve above, or discovering the point the shot has already put you on โ and the
|
|
2099
|
+
second one is not a failure to reach the density this section asks for, it is what
|
|
2100
|
+
that density is here.
|
|
2101
|
+
|
|
2102
|
+
๐จ **There is a third situation, and it is the one where nothing in the loop can
|
|
2103
|
+
help you: a tolerance under the accuracy of whatever produced the series.** The
|
|
2104
|
+
arithmetic above is about the *subject*; this is about the *estimator*. A fitted
|
|
2105
|
+
series is only as good as its objective's basin, and a tolerance below that width
|
|
2106
|
+
buys keys that encode the fitter's wander โ and **`check` cannot see that it
|
|
2107
|
+
happened**, because two candidates that are both inside tolerance render the same
|
|
2108
|
+
pixels. `diff`'s `key_counts` sees it and `diff` is the finish line, so a run gets
|
|
2109
|
+
one shot at the number. Rung 4's is the recorded case: it declared 0.28 px, and its
|
|
2110
|
+
own objective's basin on the shortest lever in the chain measured **ยฑ1.5ยฐ**, which
|
|
2111
|
+
is ยฑ0.5 px there โ so the declared figure was under the noise, and it shipped about
|
|
2112
|
+
three times the reference's key count (`key_counts` 421/1339) with every other
|
|
2113
|
+
animation measure at or near 1.000. โ **Measure the basin before you declare the
|
|
2114
|
+
tolerance**, which costs nothing and needs no reference: scan each knob around its
|
|
2115
|
+
converged value and read how far it moves before the objective does. Then declare a
|
|
2116
|
+
tolerance at or above the widest of them, and record both numbers.
|
|
2117
|
+
|
|
2118
|
+
**A rig's parameters are not identified by its pixels โ remove the gauges before you
|
|
2119
|
+
key.** A bone that carries no attachment is an exact gauge: turn it by ฮด, turn its
|
|
2120
|
+
children back by ฮด, and **not one pixel changes**. Anything optimising against pixels
|
|
2121
|
+
is free to wander along that direction, and it does. On the spineboy run the figure's
|
|
2122
|
+
topmost body bone carries no art and every moving bone sits under it, and a
|
|
2123
|
+
coordinate descent walked it to **+181ยฐ** against its child's **โ184ยฐ** โ a pose
|
|
2124
|
+
whose picture is right on every frame and whose key series spins the whole figure
|
|
2125
|
+
through a full turn between two of them. The rendered result is correct and the
|
|
2126
|
+
authored rig is nonsense, and no amount of further fitting finds it, because every
|
|
2127
|
+
point on the gauge orbit has identical error. โ Fold each gauge out *before* the
|
|
2128
|
+
series becomes keys โ for a rotation gauge, take the median of the values along the
|
|
2129
|
+
chain and fold it back. The same shape exists wherever a transform is unobservable: a
|
|
2130
|
+
bone with no art, a slot-less parent chain, a uniform scale split across two bones.
|
|
2131
|
+
|
|
2132
|
+
โ ๏ธ **That fold is exact only when the gauge bone's children sit at its own origin** โ
|
|
2133
|
+
and a character's body bone almost never has them there, which is the very shape the
|
|
2134
|
+
paragraph above was written from. Turn the parent by ฮด and a child *at the origin*
|
|
2135
|
+
back by ฮด and the child is where it was; a child sitting 10 units off swings through
|
|
2136
|
+
an arc of that radius first, and the counter-turn only spins it on the spot. The fold
|
|
2137
|
+
moves art, so it **changes the picture**. Measured on the second spineboy run, whose
|
|
2138
|
+
`hip` carries no attachment and has three children 9โ13 units off it: the fold cost
|
|
2139
|
+
**3 MAE on every `idle` frame** โ mean 23.0 with it against 19.9 without, same search
|
|
2140
|
+
โ and it was removed. โ Read the children's offsets before you fold. At the origin,
|
|
2141
|
+
fold: it is exact and it is cheap. Off the origin the degeneracy is still there but
|
|
2142
|
+
it is **soft**, not exact, and a soft degeneracy is *regularised, not folded* โ leave
|
|
2143
|
+
the values alone and add a penalty on the gauge direction to the objective instead.
|
|
2144
|
+
That run used **2e-5 per squared degree** of hip rotation: invisible at animator-sized
|
|
2145
|
+
angles, and still decisive against the +181ยฐ against โ184ยฐ above.
|
|
2146
|
+
|
|
1220
2147
|
### 10.4 Curves
|
|
1221
2148
|
|
|
1222
2149
|
๐ **Linear is what a *new* key gets, and it does not survive contact with a curve.**
|
|
@@ -1250,6 +2177,35 @@ and **Ease in** (*"the value changes more slowly near the next key"*) โ
|
|
|
1250
2177
|
across the file, is how an editor rig reads. Raw `curve` is R6's escape hatch โ one
|
|
1251
2178
|
key needing a shape no other key has โ not the normal way to write a curve.
|
|
1252
2179
|
|
|
2180
|
+
๐จ **The table is a constraint on where the keys go, not a formatting step applied
|
|
2181
|
+
afterwards โ it has to exist while the keys are chosen.** A run that plans its keys
|
|
2182
|
+
by fitting each span's **own** handles, and then writes the nearest entry of a named
|
|
2183
|
+
table, has bought a key count at one tolerance and shipped it at another. Nothing in
|
|
2184
|
+
the loop can see that: the timeline count, the key count, the curve kinds and the
|
|
2185
|
+
duration are all unmoved, so `diff` does not shift and the gate stays green, while
|
|
2186
|
+
the rendered result changes by a multiple. Rung 8's first version did exactly this
|
|
2187
|
+
and went from **1.07 to 4.65 MAE** โ four times its own fit's floor. โ Two passes.
|
|
2188
|
+
Pass A fits freely and exists only to *discover* which shapes the shot uses; those
|
|
2189
|
+
are clustered into the table; pass B re-plans **every** timeline under the table it
|
|
2190
|
+
will actually write. Never fit free handles and substitute the nearest named shape
|
|
2191
|
+
after the fact. (The table's size then trades against the key count at a fixed
|
|
2192
|
+
tolerance โ that shot ran 4 easings/368 keys, 8/314, 12/300, 16/284 โ because a
|
|
2193
|
+
richer table holds more spans.) This is rung 6's clamp defect in another suit:
|
|
2194
|
+
**a constraint that is not enforced where the value is written is not a
|
|
2195
|
+
constraint.**
|
|
2196
|
+
|
|
2197
|
+
๐งฉ **โ A span with no interior sample takes the automatic handles, not linear.** Two
|
|
2198
|
+
keys on adjacent samples leave pass B nothing to fit โ the samples cannot constrain
|
|
2199
|
+
that span's shape at all โ and a planner with nothing to fit leaves `ease` off, which
|
|
2200
|
+
is linear (ยง4.5): the one shape this section says to argue for rather than default to.
|
|
2201
|
+
"No information" is not an argument for constant speed. Take instead the tangents the
|
|
2202
|
+
keys either side imply โ the editor's own **automatic handles**, quoted above โ and
|
|
2203
|
+
snap *those* to the nearest table entry, exactly as you would a fitted span. Rung 8's
|
|
2204
|
+
second version did this for its adjacent pairs and the samples barely moved (**17.34 โ
|
|
2205
|
+
17.26 MAE**) while `curve_kinds` changed wholesale: nil in the frames, large in the
|
|
2206
|
+
structure, which is the signature of a convention rather than a fidelity fix โ and
|
|
2207
|
+
this section's whole subject.
|
|
2208
|
+
|
|
1253
2209
|
๐ **Handles are normalised, and that is the shape an `easings` entry takes.** For a
|
|
1254
2210
|
Bezier key, *"the X axis is from 0 to 1 and represents the percent of time between
|
|
1255
2211
|
the two keyframes. The Y axis is from 0 to 1 and represents the percent of the
|