spine-rigc 0.25.4 → 0.25.6
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 +32 -13
- package/cli.ts +192 -20
- package/docs/AUTHORING.md +477 -11
- package/docs/FACE.md +153 -37
- package/docs/INGEST.md +64 -5
- package/docs/MOTION.md +1 -1
- package/docs/PROMPTING.md +2 -2
- package/docs/RIGGING.md +3 -2
- package/docs/SPEC_COVERAGE.md +19 -0
- package/package.json +1 -1
- package/skills/face/SKILL.md +13 -4
- package/skills/ingest/SKILL.md +6 -3
- package/skills/rigc/SKILL.md +11 -4
- package/src/atlas.ts +11 -2
- package/src/check.ts +82 -3
- package/src/compile.ts +193 -40
- package/src/emit.ts +76 -41
- package/src/errors.ts +32 -1
- package/src/ingest.ts +41 -3
- package/src/mesh.ts +40 -0
- package/src/rig.ts +28 -6
- package/src/slots.ts +216 -25
- package/src/types.ts +41 -14
- package/src/validate.ts +32 -0
package/README.md
CHANGED
|
@@ -146,15 +146,17 @@ because they are written from a clone of this repository (`bun install`, then ru
|
|
|
146
146
|
the CLI in place); the two are interchangeable — `rigc build …` is
|
|
147
147
|
`bun cli.ts build …`.
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
The reasoning is in
|
|
149
|
+
One command is a repository workflow rather than a package one: `bench` measures
|
|
150
|
+
against Spine's official example projects — fetched, never committed — and against
|
|
151
|
+
reference frames this project renders from them, which **are** committed, each
|
|
152
|
+
example's own `license.txt` beside them under the redistribution grant those files
|
|
153
|
+
carry; the images stay **non-commercial only**. The reasoning is in
|
|
155
154
|
[`bench/reference/README.md`](https://github.com/firejune/rigc/blob/main/bench/reference/README.md)
|
|
156
|
-
and the terms in [NOTICE.md](NOTICE.md).
|
|
157
|
-
fetch-examples`, and
|
|
155
|
+
and the terms in [NOTICE.md](NOTICE.md). It needs a clone and `bun run
|
|
156
|
+
fetch-examples`, and says so by name when the corpus is absent. `check` is not one
|
|
157
|
+
of them: it reads whatever frames you point it at, so it runs from the installed
|
|
158
|
+
package on pictures of your own — which is what *Where to go next* below tells you
|
|
159
|
+
to do with it, and it is the one instrument here that can see a wrong animation.
|
|
158
160
|
|
|
159
161
|
### Install it into your agent
|
|
160
162
|
|
|
@@ -405,6 +407,17 @@ that does not belong to it. See
|
|
|
405
407
|
could. If you have reference pictures of the shot,
|
|
406
408
|
`rigc check --candidate spine --frames <dir>` is the half of the loop that can
|
|
407
409
|
see a wrong animation — AUTHORING.md §9.
|
|
410
|
+
- 🧭 **No reference pictures, because the rig is your own?** Then make them:
|
|
411
|
+
`rigc render` the first build you are happy with and keep those frames. Every
|
|
412
|
+
later build is checked against them, and the first such check — the same build
|
|
413
|
+
against frames of itself — is the floor the rest are read against, because
|
|
414
|
+
`check` grades nothing and has no pass mark. It is the same instrument and the
|
|
415
|
+
same commands; what changes is that the reference is a build of yours you have
|
|
416
|
+
already looked at, so what it measures is **what your edit did**. Every drift
|
|
417
|
+
the report prints carries the bound its own match gives it on the line under
|
|
418
|
+
it, so a figure is read against that rather than against a number from a page;
|
|
419
|
+
AUTHORING.md §9.2 says what the two halves of that bound are and why the floor
|
|
420
|
+
is not zero.
|
|
408
421
|
- [docs/LADDER.md](https://github.com/firejune/rigc/blob/main/docs/LADDER.md) is the benchmark: the same job, from a brief
|
|
409
422
|
and rendered frames, scored. [docs/PILOT.md](https://github.com/firejune/rigc/blob/main/docs/PILOT.md) is how to run an
|
|
410
423
|
agent through it and score what comes back.
|
|
@@ -511,19 +524,24 @@ commands take it and what its default is.
|
|
|
511
524
|
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
|
512
525
|
| `chainfit --candidate <dir> --images <dir> --frame <png>` | reads the parts `pose` refuses, through the candidate's own draw order and hierarchy: masked residuals over **visible** pixels, one hinge per child instead of four degrees of freedom, and the `rotate` key value each answer implies. A bone with two or more anchored descendants is **determined** rather than searched, and the residual that over-determination leaves is reported |
|
|
513
526
|
| `diff <candidate.json> <reference.json>` | structural comparison of two skeletons, one ratio per measure and deliberately no combined score |
|
|
527
|
+
| `bonedist --candidate … --reference … --bones …` | per-frame, per-bone world-transform distance against another skeleton — the ladder's stage 3, run on its own. `--bones <correspondence.json \| identity>` is required rather than defaulted: a candidate is entitled to its own bone names, so the pairing is stated |
|
|
514
528
|
| `check --candidate <dir> --frames <dir>` | the candidate against reference pictures — the only instrument here that can see a *wrong animation* |
|
|
515
529
|
| `bench <rung> --candidate <dir>` | one rung of the benchmark ladder |
|
|
516
530
|
|
|
517
|
-
`diff`, `check` and `bench` measure against something you were given; the
|
|
518
|
-
work on any
|
|
519
|
-
and `bun run fetch-examples`. The reasoning behind
|
|
531
|
+
`diff`, `bonedist`, `check` and `bench` measure against something you were given; the
|
|
532
|
+
first three work on any reference you have, and `bench` is a repository workflow that needs a clone
|
|
533
|
+
and `bun run fetch-examples`. The reasoning behind them is in
|
|
520
534
|
[the benchmark dossier](https://github.com/firejune/rigc/blob/main/docs/BENCHMARK.md).
|
|
521
535
|
|
|
522
536
|
`build` and `validate` both default to `--profile spine` — the 28 validity rules, which
|
|
523
537
|
ask *is this valid Spine 4.3 that any runtime plays correctly?* `--profile spine-html`
|
|
524
538
|
adds all 43: the other 15 are one renderer's policy and one canvas budget's, and they
|
|
525
|
-
fire on perfectly correct editor-produced Spine data,
|
|
526
|
-
|
|
539
|
+
fire on perfectly correct editor-produced Spine data, which is why they are opt-in.
|
|
540
|
+
⇒ **That reason is about foreign data and does not carry to a rig you are authoring
|
|
541
|
+
yourself: author under `--profile spine-html` and read the extra 15 as findings, and
|
|
542
|
+
gate the release under `--profile spine`.** A `deform` key that folds a mesh inside
|
|
543
|
+
out is written out under the default and refused by name under `spine-html`, which
|
|
544
|
+
is the shape of what that split buys you. A report always names
|
|
527
545
|
the profile it ran and lists what that profile left out.
|
|
528
546
|
|
|
529
547
|
Several cuts can also be registered in a `cuts.json` and built by name
|
|
@@ -666,6 +684,7 @@ letting `A17` blame the editor for the harness's own doing.
|
|
|
666
684
|
| 🎓 **[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
685
|
| 📋 [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
686
|
| 🗺️ [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 |
|
|
687
|
+
| 📐 [CLAUDE.md](https://github.com/firejune/rigc/blob/main/CLAUDE.md) | **the doctrine** — why the validator's messages are the product, why nothing reaches disk before green, why no number is ever invented, and what a change has to keep. [CONTRIBUTING.md](https://github.com/firejune/rigc/blob/main/CONTRIBUTING.md) calls it worth ten minutes before a first patch. Repository material |
|
|
669
688
|
|
|
670
689
|
## Why you can trust the output
|
|
671
690
|
|
package/cli.ts
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
type BoneDistReport,
|
|
58
58
|
} from './src/bonedist.ts';
|
|
59
59
|
import { checkAgainstFrames, checkLines, CheckError, type CheckOptions, type CheckReport } from './src/check.ts';
|
|
60
|
-
import { compile, CompileError, relativeImagesPath, type CompileOptions } from './src/compile.ts';
|
|
60
|
+
import { compile, CompileError, droppedStateReason, relativeImagesPath, type CompileOptions } from './src/compile.ts';
|
|
61
61
|
import {
|
|
62
62
|
skeletonDataFromText,
|
|
63
63
|
surveyDeformKeys,
|
|
@@ -67,9 +67,9 @@ import {
|
|
|
67
67
|
type DeformSpan,
|
|
68
68
|
} from './src/deformmeasure.ts';
|
|
69
69
|
import { diffLines, diffSkeletons, reportedFigures, sectionFigures, type DiffReport } from './src/diff.ts';
|
|
70
|
-
import { ingest, IngestError,
|
|
71
|
-
import {
|
|
72
|
-
import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, packAtlas } from './src/atlas.ts';
|
|
70
|
+
import { ingest, IngestError, INGEST_GUTTERS, type IngestStage } from './src/ingest.ts';
|
|
71
|
+
import { copyAtlasPages } from './src/emit.ts';
|
|
72
|
+
import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, packAtlas, parseAtlasText } from './src/atlas.ts';
|
|
73
73
|
import { parseJsonWithPosition } from './src/json-position.ts';
|
|
74
74
|
import { KEY_TIME_EPSILON } from './src/timelines.ts';
|
|
75
75
|
import { findRung, RUNG_IDS, type RungSkeleton } from './src/ladder.ts';
|
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
} from './src/render.ts';
|
|
118
118
|
import {
|
|
119
119
|
assertionCountForProfile,
|
|
120
|
+
attachmentRegionJoins,
|
|
120
121
|
CLI_DEFAULT_PROFILE,
|
|
121
122
|
reportLines,
|
|
122
123
|
validate,
|
|
@@ -125,7 +126,7 @@ import {
|
|
|
125
126
|
} from './src/validate.ts';
|
|
126
127
|
import { parseMotionSpec } from './src/motion.ts';
|
|
127
128
|
import { depthStepLevels, type FoldLimit, type TurnCeiling } from './src/depth.ts';
|
|
128
|
-
import type { CompileResult } from './src/types.ts';
|
|
129
|
+
import type { CompileResult, DroppedState } from './src/types.ts';
|
|
129
130
|
|
|
130
131
|
/**
|
|
131
132
|
* One entry of a cuts.json, every path relative to the cuts.json file.
|
|
@@ -148,6 +149,14 @@ export type CutTable = Record<string, CutEntry>;
|
|
|
148
149
|
|
|
149
150
|
class UsageError extends Error {}
|
|
150
151
|
|
|
152
|
+
/**
|
|
153
|
+
* `explain` refusing a pair it cannot pose — a usage error in kind, printed
|
|
154
|
+
* without the usage block for `PoseError`'s and `IngestError`'s reason: the
|
|
155
|
+
* message names an attachment, a region and two flags, and reprinting every
|
|
156
|
+
* command's usage under it buries the one line that says what to change.
|
|
157
|
+
*/
|
|
158
|
+
class ExplainError extends Error {}
|
|
159
|
+
|
|
151
160
|
// ---------------------------------------------------------------------------
|
|
152
161
|
// package metadata — the installed version and repository, for `--version`
|
|
153
162
|
// and for naming a remedy `bench` can only give from a repo checkout.
|
|
@@ -1133,6 +1142,19 @@ function readIntFlag(flags: Record<string, string>, name: string, fallback: numb
|
|
|
1133
1142
|
return Number(raw);
|
|
1134
1143
|
}
|
|
1135
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* One `DROP` line, written once because two outcomes print it.
|
|
1147
|
+
*
|
|
1148
|
+
* A build that succeeds prints it in its report; a build that REFUSES prints it
|
|
1149
|
+
* under the refusal (issue #671), and the two have to be the same line or the
|
|
1150
|
+
* failing run would be quoting a different fact from the one the green run
|
|
1151
|
+
* shows. What it names — a file or a region — is `droppedStateReason`'s, in the
|
|
1152
|
+
* compiler, beside the code that decided which of the two was consulted.
|
|
1153
|
+
*/
|
|
1154
|
+
function dropLine(dropped: DroppedState): string {
|
|
1155
|
+
return ` DROP ${dropped.slot}/${dropped.state}: ${droppedStateReason(dropped)} (state not emitted)`;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1136
1158
|
function cmdBuild(flags: Record<string, string>): void {
|
|
1137
1159
|
const { label, opts } = resolveCut(flags);
|
|
1138
1160
|
const profile = readProfile(flags);
|
|
@@ -1195,9 +1217,7 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1195
1217
|
: ` scale ${img.atlasScale} (${img.atlas.originalWidth}x${img.atlas.originalHeight} texels)`);
|
|
1196
1218
|
console.log(` .. ${img.region.padEnd(24)} ${img.width}x${img.height} <- ${where}`);
|
|
1197
1219
|
}
|
|
1198
|
-
for (const d of result.droppedStates)
|
|
1199
|
-
console.log(` DROP ${d.slot}/${d.state}: ${d.why ?? `no PNG at ${d.path}`} (state not emitted)`);
|
|
1200
|
-
}
|
|
1220
|
+
for (const d of result.droppedStates) console.log(dropLine(d));
|
|
1201
1221
|
// "The optional slots are optional" is a claim about this code path, so this
|
|
1202
1222
|
// code path says which ones it left out rather than being silently right.
|
|
1203
1223
|
for (const a of result.absentParts) {
|
|
@@ -1231,14 +1251,21 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1231
1251
|
// correct for a build sitting beside the project it came from and breaks the
|
|
1232
1252
|
// moment the directory is zipped, committed or moved on its own (issue #217).
|
|
1233
1253
|
// Opt-in only: the default stays exactly what it has always been.
|
|
1254
|
+
//
|
|
1255
|
+
// What is copied is what the ATLAS names, not what the image list holds: under
|
|
1256
|
+
// `--atlas-in` the two are different lists, and rebuilding the text from the
|
|
1257
|
+
// second wrote a file the pack never contained — zero bytes for a rig that
|
|
1258
|
+
// declares no parts, one fabricated page per part for a rig that does, both of
|
|
1259
|
+
// them green here because the gate above had already read the compile's own
|
|
1260
|
+
// text (issue #693, `src/emit.ts`).
|
|
1234
1261
|
let atlasText = result.atlasText;
|
|
1235
1262
|
if (flags['copy-images'] !== undefined) {
|
|
1236
|
-
const copied =
|
|
1263
|
+
const copied = copyAtlasPages(atlasText, opts.outDir);
|
|
1237
1264
|
atlasText = copied.atlasText;
|
|
1238
1265
|
console.log(` .. copy-images: ${copied.pages.length} page(s) copied into ${opts.outDir}`);
|
|
1239
1266
|
for (const p of copied.pages) {
|
|
1240
|
-
const note = p.to === basename(p.from) ? '' :
|
|
1241
|
-
console.log(` .. ${p.
|
|
1267
|
+
const note = p.to === basename(p.from) ? '' : ' (renamed — basename collision)';
|
|
1268
|
+
console.log(` .. ${p.to.padEnd(24)} <- ${p.from} (${p.regions} region(s))${note}`);
|
|
1242
1269
|
}
|
|
1243
1270
|
}
|
|
1244
1271
|
|
|
@@ -2423,6 +2450,81 @@ function cmdBoneDist(flags: Record<string, string>): void {
|
|
|
2423
2450
|
if (flags.json !== undefined) writeJson(flags.json, report);
|
|
2424
2451
|
}
|
|
2425
2452
|
|
|
2453
|
+
/**
|
|
2454
|
+
* Refuse a compiled pair whose art `explain` cannot pose through, by name.
|
|
2455
|
+
*
|
|
2456
|
+
* 🚨 `explain` poses the rig — `deformReportLines` loads the emitted pair through
|
|
2457
|
+
* `spine-core` to measure what each deform key did — and a pose resolves EVERY
|
|
2458
|
+
* attachment against the atlas, whether or not anything deforms it. On the specs
|
|
2459
|
+
* `ingest --art none` writes there is nothing to resolve against: the entries
|
|
2460
|
+
* state a size and name no `image`, so the compile atlases nothing, and the load
|
|
2461
|
+
* threw the runtime's own `Region not found in atlas: rear-upper-arm (attachment:
|
|
2462
|
+
* rear-upper-arm)` with a spine-core stack trace under it and exit 1 (measured on
|
|
2463
|
+
* `examples/spineboy/export/spineboy-ess.json`, issue #697). That is the tool
|
|
2464
|
+
* telling an agent about its own internals instead of about the rig, on the one
|
|
2465
|
+
* input `docs/INGEST.md` §2.0 documents as the route through a foreign skeleton.
|
|
2466
|
+
*
|
|
2467
|
+
* ⭐ Both halves of the join are rigc's own readers rather than a second opinion
|
|
2468
|
+
* on somebody else's format: the walk is `attachmentRegionJoins`, which `PS127`
|
|
2469
|
+
* measures against the loader's own `findRegion` calls, and the region names come
|
|
2470
|
+
* from `parseAtlasText`, which is what `--atlas-in` already resolves against.
|
|
2471
|
+
* Names are compared EXACTLY — as `A08` compares them and as `findRegion`
|
|
2472
|
+
* matches them — so a padded region name is a miss on both sides.
|
|
2473
|
+
*
|
|
2474
|
+
* ⚠️ What it deliberately does not do is catch the pose. A blanket `try` around
|
|
2475
|
+
* `skeletonDataFromText` would convert any exception the runtime raises into a
|
|
2476
|
+
* sentence claiming the cause is missing art, and a rigc defect reported under
|
|
2477
|
+
* somebody else's name is the doctrine's second bullet inverted. This refuses
|
|
2478
|
+
* the case it can NAME, before a line of the report is printed, and leaves
|
|
2479
|
+
* anything else to arrive as itself.
|
|
2480
|
+
*/
|
|
2481
|
+
function refuseUnposableArt(result: CompileResult, opts: CompileOptions): void {
|
|
2482
|
+
const regionNames = parseAtlasText(result.atlasText).pages.flatMap((page) => page.regions.map((region) => region.name));
|
|
2483
|
+
const have = new Set(regionNames);
|
|
2484
|
+
const misses: Array<{ at: string; attachment: string; lookup: string }> = [];
|
|
2485
|
+
let lookups = 0;
|
|
2486
|
+
for (const join of attachmentRegionJoins(JSON.parse(result.skeletonText))) {
|
|
2487
|
+
// A `sequence` this walk will not guess at names no region it can check, and
|
|
2488
|
+
// `A08` passes over it for the same reason.
|
|
2489
|
+
if (join.lookups === null) continue;
|
|
2490
|
+
for (const lookup of join.lookups) {
|
|
2491
|
+
lookups++;
|
|
2492
|
+
if (!have.has(lookup)) {
|
|
2493
|
+
misses.push({
|
|
2494
|
+
at: `skin "${join.skin}" slot "${join.slot}" placeholder "${join.placeholder}"`,
|
|
2495
|
+
attachment: join.name,
|
|
2496
|
+
lookup,
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
if (misses.length === 0) return;
|
|
2502
|
+
const first = misses[0];
|
|
2503
|
+
// Reported because it was measured, and absent where there is none — `A08`'s
|
|
2504
|
+
// own near-miss clause, in `A08`'s own words.
|
|
2505
|
+
const near = regionNames.find((region) => region.trim().toLowerCase() === first.lookup.toLowerCase());
|
|
2506
|
+
const has =
|
|
2507
|
+
regionNames.length === 0
|
|
2508
|
+
? 'it declares no region at all'
|
|
2509
|
+
: `it declares ${regionNames.length} region(s) and none of them is that${
|
|
2510
|
+
near === undefined ? '' : `, though it does have ${JSON.stringify(near)}`
|
|
2511
|
+
}`;
|
|
2512
|
+
const remedy =
|
|
2513
|
+
opts.atlasInPath === undefined
|
|
2514
|
+
? 'Art reaches a compile two ways and this run took neither: `--atlas-in <pack.atlas>` resolves the parts ' +
|
|
2515
|
+
'against a pack somebody already made, and an "image" per attachment resolves them as loose PNGs under ' +
|
|
2516
|
+
'`--images <dir>` — a spec that states a size and names no image is what `ingest --art none` writes, and ' +
|
|
2517
|
+
'`--atlas-in` is what reads it'
|
|
2518
|
+
: `Either the spec's region name or ${opts.atlasInPath} is the one that moved: fix the name, or point ` +
|
|
2519
|
+
'`--atlas-in` at the pack that has it';
|
|
2520
|
+
throw new ExplainError(
|
|
2521
|
+
`${first.at}: attachment ${JSON.stringify(first.attachment)} wants region ${JSON.stringify(first.lookup)}, ` +
|
|
2522
|
+
`which this build's atlas does not have (${has}). \`explain\` poses the rig to measure its deform keys and a ` +
|
|
2523
|
+
`pose resolves every attachment against the atlas, so there is nothing to pose it against. ${remedy}. ` +
|
|
2524
|
+
`${misses.length} of ${lookups} attachment lookup(s) here resolve to no region.`,
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2426
2528
|
function cmdExplain(flags: Record<string, string>): void {
|
|
2427
2529
|
const { label, opts } = resolveCut(flags);
|
|
2428
2530
|
console.log(`rigc explain ${label}`);
|
|
@@ -2431,6 +2533,13 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2431
2533
|
console.log(` .. rig ${opts.rigPath}`);
|
|
2432
2534
|
console.log(` .. motion ${opts.motionPath}`);
|
|
2433
2535
|
const result = compile(opts);
|
|
2536
|
+
// Before a line of the report, rather than at the pose two hundred lines in:
|
|
2537
|
+
// the blocks that need the art — DEFORM, meshes, dropped states — all sit
|
|
2538
|
+
// BELOW the pose, so a run that printed the bone and timeline dump and then
|
|
2539
|
+
// refused would be a report missing everything the missing art decides, with
|
|
2540
|
+
// the sentence saying so scrolled off the top. It is the invocation that has
|
|
2541
|
+
// to change, so it is refused before the report it cannot finish (issue #697).
|
|
2542
|
+
refuseUnposableArt(result, opts);
|
|
2434
2543
|
// `compile` has already parsed this file, so the read below cannot fail — but
|
|
2435
2544
|
// it goes through the same parser rather than a cast, because the cast was the
|
|
2436
2545
|
// last one in the repository and issue #307 was about exactly that.
|
|
@@ -2720,8 +2829,12 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2720
2829
|
}
|
|
2721
2830
|
|
|
2722
2831
|
if (result.droppedStates.length) {
|
|
2723
|
-
|
|
2724
|
-
|
|
2832
|
+
// The heading said "no PNG on disk" and the line printed the path, on a
|
|
2833
|
+
// command that takes `--atlas-in` like `build` does — so an explain of a
|
|
2834
|
+
// pack build named a file it never opened. Same renderer as the other two
|
|
2835
|
+
// printers now, for the same reason they share one (issue #671).
|
|
2836
|
+
console.log('\ndropped states (listed in the manifest, no art behind them)');
|
|
2837
|
+
for (const d of result.droppedStates) console.log(` ${d.slot}/${d.state} ${droppedStateReason(d)}`);
|
|
2725
2838
|
}
|
|
2726
2839
|
|
|
2727
2840
|
console.log('\nmix table (player config, not skeleton JSON)');
|
|
@@ -2829,10 +2942,13 @@ function cmdIngest(flags: Record<string, string>, positional: string[]): void {
|
|
|
2829
2942
|
// Grouped by kind rather than printed in discovery order: a blocker is what
|
|
2830
2943
|
// decides the exit code, and a reader scanning for one should not have to
|
|
2831
2944
|
// read past a hundred DURATION lines to find it.
|
|
2832
|
-
|
|
2945
|
+
// The gutter words are `INGEST_GUTTERS`, which is also the column
|
|
2946
|
+
// `docs/INGEST.md` §2.0's finding-code table is keyed on (issue #675); the
|
|
2947
|
+
// pad to one width is this printer's, so the codes line up.
|
|
2948
|
+
const width = Math.max(...Object.values(INGEST_GUTTERS).map((gutter) => gutter.length));
|
|
2833
2949
|
for (const kind of ['blocker', 'judgement', 'lossy'] as const) {
|
|
2834
2950
|
for (const finding of result.findings.filter((f) => f.kind === kind)) {
|
|
2835
|
-
console.log(` ${
|
|
2951
|
+
console.log(` ${INGEST_GUTTERS[kind].padEnd(width)} ${finding.code}: ${finding.where} — ${finding.detail}`);
|
|
2836
2952
|
}
|
|
2837
2953
|
}
|
|
2838
2954
|
console.log(`rigc: wrote ${join(outDir, 'rig.json')}`);
|
|
@@ -3049,6 +3165,18 @@ interface CommandDoc {
|
|
|
3049
3165
|
* nine, and nothing had ever compared the two.
|
|
3050
3166
|
*/
|
|
3051
3167
|
overrides?: Record<string, { value?: string; meaning?: string }>;
|
|
3168
|
+
/**
|
|
3169
|
+
* Lines printed under the flag table: what this command's own figures mean.
|
|
3170
|
+
*
|
|
3171
|
+
* ⚠️ Not a second place to describe a flag. It exists for what is true of the
|
|
3172
|
+
* **command** and of no flag it takes — and the case that earned it is issue
|
|
3173
|
+
* #678: `pose` and `chainfit` each say *"it is a reporting threshold, not a
|
|
3174
|
+
* pass bar"* on the flag that carries their threshold, and `check` has no such
|
|
3175
|
+
* flag, so its page said nothing at all about whether any of its figures is a
|
|
3176
|
+
* bar to beat. An agent reading `slot drift worst 3.7 px` off a correct rig had
|
|
3177
|
+
* no page to consult and no exit code to read it in.
|
|
3178
|
+
*/
|
|
3179
|
+
notes?: string[];
|
|
3052
3180
|
}
|
|
3053
3181
|
|
|
3054
3182
|
const COMMANDS: CommandDoc[] = [
|
|
@@ -3078,8 +3206,20 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3078
3206
|
},
|
|
3079
3207
|
{
|
|
3080
3208
|
name: 'explain',
|
|
3081
|
-
usage: [
|
|
3082
|
-
|
|
3209
|
+
usage: [
|
|
3210
|
+
'rigc explain --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] (it never gates, and writes nothing)',
|
|
3211
|
+
'rigc explain … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made, as build does)',
|
|
3212
|
+
'rigc explain --cut <name> --cuts <cuts.json>',
|
|
3213
|
+
],
|
|
3214
|
+
flags: ['rig', 'motion', 'out', 'manifest', 'images', 'atlas-in', 'cut', 'cuts'],
|
|
3215
|
+
notes: [
|
|
3216
|
+
'this line said "the same arguments as build, minus --profile" and was false in both',
|
|
3217
|
+
'directions (issue #697): --atlas-in was not listed here, so the one flag that lets this',
|
|
3218
|
+
'command read what `ingest --art none` writes was reachable and undocumented, while',
|
|
3219
|
+
'--pack, --page-size, --padding and --copy-images are build\'s and do nothing here —',
|
|
3220
|
+
'they decide what is WRITTEN, and this command writes nothing. What it takes is listed',
|
|
3221
|
+
'above, and that is now the whole of it.',
|
|
3222
|
+
],
|
|
3083
3223
|
},
|
|
3084
3224
|
{
|
|
3085
3225
|
name: 'validate',
|
|
@@ -3126,6 +3266,19 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3126
3266
|
'REFUSED by name, and one that records none says so in the report rather than pretending to agree',
|
|
3127
3267
|
},
|
|
3128
3268
|
},
|
|
3269
|
+
notes: [
|
|
3270
|
+
'every figure here is a reporting threshold, not a pass bar. Nothing in this report',
|
|
3271
|
+
'grades, no number has to beat anything, and the exit code says only whether the',
|
|
3272
|
+
'comparison could be MADE: 0 when it ran — including the build with every easing',
|
|
3273
|
+
'reversed, which is the defect this command exists for — 1 when it could not (frames',
|
|
3274
|
+
'that are not there, a skin the frames do not record, a candidate that will not load),',
|
|
3275
|
+
'2 on the flags.',
|
|
3276
|
+
'',
|
|
3277
|
+
'So read a figure against a floor you measured yourself: render the first green build',
|
|
3278
|
+
'and keep its frames, then check every later build against them. The identity run of',
|
|
3279
|
+
'that pair is the floor, and it is not zero — docs/AUTHORING.md §9.2 states it, what',
|
|
3280
|
+
'it comes from, and which column separates a wrong curve from a moved key.',
|
|
3281
|
+
],
|
|
3129
3282
|
},
|
|
3130
3283
|
{
|
|
3131
3284
|
name: 'bench',
|
|
@@ -3267,9 +3420,14 @@ function commandHelp(name: string): string {
|
|
|
3267
3420
|
const meaning = (key: string): string => doc.overrides?.[key]?.meaning ?? FLAG_MEANINGS[key];
|
|
3268
3421
|
const labels = keys.map((key) => `--${key}${value(key) ? ` ${value(key)}` : ''}`);
|
|
3269
3422
|
const width = Math.max(...labels.map((l) => l.length)) + 2;
|
|
3270
|
-
return [
|
|
3271
|
-
'
|
|
3272
|
-
)
|
|
3423
|
+
return [
|
|
3424
|
+
'usage:',
|
|
3425
|
+
...doc.usage.map((u) => ` ${u}`),
|
|
3426
|
+
'',
|
|
3427
|
+
'flags:',
|
|
3428
|
+
...keys.map((key, i) => ` ${labels[i].padEnd(width)}${meaning(key)}`),
|
|
3429
|
+
...(doc.notes === undefined ? [] : ['', ...doc.notes]),
|
|
3430
|
+
].join('\n');
|
|
3273
3431
|
}
|
|
3274
3432
|
|
|
3275
3433
|
const USAGE = [
|
|
@@ -3410,6 +3568,13 @@ try {
|
|
|
3410
3568
|
}
|
|
3411
3569
|
if (err instanceof CompileError) {
|
|
3412
3570
|
console.error(`rigc compile error: ${err.message}`);
|
|
3571
|
+
// The drops the compile recorded before it stopped, in the same line the
|
|
3572
|
+
// green build prints (issue #671). They were reported from the compile
|
|
3573
|
+
// RESULT alone, so the run that failed BECAUSE a file was missing was the
|
|
3574
|
+
// one run that never named the file. On stderr with the refusal rather than
|
|
3575
|
+
// on stdout, so redirecting one stream does not separate a fact from the
|
|
3576
|
+
// sentence it explains.
|
|
3577
|
+
for (const dropped of err.droppedStates ?? []) console.error(dropLine(dropped));
|
|
3413
3578
|
process.exit(1);
|
|
3414
3579
|
}
|
|
3415
3580
|
if (err instanceof CheckError) {
|
|
@@ -3427,6 +3592,13 @@ try {
|
|
|
3427
3592
|
console.error(`rigc pose: ${err.message}`);
|
|
3428
3593
|
process.exit(2);
|
|
3429
3594
|
}
|
|
3595
|
+
// A refusal of the invocation, like the two below it, and exit 2 for the same
|
|
3596
|
+
// reason: nothing was posed and nothing was written, so it is the command line
|
|
3597
|
+
// that has to change (issue #697).
|
|
3598
|
+
if (err instanceof ExplainError) {
|
|
3599
|
+
console.error(`rigc explain: ${err.message}`);
|
|
3600
|
+
process.exit(2);
|
|
3601
|
+
}
|
|
3430
3602
|
// Same kind as a PoseError, and printed the same way for the same reason: the
|
|
3431
3603
|
// messages name a path, a bone or an attachment, and reprinting the whole
|
|
3432
3604
|
// usage under them buries the one line that says what to change.
|