spine-rigc 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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 the slot is
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,19 @@ 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, for the same reason `diff` has none.
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
 
182
201
  **[docs/LADDER.md](docs/LADDER.md) is the live ledger**: the rung order
183
202
  (blockers → rung 3 first → 1 · 2 · 4 · 5 → 6 → 8 → 7 → spineboy), what each
184
203
  rung gates on, how a rung is scored, the honesty rule that keeps the reference
185
- export away from the authoring agent, and a status table. Run one with:
204
+ export away from the authoring agent, the operating rules what a pass is, and
205
+ the numbered thresholds of the current gate (**gate v2**) that decide one — and a status table. Run
206
+ one with:
186
207
 
187
208
  ```bash
188
209
  bun cli.ts bench 3 --candidate path/to/candidate/spine
@@ -567,6 +588,9 @@ for a result, and there is no flag that changes that.
567
588
  - [docs/LADDER.md](docs/LADDER.md) is the benchmark: the same job, from a brief
568
589
  and rendered frames, scored. [docs/PILOT.md](docs/PILOT.md) is how to run an
569
590
  agent through it and score what comes back.
591
+ - 🤖 **Handing the authoring to an AI agent?**
592
+ [docs/PROMPTING.md](docs/PROMPTING.md) is the operator's page — the six prompt
593
+ clauses a measured pilot run paid for, and what you can leave unsaid.
570
594
 
571
595
  ## Usage
572
596
 
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 {