spine-rigc 0.25.3 → 0.25.4
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 +4 -4
- package/docs/AUTHORING.md +227 -24
- package/docs/FACE.md +97 -7
- package/package.json +1 -1
- package/src/compile.ts +169 -30
- package/src/validate.ts +403 -20
package/README.md
CHANGED
|
@@ -519,9 +519,9 @@ work on any frames you have, and `bench` is a repository workflow that needs a c
|
|
|
519
519
|
and `bun run fetch-examples`. The reasoning behind all three is in
|
|
520
520
|
[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
521
521
|
|
|
522
|
-
`build` and `validate` both default to `--profile spine` — the
|
|
522
|
+
`build` and `validate` both default to `--profile spine` — the 28 validity rules, which
|
|
523
523
|
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
|
|
524
|
-
adds all
|
|
524
|
+
adds all 43: the other 15 are one renderer's policy and one canvas budget's, and they
|
|
525
525
|
fire on perfectly correct editor-produced Spine data, so reach for that profile when
|
|
526
526
|
you are shipping into *that* project rather than to be thorough. A report always names
|
|
527
527
|
the profile it ran and lists what that profile left out.
|
|
@@ -663,7 +663,7 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
663
663
|
| 📥 **[docs/INGEST.md](docs/INGEST.md)** | **working with a skeleton you did not author.** What every command can and cannot do with a foreign `skeleton.json`, reading it with the toolchain, transcription as the route that makes it yours, what each validator complaint means on an export, and the re-pivot/rename/extend recipes. Ships in the package too |
|
|
664
664
|
| 🤖 **[docs/PROMPTING.md](docs/PROMPTING.md)** | **handing the authoring to an AI agent** — the prompt clauses a measured pilot run paid for, and what you can leave unsaid. Ships in the package too |
|
|
665
665
|
| 🔬 **[docs/SPEC_COVERAGE.md](docs/SPEC_COVERAGE.md)** | Spine 4.3's full export surface against what rigc emits and what the official examples measurably use, with the ordered gap list |
|
|
666
|
-
| 🎓 **[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the
|
|
666
|
+
| 🎓 **[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md)** | **why you can trust the output.** The yardstick, `diff` and `check` and what neither can see, the eight-rung ladder and the spineboy graduation exam, the run viewer, the 43 named assertions with their profiles, and the selftest that has watched every one of them fire. Repository material — it is not in the npm package |
|
|
667
667
|
| 📋 [LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) · [GATE.md](https://github.com/firejune/rigc/blob/main/docs/GATE.md) · [PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) | the live rung ledger, the clause statements a candidate is graded against, and how to run an agent through the ladder and score what comes back |
|
|
668
668
|
| 🗺️ [ROADMAP.md](https://github.com/firejune/rigc/blob/main/ROADMAP.md) | where this is going, and where it has been. What 1.0 has to mean before the number is claimed — conditions rather than a feature list, because direction here comes from what users hit |
|
|
669
669
|
|
|
@@ -719,7 +719,7 @@ quality."* All six, with their verdicts, are in
|
|
|
719
719
|
[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
|
720
720
|
|
|
721
721
|
The whole dossier — the yardstick, `diff` and `check` and what neither of them can
|
|
722
|
-
see, every rung, the run viewer, the
|
|
722
|
+
see, every rung, the run viewer, the 43 assertions and the selftest behind them — is
|
|
723
723
|
[docs/BENCHMARK.md](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
724
724
|
Live rung status is
|
|
725
725
|
[docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md).
|
package/docs/AUTHORING.md
CHANGED
|
@@ -163,7 +163,7 @@ What the flags mean:
|
|
|
163
163
|
| `--atlas-in` | `build` only: resolve every part against the **regions of a pre-packed `.atlas`** instead of against loose PNGs. Region geometry is read from the file and sizes are descaled by the page's `scale:`; the atlas is re-emitted into `--out`, re-anchored — **§0.2** |
|
|
164
164
|
| `--images` | where the rig spec's `image` names resolve (overrides the rig's own `images` field, and is relative to your working directory). For `pose` it is the directory of **loose part PNGs to place** — every `.png` in it is a part, in name order. For `chainfit` it is only where each attachment's image name **resolves**: the candidate decides what the parts are, so extra PNGs are unused and a missing name is refused by name (§12.3) |
|
|
165
165
|
| `--manifest` | a cut manifest. Only for a rig with **measured art** behind it; a foreign skeleton has none |
|
|
166
|
-
| `--profile` | `spine` = the
|
|
166
|
+
| `--profile` | `spine` = the 28 validity rules (**the default**) · `spine-html` = all 43, opt-in |
|
|
167
167
|
| `--candidate` | `check`, `bench`, `render`, `preview`, `chainfit` and `vote` only: a **compiled** artifact — the directory `build --out` wrote, or a `skeleton.json` path. `--atlas <path>` names the atlas when it does not sit beside the skeleton. **`vote` is the one command that takes it more than once** — repeat it 2–4 times, one per pane, labelled A, B, C, D in the order given; everywhere else a repeat is a typo and is refused |
|
|
168
168
|
| `--animation` | `render`, `preview` and `vote` only: which animation to show. The default is **every** one for `render`, the **first** for `preview`, and for `vote` the first of candidate A. A name the skeleton does not have is refused, with the ones it does have listed — and for `vote`, so is a name that only *some* candidates have |
|
|
169
169
|
| `--record` | `vote` only: a saved vote to check against its ballot and append to the ledger, instead of writing a ballot. This is the command's second mode; it takes no `--candidate` |
|
|
@@ -1900,6 +1900,14 @@ dial, the gate swings.
|
|
|
1900
1900
|
{ "name": "reveal", "type": "slider", "animation": "curtain", "time": 0 }
|
|
1901
1901
|
```
|
|
1902
1902
|
|
|
1903
|
+
⚠️ **This branch takes neither of the repairs the bone branch takes.**
|
|
1904
|
+
`Slider.update` applies `Math.max(0, time)` and the `loop` wrap *inside* its
|
|
1905
|
+
`if (bone !== null)`, so a bone-less slider's time is used exactly as the
|
|
1906
|
+
timeline left it: a negative time is not clamped to the first frame, it is a time
|
|
1907
|
+
before the animation starts, where `Animation.apply` leaves the pose it found
|
|
1908
|
+
untouched. That is the same picture as the first frame only when the first frame
|
|
1909
|
+
*is* the rest pose. [measured] `PS140` in `selftest.ts`.
|
|
1910
|
+
|
|
1903
1911
|
| Field | Meaning |
|
|
1904
1912
|
| --- | --- |
|
|
1905
1913
|
| `animation` | **required.** An animation the **motion spec** declares |
|
|
@@ -2006,21 +2014,89 @@ it, on every frame, with the gate green — the second reason to write
|
|
|
2006
2014
|
later one. `PS130` in `selftest.ts` poses both models rather than quoting the
|
|
2007
2015
|
runtime, and [`docs/FACE.md`](FACE.md) §8 is the same rule on a face's two axes.
|
|
2008
2016
|
|
|
2009
|
-
⚠️ **And `"additive": true` is not always available.**
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2017
|
+
⚠️ **And `"additive": true` is not always available.** What composes is bone,
|
|
2018
|
+
deform, transform-constraint, path `position` and path `mix`, physics
|
|
2019
|
+
`wind`/`gravity`, and a slider's own `mix` and `time`. The rest ignore the flag —
|
|
2020
|
+
a slot colour, an attachment swap, a draw order and a sequence, and also an **ik
|
|
2021
|
+
constraint's mix**, a path's `spacing`, and every physics timeline except those
|
|
2022
|
+
two — so two sliders sharing one of those overwrite each other whatever you
|
|
2023
|
+
write. ⚠️ The four spelled out here used to read as the whole of the complement
|
|
2024
|
+
and they are examples of it; `A40` was never reading a list, which is why it
|
|
2025
|
+
refuses the ik case this sentence did not name.
|
|
2026
|
+
|
|
2027
|
+
🚨 **That list is not `Timeline.additive`, and this is what it cost to learn.**
|
|
2028
|
+
The runtime's own flag says a class "supports being applied additively", and on
|
|
2029
|
+
two classes it is not what the class does: `PathConstraintMixTimeline` and
|
|
2030
|
+
`SliderTimeline` declare themselves non-additive and their `apply` passes the
|
|
2031
|
+
`add` argument straight through anyway — every other non-additive timeline either
|
|
2032
|
+
hardcodes `false` in the call, zeroes `add` first, or never reads it. So two
|
|
2033
|
+
additive sliders keying one path constraint's `mix`, or one slider's `time`,
|
|
2034
|
+
**do** compose, as the same sum as everything else above, and `A40` refused both
|
|
2035
|
+
by name with a message saying `"additive": true` would not compose them. ✅ **It
|
|
2036
|
+
no longer reads the flag: it poses each shared timeline twice with `add` set and
|
|
2037
|
+
reads whether the second application accumulated**
|
|
2038
|
+
([#655](https://github.com/firejune/rigc/issues/655)), so the two rigs above pass
|
|
2039
|
+
and the message names what the class was measured to do. [measured] `PS143` in
|
|
2040
|
+
`selftest.ts` poses all thirty spellings of the motion vocabulary under two
|
|
2041
|
+
additive sliders and prints the flag beside the behaviour; `PS145` holds the
|
|
2042
|
+
probe's verdict to that same pose on every one of them; `PS140` holds the `time`
|
|
2043
|
+
case to a grid.
|
|
2044
|
+
|
|
2045
|
+
⭐ **The same measurement retired a refusal nothing could have distinguished.**
|
|
2046
|
+
Two sliders whose animations both fire **events** were refused as sharing a
|
|
2047
|
+
property — and a slider fires no event at all: it applies its animation with
|
|
2048
|
+
`firedEvents` null, and `EventTimeline.apply` returns on that. The probe's third
|
|
2049
|
+
answer is *this timeline moved no pose at all*, so an events pair, and a physics
|
|
2050
|
+
`reset` pair with it, are simply not findings. Nothing else changed: a shared
|
|
2051
|
+
slot colour, attachment, draw order, ik mix, path `spacing` or physics property
|
|
2052
|
+
is refused exactly as before.
|
|
2053
|
+
|
|
2054
|
+
⇒ **And the `constraints` array decides twice, for two different reasons.**
|
|
2055
|
+
*Overwriting* has a direction: the slider **later in the array** puts its own
|
|
2056
|
+
animation's value there and the earlier one contributes nothing at all, at every
|
|
2057
|
+
reading of either dial, with both flags set to `true`. And the array is also the
|
|
2058
|
+
**update order**, for **every kind of constraint** — `Skeleton.updateCache` walks
|
|
2059
|
+
it and each constraint's `sort` pushes itself as it is reached — so a property a
|
|
2060
|
+
slider keys is read by the constraint that owns it only if the slider comes
|
|
2061
|
+
**first**. A dial that drives the authority of an earlier slider, the mix of an
|
|
2062
|
+
earlier ik constraint, the `position` of an earlier path constraint or the `wind`
|
|
2063
|
+
of an earlier physics constraint writes a value nothing reads again, and what
|
|
2064
|
+
that constraint drives is dead at every position of the dial.
|
|
2065
|
+
|
|
2066
|
+
✅ **That second one is a refusal by name since
|
|
2067
|
+
[#658](https://github.com/firejune/rigc/issues/658), and it covers every
|
|
2068
|
+
constraint kind since [#665](https://github.com/firejune/rigc/issues/665).**
|
|
2069
|
+
`A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` names the driving slider, the
|
|
2070
|
+
driven constraint and its kind, the property, both `constraints` indices, the
|
|
2071
|
+
runtime class whose `update` does the reading and the animation the key sits in,
|
|
2072
|
+
and its repair is the reorder. It is a rule of its own rather than a clause of
|
|
2073
|
+
`A40` because `A40`'s population is the sliders whose `mix` nothing keys — the
|
|
2074
|
+
exclusion *is* the shape of the hole — and not a clause of `A37`, whose `keyedBy`
|
|
2075
|
+
asks whether some animation keys the `mix` and never which one.
|
|
2076
|
+
⭐ **The runtime repairs this for bones and not for constraints**, which is why an
|
|
2077
|
+
author cannot reason it out from the bone case: `Slider.sort` clears `sorted` on
|
|
2078
|
+
every bone its animation keys so those bones re-sort *after* the slider, while
|
|
2079
|
+
for a constraint it calls `skeleton.constrained(...)`, which swaps the pose the
|
|
2080
|
+
constraint is read through and moves nothing in the update cache. 🔒 The same
|
|
2081
|
+
refusal covers a slider keying its **own** `mix` or `time` — the two indices
|
|
2082
|
+
equal. `Slider.update` reads both off the applied pose before it applies the
|
|
2083
|
+
animation, so a slider muted at setup whose own animation keys its `mix` up never
|
|
2084
|
+
runs at all and `A37` reports green over it.
|
|
2085
|
+
|
|
2086
|
+
⚠️ **The constraint's own pose holds the number either way, which is why nothing
|
|
2087
|
+
found this by reading a value back.** In both array orders the keyed property
|
|
2088
|
+
reads back exactly as written — [measured] an ik mix `0.000000..1.000000`, a path
|
|
2089
|
+
`position` `0.000000..0.900000`, a physics `wind` `0.000000..400.000000`, each
|
|
2090
|
+
identical in the two orders — and what changes is what the constraint *drives*:
|
|
2091
|
+
`0.000e+0` of travel against `1.662e+1`, `1.620e+2` and `4.256e+2` the other way
|
|
2092
|
+
round. 🔸 **One spelling is outside the rule and a reorder does not repair it**:
|
|
2093
|
+
a `physics` `reset` key. It writes no pose and fires only when a frame time is
|
|
2094
|
+
crossed, and a slider applies its animation at a single instant — so the key
|
|
2095
|
+
never fires in either order, and the SKIP says so rather than advising a move
|
|
2096
|
+
that would change nothing. [measured] `PS139`, `PS140`, `PS156`, `PS157`,
|
|
2097
|
+
`PS158`, `PS159` and `PS160` in `selftest.ts`.
|
|
2098
|
+
|
|
2099
|
+
Swap the two array entries and the answer swaps with them — it is the
|
|
2024
2100
|
array that decides, not the flags and not which animation the file names first
|
|
2025
2101
|
(`PS135` in `selftest.ts` poses four such targets both ways; `PS136` poses the
|
|
2026
2102
|
three that do compose, and they are the same sum §3.5.2 states, over each target's
|
|
@@ -2061,14 +2137,28 @@ runtime says so. [measured] against `spine-core` 4.3.13, one reader at a time
|
|
|
2061
2137
|
`Math.sqrt(a² + c²)`, so a bone at `scaleX: −1` reads **`+1`**, not `−1`: a
|
|
2062
2138
|
squash axis driven through negative scale gets the mirror of the dial you wrote.
|
|
2063
2139
|
The floor `0` is *reached*, not approached — a bone whose own scale or whose
|
|
2064
|
-
parent's is 0 reads exactly 0 — so a range whose bottom is exactly 0 is fine
|
|
2065
|
-
|
|
2140
|
+
parent's is 0 reads exactly 0 — so a range whose bottom is exactly 0 is fine.
|
|
2141
|
+
🚨 One that dips below it is not *dead*, it **folds**: −2 and +2 read the same
|
|
2142
|
+
number, select the same frame and pose the same face, so the axis doubles back
|
|
2143
|
+
on itself about the point it should have passed through. Such a range is
|
|
2144
|
+
**refused at compile** (§3.5.2.2, beside the `rotate` circle). [measured]
|
|
2145
|
+
`PS138` sweeps both halves and poses them; `PS151` poses the fold on a dial the
|
|
2146
|
+
refusal leaves standing — a legal `0`..`4` window turned below 0 by a consumer
|
|
2147
|
+
applies the same time at −2 and +2 to the bit, while the same mapping read
|
|
2148
|
+
`local: true` is on its own signed mapping at every cell.
|
|
2066
2149
|
- **`shearY` under `local: false` wraps like `rotate` does, and worse.** It is a
|
|
2067
2150
|
difference of two `atan2` calls, so at any one bone orientation the readable
|
|
2068
2151
|
window is 360° wide — `(−270 − θx, 90 − θx]`, where `θx` is the bone's world
|
|
2069
2152
|
x-axis angle. The bound in the table is the union over every orientation. ⇒ the
|
|
2070
2153
|
seam is **not at a fixed value of the driven field**; it is wherever the bone is
|
|
2071
2154
|
pointing. Prefer `local: true` for a shear axis.
|
|
2155
|
+
⚠️ **Nothing refuses a `shearY` range, and that is deliberate.** The reader
|
|
2156
|
+
keeps its sign — it does not fold the way the two `scale` readers do — and what
|
|
2157
|
+
it does instead is not a fact a rig spec holds: [measured] `PS155` sweeps three
|
|
2158
|
+
orientations across their own seams and each wraps by exactly one turn at
|
|
2159
|
+
`90° − θx` (90°, 45° and 150° for a dial bone at 0°, 45° and −60°), which any
|
|
2160
|
+
animation a consumer writes can move. A range rule here would have to name a
|
|
2161
|
+
seam the compiler cannot know, and would pass the case that actually breaks.
|
|
2072
2162
|
- **The bounds are not round numbers because `MathUtils.PI` is `3.1415927`** — the
|
|
2073
2163
|
float32 π of the reference runtime. Every degree in spine-core passes through
|
|
2074
2164
|
`180 / 3.1415927`, so a full turn converts as `359.99999468178214` and a bone at
|
|
@@ -2190,6 +2280,37 @@ not on that path at all. The alternative each refusal leaves open is to move the
|
|
|
2190
2280
|
range inside the circle (a neutral at 180°, say), which is the only form
|
|
2191
2281
|
`local: false` can express.
|
|
2192
2282
|
|
|
2283
|
+
🚨 **The two world `scale` dials have a floor at 0, and a range dipping below it
|
|
2284
|
+
is refused too — for a different reason than the circle's.** `FromScaleX.value`
|
|
2285
|
+
is `Math.sqrt(a² + c²)` and `FromScaleY.value` is `Math.sqrt(b² + d²)`, a
|
|
2286
|
+
**magnitude** with the driven field in both terms, so the positions below 0 are
|
|
2287
|
+
not unreachable, they are *already taken*: [measured] with `from: 0, to: 0.5,
|
|
2288
|
+
scale: 0.25` over a 1 s animation the window is −2..+2, and the dial at −2.000,
|
|
2289
|
+
−1.500, −1.000 and −0.500 applies **1.000000 s, 0.875000 s, 0.750000 s and
|
|
2290
|
+
0.625000 s** — the same six decimals +2.000, +1.500, +1.000 and +0.500 apply, and
|
|
2291
|
+
the same posed face. The refusal names the arc that is written twice:
|
|
2292
|
+
|
|
2293
|
+
> Positions below 0 read as the mirror of positions above it: 2.000 of the range below 0 repeats 0.000..2.000, which is 0.500s..1.000s of the animation, in reverse.
|
|
2294
|
+
|
|
2295
|
+
- **A bottom of exactly 0 is legal**, because that floor is *reached*: a 0..4
|
|
2296
|
+
window builds, gates green and sweeps the whole animation. A window a
|
|
2297
|
+
thousandth of a unit below it is refused.
|
|
2298
|
+
- **A range lying wholly below 0 gets its own sentence** — *"the whole 4.000 of
|
|
2299
|
+
this range is below 0, so it reaches none of the animation's 1s"* — and that
|
|
2300
|
+
figure is the width **under** the floor, not the reach to the far end, the same
|
|
2301
|
+
distinction [#434](https://github.com/firejune/rigc/issues/434) drew for the
|
|
2302
|
+
circle.
|
|
2303
|
+
- **`loop: true` changes nothing here**, unlike the circle: a fold is not a
|
|
2304
|
+
clamp, so there is no loop branch in the message. [measured] the same ±1.500
|
|
2305
|
+
pair applies 1.875000 s either way.
|
|
2306
|
+
- **The repairs** are `"local": true`, which reads `source.scaleX` signed so the
|
|
2307
|
+
negative half drives the *first* half of the animation ([measured] −2 →
|
|
2308
|
+
0.000 s, +2 → 1.000 s), or moving the range so it does not dip below 0.
|
|
2309
|
+
|
|
2310
|
+
[measured] `PS151`–`PS154` in `selftest.ts`. `shearY` has no such rule and
|
|
2311
|
+
§3.5.2.1 says why: that reader keeps its sign and wraps at a seam the bone's own
|
|
2312
|
+
orientation places, which a rig spec does not hold.
|
|
2313
|
+
|
|
2193
2314
|
⚠️ **An artifact can still carry a dead range** — one exported from the editor,
|
|
2194
2315
|
hand-edited, or built by an older rigc. `A39` reports that from the artifact side
|
|
2195
2316
|
as a key at a time no dial selects (§4.11.4); the compile refusal above is what
|
|
@@ -2393,11 +2514,24 @@ time puts it here.
|
|
|
2393
2514
|
| `transform` | transform constraint timelines — §4.10. Same reason |
|
|
2394
2515
|
| `deform` | deform timelines — §4.11. Same reason |
|
|
2395
2516
|
|
|
2396
|
-
`groups` (`name → [member, …]`) lets one track target several bones
|
|
2397
|
-
once; `lag` shifts every key of a track, and `stagger`
|
|
2398
|
-
member order. **Member order is load-bearing** — it is
|
|
2399
|
-
what a per-member value map is read against — so a
|
|
2400
|
-
twice is a compile error, and so is one that names none.
|
|
2517
|
+
`groups` (`name → [member, …]`) lets one track target several bones, slots or
|
|
2518
|
+
physics constraints at once; `lag` shifts every key of a track, and `stagger`
|
|
2519
|
+
adds a per-member delay in member order. **Member order is load-bearing** — it is
|
|
2520
|
+
what `stagger` counts and what a per-member value map is read against — so a
|
|
2521
|
+
group that names a member twice is a compile error, and so is one that names none.
|
|
2522
|
+
|
|
2523
|
+
⭐ **Which of the three a group's members are is decided by the `property`, not
|
|
2524
|
+
by the group.** A group declares names and nothing else; the compiler reads the
|
|
2525
|
+
property first — a physics timeline makes the members physics constraints, one of
|
|
2526
|
+
a bone's ten makes them bones, and `attachment`/`rgba` makes them slots — and then
|
|
2527
|
+
resolves every member against the rig as that. So a `group` is the one target
|
|
2528
|
+
where the property picks the family rather than the other way round (§4.4's ⭐ is
|
|
2529
|
+
about the three **constraint** families, which are picked by the field), and a
|
|
2530
|
+
property that is in none of the three tables is refused naming the group and all
|
|
2531
|
+
three vocabularies rather than being read as any of them
|
|
2532
|
+
([#661](https://github.com/firejune/rigc/issues/661); §4.4). A group whose members
|
|
2533
|
+
are not all of one family is not refused as such: the first member that is not
|
|
2534
|
+
what the property made it is the one named.
|
|
2401
2535
|
|
|
2402
2536
|
A group track's keys need not give every member the same value: `v` may be a map
|
|
2403
2537
|
keyed by member name, or a `derive` model the compiler evaluates per member.
|
|
@@ -2414,6 +2548,12 @@ and `{ "path": "ride", "property": "mix" }` are different timelines with the sam
|
|
|
2414
2548
|
property name — which is why the constraint's name goes in a field named after its
|
|
2415
2549
|
type rather than in a shared `constraint` key.
|
|
2416
2550
|
|
|
2551
|
+
⚠️ **`group` is the one exception, and the reason it is one is that it names no
|
|
2552
|
+
family:** a group is a list of member names, so the property is all there is to
|
|
2553
|
+
read — it decides whether those members are bones, slots or physics constraints
|
|
2554
|
+
(§4.3), and a property in none of those three tables is refused with all three
|
|
2555
|
+
lists rather than resolved as any of them.
|
|
2556
|
+
|
|
2417
2557
|
Three families are **not** tracks and sit beside `tracks` instead — `ik` (§4.9),
|
|
2418
2558
|
`transform` (§4.10) and `deform` (§4.11). The reason is the key rather than the
|
|
2419
2559
|
target: a track's key carries one `v`, and those three carry named fields each
|
|
@@ -2438,6 +2578,37 @@ a deform). Folding them in would make `v` mean four different things depending o
|
|
|
2438
2578
|
Translate values are **relative to the bone's setup position**; scale values are
|
|
2439
2579
|
multipliers where `1` is setup; rotation is in degrees.
|
|
2440
2580
|
|
|
2581
|
+
⚠️ **A `bone` track's `property` is one of the ten above, and anything else is a
|
|
2582
|
+
compile error** — `animation "A" bone "B" has no timeline "P" (it has: translate,
|
|
2583
|
+
translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate)`,
|
|
2584
|
+
§5.1's row. The ten are the two rows above read as one list, in the order the
|
|
2585
|
+
message prints them, and they are the emitter's own dispatch table (`BONE_TRACKS`
|
|
2586
|
+
in `src/compile.ts`): `resolveTargets` asks that table which family a track
|
|
2587
|
+
belongs to, `compileValueTrack` writes a key out of the shape it finds there, and
|
|
2588
|
+
the refusal prints `Object.keys` of the same object — so what you are told a bone
|
|
2589
|
+
accepts is what it accepts.
|
|
2590
|
+
|
|
2591
|
+
- Until [#656](https://github.com/firejune/rigc/issues/656) it printed no list at
|
|
2592
|
+
all: the refusal read `bone "B" cannot take slot property "P"`, which named the
|
|
2593
|
+
**slot** family for whatever you had written — `wobble`, `translateX`, `rgb` —
|
|
2594
|
+
and told you nothing about what a bone does take. Nothing wrong reached disk
|
|
2595
|
+
then either, because the dispatch was already this table; what was missing was
|
|
2596
|
+
the way forward.
|
|
2597
|
+
- `rgba` and `attachment` are the only two names that sentence was ever right
|
|
2598
|
+
about, and for those the redirect survives as a clause beside the list:
|
|
2599
|
+
`. "rgba" is a slot timeline — put the name in "slot"`. It is read off
|
|
2600
|
+
`SLOT_TRACKS`, so the two halves of the message cannot drift apart. A
|
|
2601
|
+
**constraint** property written on a bone track (`mix`, `inertia`, `position`,
|
|
2602
|
+
…) never reaches this refusal at all — it is refused first, by the row that
|
|
2603
|
+
names the field its constraint's name belongs in (§4.12).
|
|
2604
|
+
- **Nothing derives this page's copy of the ten from the table.** What keeps the
|
|
2605
|
+
two in step is the control that quotes the message — `RF26` in `selftest.ts` —
|
|
2606
|
+
which goes red if the list ever widens without this page moving with it, and
|
|
2607
|
+
`RF27` holds the slot clause the same way. The selftest's spelling census
|
|
2608
|
+
(`PS144`) reads the ten off the same message and compares them against the ten
|
|
2609
|
+
it actually poses, both ways, which is what makes the list checkable at all: it
|
|
2610
|
+
was stated there too until the refusal had something to state.
|
|
2611
|
+
|
|
2441
2612
|
⚠️ **A `slot` track's `property` is one of the two above, and anything else is a
|
|
2442
2613
|
compile error** — `animation "A" slot "X" has no timeline "P" (it has:
|
|
2443
2614
|
attachment, rgba)`, §5.1's row. Until
|
|
@@ -2465,6 +2636,33 @@ needs 4 channels, got 1`, a message about a key you had not written.
|
|
|
2465
2636
|
`sequence` is a timeline on an **attachment**, not on a slot, and rigc does
|
|
2466
2637
|
not emit that either.
|
|
2467
2638
|
|
|
2639
|
+
⚠️ **A `group` track's `property` is one of those two lists or the physics one,
|
|
2640
|
+
and anything else is a compile error** — `animation "A" group "G" has no timeline
|
|
2641
|
+
"P" (a bone group has: …; a slot group has: …; a physics constraint group has:
|
|
2642
|
+
…)`, §5.1's row. It is the only refusal on this page that prints **three** lists,
|
|
2643
|
+
and the reason is §4.3's: a group's family is decided by the property, so a
|
|
2644
|
+
property no table claims leaves the compiler with no family to answer for. All
|
|
2645
|
+
three come from the objects the dispatch reads — `BONE_TRACKS`, `SLOT_TRACKS` and
|
|
2646
|
+
`PHYSICS_TRACKS` in `src/compile.ts` — and the refusal is raised in
|
|
2647
|
+
`resolveTargets`, after the group's own existence check and before any member is
|
|
2648
|
+
resolved against the rig.
|
|
2649
|
+
|
|
2650
|
+
- Until [#661](https://github.com/firejune/rigc/issues/661) a group of **bones**
|
|
2651
|
+
with a misspelled bone property read `animation "A" targets unknown slot "M"`:
|
|
2652
|
+
with no table claiming the property the track fell through to the slot branch,
|
|
2653
|
+
and what you were told was that the first member is not a slot — on a file that
|
|
2654
|
+
named neither a slot nor that member. A group of **slots** got §4.4's slot row
|
|
2655
|
+
instead (`slot "M" has no timeline "P" (it has: attachment, rgba)`), which is
|
|
2656
|
+
true of the member and names one family out of three on a track whose family
|
|
2657
|
+
nothing had determined.
|
|
2658
|
+
- **A constraint property never reaches it.** `position`, `spacing` and `time` are
|
|
2659
|
+
refused first with the field their constraint's name goes in (§4.12), and a
|
|
2660
|
+
physics timeline spelled correctly is not an error at all — a group of physics
|
|
2661
|
+
constraints is how several are tuned in one track.
|
|
2662
|
+
- `RF30`–`RF34` in `selftest.ts` quote this message; the three lists are typed
|
|
2663
|
+
there rather than read off the tables, so widening any of the three without
|
|
2664
|
+
moving this page turns them red.
|
|
2665
|
+
|
|
2468
2666
|
**A physics constraint's six tuning timelines override §4.6's table for the
|
|
2469
2667
|
length of an animation.** `{ "physics": "hair", "property": "wind", "keys": […] }`
|
|
2470
2668
|
is a wind that rises and falls; `damping` is how fast the jiggle settles,
|
|
@@ -4040,12 +4238,16 @@ or the key's position in its own track. These are the frequent ones, verbatim:
|
|
|
4040
4238
|
| `rig constraint "X": declares "property" but no "bone"` | §3.5.2 — name the driving bone, or key `slider.<name>.time` instead |
|
|
4041
4239
|
| `rig constraint "X": drives off bone "Y" rotate with "local": false, and the driving values that reach animation "A" (0s..Ds) run from −15.000° to 15.000° … the whole part of the range below 0° is dead` | §3.5.2 — add `"local": true`, which reads the bone's own rotation signed and unwrapped, or move the range so it does not cross 0°. A world rotation is wrapped into `[0, 360]` before the slider maps it, so the negative half of the range is unreachable and pins to one frame |
|
|
4042
4240
|
| `… run from 300.000° to 500.000° … the whole part of the range past 360° is dead` | §3.5.2 — the same wall at the other end, and the same first repair: `"local": true`, or move the range so it does not run past 360°. `[0, 360]` is the whole of what that reader returns, so a bone turned to 500° is read as 140° and selects a time far from the one the range asked for. Ending *exactly* on 360° is fine — that is the full turn, and it misses nothing: the wrap rounds, so a bone a hair below 0° is read as exactly 360 |
|
|
4241
|
+
| `rig constraint "X": drives off bone "Y" scaleX with "local": false, … run from -2.000 to 2.000 … Positions below 0 read as the mirror of positions above it: 2.000 of the range below 0 repeats 0.000..2.000` | §3.5.2 — a world scale reading is `Math.sqrt(a² + c²)`, a magnitude, so the half of the range below 0 is the half above it played backwards. Add `"local": true`, which reads `source.scaleX` signed, or move the range so it does not dip below 0. A bottom of exactly 0 is fine — that floor is reached, not approached |
|
|
4242
|
+
| `… run from -6.000 to -2.000 … the whole 4.000 of this range is below 0, so it reaches none of the animation's 1s` | §3.5.2 — the same floor with the range wholly under it: every value the reader can return maps past the animation, so the dial holds one frame at every position. The figure is the width below 0, not the reach to the far end |
|
|
4043
4243
|
| `skin "S" activates bone "B", but that bone does not declare \`"skin": true\`` | §3.4.1 — the list and the flag are one switch; add the flag or drop the list |
|
|
4044
4244
|
| `bone "B" declares \`"skin": true\` but no skin activates it` | §3.4.1 — the other half: list it in the skin it belongs to, or drop the flag |
|
|
4045
4245
|
| `skin "S": uses the long form … and also has a key "X"` | §3.4.1 — move the slot inside `attachments` |
|
|
4046
4246
|
| `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 |
|
|
4047
4247
|
| `animation "A": "position" is a path constraint timeline, and this track names no constraint` | §4.12 — put the name in `"path"` |
|
|
4048
4248
|
| `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 |
|
|
4249
|
+
| `animation "A" bone "B" has no timeline "P" (it has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate)` | §4.4 — a bone has exactly ten timelines and `P` is none of them. Fix the spelling — the single-axis ones are lower-case (`translatex`, not `translateX`). A **constraint** property is refused first, by its own row, naming the field its constraint's name goes in. When `P` is a slot timeline the message says so and where to put the name: `. "rgba" is a slot timeline — put the name in "slot"`. Before [#656](https://github.com/firejune/rigc/issues/656) all of them read `bone "B" cannot take slot property "P"`, which named the slot family whatever you had written and listed nothing |
|
|
4250
|
+
| `animation "A" group "G" has no timeline "P" (a bone group has: translate, translatex, translatey, scale, scalex, scaley, shear, shearx, sheary, rotate; a slot group has: attachment, rgba; a physics constraint group has: inertia, strength, damping, mass, wind, gravity, mix, reset)` | §4.3, §4.4 — a group's family is decided by the property, and `P` is in none of the three tables, so there is no family to resolve the members as. Fix the spelling and the group becomes whichever family the property names. The group is refused before its members are looked up, so a member the rig does not declare is a **later** message; an unknown group NAME is an earlier one. Before [#661](https://github.com/firejune/rigc/issues/661) a group of bones read `animation "A" targets unknown slot "M"` and a group of slots got the slot row below, naming one family out of three |
|
|
4049
4251
|
| `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` |
|
|
4050
4252
|
| `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)) |
|
|
4051
4253
|
| `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)) |
|
|
@@ -4152,8 +4354,9 @@ Fix A00 and run it again ([#568](https://github.com/firejune/rigc/issues/568)).
|
|
|
4152
4354
|
| `A37_SLIDER_CONSTRAINT_EFFECTIVE` | both | a slider whose animation carries no timeline, one that loops a zero-length animation (the applied time is NaN), one driving off a bone at `scale: 0`, or one muted at setup with no animation keying its `mix` (§3.5.2). **SKIP** when the skeleton declares no slider |
|
|
4153
4355
|
| `A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED` | both | a bone or constraint a skin activates that is not `skinRequired` (the list changes nothing), or one that is `skinRequired` and no skin activates (it is never active). Two keys in two places, and only together do they mean "this belongs to that skin" (§3.4.1). **SKIP** when no skin activates anything and nothing is `skinRequired` |
|
|
4154
4356
|
| `A39_DEFORM_KEEPS_TRIANGLE_WINDING` | archetype | a `deform` key reverses a triangle's winding, so the mesh has locally turned inside out and draws its texture backwards there (§4.11). The detail names the animation, the slot, the attachment, the key index and time, and each reversed triangle with its vertex triple and its signed area before and after. Measured at the key's **own** time, deformed against the same posed bones undeformed, so a mirrored slot bone cancels and a wrong *projection* with intact winding is correctly silent. A projection past its fold angle is the usual cause — [FACE.md §4.2](FACE.md) has the closed form. Legitimate art does fold, so declare `invariants.deformMayFold` (§3.7) for a slot that folds on purpose. ⚠️ A key whose slot **draws no pixels at that key's own time** — faded to alpha exactly 0, or showing another attachment — is measured and then passed over, because "draws its texture backwards" is false when nothing of it is drawn; the key is named on the stats line (`deformKeysNotDrawn`) and in the `DEFORM` block, never silently. The bar is **exactly 0**: at alpha 0.5 the fold is still refused and the alpha is in the message. It is per key and per time, so the same slot folding at full alpha in another animation is refused as before. ⚠️ And the **spans between** consecutive keys are scanned too (§4.11.3, issue #403): the runtime interpolates, so a deform inside its fold angle at every key can be past it in between. That refusal is its own sentence — `BETWEEN key 0 (t=0s) and key 1 (t=0.5s), at t=…` — with the time solved for in closed form and then posed and measured like any key, alpha read at that same moment. `deformSpansScanned` says on every green build that the scan ran. ⚠️ And the **frame** it poses in is the one the animation is reached in (§4.11.4, issue #407): on a track when nothing applies it, and otherwise once per **slider**, with that slider's mapping inverted and its bone driven until the runtime selects the key's own time — because a slider picks the time, so the two are one number and posing them independently is a frame that never occurs. The frame is on every `DEFORM` line, on the stats line as `deformFrames`, and in the refusal itself when it is not the track. A key at a time **no dial value selects** is measured in the frame the runtime does land on, left out of `deformKeysMeasured` and named as `deformKeysUnreachable`/`deformUnreachable` — never refused and never silent. ⚠️ And the **skin** it poses in is the one the timeline is keyed on (§4.11.5, issue #583), since a deform's address is a `skin / slot / attachment` triple: the pose wears that skin, which also switches on any `skin: true` bone or constraint it activates, and the "nothing is drawn" sentence names the skin it was read under. **SKIP** when no animation carries a deform timeline, when nothing keyed has triangles, when every mesh keyed is exempt, when every key measured draws no pixels or is unreachable *and no span between them folds where anything is drawn*, or when there is no rig info at all |
|
|
4155
|
-
| `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` | both | two or more sliders whose animations key the same timeline, where a later one is not `additive` — it writes that property outright at `mix: 1` and every earlier slider on it is dead (§3.5.2). Also fires when the shared timeline **cannot** be
|
|
4357
|
+
| `A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET` | both | two or more sliders whose animations key the same timeline, where a later one is not `additive` — it writes that property outright at `mix: 1` and every earlier slider on it is dead (§3.5.2). Also fires when the shared timeline **cannot** be applied additively (a slot colour, an attachment swap, a draw order, an ik mix, a path's `spacing`, most physics properties), where `"additive": true` is not the fix and one of the two has to go. ⭐ Which of the two it is, is **posed rather than read off `Timeline.additive`**: the shared timeline is applied twice with `add` set and the detail says what it did ([#655](https://github.com/firejune/rigc/issues/655) — two classes declare that flag falsely about themselves, so a path constraint's `mix` and a slider's `time` were refused although they compose). The detail names the bone or slot and the property, every slider keying it in `constraints` order with its flag, which one wins today, and the class that was posed. Four shapes are deliberately not findings: a slider below `mix: 1` or with its `mix` keyed (the apply is then a lerp from the current pose, not an overwrite), two `skinRequired` sliders no skin activates together, two sliders on different properties, and a shared timeline that writes **nothing a pose holds** — an `events` timeline fires no event under a slider (`firedEvents` is null), so neither slider has anything there for the other to erase. **SKIP** when fewer than two sliders are at full authority; a PASS means two were compared |
|
|
4156
4358
|
| `A41_PHYSICS_SURVIVES_EDITOR_ROUND_TRIP` | both | a physics constraint driving a component the **Spine editor** cannot hold, on a rig that declared `invariants.editorRoundTrip` (§3.7). The editor's physics model holds `x` and `y` only, with no cap on how many at once, so a constraint driving `rotate`, `scaleX` or `shearX` is imported, exported and handed back driving **nothing** — measured over three rigs and twelve constraints with the predictions written first ([#540](https://github.com/firejune/rigc/issues/540)). The detail names the constraint and each component. ⚠️ rigc's own output is correct — every runtime plays a rotation jiggle — so this is opt-in and the default is *not* silence: on a rig that declares nothing it **SKIPs**, and the SKIP names the constraint and the component anyway, so an author learns without having asked. Fix by driving the constraint in `x`/`y`, or by dropping the declaration if the rig never goes near the editor. Disjoint from `A23_PHYSICS_CONSTRAINT_EFFECTIVE` by construction: A23 refuses an **empty** driven set, which is what comes back from the editor, and this refuses a non-empty one that will not survive going in. **SKIP** also when the rig declares the editor and carries no physics constraint at all |
|
|
4359
|
+
| `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` | both | a slider whose animation keys a property of a constraint **at or before it** in `constraints` (§3.5.2) — a slider's `mix` or `time`, an ik or transform mix, a path `position`, `spacing` or `mix`, any physics value. That array is the update order for every kind, and each constraint reads its own applied pose when its turn comes — `Slider.update` takes `mix` as the alpha it applies with and `time` as the time it applies at, `PhysicsConstraint.update` returns on `mix` 0 before reading the rest — so the key lands after the only read of it and `Posed.resetConstrained` discards it before the next frame: what the driven constraint drives is dead at every position of the driving dial, although its pose still holds the number ([#658](https://github.com/firejune/rigc/issues/658), [#665](https://github.com/firejune/rigc/issues/665)). The detail names the slider, the driven constraint with its kind, both array indices, the property, the runtime class whose `update` reads it, and the animation the key sits in. Fix by moving the driver earlier, or by keying that property from a slider that already is. **The two indices equal is the same failure**: a slider cannot key its own `mix` or `time`, and one muted at setup that keys its own `mix` up never applies anything at all — `A37` is silent there, because it asks whether *an* animation keys the mix and not which one. **Two shapes it deliberately leaves out**, both measured: a `physics` `reset` key, which fires on a crossed frame time and so never fires from a slider at all, in either order — the reorder would repair nothing; and a physics timeline naming no constraint, which is every physics constraint declaring that property global and IS refused for the ones already run. Disjoint from `A40` by construction: `A40` asks who writes a shared property last and excludes every slider whose `mix` is keyed, this asks whether anything reads what was written. **SKIP** when the skeleton declares no slider, and when no slider's animation keys a constraint property — that SKIP names any `reset` keys it found — a pass means a driver and a driven were compared |
|
|
4157
4360
|
|
|
4158
4361
|
`both ◑` marks a mixed assertion: its validity half always runs and its policy
|
|
4159
4362
|
clauses are gated by profile.
|
package/docs/FACE.md
CHANGED
|
@@ -1211,12 +1211,23 @@ repair and writing it on both changes nothing: the slider **later in the
|
|
|
1211
1211
|
`constraints` array** owns that property outright and the earlier one contributes
|
|
1212
1212
|
nothing, at every position of its dial. An **ik constraint's mix** behaves the
|
|
1213
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
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
mechanism.
|
|
1214
|
+
mesh deform, a transform constraint's mix, a physics `wind`, a path constraint's
|
|
1215
|
+
`mix`, and another slider's own `mix` or `time` — and those are the same sum as
|
|
1216
|
+
the two axes above, over each target's own setup value. ⇒ if a blink fades a slot
|
|
1217
|
+
and the yaw dial also fades it, one of the two has to stop: key that property
|
|
1218
|
+
from **one** slider, or move both edits into the animation a single slider
|
|
1219
|
+
applies. `A40` refuses the rest by name, and AUTHORING §3.5.2 is the mechanism.
|
|
1220
|
+
|
|
1221
|
+
✅ **And which of the two a timeline is, the gate now poses rather than asks.**
|
|
1222
|
+
It read `Timeline.additive`, the runtime's own declaration, which two classes
|
|
1223
|
+
state falsely about themselves — so a path constraint's `mix` and a slider's
|
|
1224
|
+
`time`, both in the composing list above, were refused by name with a message
|
|
1225
|
+
saying the flag would not compose them
|
|
1226
|
+
([#655](https://github.com/firejune/rigc/issues/655)). `A40` applies the shared
|
|
1227
|
+
timeline twice with `add` set instead and reads whether the second application
|
|
1228
|
+
accumulated. The same measurement retired a refusal no pose could have told
|
|
1229
|
+
apart: two dials whose animations both fire **events** were refused, and a slider
|
|
1230
|
+
fires no event at all — it applies its animation with `firedEvents` null.
|
|
1220
1231
|
|
|
1221
1232
|
⭐ **Three dials are the same sum as two — as long as every one of them is
|
|
1222
1233
|
additive.** The case worth knowing is a non-additive dial in the *middle* of
|
|
@@ -1240,7 +1251,86 @@ from its position reads *identically*, so the dial cannot tell the two apart. Th
|
|
|
1240
1251
|
composition is unchanged — two world dials add exactly as two local ones do — but
|
|
1241
1252
|
`local: true` is what makes the number on the dial the number the rig reads, which
|
|
1242
1253
|
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
|
|
1254
|
+
`property` readings composes by that one arithmetic under `local: true`, and so
|
|
1255
|
+
does every one of them read through the world — the composition is the same sum
|
|
1256
|
+
on both sides of the flag, and what the flag changes is what the dial can say.
|
|
1257
|
+
|
|
1258
|
+
🚨 **A `local: false` scale axis folds at zero: the negative half is the positive
|
|
1259
|
+
half again.** A world scale reading is a square root, so a dial at −2 and a dial
|
|
1260
|
+
at +2 are not two positions — they read the same, select the same frame and pose
|
|
1261
|
+
the same face. Nothing at compile or at runtime says so, and a squash axis
|
|
1262
|
+
authored through negative scale therefore gets the mirror of the dial you wrote,
|
|
1263
|
+
symmetric about the point where it should have passed through. A world `shearY`
|
|
1264
|
+
axis folds the same way at a whole turn, and its seam is not at a fixed value —
|
|
1265
|
+
it moves with wherever the bone is pointing. ⇒ for a scale or a shear axis, write
|
|
1266
|
+
`local: true`; for a scale axis you cannot, keep the whole range on one side of
|
|
1267
|
+
zero, because the reader has no other half to give you.
|
|
1268
|
+
|
|
1269
|
+
⭐ **A dial can drive another dial's authority, and that composes as a product.**
|
|
1270
|
+
A slider's own `mix` is a keyable property, so one axis can scale another axis'
|
|
1271
|
+
whole contribution — a "strength" dial over an expression, which is the one shape
|
|
1272
|
+
on this page that multiplies rather than adds. ⚠️ **It only works downward through
|
|
1273
|
+
the `constraints` array.** A slider reads its own authority when its turn comes
|
|
1274
|
+
and the array is the update order, so a dial that keys the `mix` of a slider
|
|
1275
|
+
*earlier* than itself writes a number that slider has already read past: the
|
|
1276
|
+
driven axis is dead at every position, every frame. ✅ **The gate names that pair
|
|
1277
|
+
now** — `A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER` refuses it with both
|
|
1278
|
+
sliders, the property and both array indices, and says which way to move them
|
|
1279
|
+
([#658](https://github.com/firejune/rigc/issues/658)). Until then it was green,
|
|
1280
|
+
for a reason worth knowing: a slider whose `mix` is keyed at all leaves `A40`'s
|
|
1281
|
+
comparison, so nothing in the tool was looking at that pair. ⇒ put the driving
|
|
1282
|
+
slider **first**, which is what the refusal tells you to do.
|
|
1283
|
+
|
|
1284
|
+
🚨 **And a dial cannot turn itself on.** `Slider.update` reads its own `mix` as
|
|
1285
|
+
the alpha it applies the animation with, before that animation runs, so a slider
|
|
1286
|
+
whose *own* animation keys its `mix` writes after the only read of it — the same
|
|
1287
|
+
refusal with the two array indices equal. The shape to watch for is an axis
|
|
1288
|
+
muted at setup that means to raise itself: it never applies anything at all,
|
|
1289
|
+
because `update` returns on `mix` 0 before reaching the key that would raise it,
|
|
1290
|
+
and `A37` reports green because it asks whether *an* animation keys the mix and
|
|
1291
|
+
never which one.
|
|
1292
|
+
|
|
1293
|
+
🚨 **And it is not only another dial: a face axis that drives a jiggle or a
|
|
1294
|
+
path is the same rule.** `physics.<name>.wind`, `path.<name>.position`, an ik or
|
|
1295
|
+
transform mix — every property a dial can key belongs to a constraint that reads
|
|
1296
|
+
it when its own turn comes, and that turn is its place in `constraints`. A
|
|
1297
|
+
"wind strength" dial declared after the physics constraint it drives poses the
|
|
1298
|
+
number in that constraint's pose and moves nothing at all: [measured] the bone a
|
|
1299
|
+
physics constraint drives travels `0.000e+0` across the dial with the constraint
|
|
1300
|
+
declared first and `4.256e+2` with it declared last, and a path constraint's
|
|
1301
|
+
rider `0.000e+0` against `1.620e+2`
|
|
1302
|
+
([#665](https://github.com/firejune/rigc/issues/665)). ✅ `A42` names those pairs
|
|
1303
|
+
too, with the constraint's kind and both array indices. ⇒ **every constraint a
|
|
1304
|
+
dial drives goes after that dial in `constraints`** — which, for a face, means
|
|
1305
|
+
the dials come first and the jiggles, paths and aim constraints they scale come
|
|
1306
|
+
after. 🔸 One key is outside the rule because no order repairs it: a `physics`
|
|
1307
|
+
`reset` from a dial fires on a crossed frame time and a slider applies its
|
|
1308
|
+
animation at a single instant, so it never fires at all — the gate says that in
|
|
1309
|
+
its SKIP rather than asking you to move anything.
|
|
1310
|
+
|
|
1311
|
+
🔸 **The bone-less slider is the same story one field over.** A slider with no
|
|
1312
|
+
`bone` takes its time from `slider.<name>.time`, which any animation can key — and
|
|
1313
|
+
two dials keying it *add*, so a time-driven axis composes like everything else
|
|
1314
|
+
here, and since [#655](https://github.com/firejune/rigc/issues/655) the gate
|
|
1315
|
+
agrees: it used to refuse exactly this rig. The same array rule applies, for the
|
|
1316
|
+
same reason — and `A42` refuses it there too, naming `time` instead of `mix`.
|
|
1317
|
+
⚠️ Two things the bone
|
|
1318
|
+
form does and this one does not: there is no `Math.max(0, time)` and no wrap, so a
|
|
1319
|
+
driven time below zero does not pose the first frame — it leaves the pose exactly
|
|
1320
|
+
as it found it, which is a different picture whenever the animation's first frame
|
|
1321
|
+
is not the rest pose.
|
|
1322
|
+
|
|
1323
|
+
⚠️ **Two `skinRequired` sliders are three states, not two.** Under a skin that
|
|
1324
|
+
lists one of them the face is that dial alone; under a skin that lists the other
|
|
1325
|
+
it is the other alone; and under a skin that lists **neither** — the default skin
|
|
1326
|
+
is usually one — every dial is dead and the face holds its rest pose with both
|
|
1327
|
+
dials turned to their extremes. That last state is indistinguishable, from the
|
|
1328
|
+
outside, from a rig whose sliders do not work, and the gate is right to be silent
|
|
1329
|
+
about it because the pair genuinely never meets.
|
|
1330
|
+
|
|
1331
|
+
⭐ **Four dials are the same sum as three.** Nothing new arrives with the fourth
|
|
1332
|
+
axis: the arithmetic, the flag, and the non-additive-in-the-middle case all read
|
|
1333
|
+
exactly as they do above. Write `"additive": true` on all of them.
|
|
1244
1334
|
|
|
1245
1335
|
✅ **The editor half, measured.** This paragraph said *unknown* until the round
|
|
1246
1336
|
trip was taken with `tools/editor_roundtrip.ts` on a licensed editor (data
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.4",
|
|
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": {
|