@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 +18 -17
- package/docs/AUTHORING.md +21 -0
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +5 -5
- package/src/render.ts +22 -0
- package/tools/editor_roundtrip.ts +87 -3
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
|
|
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.
|
|
18
|
-
|
|
19
|
-
published
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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": {
|
package/skills/rigc/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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();
|