spine-rigc 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -3
- package/cli.ts +19 -2
- package/docs/AUTHORING.md +894 -47
- package/docs/SPEC_COVERAGE.md +5 -4
- package/package.json +1 -1
- package/src/check.ts +560 -24
- package/src/framing.ts +280 -0
- package/src/render.ts +21 -0
- package/src/slots.ts +102 -4
package/README.md
CHANGED
|
@@ -138,6 +138,21 @@ per animation and per frame:
|
|
|
138
138
|
land in the box `frames.json` records is measured there, exactly, and the rest
|
|
139
139
|
share one fitted framing. `--framing shared` measures every set in the shared one
|
|
140
140
|
— the whole-root behaviour before issue #100, and worth 15–25 MAE on a character.
|
|
141
|
+
A **fitted** framing then gets one last pass that searches whole-pixel offsets
|
|
142
|
+
(±2 px) for the lowest MAE and takes the best one, because a fit registers extent
|
|
143
|
+
and the best fit of two extents is not the best alignment of two pictures — a
|
|
144
|
+
constant pixel is worth up to 30 % of a set's figure (issue #146). The line says
|
|
145
|
+
what it moved and what that was worth, and it says so when the identity won as
|
|
146
|
+
well. A box that is **not** an estimate — `frames.json`'s own, or one you pinned —
|
|
147
|
+
is never moved: there the same search is reported as a finding, because a constant
|
|
148
|
+
pixel inside the right box is the candidate's own figure sitting off, not framing.
|
|
149
|
+
- **The whole shot, against the contact sheet** — a set that ships a couple of
|
|
150
|
+
stills and folds every sampled frame into one `contact.png` (rung 2's do,
|
|
151
|
+
spineboy's `@30fps` sets do) used to be compared on the stills alone, honestly
|
|
152
|
+
reported and empty behind: nothing at all was measured about the frames in
|
|
153
|
+
between. `check` now samples the candidate at the set's own rate and compares it
|
|
154
|
+
against the sheet's own tiles, whose grid it measures off the sheet (issue #36).
|
|
155
|
+
MAE only, and a sheet that is not a grid of those frames is refused by name.
|
|
141
156
|
- **Per-frame change** — how many pixels each side moved since **its own** previous
|
|
142
157
|
frame, compared against each other. It is the only measure here that looks at the
|
|
143
158
|
relation between two frames rather than at one, and it is what catches a held pose
|
|
@@ -146,7 +161,8 @@ per animation and per frame:
|
|
|
146
161
|
- **Per-slot drift** — where each of the candidate's own slots landed against the
|
|
147
162
|
reference frame, in pixels. MAE says *how wrong*; a slot's drift says *which
|
|
148
163
|
part, which way, how far*. Where the reference merged two parts into one blob —
|
|
149
|
-
the trap [AUTHORING §8](docs/AUTHORING.md) opens with
|
|
164
|
+
the trap [AUTHORING §8](docs/AUTHORING.md) opens with, and it counts as merged
|
|
165
|
+
even when one part is most of the blob (issue #37) — the slot is
|
|
150
166
|
template-matched against its own rendered pixels instead, with a confidence; and
|
|
151
167
|
where nothing inside the distance that slot could plausibly have moved matches
|
|
152
168
|
it, the answer is **no match** rather than a number about some other part.
|
|
@@ -175,14 +191,33 @@ same pixels whatever coordinates they were authored in, an invisible transparent
|
|
|
175
191
|
margin cannot move the result, and no single quad corner in a single frame can set
|
|
176
192
|
the scale for a run.
|
|
177
193
|
|
|
178
|
-
There is no pass mark
|
|
194
|
+
There is no pass mark **in the tool**, for the same reason `diff` has none. The
|
|
195
|
+
ladder's pass definition and its thresholds are a document read by a person over
|
|
196
|
+
the whole table — [docs/LADDER.md](docs/LADDER.md)'s *Operating rules* — and not an
|
|
197
|
+
exit code either command could produce.
|
|
179
198
|
|
|
180
199
|
### Benchmark ladder — the rungs, and where they stand
|
|
181
200
|
|
|
201
|
+
🎓 **The ladder is complete, 2026-08-28.** All eight numbered rungs and the
|
|
202
|
+
spineboy graduation exam are cleared under gate v2.1, every clause PASS or SKIP:
|
|
203
|
+
worst attributable slot drift **5.55 px** against a 6.0 px bar, and **0 of 124**
|
|
204
|
+
frame-change disagreements. Recompiling the same spec in a different session
|
|
205
|
+
reproduced every field of the measurement record **to the digit**. The rungs stay
|
|
206
|
+
in place as regression gates.
|
|
207
|
+
|
|
208
|
+
⚠️ **What that certifies, stated exactly.** That **the tool, the guide and the
|
|
209
|
+
protocol reach the bar across a bounded series of honest attempts, each residual
|
|
210
|
+
diagnosed and fixed** — spineboy took five, and the last inherited its
|
|
211
|
+
predecessor's specs under the run protocol's inheritance clause. It is **not**
|
|
212
|
+
that an agent authors a spineboy-scale rig from the brief alone in one run: the
|
|
213
|
+
ladder has not demonstrated that, and each row records which of the two it is.
|
|
214
|
+
|
|
182
215
|
**[docs/LADDER.md](docs/LADDER.md) is the live ledger**: the rung order
|
|
183
216
|
(blockers → rung 3 first → 1 · 2 · 4 · 5 → 6 → 8 → 7 → spineboy), what each
|
|
184
217
|
rung gates on, how a rung is scored, the honesty rule that keeps the reference
|
|
185
|
-
export away from the authoring agent,
|
|
218
|
+
export away from the authoring agent, the operating rules — what a pass is, and
|
|
219
|
+
the numbered thresholds of the current gate (**gate v2.1**) that decide one — and a status table. Run
|
|
220
|
+
one with:
|
|
186
221
|
|
|
187
222
|
```bash
|
|
188
223
|
bun cli.ts bench 3 --candidate path/to/candidate/spine
|
|
@@ -567,6 +602,9 @@ for a result, and there is no flag that changes that.
|
|
|
567
602
|
- [docs/LADDER.md](docs/LADDER.md) is the benchmark: the same job, from a brief
|
|
568
603
|
and rendered frames, scored. [docs/PILOT.md](docs/PILOT.md) is how to run an
|
|
569
604
|
agent through it and score what comes back.
|
|
605
|
+
- 🤖 **Handing the authoring to an AI agent?**
|
|
606
|
+
[docs/PROMPTING.md](docs/PROMPTING.md) is the operator's page — the six prompt
|
|
607
|
+
clauses a measured pilot run paid for, and what you can leave unsaid.
|
|
570
608
|
|
|
571
609
|
## Usage
|
|
572
610
|
|
package/cli.ts
CHANGED
|
@@ -523,9 +523,18 @@ function cmdBench(flags: Record<string, string>, positional: string[]): void {
|
|
|
523
523
|
: framing.source === 'declared'
|
|
524
524
|
? `frames.json's own box, the candidate measured into it`
|
|
525
525
|
: `fitted to the candidate's pixels, ${framing.passes} pass(es)${framing.settled ? '' : framing.cycled ? ', cycling' : ', unsettled'}`;
|
|
526
|
+
// The MAE-refined offset belongs on this line rather than only in `check`'s
|
|
527
|
+
// own table: it moved the box every figure below was measured in, so a row
|
|
528
|
+
// that quoted the figures without it would not say what they were measured
|
|
529
|
+
// against — issue #146's own version of the #34 lesson above.
|
|
530
|
+
const r = framing.refinement;
|
|
531
|
+
const refined =
|
|
532
|
+
r === null || !r.applied
|
|
533
|
+
? ''
|
|
534
|
+
: ` MAE-refined ${signed(r.dx)}, ${signed(r.dy)}px (${r.before.toFixed(2)} → ${r.after.toFixed(2)} ref)`;
|
|
526
535
|
console.log(
|
|
527
536
|
` framing fit x${framing.fit.scale.toFixed(6)} rms ${framing.fit.rms.toFixed(2)}px union residual ` +
|
|
528
|
-
`${signed(framing.fit.residualWidth)} x ${signed(framing.fit.residualHeight)}px (${how})`,
|
|
537
|
+
`${signed(framing.fit.residualWidth)} x ${signed(framing.fit.residualHeight)}px (${how})${refined}`,
|
|
529
538
|
);
|
|
530
539
|
}
|
|
531
540
|
for (const anim of check.animations) {
|
|
@@ -551,9 +560,17 @@ function cmdBench(flags: Record<string, string>, positional: string[]): void {
|
|
|
551
560
|
// carried here and not only in `check`'s own table because this is the line a
|
|
552
561
|
// loop reads between builds, and `mean=` has a denominator the candidate can
|
|
553
562
|
// grow — see `FrameCheck.maeReference`.
|
|
563
|
+
// ...and the contact sheet, when the set ships one: a row reading "over 2
|
|
564
|
+
// frame(s)" for a 311-frame shot is the hole issue #36 closed, and the whole
|
|
565
|
+
// -shot figure is the one that says the frames between the stills were seen.
|
|
566
|
+
const sheet =
|
|
567
|
+
anim.sheet === null
|
|
568
|
+
? ''
|
|
569
|
+
: `, sheet ${anim.sheet.compared} tile(s) mean=${anim.sheet.meanMae.toFixed(2)} ` +
|
|
570
|
+
`worst=${anim.sheet.worstMae.toFixed(2)}`;
|
|
554
571
|
console.log(
|
|
555
572
|
` ${anim.dir.padEnd(10)} MAE mean=${anim.meanMae.toFixed(2)} worst=${anim.worstMae.toFixed(2)} ` +
|
|
556
|
-
`ref=${anim.meanMaeReference.toFixed(2)} over ${anim.compared} frame(s) ${drift}${change}${chain}`,
|
|
573
|
+
`ref=${anim.meanMaeReference.toFixed(2)} over ${anim.compared} frame(s) ${drift}${change}${chain}${sheet}`,
|
|
557
574
|
);
|
|
558
575
|
}
|
|
559
576
|
} else {
|