@rig-c/core 2.61.0 → 2.61.1
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 +1 -1
- package/docs/AUTHORING.md +1 -1
- package/package.json +1 -1
- package/src/cli/core_commands.ts +35 -0
- package/src/stop.ts +42 -10
package/README.md
CHANGED
|
@@ -617,7 +617,7 @@ commands take it and what its default is.
|
|
|
617
617
|
| `bench <rung> --candidate <dir>` | one rung of the benchmark ladder |
|
|
618
618
|
| `guide <RULE>` / `guide §<n>[.<m>[.<k>]] [--all]` | the guide's row for the rule a failure line names — every `FAIL` line, `vote --record` refusal, `ingest` finding and coded compile error ends with ` (see rigc guide <RULE>)`; only legacy uncoded library errors point at `§5.1` — or a section's heading, own prose and subsection index, read from the installed `docs/AUTHORING.md` when it runs. `--all` includes the subsection bodies; a leaf prints its full text either way. Sizes describe full ledger spans. `--all` with a rule is refused, exit 2; an unknown name is refused with the nearest real ones, exit 1 |
|
|
619
619
|
| `skills install [--dir …] [--copy]` | links every agent skill the package ships into `.agents/skills` (or `--dir`) as relative symlinks, or copies them with `--copy` — where Codex, Gemini CLI and Antigravity look. A second run has nothing to do; an entry already there that is not this command's is refused by name and nothing is written |
|
|
620
|
-
| `review <dir> […]` | the human review of a build, handed to the `rig-review` bin of `@rig-c/review` installed beside the package, with every argument untouched and its exit code returned: `rigc review open <dir>` prints a loopback URL to give a person, `check <dir>` validates the folder it would serve, `feedback <dir>` prints what they left, and `rigc review --help` is review's own usage. rigc does not depend on it: without it the command prints `rigc review: @rig-c/review is not installed — npm i -D @rig-c/review` and exits 2 |
|
|
620
|
+
| `review <dir> […]` | the human review of a build, handed to the `rig-review` bin of `@rig-c/review` installed beside the package, with every argument untouched and its exit code returned: `rigc review open <dir>` prints a loopback URL to give a person, `check <dir>` validates the folder it would serve, `feedback <dir>` prints what they left, and `rigc review --help` is review's own usage. rigc does not depend on it: without it the command prints `rigc review: @rig-c/review is not installed — npm i -D @rig-c/review` and exits 2. An empty argument (`""`) is handed over only where the running Bun keeps one when it starts a process — Bun 1.2.0 drops it, measured by a probe rather than by version — and is otherwise refused by name, `rigc review: argument N is empty, and Bun <version>, the Bun running this, drops an empty argument when it starts a process — …`, exit 2 |
|
|
621
621
|
|
|
622
622
|
`diff`, `bonedist`, `check` and `bench` measure against something you were given; the
|
|
623
623
|
first three work on any reference you have, and `bench` is a repository workflow that needs a clone
|
package/docs/AUTHORING.md
CHANGED
|
@@ -7382,7 +7382,7 @@ retain the minimal inputs and report that code rather than inventing author data
|
|
|
7382
7382
|
| <a id="CE_STOP_LINK_UNDRIVEN"></a>`CE_STOP_LINK_UNDRIVEN` | “<stop.who>: links[<k>] "<link>" is written by no constraint of this rig spec — a stop holds a link something swings (a physics, ik, transform or path constraint in "constraints"), and its copy and clamp run right after that. A physics constraint in the motion spec's table is emitted after every rig constraint, past the clamp; declare it here” | Declare the swinging physics, ik, transform or path constraint in the rig spec's constraints before its stop clamp. |
|
|
7383
7383
|
| <a id="CE_STOP_LINK_PARENT_CHAIN"></a>`CE_STOP_LINK_PARENT_CHAIN` | “<stop.who>: links[<k>] "<links[k]>" does not hang below "<names.drawn[k - 1]>", the drawn link of links[<k - 1>] — a chain's next link hangs from the drawn link above it, so the clamp that holds that one carries this one; parent it there” | Parent each lower link under the preceding generated drawn link. |
|
|
7384
7384
|
| <a id="CE_STOP_AGAINST_MOVES_WITH_STOP"></a>`CE_STOP_AGAINST_MOVES_WITH_STOP` | “<stop.who>: "against" "<stop.against>" hangs below "<under>", which the stop moves — the point would swing with the chain it holds off; name the leg” | Name an against bone outside the subtree moved by the stop. |
|
|
7385
|
-
| <a id="CE_STOP_LOCAL_BOUND_RANGE"></a>`CE_STOP_LOCAL_BOUND_RANGE` | “<stop.who>: "<field><end>" <range[end]> puts <what>'s bound at <value>°, past ±<BOUND>° — the clamp reads a local rotation in (-180, 180], so a bound past it is never reached and the range it states is not the one that holds”<br>On the first link it goes on: “. "<stop.links[0]>" hangs at <at.hang>° from its root at [<at.root>], and "<stop.names.point>" ("at" [<stop.at>] on "<stop.against>") sits at [<at.point>] — Spine world, y up, at the setup pose — so the aim from that root to the point lies <off>° off the hang (rest <rest>°): \|rest\| <off>° + "<end>" <e>° is <sum>°”, then one of “, above <BOUND>°. Move "<stop.names.point>" to within <BOUND − e>° (<BOUND>° − "<end>") of the direction "<stop.links[0]>" hangs in from its root, <at.hang>°, or state "<end>" at most <BOUND − off>°”, “, above <BOUND>°, and no point lowers it: \|rest\| is above 0° for every point a stop accepts. State "<end>" below <BOUND>°”, “, below -<BOUND>°. State "<end>" at least <−BOUND − off>°”<br>Here the values named in the message include: “inward”, “outward”. | On the first link the bound is \|rest\| + the end, where rest is the angle from the link's hang to the aim at `<chain>_touch`, both read from the link's root: move `<chain>_touch` (its `at` on `against`) to within 180° − `outward` of the direction the link hangs in from its root — on a link hanging straight down with `outward` at most 90°, any point below the root — or lower `outward` to at most 180° − \|rest\|. A leg that rises from behind the cloth, as on a prone figure, puts a point at its far end above the root; put it at the knee. On a lower link, bring `lower`'s ends within ±180° of the link's own rest bend. |
|
|
7385
|
+
| <a id="CE_STOP_LOCAL_BOUND_RANGE"></a>`CE_STOP_LOCAL_BOUND_RANGE` | “<stop.who>: "<field><end>" <range[end]> puts <what>'s bound at <value>°, past ±<BOUND>° — the clamp reads a local rotation in (-180, 180], so a bound past it is never reached and the range it states is not the one that holds”<br>On the first link it goes on: “. "<stop.links[0]>" hangs at <at.hang>° from its root at [<at.root>], and "<stop.names.point>" ("at" [<stop.at>] on "<stop.against>") sits at [<at.point>] — Spine world, y up, at the setup pose — so the aim from that root to the point lies <off>° off the hang (rest <rest>°): \|rest\| <off>° + "<end>" <e>° is <sum>°”, then one of “, above <BOUND>°. Move "<stop.names.point>" to within <BOUND − e>° (<BOUND>° − "<end>") of the direction "<stop.links[0]>" hangs in from its root, <at.hang>°, or state "<end>" at most <BOUND − off>°”, “, above <BOUND>°, and no point lowers it: \|rest\| is above 0° for every point a stop accepts. State "<end>" below <BOUND>°”, “, below -<BOUND>°. State "<end>" at least <−BOUND − off>°”<br>Here the values named in the message include: “inward”, “outward”. | On the first link the bound is \|rest\| + the end, where rest is the angle from the link's hang to the aim at `<chain>_touch`, both read from the link's root: move `<chain>_touch` (its `at` on `against`) to within 180° − `outward` of the direction the link hangs in from its root — on a link hanging straight down with `outward` at most 90°, any point below the root — or lower `outward` to at most 180° − \|rest\|. A leg that rises from behind the cloth, as on a prone figure, puts a point at its far end above the root; put it at the knee. On a lower link, bring `lower`'s ends within ±180° of the link's own rest bend. The values you stated (`inward`, `outward`, `at`) are echoed as written; every figure the expansion derived prints to three decimals, a figure past ±180° rounded away from zero so it never reads as on the bound, and a remedy (`at most`, `at least`, `within`) rounded toward zero, so stating the printed value is accepted. |
|
|
7386
7386
|
| <a id="CE_STOP_DEPENDENCY_ORDER"></a>`CE_STOP_DEPENDENCY_ORDER` | “<who>: "<tip>" hangs below "<name>", which a later stop generates — declare that stop first, so its angles are known when this one is read”<br>Also: “<value>: stop "<value>"” | Declare the stop generating the parent before the stop depending on it. |
|
|
7387
7387
|
| <a id="CE_STOP_ANCESTOR_MANIFEST_POSITION"></a>`CE_STOP_ANCESTOR_MANIFEST_POSITION` | “<who>: "<name>" takes its place from the manifest ("from"), and a stop's angles are read off the setup pose this rig spec states — state its x, y and rotation”<br>Also: “<value>: stop "<value>"” | State x, y and rotation for the named setup-pose ancestor instead of from. |
|
|
7388
7388
|
| <a id="CE_STOP_FRAME_PARENT_TRANSFORM"></a>`CE_STOP_FRAME_PARENT_TRANSFORM` | “<who>: "<parent>", where the frame hangs, is scaled unevenly, sheared or reflected at the setup pose — the frame's angles add to its parent's only through a rotation and an even scale, and inward is a turn's sense”<br>Also: “<value>: stop "<value>"” | Use a frame parent with rotation and even positive scale, without shear or reflection. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.61.
|
|
3
|
+
"version": "2.61.1",
|
|
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/cli/core_commands.ts
CHANGED
|
@@ -2239,6 +2239,39 @@ function resolveDelegate(name: string): string | null {
|
|
|
2239
2239
|
return null;
|
|
2240
2240
|
}
|
|
2241
2241
|
|
|
2242
|
+
/**
|
|
2243
|
+
* Whether the Bun running this hands an empty argument to a process it starts
|
|
2244
|
+
* (issue #1465). Bun 1.2.0 — the declared minimum — does not: `["a", "", "b"]`
|
|
2245
|
+
* reaches the child as `["a", "b"]`, through `spawnSync` and `Bun.spawnSync`
|
|
2246
|
+
* alike and whatever the child is; 1.4.2 hands it over. That is measured here
|
|
2247
|
+
* rather than read off a version number, by starting this Bun twice with
|
|
2248
|
+
* `-e` and comparing how many arguments arrived: `[pad, "x"]` and
|
|
2249
|
+
* `[pad, "", "x"]` differ by one exactly where the empty one survives. The pad
|
|
2250
|
+
* is there because 1.2.0's `-e` also swallows the first argument after the
|
|
2251
|
+
* script; it is the same on both runs, so the difference is the empty one's.
|
|
2252
|
+
*/
|
|
2253
|
+
export function spawnKeepsEmptyArguments(): boolean {
|
|
2254
|
+
const count = (args: string[]): string =>
|
|
2255
|
+
spawnSync(process.execPath, ['-e', 'process.stdout.write(String(process.argv.length))', ...args], { encoding: 'utf8' }).stdout;
|
|
2256
|
+
return Number(count(['pad', '', 'x'])) === Number(count(['pad', 'x'])) + 1;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* The refusal for a delegated command line this Bun cannot hand over whole, or
|
|
2261
|
+
* `null` (issue #1465): an empty argument, under a Bun that drops one, would
|
|
2262
|
+
* reach the package with the others and nothing would say so. `keeps` is asked
|
|
2263
|
+
* only when there is an empty argument, so a command line without one never
|
|
2264
|
+
* starts the probe.
|
|
2265
|
+
*/
|
|
2266
|
+
export function emptyArgumentRefusal(pkg: string, raw: readonly string[], keeps: () => boolean, bun: string): string | null {
|
|
2267
|
+
const at = raw.indexOf('');
|
|
2268
|
+
if (at < 0 || keeps()) return null;
|
|
2269
|
+
return (
|
|
2270
|
+
`argument ${at + 1} is empty, and Bun ${bun}, the Bun running this, drops an empty argument when it starts a process — ` +
|
|
2271
|
+
`${pkg} would receive the others without it, and nothing would say so. Give it a value, or run rigc under a Bun that keeps it`
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2242
2275
|
/**
|
|
2243
2276
|
* A delegating command's body (`CommandDoc.delegate`, issue #1457): resolve the
|
|
2244
2277
|
* package's bin and run it under the Bun running this — the bin is a Bun script
|
|
@@ -2261,6 +2294,8 @@ function cmdDelegate(command: string, raw: readonly string[]): void {
|
|
|
2261
2294
|
2,
|
|
2262
2295
|
);
|
|
2263
2296
|
}
|
|
2297
|
+
const dropped = emptyArgumentRefusal(delegate.package, raw, spawnKeepsEmptyArguments, process.versions.bun ?? 'unknown');
|
|
2298
|
+
if (dropped !== null) throw new DelegateError(dropped, 2);
|
|
2264
2299
|
const ran = spawnSync(process.execPath, [bin, ...raw], { stdio: 'inherit' });
|
|
2265
2300
|
// ⚠️ The exit and the signal are read before `error`: Bun 1.2.0 — the declared minimum — sets `error` on every
|
|
2266
2301
|
// child that exits non-zero, so reading it first refused review's own exit 7 as "could not run". `error` is a
|
package/src/stop.ts
CHANGED
|
@@ -126,6 +126,37 @@ interface FirstLink {
|
|
|
126
126
|
point: readonly [number, number];
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Every number a stop's refusal prints, in one place (issue #1463). How it is
|
|
131
|
+
* printed depends on what the number is to the author:
|
|
132
|
+
*
|
|
133
|
+
* - `stated` — a value the author typed (`inward`, `outward`, `at`), echoed
|
|
134
|
+
* exactly as JavaScript reads it back, so it can be found in the rig spec.
|
|
135
|
+
* It is never rounded: two stated ends 2.60251 and 2.60249 would both print
|
|
136
|
+
* 2.603, and the refusal would read "2.603 is greater than 2.603";
|
|
137
|
+
* - `reading` — a figure the expansion derived (a hang, a world position, a
|
|
138
|
+
* rest angle), at the three decimals the compiler's other degree refusals
|
|
139
|
+
* print, rounded to nearest;
|
|
140
|
+
* - `past` — a figure refused for lying past ±180°, rounded **away from
|
|
141
|
+
* zero**, so it never prints on the bound: 180.0000001 prints 180.001,
|
|
142
|
+
* not 180.000, which would read as inside the range;
|
|
143
|
+
* - `remedy` — a figure the refusal offers the author to state ("at most",
|
|
144
|
+
* "at least", "within"), rounded **toward zero**, so stating the printed
|
|
145
|
+
* value is accepted rather than refused again by the digits dropped.
|
|
146
|
+
*
|
|
147
|
+
* The rule is that rounding never moves a figure across the line the
|
|
148
|
+
* refusal is about: a `past` figure stays past it, a `remedy` stays inside.
|
|
149
|
+
*/
|
|
150
|
+
function stopFigure(v: number, how: 'stated' | 'reading' | 'past' | 'remedy'): string {
|
|
151
|
+
if (how === 'stated') return String(v);
|
|
152
|
+
const near = Number(v.toFixed(3));
|
|
153
|
+
let r = near;
|
|
154
|
+
if (how === 'past' && Math.abs(near) < Math.abs(v)) r = near + Math.sign(v) * 0.001;
|
|
155
|
+
if (how === 'remedy' && Math.abs(near) > Math.abs(v)) r = near - Math.sign(v) * 0.001;
|
|
156
|
+
const text = r.toFixed(3);
|
|
157
|
+
return text === '-0.000' ? '0.000' : text;
|
|
158
|
+
}
|
|
159
|
+
|
|
129
160
|
/** An angle in (-540, 540) brought into (-180, 180] by at most one whole turn. */
|
|
130
161
|
function halfTurn(deg: number): number {
|
|
131
162
|
if (deg > 180) return deg - 360;
|
|
@@ -216,7 +247,7 @@ export function expandStops(spec: RigSpec, where: string, plant: StopPlant = {})
|
|
|
216
247
|
}
|
|
217
248
|
if (out.inward > out.outward) {
|
|
218
249
|
throw new CompileError(
|
|
219
|
-
`${who}: "${field}inward" ${out.inward} is greater than "${field}outward" ${out.outward} — the range is [inward, outward] in degrees counted positive away from "${against}", and this one holds no angle`, 'CE_STOP_RANGE_REVERSED',
|
|
250
|
+
`${who}: "${field}inward" ${stopFigure(out.inward, 'stated')} is greater than "${field}outward" ${stopFigure(out.outward, 'stated')} — the range is [inward, outward] in degrees counted positive away from "${against}", and this one holds no angle`, 'CE_STOP_RANGE_REVERSED',
|
|
220
251
|
);
|
|
221
252
|
}
|
|
222
253
|
return out;
|
|
@@ -350,15 +381,16 @@ export function expandStops(spec: RigSpec, where: string, plant: StopPlant = {})
|
|
|
350
381
|
const e = stop[end];
|
|
351
382
|
const off = Math.abs(rest);
|
|
352
383
|
const sum = off + e;
|
|
384
|
+
const position = (p: readonly number[], how: 'stated' | 'reading'): string => p.map((v) => stopFigure(v, how)).join(', ');
|
|
353
385
|
const geometry =
|
|
354
|
-
`. "${stop.links[0]}" hangs at ${at.hang}° from its root at [${at.root
|
|
355
|
-
`sits at [${at.point
|
|
356
|
-
`|rest| ${off}° + "${end}" ${e}° is ${sum}°`;
|
|
357
|
-
if (sum < -BOUND) return `${geometry}, below -${BOUND}°. State "${end}" at least ${-BOUND - off}°`;
|
|
386
|
+
`. "${stop.links[0]}" hangs at ${stopFigure(at.hang, 'reading')}° from its root at [${position(at.root, 'reading')}], and "${stop.names.point}" ("at" [${position(stop.at, 'stated')}] on "${stop.against}") ` +
|
|
387
|
+
`sits at [${position(at.point, 'reading')}] — Spine world, y up, at the setup pose — so the aim from that root to the point lies ${stopFigure(off, 'reading')}° off the hang (rest ${stopFigure(rest, 'reading')}°): ` +
|
|
388
|
+
`|rest| ${stopFigure(off, 'reading')}° + "${end}" ${stopFigure(e, 'stated')}° is ${stopFigure(sum, 'past')}°`;
|
|
389
|
+
if (sum < -BOUND) return `${geometry}, below -${BOUND}°. State "${end}" at least ${stopFigure(-BOUND - off, 'remedy')}°`;
|
|
358
390
|
if (e >= BOUND) return `${geometry}, above ${BOUND}°, and no point lowers it: |rest| is above 0° for every point a stop accepts. State "${end}" below ${BOUND}°`;
|
|
359
391
|
return (
|
|
360
|
-
`${geometry}, above ${BOUND}°. Move "${stop.names.point}" to within ${BOUND - e}° (${BOUND}° − "${end}") of the direction "${stop.links[0]}" hangs in from its root, ${at.hang}°, ` +
|
|
361
|
-
`or state "${end}" at most ${BOUND - off}°`
|
|
392
|
+
`${geometry}, above ${BOUND}°. Move "${stop.names.point}" to within ${stopFigure(BOUND - e, 'remedy')}° (${BOUND}° − "${end}") of the direction "${stop.links[0]}" hangs in from its root, ${stopFigure(at.hang, 'reading')}°, ` +
|
|
393
|
+
`or state "${end}" at most ${stopFigure(BOUND - off, 'remedy')}°`
|
|
362
394
|
);
|
|
363
395
|
};
|
|
364
396
|
const bounds = (stop: ReadStop, rest: number, sign: number, range: { inward: number; outward: number }, field: string, what: string, first?: FirstLink): [number, number] => {
|
|
@@ -368,7 +400,7 @@ export function expandStops(spec: RigSpec, where: string, plant: StopPlant = {})
|
|
|
368
400
|
if (Math.abs(value) > BOUND) {
|
|
369
401
|
const why = first === undefined ? '' : aimed(stop, rest, end, first);
|
|
370
402
|
throw new CompileError(
|
|
371
|
-
`${stop.who}: "${field}${end}" ${range[end]} puts ${what}'s bound at ${value}°, past ±${BOUND}° — the clamp reads a local rotation in (-180, 180], so a bound past it is never reached and the range it states is not the one that holds${why}`, 'CE_STOP_LOCAL_BOUND_RANGE',
|
|
403
|
+
`${stop.who}: "${field}${end}" ${stopFigure(range[end], 'stated')} puts ${what}'s bound at ${stopFigure(value, 'past')}°, past ±${BOUND}° — the clamp reads a local rotation in (-180, 180], so a bound past it is never reached and the range it states is not the one that holds${why}`, 'CE_STOP_LOCAL_BOUND_RANGE',
|
|
372
404
|
);
|
|
373
405
|
}
|
|
374
406
|
}
|
|
@@ -422,7 +454,7 @@ export function expandStops(spec: RigSpec, where: string, plant: StopPlant = {})
|
|
|
422
454
|
const dx = target[0] - linkFrame.worldX;
|
|
423
455
|
const dy = target[1] - linkFrame.worldY;
|
|
424
456
|
if (dx === 0 && dy === 0) {
|
|
425
|
-
throw new CompileError(`${who}: "at" [${stop.at.join(', ')}] on "${stop.against}" sits on the root of "${links[0]}" at the setup pose — the frame aims from the root at the point, and there is no direction to a point it stands on`, 'CE_STOP_POINT_AT_ROOT');
|
|
457
|
+
throw new CompileError(`${who}: "at" [${stop.at.map((v) => stopFigure(v, 'stated')).join(', ')}] on "${stop.against}" sits on the root of "${links[0]}" at the setup pose — the frame aims from the root at the point, and there is no direction to a point it stands on`, 'CE_STOP_POINT_AT_ROOT');
|
|
426
458
|
}
|
|
427
459
|
const aim = directionDegrees(dx, dy);
|
|
428
460
|
// Each angle is brought into (-180, 180] by one whole turn at most, so a
|
|
@@ -431,7 +463,7 @@ export function expandStops(spec: RigSpec, where: string, plant: StopPlant = {})
|
|
|
431
463
|
const rest = halfTurn(halfTurn(linkFrame.worldRotation) - aim);
|
|
432
464
|
if (rest === 0) {
|
|
433
465
|
throw new CompileError(
|
|
434
|
-
`${who}: "at" [${stop.at.join(', ')}] on "${stop.against}" lies on "${links[0]}"'s own line at the setup pose — inward is the turn from the link toward the point, and a point on the link's line has no side`, 'CE_STOP_POINT_ON_LINK_LINE',
|
|
466
|
+
`${who}: "at" [${stop.at.map((v) => stopFigure(v, 'stated')).join(', ')}] on "${stop.against}" lies on "${links[0]}"'s own line at the setup pose — inward is the turn from the link toward the point, and a point on the link's line has no side`, 'CE_STOP_POINT_ON_LINK_LINE',
|
|
435
467
|
);
|
|
436
468
|
}
|
|
437
469
|
// Inward turns the link toward the aim line: counter-clockwise when the link
|