spine-rigc 0.5.0 β 0.6.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 +323 -224
- package/bin/rigc.cjs +40 -0
- package/cli.ts +558 -49
- package/docs/AUTHORING.md +60 -23
- package/docs/PROMPTING.md +106 -0
- package/docs/SPEC_COVERAGE.md +16 -14
- package/package.json +4 -2
- package/src/compile.ts +189 -142
- 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 +8 -0
- package/src/validate.ts +58 -13
- package/tools/plate.ts +164 -21
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/banner.svg" alt="rigc - Rig compiler for Spine" width="100%" />
|
|
2
|
+
<img src="https://raw.githubusercontent.com/firejune/rigc/main/assets/banner.svg" alt="rigc - Rig compiler for Spine" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -53,6 +53,252 @@ weights. Every one of those loads clean, plays, and is wrong. rigc's answer is t
|
|
|
53
53
|
make the failure legible: compile from a spec, round-trip through the real parser,
|
|
54
54
|
run a list of named assertions, and **write nothing unless all of them are green.**
|
|
55
55
|
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
π¦ **rigc measures loose PNGs directly β one atlas page per image.** It is not an
|
|
59
|
+
atlas packer: packing several regions onto one page is tracked as
|
|
60
|
+
[issue #4](https://github.com/firejune/rigc/issues/4), not something the tool does
|
|
61
|
+
today.
|
|
62
|
+
|
|
63
|
+
rigc runs on [Bun](https://bun.sh). The package ships its TypeScript sources and
|
|
64
|
+
Bun runs them, so there is no build step and no `dist/` that can drift from the
|
|
65
|
+
repository it was cut from.
|
|
66
|
+
|
|
67
|
+
**The npm package is `spine-rigc`; the command it installs is `rigc`.** npm
|
|
68
|
+
refuses the name `rigc` as too similar to packages that already exist, so the
|
|
69
|
+
project, this repository and the executable keep their name and only the
|
|
70
|
+
registry entry is spelled out.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bunx spine-rigc --help # run it without installing
|
|
74
|
+
bun add -g spine-rigc # or install the command
|
|
75
|
+
bun add -d spine-rigc # or pin it in a project
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`npx spine-rigc` works too, as long as Bun is on `PATH` β the executable is a
|
|
79
|
+
Bun script, and npm only writes the shim that calls it.
|
|
80
|
+
|
|
81
|
+
Installed, the command is `rigc`. The examples below spell it `bun cli.ts`
|
|
82
|
+
because they are written from a clone of this repository (`bun install`, then run
|
|
83
|
+
the CLI in place); the two are interchangeable β `rigc build β¦` is
|
|
84
|
+
`bun cli.ts build β¦`.
|
|
85
|
+
|
|
86
|
+
Two commands are repository workflows rather than package ones: `bench` and
|
|
87
|
+
`check` measure against Spine's official example projects and the reference
|
|
88
|
+
frames rendered from them, which are fetched rather than redistributed (see
|
|
89
|
+
[NOTICE.md](NOTICE.md)). They need a clone and `bun run fetch-examples`, and say
|
|
90
|
+
so by name when the corpus is absent.
|
|
91
|
+
|
|
92
|
+
## First rig in ten minutes
|
|
93
|
+
|
|
94
|
+
A whole rig, end to end, in a scratch directory: three tiny plates, two JSON
|
|
95
|
+
files, one `build`, one `validate`. No clone, no art pipeline, nothing fetched.
|
|
96
|
+
|
|
97
|
+
π« **Every value below is invented for this section** β a doll that exists
|
|
98
|
+
nowhere else in this repository. That is [AUTHORING.md](docs/AUTHORING.md) Β§3's
|
|
99
|
+
rule applied here: no example value in these documents is copied out of a
|
|
100
|
+
reference export, so nothing you read in a quickstart is an answer to anything
|
|
101
|
+
[the ladder](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) measures.
|
|
102
|
+
|
|
103
|
+
**1. Install the command.**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bun add -g spine-rigc # installs `rigc`
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or skip the install and prefix every command below with `bunx `, e.g.
|
|
110
|
+
`bunx spine-rigc build β¦`.
|
|
111
|
+
|
|
112
|
+
**2. Make a directory and three plates.** rigc measures PNGs rather than trusting
|
|
113
|
+
a number you typed (R5), so the art has to exist. These three are solid colours a
|
|
114
|
+
few dozen pixels across β a hull, a mast and a lamp:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mkdir -p buoy/images && cd buoy
|
|
118
|
+
bun -e '
|
|
119
|
+
const parts = {
|
|
120
|
+
"images/hull.png": "iVBORw0KGgoAAAANSUhEUgAAADgAAAAMCAYAAAA3bX6lAAAAKElEQVR42mOI8bL6P5wxw6gHRz046sFRD456cNSDox4c9eCoBwcrBgDSZ+mdl2OiDgAAAABJRU5ErkJggg==",
|
|
121
|
+
"images/mast.png": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAA0CAYAAAC3t3ldAAAAH0lEQVR42mO4dunIf3yYYVTBqIJRBaMKRhWMKhgcCgBGJo4s9YnopgAAAABJRU5ErkJggg==",
|
|
122
|
+
"images/lamp.png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAHElEQVR42mP4v8HhPzUww6hBowaNGjRq0HAzCADvdrVmFPbc+QAAAABJRU5ErkJggg=="
|
|
123
|
+
};
|
|
124
|
+
for (const [p, b] of Object.entries(parts)) await Bun.write(p, Buffer.from(b, "base64"));
|
|
125
|
+
'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**3. The rig spec β `buoy.rig.json`.** Structure only: bones, the slots array in
|
|
129
|
+
draw order, and one skin mapping each slot to a plate.
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"spec": "rigc-rig/1",
|
|
134
|
+
"name": "buoy",
|
|
135
|
+
"images": "images",
|
|
136
|
+
"skeleton": { "width": 200, "height": 200 },
|
|
137
|
+
"bones": [
|
|
138
|
+
{ "name": "root" },
|
|
139
|
+
{ "name": "hull", "parent": "root", "x": 0, "y": 0 },
|
|
140
|
+
{ "name": "mast", "parent": "hull", "x": 0, "y": 4 },
|
|
141
|
+
{ "name": "lamp", "parent": "mast", "x": 0, "y": 52 }
|
|
142
|
+
],
|
|
143
|
+
"slots": [
|
|
144
|
+
{ "name": "mast", "bone": "mast", "attachment": "mast" },
|
|
145
|
+
{ "name": "hull", "bone": "hull", "attachment": "hull" },
|
|
146
|
+
{ "name": "lamp", "bone": "lamp", "attachment": "lamp" }
|
|
147
|
+
],
|
|
148
|
+
"skins": {
|
|
149
|
+
"default": {
|
|
150
|
+
"mast": { "mast": { "image": "mast.png", "y": 26 } },
|
|
151
|
+
"hull": { "hull": { "image": "hull.png" } },
|
|
152
|
+
"lamp": { "lamp": { "image": "lamp.png" } }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Three things in there are worth naming, because each is a rule rather than a
|
|
159
|
+
style: the **slots array is the setup draw order** (R4) β index 0 is furthest
|
|
160
|
+
back, so the mast is behind the hull; the attachment carries an **`image`
|
|
161
|
+
instead of a `width`/`height`** (R5), which is what makes the size in the
|
|
162
|
+
skeleton and the size in the atlas incapable of drifting apart; and the mast's
|
|
163
|
+
`"y": 26` offsets the plate *within* its slot so the bone sits at the mast's foot
|
|
164
|
+
rather than its middle.
|
|
165
|
+
|
|
166
|
+
**4. The motion spec β `buoy.motion.json`.** Time only, aimed at the rig by name:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"spec": "rigc-motion/1",
|
|
171
|
+
"archetype": "buoy",
|
|
172
|
+
"cut": "buoy",
|
|
173
|
+
"easings": { "swing": [0.42, 0, 0.58, 1] },
|
|
174
|
+
"animations": {
|
|
175
|
+
"bob": {
|
|
176
|
+
"duration": 2,
|
|
177
|
+
"loop": true,
|
|
178
|
+
"tracks": [
|
|
179
|
+
{
|
|
180
|
+
"bone": "hull",
|
|
181
|
+
"property": "translatey",
|
|
182
|
+
"keys": [
|
|
183
|
+
{ "t": 0, "v": [0], "ease": "swing" },
|
|
184
|
+
{ "t": 0.5, "v": [5], "ease": "swing" },
|
|
185
|
+
{ "t": 1.5, "v": [-5], "ease": "swing" },
|
|
186
|
+
{ "t": 2, "v": [0] }
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"bone": "mast",
|
|
191
|
+
"property": "rotate",
|
|
192
|
+
"keys": [
|
|
193
|
+
{ "t": 0, "v": [-6], "ease": "swing" },
|
|
194
|
+
{ "t": 1, "v": [6], "ease": "swing" },
|
|
195
|
+
{ "t": 2, "v": [-6] }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`archetype` must equal the rig's `name`. `duration` is declared and then checked
|
|
205
|
+
against what actually compiled (R7). The **last key of each track carries no
|
|
206
|
+
easing** β there is nothing after it to ease towards, and saying otherwise is a
|
|
207
|
+
compile error.
|
|
208
|
+
|
|
209
|
+
**5. Build, then re-gate what it wrote.**
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
rigc build --rig buoy.rig.json --motion buoy.motion.json --images images --out spine
|
|
213
|
+
rigc validate spine
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`build` prints every assertion by name, then the shape of what it emitted, then
|
|
217
|
+
the two files:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
.. pages=3 regions=3 bones=4 slots=3 animations=1 version=4.3.13 regionAttachments=3 meshAttachments=0 physicsConstraints=0 rig=buoy profile=spine
|
|
221
|
+
rigc: wrote β¦/buoy/spine/skeleton.json
|
|
222
|
+
rigc: wrote β¦/buoy/spine/skeleton.atlas
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`profile=spine` is the rulebook that judged it: *is this valid Spine 4.3 that any
|
|
226
|
+
runtime plays correctly?* That is the default, and the [Profiles](#profiles--wrong-versus-not-how-we-do-it-here)
|
|
227
|
+
section below is where the other one lives. `validate` then re-reads those
|
|
228
|
+
artifacts from disk and ends `rigc: green`. That is a rig. `spine/skeleton.json`
|
|
229
|
+
is Spine 4.3 skeleton data β it loads in a Spine runtime and it imports into the
|
|
230
|
+
Spine editor.
|
|
231
|
+
|
|
232
|
+
**Try breaking it**, because the validator's messages are the interface here and
|
|
233
|
+
they are worth meeting once on purpose. With `spine/` built, rename
|
|
234
|
+
`images/hull.png` to `images/raft.png` and re-run `rigc validate spine`:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
FAIL A17_ATLAS_PAGE_FILES_EXIST: page "../images/hull.png" is not on disk at β¦/images/hull.png
|
|
238
|
+
rigc: 1 assertion(s) failed
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Put the name back and it is green again. The same gate runs inside `build`, and
|
|
242
|
+
a FAIL there stops it **before it writes** β a red build leaves no half-built
|
|
243
|
+
artifact on disk to mistake for a result, and there is no flag that changes that.
|
|
244
|
+
|
|
245
|
+
**6. See what you built.**
|
|
246
|
+
|
|
247
|
+
π¨ **Green is a claim about validity and about nothing else.** A rig whose head
|
|
248
|
+
sits visibly off its torso passes every assertion, loads in `spine-core` and steps
|
|
249
|
+
numerically clean β the offsets are the ones your spec asked for, and no
|
|
250
|
+
assertion can know you did not mean them. The only remedy is looking, and both
|
|
251
|
+
commands below need nothing you do not already have: no reference frames, no
|
|
252
|
+
second package, no server.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
rigc render --candidate spine # PNG frames + a contact sheet, in render/
|
|
256
|
+
rigc preview --candidate spine # one .html file that plays it: preview.html
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**`render`** samples every animation at 12 fps and writes `render/<animation>/f0000.pngβ¦`
|
|
260
|
+
with a `contact.png` beside them β every frame of the shot as one labelled grid,
|
|
261
|
+
which is the picture to open first, because spacing is a comparison *across*
|
|
262
|
+
frames. It draws with rigc's own rasteriser (the one `check` measures with), so
|
|
263
|
+
it needs no browser and no network, and the `frames.json` it leaves beside the
|
|
264
|
+
directories makes the result a frame set like any other β the world box every
|
|
265
|
+
frame is a picture of. `--animation <name>` narrows it to one, `--fps` and
|
|
266
|
+
`--max` change the rate and the frame size.
|
|
267
|
+
|
|
268
|
+
**`preview`** writes a single self-contained `.html`: your skeleton, your atlas
|
|
269
|
+
and every page's PNG bytes are embedded in it as data URIs, and it plays them in
|
|
270
|
+
the **official [Spine Web Player](https://esotericsoftware.com/spine-player)**.
|
|
271
|
+
Double-click it, or attach it to a message β the file carries the whole artifact.
|
|
272
|
+
It is also the strongest interop statement in this repository: a rig that plays
|
|
273
|
+
there has been played by Esoteric Software's own runtime rather than by ours.
|
|
274
|
+
|
|
275
|
+
> βοΈ The player itself is **referenced, not embedded** β the page loads it from
|
|
276
|
+
> unpkg, so the first open needs a network, and rigc redistributes nothing
|
|
277
|
+
> Esoteric Software owns (see [NOTICE.md](NOTICE.md)). Everything the player
|
|
278
|
+
> draws is inside your file.
|
|
279
|
+
|
|
280
|
+
**Where to go next.**
|
|
281
|
+
|
|
282
|
+
- π **[docs/AUTHORING.md](docs/AUTHORING.md)** is the real guide β both files
|
|
283
|
+
field by field, the emission rules, every named failure mapped to the file that
|
|
284
|
+
has to change, and Β§8βΒ§9 for reproducing a shot you were given as pictures. It
|
|
285
|
+
ships inside the npm package too, at
|
|
286
|
+
`node_modules/spine-rigc/docs/AUTHORING.md`.
|
|
287
|
+
- `rigc explain --rig buoy.rig.json --motion buoy.motion.json --out spine` prints
|
|
288
|
+
the compiled rig as a table β every bone with its resolved parent, the slots in
|
|
289
|
+
draw order, every timeline key by key β and writes nothing. It is what to reach
|
|
290
|
+
for when a rig compiles and still looks wrong.
|
|
291
|
+
- π¨ **A green gate does not mean the animation is right**, and no assertion
|
|
292
|
+
could. If you have reference pictures of the shot,
|
|
293
|
+
`rigc check --candidate spine --frames <dir>` is the half of the loop that can
|
|
294
|
+
see a wrong animation β AUTHORING.md Β§9.
|
|
295
|
+
- [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) is the benchmark: the same job, from a brief
|
|
296
|
+
and rendered frames, scored. [docs/PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) is how to run an
|
|
297
|
+
agent through it and score what comes back.
|
|
298
|
+
- π€ **Handing the authoring to an AI agent?**
|
|
299
|
+
[docs/PROMPTING.md](docs/PROMPTING.md) is the operator's page β the six prompt
|
|
300
|
+
clauses a measured pilot run paid for, and what you can leave unsaid.
|
|
301
|
+
|
|
56
302
|
## The yardstick
|
|
57
303
|
|
|
58
304
|
The measure of whether this works is **Spine's own official example projects** β
|
|
@@ -193,13 +439,14 @@ the scale for a run.
|
|
|
193
439
|
|
|
194
440
|
There is no pass mark **in the tool**, for the same reason `diff` has none. The
|
|
195
441
|
ladder's pass definition and its thresholds are a document read by a person over
|
|
196
|
-
the whole table β [docs/
|
|
442
|
+
the whole table β [docs/GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md) states the clauses and
|
|
443
|
+
[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md)'s *Operating rules* derives them β and not an
|
|
197
444
|
exit code either command could produce.
|
|
198
445
|
|
|
199
446
|
### Benchmark ladder β the rungs, and where they stand
|
|
200
447
|
|
|
201
448
|
π **The ladder is complete, 2026-08-28.** All eight numbered rungs and the
|
|
202
|
-
spineboy graduation exam are cleared under gate v2.1
|
|
449
|
+
spineboy graduation exam are cleared under gate v2.1 and hold under **v2.2**, every clause PASS or SKIP:
|
|
203
450
|
worst attributable slot drift **5.55 px** against a 6.0 px bar, and **0 of 124**
|
|
204
451
|
frame-change disagreements. Recompiling the same spec in a different session
|
|
205
452
|
reproduced every field of the measurement record **to the digit**. The rungs stay
|
|
@@ -212,11 +459,11 @@ predecessor's specs under the run protocol's inheritance clause. It is **not**
|
|
|
212
459
|
that an agent authors a spineboy-scale rig from the brief alone in one run: the
|
|
213
460
|
ladder has not demonstrated that, and each row records which of the two it is.
|
|
214
461
|
|
|
215
|
-
**[docs/LADDER.md](docs/LADDER.md) is the live ledger**: the rung order
|
|
462
|
+
**[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) is the live ledger**: the rung order
|
|
216
463
|
(blockers β rung 3 first β 1 Β· 2 Β· 4 Β· 5 β 6 β 8 β 7 β spineboy), what each
|
|
217
464
|
rung gates on, how a rung is scored, the honesty rule that keeps the reference
|
|
218
465
|
export away from the authoring agent, the operating rules β what a pass is, and
|
|
219
|
-
the numbered thresholds of the current gate (**gate v2.
|
|
466
|
+
the numbered thresholds of the current gate (**gate v2.2**, stated in [docs/GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md)) that decide one β and a status table. Run
|
|
220
467
|
one with:
|
|
221
468
|
|
|
222
469
|
```bash
|
|
@@ -239,9 +486,40 @@ that every example declares; and **B3**, every example ships a **packed** atlas
|
|
|
239
486
|
page) against rigc's one-part-per-page model, which `A06` enforced unconditionally. **B1 and B2 are
|
|
240
487
|
closed**; B3's validator half is (the packed-atlas clauses live behind `--profile`, above) and its
|
|
241
488
|
emitter half β no packer, no atlas importer β is not. Ordered gap list in Part 4 of that document;
|
|
242
|
-
live status, and B1's proof, in [docs/LADDER.md](docs/LADDER.md).
|
|
489
|
+
live status, and B1's proof, in [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
|
490
|
+
|
|
491
|
+
## Looking at a rig β `rigc render` and `rigc preview`
|
|
492
|
+
|
|
493
|
+
The validator cannot see a wrong pose and says so honestly; `check` can, and needs
|
|
494
|
+
reference frames a first user does not have. That left looking as the one thing
|
|
495
|
+
the package could not do, and these two commands are it. Both take a compiled
|
|
496
|
+
artifact β the directory `build --out` wrote β and neither needs a reference, a
|
|
497
|
+
clone or a server:
|
|
243
498
|
|
|
244
|
-
|
|
499
|
+
```bash
|
|
500
|
+
rigc render --candidate spine [--animation <name>] [--fps 12] [--max 256] [--out render/]
|
|
501
|
+
rigc preview --candidate spine [--animation <name>] [--out preview.html]
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`render` writes `render/<animation>/f0000.pngβ¦` plus a `contact.png` grid of every
|
|
505
|
+
frame and a `frames.json` sidecar describing the world box they are pictures of β
|
|
506
|
+
the same frame-set shape `bench/render_reference.ts` writes and `rigc check`
|
|
507
|
+
reads, drawn by the same rasteriser, so the output is a frame set rather than a
|
|
508
|
+
pile of images. `preview` writes one self-contained `.html` that plays the
|
|
509
|
+
artifact in the official Spine Web Player, with the skeleton, the atlas and every
|
|
510
|
+
page embedded as data URIs; the player is loaded from unpkg rather than copied, so
|
|
511
|
+
the first open needs a network and rigc redistributes nothing Esoteric Software
|
|
512
|
+
owns ([NOTICE.md](NOTICE.md)).
|
|
513
|
+
|
|
514
|
+
They complement each other rather than overlap. `render` is offline, deterministic
|
|
515
|
+
and measurable β its pixels are the ones `check` reports on. `preview` is the
|
|
516
|
+
interop proof: what plays there was played by Esoteric's own runtime, not by ours.
|
|
517
|
+
|
|
518
|
+
## Run viewer β watching a *run* instead of reading it
|
|
519
|
+
|
|
520
|
+
π **This is the ladder's instrument, not the way to look at your own rig** β that
|
|
521
|
+
is the section above. The viewer is reference-bound and repository-bound, and it
|
|
522
|
+
deliberately never ships.
|
|
245
523
|
|
|
246
524
|
`check.txt` says a candidate's worst frame is f0012 at 56 MAE. The viewer shows
|
|
247
525
|
you f0012.
|
|
@@ -356,8 +634,15 @@ So `validate` and `build` take a `--profile`:
|
|
|
356
634
|
|
|
357
635
|
| Profile | Runs | For |
|
|
358
636
|
| --- | --- | --- |
|
|
359
|
-
| `spine
|
|
360
|
-
| `spine` |
|
|
637
|
+
| `spine` | the 20 validity rules | **the default.** Is this valid Spine 4.3 that any runtime plays correctly? |
|
|
638
|
+
| `spine-html` | all 34 | Opt-in. Is this a rig *this* project can ship? |
|
|
639
|
+
|
|
640
|
+
`spine` is the default because it is the question this package's output answers:
|
|
641
|
+
the artifact imports into the Spine editor and plays in any 4.3 runtime, and
|
|
642
|
+
that is what the 20 validity rules are about. The other 14 are somebody's policy
|
|
643
|
+
β one renderer's, one canvas budget's, one compiler's own formations' β and a
|
|
644
|
+
rig arriving from anywhere else has no stake in them. Ask for them with
|
|
645
|
+
`--profile spine-html` when you want them.
|
|
361
646
|
|
|
362
647
|
The **Profile** column below says which is which β `both` = validity, `renderer` and
|
|
363
648
|
`archetype` = `spine-html` only, and **`both β`** = a mixed assertion whose validity
|
|
@@ -388,7 +673,7 @@ the renderer policy*.
|
|
|
388
673
|
| `A16_SKELETON_VERSION_4_3` | both | the `skeleton.spine` version label is on the 4.3 line (the parser never checks it) |
|
|
389
674
|
| `A17_ATLAS_PAGE_FILES_EXIST` | both | every page the atlas declares is a file on disk |
|
|
390
675
|
| `A18_DETERMINISTIC_EMIT` | both | a second, independent compile of the same inputs is byte-identical. SKIPs when re-gating artifacts already on disk |
|
|
391
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | every overlay
|
|
676
|
+
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | renderer | every overlay part image can be transparent somewhere β an alpha channel (colour type 4 or 6) **or** a `tRNS` chunk, which is where indexed and greyscale PNGs keep theirs. Only the base plate β identified structurally as the region covering the stage β may be opaque |
|
|
392
677
|
| `A20_MESH_WEIGHTS_COHERENT` | both β | every weighted vertex has at least one bone, no negative weight, bone indices in range, and each vertex's weights sum to 1. `spine-html` also requires that a mesh be weighted at all and that no binding sit at weight 0 |
|
|
393
678
|
| `A21_MESH_RIM_PINNED` | archetype | a ring mesh's rim vertices are pinned to the anchor bone and its hull is a real ring; a ribbon's entry row stays put. **SKIPs on authored geometry** β rigc did not place its rim |
|
|
394
679
|
| `A22_MESH_UVS_IN_UNIT_RANGE` | both | every UV lies inside its region |
|
|
@@ -404,208 +689,6 @@ the renderer policy*.
|
|
|
404
689
|
| `A32_EVENT_KEYS_RESOLVE` | both | every event key fires an event the skeleton declares, no key sits earlier in time than the one before it, and `volume`/`balance` appear only on an event with an `audio` path. Only the first of those is loud in the parser; the other two load clean and drop the firing or the value in silence. SKIPs when no animation carries an event timeline |
|
|
405
690
|
| `A33_VERTEX_ATTACHMENT_GEOMETRY` | both | every bounding box and clipping polygon states a `vertexCount` that agrees with its vertex array, its weighted run decodes to that many vertices with bone indices in range, and a clipping `end` names a slot that exists. All three load clean: a missing count reads as zero and empties the polygon, and a missing end slot makes the clip run to the bottom of the draw order. SKIPs when the skeleton carries neither type |
|
|
406
691
|
|
|
407
|
-
## Install
|
|
408
|
-
|
|
409
|
-
rigc runs on [Bun](https://bun.sh). The package ships its TypeScript sources and
|
|
410
|
-
Bun runs them, so there is no build step and no `dist/` that can drift from the
|
|
411
|
-
repository it was cut from.
|
|
412
|
-
|
|
413
|
-
**The npm package is `spine-rigc`; the command it installs is `rigc`.** npm
|
|
414
|
-
refuses the name `rigc` as too similar to packages that already exist, so the
|
|
415
|
-
project, this repository and the executable keep their name and only the
|
|
416
|
-
registry entry is spelled out.
|
|
417
|
-
|
|
418
|
-
```bash
|
|
419
|
-
bunx spine-rigc --help # run it without installing
|
|
420
|
-
bun add -g spine-rigc # or install the command
|
|
421
|
-
bun add -d spine-rigc # or pin it in a project
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
`npx spine-rigc` works too, as long as Bun is on `PATH` β the executable is a
|
|
425
|
-
Bun script, and npm only writes the shim that calls it.
|
|
426
|
-
|
|
427
|
-
Installed, the command is `rigc`. The examples below spell it `bun cli.ts`
|
|
428
|
-
because they are written from a clone of this repository (`bun install`, then run
|
|
429
|
-
the CLI in place); the two are interchangeable β `rigc build β¦` is
|
|
430
|
-
`bun cli.ts build β¦`.
|
|
431
|
-
|
|
432
|
-
Two commands are repository workflows rather than package ones: `bench` and
|
|
433
|
-
`check` measure against Spine's official example projects and the reference
|
|
434
|
-
frames rendered from them, which are fetched rather than redistributed (see
|
|
435
|
-
[NOTICE.md](NOTICE.md)). They need a clone and `bun run fetch-examples`, and say
|
|
436
|
-
so by name when the corpus is absent.
|
|
437
|
-
|
|
438
|
-
## First rig in ten minutes
|
|
439
|
-
|
|
440
|
-
A whole rig, end to end, in a scratch directory: three tiny plates, two JSON
|
|
441
|
-
files, one `build`, one `validate`. No clone, no art pipeline, nothing fetched.
|
|
442
|
-
|
|
443
|
-
π« **Every value below is invented for this section** β a doll that exists
|
|
444
|
-
nowhere else in this repository. That is [AUTHORING.md](docs/AUTHORING.md) Β§3's
|
|
445
|
-
rule applied here: no example value in these documents is copied out of a
|
|
446
|
-
reference export, so nothing you read in a quickstart is an answer to anything
|
|
447
|
-
[the ladder](docs/LADDER.md) measures.
|
|
448
|
-
|
|
449
|
-
**1. Install the command.**
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
bun add -g spine-rigc # installs `rigc`
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
Or skip the install and prefix every command below with `bunx `, e.g.
|
|
456
|
-
`bunx spine-rigc build β¦`.
|
|
457
|
-
|
|
458
|
-
**2. Make a directory and three plates.** rigc measures PNGs rather than trusting
|
|
459
|
-
a number you typed (R5), so the art has to exist. These three are solid colours a
|
|
460
|
-
few dozen pixels across β a hull, a mast and a lamp:
|
|
461
|
-
|
|
462
|
-
```bash
|
|
463
|
-
mkdir -p buoy/images && cd buoy
|
|
464
|
-
bun -e '
|
|
465
|
-
const parts = {
|
|
466
|
-
"images/hull.png": "iVBORw0KGgoAAAANSUhEUgAAADgAAAAMCAYAAAA3bX6lAAAAKElEQVR42mOI8bL6P5wxw6gHRz046sFRD456cNSDox4c9eCoBwcrBgDSZ+mdl2OiDgAAAABJRU5ErkJggg==",
|
|
467
|
-
"images/mast.png": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAA0CAYAAAC3t3ldAAAAH0lEQVR42mO4dunIf3yYYVTBqIJRBaMKRhWMKhgcCgBGJo4s9YnopgAAAABJRU5ErkJggg==",
|
|
468
|
-
"images/lamp.png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAHElEQVR42mP4v8HhPzUww6hBowaNGjRq0HAzCADvdrVmFPbc+QAAAABJRU5ErkJggg=="
|
|
469
|
-
};
|
|
470
|
-
for (const [p, b] of Object.entries(parts)) await Bun.write(p, Buffer.from(b, "base64"));
|
|
471
|
-
'
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
**3. The rig spec β `buoy.rig.json`.** Structure only: bones, the slots array in
|
|
475
|
-
draw order, and one skin mapping each slot to a plate.
|
|
476
|
-
|
|
477
|
-
```json
|
|
478
|
-
{
|
|
479
|
-
"spec": "rigc-rig/1",
|
|
480
|
-
"name": "buoy",
|
|
481
|
-
"images": "images",
|
|
482
|
-
"skeleton": { "width": 200, "height": 200 },
|
|
483
|
-
"bones": [
|
|
484
|
-
{ "name": "root" },
|
|
485
|
-
{ "name": "hull", "parent": "root", "x": 0, "y": 0 },
|
|
486
|
-
{ "name": "mast", "parent": "hull", "x": 0, "y": 4 },
|
|
487
|
-
{ "name": "lamp", "parent": "mast", "x": 0, "y": 52 }
|
|
488
|
-
],
|
|
489
|
-
"slots": [
|
|
490
|
-
{ "name": "mast", "bone": "mast", "attachment": "mast" },
|
|
491
|
-
{ "name": "hull", "bone": "hull", "attachment": "hull" },
|
|
492
|
-
{ "name": "lamp", "bone": "lamp", "attachment": "lamp" }
|
|
493
|
-
],
|
|
494
|
-
"skins": {
|
|
495
|
-
"default": {
|
|
496
|
-
"mast": { "mast": { "image": "mast.png", "y": 26 } },
|
|
497
|
-
"hull": { "hull": { "image": "hull.png" } },
|
|
498
|
-
"lamp": { "lamp": { "image": "lamp.png" } }
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
Three things in there are worth naming, because each is a rule rather than a
|
|
505
|
-
style: the **slots array is the setup draw order** (R4) β index 0 is furthest
|
|
506
|
-
back, so the mast is behind the hull; the attachment carries an **`image`
|
|
507
|
-
instead of a `width`/`height`** (R5), which is what makes the size in the
|
|
508
|
-
skeleton and the size in the atlas incapable of drifting apart; and the mast's
|
|
509
|
-
`"y": 26` offsets the plate *within* its slot so the bone sits at the mast's foot
|
|
510
|
-
rather than its middle.
|
|
511
|
-
|
|
512
|
-
**4. The motion spec β `buoy.motion.json`.** Time only, aimed at the rig by name:
|
|
513
|
-
|
|
514
|
-
```json
|
|
515
|
-
{
|
|
516
|
-
"spec": "rigc-motion/1",
|
|
517
|
-
"archetype": "buoy",
|
|
518
|
-
"cut": "buoy",
|
|
519
|
-
"easings": { "swing": [0.42, 0, 0.58, 1] },
|
|
520
|
-
"animations": {
|
|
521
|
-
"bob": {
|
|
522
|
-
"duration": 2,
|
|
523
|
-
"loop": true,
|
|
524
|
-
"tracks": [
|
|
525
|
-
{
|
|
526
|
-
"bone": "hull",
|
|
527
|
-
"property": "translatey",
|
|
528
|
-
"keys": [
|
|
529
|
-
{ "t": 0, "v": [0], "ease": "swing" },
|
|
530
|
-
{ "t": 0.5, "v": [5], "ease": "swing" },
|
|
531
|
-
{ "t": 1.5, "v": [-5], "ease": "swing" },
|
|
532
|
-
{ "t": 2, "v": [0] }
|
|
533
|
-
]
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
"bone": "mast",
|
|
537
|
-
"property": "rotate",
|
|
538
|
-
"keys": [
|
|
539
|
-
{ "t": 0, "v": [-6], "ease": "swing" },
|
|
540
|
-
{ "t": 1, "v": [6], "ease": "swing" },
|
|
541
|
-
{ "t": 2, "v": [-6] }
|
|
542
|
-
]
|
|
543
|
-
}
|
|
544
|
-
]
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
`archetype` must equal the rig's `name`. `duration` is declared and then checked
|
|
551
|
-
against what actually compiled (R7). The **last key of each track carries no
|
|
552
|
-
easing** β there is nothing after it to ease towards, and saying otherwise is a
|
|
553
|
-
compile error.
|
|
554
|
-
|
|
555
|
-
**5. Build, then re-gate what it wrote.**
|
|
556
|
-
|
|
557
|
-
```bash
|
|
558
|
-
rigc build --rig buoy.rig.json --motion buoy.motion.json --images images --out spine
|
|
559
|
-
rigc validate spine
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
`build` prints every assertion by name, then the shape of what it emitted, then
|
|
563
|
-
the two files:
|
|
564
|
-
|
|
565
|
-
```
|
|
566
|
-
.. pages=3 regions=3 bones=4 slots=3 animations=1 version=4.3.13 regionAttachments=3 meshAttachments=0 physicsConstraints=0 rig=buoy profile=spine-html
|
|
567
|
-
rigc: wrote β¦/buoy/spine/skeleton.json
|
|
568
|
-
rigc: wrote β¦/buoy/spine/skeleton.atlas
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
and `validate` re-reads those artifacts from disk and ends `rigc: green`. That is
|
|
572
|
-
a rig. `spine/skeleton.json` is Spine 4.3 skeleton data β it loads in a Spine
|
|
573
|
-
runtime and it imports into the Spine editor.
|
|
574
|
-
|
|
575
|
-
**Try breaking it**, because the validator's messages are the interface here and
|
|
576
|
-
they are worth meeting once on purpose. Rename `images/hull.png` to
|
|
577
|
-
`images/raft.png`, point the spec's `image` at the new name, and build again:
|
|
578
|
-
|
|
579
|
-
```
|
|
580
|
-
FAIL A08_REGION_NAMES_MATCH_ATTACHMENTS: attachment "hull" resolves to region "raft"; v0 requires them identical
|
|
581
|
-
rigc: 1 assertion(s) failed β nothing written
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
Nothing was written. A red run leaves no half-built artifact on disk to mistake
|
|
585
|
-
for a result, and there is no flag that changes that.
|
|
586
|
-
|
|
587
|
-
**Where to go next.**
|
|
588
|
-
|
|
589
|
-
- π **[docs/AUTHORING.md](docs/AUTHORING.md)** is the real guide β both files
|
|
590
|
-
field by field, the emission rules, every named failure mapped to the file that
|
|
591
|
-
has to change, and Β§8βΒ§9 for reproducing a shot you were given as pictures. It
|
|
592
|
-
ships inside the npm package too, at
|
|
593
|
-
`node_modules/spine-rigc/docs/AUTHORING.md`.
|
|
594
|
-
- `rigc explain --rig buoy.rig.json --motion buoy.motion.json --out spine` prints
|
|
595
|
-
the compiled rig as a table β every bone with its resolved parent, the slots in
|
|
596
|
-
draw order, every timeline key by key β and writes nothing. It is what to reach
|
|
597
|
-
for when a rig compiles and still looks wrong.
|
|
598
|
-
- π¨ **A green gate does not mean the animation is right**, and no assertion
|
|
599
|
-
could. If you have reference pictures of the shot,
|
|
600
|
-
`rigc check --candidate spine --frames <dir>` is the half of the loop that can
|
|
601
|
-
see a wrong animation β AUTHORING.md Β§9.
|
|
602
|
-
- [docs/LADDER.md](docs/LADDER.md) is the benchmark: the same job, from a brief
|
|
603
|
-
and rendered frames, scored. [docs/PILOT.md](docs/PILOT.md) is how to run an
|
|
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.
|
|
608
|
-
|
|
609
692
|
## Usage
|
|
610
693
|
|
|
611
694
|
π **Writing a spec? Read [docs/AUTHORING.md](docs/AUTHORING.md) first.** It is the
|
|
@@ -629,6 +712,11 @@ bun cli.ts build \
|
|
|
629
712
|
[--manifest path/to/manifest.json] [--images path/to/images]
|
|
630
713
|
```
|
|
631
714
|
|
|
715
|
+
By default, atlas page paths point back at the source art wherever it lives β
|
|
716
|
+
often outside `--out` β so add `--copy-images` when `spine/` itself needs to be
|
|
717
|
+
self-contained (zipped, committed, or handed off on its own): it copies every
|
|
718
|
+
referenced page PNG into `--out` and rewrites the atlas to match.
|
|
719
|
+
|
|
632
720
|
β¦or register cuts in a `cuts.json` and build them by name. Every path in the table
|
|
633
721
|
resolves **relative to the `cuts.json` file itself**, so the table lives with the
|
|
634
722
|
project that owns the art:
|
|
@@ -654,17 +742,24 @@ commands:
|
|
|
654
742
|
```bash
|
|
655
743
|
bun cli.ts explain --cut my_cut --cuts path/to/cuts.json # the compiled rig as a table
|
|
656
744
|
bun cli.ts validate path/to/spine # re-gate artifacts already on disk
|
|
657
|
-
bun cli.ts validate --profile spine path/to/
|
|
745
|
+
bun cli.ts validate --profile spine-html path/to/spine # β¦and this project's policy too (see Profiles)
|
|
658
746
|
bun cli.ts diff candidate.json reference.json # structural comparison
|
|
659
747
|
bun cli.ts check --candidate path/to/spine \
|
|
660
748
|
--frames bench/reference/3-timing-and-spacing # against pictures
|
|
661
749
|
bun cli.ts bench 3 --candidate path/to/spine # one rung of the ladder
|
|
750
|
+
bun cli.ts render --candidate path/to/spine # PNG frames + a contact sheet
|
|
751
|
+
bun cli.ts preview --candidate path/to/spine # one .html that plays it
|
|
662
752
|
```
|
|
663
753
|
|
|
664
754
|
`validate` on a bare directory checks what it can see. Adding `--cut`/`--cuts` lets
|
|
665
755
|
it re-derive the declared durations and the structural expectations too, and the
|
|
666
|
-
report says which it had. `build` and `validate` both
|
|
667
|
-
the
|
|
756
|
+
report says which it had. `build` and `validate` both default to `--profile spine`,
|
|
757
|
+
the 20 validity rules; `--profile spine-html` adds this project's renderer and
|
|
758
|
+
archetype policy on top.
|
|
759
|
+
|
|
760
|
+
`render` and `preview` are the two that need no reference at all β see
|
|
761
|
+
[Looking at a rig](#looking-at-a-rig--rigc-render-and-rigc-preview). Run either
|
|
762
|
+
straight after a green `build`, on the same directory `--out` wrote.
|
|
668
763
|
|
|
669
764
|
## Checks
|
|
670
765
|
|
|
@@ -675,7 +770,7 @@ bun run selftest # the validator's own negative controls (next section)
|
|
|
675
770
|
```
|
|
676
771
|
|
|
677
772
|
All three run on every push and pull request β
|
|
678
|
-
[`.github/workflows/ci.yml`](.github/workflows/ci.yml). Bun runs the sources
|
|
773
|
+
[`.github/workflows/ci.yml`](https://github.com/firejune/rigc/blob/main/.github/workflows/ci.yml). Bun runs the sources
|
|
679
774
|
directly, so the first two are not on the path of anything; they exist because a
|
|
680
775
|
convention nothing checks is a convention. `tsconfig.json` is
|
|
681
776
|
`strict: false` with `strictNullChecks: true` and says in place why the rest is
|
|
@@ -695,7 +790,7 @@ for each. Two further edits are *tolerance* controls the gate must let through,
|
|
|
695
790
|
because a widened assertion can fail by firing too often as easily as by firing too
|
|
696
791
|
rarely.
|
|
697
792
|
|
|
698
|
-
**The rigs it breaks are generated.** [`fixtures/public.ts`](fixtures/public.ts)
|
|
793
|
+
**The rigs it breaks are generated.** [`fixtures/public.ts`](https://github.com/firejune/rigc/blob/main/fixtures/public.ts)
|
|
699
794
|
writes three synthetic cuts into a temp directory on every run, and between them
|
|
700
795
|
they carry every structure the assertions have an opinion about β region
|
|
701
796
|
attachments, attachment swaps, rgba fades, a ring mesh on a control bone, a ribbon
|
|
@@ -761,7 +856,7 @@ nothing substantive executed exits 2 rather than printing green.
|
|
|
761
856
|
|
|
762
857
|
```
|
|
763
858
|
tsconfig.json type-check config (noEmit); eslint.config.js β the no-any gate
|
|
764
|
-
cli.ts build / validate / explain / diff / check / bench
|
|
859
|
+
cli.ts build / validate / explain / diff / check / bench / render / preview
|
|
765
860
|
selftest.ts the validator's own negative controls, and diff's and check's
|
|
766
861
|
fixtures/ public.ts β the three synthetic cuts the selftest breaks
|
|
767
862
|
src/
|
|
@@ -769,14 +864,17 @@ src/
|
|
|
769
864
|
rig.ts the rig spec β `spec: "rigc-rig/1"`, the skeleton as data
|
|
770
865
|
validate.ts spine-core round trip + the 34 assertions
|
|
771
866
|
diff.ts structural comparison of two skeletons, one ratio per measure
|
|
772
|
-
render.ts the rasteriser (regions + meshes), shared by the reference renderer
|
|
867
|
+
render.ts the rasteriser (regions + meshes), shared by the reference renderer,
|
|
868
|
+
`rigc render` and check
|
|
869
|
+
preview.ts the single-file HTML player page β the artifact embedded as data
|
|
870
|
+
URIs, played by the official Spine Web Player (referenced, not vendored)
|
|
773
871
|
check.ts a candidate against rendered frames β pixels and per-slot drift,
|
|
774
872
|
and it never opens the reference skeleton
|
|
775
873
|
ladder.ts which example is which rung, and which file in it is the reference
|
|
776
874
|
timelines.ts the 4.3 timeline catalogue and its walker (shared, pure JSON)
|
|
777
875
|
mesh.ts ring and ribbon mesh builders, weighted-vertex encoding
|
|
778
876
|
transform.ts crop pixels (y down) <-> Spine world (y up), world transforms
|
|
779
|
-
png.ts PNG header reader (size
|
|
877
|
+
png.ts PNG header reader (size, colour type, tRNS; no pixel decode)
|
|
780
878
|
errors.ts CompileError, and NotImplementedError for what the format holds
|
|
781
879
|
and the emitter does not write
|
|
782
880
|
types.ts manifest, motion spec, and emitted-JSON shapes
|
|
@@ -793,7 +891,8 @@ viewer/ the run viewer β dev server only, no build (see above)
|
|
|
793
891
|
vite.config.ts /api/inventory and /repo/<path>, and the build refusal
|
|
794
892
|
inventory.ts what is under bench/runs, resolved to URLs
|
|
795
893
|
main.ts the two panes, the transport, the report
|
|
796
|
-
docs/ AUTHORING.md (how to author a rig),
|
|
894
|
+
docs/ AUTHORING.md (how to author a rig), GATE.md (the clause statements
|
|
895
|
+
a candidate is graded against), LADDER.md (live rung status),
|
|
797
896
|
SPEC_COVERAGE.md (format survey),
|
|
798
897
|
feature_matrix.{csv,json}
|
|
799
898
|
.github/ workflows/ β ci.yml (the gates) and release.yml (release-please)
|
|
@@ -806,14 +905,14 @@ CONTRIBUTING.md how to propose a change; RELEASING.md β how a version is cut
|
|
|
806
905
|
| --- | --- |
|
|
807
906
|
| `measure_contact_depth.ts` | measures a cut's contact depth from its plates, with the two-sided proof it has to satisfy. Both slot names are required: which plate is the mass and which is the occluder is a fact about one cut, and a default would measure the wrong pair and still print a number |
|
|
808
907
|
| `contact.ts` | plate-vs-plate overlap measurement β the largest advance that keeps two footprints disjoint |
|
|
809
|
-
| `plate.ts` / `png_probe.mjs` | minimal PNG read/write and decode |
|
|
908
|
+
| `plate.ts` / `png_probe.mjs` | minimal PNG read/write and decode. The writer emits colour type 6 only; the reader takes every colour type and bit depth PNG allows except interlaced, expanding indexed palettes (`PLTE` + `tRNS`) and greyscale to RGBA β because the gate accepts that art, so the renderer has to as well (issue #226) |
|
|
810
909
|
| `font5x7.ts` | bitmap labels for diagnostic images and generated plates |
|
|
811
910
|
|
|
812
911
|
## Contributing
|
|
813
912
|
|
|
814
|
-
Issues are the ledger; see [CONTRIBUTING.md](CONTRIBUTING.md) for what a change
|
|
913
|
+
Issues are the ledger; see [CONTRIBUTING.md](https://github.com/firejune/rigc/blob/main/CONTRIBUTING.md) for what a change
|
|
815
914
|
has to clear before it lands. Releases are cut by release-please β
|
|
816
|
-
[RELEASING.md](RELEASING.md).
|
|
915
|
+
[RELEASING.md](https://github.com/firejune/rigc/blob/main/RELEASING.md).
|
|
817
916
|
|
|
818
917
|
## Licence
|
|
819
918
|
|