spine-rigc 0.25.2 → 0.25.3
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/docs/AUTHORING.md +50 -7
- package/docs/FACE.md +37 -0
- package/package.json +1 -1
- package/src/compile.ts +45 -2
- package/src/ingest.ts +13 -3
package/docs/AUTHORING.md
CHANGED
|
@@ -2006,13 +2006,27 @@ it, on every frame, with the gate green — the second reason to write
|
|
|
2006
2006
|
later one. `PS130` in `selftest.ts` poses both models rather than quoting the
|
|
2007
2007
|
runtime, and [`docs/FACE.md`](FACE.md) §8 is the same rule on a face's two axes.
|
|
2008
2008
|
|
|
2009
|
-
⚠️ **And `"additive": true` is not always available.**
|
|
2010
|
-
additive application at all: bone, deform, transform-constraint,
|
|
2011
|
-
physics `wind`/`gravity`, and a slider's own `mix`.
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2009
|
+
⚠️ **And `"additive": true` is not always available.** The list that supports
|
|
2010
|
+
additive application at all is the closed one: bone, deform, transform-constraint,
|
|
2011
|
+
path `position`, physics `wind`/`gravity`, and a slider's own `mix`. **Everything
|
|
2012
|
+
else ignores the flag** — a slot colour, an attachment swap, a draw order and a
|
|
2013
|
+
sequence, and also an **ik constraint's mix**, a path's `spacing`, and every
|
|
2014
|
+
physics timeline except those two — so two sliders sharing one of those overwrite
|
|
2015
|
+
each other whatever you write. ⚠️ The four spelled out here used to read as the
|
|
2016
|
+
whole of the complement and they are examples of it; `A40` was never reading a
|
|
2017
|
+
list, it reads the runtime's own `Timeline.additive`, which is why it refuses the
|
|
2018
|
+
ik case this sentence did not name.
|
|
2019
|
+
|
|
2020
|
+
⇒ **And "overwrite each other" has a direction, measured**: the slider **later in
|
|
2021
|
+
the `constraints` array** puts its own animation's value there and the earlier one
|
|
2022
|
+
contributes nothing at all, at every reading of either dial, with both flags set
|
|
2023
|
+
to `true`. Swap the two array entries and the answer swaps with them — it is the
|
|
2024
|
+
array that decides, not the flags and not which animation the file names first
|
|
2025
|
+
(`PS135` in `selftest.ts` poses four such targets both ways; `PS136` poses the
|
|
2026
|
+
three that do compose, and they are the same sum §3.5.2 states, over each target's
|
|
2027
|
+
own setup value). A40 names this case separately, because the fix is different:
|
|
2028
|
+
key such a property from one slider only, or move both edits into the single
|
|
2029
|
+
animation one slider applies.
|
|
2016
2030
|
|
|
2017
2031
|
#### 3.5.2.1 What each `property` can actually be read AS
|
|
2018
2032
|
|
|
@@ -2424,6 +2438,33 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2424
2438
|
Translate values are **relative to the bone's setup position**; scale values are
|
|
2425
2439
|
multipliers where `1` is setup; rotation is in degrees.
|
|
2426
2440
|
|
|
2441
|
+
⚠️ **A `slot` track's `property` is one of the two above, and anything else is a
|
|
2442
|
+
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
2443
|
+
attachment, rgba)`, §5.1's row. Until
|
|
2444
|
+
[#650](https://github.com/firejune/rigc/issues/650) it was not: the emitter had a
|
|
2445
|
+
branch for `attachment` and wrote **everything else** as an rgba timeline under
|
|
2446
|
+
the name you gave it, so a track spelled `sequence` compiled, emitted
|
|
2447
|
+
`slots.X.sequence` with rgba keys, and was refused one stage later by the gate
|
|
2448
|
+
(`A00_ROUNDTRIP_PARSE: threw: Invalid timeline type for a slot`) — while the
|
|
2449
|
+
one-channel spelling of the same mistake was refused at compile as `rgba value
|
|
2450
|
+
needs 4 channels, got 1`, a message about a key you had not written.
|
|
2451
|
+
|
|
2452
|
+
- The pair is the emitter's own dispatch table (`SLOT_TRACKS` in
|
|
2453
|
+
`src/compile.ts`): `compileTrack` reads it to pick its branch, and the refusal
|
|
2454
|
+
prints `Object.keys` of the same object, so what you are told a slot accepts
|
|
2455
|
+
is what it accepts.
|
|
2456
|
+
- **Nothing derives this page's copy of that pair from the table**, and the list
|
|
2457
|
+
is two names long: no `DQ*`/`RD*`/`CUR*` control reads §4.4 (the only gated
|
|
2458
|
+
table on this page is §3.5.2.1's, held by `RD01`–`RD06`). What keeps the two
|
|
2459
|
+
in step is the control that quotes the message — `RF23` in `selftest.ts` —
|
|
2460
|
+
which goes red if the accepted pair ever widens without this page moving with
|
|
2461
|
+
it.
|
|
2462
|
+
- The format has four more slot timelines (`rgb`, `alpha`, `rgba2`, `rgb2`) and
|
|
2463
|
+
rigc emits none of them, so their names are refused here too; `A12_NO_DARK_COLOR`
|
|
2464
|
+
refuses the last two in a file rigc did not write (SPEC_COVERAGE §2.1).
|
|
2465
|
+
`sequence` is a timeline on an **attachment**, not on a slot, and rigc does
|
|
2466
|
+
not emit that either.
|
|
2467
|
+
|
|
2427
2468
|
**A physics constraint's six tuning timelines override §4.6's table for the
|
|
2428
2469
|
length of an animation.** `{ "physics": "hair", "property": "wind", "keys": […] }`
|
|
2429
2470
|
is a wind that rises and falls; `damping` is how fast the jiggle settles,
|
|
@@ -4004,6 +4045,8 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4004
4045
|
| `skin "S": uses the long form … and also has a key "X"` | §3.4.1 — move the slot inside `attachments` |
|
|
4005
4046
|
| `animation "A" keys "X" as a path constraint, but the rig declares it as a "slider"` | §4.12 — a timeline group resolves by name AND type; use the field named after the constraint's own type |
|
|
4006
4047
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
4048
|
+
| `rgba value needs 4 channels, got 3` | §4.4 — an `rgba` key is `[r, g, b, a]`. It names no animation, slot or key time, and the only input that reaches it is a slot `rgba` key: the setup pose's `color` is refused earlier, by its own row, with the slot named |
|
|
4049
|
+
| `animation "A" slot "X" has no timeline "P" (it has: attachment, rgba)` | §4.4 — a slot has exactly two timelines and `P` is neither. Fix the spelling; a bone or constraint property written on a slot track is refused by its own row instead. Before [#650](https://github.com/firejune/rigc/issues/650) every other name compiled as an **rgba** timeline called `P`, and what you saw was `A00_ROUNDTRIP_PARSE` on the emitted file — or, for the one-channel spelling, `rgba value needs 4 channels, got 1` |
|
|
4007
4050
|
| `N pair(s) of animation names have no one order: … "turn" / "Turn" (case) — they are one name in two cases, and which of them the editor puts first is not measured; rename one of them so they differ by more than letter case` | **R10** — rename until no pair is left. The kind in brackets says which of the editor comparator's four UNMEASURED choices decides the pair: `case` (a pure case tie), `number` (one number written two ways, or a run of digits against a word) or `separator` (make the first character that differs a letter or a digit). rigc keys `animations` in the editor's own comparator — natural and case-insensitive ([#539](https://github.com/firejune/rigc/issues/539), [#543](https://github.com/firejune/rigc/issues/543)) — so a pair that comparator settles is emitted rather than refused, and only the four choices nobody has measured are a compile error; on those, the editor's re-key repoints every slider whose animation moves index ([#535](https://github.com/firejune/rigc/issues/535)) |
|
|
4008
4051
|
| `N pair(s) of skin names have no one order: … "Zulu" / "mike" (case) — folded to one case "Zulu" and "mike" order the other way round, so whether the editor folds SKIN names decides this pair` | **R11** — rename until no pair is left. The same shape as the row above with a **wider** family: #539 measured the editor's comparator for animation names and thereby ruled codepoint out, and nothing has ruled anything out for skin names, so a pair the candidates could disagree about is refused even where the animation rule would emit it. `Zulu`/`mike` and `mike10`/`mike2` build as animation names and are refused as skin names ([#541](https://github.com/firejune/rigc/issues/541)) |
|
|
4009
4052
|
| `slot "patch": placeholder "patch" is filled by the "default" skin AND by skins "zulu", "mike", and the Spine editor has no way to hold that … Move the default skin's entry for this slot into a named skin — call it "base"` | **R12** — do what it says: move that entry out of `default` into a named skin. The editor has no representation for a placeholder the default skin shares with a named one, in either spelling, and §3.4.2 has both measurements. Renaming the placeholder does not help; the shape is what is refused |
|
package/docs/FACE.md
CHANGED
|
@@ -1205,6 +1205,43 @@ green. ⇒ write `"additive": true` on **every** slider that shares a target and
|
|
|
1205
1205
|
not only on the later one, which is what the paragraph above already asks for and
|
|
1206
1206
|
this is the second reason for.
|
|
1207
1207
|
|
|
1208
|
+
🚨 **A second dial on a slot colour, an attachment swap or a draw order is not a
|
|
1209
|
+
second dial at all.** Those timelines ignore `additive`, so the flag is not the
|
|
1210
|
+
repair and writing it on both changes nothing: the slider **later in the
|
|
1211
|
+
`constraints` array** owns that property outright and the earlier one contributes
|
|
1212
|
+
nothing, at every position of its dial. An **ik constraint's mix** behaves the
|
|
1213
|
+
same way. What does compose is the rest of what a face keys — a bone transform, a
|
|
1214
|
+
mesh deform, a transform constraint's mix, a physics `wind` — and those are the
|
|
1215
|
+
same sum as the two axes above, over each target's own setup value. ⇒ if a blink
|
|
1216
|
+
fades a slot and the yaw dial also fades it, one of the two has to stop: key that
|
|
1217
|
+
property from **one** slider, or move both edits into the animation a single
|
|
1218
|
+
slider applies. `A40` refuses the rest by name, and AUTHORING §3.5.2 is the
|
|
1219
|
+
mechanism.
|
|
1220
|
+
|
|
1221
|
+
⭐ **Three dials are the same sum as two — as long as every one of them is
|
|
1222
|
+
additive.** The case worth knowing is a non-additive dial in the *middle* of
|
|
1223
|
+
three, because it is neither of the two failures you would expect: it erases every
|
|
1224
|
+
dial **before** it and is then added to by every dial **after** it, so the face is
|
|
1225
|
+
neither the sum nor the last dial alone, and no reading of a two-dial rig has that
|
|
1226
|
+
shape.
|
|
1227
|
+
|
|
1228
|
+
⚠️ **`"loop": true` puts the animation's FIRST frame at the top of the dial.** A
|
|
1229
|
+
looping slider wraps its time as a positive modulo rather than holding the last
|
|
1230
|
+
frame, so the axis is a sawtooth: the two ends of the range are the same pose and
|
|
1231
|
+
every position past the top repeats the range from its bottom. That is right for a
|
|
1232
|
+
parameter that genuinely cycles — a wheel, a breath — and wrong for a yaw, where
|
|
1233
|
+
the range's top has to *stay* at the extreme of the turn. Leave `loop` off for a
|
|
1234
|
+
face axis; the default is the one you want.
|
|
1235
|
+
|
|
1236
|
+
🔸 **And a `local: false` dial never reads back the number you set.** The world
|
|
1237
|
+
reader goes through the bone's matrix, where the reference runtime's float32 π
|
|
1238
|
+
leaves a fraction of a degree behind, and it has a period: a bone one whole turn
|
|
1239
|
+
from its position reads *identically*, so the dial cannot tell the two apart. The
|
|
1240
|
+
composition is unchanged — two world dials add exactly as two local ones do — but
|
|
1241
|
+
`local: true` is what makes the number on the dial the number the rig reads, which
|
|
1242
|
+
is the same repair §3.5.2's circle already asks for. Every one of the six
|
|
1243
|
+
`property` readings composes by that one arithmetic under `local: true`.
|
|
1244
|
+
|
|
1208
1245
|
✅ **The editor half, measured.** This paragraph said *unknown* until the round
|
|
1209
1246
|
trip was taken with `tools/editor_roundtrip.ts` on a licensed editor (data
|
|
1210
1247
|
version 4.3.26) against a 4.3.13 build of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Rig compiler for Spine — declarative rig specs in, Spine 4.3 skeleton data out, verified by a spine-core round-trip. Built so AI agents can author rigs and check their own work; the output imports into the Spine editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/compile.ts
CHANGED
|
@@ -853,6 +853,38 @@ const SLIDER_TRACKS: Record<string, ValueTrackShape> = {
|
|
|
853
853
|
mix: { fields: ['value'], identity: [1] },
|
|
854
854
|
};
|
|
855
855
|
|
|
856
|
+
/**
|
|
857
|
+
* Slot timelines (`animations.<a>.slots.<slot>.<timeline>`): the two
|
|
858
|
+
* `compileTrack` writes, and which key shape each one is written with.
|
|
859
|
+
*
|
|
860
|
+
* ⭐ **The table IS the dispatch.** `compileTrack` reads the shape out of here
|
|
861
|
+
* to pick its branch, and the refusal for a property that is not in it prints
|
|
862
|
+
* `Object.keys` of the same object — so the list an author is given cannot
|
|
863
|
+
* disagree with the list the emitter has, because there is only one.
|
|
864
|
+
*
|
|
865
|
+
* 🚨 It exists because the dispatch used to be one `if` on `attachment` and a
|
|
866
|
+
* fall-through to rgba, which made **every** other property name a legal
|
|
867
|
+
* spelling of an rgba timeline: `{"slot": "x", "property": "sequence", "v": [0,
|
|
868
|
+
* 0, 0, 0]}` compiled and wrote `slots.x.sequence` with rgba-shaped keys. The
|
|
869
|
+
* gate caught the file (`A00_ROUNDTRIP_PARSE: threw: Invalid timeline type for
|
|
870
|
+
* a slot`, and `A05_CURVE_ARRAY_LENGTH`) and the one-channel spelling of the
|
|
871
|
+
* same mistake was refused at compile as *"rgba value needs 4 channels, got
|
|
872
|
+
* 1"* — a message about a key nobody wrote. It is the shape `A21`'s
|
|
873
|
+
* `meshKinds[slot] || 'ring'` had (issue #44): a default that turns "nothing to
|
|
874
|
+
* emit" into an emission of the wrong thing (issue #650).
|
|
875
|
+
*
|
|
876
|
+
* ⚠️ The KEY is the timeline name as the file carries it, and the VALUE names
|
|
877
|
+
* the branch below that writes its keys — so an entry added here without a
|
|
878
|
+
* branch to write it is an entry emitted in some other timeline's shape, which
|
|
879
|
+
* is the defect this table closed rather than a new affordance. The format has
|
|
880
|
+
* four more (`rgb`, `alpha`, `rgba2`, `rgb2`); rigc emits none of them, and
|
|
881
|
+
* `A12_NO_DARK_COLOR` refuses the last two outright.
|
|
882
|
+
*/
|
|
883
|
+
export const SLOT_TRACKS: Record<string, 'attachment' | 'rgba'> = {
|
|
884
|
+
attachment: 'attachment',
|
|
885
|
+
rgba: 'rgba',
|
|
886
|
+
};
|
|
887
|
+
|
|
856
888
|
/**
|
|
857
889
|
* The three constraint families a `MotionTrack` can target, and the table of
|
|
858
890
|
* timelines each one accepts.
|
|
@@ -6601,6 +6633,16 @@ function compileTrack(
|
|
|
6601
6633
|
skinAttachments: Record<string, Record<string, SpineAttachment>>,
|
|
6602
6634
|
): SpineTimelineKey[] {
|
|
6603
6635
|
const where = `animation "${animName}" slot "${target}" ${track.property}`;
|
|
6636
|
+
// Before any key is shaped, and before the empty-track refusal: a property
|
|
6637
|
+
// the emitter has no branch for is the fault, and a track that names one has
|
|
6638
|
+
// no shape to be missing keys from (issue #650).
|
|
6639
|
+
const shape = SLOT_TRACKS[track.property];
|
|
6640
|
+
if (shape === undefined) {
|
|
6641
|
+
throw new CompileError(
|
|
6642
|
+
`animation "${animName}" slot "${target}" has no timeline "${track.property}" ` +
|
|
6643
|
+
`(it has: ${Object.keys(SLOT_TRACKS).join(', ')})`,
|
|
6644
|
+
);
|
|
6645
|
+
}
|
|
6604
6646
|
if (!track.keys.length) throw new CompileError(`${where}: no keys`);
|
|
6605
6647
|
|
|
6606
6648
|
const out: SpineTimelineKey[] = [];
|
|
@@ -6613,7 +6655,7 @@ function compileTrack(
|
|
|
6613
6655
|
}
|
|
6614
6656
|
checkKeyTime(where, time, key.t, duration);
|
|
6615
6657
|
|
|
6616
|
-
if (
|
|
6658
|
+
if (shape === 'attachment') {
|
|
6617
6659
|
if (key.v !== null && typeof key.v !== 'string') {
|
|
6618
6660
|
throw new CompileError(`${where}: attachment key value must be a string or null`);
|
|
6619
6661
|
}
|
|
@@ -6626,7 +6668,8 @@ function compileTrack(
|
|
|
6626
6668
|
continue;
|
|
6627
6669
|
}
|
|
6628
6670
|
|
|
6629
|
-
// rgba
|
|
6671
|
+
// rgba — the other shape `SLOT_TRACKS` names, and now the only way to reach
|
|
6672
|
+
// this branch: a property the table does not carry was refused above.
|
|
6630
6673
|
if (!Array.isArray(key.v)) throw new CompileError(`${where}: rgba key value must be [r,g,b,a]`);
|
|
6631
6674
|
const entry: SpineTimelineKey = { time, color: rgbaHex(key.v) };
|
|
6632
6675
|
if (key.ease !== undefined && key.curve !== undefined) {
|
package/src/ingest.ts
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* break `A18_DETERMINISTIC_EMIT` the first time anybody rebuilt from an ingested
|
|
45
45
|
* spec.
|
|
46
46
|
*/
|
|
47
|
-
import { SPINE_VERSION } from './compile.ts';
|
|
47
|
+
import { SLOT_TRACKS as EMITTED_SLOT_TRACKS, SPINE_VERSION } from './compile.ts';
|
|
48
48
|
import { MOTION_SPEC_VERSION, parseMotionSpec } from './motion.ts';
|
|
49
49
|
import { parseRigSpec, RIG_KEYS, RIG_SPEC_VERSION, type RigSpec } from './rig.ts';
|
|
50
50
|
import type { MotionSpec } from './types.ts';
|
|
@@ -347,8 +347,18 @@ const HEADER_REDERIVED = ['spine'];
|
|
|
347
347
|
/** The attachment types this module inverts. Everything else is refused by name. */
|
|
348
348
|
const ATTACHMENT_TYPES = ['region', 'mesh', 'boundingbox', 'clipping', 'path'];
|
|
349
349
|
|
|
350
|
-
/**
|
|
351
|
-
|
|
350
|
+
/**
|
|
351
|
+
* The slot timelines the motion spec carries — `compileTrack`'s own table,
|
|
352
|
+
* rather than a second list of the same two names.
|
|
353
|
+
*
|
|
354
|
+
* ⚠️ It was that second list until issue #650, spelled `['rgba', 'attachment']`
|
|
355
|
+
* with a comment saying where it had been copied from. The copy was true, which
|
|
356
|
+
* is the point: the emitter had no list at all — one `if` and a fall-through —
|
|
357
|
+
* so this module's blocker was the only place in `src/` that said what a slot
|
|
358
|
+
* track may be, and it said it about a compiler that accepted anything. Now
|
|
359
|
+
* there is one list and both sides read it.
|
|
360
|
+
*/
|
|
361
|
+
const SLOT_TRACKS = Object.keys(EMITTED_SLOT_TRACKS);
|
|
352
362
|
|
|
353
363
|
/**
|
|
354
364
|
* Everything this module has a branch for, as the branches themselves state it.
|