spine-rigc 1.0.0 → 1.0.2
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 +35 -71
- package/docs/AUTHORING.md +727 -1160
- package/docs/FACE.md +205 -371
- package/docs/INGEST.md +155 -305
- package/docs/MOTION.md +11 -27
- package/docs/PROMPTING.md +1 -1
- package/docs/RIGGING.md +10 -26
- package/docs/SPEC_COVERAGE.md +27 -871
- package/package.json +1 -1
- package/src/atlas.ts +9 -8
- package/src/compile.ts +1 -1
- package/src/generation.ts +2 -1
- package/src/ladder.ts +1 -1
- package/src/rig.ts +4 -4
- package/src/validate.ts +20 -14
package/docs/SPEC_COVERAGE.md
CHANGED
|
@@ -1,226 +1,20 @@
|
|
|
1
|
-
# Spine 4.3 export-format surface
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
> exports pass it.
|
|
19
|
-
> - **The profile split exists** — `--profile spine | spine-html`, **default `spine` since
|
|
20
|
-
> 2026-08-29** (issue #221; it was `spine-html` when this survey was written), so the nine
|
|
21
|
-
> renderer-policy assertions of §2.2 no longer apply to foreign data, and no longer apply
|
|
22
|
-
> unasked. That closes the validator half of B3; **the emitter half closed on 2026-09-03
|
|
23
|
-
> (issue #4)** — `build --pack` arranges the parts onto shared power-of-two pages written into
|
|
24
|
-
> `--out`, and `build --atlas-in <file.atlas>` resolves every part against the regions of a
|
|
25
|
-
> pack somebody else made. Both are opt-in and both are narrow: no trimming, no rotation
|
|
26
|
-
> (`rotate: 0` on every region rigc writes), no scaling, and a packed atlas is refused under
|
|
27
|
-
> `--profile spine-html`, whose A06 clause **is** the one-part-per-page convention. So §3-2's
|
|
28
|
-
> atlas table, §4-0's B3 row and its "no packer *and* no importer" both describe a state that
|
|
29
|
-
> no longer exists; what remains true of them is the DEFAULT, which is still one part per page.
|
|
30
|
-
> Limits and flag shapes: [AUTHORING §0.1–§0.2](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md).
|
|
31
|
-
> - **Part 4-2's ❌ rows for `path`, `slider` and per-skin members are gone (issues #2, #7)** —
|
|
32
|
-
> rigc emits path attachments and path constraints, sliders in both of their models, per-skin
|
|
33
|
-
> `bones` and constraint lists, and the `path` / `slider` timeline groups. Three assertions came
|
|
34
|
-
> with them (A36, A37, A38). The **corpus** measurements in part 3 are untouched and still true:
|
|
35
|
-
> none of these features appears in any of the twelve example files, which is why they were
|
|
36
|
-
> off the ladder and why nothing in it moved.
|
|
37
|
-
> - **`walkTimelines` reaches all eleven groups** — §2.3 item 1 and §4.3 item 4 are done, so
|
|
38
|
-
> A05 is no longer blind to `ik` / `transform` / `path` / `slider` / `drawOrder` /
|
|
39
|
-
> `drawOrderFolder` / `events`.
|
|
40
|
-
> - **B1 is closed (2026-08-22)** — the bone tree is no longer code. `src/archetype.ts` is gone
|
|
41
|
-
> and the skeleton is a **rig spec** (`spec: "rigc-rig/1"`, `src/rig.ts`): bones, slots, skins,
|
|
42
|
-
> attachments, the 4.3 typed constraint array and the invariants, all as data. So §2.4's
|
|
43
|
-
> "the bone tree is code, not data", §4.0's B1 row and executive-summary item 7 describe a
|
|
44
|
-
> state that no longer exists. Rung 3 transcribed into that format scores 1.000 on every
|
|
45
|
-
> measure of every section — figures in [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
|
46
|
-
> - Since that change rigc also emits **ik and transform constraints**, the **single-axis bone
|
|
47
|
-
> timelines** (`translatex`/`translatey`/`scalex`/`scaley`/`shear`/`shearx`/`sheary`), bone
|
|
48
|
-
> `length`/`scale`/`shear`/`inherit`/`skin`/`color` setup fields, slot `dark`/`blend`, region
|
|
49
|
-
> `path`/`scaleX`/`scaleY`/`color`, mesh `path`/`edges`/`color`, and the header's `fps` /
|
|
50
|
-
> `referenceScale` / `images`. Part 2's coverage tables predate all of it.
|
|
51
|
-
> - **A08's name-identity clause is retired (issue #574, 2026-09-17)** — so every line below
|
|
52
|
-
> that calls A08 *mixed* or names it as policy describes a rule that no longer exists:
|
|
53
|
-
> §2.1's `region` row ("the region name == attachment name == PNG basename convention
|
|
54
|
-
> **A08** + **A27** enforce" — A27 still enforces its half, A08 enforces none of it),
|
|
55
|
-
> §2.2's classification row and its "Nine assertions are renderer-profile or
|
|
56
|
-
> profile-mixed" paragraph, §4.1's row (c), and §4.3's item 2. A08 is plain **validity**
|
|
57
|
-
> now and identical under both profiles. The measurement behind it: `spine-html` resolves
|
|
58
|
-
> art through the attachment's `path` and keys its images on the atlas region name, in
|
|
59
|
-
> every published version of it, so the clause was a rule no renderer performed — and it
|
|
60
|
-
> fired on **0** attachments in this corpus, because no export in `examples/` carries a
|
|
61
|
-
> `path` field at all. The executive summary's item 8 is the one A08 line here that was
|
|
62
|
-
> already right and has only got more so: A08 is a Spine-validity rule.
|
|
63
|
-
> - **The executive summary's item 6 is superseded where it lists what rigc does
|
|
64
|
-
> not emit** — *"No IK, no transform constraint, no path, no slider, no deform,
|
|
65
|
-
> no drawOrder, no events"*. The first four are covered by the bullets above;
|
|
66
|
-
> the last three emit too, and each has an assertion over it
|
|
67
|
-
> (`A31_DRAW_ORDER_OFFSETS_RESOLVE`, `A32_EVENT_KEYS_RESOLVE`,
|
|
68
|
-
> `A35_DEFORM_KEYS_FIT_THE_ATTACHMENT`). What survives of that item is that the
|
|
69
|
-
> emitter covers a slice of the format, not which slice it covers.
|
|
70
|
-
> - **Part 4's *"Three of its items have been built since"* undercounts** — the
|
|
71
|
-
> bullets above already name more than three between them, and most of the
|
|
72
|
-
> requirement list under it has landed. Read it as *some*, and take which from
|
|
73
|
-
> these bullets and [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md);
|
|
74
|
-
> the requirement text itself is still left exactly as surveyed, which is what
|
|
75
|
-
> a dated record is for.
|
|
76
|
-
> - **"Things I could not verify" item 5 — whether the Spine editor itself would
|
|
77
|
-
> round-trip a rigc file — has been answered by measurement since.**
|
|
78
|
-
> `tools/editor_roundtrip.ts` ships inside the package, the registry carries
|
|
79
|
-
> `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP`, and [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md)
|
|
80
|
-
> records the trips by number. *"The round-trip proof in this project is
|
|
81
|
-
> spine-core-only"* was true on the date above and is not the state now.
|
|
82
|
-
>
|
|
83
|
-
> **The measurements of the CORPUS (Part 3) have not moved and stay as written.**
|
|
84
|
-
>
|
|
85
|
-
> 🔒 **Redacted for publication (2026-08-23), and nothing but names.** The note was written
|
|
86
|
-
> inside the sandbox of the project rigc was split out of, and it enumerated that project's
|
|
87
|
-
> archetype names, bone tree and slot table. Those are the consumer's, not this
|
|
88
|
-
> repository's, so they are replaced by the SHAPE they stood for — counts, parentage,
|
|
89
|
-
> structure — which is what every argument here actually rests on: §2.4's point is that
|
|
90
|
-
> eighteen bones were hard-coded and *no example fits them*, and that survives the names
|
|
91
|
-
> going. Where a number was measured it is still the measured number.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Executive summary (10 lines)
|
|
96
|
-
|
|
97
|
-
1. **The de facto 4.3 spec is `SkeletonJson.ts`, and it has no schema** — every field passes through
|
|
98
|
-
`getValue(map, key, default)` (`SkeletonJson.ts:1404-1406`), so anything unknown is ignored and
|
|
99
|
-
anything missing silently becomes a default. There is no version gate in the JSON path.
|
|
100
|
-
2. **The official JSON doc is still 3.8.24** (verified today, one version token on the page) and it
|
|
101
|
-
documents a format shape that 4.3 *no longer reads*: top-level `"ik"`/`"transform"`/`"path"`
|
|
102
|
-
arrays, bone `"transform"`, `"draworder"` all-lowercase. Where doc and parser disagree, **the
|
|
103
|
-
parser wins**; writing to the doc produces a file that loads clean and is wrong.
|
|
104
|
-
3. **The atlas and binary docs, by contrast, are current 4.x** — the atlas page documents
|
|
105
|
-
`bounds:`/`offsets:`/`pma:`, which is the compact form the 4.3 reader prefers.
|
|
106
|
-
4. **4.3 is the biggest format break of the 4.x line**, and I verified the transition points against
|
|
107
|
-
the 4.0/4.1/4.2 parsers: 4.1 added `sequence`, 4.2 added `physics` + `referenceScale` and renamed
|
|
108
|
-
bone `transform`→`inherit`, 4.3 collapsed all constraints into one `constraints[]` array with a
|
|
109
|
-
`type` discriminator, added the `slider` constraint, `drawOrderFolder`, bone icons, and rebuilt
|
|
110
|
-
the transform constraint around `source` + `properties`.
|
|
111
|
-
5. The full 4.3 JSON surface is **7 attachment types, 5 constraint types, 37 timeline kinds,
|
|
112
|
-
3 curve encodings** — enumerated with line numbers in Part 1.
|
|
113
|
-
6. **rigc emits a narrow slice of it**: 2 of 7 attachment types (region, weighted mesh), 1 of 5
|
|
114
|
-
constraint types (physics), and **7 of 37 timeline kinds** (`rotate`/`translate`/`scale` on bones,
|
|
115
|
-
`attachment`/`rgba` on slots, `mix`/`reset` on physics). One skin, named `default`. No IK, no
|
|
116
|
-
transform constraint, no path, no slider, no deform, no drawOrder, no events, no sequences.
|
|
117
|
-
7. rigc's **input model is narrower than its emitter**: the bone tree is *code*
|
|
118
|
-
(`src/archetype.ts`, three hard-coded archetypes), not data. That is the single largest blocker
|
|
119
|
-
for the example ladder — **no example project fits any of the three.**
|
|
120
|
-
8. rigc's 31 assertions split into two kinds that must be separated before the ladder:
|
|
121
|
-
**Spine-validity rules** (A00–A05, A08, A17, A27 — these catch real silent corruption) and
|
|
122
|
-
**renderer/canvas-profile rules for spine-html** (A06 no-rotate/no-PMA, A11 no clipping, A12 no
|
|
123
|
-
dark colour, A13 ≤4 mesh slots ≤80 tris, A14 no full-frame mesh, A19 alpha, A20 meshes must be
|
|
124
|
-
weighted). The second group is a *policy*, not a spec, and must become a switch.
|
|
125
|
-
9. The validator is **structurally blind** to whole timeline groups: `walkTimelines`
|
|
126
|
-
(`src/validate.ts:1159-1199`) only descends `bones`/`slots`/`physics`/`attachments`. An `ik`,
|
|
127
|
-
`transform`, `path`, `slider`, `drawOrder`, or `events` block would pass A05 unexamined.
|
|
128
|
-
10. **The ladder uses far less of the format than the format holds** (12 skeleton files measured,
|
|
129
|
-
all `"4.3.75-beta"`, all flat-`constraints` shape): **no example uses a non-default skin**, path
|
|
130
|
-
constraint, slider, sequence, linked mesh, or dark colour. First appearances that matter:
|
|
131
|
-
drawOrder at rung 5, transform constraints + weighted meshes at rung 6, physics + deform at
|
|
132
|
-
rung 7, IK + clipping + events + bounding box + unweighted meshes only at spineboy. Three
|
|
133
|
-
blockers precede rung 1 — the bone tree being code, `A16`'s regex rejecting `4.3.75-beta`, and
|
|
134
|
-
the one-part-per-page atlas model against nine packed atlases. **Nothing was implemented *at the
|
|
135
|
-
time of writing*; see the banner above for what has been since.**
|
|
136
|
-
|
|
137
|
-
**Prior work this document extends, not repeats:** an earlier round of design notes covering parse
|
|
138
|
-
coverage (including the finding that the official JSON documentation is still 3.8), the round-trip
|
|
139
|
-
probe and the **six silent-failure cases** it measured — referred to below as cases 6a–6h — and a
|
|
140
|
-
coverage matrix of what one editor export actually contained. Those notes are design records held
|
|
141
|
-
by the project rigc was split out of, not files in this repository; every finding this document
|
|
142
|
-
leans on is restated here rather than cited. The wider question they did not ask is the one below:
|
|
143
|
-
what can the format hold at all, and what does the public benchmark corpus actually use.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Sources
|
|
148
|
-
|
|
149
|
-
| # | Source | Lines | Version |
|
|
150
|
-
| --- | --- | ---: | --- |
|
|
151
|
-
| S1 | [`spine-core/src/SkeletonJson.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/SkeletonJson.ts) | 1406 | matches the pinned `@esotericsoftware/spine-core@4.3.13` (case-label diff: identical) |
|
|
152
|
-
| S2 | [`spine-core/src/SkeletonBinary.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/SkeletonBinary.ts) | 1455 | idem |
|
|
153
|
-
| S3 | [`spine-core/src/TextureAtlas.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/TextureAtlas.ts) | 274 | idem |
|
|
154
|
-
| S4 | [`spine-core/src/Animation.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/Animation.ts) | 2560 | idem |
|
|
155
|
-
| S5 | `spine-core/src/` — `BoneData.ts`, `SlotData.ts`, `ConstraintData.ts`, `PathConstraintData.ts`, `attachments/Sequence.ts`, `Skin.ts`, `SkeletonData.ts`, `Utils.ts` | — | idem |
|
|
156
|
-
| S6 | `SkeletonJson.ts` on branches `4.0`, `4.1`, `4.2` | — | for the format-diff table below |
|
|
157
|
-
| S7 | https://esotericsoftware.com/spine-json-format | — | **3.8.24** |
|
|
158
|
-
| S8 | https://esotericsoftware.com/spine-binary-format | — | no version token on page |
|
|
159
|
-
| S9 | https://esotericsoftware.com/spine-atlas-format | — | no version token; content is 4.x |
|
|
160
|
-
| S10 | https://esotericsoftware.com/spine-loading-skeleton-data | — | — |
|
|
161
|
-
| S11 | [`spine-runtimes/CHANGELOG.md`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/CHANGELOG.md) | 3164 | — |
|
|
162
|
-
| S12 | [`spine-runtimes/README.md`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/README.md) | — | — |
|
|
163
|
-
| S13 | this repository | — | v0.1.0; `src/*.ts`, `cli.ts`, `selftest.ts`, `README.md`, `NOTICE.md` |
|
|
164
|
-
| S14 | the nine example projects under `examples/` (`bun run fetch-examples`) | — | all export `"spine": "4.3.75-beta"` |
|
|
165
|
-
|
|
166
|
-
**Citation convention.** Bare `:NNN` line numbers are into the branch-`4.3` TypeScript sources above,
|
|
167
|
-
which are also what `node_modules/@esotericsoftware/spine-core/src/<File>.ts` would hold —
|
|
168
|
-
⚠️ the published npm package ships **`dist/` only**, so the local copy to read is
|
|
169
|
-
`node_modules/@esotericsoftware/spine-core/dist/<File>.js`, whose line numbers differ. The two were
|
|
170
|
-
reconciled before publication: the parser's full `case` label set is identical in both.
|
|
171
|
-
|
|
172
|
-
**Marker convention used in Part 1:** 📘 = the field is described on an official Esoteric docs page.
|
|
173
|
-
🔍 = source-only; the parser reads it but no public doc page describes it.
|
|
174
|
-
|
|
175
|
-
### Verified doc-status findings
|
|
176
|
-
|
|
177
|
-
- **The JSON format page documents 3.8.24.** The only version token anywhere in the page body is
|
|
178
|
-
`"spine" : "3.8.24"` inside its skeleton example (S7). Plan 04 §1-0 recorded this on 2026-08-21;
|
|
179
|
-
**it is still true on 2026-08-22.** The page also still shows the 3.8-shaped
|
|
180
|
-
`"ik" : [ { "name" : "left leg" …` top-level array and a `"draworder" : { … }` key spelled
|
|
181
|
-
all-lowercase — the 4.3 parser reads `map.drawOrder`, camel-cased (`SkeletonJson.ts:1209`).
|
|
182
|
-
- **The binary page carries no version token at all** and is otherwise structured like the JSON page.
|
|
183
|
-
- **The atlas page is current.** It documents `bounds:` / `offsets:` (the 4.x compact form), `pma:`,
|
|
184
|
-
`index:`, `split:`, `pad:`, and `rotate:` as "`true` … or a number representing degrees from 0 to
|
|
185
|
-
360" — which is exactly what `TextureAtlas.ts:91-97` implements. **One gap:** the atlas doc does not
|
|
186
|
-
mention the page-level `scale:` line, yet every one of the nine official example atlases emits it
|
|
187
|
-
(Part 3) and the spine-ts reader silently discards it. Docs and parser are both incomplete here;
|
|
188
|
-
the *packer* is the only authority.
|
|
189
|
-
- **The CHANGELOG is not a format ledger for 4.x.** It records per-runtime API churn. The 4.0→4.3
|
|
190
|
-
JSON shape changes are not enumerated anywhere public; I derived them from the four parsers (S6),
|
|
191
|
-
table below.
|
|
192
|
-
|
|
193
|
-
### On third-party tools writing these formats
|
|
194
|
-
|
|
195
|
-
Esoteric's own documentation **invites** third-party tools to write these formats, in
|
|
196
|
-
three places:
|
|
197
|
-
|
|
198
|
-
- JSON format page (S7): *"Also, Spine can import data in this format, allowing interoperability with
|
|
199
|
-
other tools."* — https://esotericsoftware.com/spine-json-format
|
|
200
|
-
- Loading guide (S10): *"Other tools can also be used to pack a texture atlas in the Spine atlas
|
|
201
|
-
format, such as Texture Packer Pro using the "libgdx" atlas format."* —
|
|
202
|
-
https://esotericsoftware.com/spine-loading-skeleton-data
|
|
203
|
-
- Esoteric's own `spine-scripts` repo README: *"In addition to exporting images, some scripts also
|
|
204
|
-
write JSON data which can be imported into Spine."* —
|
|
205
|
-
https://github.com/EsotericSoftware/spine-scripts
|
|
206
|
-
|
|
207
|
-
What *is* stated as policy is a **version-lockstep expectation**, not a format restriction —
|
|
208
|
-
`spine-runtimes/README.md` §Versioning (S12): *"It is highly suggested to freeze the Spine editor
|
|
209
|
-
version to match the Spine Runtimes source being used and to update them in lock step."* Note the
|
|
210
|
-
spine-ts **JSON** path does **not** enforce this: `SkeletonJson` stores `skeletonData.version` and
|
|
211
|
-
never compares it (`SkeletonJson.ts:78`). Some other-language runtimes do gate on it (CHANGELOG
|
|
212
|
-
line 1678: *"Parsing skeleton .JSON and .skel files will report an error if the skeleton version does
|
|
213
|
-
not match the runtime version"*), so a wrong `skeleton.spine` string is portable-fragile even though
|
|
214
|
-
spine-ts tolerates it. rigc's A16 already pins it to `4.3.x`.
|
|
215
|
-
|
|
216
|
-
Separately, the licence chain matters for the ladder, and rigc's `NOTICE.md` states it as a
|
|
217
|
-
four-point chain rather than leaving it to be inferred: rigc's output **is** Spine skeleton data,
|
|
218
|
-
playing that data in a product needs **a Spine Runtime**, the Spine Runtimes License Agreement
|
|
219
|
-
requires **each user of such a product to hold a Spine Editor licence**, and rigc **links
|
|
220
|
-
spine-core** itself — so the same obligation covers running rigc at all. rigc therefore carries that
|
|
221
|
-
obligation forward rather than routing around it, and the interoperability the pages above invite is
|
|
222
|
-
taken on those terms. Reproducing the example projects does not change any of it; see the
|
|
223
|
-
per-example licence findings in Part 3.
|
|
1
|
+
# Spine 4.3 export-format surface
|
|
2
|
+
|
|
3
|
+
This page is the Spine 4.3 skeleton JSON and atlas text surface, read off the `spine-core`
|
|
4
|
+
this package pins (`@esotericsoftware/spine-core@4.3.13`): every field the parser reads, what it
|
|
5
|
+
defaults to, and the parser line each row cites. rigc's refusals point at its rows by part
|
|
6
|
+
number — `part 1-6` is §1.6. It is not a statement of what rigc emits; that is
|
|
7
|
+
[AUTHORING.md](AUTHORING.md).
|
|
8
|
+
|
|
9
|
+
**Sources.** S1 is [`spine-core/src/SkeletonJson.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/SkeletonJson.ts),
|
|
10
|
+
S2 [`SkeletonBinary.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/SkeletonBinary.ts)
|
|
11
|
+
and S3 [`TextureAtlas.ts`](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/spine-ts/spine-core/src/TextureAtlas.ts),
|
|
12
|
+
all on branch `4.3`; S6 is `SkeletonJson.ts` on branches `4.0`, `4.1` and `4.2`, for the
|
|
13
|
+
timeline below. Bare `:NNN` line numbers are into those TypeScript sources — ⚠️ the published npm
|
|
14
|
+
package ships **`dist/` only**, so the local copy to read is
|
|
15
|
+
`node_modules/@esotericsoftware/spine-core/dist/<File>.js`, whose line numbers differ; the
|
|
16
|
+
parser's full `case` label set is identical in both. 📘 = the field is described on an official
|
|
17
|
+
Esoteric docs page. 🔍 = source-only; the parser reads it but no public doc page describes it.
|
|
224
18
|
|
|
225
19
|
---
|
|
226
20
|
|
|
@@ -243,9 +37,8 @@ Evidence: `root.ik` present in 4.0/4.1/4.2 and absent in 4.3; `root.constraints`
|
|
|
243
37
|
4.0/4.1/4.2 and present in 4.3; `uniform` at `4.2 SkeletonJson.ts:161`, gone in 4.3; `target` at
|
|
244
38
|
`4.2:182,222` vs `source` at `4.3:188`.
|
|
245
39
|
|
|
246
|
-
⇒ **
|
|
247
|
-
|
|
248
|
-
with no error. Part 3 measures which shape each example actually ships.
|
|
40
|
+
⇒ **A file exported by a pre-4.3 editor carries the *legacy* shape, and the 4.3 parser drops
|
|
41
|
+
those constraints on the floor with no error.**
|
|
249
42
|
|
|
250
43
|
---
|
|
251
44
|
|
|
@@ -287,7 +80,7 @@ The whole `skeleton` block is optional (`if (skeletonMap)` at `:76`).
|
|
|
287
80
|
| `icon` | `undefined` | **4.3, editor-only affordance** | 🔍 |
|
|
288
81
|
| `iconSize` | `1` | 4.3 | 🔍 |
|
|
289
82
|
| `iconRotation` | `0` | 4.3 | 🔍 |
|
|
290
|
-
| ~~`visible`~~ | — | **NOT read from JSON.** `SkeletonBinary.ts:126` reads it (nonessential); the JSON parser has no equivalent. See §1.
|
|
83
|
+
| ~~`visible`~~ | — | **NOT read from JSON.** `SkeletonBinary.ts:126` reads it (nonessential); the JSON parser has no equivalent. See §1.11. | 🔍 |
|
|
291
84
|
|
|
292
85
|
### 1.3 Slots — `root.slots[]` (`:121-141`)
|
|
293
86
|
|
|
@@ -413,14 +206,13 @@ placeholder → name → path → atlas region.
|
|
|
413
206
|
There is no flag. If `vertices.length === verticesLength` (i.e. `uvs.length`, or `vertexCount<<1`)
|
|
414
207
|
it is read as **unweighted** x/y pairs; otherwise as the **weighted** run-length encoding
|
|
415
208
|
`boneCount, (boneIndex, bindX, bindY, weight) × boneCount, …`. A coincidental length match reads
|
|
416
|
-
weight data as coordinates.
|
|
417
|
-
example corpus contains both encodings.
|
|
209
|
+
weight data as coordinates. Editor exports carry both encodings.
|
|
418
210
|
|
|
419
211
|
🚨 The second risk in the same field is `boneIndex`: it is a position in the emitted bone array,
|
|
420
212
|
so the run means something different the moment the bone list changes, and nothing in the file
|
|
421
213
|
records what it used to mean. A rig spec therefore writes `weights` — the same data with the bones
|
|
422
214
|
**named** — and rigc encodes this run on emit. The raw form stays reachable behind
|
|
423
|
-
`"boneIndexing": "raw"` for transcribing an export verbatim.
|
|
215
|
+
`"boneIndexing": "raw"` for transcribing an export verbatim.
|
|
424
216
|
|
|
425
217
|
### 1.7 Events — `root.events` (object, not array) (`:469-484`)
|
|
426
218
|
|
|
@@ -500,7 +292,7 @@ rotate 4, translate 8, scale 8, shear 8, rgb 12, rgba 16, rgb2 24, rgba2 28, ik
|
|
|
500
292
|
path mix 12, alpha/scalex/deform/physics/slider 4.
|
|
501
293
|
|
|
502
294
|
**A short array is the format's nastiest silent failure**: `curve[i+3]` is `undefined`, the product
|
|
503
|
-
is `NaN`, and nothing throws
|
|
295
|
+
is `NaN`, and nothing throws. Timelines with no curve at all (`attachment`,
|
|
504
296
|
`inherit`, `sequence`, `drawOrder`, `events`, `physics reset`) ignore a `curve` key entirely.
|
|
505
297
|
|
|
506
298
|
The X axis is **seconds** in 4.x. It was frames in 3.8 — one more reason the 3.8 doc is actively
|
|
@@ -511,7 +303,7 @@ dangerous as a spec.
|
|
|
511
303
|
Reader rules (`:194-227`): entries are `key: v1, v2, v3, v4` with **at most four values**
|
|
512
304
|
(`:224`); a line with no colon terminates the current block (`:214`). A **blank line closes a page
|
|
513
305
|
block** (`:119-121`). Page names are `line.trim()` (`:123`) but **region names are the raw line**
|
|
514
|
-
(`:131`) — leading whitespace becomes part of the name.
|
|
306
|
+
(`:131`) — leading whitespace becomes part of the name.
|
|
515
307
|
|
|
516
308
|
**Header entries before the first page are read and silently discarded** (`:106-111` — the comment
|
|
517
309
|
says so literally).
|
|
@@ -523,7 +315,7 @@ says so literally).
|
|
|
523
315
|
| `filter: min, mag` | `minFilter`/`magFilter` (`:50-53`) | 📘 |
|
|
524
316
|
| `repeat: x\|y\|xy\|none` | `uWrap`/`vWrap` (`:54-57`) | 📘 |
|
|
525
317
|
| `pma: true\|false` | `page.pma` (`:58-60`) | 📘 |
|
|
526
|
-
| `scale: n` | 🚨 **emitted by the Spine texture packer, documented nowhere, and silently discarded by the reader.** There is no `pageFields.scale`, so `if (field) field(page)` at `:126-127` is a no-op.
|
|
318
|
+
| `scale: n` | 🚨 **emitted by the Spine texture packer, documented nowhere, and silently discarded by the reader.** There is no `pageFields.scale`, so `if (field) field(page)` at `:126-127` is a no-op. It records the export-time downscale factor; the runtime is expected to compensate via `SkeletonJson.scale` or `referenceScale`, not to read this line. | ❌ undocumented |
|
|
527
319
|
|
|
528
320
|
| Region field | Parsed into | Doc |
|
|
529
321
|
| --- | --- | --- |
|
|
@@ -560,642 +352,6 @@ emitter:
|
|
|
560
352
|
| Attachment colours | always read when present | only when nonessential (`:513`, `:600`, `:617`, `:630`) |
|
|
561
353
|
|
|
562
354
|
⇒ **Two things are JSON-inexpressible in 4.3 spine-ts: `bone.visible` and `skin.color`.** Both are
|
|
563
|
-
editor-affordance data with zero rendering effect
|
|
564
|
-
|
|
565
|
-
cannot.
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
## Part 2 — what rigc emits today
|
|
570
|
-
|
|
571
|
-
rigc v0.1.0, pinned to `@esotericsoftware/spine-core@4.3.13`. Line references in this Part are into
|
|
572
|
-
this repository (`src/`, `cli.ts`, `selftest.ts`) and were taken at commit `795e6f9`.
|
|
573
|
-
|
|
574
|
-
Legend: ✅ emits · 🟡 partial · ❌ not emitted · 🚫 deliberately excluded by a named assertion.
|
|
575
|
-
|
|
576
|
-
### 2.1 Coverage against Part 1
|
|
577
|
-
|
|
578
|
-
> 🔄 **Re-synced 2026-08-23** against `src/compile.ts`/`src/rig.ts` on `main`. Only the rows below
|
|
579
|
-
> were touched; the rest of this table was not re-verified in this pass.
|
|
580
|
-
|
|
581
|
-
**Header** (`compile.ts:634-646`)
|
|
582
|
-
|
|
583
|
-
| Part-1 row | rigc | Note |
|
|
584
|
-
| --- | --- | --- |
|
|
585
|
-
| `spine` | ✅ | constant `SPINE_VERSION = '4.3.13'` (`compile.ts:41`); A16 re-checks it is `4.3.x` |
|
|
586
|
-
| `x`, `y` | ✅ | always literal `0, 0` |
|
|
587
|
-
| `width`, `height` | ✅ | from `manifest.crop.w/h` |
|
|
588
|
-
| `hash` | ❌ | never emitted (harmless; the parser stores it and nothing reads it) |
|
|
589
|
-
| `fps`, `referenceScale` | ✅ | copied from `rig.skeleton` when the rig spec gives them, omitted when it does not (landed in `c5eda3b`) |
|
|
590
|
-
| `images` | ✅ | a declared `rig.skeleton.images` verbatim; otherwise the path from `--out` to the one directory the spec names every part PNG in — `--out` itself, spelled `../<basename>/`, under `--copy-images`, which overrides a declaration — so the editor's import finds them (issue #370). Omitted when the parts are spread over several directories, since no single path is true of all of them |
|
|
591
|
-
| `audio` | ❌ | |
|
|
592
|
-
|
|
593
|
-
**Bones** (`compile.ts:367-456`; type at `types.ts:285-292`)
|
|
594
|
-
|
|
595
|
-
| Part-1 row | rigc |
|
|
596
|
-
| --- | --- |
|
|
597
|
-
| `name`, `parent`, `x`, `y` | ✅ |
|
|
598
|
-
| `rotation` | ✅ — emitted only when non-zero; comes from `manifest.axis.deg` or a per-anchor facing angle, screen→Spine converted (`transform.ts`) |
|
|
599
|
-
| `length`, `scaleX`, `scaleY`, `shearX`, `shearY`, `inherit`, `skin`, `color` | ✅ — copied from the rig spec when it gives them, omitted when it does not |
|
|
600
|
-
| `icon` | ✅ — copied verbatim, unchecked; it is the editor's vocabulary, not rigc's (issue #47) |
|
|
601
|
-
| `iconSize`, `iconRotation` | ❌ |
|
|
602
|
-
| bone `visible` | ❌ (JSON-inexpressible anyway, §1.10) |
|
|
603
|
-
|
|
604
|
-
**Slots** (`compile.ts:466-524`; type at `types.ts:294-299`)
|
|
605
|
-
|
|
606
|
-
| Part-1 row | rigc |
|
|
607
|
-
| --- | --- |
|
|
608
|
-
| `name`, `bone` | ✅ |
|
|
609
|
-
| `attachment` | ✅ — only when the motion spec's `setup` block names one; `null` means "show nothing" and the key is omitted |
|
|
610
|
-
| `color` | ✅ — from `setup.color`, hex-encoded (`compile.ts:63-66`) |
|
|
611
|
-
| `dark` | ✅ — copied from the rig spec's slot when it gives one. 🚫 **A12_NO_DARK_COLOR** still names it, and that is a **renderer-profile** verdict rather than a validity one: `build` runs `--profile spine`, where A12 reports `PROF`. **A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN** reads it back off the runtime |
|
|
612
|
-
| `blend` | ✅ — copied from the rig spec's slot when it gives one (`compile.ts:600`, landed in `c5eda3b`) |
|
|
613
|
-
| `visible` | ❌ |
|
|
614
|
-
| draw order = slots array order | ✅, and **A26_SLOT_DRAW_ORDER** pins it to the archetype's `slotOrder` table |
|
|
615
|
-
|
|
616
|
-
**Skins** — ✅ named skins, each `{ name, bones?, ik?, transform?, path?, physics?, slider?, attachments }`
|
|
617
|
-
in the parser's own reading order; `default` is always emitted even when empty. ✅ per-skin `bones` and
|
|
618
|
-
per-type constraint lists (issue #7), which a rig spec writes in a skin entry's long form and rigc pairs
|
|
619
|
-
with the member's own `skin: true` — either half alone is refused, because `Skeleton.updateCache` starts a
|
|
620
|
-
`skinRequired` object inactive and activates only what the applied skin names (**A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED**).
|
|
621
|
-
❌ `color` (JSON-inexpressible).
|
|
622
|
-
|
|
623
|
-
**Attachments**
|
|
624
|
-
|
|
625
|
-
| Part-1 type | rigc | Detail |
|
|
626
|
-
| --- | --- | --- |
|
|
627
|
-
| `region` | 🟡 | emits `width`, `height` (always, from PNG measurement — the fix for case 6c), `x`, `y` (only when non-zero), `rotation` (only when non-zero, cancelling the bone's world rotation), `scaleX`/`scaleY` (`compile.ts:1238-1239`), `color`, and `path` — taken from the rig spec, else derived from the image basename and omitted when that basename *is* the attachment name (the region name == attachment name == PNG basename convention **A08** + **A27** enforce). ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row |
|
|
628
|
-
| `mesh` | 🟡 | emits `type`, `uvs`, `triangles`, `vertices` (**weighted encoding only**), `hull`, `width`, `height`, `edges` (`compile.ts:1343`, and part 4's rung-6 entry measures it byte-identical to the reference), `path`, `color`. ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row. Unweighted meshes are 🚫 **A20_MESH_WEIGHTS_COHERENT** (`validate.ts:430-433`) |
|
|
629
|
-
| `linkedmesh` | 🟡 | emits `type`, `source`, its own `path`, `width`, `height` and `color`, and `slot`/`skin`/`timelines` **only where they differ from the parser's defaults** (this attachment's slot, the default skin, true). ✅ `sequence` since [#729](https://github.com/firejune/rigc/issues/729) — see the `sequence` block row. Geometry keys on a link are refused by name — the parser returns before `readVertices`, so it reads none of them. A chain (a `source` that is itself a link) and a link to itself are refused: they resolve in file order and load `worldVerticesLength` 0 in one of the two orders, silently. `A21`/`A28` leave a link out (its rim and rows are its source's), `A04`/`A20`/`A22` read it as the mesh it resolves to, `A13` counts it as a mesh slot of its own ([#691](https://github.com/firejune/rigc/issues/691)) |
|
|
630
|
-
| `boundingbox` | ✅ | `vertexCount` (required and cross-checked), `vertices` **or** by-name `weights`, `color`. **A33_VERTEX_ATTACHMENT_GEOMETRY** |
|
|
631
|
-
| `path` | ✅ | `vertexCount` (required, and checked as a multiple of 3 — the parser's own `vertexCount / 3` takes a fractional size in silence), `vertices` **or** by-name `weights`, `closed`, `constantSpeed`, `color`, and `lengths` **stated or measured** (#804): a stated array — `vertexCount / 3` entries, which is what `ingest` carries from an export — is emitted as stated; an omitted one is the cumulative setup length at the end of each curve over the closed chain, taken through each influence's own bone on the unconstrained setup pose and measured as `PathConstraint` measures it — its own four-sample forward difference, about 0.5 % below the arc (AUTHORING §10.6). **A33_VERTEX_ATTACHMENT_GEOMETRY** re-checks the structure and the array's monotonicity. A `deform` timeline may key one: the array is the control points, `A39` SKIPs on it (no triangles), and `lengths` stays the setup measurement because the format has nowhere to put a per-key one — which only a `constantSpeed: false` traversal reads (AUTHORING §4.11) |
|
|
632
|
-
| `point` | ❌ | deliberately deferred — never appears in the corpus (part 3-1) |
|
|
633
|
-
| `clipping` | ✅ under `--profile spine` · 🚫 under `spine-html` | `end` (refused when it names no slot), `convex`, `inverse`, `vertexCount`, geometry, `color`. **A33**, and **A11_NO_CLIPPING_ATTACHMENTS** is the renderer-profile refusal |
|
|
634
|
-
| `sequence` block | ✅ | on a region, a mesh or a linked mesh: `count` (required — the parser's 0 loads no region), `start`, `digits`, `setup`, emitted as stated. The frames are the regions `<path><start + i>` zero-padded to `digits`, each atlased by name — the loose route's PNG of that name, or `--atlas-in`'s region — and a missing frame is refused with its number and the name looked for. A `setup` past the end (clamped), a fraction, an `image` or a `generator` beside it, and a `sequence` on any other kind are refused by name. **A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES** ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
635
|
-
|
|
636
|
-
Across all six types rigc emits, the attachment's own **`name`** is written exactly when the rig
|
|
637
|
-
spec states it, verbatim, as the attachment's first key — and never derived
|
|
638
|
-
([#796](https://github.com/firejune/rigc/issues/796)). Part 1-5 above states why it matters:
|
|
639
|
-
`name` defaults to the placeholder and `path` defaults to `name`, so it is the runtime's
|
|
640
|
-
`Attachment.name` and, on a type that draws, the region it resolves; nothing resolves an
|
|
641
|
-
attachment by it. From [#541](https://github.com/firejune/rigc/issues/541) to #796 rigc composed
|
|
642
|
-
`<skin>/<placeholder>` for a placeholder several skins fill, on the reading that a linked mesh
|
|
643
|
-
resolves its source by name; it resolves it by skin, slot and key, and the editor imports the
|
|
644
|
-
uncomposed shape. ⚠️ The **`default` skin may not be one of the skins sharing a placeholder**, and
|
|
645
|
-
that is a `CompileError` rather than an emission rule: the editor holds no placeholder the default
|
|
646
|
-
skin shares with a named one in either spelling — named, the export re-keys the attachment by its
|
|
647
|
-
name and the slot's setup attachment stops resolving; unnamed, the import is refused with
|
|
648
|
-
`Multiple attachments have the same name` ([#567](https://github.com/firejune/rigc/issues/567),
|
|
649
|
-
Spine 4.3.26, round trips 7 and 8). Of the twelve editor exports in `examples/`, **0**
|
|
650
|
-
attachments state a `name`, because all twelve declare one skin.
|
|
651
|
-
|
|
652
|
-
Mesh geometry is generated by exactly three procedural generators (`mesh.ts`): `buildRingMesh`
|
|
653
|
-
(three concentric rings + hub, outer two pinned), `buildRibbonMesh` (a two-wide strip along a bone
|
|
654
|
-
chain) and `buildContourMesh` (marching-squares trace of the part's own alpha → Douglas-Peucker →
|
|
655
|
-
mitred outset → ear clipping, every vertex pinned to the slot bone). The triangulator takes a
|
|
656
|
-
**simple** polygon only: holes are enclosed rather than cut out, and a self-intersecting or
|
|
657
|
-
diagonally pinched silhouette is refused by name (AUTHORING §3.4). There is still no importer for
|
|
658
|
-
editor-made meshes — those arrive as authored `uvs`/`triangles`/`weights`.
|
|
659
|
-
|
|
660
|
-
**Constraints** (`compile.ts:526-568`)
|
|
661
|
-
|
|
662
|
-
| Part-1 type | rigc |
|
|
663
|
-
| --- | --- |
|
|
664
|
-
| `physics` | ✅ — full field set: `bone`, the five components `x`/`y`/`rotate`/`scaleX`/`shearX`, `scaleY` (ScaleYMode), `inertia`/`strength`/`damping`/`mass`/`wind`/`gravity`/`mix`/`fps`/`limit`, the seven `*Global` flags and `skin` (`compile.ts:1522-1548`). A field the rig spec does not give is **omitted**, never guessed (`compile.ts:1463-1468`), so the parser's own default stands. The motion spec's `physics` tuning table is the second path into this same array and is narrower: no `scaleY`, no `*Global`, no `skin`, and it drops a value that equals the parser default even when the table gives it (`compile.ts:758-761`) |
|
|
665
|
-
| `ik` | ✅ — `bones`, `target`, `scaleY`/`mix`/`softness`/`bendPositive`/`compress`/`stretch`/`skin` (`compile.ts:1425-1430`, landed in `c5eda3b`) |
|
|
666
|
-
| `transform` | ✅ — `bones`, `source`, the 4.3 `properties{from→to}` model, and the full field set (`compile.ts:1431-1462`, landed in `c5eda3b`) |
|
|
667
|
-
| `path` | ✅ — `bones`, `slot`, `positionMode`/`spacingMode`/`rotateMode` (checked against the enum names `Utils.enumValue` can resolve, where only the first letter's case is free), `rotation`, `position`, `spacing`, `mixRotate`/`mixX`/`mixY`, `skin`. The slot must carry a path attachment in some skin: `PathConstraint.update` returns on its first line otherwise, so the constraint loads and moves nothing (**A36_PATH_CONSTRAINT_EFFECTIVE**) |
|
|
668
|
-
| `slider` | ✅ — `animation` (an animation the MOTION spec declares; the parser resolves it in a second pass and throws on a miss), `mix`, `additive`, `loop`, and one of the two models `bone` switches between: `bone` + `property`/`from`/`to`/`scale`/`max`/`local`, or the bone-less `time`. The losing model's fields are refused rather than emitted, because the parser reads each set only inside its own branch (**A37_SLIDER_CONSTRAINT_EFFECTIVE** covers the rest) |
|
|
669
|
-
| flat `constraints[]` shape | ✅ — and **A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS** actively rejects the 4.1/4.2 shape (`validate.ts:225-235`), which is the machine form of case 6a |
|
|
670
|
-
|
|
671
|
-
**Timelines** (`compile.ts:570-631`, `:877-936`, `:981-1041`)
|
|
672
|
-
|
|
673
|
-
| Part-1 group | rigc |
|
|
674
|
-
| --- | --- |
|
|
675
|
-
| `bones.rotate` / `translate` / `scale` | ✅ (`BONE_TRACKS`, `compile.ts:75-79`) |
|
|
676
|
-
| `bones.translatex/y`, `scalex/y`, `shear`, `shearx/y`, `inherit` | ❌ emitted (🟡 the **validator** knows their channel counts, `validate.ts:83-95`, so a hand-written file with them would pass A05) |
|
|
677
|
-
| `slots.attachment` | ✅ — value `name`, nullable; easing is refused |
|
|
678
|
-
| `slots.rgba` | ✅ |
|
|
679
|
-
| `slots.rgb`, `alpha` | ✅ — `rgb` is `{time, color}` with `color` in six hex digits and `[r, g, b]` in the motion spec; `alpha` is `{time, value}`, a number, `[a]` in the spec and refused outside 0..1. Each is emitted as itself and never folded into `rgba` ([#730](https://github.com/firejune/rigc/issues/730)); two colour tracks of one slot that share a channel are refused at compile, and **A45** holds the same in a foreign file and poses each key |
|
|
680
|
-
| `slots.rgba2` | ✅ — `{time, light, dark}`, seven channels as `[lr, lg, lb, la, dr, dg, db]` in the motion spec ([#690](https://github.com/firejune/rigc/issues/690)). 🚫 **A12_NO_DARK_COLOR** names it under `spine-html` only; **A43** poses it and compares |
|
|
681
|
-
| `slots.rgb2` | ✅ — `{time, light, dark}`, six hex digits each, `[lr, lg, lb, dr, dg, db]` in the motion spec; the slot must declare a setup `dark` ([#730](https://github.com/firejune/rigc/issues/730)). 🚫 **A12_NO_DARK_COLOR** under `spine-html`; **A43** poses it and compares |
|
|
682
|
-
| `physics.inertia/strength/damping/mass/wind/gravity`, `physics.mix`, `physics.reset` | ✅ — all eight (`PHYSICS_TRACKS` in `compile.ts`), authored as `tracks` entries naming `physics`. ⚠️ Their per-key defaults are the parser's, and part 1-8 above is the source: 0 on the six, 1 on `mix`. Not the constraint defaults at `:306-312` |
|
|
683
|
-
| `ik`, `transform` | ✅ — one unnamed timeline per constraint, keyed by the motion spec's `ik` / `transform` arrays |
|
|
684
|
-
| `path.position/spacing/mix`, `slider.time/mix` | ✅ (`PATH_TRACKS` / `SLIDER_TRACKS`) — authored as `tracks` entries naming `path` or `slider`, the same shape as `physics`, since both groups put named timelines under a constraint name. `path.mix` is one timeline of three channels |
|
|
685
|
-
| `attachments.<skin>.…deform` | ❌ (validator knows it: 1 channel, `validate.ts:104-107`) |
|
|
686
|
-
| `attachments.…sequence` | ✅ — the motion spec's per-animation `sequence` array of `{ skin?, slot, attachment, keys: [{ t, mode?, index?, delay? }] }`, each field emitted only where stated. A mode outside the seven (read as `hold`), a fractional or out-of-range `index` (truncated, clamped), an advancing mode at an effective delay of 0 (never advances), an attachment with no `sequence` block (every mode shows its one region) and a linked mesh that plays its source's timelines (the key is applied to nothing) are refused by name. **A46** poses every key at mid-frame samples against the file's own statement ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
687
|
-
| `drawOrder` | ✅ — the motion spec's per-animation `drawOrder` array of `{ t, offsets: [{ slot, offset }] }` (`compile.ts:823`, `:850`, `compileDrawOrder` `:1918-1970`). A key with no `offsets`, and a key with an empty one, both emit the parser's reset-to-setup encoding, so two spellings cannot make two files. An unknown slot, a slot offset twice in one key, a non-whole offset and a landing outside the emitted slots are each refused by name, and **A31_DRAW_ORDER_OFFSETS_RESOLVE** re-checks the emitted file |
|
|
688
|
-
| `drawOrderFolder` | ❌ — editor bookkeeping, 4.3-only |
|
|
689
|
-
| `events` (+ the `root.events` block) | ✅ — the rig spec declares the names (`rig.ts` `RigEvent`), the motion spec's per-animation `events` array fires them, and **A32_EVENT_KEYS_RESOLVE** checks the three ways the timeline goes wrong quietly. `int`/`float`/`string` overrides and `audio`/`volume`/`balance` all round-trip |
|
|
690
|
-
| animation `color` | ❌ |
|
|
691
|
-
|
|
692
|
-
**Curves** — ✅ all three encodings. Linear = no `curve` key; `"stepped"` passes through as the string
|
|
693
|
-
(`compile.ts:917-918`); bezier is computed from **named easings** (`motion.easings`, four normalised
|
|
694
|
-
handles) into **absolute (time, value) control points**, `bezierForChannel` (`compile.ts:121-135`),
|
|
695
|
-
emitted 4 numbers × channel count in field order. This is the correct transform — writing the
|
|
696
|
-
normalised handles straight in is the silent bug that costs an authoring loop.
|
|
697
|
-
|
|
698
|
-
**Atlas** (`compile.ts:346-365`) — one part per page, no packer.
|
|
699
|
-
|
|
700
|
-
| Part-1 field | rigc |
|
|
701
|
-
| --- | --- |
|
|
702
|
-
| page name | ✅ (PNG path relative to the atlas file) |
|
|
703
|
-
| `size: w, h` | ✅ from PNG measurement (`png.ts`), re-checked against the file by **A06** |
|
|
704
|
-
| `filter: Linear, Linear` | ✅ hard-coded |
|
|
705
|
-
| `pma: false` | ✅ hard-coded; a `true` is 🚫 **A06** |
|
|
706
|
-
| `format:`, `repeat:` | ❌ (both optional; `format` is discarded by the reader anyway) |
|
|
707
|
-
| region `bounds: 0,0,w,h` + `offsets: 0,0,w,h` | ✅ — the 4.x compact form |
|
|
708
|
-
| region `rotate: 0` | ✅ always zero; anything else is 🚫 **A06** ("there is no packer, so nothing can be") |
|
|
709
|
-
| region `index:`, `split:`, `pad:` | ❌ |
|
|
710
|
-
| multi-region pages | ❌ by construction; **A06** requires each region's UVs to be exactly (0,0)-(1,1) |
|
|
711
|
-
|
|
712
|
-
**Binary `.skel`** — ❌ not emitted, and out of scope: rigc's whole validation strategy is a JSON
|
|
713
|
-
round-trip through spine-core.
|
|
714
|
-
|
|
715
|
-
### 2.2 The assertions, classified
|
|
716
|
-
|
|
717
|
-
This is the split Part 4(c) needs. **Spine-validity** = the file is wrong for any consumer.
|
|
718
|
-
**Renderer-profile** = the file is valid Spine but violates a spine-html / canvas-budget policy.
|
|
719
|
-
**Archetype** = a structural rule about *our* rigs that no Spine consumer would care about.
|
|
720
|
-
|
|
721
|
-
| Assertion | Kind | What it forbids |
|
|
722
|
-
| --- | --- | --- |
|
|
723
|
-
| `A00_ROUNDTRIP_PARSE` | validity | JSON or atlas that spine-core refuses (cases 6d, 6e) |
|
|
724
|
-
| `A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS` | validity | `ik`/`transform`/`path`/`physics` at top level (case 6a) |
|
|
725
|
-
| `A02_NO_BONE_TRANSFORM_KEY` | validity | bone `transform` instead of `inherit` (case 6b) |
|
|
726
|
-
| `A03_REGION_WIDTH_HEIGHT_FINITE` | validity | `NaN`/non-positive region size (case 6c) |
|
|
727
|
-
| `A04_MESH_TRIANGLES_AND_ENCODING` | validity | missing/misaligned `triangles`, out-of-range indices, incoherent vertex run (case 6f) |
|
|
728
|
-
| `A05_CURVE_ARRAY_LENGTH` | validity | short/non-finite bezier arrays, curves on curve-less timelines (case 6g) |
|
|
729
|
-
| `A07_ATLAS_TEXT_SHAPE` | validity | blank line inside a page block, indented region name (the two `TextureAtlas` traps) |
|
|
730
|
-
| `A08_REGION_NAMES_MATCH_ATTACHMENTS` | validity + policy | attachment→region miss is validity; **"v0 requires them identical"** is policy |
|
|
731
|
-
| `A09_ANIMATION_DURATION_MATCHES_SPEC` | spec-fidelity | compiled last-key time ≠ declared duration |
|
|
732
|
-
| `A10_NO_NAN_AFTER_STEPPING` | validity | NaN world transforms / colours after stepping every animation (120 frames) |
|
|
733
|
-
| `A16_SKELETON_VERSION_4_3` | validity (portability) | a `spine` string outside `4.3.x` |
|
|
734
|
-
| `A17_ATLAS_PAGE_FILES_EXIST` | validity | a page PNG not on disk |
|
|
735
|
-
| `A18_DETERMINISTIC_EMIT` | tool contract | recompiling differs byte-for-byte |
|
|
736
|
-
| `A06_ATLAS_PAGE_SIZE_MATCHES_PNG` | **mixed** | size≠PNG is **validity** (case 6h) — measured rather than inherited ([#715](https://github.com/firejune/rigc/issues/715)): the runtime's UVs are a fraction of the DECLARED size, so a rescaled page draws, and the clause stays validity because every texel reader breaks on it (two of them rigc's own, under both profiles) and the format can state the same art truthfully with `scale:`, which the message prints. And so is a region whose rectangle is outside the page it names, rotation honoured ([#694](https://github.com/firejune/rigc/issues/694)); `pma:true`, region rotation, and two regions on one page over the same texels are **renderer-profile** |
|
|
737
|
-
| `A11_NO_CLIPPING_ATTACHMENTS` | **renderer-profile** | clipping attachments — "the renderer skips them silently" |
|
|
738
|
-
| `A12_NO_DARK_COLOR` | **renderer-profile** | slot `dark`, `rgba2`/`rgb2` timelines — "parsed, then ignored". ⚠️ rigc **emits** the first two; a renderer that drops a construct is what a profile is for, not a reason not to emit it |
|
|
739
|
-
| `A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN` | validity | a slot `dark` the parser drops or reads as NaN, an `rgba2` or `rgb2` timeline on a slot with no dark colour to pose, or a key whose posed light/dark is not what it states |
|
|
740
|
-
| `A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN` | validity | a linked mesh, in either spelling, that also states `uvs`, `triangles`, `vertices`, `hull` or `edges` — keys the `source` branch returns before reading, so the file says one mesh and every runtime draws its source's |
|
|
741
|
-
| `A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN` | validity | an `rgb` or `alpha` timeline beside another colour timeline of the slot that poses the same channel — the later in the file overwrites the other at every time — or a key of one the pose does not reproduce |
|
|
742
|
-
| `A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES` | validity | a numbered series that loads as something other than what the file states — a `sequence` block with no `count` (0 regions) or a `setup` past the end (clamped), or a `sequence` key whose `mode` is outside the seven (read as `hold`), whose `index` is fractional or past the end, whose advancing mode runs at an effective delay of 0, or that steps an attachment with no block — and then the pose: every key sampled mid-frame, the region shown held to the frame the file's statement gives ([#729](https://github.com/firejune/rigc/issues/729)) |
|
|
743
|
-
| `A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT` | validity | an ik constraint at `mix` 0 at setup that no animation keys away from 0 — `update()` returns on it, and the rig parses and moves nothing ([#765](https://github.com/firejune/rigc/issues/765)) |
|
|
744
|
-
| `A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT` | validity | a transform constraint whose mixes for the properties it drives are all 0 at setup and on every value a key poses, or one that drives no property ([#765](https://github.com/firejune/rigc/issues/765)) |
|
|
745
|
-
| `A13_MESH_BUDGET` | **renderer-profile** | >4 mesh slots, >80 triangles per mesh |
|
|
746
|
-
| `A14_NO_FULL_FRAME_MESH` | **renderer-profile** | a mesh spanning the whole stage |
|
|
747
|
-
| `A19_OVERLAY_PNGS_HAVE_ALPHA` | **renderer-profile** | an overlay part none of whose texels is below full alpha — refused unopened when the file has no alpha channel and no `tRNS` chunk, and by its texels otherwise, on a loose page and a shared one alike |
|
|
748
|
-
| `A20_MESH_WEIGHTS_COHERENT` | **renderer-profile / archetype** | **unweighted meshes**, weights not summing to 1, out-of-range bone indices |
|
|
749
|
-
| `A21_MESH_RIM_PINNED` | archetype | ring/ribbon/contour rim not pinned |
|
|
750
|
-
| `A22_MESH_UVS_IN_UNIT_RANGE` | validity-ish | UVs outside 0..1 |
|
|
751
|
-
| `A15_IDLE_NO_MESH_BONE_KEYS` | archetype | `idle` keying a mesh-driving bone |
|
|
752
|
-
| `A23_PHYSICS_CONSTRAINT_EFFECTIVE` | validity-ish | a physics constraint that parses and does nothing |
|
|
753
|
-
| `A24_AXIS_SPACE_STROKE` | archetype | screen-space stroke keys |
|
|
754
|
-
| `A25_DETACHED_BONE_PARENTAGE` | archetype | a bone the rig declares detached, parented under the bone it must stay clear of |
|
|
755
|
-
| `A26_SLOT_DRAW_ORDER` | archetype | draw order ≠ the archetype's slot table |
|
|
756
|
-
| `A32_EVENT_KEYS_RESOLVE` | validity | an event key firing an undeclared name (parser throws), key times going backwards (silent — frames are filled in array order), `volume`/`balance` on an event with no `audio` (silent) |
|
|
757
|
-
| `A33_VERTEX_ATTACHMENT_GEOMETRY` | validity | a bounding box or clipping polygon with a missing or disagreeing `vertexCount` (`undefined << 1` = 0, so the coordinates are decoded as a weight run), a weighted run of the wrong length, an out-of-range bone index, or a clipping `end` naming no slot (`findSlot` returns null and the null is assigned) |
|
|
758
|
-
| `A27_REGION_NAME_MATCHES_PAGE_FILENAME` | policy | region name ≠ PNG basename |
|
|
759
|
-
| `A28_RIBBON_ROWS_SHARE_WEIGHTS` | archetype | ribbon rows with divergent weights |
|
|
760
|
-
| `A29_STROKE_WITHIN_CONTACT_DEPTH` | archetype | stroke past the measured contact depth |
|
|
761
|
-
| `A30_STROKE_WITHIN_CAP_CONTAINMENT` | archetype | stroke past the measured containment ceiling |
|
|
762
|
-
|
|
763
|
-
**Nine assertions are renderer-profile or profile-mixed** (A06 partly, A11, A12, A13, A14, A19, A20,
|
|
764
|
-
plus A08/A27's "identical names" policy). Every one of them would fire on real Spine example data.
|
|
765
|
-
They are correct for spine-html; they are wrong as *spec* gates, and they must become a
|
|
766
|
-
profile switch (`--profile spine-html` vs `--profile spine`) before rung 1.
|
|
767
|
-
|
|
768
|
-
**Twelve are archetype assertions** (A15, A21, A24–A26, A28–A30, plus the parts of A20/A21 that
|
|
769
|
-
encode ring/ribbon geometry). They are already gated on `input.rig` being present and **SKIP**
|
|
770
|
-
when validating a bare directory (`validate.ts:47-55`, `:808`), which is the right shape — nothing
|
|
771
|
-
needs to change there, they simply will not run on example data.
|
|
772
|
-
|
|
773
|
-
### 2.3 What the round-trip checks — and what it lets through
|
|
774
|
-
|
|
775
|
-
**Checked**: the artifact is parsed by the real `TextureAtlas` and `SkeletonJson` (A00), then every
|
|
776
|
-
animation is stepped 120 frames through a real `Skeleton` + `AnimationState` looking for non-finite
|
|
777
|
-
world transforms and colours (A10, `validate.ts:703-733`), then the compiler is re-run and the two
|
|
778
|
-
emits compared byte-for-byte (A18).
|
|
779
|
-
|
|
780
|
-
**Let through silently** — the honest list:
|
|
781
|
-
|
|
782
|
-
1. **Whole timeline groups the walker never visits.** `walkTimelines` (`validate.ts:1159-1199`)
|
|
783
|
-
descends only `animations.<a>.bones`, `.slots`, `.physics`, and `.attachments`. An `ik`,
|
|
784
|
-
`transform`, `path`, `slider`, `drawOrder`, `drawOrderFolder`, or `events` block is **not visited
|
|
785
|
-
at all**, so A05 never sees its curves. Adding those emitters without extending the walker
|
|
786
|
-
silently disables the format's most dangerous check.
|
|
787
|
-
2. **Unknown top-level and per-map keys.** Nothing enumerates what the parser reads, so a typo'd
|
|
788
|
-
key (`"inherits"`, `"tringles"`) is invisible to both parser and validator.
|
|
789
|
-
3. **Semantics the parser cannot see.** A wrong-but-parseable `inherit` value, a `blend` mode, a
|
|
790
|
-
slot `visible: false`, a linked mesh pointing at the wrong source — none are checked.
|
|
791
|
-
4. **`skin` / `skinRequired` interactions** — rigc emits one skin, so nothing is exercised.
|
|
792
|
-
5. **A05's own escape hatch is a fail, not a pass** (`validate.ts:275-278`): an unrecognised timeline
|
|
793
|
-
name inside a *visited* group fails with `unchecked <kind> timeline "<name>" — extend the
|
|
794
|
-
validator`. That is good design, and it means new bone/slot/physics timelines cannot be added
|
|
795
|
-
without touching the channel tables. It does **not** apply to the groups in item 1.
|
|
796
|
-
|
|
797
|
-
`selftest.ts` runs 35 named mutants (M01–M35) across three fixtures, each mutant asserting that a
|
|
798
|
-
specific assertion fires. The mutant set mirrors the assertion set, so the same blind spots apply:
|
|
799
|
-
there is no mutant for an IK or draw-order timeline because there is no emitter and no walker for
|
|
800
|
-
one.
|
|
801
|
-
|
|
802
|
-
> 🔼 Both halves of that paragraph are now stale. The walker reaches all eleven groups and the
|
|
803
|
-
> mutants that prove it exist; the suite runs on fixtures it **generates**, so it needs no
|
|
804
|
-
> project's art. Current shape: [CLAUDE.md](https://github.com/firejune/rigc/blob/main/CLAUDE.md), *The selftest and its fixtures*.
|
|
805
|
-
|
|
806
|
-
### 2.4 The input model — where the rig actually comes from
|
|
807
|
-
|
|
808
|
-
Two data files in, per `types.ts:1-13`:
|
|
809
|
-
|
|
810
|
-
- **cut manifest** (`FaceManifest`) — owns *geometry*: `crop`, per-part `offset`/`size`,
|
|
811
|
-
`polygon`, `state_machine`, `anchors` (bone positions in crop pixels), `axis`, `stroke`.
|
|
812
|
-
- **motion spec** (`MotionSpec`, `spec: 'rigc-motion/1'`) — owns *time*: `easings`, `groups`,
|
|
813
|
-
`setup` (per-slot setup pose), `physics` constraint table, `animations{ tracks[] }`, `mix`.
|
|
814
|
-
|
|
815
|
-
**The bone tree is code, not data.** `src/archetype.ts` hard-codes three archetypes:
|
|
816
|
-
|
|
817
|
-
| Archetype | Bone tree | Mesh budget | Slots |
|
|
818
|
-
| --- | --- | --- | --- |
|
|
819
|
-
| overlay, tier 1 (`:65-71`) | a two-bone root chain, then **one bone per slot**, auto-named after the slot and pinned at the part-window centre | 0 | derived from the manifest's parts |
|
|
820
|
-
| overlay, tier 2 (`:84-88`) | identical | 3 | identical |
|
|
821
|
-
| articulated (`:109-161`) | **18 explicitly named bones**: a camera handle, a base, a mass with two soft-body controls, an axis bone carrying four subtrees (a travelling part and its tip, a rim with four grips, a detached emitter with a three-link chain) | 3 | a fixed `slotBone` map of **9 slots** with a fixed `slotOrder` |
|
|
822
|
-
|
|
823
|
-
The manifest supplies *positions* for those bones (`anchors`), never their *names*, *parentage*, or
|
|
824
|
-
*count*. Consequences, stated plainly:
|
|
825
|
-
|
|
826
|
-
- A slot not in the archetype's `slotBone` table is a **hard compile error**
|
|
827
|
-
(`compile.ts:412-417`: *"extend its slot table rather than inventing one"*).
|
|
828
|
-
- A control bone not in the archetype tree is a hard error (`compile.ts:420-426`).
|
|
829
|
-
- Therefore **no example project can be compiled by rigc today at any rung** — not because a feature
|
|
830
|
-
is missing, but because there is no way to *state* a skeleton whose bone tree is not one of these
|
|
831
|
-
three. This is the single largest item in Part 4(b) and it gates everything else.
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
---
|
|
835
|
-
|
|
836
|
-
## Part 3 — what the example ladder actually uses
|
|
837
|
-
|
|
838
|
-
Corpus: the nine official example projects on branch `4.3`, fetched by
|
|
839
|
-
[`scripts/fetch-examples.sh`](https://github.com/firejune/rigc/blob/main/scripts/fetch-examples.sh) into the gitignored `examples/<name>/`
|
|
840
|
-
(**12 skeleton JSON files** — some examples ship more than one — **10 `.atlas` files**, 8 `license.txt`).
|
|
841
|
-
Counted by [`bench/count_features.ts`](https://github.com/firejune/rigc/blob/main/bench/count_features.ts); full output in
|
|
842
|
-
[`feature_matrix.json`](https://github.com/firejune/rigc/blob/main/docs/feature_matrix.json) and [`feature_matrix.csv`](https://github.com/firejune/rigc/blob/main/docs/feature_matrix.csv)
|
|
843
|
-
(12 rows × 135 columns), both regenerated by `bun run bench:usage`.
|
|
844
|
-
|
|
845
|
-
> ⚠️ **Verification note.** The timeline columns below were re-derived independently from the raw JSON
|
|
846
|
-
> and reconciled against the CSV before publication. Where a prose summary and the machine output
|
|
847
|
-
> disagreed, the machine output won. Everything in these tables is from the CSV.
|
|
848
|
-
|
|
849
|
-
### 3.0 Corpus-wide facts
|
|
850
|
-
|
|
851
|
-
- **Every one of the 12 files reports `"spine": "4.3.75-beta"`.** No legacy exports in the corpus.
|
|
852
|
-
- **Every file with constraints uses the flat 4.3 `constraints[]` shape**; all four transform-constraint
|
|
853
|
-
users use the 4.3 `source` + `properties` model. **The legacy 4.0–4.2 array shape never appears**, so
|
|
854
|
-
this corpus does *not* exercise case 6a — do not treat "the ladder is green" as evidence
|
|
855
|
-
that a legacy-shape importer works.
|
|
856
|
-
- **Every atlas uses the 4.x compact `bounds:` / `offsets:` region form.** The deprecated
|
|
857
|
-
`xy:`/`size:`/`orig:`/`offset:` region form never appears.
|
|
858
|
-
- **Zero unknown keys anywhere** — every top-level, bone, slot, and attachment key in all 12 files is
|
|
859
|
-
one the 4.3 parser reads. The corpus is a clean specimen of exactly the surface Part 1 enumerates.
|
|
860
|
-
- **No file uses:** named skins (every file has one skin, `default`), linked meshes, path constraints,
|
|
861
|
-
slider constraints, sequences, slot `dark` colour, `drawOrderFolder`, bone `visible`, or path/point
|
|
862
|
-
attachments.
|
|
863
|
-
- **Nine of ten atlases carry a page `scale:` line** that spine-ts ignores (§1.10).
|
|
864
|
-
|
|
865
|
-
### 3.1 Structure and attachments
|
|
866
|
-
|
|
867
|
-
| File | bones | slots | skins | region | mesh | bbox | clip | mesh W/U | mesh verts | mesh tris | max verts in one |
|
|
868
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
869
|
-
| `1-weight-and-mass-balls-ess` | 9 | 8 | 1 | 8 | 0 | 0 | 0 | – | – | – | – |
|
|
870
|
-
| `1-weight-and-mass-drop-ess` | 4 | 5 | 1 | 5 | 0 | 0 | 0 | – | – | – | – |
|
|
871
|
-
| `2-the-12-principles-ess` | 12 | 17 | 1 | 17 | 0 | 0 | 0 | – | – | – | – |
|
|
872
|
-
| `3-timing-and-spacing-ess` | 3 | 2 | 1 | 2 | 0 | 0 | 0 | – | – | – | – |
|
|
873
|
-
| `4-wave-principle-ess` | 9 | 9 | 1 | 9 | 0 | 0 | 0 | – | – | – | – |
|
|
874
|
-
| `5-squash-and-stretch-ess` | 16 | 13 | 1 | 29 | 0 | 0 | 0 | – | – | – | – |
|
|
875
|
-
| `6-arcs-pro` | 14 | 4 | 1 | 2 | 2 | 0 | 0 | **2 / 0** | 44 | 40 | 40 |
|
|
876
|
-
| `7-anticipation / sack-pro` | 31 | 3 | 1 | 0 | 3 | 0 | 0 | **3 / 0** | 193 | 230 | 96 |
|
|
877
|
-
| `8-follow-through-pro-ball` | 12 | 2 | 1 | 0 | 2 | 0 | 0 | **2 / 0** | 34 | 30 | 30 |
|
|
878
|
-
| `8-follow-through-pro-pendulum` | 7 | 6 | 1 | 6 | 0 | 0 | 0 | – | – | – | – |
|
|
879
|
-
| `spineboy-ess` | 18 | 20 | 1 | 26 | 0 | **1** | 0 | – | – | – | – |
|
|
880
|
-
| `spineboy-pro` | **67** | **52** | 1 | 66 | **12** | **1** | **1** | **10 / 2** | 322 | 430 | 74 |
|
|
881
|
-
|
|
882
|
-
All meshes in the corpus carry both `edges` and `hull`.
|
|
883
|
-
|
|
884
|
-
### 3.2 Timelines actually keyed (verified twice)
|
|
885
|
-
|
|
886
|
-
| File | bone timelines | slot timelines | constraint / other |
|
|
887
|
-
| --- | --- | --- | --- |
|
|
888
|
-
| `1-w&m-balls-ess` | translate, translatex, translatey, scale, shear | **rgba** | — |
|
|
889
|
-
| `1-w&m-drop-ess` | *(none — a static skeleton)* | — | — |
|
|
890
|
-
| `2-the-12-principles-ess` | rotate, translate, translatex, translatey, scale, shear | attachment, **rgba** | — |
|
|
891
|
-
| `3-timing-and-spacing-ess` | rotate, translatex, translatey | — | — |
|
|
892
|
-
| `4-wave-principle-ess` | rotate, translatex, translatey, scale | attachment | — |
|
|
893
|
-
| `5-squash-and-stretch-ess` | rotate, translate, translatex, translatey, scale | attachment, **rgba** | **drawOrder** (2 keys) |
|
|
894
|
-
| `6-arcs-pro` | rotate, translate, translatex, translatey, scale | attachment | *(4 transform constraints, but **no** transform timeline — they are static)* |
|
|
895
|
-
| `7-anticipation / sack-pro` | rotate, translate, translatex, translatey, scale | — | **transform** (11 keys), **physics**: inertia, strength, damping, mass, wind, mix, **deform** (1 key) |
|
|
896
|
-
| `8-follow-through-pro-ball` | rotate, translate, translatex, translatey | — | *(4 transform constraints, no transform timeline)* |
|
|
897
|
-
| `8-follow-through-pro-pendulum` | rotate, translatex, translatey | — | — |
|
|
898
|
-
| `spineboy-ess` | rotate, translate, scale | attachment, **rgba** | **drawOrder** (1 key), **events** (5 keys) |
|
|
899
|
-
| `spineboy-pro` | rotate, translate, scale, shear | attachment, **rgba** | **ik** (29 keys), **transform** (6 keys), **deform** (9 keys), **events** (5 keys) |
|
|
900
|
-
|
|
901
|
-
Notable absences across the whole corpus: `inherit`, `scalex`/`scaley`, `shearx`/`sheary`,
|
|
902
|
-
`rgb`, `alpha`, `rgba2`, `rgb2`, `sequence`, `drawOrderFolder`, `path.*`, `slider.*`,
|
|
903
|
-
`physics.gravity`, `physics.reset`.
|
|
904
|
-
|
|
905
|
-
### 3.3 Constraints, curves, events, blend, inheritance
|
|
906
|
-
|
|
907
|
-
| File | ik | transform | path | physics | slider | curve linear / stepped / bezier | bezier array lengths | slot blend ≠ normal | bone inherit ≠ Normal | events defined |
|
|
908
|
-
| --- | ---: | ---: | ---: | ---: | ---: | --- | --- | --- | --- | ---: |
|
|
909
|
-
| `1-w&m-balls-ess` | 0 | 0 | 0 | 0 | 0 | 206 / 1 / 44 | 4 | 0 | 0 | 0 |
|
|
910
|
-
| `1-w&m-drop-ess` | 0 | 0 | 0 | 0 | 0 | 0 / 0 / 0 | — | 0 | 0 | 0 |
|
|
911
|
-
| `2-the-12-principles-ess` | 0 | 0 | 0 | 0 | 0 | 150 / 127 / **1010** | 4, 8 | **8** (4 additive, 4 multiply) | 4 × `noRotationOrReflection` | 0 |
|
|
912
|
-
| `3-timing-and-spacing-ess` | 0 | 0 | 0 | 0 | 0 | 2 / 1 / 58 | 4 | 0 | 0 | 0 |
|
|
913
|
-
| `4-wave-principle-ess` | 0 | 0 | 0 | 0 | 0 | 36 / 8 / 470 | 4 | **2** (1 additive, 1 multiply) | 1 × `noRotationOrReflection` | 0 |
|
|
914
|
-
| `5-squash-and-stretch-ess` | 0 | 0 | 0 | 0 | 0 | 162 / 82 / **1665** | 4, 8 | 0 | **8 × `onlyTranslation`** | 0 |
|
|
915
|
-
| `6-arcs-pro` | 0 | **4** | 0 | 0 | 0 | 18 / 7 / 498 | 4, 8 | 0 | 0 | 0 |
|
|
916
|
-
| `7-anticipation / sack-pro` | 0 | **4** | 0 | **20** | 0 | 171 / 62 / 757 | 4, 8 | 0 | 3 × `noScale` | 0 |
|
|
917
|
-
| `8-follow-through-pro-ball` | 0 | **4** | 0 | 0 | 0 | 22 / 5 / 328 | 4, 8 | 0 | 0 | 0 |
|
|
918
|
-
| `8-follow-through-pro-pendulum` | 0 | 0 | 0 | 0 | 0 | 1 / 0 / 156 | 4 | 0 | 0 | 0 |
|
|
919
|
-
| `spineboy-ess` | 0 | 0 | 0 | 0 | 0 | 43 / 8 / 526 | 4, 8 | 1 additive | 0 | **1** (5 keys) |
|
|
920
|
-
| `spineboy-pro` | **7** (2 multi-bone) | **7** | 0 | 0 | 0 | 90 / 88 / **1136** | 4, 8, **16** | **25 additive** | 4 × `noRotationOrReflection` | **1** (5 keys) |
|
|
921
|
-
|
|
922
|
-
Bezier array length is `4 × channels` (§1.9). Observed lengths **4** (1-channel: rotate, translatex/y),
|
|
923
|
-
**8** (2-channel: translate, scale) and **16** (4-channel: rgba, spineboy-pro only). Never 12/24 —
|
|
924
|
-
so no example ever puts a bezier on a `transform`, `path mix`, or `rgb` timeline.
|
|
925
|
-
|
|
926
|
-
Nonessential data is present throughout: `hash` and `images` in all 12; bone `color` in 9 files
|
|
927
|
-
(up to 42 bones in spineboy-pro); bone `icon` in 8 files.
|
|
928
|
-
|
|
929
|
-
### 3.4 Atlases
|
|
930
|
-
|
|
931
|
-
| Atlas | pages | regions | region form | `scale:` | `pma:` | rotated regions | index / split / pad |
|
|
932
|
-
| --- | ---: | ---: | --- | --- | --- | --- | --- |
|
|
933
|
-
| `1-weight-and-mass.atlas` | 1 | 13 | compact | `0.5` | absent | **1** (`rotate: 90`, `ground-bg`) | — |
|
|
934
|
-
| `2-the-12-principles.atlas` | 1 | 15 | compact | `0.4` | absent | 0 | — |
|
|
935
|
-
| `3-timing-and-spacing.atlas` | 1 | 2 | compact | `0.5` | absent | 0 | — |
|
|
936
|
-
| `4-wave-principle.atlas` | 1 | 8 | compact | `0.5` | absent | 0 | — |
|
|
937
|
-
| `5-squash-and-stretch.atlas` | 1 | 29 | compact | `0.5` | absent | **1** (`rotate: 90`) | — |
|
|
938
|
-
| `6-arcs.atlas` | 1 | 4 | compact | `0.5` | absent | 0 | — |
|
|
939
|
-
| `7-anticipation.atlas` | 1 | 3 | compact | `0.5` | absent | **1** (`rotate: 270`, `cape-back`) | — |
|
|
940
|
-
| `8-follow-through.atlas` | 1 | 8 | compact | `0.5` | absent | 0 | — |
|
|
941
|
-
| `spineboy.atlas` | 1 | 50 | compact | `0.5` | absent | 0 | — |
|
|
942
|
-
| `spineboy-run.atlas` | 1 | 10 | compact | absent | **`true`** | 0 | **`index: 8`** on all 10 |
|
|
943
|
-
|
|
944
|
-
Two things worth flagging:
|
|
945
|
-
|
|
946
|
-
- **`rotate: 270` exists in the wild** (`7-anticipation`), so a reader that treats `rotate` as a
|
|
947
|
-
boolean is wrong on real data — matching `TextureAtlas.ts:91-97`'s three-way branch.
|
|
948
|
-
- **`spineboy-run.atlas` is a degenerate file**: ten region entries, all named `spineboy-pro-run`,
|
|
949
|
-
all with `index: 8` and identical `bounds`. Byte length matches upstream (869 B), so this is what
|
|
950
|
-
the repository ships, not a fetch artefact. It is not referenced by either spineboy skeleton
|
|
951
|
-
(both use `spineboy.atlas`) and it is the **only** `pma: true` page in the corpus. Treat it as an
|
|
952
|
-
outlier, not as a ladder target.
|
|
953
|
-
|
|
954
|
-
### 3.5 Licences — **not uniform, and one example has none**
|
|
955
|
-
|
|
956
|
-
Eight of nine examples ship `license.txt`; **`7-anticipation` has none** (verified: 404 for both
|
|
957
|
-
`license.txt` and `LICENSE.txt`, and no licence file in that example's directory listing). The eight
|
|
958
|
-
present files fall into **five byte-distinct variants that differ only in the copyright line**:
|
|
959
|
-
|
|
960
|
-
| Variant | Examples | Copyright line |
|
|
961
|
-
| --- | --- | --- |
|
|
962
|
-
| A | `1-weight-and-mass`, `2-the-12-principles`, `4-wave-principle` | `Copyright (c) 2021-2025, Esoteric Software LLC` |
|
|
963
|
-
| B | `3-timing-and-spacing`, `5-squash-and-stretch` | `Copyright (c) 2021-2025, Esoteric Software` |
|
|
964
|
-
| C | `6-arcs` | `Copyright (c) 2022-2025, Esoteric Software` |
|
|
965
|
-
| D | `8-follow-through` | `Copyright (c) 2024-2025, Esoteric Software` |
|
|
966
|
-
| E | `spineboy` | `Copyright (c) 2013, Esoteric Software LLC` |
|
|
967
|
-
|
|
968
|
-
The body is identical in all five:
|
|
969
|
-
|
|
970
|
-
> The images in this project may be redistributed as long as they are accompanied by this license
|
|
971
|
-
> file. The images may not be used for commercial use of any kind.
|
|
972
|
-
>
|
|
973
|
-
> The project file is released into the public domain. It may be used as the basis for derivative work.
|
|
974
|
-
|
|
975
|
-
**What this means for the ladder**, stated as a chain and not as legal advice:
|
|
976
|
-
|
|
977
|
-
1. **Images** — redistributable *only* alongside the licence file, and **not for commercial use**.
|
|
978
|
-
A benchmark that ships the example PNGs is fine as a non-commercial research artefact; shipping
|
|
979
|
-
them inside a commercial product is not.
|
|
980
|
-
2. **Project files / exports** — public domain. So the `.json`/`.atlas` we are trying to *reproduce*
|
|
981
|
-
carry no restriction, and a rigc-generated look-alike inherits nothing.
|
|
982
|
-
3. **`7-anticipation` has no grant on file at all.** Its images and `sack-pro.json` are governed only
|
|
983
|
-
by the repository-level Spine Runtimes License. It is the one rung where "we redistributed the
|
|
984
|
-
images" is not covered by an explicit permission — **use it for measurement, do not vendor its
|
|
985
|
-
images**. Since it is also the physics rung, this is worth deciding before rung 7.
|
|
986
|
-
4. Independent of any of this, rigc links spine-core, so its own `NOTICE.md` chain (each user needs a
|
|
987
|
-
Spine Editor licence) applies regardless of which example is being reproduced.
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
---
|
|
991
|
-
|
|
992
|
-
## Part 4 — the gap list, ordered by the ladder
|
|
993
|
-
|
|
994
|
-
Crossing Part 2 with Part 3. Categories, as requested:
|
|
995
|
-
**(a) emitter** — new attachment / constraint / timeline / atlas kinds ·
|
|
996
|
-
**(b) input model** — what the spec files must be able to *say* ·
|
|
997
|
-
**(c) validator** — assertions that must become a profile switch or be widened ·
|
|
998
|
-
**(d) JSON-inexpressible** — things the format genuinely cannot hold.
|
|
999
|
-
|
|
1000
|
-
**Nothing below was implemented when this was written. This is a survey.** Three of its items
|
|
1001
|
-
have been built since — see the banner at the top of this document and [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) for
|
|
1002
|
-
which; the requirement text itself is left exactly as surveyed.
|
|
1003
|
-
|
|
1004
|
-
### 4.0 Three blockers that precede rung 1
|
|
1005
|
-
|
|
1006
|
-
These are not per-rung; they gate the whole ladder and should be settled before rung 1 is attempted.
|
|
1007
|
-
|
|
1008
|
-
| # | Blocker | Category | Detail |
|
|
1009
|
-
| --- | --- | --- | --- |
|
|
1010
|
-
| **B1** | **The bone tree is code** | (b) | `archetype.ts` hard-codes all three archetypes. A slot outside the archetype's `slotBone` table is a compile error (`compile.ts:412-417`); a control bone outside its tree likewise (`:420-426`). **No example fits any of the three.** rigc needs a "skeleton-as-data" input — bones with explicit names/parents/transforms — before any rung is reachable. |
|
|
1011
|
-
| **B2** | **A16 rejects every example** | (c) | `A16_SKELETON_VERSION_4_3` tests `/^4\.3(\.\d+)?$/` (`validate.ts:216`). All twelve example files declare `"4.3.75-beta"`, which **fails**. One-line fix, but it fails on rung 1 file 1. |
|
|
1012
|
-
| **B3** | **The atlas model is one-part-per-page** | (a)+(c) | rigc emits `bounds: 0,0,w,h` per page and **A06** demands every region's UVs be exactly (0,0)-(1,1). Every example ships a **packed** atlas (13, 15, 29, 50 regions on one page). rigc has no packer *and* no importer for a pre-packed atlas. Reproducing a rung means either accepting a hand-supplied atlas or writing a packer. |
|
|
1013
|
-
|
|
1014
|
-
### 4.1 Cumulative per-rung requirements
|
|
1015
|
-
|
|
1016
|
-
Each row lists only what is **new at that rung**. Read cumulatively: to stand on rung N you need
|
|
1017
|
-
rows 1..N plus §4.0.
|
|
1018
|
-
|
|
1019
|
-
#### Rung 1 — `1-weight-and-mass` (2 skeletons: `balls-ess`, `drop-ess`)
|
|
1020
|
-
|
|
1021
|
-
| Cat | Requirement |
|
|
1022
|
-
| --- | --- |
|
|
1023
|
-
| (a) | Bone timelines **`translatex`**, **`translatey`**, **`shear`** — rigc emits only `translate`/`scale`/`rotate` |
|
|
1024
|
-
| (a) | Bone setup **`length`** (3 bones in `drop-ess`) — cosmetic in a renderer, but part of a faithful reproduction |
|
|
1025
|
-
| (a) | Region attachment **`rotation`** on 3 attachments *(rigc emits `rotation` already, as a bone-cancel term — it would need to become an authorable value)* |
|
|
1026
|
-
| (a) | Atlas: **multi-region packed page**, per-region `bounds`/`offsets`, **`rotate: 90`**, page `scale:` line |
|
|
1027
|
-
| (b) | Bone tree as data (B1); a skeleton with **zero animations** (`drop-ess`) — today `A09` compares every declared duration and `compile.ts:622` refuses a mismatch, and the motion-spec schema has no "static rig" mode |
|
|
1028
|
-
| (c) | **A06** — "region covers the whole page" and "no rotated region" must move behind the profile switch |
|
|
1029
|
-
| (c) | **A16** (B2), **A26** (`slotOrder` is an archetype table; example slot order comes from the file), **A08/A27** (region-name == PNG-basename is meaningless for a packed atlas) |
|
|
1030
|
-
| (d) | none |
|
|
1031
|
-
|
|
1032
|
-
#### Rung 2 — `2-the-12-principles`
|
|
1033
|
-
|
|
1034
|
-
| Cat | Requirement |
|
|
1035
|
-
| --- | --- |
|
|
1036
|
-
| (a) | **Slot `blend`** — first appearance: 4 additive + 4 multiply slots |
|
|
1037
|
-
| (a) | **Bone `inherit`** — first appearance: 4 × `noRotationOrReflection` |
|
|
1038
|
-
| (b) | The manifest must be able to declare per-slot blend and per-bone inherit |
|
|
1039
|
-
| (c) | A05 already knows the channel counts for every bone timeline; no change needed for this rung's curves (4- and 8-length beziers) |
|
|
1040
|
-
| (d) | none |
|
|
1041
|
-
|
|
1042
|
-
#### Rung 3 — `3-timing-and-spacing`
|
|
1043
|
-
|
|
1044
|
-
Nothing new. **This is the smallest skeleton in the corpus (3 bones, 2 slots, 2 animations) and is
|
|
1045
|
-
the natural *first* rung** despite its number — it needs only B1–B3 plus `translatex`/`translatey`.
|
|
1046
|
-
|
|
1047
|
-
#### Rung 4 — `4-wave-principle`
|
|
1048
|
-
|
|
1049
|
-
Nothing structurally new (attachment timeline, blend, inherit all arrived by rung 2). It is a
|
|
1050
|
-
**volume** test: 9 bones, 9 slots, 3 animations, 470 bezier keys.
|
|
1051
|
-
|
|
1052
|
-
#### Rung 5 — `5-squash-and-stretch`
|
|
1053
|
-
|
|
1054
|
-
| Cat | Requirement |
|
|
1055
|
-
| --- | --- |
|
|
1056
|
-
| (a) | **`drawOrder` timeline** — 🔴 first appearance (2 keys). rigc emits none, and the *validator does not walk the group at all* (`validate.ts:1166-1170`) |
|
|
1057
|
-
| (a) | **Bone `inherit: onlyTranslation`** (8 bones); non-unit setup `scaleX`/`scaleY` (3 bones) |
|
|
1058
|
-
| (b) | A draw-order track type in the motion spec (a list of slot offsets over time) |
|
|
1059
|
-
| (c) | Extend `walkTimelines` to visit `drawOrder`, and give A05 a "no curve here" entry for it |
|
|
1060
|
-
| (d) | none |
|
|
1061
|
-
|
|
1062
|
-
#### Rung 6 — `6-arcs`
|
|
1063
|
-
|
|
1064
|
-
> ✅ **Measured, 2026-08-23.** The rows below were written as predictions; the
|
|
1065
|
-
> transcription in [`bench/transcriptions/6-arcs/`](https://github.com/firejune/rigc/tree/main/bench/transcriptions/6-arcs/)
|
|
1066
|
-
> settled them. It compiles green under `--profile spine` and `bench 6` reports
|
|
1067
|
-
> **1.000 on all 44 measures in all six sections**, so the rig spec expresses this
|
|
1068
|
-
> skeleton — weighted meshes, `edges` and the 4.3 transform constraints alike. A
|
|
1069
|
-
> field-by-field comparison against the reference export leaves 49 differences and
|
|
1070
|
-
> every one is benign: 39 are defaults rigc writes explicitly where the editor
|
|
1071
|
-
> omits them, 3 are editor bookkeeping (`hash`, `images`, `audio`), 1 is the runtime
|
|
1072
|
-
> version string, and **6 are bone `icon`** — an editor decoration the rig spec had
|
|
1073
|
-
> no field for and the only thing here it could not say. `RigBone.icon` carries it
|
|
1074
|
-
> since issue #47; the transcription itself has not been re-cut. What did not
|
|
1075
|
-
> survive contact is noted per row.
|
|
1076
|
-
|
|
1077
|
-
| Cat | Requirement |
|
|
1078
|
-
| --- | --- |
|
|
1079
|
-
| (a) | **Transform constraints** — 🔴 first appearance (4). Full 4.3 `source` + `properties{from→to}` model (§1.4), which is the least-documented constraint in the format. ✅ Expressible and round-trips exactly; `RigTransformConstraint` already carried the 4.3 shape |
|
|
1080
|
-
| (a) | **Weighted meshes from authored geometry** — 🔴 first appearance. ~~rigc can emit weighted meshes, but only from `buildRingMesh`/`buildRibbonMesh`. An arbitrary 40-vertex/38-triangle mesh cannot be expressed~~ ⚠️ **This was wrong.** `RigMeshAttachment` takes authored `uvs`/`triangles`/`vertices`/`hull`, and `buildRigMesh` copies them verbatim. Both of 6-arcs' meshes round-trip to 1e-5 |
|
|
1081
|
-
| (a) | Mesh **`edges`** key ~~(rigc emits `hull` but not `edges`)~~ ✅ emitted from `RigMeshAttachment.edges` and byte-identical to the reference. 🚨 But **nothing measures it** — deleting `edges` from the rig still scores 1.000 on all nine attachment measures, so this rung's own gating feature is invisible to `bench` (issue #46) |
|
|
1082
|
-
| (b) | Mesh geometry as data — vertices, triangles, uvs, per-vertex bone weights — instead of a generator name plus a polygon. ✅ Present. ~~🚨 But the weights bind bones by **index into the emitted bone array**, not by name — inserting a bone rebinds every vertex with the gate still green (issue #45)~~ ✅ **Fixed.** Weights bind **by name** (`weights: [[{ bone, x, y, weight }, …], …]`) and the compiler resolves them at emit; an unknown name is a `CompileError` (selftest `RF08`). Spine's index run survives behind an explicit `"boneIndexing": "raw"`, whose cost — silence — `MR07` still measures |
|
|
1083
|
-
| (c) | **A20** (unweighted forbidden) is satisfied here. ~~Under `--profile spine-html` its extra clause fires 11 times instead — the editor writes zero-weight bindings and that profile forbids them~~ ✅ **Fixed with #44**: both of A20's policy clauses are statements about what a rigc *generator* produces, so neither applies to authored geometry. Its coherence clauses — present, in range, summing to 1 — still do, in every profile |
|
|
1084
|
-
| (c) | **A21_MESH_RIM_PINNED** and **A28** encode ring/ribbon topology and will fire on an arbitrary mesh. ~~✅ **Confirmed**: A21 fires 40 times on the `tail` mesh under the default profile, because `meshKinds` has no entry for an authored mesh and the lookup falls back to `'ring'`~~ ✅ **Fixed** (issue #44): `meshKinds` has a third state, `authored`, and both assertions SKIP on one with that as the reason. The whole transcription is green under the default profile; selftest `MR08` holds it |
|
|
1085
|
-
| (c) | A13's mesh budget (≤4 slots, ≤80 tris) is **satisfied** at this rung (2 slots, max 38 tris) |
|
|
1086
|
-
| (d) | none |
|
|
1087
|
-
|
|
1088
|
-
#### Rung 7 — `7-anticipation` (`sack-pro`)
|
|
1089
|
-
|
|
1090
|
-
| Cat | Requirement |
|
|
1091
|
-
| --- | --- |
|
|
1092
|
-
| (a) | **Physics timelines** `inertia`, `strength`, `damping`, `mass`, `wind` — rigc emits only `mix` and `reset` |
|
|
1093
|
-
| (a) | **`transform` constraint timeline** — 🔴 first appearance as a *keyed* timeline (11 keys); rung 6's transform constraints were static |
|
|
1094
|
-
| (a) | **`deform` timeline** — 🔴 first appearance (1 key). Also needs the `animations.<a>.attachments.<skin>.<slot>.<att>` container, which rigc never writes |
|
|
1095
|
-
| (a) | 20 physics constraints on a 31-bone tree; bone `inherit: noScale` |
|
|
1096
|
-
| (b) | Per-vertex deform keys in the motion spec |
|
|
1097
|
-
| (c) | 🚨 **A13_MESH_BUDGET fails**: the `sack` mesh has **116 triangles** against a budget of 80. Renderer-profile — must become a switch |
|
|
1098
|
-
| (c) | A24/A29/A30 (axis-space stroke, contact depth, containment ceiling) are archetype assertions and will correctly **SKIP** — no change needed |
|
|
1099
|
-
| (⚖️) | **Licence**: this is the one example with **no `license.txt`** (§3.5). Measure against it; do not vendor its images |
|
|
1100
|
-
| (d) | none |
|
|
1101
|
-
|
|
1102
|
-
#### Rung 8 — `8-follow-through` (2 skeletons: `pro-ball`, `pro-pendulum`)
|
|
1103
|
-
|
|
1104
|
-
Nothing new — transform constraints (static, 4) and weighted meshes (2, max 28 tris) both arrived at
|
|
1105
|
-
rung 6. `pro-pendulum` is region-only and is effectively a rung-3-class file.
|
|
1106
|
-
|
|
1107
|
-
#### Top rung — `spineboy` (2 skeletons: `ess`, `pro`)
|
|
1108
|
-
|
|
1109
|
-
| Cat | Requirement |
|
|
1110
|
-
| --- | --- |
|
|
1111
|
-
| (a) | **IK constraints** — 🔴 first appearance: 7, of which 2 are multi-bone (two-bone IK) |
|
|
1112
|
-
| (a) | **IK timelines** — 29 keys, 2-channel curves (mix, softness) |
|
|
1113
|
-
| (a) | **Events** — 🔴 first appearance: the skeleton-level `events` block *and* the `events` timeline (5 keys) |
|
|
1114
|
-
| (a) | **Bounding-box attachments** — 🔴 first appearance (1, in both `ess` and `pro`) |
|
|
1115
|
-
| (a) | **Clipping attachment** — 🔴 first appearance (1, `pro` only) |
|
|
1116
|
-
| (a) | **Unweighted meshes** — 🔴 first appearance (2 of 12 in `pro`) |
|
|
1117
|
-
| (a) | Multi-page atlas handling; 4-channel (`rgba`, length-16) bezier curves — rigc already emits these correctly |
|
|
1118
|
-
| (a) | Scale: 67 bones, 52 slots, 11 animations, 80 attachments |
|
|
1119
|
-
| (b) | Event definitions and event tracks in the motion spec; bounding-box and clipping attachment declarations |
|
|
1120
|
-
| (c) | 🚫 **A11_NO_CLIPPING_ATTACHMENTS** fails — renderer-profile |
|
|
1121
|
-
| (c) | 🚫 **A20_MESH_WEIGHTS_COHERENT** fails on the 2 unweighted meshes — renderer-profile |
|
|
1122
|
-
| (c) | 🚨 **A13_MESH_BUDGET** fails twice over: 9 mesh slots (budget 4) and two meshes at 95 / 101 triangles (budget 80) |
|
|
1123
|
-
| (c) | Extend `walkTimelines` to visit `ik`, `transform`, and `events` — otherwise A05 silently stops guarding this rung's curves |
|
|
1124
|
-
| (d) | none |
|
|
1125
|
-
|
|
1126
|
-
### 4.2 First appearance of each named feature
|
|
1127
|
-
|
|
1128
|
-
| Feature | First rung | Where |
|
|
1129
|
-
| --- | --- | --- |
|
|
1130
|
-
| Packed / rotated atlas | **1** | `1-weight-and-mass` (`rotate: 90`) |
|
|
1131
|
-
| Slot blend modes | **2** | `2-the-12-principles` (additive + multiply) |
|
|
1132
|
-
| Bone `inherit` ≠ Normal | **2** | `2-the-12-principles` (`noRotationOrReflection`) |
|
|
1133
|
-
| **drawOrder** | **5** | `5-squash-and-stretch` (2 keys) |
|
|
1134
|
-
| **Transform constraints** | **6** | `6-arcs` (4, static) |
|
|
1135
|
-
| **Weighted meshes** | **6** | `6-arcs` (2 meshes, 44 verts) |
|
|
1136
|
-
| **Physics** | **7** | `7-anticipation` (20 constraints + 6 timeline kinds) |
|
|
1137
|
-
| **Deform** | **7** | `7-anticipation` (1 key) |
|
|
1138
|
-
| Keyed transform timeline | **7** | `7-anticipation` (11 keys) |
|
|
1139
|
-
| **IK** | **spineboy** | `spineboy-pro` (7, incl. 2 multi-bone) |
|
|
1140
|
-
| **Clipping** | **spineboy** | `spineboy-pro` (1) |
|
|
1141
|
-
| Bounding box | **spineboy** | `spineboy-ess` and `-pro` (1 each) |
|
|
1142
|
-
| Events | **spineboy** | both spineboy files (1 def, 5 keys) |
|
|
1143
|
-
| Unweighted meshes | **spineboy** | `spineboy-pro` (2) |
|
|
1144
|
-
| **Skins** | **never** | 🔵 **no example in the ladder uses a non-default skin.** Every one of the 12 files has exactly one skin, named `default`. Named skins, per-skin bones, and per-skin constraint lists are **not on this ladder at all** and were scheduled by some other justification: they landed with issues #2 and #7, off the ladder, and no rung's status depends on them. |
|
|
1145
|
-
| Path constraints, sliders, sequences, linked meshes, dark colour, `drawOrderFolder` | **never** | absent from the entire corpus |
|
|
1146
|
-
|
|
1147
|
-
### 4.3 (c) The validator changes, consolidated
|
|
1148
|
-
|
|
1149
|
-
The single structural change: **split the assertion set into profiles.** Concretely —
|
|
1150
|
-
|
|
1151
|
-
1. **`--profile spine`** (spec-only): A00–A05, A07, A09, A10, A16 (widened to accept
|
|
1152
|
-
`4.3.75-beta`-style suffixes), A17, A18, A22, A23. These stay on for every rung.
|
|
1153
|
-
2. **`--profile spine-html`** (today's behaviour = spec + renderer policy): adds A06's PMA/rotation/
|
|
1154
|
-
full-page clauses, A08/A27's name-identity policy, A11, A12, A13, A14, A19, A20.
|
|
1155
|
-
3. **Archetype assertions** (A15, A21, A24–A26, A28–A30) already SKIP without `RigInfo`
|
|
1156
|
-
(`validate.ts:47-55`, `:808`) — no change; they simply never run on example data.
|
|
1157
|
-
4. **Widen `walkTimelines`** (`validate.ts:1159-1199`) to `ik`, `transform`, `path`, `slider`,
|
|
1158
|
-
`drawOrder`, `drawOrderFolder`, `events`, with channel tables for each (§1.8 has the numbers).
|
|
1159
|
-
Do this **before** the corresponding emitters land, not after — an unwalked group is a check that
|
|
1160
|
-
silently reports green.
|
|
1161
|
-
5. Keep A05's "unchecked timeline — extend the validator" fail-closed behaviour. It is the reason
|
|
1162
|
-
this document could enumerate the gap at all.
|
|
1163
|
-
|
|
1164
|
-
### 4.4 (d) What JSON cannot express
|
|
1165
|
-
|
|
1166
|
-
Only two things, both editor-affordance data with **zero rendering effect** (§1.11):
|
|
1167
|
-
|
|
1168
|
-
- **`bone.visible`** — read by `SkeletonBinary.ts:126`, not by `SkeletonJson`.
|
|
1169
|
-
- **`skin.color`** — read by `SkeletonBinary.ts:448`, not by `SkeletonJson`.
|
|
1170
|
-
|
|
1171
|
-
Neither appears in the ladder corpus (§3.0) and neither affects a rendered frame. **There is no
|
|
1172
|
-
rendering-relevant feature that the binary format can express and JSON cannot**, so "we only emit
|
|
1173
|
-
JSON" costs the benchmark nothing.
|
|
1174
|
-
|
|
1175
|
-
One near-miss worth naming: the atlas page **`scale:`** line is emitted by the Spine packer and read
|
|
1176
|
-
by *nobody* in spine-ts (§1.10). Reproducing it byte-for-byte is possible; reproducing its *effect*
|
|
1177
|
-
is not, because there is no effect to reproduce in this runtime.
|
|
1178
|
-
|
|
1179
|
-
---
|
|
1180
|
-
|
|
1181
|
-
## Things I could not verify
|
|
355
|
+
editor-affordance data with zero rendering effect: there is no *rendering-relevant* feature that
|
|
356
|
+
binary can express and JSON cannot.
|
|
1182
357
|
|
|
1183
|
-
1. **PNG-level facts.** `count_features.ts` reads only `.json` and `.atlas`, so
|
|
1184
|
-
`A19_OVERLAY_PNGS_HAVE_ALPHA` and `A06`'s "declared `size:` matches the file" were not evaluated
|
|
1185
|
-
against the corpus. `fetch-examples` does download the pages, and `src/png.ts` already measures
|
|
1186
|
-
PNG headers, so this is cheap to add.
|
|
1187
|
-
2. **`A14_NO_FULL_FRAME_MESH`** — needs `skeleton.width/height` compared against each mesh's
|
|
1188
|
-
`width`/`height`; not computed per file.
|
|
1189
|
-
3. **Binary `.skel` behaviour** was read from source only; I did not parse an actual `.skel`.
|
|
1190
|
-
4. **`spineboy-run.atlas`'s ten identical regions** — the file matches upstream byte length, so it is
|
|
1191
|
-
genuinely what the repo ships, but *why* it is shaped that way (a broken sequence export?) is
|
|
1192
|
-
unexplained. It is unreferenced by both spineboy skeletons.
|
|
1193
|
-
5. **Whether the Spine editor itself would round-trip a rigc file.** Esoteric documents JSON import
|
|
1194
|
-
(§"On third-party tools"), but nothing here tested it; the round-trip proof in this project is
|
|
1195
|
-
spine-core-only.
|
|
1196
|
-
6. **Collection ran against a rate-limited GitHub REST API**, so some directory listings came from an
|
|
1197
|
-
HTML fallback rather than the contents API. File contents came from `raw.githubusercontent.com`
|
|
1198
|
-
and byte lengths were spot-checked against upstream `Content-Length`. `fetch-examples.sh` uses the
|
|
1199
|
-
contents API and honours `GITHUB_TOKEN`; export one if a re-run hits the 60/hour limit.
|
|
1200
|
-
7. **Numbers here are a snapshot of branch `4.3`.** Re-run `bun run bench:usage` after a
|
|
1201
|
-
`fetch-examples` to confirm the corpus has not moved before relying on a count.
|