rig-c 2.20.4 → 2.21.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/package.json +1 -1
- package/src/meshcompare.ts +5 -2
- package/src/meshquality.ts +20 -11
- package/src/meshreduce.ts +26 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-c",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/meshcompare.ts
CHANGED
|
@@ -191,8 +191,11 @@ function missing(field: string, found: unknown, required: string): never {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
function validateFit(field: string, f: unknown): void {
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
// `null` on maxOvershoot or maxUndercut is a bound declared absent (issue #1254): measured, reported `undeclared`,
|
|
195
|
+
// never refusing a reference or failing a candidate. A field left out is still refused.
|
|
196
|
+
const distance = (v: unknown): boolean => v === null || (isFiniteNumber(v) && v >= 0);
|
|
197
|
+
if (!isObject(f) || !isFiniteNumber(f.minCoverage) || f.minCoverage < 0 || f.minCoverage > 1 || !distance(f.maxOvershoot) || !distance(f.maxUndercut)) {
|
|
198
|
+
missing(field, f, '{ minCoverage in 0..1, maxOvershoot >= 0 or null, maxUndercut >= 0 or null }');
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
|
package/src/meshquality.ts
CHANGED
|
@@ -116,14 +116,23 @@ export interface SourceMesh {
|
|
|
116
116
|
weights: Array<Array<{ bone: string; weight: number }>> | null;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* All distances in the drawing's pixels; converted to texels by `pageScale`.
|
|
121
|
+
*
|
|
122
|
+
* `maxOvershoot` and `maxUndercut` may be `null` — a bound **declared absent**
|
|
123
|
+
* (issue #1254): the row is measured and reported `undeclared`, with its value
|
|
124
|
+
* and worst sample, and never passes, fails, blocks a step or refuses a source.
|
|
125
|
+
* A field left out (`undefined`) is still refused by name: an omission cannot be
|
|
126
|
+
* told from a caller that forgot, and only the explicit `null` says "measured,
|
|
127
|
+
* not bounded". `minCoverage` has no such form and is always required.
|
|
128
|
+
*/
|
|
120
129
|
export interface ArtFitBounds {
|
|
121
130
|
/** Share of art pixel centres the triangles cover, 0..1. */
|
|
122
131
|
minCoverage: number;
|
|
123
|
-
/** Furthest a covered pixel may sit outside the filled silhouette, px. */
|
|
124
|
-
maxOvershoot: number;
|
|
125
|
-
/** Furthest an uncovered art pixel may sit from the covered set, px. */
|
|
126
|
-
maxUndercut: number;
|
|
132
|
+
/** Furthest a covered pixel may sit outside the filled silhouette, px; null = declared absent. */
|
|
133
|
+
maxOvershoot: number | null;
|
|
134
|
+
/** Furthest an uncovered art pixel may sit from the covered set, px; null = declared absent. */
|
|
135
|
+
maxUndercut: number | null;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
/** What a reduction's RESULT must satisfy (correction 3). Read by `reduceMesh`, stage B2. */
|
|
@@ -607,7 +616,7 @@ function validateInput(input: MeshMeasureInput): void {
|
|
|
607
616
|
if (!isFiniteNumber(coverage) || coverage < 0 || coverage > 1) refuse('REDUCE_INPUT_MISSING', `${who}: targets.artFit.minCoverage is ${JSON.stringify(coverage)}; required a fraction in 0..1`);
|
|
608
617
|
for (const field of ['maxOvershoot', 'maxUndercut'] as const) {
|
|
609
618
|
const v = fit[field];
|
|
610
|
-
if (!isFiniteNumber(v) || v < 0) refuse('REDUCE_INPUT_MISSING', `${who}: targets.artFit.${field} is ${JSON.stringify(v)}; required a finite number of px, 0 or more`);
|
|
619
|
+
if (v !== null && (!isFiniteNumber(v) || v < 0)) refuse('REDUCE_INPUT_MISSING', `${who}: targets.artFit.${field} is ${JSON.stringify(v)}; required a finite number of px, 0 or more, or null (declared absent: measured, reported undeclared)`);
|
|
611
620
|
}
|
|
612
621
|
}
|
|
613
622
|
present(who, 'targets.maxBoundaryDeviation', targets.maxBoundaryDeviation, 'a number of px or null');
|
|
@@ -1298,8 +1307,8 @@ function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: S
|
|
|
1298
1307
|
if (artCount < input.minArtSamples) {
|
|
1299
1308
|
const why = `attachment ${nameOf(attachment)} has ${artCount} art sample(s) at alpha >= ${threshold}; required at least ${input.minArtSamples} (minArtSamples, P9) — a row over fewer is not a measurement`;
|
|
1300
1309
|
rows.push(withRaster(unmeasuredRow(spec('MQ_COVERAGE', 'fraction'), 'not-measurable', why, fit !== null), rasterArt(null), grid, artCount === 0 ? 1 : 1 / artCount));
|
|
1301
|
-
rows.push(withRaster(unmeasuredRow(spec('MQ_OVERSHOOT', 'px'), 'not-measurable', why, fit !== null), rasterArt(8), grid, pxIncrement));
|
|
1302
|
-
rows.push(withRaster(unmeasuredRow(spec('MQ_UNDERCUT', 'px'), 'not-measurable', why, fit !== null), rasterArt(null), grid, pxIncrement));
|
|
1310
|
+
rows.push(withRaster(unmeasuredRow(spec('MQ_OVERSHOOT', 'px'), 'not-measurable', why, fit !== null && fit.maxOvershoot !== null), rasterArt(8), grid, pxIncrement));
|
|
1311
|
+
rows.push(withRaster(unmeasuredRow(spec('MQ_UNDERCUT', 'px'), 'not-measurable', why, fit !== null && fit.maxUndercut !== null), rasterArt(null), grid, pxIncrement));
|
|
1303
1312
|
rows.push(withRaster(unmeasuredRow(spec('MQ_HOLES', 'count'), 'not-measurable', why, false), rasterArt(8), grid, 1));
|
|
1304
1313
|
rows.push(withRaster(unmeasuredRow(spec('MQ_ISLANDS', 'count'), 'not-measurable', why, false), rasterArt(4), grid, 1));
|
|
1305
1314
|
} else {
|
|
@@ -1322,8 +1331,8 @@ function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: S
|
|
|
1322
1331
|
rows.push(
|
|
1323
1332
|
withRaster(
|
|
1324
1333
|
!anyCovered
|
|
1325
|
-
? unmeasuredRow(undercutSpec, 'not-measurable', `attachment ${nameOf(attachment)}: the triangles cover no pixel centre of the ${w}x${h} grid, so no art pixel has a distance to a covered one`, fit !== null)
|
|
1326
|
-
: measuredRow(undercutSpec, r6(Math.sqrt(undercutSq) / scale), fit === null ? null : { op: '<=', value: fit.maxUndercut }, missingWorst, true),
|
|
1334
|
+
? unmeasuredRow(undercutSpec, 'not-measurable', `attachment ${nameOf(attachment)}: the triangles cover no pixel centre of the ${w}x${h} grid, so no art pixel has a distance to a covered one`, fit !== null && fit.maxUndercut !== null)
|
|
1335
|
+
: measuredRow(undercutSpec, r6(Math.sqrt(undercutSq) / scale), fit === null || fit.maxUndercut === null ? null : { op: '<=', value: fit.maxUndercut }, missingWorst, true),
|
|
1327
1336
|
rasterArt(null),
|
|
1328
1337
|
grid,
|
|
1329
1338
|
pxIncrement,
|
|
@@ -1335,7 +1344,7 @@ function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: S
|
|
|
1335
1344
|
const fillsDiffer = rasters.fills().differ;
|
|
1336
1345
|
const silhouetteRows = (connectivity: 4 | 8, gated: boolean): void => {
|
|
1337
1346
|
const { overAt, overSq, holes, holeAt } = reading.silhouette(connectivity);
|
|
1338
|
-
const bound = gated && fit !== null ? { op: '<=' as const, value: fit.maxOvershoot } : null;
|
|
1347
|
+
const bound = gated && fit !== null && fit.maxOvershoot !== null ? { op: '<=' as const, value: fit.maxOvershoot } : null;
|
|
1339
1348
|
rows.push(
|
|
1340
1349
|
withRaster(
|
|
1341
1350
|
measuredRow(spec('MQ_OVERSHOOT', 'px'), r6(Math.sqrt(overSq) / scale), bound, overAt === -1 ? NOTHING_WORSE : { at: { pixel: pixelOf(overAt) } }, gated),
|
package/src/meshreduce.ts
CHANGED
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
* #1240), and as `measureMeshQualityStep` carried from the last step's
|
|
30
30
|
* measurement (`StepRasters`, issue #1246), each of which returns the same
|
|
31
31
|
* report — and taken only when every row the caller's
|
|
32
|
-
* contract requires is `pass`: the art fit at `targets.artFit
|
|
32
|
+
* contract requires is `pass`: the art fit at `targets.artFit` (an overshoot or
|
|
33
|
+
* undercut bound declared `null` is measured and never required), the boundary
|
|
33
34
|
* deviation from the source hull at `targets.maxBoundaryDeviation`, the minimum
|
|
34
35
|
* angle when declared, orientation and degeneracy at 0, and every region's
|
|
35
36
|
* `MQ_MAX_EDGE` and `MQ_TRANSITION`. Before the measurement, the structural
|
|
@@ -216,11 +217,27 @@ class Stop extends Error {
|
|
|
216
217
|
function checkFit(who: string, field: string, fit: unknown): void {
|
|
217
218
|
if (fit === undefined || fit === null || !isObject(fit)) refuse('REDUCE_INPUT_MISSING', `${who}: ${field} is ${JSON.stringify(fit)}; required { minCoverage, maxOvershoot, maxUndercut } — no field has a default`);
|
|
218
219
|
if (!isFiniteNumber(fit.minCoverage) || fit.minCoverage < 0 || fit.minCoverage > 1) refuse('REDUCE_INPUT_MISSING', `${who}: ${field}.minCoverage is ${JSON.stringify(fit.minCoverage)}; required a fraction in 0..1`);
|
|
220
|
+
// `null` is a bound declared absent (issue #1254): measured, reported `undeclared`, never gating. A field left
|
|
221
|
+
// out is not the same thing and stays refused — `JSON.stringify` names it `undefined` in the message.
|
|
219
222
|
for (const k of ['maxOvershoot', 'maxUndercut'] as const) {
|
|
220
|
-
|
|
223
|
+
const v = fit[k];
|
|
224
|
+
if (v !== null && (!isFiniteNumber(v) || v < 0)) refuse('REDUCE_INPUT_MISSING', `${who}: ${field}.${k} is ${JSON.stringify(v)}; required a finite number of px, 0 or more, or null (declared absent: measured, reported undeclared)`);
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Whether `fit` declares the bound of an art row absent (issue #1254): the
|
|
230
|
+
* 8-connected `MQ_OVERSHOOT` under `maxOvershoot: null`, `MQ_UNDERCUT` under
|
|
231
|
+
* `maxUndercut: null`. Such a row is `undeclared` when measured and is never
|
|
232
|
+
* required — not for admission, not for a step, not when it could not be
|
|
233
|
+
* measured. Every other row answers false.
|
|
234
|
+
*/
|
|
235
|
+
function artBoundAbsent(fit: ArtFitBounds, row: MeasureRow): boolean {
|
|
236
|
+
if (row.code === 'MQ_OVERSHOOT') return fit.maxOvershoot === null;
|
|
237
|
+
if (row.code === 'MQ_UNDERCUT') return fit.maxUndercut === null;
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
224
241
|
/**
|
|
225
242
|
* The fields a reduction reads and a measurement does not, refused by name. The
|
|
226
243
|
* art, the mesh's arrays, the regions' shapes and the sample floors are refused
|
|
@@ -755,12 +772,14 @@ function rowName(row: MeasureRow): string {
|
|
|
755
772
|
|
|
756
773
|
/**
|
|
757
774
|
* The first required row that is not `pass`, in `BLOCKING_ORDER`, or null when
|
|
758
|
-
* every one passes. A row is required exactly when its bound is declared
|
|
775
|
+
* every one passes. A row is required exactly when its bound is declared — an
|
|
776
|
+
* art bound declared `null` is not (`artBoundAbsent`, issue #1254); the
|
|
759
777
|
* overshoot row gated is the 8-connected one (P12).
|
|
760
778
|
*/
|
|
761
|
-
function firstBlockingRow(report: MeshQualityReport): MeasureRow | null {
|
|
779
|
+
function firstBlockingRow(report: MeshQualityReport, artFit: ArtFitBounds): MeasureRow | null {
|
|
762
780
|
const rows = report.candidates[0]?.geometry?.rows ?? [];
|
|
763
781
|
const required = (r: MeasureRow): boolean => {
|
|
782
|
+
if (artBoundAbsent(artFit, r)) return false;
|
|
764
783
|
if (r.code === 'MQ_OVERSHOOT') return r.art?.connectivity === 8;
|
|
765
784
|
if (r.code === 'MQ_MIN_ANGLE') return r.bound !== null;
|
|
766
785
|
if (r.code === 'MQ_HOLES' || r.code === 'MQ_ISLANDS' || r.code === 'MQ_TRACE_DEVIATION' || r.code === 'MQ_FILL_DISTANCE') return false;
|
|
@@ -1142,7 +1161,7 @@ function tryRemoval(run: Run, v: number): string | null {
|
|
|
1142
1161
|
undo();
|
|
1143
1162
|
return `outline: ${err.message}`;
|
|
1144
1163
|
}
|
|
1145
|
-
const blocking = firstBlockingRow(measureAgainstTargets(run, canon.mesh, 'candidate'));
|
|
1164
|
+
const blocking = firstBlockingRow(measureAgainstTargets(run, canon.mesh, 'candidate'), run.input.targets.artFit);
|
|
1146
1165
|
if (blocking !== null) {
|
|
1147
1166
|
undo();
|
|
1148
1167
|
return rowName(blocking);
|
|
@@ -1250,7 +1269,7 @@ function reduceValidated(input: MeshReductionInput, rasters: ArtRasters, steps:
|
|
|
1250
1269
|
}
|
|
1251
1270
|
for (const code of ['MQ_ORIENTATION', 'MQ_DEGENERATE', 'MQ_COVERAGE', 'MQ_OVERSHOOT', 'MQ_UNDERCUT']) {
|
|
1252
1271
|
const row = admitRows.find((r) => r.code === code && (code !== 'MQ_OVERSHOOT' || r.art?.connectivity === 8));
|
|
1253
|
-
if (row !== undefined && row.state !== 'pass') {
|
|
1272
|
+
if (row !== undefined && row.state !== 'pass' && !artBoundAbsent(input.sourceBounds, row)) {
|
|
1254
1273
|
const found = row.state === 'fail' ? `${row.value} against ${row.bound!.op} ${row.bound!.value}` : `${row.state} (${row.reason ?? ''})`;
|
|
1255
1274
|
throw new Stop(
|
|
1256
1275
|
'invalid-input',
|
|
@@ -1294,7 +1313,7 @@ function reduceValidated(input: MeshReductionInput, rasters: ArtRasters, steps:
|
|
|
1294
1313
|
const refined = refineRegions(run);
|
|
1295
1314
|
if (refined.kind === 'budget') return noMesh({ reason: 'budget-exhausted', candidatesTried: run.steps, budget: input.budget.maxCandidates, result: 'none-met-the-targets' });
|
|
1296
1315
|
const startCanon = canonicalise(work, run.sourceTurn, boneRank);
|
|
1297
|
-
const startBlock = firstBlockingRow(measureAgainstTargets(run, startCanon.mesh, 'start'));
|
|
1316
|
+
const startBlock = firstBlockingRow(measureAgainstTargets(run, startCanon.mesh, 'start'), input.targets.artFit);
|
|
1298
1317
|
let termination: Termination;
|
|
1299
1318
|
if (startBlock !== null) {
|
|
1300
1319
|
const constraint = refined.kind === 'stuck' ? refined.constraint : rowName(startBlock);
|