spine-rigc 0.5.0 → 0.7.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/NOTICE.md +21 -0
- package/README.md +409 -229
- package/bin/rigc.cjs +40 -0
- package/cli.ts +916 -56
- package/docs/AUTHORING.md +345 -27
- package/docs/PROMPTING.md +106 -0
- package/docs/SPEC_COVERAGE.md +16 -14
- package/package.json +4 -2
- package/src/ballot.ts +869 -0
- package/src/compile.ts +796 -137
- package/src/emit.ts +88 -0
- package/src/json-position.ts +253 -0
- package/src/png.ts +72 -7
- package/src/preview.ts +243 -0
- package/src/render.ts +58 -0
- package/src/types.ts +171 -0
- package/src/validate.ts +287 -14
- package/tools/plate.ts +164 -21
package/docs/AUTHORING.md
CHANGED
|
@@ -29,22 +29,27 @@ that can see that, and a run that skips it has verified nothing about the motion
|
|
|
29
29
|
attachment, keying practice, curve kind, draw order — sourced from Spine's own
|
|
30
30
|
public documentation: **§10**
|
|
31
31
|
|
|
32
|
+
If you were given no brief and no reference frames, this section does not apply to
|
|
33
|
+
you — skip to **§0**. You are rigging somebody's own art rather than reproducing a
|
|
34
|
+
measured shot, so none of what follows applies: it is the ladder's protocol, not a
|
|
35
|
+
property of the tool.
|
|
36
|
+
|
|
32
37
|
🔒 **A ladder run reads this guide in full and does not follow its references out of
|
|
33
38
|
it.** The guide is allowed reading; not everything it cites is. Citations here are
|
|
34
39
|
provenance for a reader of record — the loop that hit a trap, the issue that closed it —
|
|
35
40
|
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
|
|
41
|
+
or at the **derivation** of the gate a verdict is read against, none of which a run may
|
|
42
|
+
open. ⭐ The gate's **clause statements** are a different matter and a run may read them:
|
|
43
|
+
they are in `docs/GATE.md`, item 11 of the allowed list (owner ruling 2026-08-29) — the
|
|
44
|
+
measure, the comparator, the number and the SKIP semantics, with no recorded figure in it.
|
|
45
|
+
So: read the
|
|
37
46
|
document, take its numbered sections as the input, and leave its footprints to whoever
|
|
38
47
|
is maintaining it. The rule this states is that an **allowed-reading surface has to be
|
|
39
48
|
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
|
|
49
|
+
[LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md), and the enumerated allowed and forbidden lists are in
|
|
41
50
|
`bench/runs/README.md`, *What a run may read* — the prompt that starts a run quotes them
|
|
42
51
|
outright, which is the copy that binds.
|
|
43
52
|
|
|
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
|
-
|
|
48
53
|
## The vocabulary is Spine's
|
|
49
54
|
|
|
50
55
|
Wherever rigc has no better abstraction it uses **Spine 4.3's own concept, its own
|
|
@@ -80,6 +85,15 @@ bun cli.ts check \
|
|
|
80
85
|
|
|
81
86
|
# read the table → fix the spec → build again → check again
|
|
82
87
|
# ↳ read its per-frame column before its MAE — §9.2
|
|
88
|
+
|
|
89
|
+
# …and if nobody gave you frames, LOOK at it instead — neither needs a reference:
|
|
90
|
+
bun cli.ts render --candidate path/to/spine # PNG frames + a contact sheet grid
|
|
91
|
+
bun cli.ts preview --candidate path/to/spine # one .html that plays it in Spine's own player
|
|
92
|
+
|
|
93
|
+
# …and where you have several green candidates and no instrument that separates
|
|
94
|
+
# them, ask a human — the one loop step this toolchain cannot run for you:
|
|
95
|
+
bun cli.ts vote --candidate path/to/spine-a --candidate path/to/spine-b # -> ballot.html
|
|
96
|
+
bun cli.ts vote --record vote-<id>.json # -> votes.jsonl
|
|
83
97
|
```
|
|
84
98
|
|
|
85
99
|
`build` compiles, round-trips the result through `@esotericsoftware/spine-core`,
|
|
@@ -112,16 +126,31 @@ What the flags mean:
|
|
|
112
126
|
| `--rig` | the rig spec — skeleton structure |
|
|
113
127
|
| `--motion` | the motion spec — time |
|
|
114
128
|
| `--out` | directory for `skeleton.json` + `skeleton.atlas`; atlas page paths are written relative to it |
|
|
129
|
+
| `--copy-images` | `build` only: also copies every referenced page PNG into `--out` and rewrites the atlas to the copies, so the directory is self-contained enough to zip or commit on its own. Default is unchanged — page paths still point at the source art (issue #217) |
|
|
115
130
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory) |
|
|
116
131
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
117
|
-
| `--profile` | `spine` = the
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`--
|
|
124
|
-
|
|
132
|
+
| `--profile` | `spine` = the 22 validity rules (**the default**) · `spine-html` = all 36, opt-in |
|
|
133
|
+
| `--candidate` | `check`, `bench`, `render`, `preview` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton. **`vote` is the one command that takes it more than once** — repeat it 2–4 times, one per pane, labelled A, B, C, D in the order given; everywhere else a repeat is a typo and is refused |
|
|
134
|
+
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview`, and for `vote` the first of candidate A. A name the skeleton does not have is refused, with the ones it does have listed — and for `vote`, so is a name that only *some* candidates have |
|
|
135
|
+
| `--record` | `vote` only: a saved vote to check against its ballot and append to the ledger, instead of writing a ballot. This is the command's second mode; it takes no `--candidate` |
|
|
136
|
+
| `--ballot` | `vote --record` only: the ballot the vote answers (default `ballot.html`). Its embedded manifest is what the vote is checked against, so the ballot file is the record of the question |
|
|
137
|
+
| `--ledger` | `vote --record` only: the append-only JSONL the vote lands in (default `votes.jsonl`), one vote per line |
|
|
138
|
+
| `--again` | `vote --record` only: record a second vote on a ballot the ledger already has. Without it a repeat is refused by name rather than doubled |
|
|
139
|
+
|
|
140
|
+
`render` also takes `--fps <n>` (the rate it samples at, default 12 — the same
|
|
141
|
+
protocol rate the reference frames use) and `--max <px>` (the long side of a
|
|
142
|
+
frame, default 256). Three commands take `--out`: a directory for `render`
|
|
143
|
+
(default `render/`), the `.html` file for `preview` (default `preview.html`) and
|
|
144
|
+
for `vote` (default `ballot.html`).
|
|
145
|
+
|
|
146
|
+
Pick the profile deliberately, and know which one you got by saying nothing. The
|
|
147
|
+
default is `spine`: "is this valid Spine 4.3 that any runtime plays correctly",
|
|
148
|
+
which is the question a rig authored anywhere is asking. `--profile spine-html`
|
|
149
|
+
adds one renderer's policy and one project's canvas budget on top, and those
|
|
150
|
+
extra rules fire on perfectly correct Spine data (clipping attachments,
|
|
151
|
+
unweighted meshes, packed atlases) — reach for it when you are shipping into
|
|
152
|
+
*that* project, not to be thorough. A report always prints which profile ran and
|
|
153
|
+
lists what that profile left out, on `PROF` lines.
|
|
125
154
|
|
|
126
155
|
The other commands:
|
|
127
156
|
|
|
@@ -131,6 +160,10 @@ bun cli.ts validate path/to/spine # re-gate artifacts already on
|
|
|
131
160
|
bun cli.ts diff candidate.json reference.json
|
|
132
161
|
bun cli.ts check --candidate path/to/spine --frames path/to/frames
|
|
133
162
|
bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
163
|
+
bun cli.ts render --candidate path/to/spine [--animation …] [--fps 12] [--max 256]
|
|
164
|
+
bun cli.ts preview --candidate path/to/spine [--animation …] [--out preview.html]
|
|
165
|
+
bun cli.ts vote --candidate path/to/a --candidate path/to/b [--out ballot.html]
|
|
166
|
+
bun cli.ts vote --record vote-<id>.json [--ballot ballot.html] [--ledger votes.jsonl]
|
|
134
167
|
```
|
|
135
168
|
|
|
136
169
|
- **`explain`** is the one to reach for when a rig compiles but looks wrong. It
|
|
@@ -144,12 +177,46 @@ bun cli.ts bench 3 --candidate path/to/spine [--frames path/to/frames]
|
|
|
144
177
|
for opposite fixes. A measure with nothing to compare says `0/0` and says so.
|
|
145
178
|
- **`check`** renders your candidate into the reference frames' own pixel grid and
|
|
146
179
|
compares pixels — the only thing here that can see a wrong animation. **§9.**
|
|
147
|
-
- **`bench <rung>`** runs one rung of [the benchmark ladder](LADDER.md): validate
|
|
180
|
+
- **`bench <rung>`** runs one rung of [the benchmark ladder](https://github.com/firejune/rigc/blob/main/docs/LADDER.md): validate
|
|
148
181
|
under `--profile spine`, then diff against that rung's reference export, and with
|
|
149
182
|
`--frames` the `check` table as well. Unlike the three above it is a **finish
|
|
150
183
|
line, not a loop**: it opens the reference export, so a run that consults it and
|
|
151
184
|
then edits is no longer an authoring run. `check` carries no such restriction —
|
|
152
185
|
see §9.
|
|
186
|
+
- 🚨 **`render` and `preview` are how you LOOK at what you built**, and they are
|
|
187
|
+
the two that need no reference at all. Reach for them the moment a rig compiles
|
|
188
|
+
green, because green says nothing about the picture: a head that sits visibly
|
|
189
|
+
off its torso passes every assertion, loads in `spine-core` and steps
|
|
190
|
+
numerically clean — the offsets are the ones you asked for, and nothing in the
|
|
191
|
+
gate can know you did not mean them. `render` writes
|
|
192
|
+
`render/<animation>/f0000.png…` with a `contact.png` grid of **every** frame
|
|
193
|
+
beside them (open that one first — spacing is a comparison across frames) and a
|
|
194
|
+
`frames.json` sidecar naming the world box they are pictures of. `preview`
|
|
195
|
+
writes one self-contained `.html`: your skeleton, atlas and page PNGs are
|
|
196
|
+
embedded in it as data URIs and played by the official **Spine Web Player**, so
|
|
197
|
+
double-clicking it is also the interop proof — what plays there was played by
|
|
198
|
+
Esoteric Software's runtime, not by rigc's. The player is loaded from a CDN
|
|
199
|
+
rather than copied into the file, so the first open needs a network.
|
|
200
|
+
- 🗳️ **`vote` is `preview` with more than one candidate in it and an answer
|
|
201
|
+
coming back**, and it is the one step of this loop you cannot run yourself.
|
|
202
|
+
Reach for it where the instruments have run out: two builds that `check` and
|
|
203
|
+
`diff` cannot separate, a pose fit with two local optima, a key density that is
|
|
204
|
+
a matter of taste, a first draft with no reference at all. **The intended loop
|
|
205
|
+
is: you compile 2–4 candidates that every one of them passes the gate → `rigc
|
|
206
|
+
vote` writes one `ballot.html` → a human opens it, watches the panes loop, and
|
|
207
|
+
picks a winner or declares a tie → they save the small JSON result the page
|
|
208
|
+
hands them → `rigc vote --record <that file>` checks it against the ballot and
|
|
209
|
+
appends one line to `votes.jsonl` → you read the ledger and proceed.**
|
|
210
|
+
Compile first, vote last: never put a candidate on a ballot that did not build
|
|
211
|
+
green, and never ask the human to read JSON, a diff or a spec — the page holds
|
|
212
|
+
playable pixels and nothing else. The panes are labelled `A`/`B`/`C`/`D` and
|
|
213
|
+
carry no paths, so do not describe the candidates to the voter either. What
|
|
214
|
+
comes back is machine-checkable rather than prose: each line names the winner
|
|
215
|
+
by content **digest** (a label means nothing outside one ballot), carries the
|
|
216
|
+
`coverage` set so you can compute what is still unreviewed, and carries a
|
|
217
|
+
reason code from a closed enumeration. A **tie is a recorded answer**, and
|
|
218
|
+
`both-unacceptable` is the one that means *propose again* rather than *adopt
|
|
219
|
+
either* — check for it before you treat a ballot as settled.
|
|
153
220
|
|
|
154
221
|
---
|
|
155
222
|
|
|
@@ -294,7 +361,7 @@ must be non-empty. `slots` must be present (it may be empty).
|
|
|
294
361
|
payloads in this guide are written to illustrate a field, never copied out of a
|
|
295
362
|
reference export — an example lifted from one would be handing an authoring agent an
|
|
296
363
|
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,
|
|
364
|
+
rule in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) turns on. If a snippet here matches a reference file,
|
|
298
365
|
that is a defect in this guide: report it. (It has happened — 2026-08-23; the incident
|
|
299
366
|
is recorded in `bench/runs/README.md`, *What a run may read*.)
|
|
300
367
|
|
|
@@ -482,10 +549,10 @@ and takes every slot below it out of the frame. rigc refuses a name the rig does
|
|
|
482
549
|
not declare. Omitting `end` entirely is the format's own way of saying "clip
|
|
483
550
|
everything after this one", and is left alone.
|
|
484
551
|
|
|
485
|
-
🚫 Under
|
|
552
|
+
🚫 Under `--profile spine-html` a clipping attachment is refused by
|
|
486
553
|
`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
|
|
488
|
-
accepts it; the refusal is policy, not validity.
|
|
554
|
+
was supposed to hide something would not. It is valid Spine and the default
|
|
555
|
+
`--profile spine` accepts it; the refusal is policy, not validity.
|
|
489
556
|
|
|
490
557
|
### 3.5 `constraints` — 4.3's single typed array
|
|
491
558
|
|
|
@@ -582,6 +649,9 @@ time puts it here.
|
|
|
582
649
|
| `tracks` | the timelines |
|
|
583
650
|
| `drawOrder` | the draw-order timeline — §4.7. Not a track: it names no target |
|
|
584
651
|
| `events` | the event timeline — §4.8. Not a track, for the same reason |
|
|
652
|
+
| `ik` | IK constraint timelines — §4.9. Not a track: its keys carry named fields, not one `v` |
|
|
653
|
+
| `transform` | transform constraint timelines — §4.10. Same reason |
|
|
654
|
+
| `deform` | deform timelines — §4.11. Same reason |
|
|
585
655
|
|
|
586
656
|
`groups` (`name → [member, …]`) lets one track target several bones or slots at
|
|
587
657
|
once; `lag` shifts every key of a track, and `stagger` adds a per-member delay in
|
|
@@ -592,6 +662,13 @@ member order.
|
|
|
592
662
|
A track names **exactly one** of `bone`, `slot`, `group`, `physics`. Two tracks on
|
|
593
663
|
the same `target.property` is a compile error: merge them.
|
|
594
664
|
|
|
665
|
+
Three families are **not** tracks and sit beside `tracks` instead — `ik` (§4.9),
|
|
666
|
+
`transform` (§4.10) and `deform` (§4.11). The reason is the key rather than the
|
|
667
|
+
target: a track's key carries one `v`, and those three carry named fields each
|
|
668
|
+
(two for an IK constraint, six for a transform constraint, a sparse vertex run for
|
|
669
|
+
a deform). Folding them in would make `v` mean four different things depending on
|
|
670
|
+
`property`. 4.3 also writes them as their own groups beside `bones` and `slots`.
|
|
671
|
+
|
|
595
672
|
| Target | `property` | Key `v` |
|
|
596
673
|
| --- | --- | --- |
|
|
597
674
|
| `bone` | `translate`, `scale`, `shear` | `[x, y]` |
|
|
@@ -650,7 +727,7 @@ stepped.
|
|
|
650
727
|
declared duration `6.5` never fired at all against an accumulated
|
|
651
728
|
`6.499999999999994` — which read as a frame-change disagreement the pose series had
|
|
652
729
|
already fixed, and cost that run three builds
|
|
653
|
-
([`2026-08-26-rung5-1`](
|
|
730
|
+
([`2026-08-26-rung5-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-26-rung5-1/LOOP.md), §8). ⇒ **For a
|
|
654
731
|
stepped timeline, write `T − 1e-6` rather than `T`.** One grid step early cannot
|
|
655
732
|
reach the previous sample — 83,333 µs away at 12 fps — and is always seen by the
|
|
656
733
|
sample it was written for; one ULP late loses the frame. This is the same asymmetry
|
|
@@ -784,6 +861,232 @@ decreasing time builds an `EventTimeline` whose earlier firing is simply
|
|
|
784
861
|
unreachable, with a perfectly clean load.
|
|
785
862
|
`A32_EVENT_KEYS_RESOLVE` checks all three from the other side.
|
|
786
863
|
|
|
864
|
+
### 4.9 `ik` — mixing an IK constraint in over time
|
|
865
|
+
|
|
866
|
+
The rig spec declares the constraint (§3.5); this keys it. 4.3 writes the group as
|
|
867
|
+
`animations.<a>.ik.<constraint>` — **one unnamed timeline per constraint**, so the
|
|
868
|
+
constraint name is the only target there is, and there is no timeline name between
|
|
869
|
+
the two the way a bone has `rotate` and `translate`.
|
|
870
|
+
|
|
871
|
+
```json
|
|
872
|
+
"animations": {
|
|
873
|
+
"walk": {
|
|
874
|
+
"duration": 0.8666666,
|
|
875
|
+
"loop": true,
|
|
876
|
+
"tracks": [],
|
|
877
|
+
"ik": [
|
|
878
|
+
{
|
|
879
|
+
"constraint": "rear-leg-ik",
|
|
880
|
+
"keys": [
|
|
881
|
+
{ "t": 0, "mix": 1, "softness": 0 },
|
|
882
|
+
{ "t": 0.2666666, "mix": 1, "softness": 14, "ease": "settle" },
|
|
883
|
+
{ "t": 0.5333333, "mix": 0, "softness": 0 },
|
|
884
|
+
{ "t": 0.8666666, "mix": 1, "softness": 0 }
|
|
885
|
+
]
|
|
886
|
+
}
|
|
887
|
+
]
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
⚠️ **Every number above is invented.** They are shaped like a walk cycle — the leg
|
|
893
|
+
under load is solved to the ground and the leg in flight is let go — and nothing
|
|
894
|
+
here was measured off a real rig. Copy the shape, not the values.
|
|
895
|
+
|
|
896
|
+
| Field | Meaning | Absent means |
|
|
897
|
+
| --- | --- | --- |
|
|
898
|
+
| `mix` | 0..1, how much of the solved rotation is applied | `1` |
|
|
899
|
+
| `softness` | distance from full reach at which the bones stop straightening | `0` |
|
|
900
|
+
| `bendPositive` | two-bone bend direction | `true` |
|
|
901
|
+
| `compress` | one-bone IK scales the bone down to reach a close target | `false` |
|
|
902
|
+
| `stretch` | scales the bone up to reach a far target | `false` |
|
|
903
|
+
|
|
904
|
+
- **`mix` and `softness` are the two curve channels, in that order.** A raw
|
|
905
|
+
`curve` is therefore 8 numbers, and the three booleans are stepped by nature —
|
|
906
|
+
nothing interpolates a bend direction.
|
|
907
|
+
- `mix` outside `0..1` is a compile error: `IkConstraintPose.mix` is documented as
|
|
908
|
+
a percentage. A **transform** mix is documented *unbounded*, which is why §4.10
|
|
909
|
+
has no such rule — the asymmetry is the runtime's, not ours.
|
|
910
|
+
- 🚨 **A field stated on one key and not the next is a compile error.** This is the
|
|
911
|
+
refusal worth reading twice, because the format's shape invites the mistake: the
|
|
912
|
+
parser reads every field **fresh per key** with its own default, so a key that
|
|
913
|
+
omits `softness` does not hold the previous key's 14 — it snaps to 0, and
|
|
914
|
+
interpolates down to it on the way. It loads. It plays. It is not what you
|
|
915
|
+
wrote. So state a field on every key of a track or on none of them; writing the
|
|
916
|
+
default out loud is how you opt in.
|
|
917
|
+
- Its last key counts towards the declared duration like any other (R7).
|
|
918
|
+
|
|
919
|
+
rigc refuses four things here:
|
|
920
|
+
|
|
921
|
+
| You wrote | You get |
|
|
922
|
+
| --- | --- |
|
|
923
|
+
| a constraint the rig does not declare | `keys unknown ik constraint "X"; the rig declares ik constraint(s): …` |
|
|
924
|
+
| a `transform` constraint's name | `keys "X" as an ik constraint, but the rig declares it as a "transform" constraint` |
|
|
925
|
+
| `softness` on the first key only | `key 0 names "softness" and key 1 (t=…) does not … it would snap to 0` |
|
|
926
|
+
| `mix: 1.5` | `mix is 1.5, outside 0..1 — the runtime documents it as a percentage 0-1` |
|
|
927
|
+
|
|
928
|
+
The type check matters because the parser resolves a timeline's target by name
|
|
929
|
+
**and** type: `findConstraint(name, IkConstraintData)` misses a transform
|
|
930
|
+
constraint of the same name, returns null, and `readAnimation` throws — in the
|
|
931
|
+
consumer's process, which is late. `A34_CONSTRAINT_TIMELINE_TARGETS` checks the
|
|
932
|
+
same two from the other side, plus one thing the compiler cannot produce and a
|
|
933
|
+
hand-edited file can: an **empty key array**, which the parser skips in silence.
|
|
934
|
+
|
|
935
|
+
### 4.10 `transform` — turning a muted transform constraint on
|
|
936
|
+
|
|
937
|
+
Same shape as §4.9 and the same absent-means-default rule; six mixes instead of
|
|
938
|
+
two. The idiom this exists for is an aim rig: the constraints are declared with
|
|
939
|
+
`mixRotate: 0` in the rig spec — muted at setup, so the figure is posed by its own
|
|
940
|
+
bones — and the animation that needs them mixes them in.
|
|
941
|
+
|
|
942
|
+
```json
|
|
943
|
+
"animations": {
|
|
944
|
+
"aim": {
|
|
945
|
+
"duration": 0.3333333,
|
|
946
|
+
"loop": false,
|
|
947
|
+
"tracks": [],
|
|
948
|
+
"transform": [
|
|
949
|
+
{ "constraint": "aim-head-transform", "keys": [{ "t": 0, "mixRotate": 1 }] },
|
|
950
|
+
{ "constraint": "aim-torso-transform", "keys": [{ "t": 0, "mixRotate": 0.6 }] },
|
|
951
|
+
{ "constraint": "aim-front-arm-transform", "keys": [{ "t": 0, "mixRotate": 1 }] }
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
⚠️ **Every number above is invented**, including the 0.6 — a torso that follows the
|
|
958
|
+
aim less than the head is a plausible rig and not a measured one.
|
|
959
|
+
|
|
960
|
+
A single key at `t: 0` is the whole timeline here, and that is not a degenerate
|
|
961
|
+
case: it says "while this animation is playing, this constraint is on", which is
|
|
962
|
+
exactly what a mix that was 0 at setup needs said.
|
|
963
|
+
|
|
964
|
+
| Field | Absent means |
|
|
965
|
+
| --- | --- |
|
|
966
|
+
| `mixRotate` | `1` |
|
|
967
|
+
| `mixX` | `1` |
|
|
968
|
+
| `mixY` | **this key's own `mixX`** — not `1` |
|
|
969
|
+
| `mixScaleX` | `1` |
|
|
970
|
+
| `mixScaleY` | `1` |
|
|
971
|
+
| `mixShearY` | `1` |
|
|
972
|
+
|
|
973
|
+
- **Six curve channels, in the order above.** A raw `curve` is 24 numbers.
|
|
974
|
+
- No range check: every one of these is documented **unbounded**, and an over-mix
|
|
975
|
+
above 1 is a real editor idiom rather than a mistake.
|
|
976
|
+
- ⚠️ A mix is only read by the runtime if the constraint declares the matching
|
|
977
|
+
`properties` mapping (§3.5). Keying `mixScaleY` on a constraint that maps
|
|
978
|
+
rotation only is dead data — legal, loaded, and it moves nothing.
|
|
979
|
+
- The refusals are §4.9's, with `transform` in place of `ik`.
|
|
980
|
+
|
|
981
|
+
### 4.11 `deform` — moving an attachment's vertices
|
|
982
|
+
|
|
983
|
+
The one timeline whose keys mean something different depending on what they are
|
|
984
|
+
attached to, and the only one keyed on a **skin / slot / attachment** triple:
|
|
985
|
+
`animations.<a>.attachments.<skin>.<slot>.<attachment>.deform`.
|
|
986
|
+
|
|
987
|
+
```json
|
|
988
|
+
"animations": {
|
|
989
|
+
"hoverboard": {
|
|
990
|
+
"duration": 1.6666666,
|
|
991
|
+
"loop": true,
|
|
992
|
+
"tracks": [],
|
|
993
|
+
"deform": [
|
|
994
|
+
{
|
|
995
|
+
"slot": "board",
|
|
996
|
+
"attachment": "board",
|
|
997
|
+
"keys": [
|
|
998
|
+
{ "t": 0 },
|
|
999
|
+
{ "t": 0.5, "fromVertex": 4, "vertices": [0, -7, 0, -7], "ease": "settle" },
|
|
1000
|
+
{ "t": 1.1666666, "fromVertex": 4, "vertices": [0, 4, 0, 4], "ease": "settle" },
|
|
1001
|
+
{ "t": 1.6666666 }
|
|
1002
|
+
]
|
|
1003
|
+
}
|
|
1004
|
+
]
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
⚠️ **Every number above is invented** — the vertex indices, the offsets and the
|
|
1010
|
+
times alike. A real deform's numbers come from the geometry it is bending, and
|
|
1011
|
+
there is no way to guess which vertices of somebody's board are its middle.
|
|
1012
|
+
|
|
1013
|
+
| Field | Meaning |
|
|
1014
|
+
| --- | --- |
|
|
1015
|
+
| `skin` | which skin the attachment lives in. Absent = `"default"` |
|
|
1016
|
+
| `slot` | the slot |
|
|
1017
|
+
| `attachment` | the attachment's **placeholder** name inside that skin and slot |
|
|
1018
|
+
|
|
1019
|
+
Per key:
|
|
1020
|
+
|
|
1021
|
+
| Field | Meaning |
|
|
1022
|
+
| --- | --- |
|
|
1023
|
+
| `vertices` | the run: `x, y` offset pairs. **Absent** = back to the setup pose |
|
|
1024
|
+
| `fromVertex` | which VERTEX the run starts at — rigc translates it |
|
|
1025
|
+
| `offset` | the same start as a raw index into the deform array. Never with `fromVertex` |
|
|
1026
|
+
| `ease` / `curve` | one channel, and it eases the **blend**, not a coordinate |
|
|
1027
|
+
|
|
1028
|
+
Four things are worth having straight before you write one.
|
|
1029
|
+
|
|
1030
|
+
**A key is a sparse edit, and a key with no `vertices` is the setup pose.** The
|
|
1031
|
+
parser builds an array as long as the attachment's own, copies your run into it at
|
|
1032
|
+
`offset`, and leaves the rest at zero. So a run only has to cover the vertices that
|
|
1033
|
+
move, and the `{ "t": 0 }` and `{ "t": 1.6666666 }` keys above are how a looping
|
|
1034
|
+
deform starts and ends undeformed. That is the format's own encoding for it, not a
|
|
1035
|
+
convention — which is why rigc refuses a start index on such a key: there would be
|
|
1036
|
+
nothing for it to point at.
|
|
1037
|
+
|
|
1038
|
+
**Offsets, not positions.** The numbers are relative to the setup geometry in both
|
|
1039
|
+
encodings. On an unweighted attachment the parser literally adds the setup vertex
|
|
1040
|
+
back on load; on a weighted one zero *is* "unmoved".
|
|
1041
|
+
|
|
1042
|
+
**The array is indexed by vertex on an unweighted attachment and by bone influence
|
|
1043
|
+
on a weighted one.** Its length is `vertices.length` in the first case and
|
|
1044
|
+
`vertices.length / 3 * 2` in the second, because a weighted attachment stores
|
|
1045
|
+
`x, y, weight` per influence. A vertex with three bones on it therefore occupies
|
|
1046
|
+
**three** pairs, each in that bone's own bind space. This is the whole reason
|
|
1047
|
+
`fromVertex` exists and the whole reason it is sometimes refused:
|
|
1048
|
+
|
|
1049
|
+
- on an **unweighted** attachment `fromVertex` always works — vertex *v* is array
|
|
1050
|
+
index `2v`, exactly;
|
|
1051
|
+
- on a **weighted** one it works while every vertex the run covers has exactly one
|
|
1052
|
+
bone on it, and rigc computes the true start index for you (which is *not*
|
|
1053
|
+
`2v` — earlier multi-bone vertices push it along);
|
|
1054
|
+
- on a **multi-bone** vertex it is a compile error, and deliberately so. One
|
|
1055
|
+
`x, y` for such a vertex is not a thing the array can hold: its world offset is
|
|
1056
|
+
`Σ weightᵦ · Mᵦ · offsetᵦ` over its bones, so a single pair only means what you
|
|
1057
|
+
meant if every influencing bone happens to share one world matrix. rigc will not
|
|
1058
|
+
guess. Either key the control bone instead — which is what a rigc-generated ring
|
|
1059
|
+
or ribbon mesh is *for* — or write the bind-space pairs yourself and start the
|
|
1060
|
+
run with `offset`. The refusal tells you the index that vertex starts at.
|
|
1061
|
+
|
|
1062
|
+
**The curve eases the blend.** A deform timeline has exactly one channel and
|
|
1063
|
+
`readCurve` builds it between **0 and 1** — the fraction of the way from this key's
|
|
1064
|
+
geometry to the next one's. So a named `ease` behaves as it does anywhere, and a
|
|
1065
|
+
raw `curve` is 4 numbers whose value axis runs 0..1, not the range of your vertex
|
|
1066
|
+
offsets.
|
|
1067
|
+
|
|
1068
|
+
rigc refuses six things here, and the first is the quietest defect in the whole
|
|
1069
|
+
animation half of the format:
|
|
1070
|
+
|
|
1071
|
+
| You wrote | You get |
|
|
1072
|
+
| --- | --- |
|
|
1073
|
+
| a run that ends past the array | `the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long (4 vertices)` |
|
|
1074
|
+
| an odd `offset` | `offset 3 is odd. The deform array is x, y pairs, so an odd start puts every x of this run on a y` |
|
|
1075
|
+
| an odd number of `vertices` | `"vertices" holds 3 numbers; the deform array is x, y PAIRS` |
|
|
1076
|
+
| `fromVertex` on a multi-bone vertex | `"fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` |
|
|
1077
|
+
| an attachment that is not there | `slot "flat" in skin "default" has no attachment "flatt" (it has: flat)` |
|
|
1078
|
+
| a deform on a region attachment | `a deform timeline keys the vertices of an attachment, and this one is a "region"` |
|
|
1079
|
+
|
|
1080
|
+
The overrun is the one to fear. `readAnimation` copies with
|
|
1081
|
+
`Utils.arrayCopy(vertices, 0, deform, offset, vertices.length)` into a
|
|
1082
|
+
`Float32Array`, and writing past the end of a typed array is a **no-op in
|
|
1083
|
+
JavaScript** — no throw, no warning, no `NaN`. A run one pair too long loses its
|
|
1084
|
+
tail and deforms the rest of the mesh correctly, which looks nearly right, and
|
|
1085
|
+
"nearly right" is the hardest kind of wrong to find.
|
|
1086
|
+
`A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` checks all of it from the other side, on the
|
|
1087
|
+
emitted file, measuring the array's length from the attachment rather than assuming
|
|
1088
|
+
an encoding.
|
|
1089
|
+
|
|
787
1090
|
---
|
|
788
1091
|
|
|
789
1092
|
## 5. Reading a failure
|
|
@@ -822,6 +1125,14 @@ the frequent ones, verbatim:
|
|
|
822
1125
|
| `vertexCount N wants M unweighted numbers and "vertices" holds K` | fix the count or the array; they decide the encoding between them |
|
|
823
1126
|
| `end names slot "X", which this rig does not declare` | fix the clipping attachment's `end`, or add the slot |
|
|
824
1127
|
| `bone "X" takes its position from …, which needs a cut manifest` | R8 — pass `--manifest`, or write literal `x`/`y` |
|
|
1128
|
+
| `animation "A" keys unknown ik constraint "X"; the rig declares ik constraint(s): …` | §4.9 — fix the name, or declare the constraint in the rig spec |
|
|
1129
|
+
| `animation "A" keys "X" as an ik constraint, but the rig declares it as a "transform" constraint` | §4.9 — a timeline's target resolves by name AND type; put the entry under the right group |
|
|
1130
|
+
| `ik constraint "X": key 0 names "softness" and key 1 (t=…) does not` | §4.9 — every key is read with its own default, so state the field on every key or on none |
|
|
1131
|
+
| `ik constraint "X" (t=…): mix is 1.5, outside 0..1` | §4.9 — an IK mix is a percentage; a transform mix is unbounded |
|
|
1132
|
+
| `deform …: the run starts at deform index 4 and is 6 long, which ends at 10; this attachment's deform array is 8 long` | §4.11 — shorten the run or move its start; the parser would drop the tail in silence |
|
|
1133
|
+
| `deform … (t=…): offset 3 is odd` | §4.11 — the deform array is `x, y` pairs, so a run starts on an even index |
|
|
1134
|
+
| `deform …: "fromVertex" counts VERTICES, and this attachment is weighted … vertex 2 has 2 of them` | §4.11 — key the control bone, or write bind-space pairs and start with `offset` |
|
|
1135
|
+
| `deform …: slot "X" in skin "default" has no attachment "Y" (it has: …)` | §4.11 — fix the placeholder name |
|
|
825
1136
|
|
|
826
1137
|
### 5.2 Assertions — the gate
|
|
827
1138
|
|
|
@@ -867,7 +1178,7 @@ The report prints one line per assertion:
|
|
|
867
1178
|
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` label is not on the 4.3 line (`4.3`, `4.3.N`, `4.3.N-suffix`) |
|
|
868
1179
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | a page the atlas declares is not a file. Check `--images` and `--out` |
|
|
869
1180
|
| `A18_DETERMINISTIC_EMIT` | both | a second compile of the same inputs differed. That is a compiler bug, not a spec bug — report it |
|
|
870
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay
|
|
1181
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | an overlay part image can never be transparent: no alpha channel (colour type 4 or 6) and no `tRNS` chunk either, so it would paint a solid rectangle over what is behind it. Re-export it as RGBA, or as an indexed / greyscale PNG that keeps its `tRNS`. Only the full-stage base plate may be opaque. Indexed-with-`tRNS` — the usual output of ImageMagick, "Export as PNG-8", GIMP's indexed mode, aseprite and pngquant — **passes**: it is transparent art |
|
|
871
1182
|
| `A20_MESH_WEIGHTS_COHERENT` | both ◑ | a weighted vertex with no bone, a negative weight, a bone index out of range, or weights that do not sum to 1. Under `spine-html` also: an unweighted mesh, or a binding at weight 0 |
|
|
872
1183
|
| `A21_MESH_RIM_PINNED` | archetype | a generated ring's rim, or a ribbon's entry row, is not pinned to its anchor bone at weight 1 |
|
|
873
1184
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | a mesh UV outside its region, or a UV array that disagrees with the vertex count |
|
|
@@ -882,6 +1193,8 @@ The report prints one line per assertion:
|
|
|
882
1193
|
| `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
1194
|
| `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
1195
|
| `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 |
|
|
1196
|
+
| `A34_CONSTRAINT_TIMELINE_TARGETS` | both | an `ik` or `transform` timeline names a constraint the skeleton does not declare, names one of the other type, or carries no keys at all (§4.9, §4.10). The last is silent: the parser reads key 0, finds nothing, and skips the timeline. **SKIP** when no animation carries one |
|
|
1197
|
+
| `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT` | both | a deform key's run runs past the end of the attachment's deform array, starts on an odd index, holds an odd count of numbers or a non-finite one, or names a skin/slot/attachment triple that does not resolve (§4.11). The overrun is the quiet one — the parser copies into a `Float32Array` and drops the tail. **SKIP** when no animation carries a deform timeline |
|
|
885
1198
|
|
|
886
1199
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
887
1200
|
clauses are gated by profile.
|
|
@@ -912,16 +1225,21 @@ Two more limits that are not errors but will shape what you can attempt:
|
|
|
912
1225
|
region covers its whole page, `pma: false`. To reproduce a skeleton whose art
|
|
913
1226
|
ships as a packed atlas you either supply loose PNGs and let rigc build its own
|
|
914
1227
|
atlas, or hand the packed atlas to `validate`/`bench` alongside the candidate.
|
|
915
|
-
-
|
|
916
|
-
by the validator
|
|
917
|
-
|
|
918
|
-
|
|
1228
|
+
- **`sequence` timelines, `drawOrderFolder` and `slider` / `path` constraint
|
|
1229
|
+
timelines** are walked by the validator (so `A05` checks their curves and `diff`
|
|
1230
|
+
counts their keys) and cannot be *written*: there is no motion-spec property for
|
|
1231
|
+
any of them. The two constraint groups are blocked one level down — rigc emits no
|
|
1232
|
+
`path` or `slider` constraint to key. Everything a motion spec **can** key is
|
|
1233
|
+
§4.4's track table plus the five families that sit beside `tracks`: `drawOrder`
|
|
1234
|
+
(§4.7), `events` (§4.8), `ik` (§4.9), `transform` (§4.10) and `deform` (§4.11).
|
|
919
1235
|
|
|
920
1236
|
---
|
|
921
1237
|
|
|
922
1238
|
## 7. Before you call it done
|
|
923
1239
|
|
|
924
1240
|
1. `build --profile <the one you meant>` exits 0 and the report has **no FAIL**.
|
|
1241
|
+
Saying nothing means `spine`, so "the one you meant" is a decision either way —
|
|
1242
|
+
the report's first line names the profile that judged it.
|
|
925
1243
|
2. Read the `SKIP` lines. Each one is a check that did *not* run — make sure none of
|
|
926
1244
|
them is a check you were relying on.
|
|
927
1245
|
⚠️ Under `--profile spine` a foreign skeleton usually produces **no SKIP lines
|
|
@@ -1557,13 +1875,13 @@ that drew the reference frames, onto the same pixel grid, and reports what diffe
|
|
|
1557
1875
|
you specifically: it means **you may run `check` as often as you like** without
|
|
1558
1876
|
your run ceasing to be an honest authoring run. It is a loop, in the way `build` is
|
|
1559
1877
|
a loop. `bench` and `diff` against a rung's export are not — they read the answer,
|
|
1560
|
-
and [the ladder's honesty rule](LADDER.md) makes them a finish line you reach once.
|
|
1878
|
+
and [the ladder's honesty rule](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) makes them a finish line you reach once.
|
|
1561
1879
|
|
|
1562
1880
|
📌 **That is also why the MAE figures quoted through this section stay.** Every one of
|
|
1563
1881
|
them is a candidate's own reading against rendered frames — the exam question, not the
|
|
1564
1882
|
answer key — so none of them narrows a reference-side measure, and a guide that censored
|
|
1565
1883
|
them would be teaching less for no gain in honesty. The criterion is under *The honesty
|
|
1566
|
-
rule* in [LADDER.md](LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1884
|
+
rule* in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) (issue #158); what it *does* seal is a score written
|
|
1567
1885
|
over a reference's own count, and no such figure appears here.
|
|
1568
1886
|
|
|
1569
1887
|
🚨 **If you drive the runtime yourself, a bone's local transform lives on
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Prompting an AI to author with rigc
|
|
2
|
+
|
|
3
|
+
This page is for the person **operating** an AI agent — any AI agent — that will
|
|
4
|
+
author a rig with rigc. It is not authoring guidance; that is
|
|
5
|
+
[AUTHORING.md](AUTHORING.md), and the agent should read that file, not this one.
|
|
6
|
+
This page tells you what to put in the prompt, and it earns its claims from a
|
|
7
|
+
measured case: in August 2026 a deliberately small model — Gemini 3.7 Flash on
|
|
8
|
+
the Antigravity harness — authored a benchmark rung unattended for three hours,
|
|
9
|
+
and every stumble below is one it actually made, priced by the instruments in
|
|
10
|
+
[PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md)'s protocol. A large model makes fewer of these mistakes
|
|
11
|
+
unprompted; the clauses cost you three lines and make the small model's run
|
|
12
|
+
land and the large model's run land cleaner.
|
|
13
|
+
|
|
14
|
+
## The prompt skeleton
|
|
15
|
+
|
|
16
|
+
For ordinary use (not a benchmark), the whole prompt is short:
|
|
17
|
+
|
|
18
|
+
> Author a Spine rig with rigc for <what you want>. The images are in
|
|
19
|
+
> `<dir>`. Read `docs/AUTHORING.md` in full before writing either spec —
|
|
20
|
+
> sections 1–5 are the two file formats and the failure map, section 8 is how
|
|
21
|
+
> to fit poses against picture references, section 10 is the editor's
|
|
22
|
+
> conventions. Build with `rigc build`, keep going until `rigc validate` is
|
|
23
|
+
> green, and treat every named failure as pointing at the file to change.
|
|
24
|
+
|
|
25
|
+
Then add the clauses below. If you are running the **benchmark**, do not
|
|
26
|
+
improvise a prompt at all — hand the agent
|
|
27
|
+
[pilot/rung3-runner-prompt.md](https://github.com/firejune/rigc/blob/main/docs/pilot/rung3-runner-prompt.md) verbatim; it
|
|
28
|
+
carries the honesty rule, and an improvised summary of that rule is how a run
|
|
29
|
+
stops being scorable.
|
|
30
|
+
|
|
31
|
+
## Five clauses the pilot paid for
|
|
32
|
+
|
|
33
|
+
**1. Any search loop must print progress and carry a time-box.**
|
|
34
|
+
The pilot wrote a pose-fitting script with no output and no bound, and its
|
|
35
|
+
operator spent part of the evening deciding whether a silent 99 %-CPU process
|
|
36
|
+
was working or hung. (It was working — but nobody could tell.) Say:
|
|
37
|
+
|
|
38
|
+
> Long-running scripts must print one progress line per unit of work and state
|
|
39
|
+
> an expected total up front. If a search would exceed ten minutes, stop and
|
|
40
|
+
> narrow it instead.
|
|
41
|
+
|
|
42
|
+
**2. Intermediate results go to files, not scrollback.**
|
|
43
|
+
The pilot's first fitters printed results to stdout; a crashed step would have
|
|
44
|
+
lost the lot. It later switched to JSON files on its own — start there. Say:
|
|
45
|
+
|
|
46
|
+
> Write measured values (placements, angles, fitted poses) to files in the
|
|
47
|
+
> working directory as you go. Treat your transcript as disposable.
|
|
48
|
+
|
|
49
|
+
**3. Point the fitting method at AUTHORING §8, by number.**
|
|
50
|
+
Given frames to match, the pilot's first instinct was a nested grid over every
|
|
51
|
+
knob at once — tens of thousands of renders per frame. §8 describes the cheap
|
|
52
|
+
loop (one knob at a time, render back, keep what the measurement keeps). The
|
|
53
|
+
pilot had the file "in hand" and did not apply the section until it had burned
|
|
54
|
+
hours. Say:
|
|
55
|
+
|
|
56
|
+
> Fit poses the way AUTHORING §8 does — one variable at a time against a
|
|
57
|
+
> render-back measurement. Do not grid-search several knobs jointly.
|
|
58
|
+
|
|
59
|
+
**4. Keys are structure, not samples.**
|
|
60
|
+
A fitter produces one pose per frame, and the pilot keyed nearly every one of
|
|
61
|
+
them. The result *placed* its parts well — and still scored poorly on every
|
|
62
|
+
timing measure, because animation data is sparse keys plus interpolation
|
|
63
|
+
(AUTHORING §10.3–§10.4), and a key on every frame is a pixel transcription
|
|
64
|
+
wearing a skeleton. Say:
|
|
65
|
+
|
|
66
|
+
> Key extremes and contacts, then let curves carry the in-betweens, per
|
|
67
|
+
> AUTHORING §10.3–10.4. If your fitted poses disagree with a curve, move the
|
|
68
|
+
> curve, not key count. Also §4.4: when a bone's two axes need different
|
|
69
|
+
> timing or easing, key the single-axis timelines, not the paired one.
|
|
70
|
+
|
|
71
|
+
**5. Keep the loop log live.**
|
|
72
|
+
The pilot wrote its `LOOP.md` at the end, from memory — two entries for a
|
|
73
|
+
three-hour session, with eight generations of fitting scripts invisible
|
|
74
|
+
between them. A log written afterwards records the story, not the work. Say:
|
|
75
|
+
|
|
76
|
+
> Append to `LOOP.md` at the moment each build/measure/change step happens,
|
|
77
|
+
> not at the end. One line per step is enough.
|
|
78
|
+
|
|
79
|
+
**6. Make the agent typecheck its own helper scripts.**
|
|
80
|
+
Bun strips types and runs, so an agent can call methods that do not exist and
|
|
81
|
+
watch the script fail — or worse, silently misbehave — without ever learning
|
|
82
|
+
the API was imagined. The pilot left behind a helper written against
|
|
83
|
+
spine-core methods that were never real; the repository's `typecheck` gate
|
|
84
|
+
caught it at landing, hours too late to help the run. Say:
|
|
85
|
+
|
|
86
|
+
> After writing any helper script, run `bunx tsc --noEmit <file>` (or the
|
|
87
|
+
> project's `typecheck` task) before trusting its output.
|
|
88
|
+
|
|
89
|
+
## What you do not need to say
|
|
90
|
+
|
|
91
|
+
The same pilot, unprompted, did all of this correctly: it looped
|
|
92
|
+
`build → validate` until green and read the named failures as pointers; it ran
|
|
93
|
+
the whole thing without a human answer for three hours; it killed its own
|
|
94
|
+
stray processes; it recorded its model name honestly; and under the benchmark
|
|
95
|
+
protocol it ran the scored diff exactly once, at the end, editing nothing
|
|
96
|
+
after. The tool's own error surface carries that part of the conversation —
|
|
97
|
+
prompt for the five clauses above and then let the agent work.
|
|
98
|
+
|
|
99
|
+
## If you are scoring the run
|
|
100
|
+
|
|
101
|
+
That is a different document: [PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) is the protocol,
|
|
102
|
+
`pilot/rung3-runner-prompt.md` is the exact prompt, and the evaluator sheet it
|
|
103
|
+
names is for the judge only. The one rule that reaches this page: **never put
|
|
104
|
+
baseline figures, reference structure, or previous runs' measures into the
|
|
105
|
+
runner's prompt** — a number in the prompt is a target, and a run that aimed
|
|
106
|
+
at a target measured nothing.
|