@rig-c/core 2.64.3 → 2.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ published beside it with the same files at every version.
26
26
 
27
27
  <p align="center"><em>Fourteen part PNGs drawn from scratch for this repo, one rig spec, one motion spec — the assembly,
28
28
  the breathing and the wave are all rigc-compiled Spine animations, rendered with
29
- <code>rigc render</code>.</em></p>
29
+ <code>rigc render</code>. A <a href="https://raw.githubusercontent.com/rig-c/core/main/assets/rigc-demo@2x.gif">2x version</a> (1200x810) sits beside it.</em></p>
30
30
 
31
31
  Loose part PNGs and two small JSON files in; **Spine 4.3 skeleton data out** — a
32
32
  `skeleton.json` and a `skeleton.atlas` that load in any Spine runtime and **import
@@ -526,7 +526,7 @@ a rig compiles green, because green says nothing at all about the picture.
526
526
  conditions, <code>rigc pose</code> reads where every part sits in each picture, two
527
527
  candidate in-betweenings are compiled from the same pair — and a real
528
528
  <code>rigc vote</code> ballot picks the winner, because the movement between the poses
529
- is the one thing no instrument here will grade.</em></p>
529
+ is the one thing no instrument here will grade. A <a href="https://raw.githubusercontent.com/rig-c/core/main/assets/rigc-keypose@2x.gif">2x version</a> (1200x810) sits beside it.</em></p>
530
530
 
531
531
  Three properties of `vote` are worth stating, because they are what make its ledger
532
532
  usable by the next agent rather than by a reader: **a tie is a recorded outcome, not a
@@ -580,7 +580,7 @@ while <code>gaze</code> and <code>turn</code> return to rest, so the hand-offs m
580
580
  differing pixels — and the composing is the consumer's. Authorable on plain Spine 4.3, no
581
581
  plugin, no runtime patch; what the turn costs is authoring rather than runtime capability,
582
582
  and that cost is one stated expression per key. Compiled and rendered entirely by the
583
- published package.</em></p>
583
+ published package. A <a href="https://raw.githubusercontent.com/rig-c/core/main/assets/rigc-scene@2x.gif">2x version</a> (1200x810) sits beside it.</em></p>
584
584
 
585
585
  🎞️ **How the three films on this page were made** is kept with them, one directory per
586
586
  film in [`films/`](https://github.com/rig-c/core/tree/main/films) — a `run.sh` that
package/docs/AUTHORING.md CHANGED
@@ -4993,6 +4993,14 @@ consumer's, decided by dressing the skeleton rather than by the animation.
4993
4993
  Seconds, not frames: nothing requires a key to land on any frame grid, and a
4994
4994
  reference rendered at some rate says nothing about where its keys are. Put keys
4995
4995
  where the motion changes.
4996
+ ⚠️ **A phrase generated cycle by cycle ends one key before its period.** Write
4997
+ each cycle of a 1 s phrase as t = 0…1 and the closing key of one cycle lands on
4998
+ the opening key of the next, so the track is refused at the first boundary; the
4999
+ refusal names both keys and, when the keys after the pair repeat the spacing of
5000
+ the keys before it, says so. Generate each cycle without its closing key and
5001
+ keep that key on the last cycle only: a looping player takes `time % duration`
5002
+ and nothing interpolates across the wrap, so a track that stops short of its
5003
+ period holds its last key until it (issue #1506).
4996
5004
  - **Key times are emitted as float32s like every other number, and never stored
4997
5005
  LATER than you wrote them.** Every emitted number is the shortest decimal naming
4998
5006
  its float32 — the text the editor writes, and the precision the runtime keeps,
@@ -7213,7 +7221,7 @@ retain the minimal inputs and report that code rather than inventing author data
7213
7221
  | <a id="CE_COMPILE_CURVE_VALUE_NONFINITE"></a>`CE_COMPILE_CURVE_VALUE_NONFINITE` | “&lt;value&gt; (t=&lt;value&gt;): raw curve holds a non-finite value &lt;value&gt;” | Supply finite numbers in the raw curve array. |
7214
7222
  | <a id="CE_COMPILE_VALUE_TIMELINE_PROPERTY_UNKNOWN"></a>`CE_COMPILE_VALUE_TIMELINE_PROPERTY_UNKNOWN` | “animation "&lt;value&gt;": &lt;value&gt; "&lt;value&gt;" has no property "&lt;value&gt;"” | Use a property present on the named target's timeline family. |
7215
7223
  | <a id="CE_COMPILE_TIMELINE_KEYS_EMPTY"></a>`CE_COMPILE_TIMELINE_KEYS_EMPTY` | “&lt;value&gt;: no keys”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>“animation "&lt;value&gt;" events”<br>“animation "&lt;value&gt;" drawOrder”<br>“animation "&lt;value&gt;" slot "&lt;value&gt;" &lt;value&gt;”<br>“animation "&lt;value&gt;" &lt;value&gt; constraint "&lt;value&gt;"”<br>Here the values named in the message include: “default”. | Supply at least one key in the timeline. |
7216
- | <a id="CE_COMPILE_KEY_TIMES_NOT_INCREASING"></a>`CE_COMPILE_KEY_TIMES_NOT_INCREASING` | “&lt;value&gt;: key times must strictly increase (at t=&lt;value&gt;)”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>“animation "&lt;value&gt;" drawOrder”<br>“animation "&lt;value&gt;" slot "&lt;value&gt;" &lt;value&gt;”<br>“animation "&lt;value&gt;" &lt;value&gt; constraint "&lt;value&gt;"”<br>Here the values named in the message include: “default”. | Order keys by strictly increasing time. |
7224
+ | <a id="CE_COMPILE_KEY_TIMES_NOT_INCREASING"></a>`CE_COMPILE_KEY_TIMES_NOT_INCREASING` | “&lt;value&gt;: key times must strictly increase — key &lt;value&gt; at t=&lt;value&gt; does not come after key &lt;value&gt; at t=&lt;value&gt; (keys counted from 0)”, and when the two keys share a time and the keys after them repeat the spacing of the keys before them: “. The keys from t=&lt;value&gt; repeat every &lt;value&gt;s, so key &lt;value&gt; closes one cycle and key &lt;value&gt; opens the next at the same time: a phrase that repeats every &lt;value&gt;s ends one key before its period. Generate each cycle without its closing key and keep that key on the last cycle only — a looping player wraps from it to t=0, and a track that stops short holds its last key until the wrap”<br>Also: “animation "&lt;value&gt;" deform &lt;value&gt;/&lt;value&gt;/&lt;value&gt;”<br>“animation "&lt;value&gt;" drawOrder”<br>“animation "&lt;value&gt;" slot "&lt;value&gt;" &lt;value&gt;”<br>“animation "&lt;value&gt;" &lt;value&gt; constraint "&lt;value&gt;"”<br>Here the values named in the message include: “default”. | Order keys by strictly increasing time; the message names both keys of the pair. A phrase generated cycle by cycle with each cycle written from its start to its end repeats every boundary key — drop each cycle's closing key and keep it on the last cycle only (§4.5). |
7217
7225
  | <a id="CE_COMPILE_STEPPED_TIMELINE_EASING"></a>`CE_COMPILE_STEPPED_TIMELINE_EASING` | “&lt;value&gt; key at t=&lt;value&gt; carries [an easing ("&lt;value&gt;") / a curve], and this timeline is stepped by the format — its reader builds no curve, so the mode changes AT the key and holds until the next one. Remove it”<br>Also: “animation "&lt;value&gt;" &lt;value&gt; "&lt;value&gt;" &lt;value&gt;” | Remove easing/curve from the stepped mode timeline. |
7218
7226
  | <a id="CE_COMPILE_TIMELINE_MODE_UNKNOWN"></a>`CE_COMPILE_TIMELINE_MODE_UNKNOWN` | “&lt;value&gt; key at t=&lt;value&gt; names mode &lt;value&gt;; &lt;value&gt;”<br>“known: &lt;value&gt; — the runtime folds the case of the first letter and of nothing else”<br>Also: “animation "&lt;value&gt;" &lt;value&gt; "&lt;value&gt;" &lt;value&gt;”<br>“, ”<br>Here the values named in the message include: “noScaleOrReflection”, “normal”, “onlyTranslation”, “noRotationOrReflection”. | Use a mode named in the timeline's allowed mode list. |
7219
7227
  | <a id="CE_COMPILE_VALUELESS_TIMELINE_VALUE"></a>`CE_COMPILE_VALUELESS_TIMELINE_VALUE` | “&lt;value&gt;: this timeline takes no value; use null”<br>Also: “animation "&lt;value&gt;" &lt;value&gt; "&lt;value&gt;" &lt;value&gt;” | Use null for a timeline that takes no value. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/core",
3
- "version": "2.64.3",
3
+ "version": "2.65.0",
4
4
  "description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/compile.ts CHANGED
@@ -1148,6 +1148,51 @@ function checkKeyTime(where: string, time: number, authored: number, duration: n
1148
1148
  );
1149
1149
  }
1150
1150
 
1151
+ /**
1152
+ * The one refusal for a key that does not come after the key before it, for
1153
+ * every timeline kind (issue #1506) — six call sites used to spell it six times.
1154
+ *
1155
+ * It names both keys by index and authored time, because the time alone does
1156
+ * not say which pair to look at: a generated phrase of four one-second cycles,
1157
+ * each written t = 0…1, collides at every cycle boundary, and `(at t=1)` read
1158
+ * the same as an ordinary slip. When the two keys share a time AND the keys
1159
+ * after the pair repeat the spacing of the keys before it, the period is read
1160
+ * off the keys themselves — the second key's time less the first key's — and
1161
+ * the message says that the closing key of one cycle is the opening key of the
1162
+ * next. Nothing else is inferred: a duplicate with no keys after it, or with
1163
+ * keys that do not repeat, carries the pair alone, and the declared `loop` is
1164
+ * not consulted because it is a player hint (§4.1) and the boundary is the same
1165
+ * either way. The fix is phrased from the runtime rather than from taste:
1166
+ * `Animation.apply` wraps a looping time as `time % duration` and the last
1167
+ * frame of every curve timeline is stepped, so a track that stops short of its
1168
+ * period holds its last key until the wrap rather than easing into t = 0.
1169
+ *
1170
+ * ⚠️ The repetition is compared in authored seconds within one float32 step of
1171
+ * the later key (`float32Step`) — `lag`/`stagger` shift every key of a track by
1172
+ * the same amount, so the spacing is the same before and after them.
1173
+ */
1174
+ function refuseKeyOrder(where: string, keys: ReadonlyArray<{ t: number }>, i: number, duplicate: boolean): never {
1175
+ const at = keys[i].t;
1176
+ const before = keys[i - 1].t;
1177
+ const period = at - keys[0].t;
1178
+ const following = Math.min(i - 1, keys.length - 1 - i);
1179
+ let repeats = duplicate && period > 0 && following >= 1;
1180
+ for (let j = 1; repeats && j <= following; j++) {
1181
+ if (Math.abs(keys[i + j].t - keys[j].t - period) > float32Step(keys[i + j].t)) repeats = false;
1182
+ }
1183
+ const boundary = repeats
1184
+ ? `. The keys from t=${keys[0].t} repeat every ${period}s, so key ${i - 1} closes one cycle and key ${i} opens ` +
1185
+ `the next at the same time: a phrase that repeats every ${period}s ends one key before its period. Generate ` +
1186
+ 'each cycle without its closing key and keep that key on the last cycle only — a looping player wraps from ' +
1187
+ 'it to t=0, and a track that stops short holds its last key until the wrap'
1188
+ : '';
1189
+ throw new CompileError(
1190
+ `${where}: key times must strictly increase — key ${i} at t=${at} does not come after key ${i - 1} at ` +
1191
+ `t=${before} (keys counted from 0)${boundary}`,
1192
+ 'CE_COMPILE_KEY_TIMES_NOT_INCREASING',
1193
+ );
1194
+ }
1195
+
1151
1196
  function channelHex(v: number): string {
1152
1197
  const clamped = Math.max(0, Math.min(1, v));
1153
1198
  return Math.round(clamped * 255)
@@ -8106,9 +8151,7 @@ function compileValueTrack(
8106
8151
  const key = track.keys[i];
8107
8152
  const next = track.keys[i + 1];
8108
8153
  const time = keyTime(key.t + shift);
8109
- if (i > 0 && time <= (out[i - 1].time as number)) {
8110
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
8111
- }
8154
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, track.keys, i, time === out[i - 1].time);
8112
8155
  checkKeyTime(where, time, key.t, duration);
8113
8156
  // A named-value timeline (`inherit`): the key's `v` is one name, stepped by
8114
8157
  // the format. The name is resolved rather than copied, and what is WRITTEN
@@ -8237,9 +8280,7 @@ function compileDrawOrder(
8237
8280
  for (let i = 0; i < keys.length; i++) {
8238
8281
  const key = keys[i];
8239
8282
  const time = keyTime(key.t);
8240
- if (i > 0 && time <= (out[i - 1].time as number)) {
8241
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
8242
- }
8283
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, keys, i, time === out[i - 1].time);
8243
8284
  checkKeyTime(where, time, key.t, duration);
8244
8285
  // No offsets = "back to the setup draw order", which is the parser's own
8245
8286
  // encoding for it. An empty array means the same thing and is written the
@@ -8478,9 +8519,7 @@ function compileConstraintTrack(
8478
8519
  const key = keys[i];
8479
8520
  const next = keys[i + 1];
8480
8521
  const time = keyTime(key.t);
8481
- if (i > 0 && time <= (out[i - 1].time as number)) {
8482
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
8483
- }
8522
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, keys, i, time === out[i - 1].time);
8484
8523
  checkKeyTime(where, time, key.t, duration);
8485
8524
 
8486
8525
  const entry: ModelKey = { time };
@@ -8906,9 +8945,7 @@ function compileSequenceTrack(
8906
8945
  for (let i = 0; i < track.keys.length; i++) {
8907
8946
  const key = track.keys[i];
8908
8947
  const time = keyTime(key.t);
8909
- if (i > 0 && time <= (out[i - 1].time as number)) {
8910
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
8911
- }
8948
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, track.keys, i, time === out[i - 1].time);
8912
8949
  checkKeyTime(where, time, key.t, duration);
8913
8950
  if (key.index !== undefined && key.index >= series.count) {
8914
8951
  throw new CompileError(
@@ -8946,9 +8983,7 @@ function compileDeformTrack(
8946
8983
  for (let i = 0; i < keys.length; i++) {
8947
8984
  const key = keys[i];
8948
8985
  const time = keyTime(key.t);
8949
- if (i > 0 && time <= (out[i - 1].time as number)) {
8950
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
8951
- }
8986
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, keys, i, time === out[i - 1].time);
8952
8987
  checkKeyTime(where, time, key.t, duration);
8953
8988
  // -- a key that states a MODEL rather than a run (issue #294) ------------
8954
8989
  //
@@ -9660,9 +9695,7 @@ function compileTrack(
9660
9695
  const key = track.keys[i];
9661
9696
  const next = track.keys[i + 1];
9662
9697
  const time = keyTime(key.t + shift);
9663
- if (i > 0 && time <= (out[i - 1].time as number)) {
9664
- throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
9665
- }
9698
+ if (i > 0 && time <= (out[i - 1].time as number)) refuseKeyOrder(where, track.keys, i, time === out[i - 1].time);
9666
9699
  checkKeyTime(where, time, key.t, duration);
9667
9700
 
9668
9701
  if (shape === 'attachment') {