@rig-c/core 2.79.0 → 2.80.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
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <a href="https://www.npmjs.com/package/rig-c"><img src="https://img.shields.io/npm/v/rig-c.svg?style=flat-square&color=FF6B4A" alt="npm version" /></a>
6
+ <a href="https://www.npmjs.com/package/@rig-c/core"><img src="https://img.shields.io/npm/v/@rig-c/core.svg?style=flat-square&color=FF6B4A" alt="npm version" /></a>
7
7
  <a href="https://www.npmjs.com/package/rig-c"><img src="https://img.shields.io/npm/dm/rig-c.svg?style=flat-square&color=A855F7" alt="npm downloads" /></a>
8
8
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-38BDF8.svg?style=flat-square" alt="license" /></a>
9
9
  </p>
@@ -14,9 +14,9 @@ format, with rigc's own compiled model beside it —
14
14
  gated by a list of named assertions before a byte is written — rigc's own validator in
15
15
  the published package, held to a `spine-core` round trip's verdicts in this
16
16
  repository's CI. Built so AI agents can author rigs and check their own work; it ships
17
- as an agent skill. On npm it is `rig-c` — the same package as `spine-rigc`, the name it
18
- first shipped under, and as `@rig-c/core`, its name under the toolchain's scope, both
19
- published beside it with the same files at every version.
17
+ as an agent skill. Its canonical npm package is `@rig-c/core`, under the toolchain's
18
+ scope. `rig-c` is the convenience install name; `spine-rigc` is the legacy install
19
+ name. Both remain published alongside it with the same product files.
20
20
 
21
21
  ## What you get
22
22
 
@@ -177,22 +177,23 @@ name with exit 2 before upload. Use a published exact `X.Y.Z` (no `v` prefix,
177
177
  alias, range or prerelease), or retry a failed network fetch. This is repository
178
178
  verification tooling, not an installed `rigc` command or a change to CI's matrix.
179
179
 
180
- **The npm package is `rig-c`; the command it installs is `rigc`.** npm
180
+ **The canonical npm package is `@rig-c/core`; the command it installs is `rigc`.**
181
+ `rig-c` remains a convenience install name. npm
181
182
  refuses the name `rigc` as too similar to packages that already exist, so the
182
- project and the executable keep their name and the registry entry carries one
183
- hyphen; the repository is [`rig-c/core`](https://github.com/rig-c/core). **`spine-rigc` is the same package**: it is the name
183
+ project and the executable keep their name; the convenience name `rig-c` carries one
184
+ hyphen; the repository is [`rig-c/core`](https://github.com/rig-c/core). **`spine-rigc` is the legacy install name for the same package**: it is the name
184
185
  every version up to 2.20.4 shipped under, and it stays published as an alias —
185
186
  every version from 2.20.4 on is on both names, with the same files — so a
186
187
  project that depends on `spine-rigc` keeps receiving every release. A new
187
- project installs `rig-c`; `@rig-c/core` is the same package under the scope.
188
+ project installs `@rig-c/core`, the canonical package under the toolchain's scope.
188
189
 
189
190
  ```bash
190
- bunx rig-c --help # run it without installing
191
- bun add -g rig-c # or install the command
192
- bun add -d rig-c # or pin it in a project
191
+ bunx @rig-c/core --help # run it without installing
192
+ bun add -g @rig-c/core # or install the command
193
+ bun add -d @rig-c/core # or pin it in a project
193
194
  ```
194
195
 
195
- `npx rig-c` works too, as long as Bun is on `PATH` — the executable is a
196
+ `npx @rig-c/core` works too, as long as Bun is on `PATH` — the executable is a
196
197
  Bun script, and npm only writes the shim that calls it.
197
198
 
198
199
  Installed, the command is `rigc`. The examples below spell it `bun cli.ts`
@@ -242,7 +243,7 @@ plugin marketplace:
242
243
  /plugin install rigc@rigc
243
244
  ```
244
245
 
245
- With the package already installed, `claude --plugin-dir node_modules/rig-c`
246
+ With the package already installed, `claude --plugin-dir node_modules/@rig-c/core`
246
247
  loads the same skills without a marketplace. The plugin carries no version of its
247
248
  own — `/plugin update` follows `main` commit by commit, and the only version on
248
249
  disk stays the one in `package.json`.
@@ -254,8 +255,8 @@ Codex, Gemini CLI and Antigravity read skills from one directory in the workspac
254
255
  `node_modules`. With the package installed, one command puts every skill there:
255
256
 
256
257
  ```shell
257
- bun add -d rig-c
258
- bun rigc skills install # relative links: .agents/skills/rigc -> ../../node_modules/rig-c/skills/rigc
258
+ bun add -d @rig-c/core
259
+ bun rigc skills install # relative links: .agents/skills/rigc -> ../../node_modules/@rig-c/core/skills/rigc
259
260
  bun rigc skills install --copy # the folders themselves, for a host that does not follow a link
260
261
  ```
261
262
 
@@ -290,11 +291,11 @@ reference export, so nothing you read in a quickstart is an answer to anything
290
291
  **1. Install the command.**
291
292
 
292
293
  ```bash
293
- bun add -g rig-c # installs `rigc`
294
+ bun add -g @rig-c/core # installs `rigc`
294
295
  ```
295
296
 
296
297
  Or skip the install and prefix every command below with `bunx `, e.g.
297
- `bunx rig-c build …`.
298
+ `bunx @rig-c/core build …`.
298
299
 
299
300
  **2. Make a directory and three plates.** rigc measures PNGs rather than trusting
300
301
  a number you typed (R5), so the art has to exist. These three are solid colours a
package/docs/AUTHORING.md CHANGED
@@ -11267,6 +11267,27 @@ Everything above is what the editor **does**. This is what it **returns** — wh
11267
11267
  matters to you for one reason: a construct nobody has carried through the editor
11268
11268
  is a construct that might vanish there, and an agent cannot see that it did.
11269
11269
 
11270
+ `tools/editor_roundtrip.ts` also names a clipping semantic change after export
11271
+ (issue #1630), including under `--exported`. If the export adds `convex: true`
11272
+ where the same skin/slot/attachment in the build did not state true, and the
11273
+ build's authored setup polygon has a reflex vertex, it prints:
11274
+
11275
+ ```text
11276
+ FAIL EDITOR_ADDED_CONVEX_ON_CONCAVE_CLIP skin "S" slot "C" attachment "A" end "E": export convex=true, build convex is not true; setup reflex vertices=N; the editor's export clips with the convex hull of the authored concave polygon
11277
+ ```
11278
+
11279
+ The setup polygon is read through the runtime with that skin applied, setup
11280
+ bones and constraints posed, and the slot's deform empty, as for the renderer's
11281
+ authored rest geometry. Weighted vertex runs are therefore read as posed points,
11282
+ not mistaken for coordinate pairs; animation-time deformation is not substituted
11283
+ for the authored setup shape. Reflex turns are counted in either winding and
11284
+ straight boundary vertices do not make a convex polygon concave. A convex setup
11285
+ polygon or a build already stating `convex: true` adds no line. The named failure
11286
+ makes the trip exit nonzero **after** the existing per-skin render/check figures
11287
+ are printed; it does not replace those measurements. It changes neither compiler
11288
+ emission nor coordinates, and does not imply that removing the export's flag
11289
+ undoes the editor's other changes.
11290
+
11270
11291
  🔬 The source is one run: eight discriminator rigs, each built to isolate a group
11271
11292
  of fields, compiled by rigc **0.21.0** (emitting 4.3.13), imported into a licensed
11272
11293
  Spine **4.3.26** through the documented CLI and exported back, with the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/core",
3
- "version": "2.79.0",
3
+ "version": "2.80.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": {
@@ -2,7 +2,7 @@
2
2
  name: rigc
3
3
  description: Author, build and validate Spine 4.3 skeleton data (skeleton.json plus its .atlas) from loose part PNGs with rigc, the rig compiler that verifies its own output with named assertions before writing it. Use for any request to make a Spine rig or Spine animation from PNG parts, or where the source is a Live2D, Unity or video model whose pictures you can render, to run or read rigc build, validate, render, preview, check or vote, or to write or fix a *.rig.json or *.motion.json spec; it says which shipped guide to open for the need at hand. Not for Live2D conversion, cutting an illustration into parts, or real-time face tracking.
4
4
  license: MIT
5
- compatibility: Requires Bun 1.4 or later. The tool is the npm package rig-c (bunx rig-c, or bun add -d rig-c); the command it installs is rigc.
5
+ compatibility: Requires Bun 1.4 or later. The canonical npm package is @rig-c/core (bunx @rig-c/core, or bun add -d @rig-c/core); the command it installs is rigc. rig-c is the convenience install name; spine-rigc is the legacy install name.
6
6
  ---
7
7
 
8
8
  # rigc — a rig compiler for agents
@@ -46,8 +46,8 @@ index within that same limit; only legacy uncoded library errors point at `§5.1
46
46
  ## Install
47
47
 
48
48
  ```shell
49
- bunx rig-c --help # run it without installing
50
- bun add -d rig-c # or pin it in the project; the command is `rigc`
49
+ bunx @rig-c/core --help # run it without installing
50
+ bun add -d @rig-c/core # or pin it in the project; the command is `rigc`
51
51
  bun rigc skills install # then link these skills into .agents/skills
52
52
  ```
53
53
 
@@ -145,8 +145,8 @@ spec files field by field, the emission rules, the loop, and the failure map. Th
145
145
  | a **skeleton.json somebody else authored** — read it, repair it, extend it | [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) | `rigc-ingest` |
146
146
  | you are the **person operating** the agent rather than the agent | [PROMPTING.md](https://github.com/rig-c/core/blob/main/docs/PROMPTING.md) | — |
147
147
 
148
- Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
148
+ Every guide linked here is in the installed package at `node_modules/@rig-c/core/docs/`,
149
149
  which is the copy that matches the rigc you run; the links go to the repository's
150
150
  `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
151
151
  Formats, the CLI reference and the licence chain:
152
- [README.md](https://github.com/rig-c/core/blob/main/README.md), installed at `node_modules/rig-c/README.md`.
152
+ [README.md](https://github.com/rig-c/core/blob/main/README.md), installed at `node_modules/@rig-c/core/README.md`.
package/src/render.ts CHANGED
@@ -581,6 +581,28 @@ function setupPosed(data: SkeletonData, skin: string | undefined): Skeleton {
581
581
  return skeleton;
582
582
  }
583
583
 
584
+ /** A named clip's authored setup polygon under its own skin, with no deform. */
585
+ export function setupClipPolygon(
586
+ data: SkeletonData,
587
+ skin: string,
588
+ slotName: string,
589
+ attachmentName: string,
590
+ ): number[] {
591
+ const skeleton = setupPosed(data, skin);
592
+ const index = data.findSlot(slotName)?.index ?? -1;
593
+ const slot = skeleton.slots[index];
594
+ const attachment = data.findSkin(skin)?.getAttachment(index, attachmentName);
595
+ if (slot === undefined || !(attachment instanceof ClippingAttachment)) {
596
+ throw new Error(
597
+ `setup clip skin ${JSON.stringify(skin)} slot ${JSON.stringify(slotName)} attachment ${JSON.stringify(attachmentName)} did not resolve`,
598
+ );
599
+ }
600
+ slot.appliedPose.deform.length = 0;
601
+ const polygon = new Array<number>(attachment.worldVerticesLength).fill(0);
602
+ attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, polygon, 0, 2);
603
+ return polygon;
604
+ }
605
+
584
606
  /**
585
607
  * Both posers for the skeleton at `skeletonPath` drawn through the atlas at
586
608
  * `atlasPath`, with spine-core's parse of it already in hand (`data`) — the
@@ -917,6 +917,75 @@ export function skinRoster(
917
917
  };
918
918
  }
919
919
 
920
+ /** An attachment's declaration key, kept separate from its skin and slot. */
921
+ interface ClipFlagSkin {
922
+ name: string;
923
+ attachments?: Record<string, Record<string, { type?: string; convex?: boolean; end?: string }>>;
924
+ }
925
+
926
+ /** Reflex turns of the world's own doubles, winding independent; straight turns are not reflex. */
927
+ function reflexVertices(polygon: readonly number[]): number {
928
+ const n = polygon.length / 2;
929
+ let area2 = 0;
930
+ for (let i = 0; i < n; i++) {
931
+ const j = (i + 1) % n;
932
+ area2 += polygon[2 * i] * polygon[2 * j + 1] - polygon[2 * j] * polygon[2 * i + 1];
933
+ }
934
+ const winding = Math.sign(area2);
935
+ if (winding === 0) return 0; // Degenerate geometry is the validator's refusal.
936
+ let count = 0;
937
+ for (let i = 0; i < n; i++) {
938
+ const j = (i + 1) % n;
939
+ const k = (i + 2) % n;
940
+ const turn =
941
+ (polygon[2 * j] - polygon[2 * i]) * (polygon[2 * k + 1] - polygon[2 * j + 1]) -
942
+ (polygon[2 * j + 1] - polygon[2 * i + 1]) * (polygon[2 * k] - polygon[2 * j]);
943
+ if (turn !== 0 && Math.sign(turn) !== winding) count++;
944
+ }
945
+ return count;
946
+ }
947
+
948
+ /**
949
+ * An editor-added convex flag changes a concave clip's meaning (issue #1630).
950
+ * Compare the same skin/slot/attachment, then read the BUILD's posed authored
951
+ * setup polygon. Weighted runs are not points; an animation deform is not the
952
+ * authored setup shape. Empty deform follows the renderer's `restOf` convention.
953
+ * Imports are lazy so the existing no-editor/trial/read refusals need no runtime.
954
+ */
955
+ async function addedConvexClipFailures(build: string, exported: string, buildDir: string): Promise<string[]> {
956
+ const before = (JSON.parse(readFileSync(build, 'utf8')) as { skins?: ClipFlagSkin[] }).skins;
957
+ const after = (JSON.parse(readFileSync(exported, 'utf8')) as { skins?: ClipFlagSkin[] }).skins;
958
+ const candidates: Array<{ skin: string; slot: string; attachment: string; end: string | null }> = [];
959
+ for (const skin of Array.isArray(after) ? after : []) {
960
+ const originalSkin = (Array.isArray(before) ? before : []).find((s) => s.name === skin.name);
961
+ for (const [slot, attachments] of Object.entries(skin.attachments ?? {})) {
962
+ for (const [attachment, clip] of Object.entries(attachments)) {
963
+ const original = originalSkin?.attachments?.[slot]?.[attachment];
964
+ if (clip.type === 'clipping' && clip.convex === true && original?.type === 'clipping' && original.convex !== true) {
965
+ candidates.push({ skin: skin.name, slot, attachment, end: original.end ?? null });
966
+ }
967
+ }
968
+ }
969
+ }
970
+ if (candidates.length === 0) return [];
971
+ const { posableFromText, setupClipPolygon } = await import('../src/render.ts');
972
+ const atlas = readdirSync(buildDir).find((name) => name.endsWith('.atlas'))!;
973
+ const { data } = posableFromText(readFileSync(build, 'utf8'), readFileSync(join(buildDir, atlas), 'utf8'), buildDir);
974
+ const lines: string[] = [];
975
+ for (const candidate of candidates) {
976
+ const polygon = setupClipPolygon(data, candidate.skin, candidate.slot, candidate.attachment);
977
+ const reflex = reflexVertices(polygon);
978
+ if (reflex > 0) {
979
+ lines.push(
980
+ ` FAIL EDITOR_ADDED_CONVEX_ON_CONCAVE_CLIP skin ${JSON.stringify(candidate.skin)} slot ${JSON.stringify(candidate.slot)} ` +
981
+ `attachment ${JSON.stringify(candidate.attachment)} end ${JSON.stringify(candidate.end)}: export convex=true, build convex is not true; ` +
982
+ `setup reflex vertices=${reflex}; the editor's export clips with the convex hull of the authored concave polygon`,
983
+ );
984
+ }
985
+ }
986
+ return lines;
987
+ }
988
+
920
989
  /**
921
990
  * One measure of a `diff --json` report as `matched/total (ratio)`, or `null`
922
991
  * when the report or the measure is not there — read off the report for the
@@ -1185,7 +1254,7 @@ function imagesProbeFault(skeleton: Record<string, unknown>): string | null {
1185
1254
  return null;
1186
1255
  }
1187
1256
 
1188
- function main(): void {
1257
+ async function main(): Promise<void> {
1189
1258
  const opts = parseArgs(process.argv.slice(2));
1190
1259
  const rigc = rigcCommand();
1191
1260
 
@@ -1489,6 +1558,21 @@ function main(): void {
1489
1558
  // console layout for them would break the first time that layout is tidied.
1490
1559
  for (const line of diffSummaryLines(diffJson)) emit(` ${line}`);
1491
1560
 
1561
+ // Report the changed meaning without skipping any later per-skin figures.
1562
+ let clipFailures: string[];
1563
+ try {
1564
+ // An unreadable export has no semantic comparison. Leave its established
1565
+ // gate/read failure intact (including the late-read planted controls).
1566
+ clipFailures = gate.status === 0 ? await addedConvexClipFailures(source, join(cand, 'skeleton.json'), opts.build) : [];
1567
+ } catch (error) {
1568
+ return fail(`could not measure clipping setup geometry: ${error instanceof Error ? error.message : String(error)}`);
1569
+ }
1570
+ if (clipFailures.length > 0) {
1571
+ emit('');
1572
+ emit('## 4.1 clipping semantics');
1573
+ for (const line of clipFailures) emit(line);
1574
+ }
1575
+
1492
1576
  // 🔒 The skins come off BOTH files, compared before anything is rendered
1493
1577
  // (issue #801). A skin the export dropped is named here as LOST BY THE EXPORT
1494
1578
  // and fails the run; enumerating the export's skins alone would quietly stop
@@ -1627,7 +1711,7 @@ function main(): void {
1627
1711
  // is the half a single un-skinned check could not say.
1628
1712
  // A version line that disagrees with the export is red too (issue #1199): the
1629
1713
  // report would otherwise name an editor the trip did not run on, green.
1630
- process.exit(gate.status === 0 && checksClean && rosterClean && !versionDisagrees ? 0 : 1);
1714
+ process.exit(gate.status === 0 && checksClean && rosterClean && !versionDisagrees && clipFailures.length === 0 ? 0 : 1);
1631
1715
  }
1632
1716
 
1633
1717
  // ⭐ Guarded so `shapeOf`, `shapeDiff`, `skinsDeclaredBy` and `skinBlocks` can be
@@ -1638,4 +1722,4 @@ function main(): void {
1638
1722
  // reach either would be paying for a round trip to test arithmetic. Run as a
1639
1723
  // program this is unchanged: `bun tools/editor_roundtrip.ts …` makes this module
1640
1724
  // the entry, so `import.meta.main` is true.
1641
- if (import.meta.main) main();
1725
+ if (import.meta.main) await main();