spine-rigc 2.44.0 → 2.45.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/meshquality.ts +63 -1
- package/src/meshreduce.ts +96 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.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/src/meshquality.ts
CHANGED
|
@@ -306,6 +306,54 @@ export interface MeshReductionInput {
|
|
|
306
306
|
* a list. `protect.edges` keeps its meaning: a line held whole is listed there.
|
|
307
307
|
*/
|
|
308
308
|
lines?: NamedLine[];
|
|
309
|
+
/**
|
|
310
|
+
* What refused each kept source vertex (issue #1374) — opt-in. Left out = the report is the one it was before the
|
|
311
|
+
* field existed, byte for byte, and no refusal is measured that the run would not measure. Set, `changes` ends in
|
|
312
|
+
* `kept` (`KeptRefusals`): for the last removal pass, the one that took nothing, each kept source vertex's reason
|
|
313
|
+
* counted per reason and split boundary / interior — and with `'by-vertex'` also listed vertex by vertex with the
|
|
314
|
+
* refusal's text. A refusal the deviation floor decided is measured in full to be named, in that pass only, as
|
|
315
|
+
* `blockingConstraint`'s is. No step, order or mesh reads it. Any other value, `null` included, is refused.
|
|
316
|
+
*/
|
|
317
|
+
explainKept?: 'by-row' | 'by-vertex';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Issue #1374: why the kept source vertices are kept — `ReductionChanges.kept`, written last in `changes` and only when
|
|
322
|
+
* the input set `explainKept`. The reasons are read from the last removal pass, the one in which no step was taken, so
|
|
323
|
+
* every kept vertex not protected was attempted there once as a single removal, on the mesh the result carries (before
|
|
324
|
+
* any `retriangulate` post-pass). A vertex is boundary when it is a source-hull vertex — a kept source vertex is on the
|
|
325
|
+
* outline exactly then — and interior otherwise. Vertices the refinement inserted are never candidates and are not
|
|
326
|
+
* listed.
|
|
327
|
+
*
|
|
328
|
+
* A reason is the refusal's constraint without the object an author named: a row's code (`MQ_BOUNDARY_DEVIATION`,
|
|
329
|
+
* `MQ_SKINNING_RESIDUAL`, …, the region or line it names left in `refusal`), or a structural condition as the refusal
|
|
330
|
+
* opens — `retriangulation`, `outline`, `protect (a)`, `weightJump (b)`, `line`. A vertex no attempt in such a pass
|
|
331
|
+
* reached reads why, never an absence: `protected` (`protect`, or a line's end or crossing), and — when the run has no
|
|
332
|
+
* pass that took nothing — `budget-exhausted`, `replayed-to-accepted-step` or `no-removal-pass` (the refined source
|
|
333
|
+
* did not meet its targets, so no removal was tried), the termination it ended in.
|
|
334
|
+
*/
|
|
335
|
+
export interface KeptRefusals {
|
|
336
|
+
/** The 1-based removal pass the reasons were read from; null when the run had no pass that took nothing. */
|
|
337
|
+
pass: number | null;
|
|
338
|
+
/** One entry per reason that holds a kept source vertex, ascending by reason. `boundary + interior` over the entries is the kept source vertices. */
|
|
339
|
+
rows: KeptRow[];
|
|
340
|
+
/** `explainKept: 'by-vertex'` only: every kept source vertex, ascending by source index. */
|
|
341
|
+
vertices?: KeptVertex[];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Issue #1374: one reason, and the kept source vertices it holds on the outline and inside it. */
|
|
345
|
+
export interface KeptRow {
|
|
346
|
+
reason: string;
|
|
347
|
+
boundary: number;
|
|
348
|
+
interior: number;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Issue #1374: one kept source vertex, its reason, and the refusal's text as `blockingConstraint` writes it — null for a reason no attempt gave. */
|
|
352
|
+
export interface KeptVertex {
|
|
353
|
+
vertex: number;
|
|
354
|
+
boundary: boolean;
|
|
355
|
+
reason: string;
|
|
356
|
+
refusal: string | null;
|
|
309
357
|
}
|
|
310
358
|
|
|
311
359
|
/**
|
|
@@ -687,8 +735,10 @@ export interface ReductionChanges {
|
|
|
687
735
|
* `stopAfterAccepted: k` replays to the operation at `acceptedAt[k - 1]`.
|
|
688
736
|
*/
|
|
689
737
|
acceptedAt: AcceptedOperation[];
|
|
690
|
-
/** Issue #1283: which triangulation the mesh carries — present only when the input set `retriangulate
|
|
738
|
+
/** Issue #1283: which triangulation the mesh carries — present only when the input set `retriangulate`. */
|
|
691
739
|
retriangulation?: Retriangulation;
|
|
740
|
+
/** Issue #1374: what refused each kept source vertex in the last removal pass — present only when the input set `explainKept`, written last. */
|
|
741
|
+
kept?: KeptRefusals;
|
|
692
742
|
}
|
|
693
743
|
|
|
694
744
|
export interface MeshQualityReport {
|
|
@@ -750,6 +800,8 @@ export interface EffectiveSettings {
|
|
|
750
800
|
lines?: NamedLine[];
|
|
751
801
|
/** A measurement's `lineSource`, by id and `sourceMeshDigest`, echoed when the input set it (issue #1326); absent otherwise. */
|
|
752
802
|
lineSource?: { id: string; digest: string };
|
|
803
|
+
/** A reduction's `explainKept`, echoed when the input set it (issue #1374); absent otherwise. */
|
|
804
|
+
explainKept?: 'by-row' | 'by-vertex';
|
|
753
805
|
}
|
|
754
806
|
|
|
755
807
|
export interface MeshCounts {
|
|
@@ -2805,6 +2857,7 @@ function effectiveJson(e: EffectiveSettings): Json {
|
|
|
2805
2857
|
...(e.overBound === undefined ? {} : { overBound: { maxSamples: e.overBound.maxSamples, triangles: e.overBound.triangles } }),
|
|
2806
2858
|
...(e.lines === undefined ? {} : { lines: e.lines.map((l): Json => ({ name: l.name, vertices: [...l.vertices], closed: l.closed, maxDeviation: l.maxDeviation })) }),
|
|
2807
2859
|
...(e.lineSource === undefined ? {} : { lineSource: { id: e.lineSource.id, digest: e.lineSource.digest } }),
|
|
2860
|
+
...(e.explainKept === undefined ? {} : { explainKept: e.explainKept }),
|
|
2808
2861
|
};
|
|
2809
2862
|
}
|
|
2810
2863
|
|
|
@@ -2950,6 +3003,15 @@ function candidateJson(c: CandidateReport): Json {
|
|
|
2950
3003
|
monotonicity: k.retriangulation.monotonicity,
|
|
2951
3004
|
},
|
|
2952
3005
|
}),
|
|
3006
|
+
...(k.kept === undefined
|
|
3007
|
+
? {}
|
|
3008
|
+
: {
|
|
3009
|
+
kept: {
|
|
3010
|
+
pass: k.kept.pass,
|
|
3011
|
+
rows: k.kept.rows.map((r) => ({ reason: r.reason, boundary: r.boundary, interior: r.interior })),
|
|
3012
|
+
...(k.kept.vertices === undefined ? {} : { vertices: k.kept.vertices.map((v) => ({ vertex: v.vertex, boundary: v.boundary, reason: v.reason, refusal: v.refusal })) }),
|
|
3013
|
+
},
|
|
3014
|
+
}),
|
|
2953
3015
|
};
|
|
2954
3016
|
}
|
|
2955
3017
|
return out;
|
package/src/meshreduce.ts
CHANGED
|
@@ -90,6 +90,15 @@
|
|
|
90
90
|
* of the triangles it changed; the result's own measurement reads the row in
|
|
91
91
|
* full. Left out, nothing here reads it and the call is the one it was.
|
|
92
92
|
*
|
|
93
|
+
* ## What refused each kept vertex (issue #1374, opt-in)
|
|
94
|
+
*
|
|
95
|
+
* With `explainKept`, the pass that ends the reduction having taken nothing
|
|
96
|
+
* keeps each single removal's refusal and names it once the pass is over — the
|
|
97
|
+
* working mesh is then the one every attempt in it was tried on, so a refusal
|
|
98
|
+
* the deviation floor decided is measured as `blockingConstraint`'s is — and the
|
|
99
|
+
* result's `changes.kept` counts the kept source vertices per reason, boundary
|
|
100
|
+
* and interior (`keptOf`). No step, order or mesh reads it.
|
|
101
|
+
*
|
|
93
102
|
* A removal whose deviation floor — the furthest removed source-hull vertex
|
|
94
103
|
* from the candidate's outline, which `MQ_BOUNDARY_DEVIATION` can only exceed —
|
|
95
104
|
* is over the bound is refused without the measurement; its name, when read,
|
|
@@ -148,6 +157,8 @@ import {
|
|
|
148
157
|
type CandidateReport,
|
|
149
158
|
type DeformKeyInput,
|
|
150
159
|
type EffectiveSettings,
|
|
160
|
+
type KeptRefusals,
|
|
161
|
+
type KeptVertex,
|
|
151
162
|
type MeasureRow,
|
|
152
163
|
type MeshCounts,
|
|
153
164
|
type MeshMeasureInput,
|
|
@@ -344,6 +355,9 @@ function validateReduction(input: MeshReductionInput, plant: ReductionPlant | nu
|
|
|
344
355
|
if ('removalOrder' in input && input.removalOrder !== undefined && input.removalOrder !== 'deformation-load') {
|
|
345
356
|
refuse('REDUCE_INPUT_MISSING', `${who}: removalOrder is ${JSON.stringify(input.removalOrder)}; required "deformation-load" — single removals in ascending predicted deformation load, ties by source index — or the field left out (issue #1283: ascending source index)`);
|
|
346
357
|
}
|
|
358
|
+
if ('explainKept' in input && input.explainKept !== undefined && input.explainKept !== 'by-row' && input.explainKept !== 'by-vertex') {
|
|
359
|
+
refuse('REDUCE_INPUT_MISSING', `${who}: explainKept is ${JSON.stringify(input.explainKept)}; required "by-row" — what refused the kept source vertices in the last removal pass, counted per reason — or "by-vertex" — those counts and every kept source vertex listed — or the field left out (issue #1374: no such block)`);
|
|
360
|
+
}
|
|
347
361
|
// Issue #1287: refused here, before the admission measurement and any step, in the measurement's own words — the
|
|
348
362
|
// admission does not carry the field, and a call whose source is refused never reaches the one measurement that does.
|
|
349
363
|
if (plant !== 'amplitude-unvalidated') validateMotionAmplitude(who, input.motionAmplitude);
|
|
@@ -961,6 +975,18 @@ interface Run {
|
|
|
961
975
|
hooks: SkinningHooks | null;
|
|
962
976
|
/** Issue #1326: the declared lines, or null when the input left `lines` out. */
|
|
963
977
|
lines: LineRun | null;
|
|
978
|
+
/** Issue #1374: the refusals of the current pass's single removals, and the last pass's once it took nothing — null when the input left `explainKept` out. */
|
|
979
|
+
kept: KeptLog | null;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Issue #1374: what `explainKept` reads. `attempts` is the current pass's single removals refused, by source vertex,
|
|
984
|
+
* as tried — a floor's refusal still a thunk, read only if the pass takes nothing; `final` is that pass's, read in
|
|
985
|
+
* the state it was tried in, once a pass ends having taken nothing.
|
|
986
|
+
*/
|
|
987
|
+
interface KeptLog {
|
|
988
|
+
attempts: Map<number, Refusal>;
|
|
989
|
+
final: { pass: number; refusals: Map<number, string> } | null;
|
|
964
990
|
}
|
|
965
991
|
|
|
966
992
|
/** Issue #1326: the input's lines as the steps read them. */
|
|
@@ -1486,7 +1512,14 @@ export type ReductionPlant =
|
|
|
1486
1512
|
| OrderPlant
|
|
1487
1513
|
| AmplitudePlant
|
|
1488
1514
|
| RefinementPlant
|
|
1489
|
-
| LinePlant
|
|
1515
|
+
| LinePlant
|
|
1516
|
+
| KeptPlant;
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Issue #1374's fault, for the `mesh-quality` suite's control: each kept vertex named by the refusal of the next
|
|
1520
|
+
* vertex refused in the last pass (the first's for the last) — a reason that did not refuse it.
|
|
1521
|
+
*/
|
|
1522
|
+
export type KeptPlant = 'kept-reason-shifted';
|
|
1490
1523
|
|
|
1491
1524
|
/**
|
|
1492
1525
|
* Issue #1326's faults in the line mechanism, for the `mesh-quality` suite's controls: the line rows read by no step
|
|
@@ -1909,6 +1942,7 @@ function removeVertices(run: Run): PhaseEnd {
|
|
|
1909
1942
|
let taken = 0;
|
|
1910
1943
|
let tried = 0;
|
|
1911
1944
|
pass++;
|
|
1945
|
+
if (run.kept !== null) run.kept.attempts = new Map();
|
|
1912
1946
|
if (runs !== undefined) {
|
|
1913
1947
|
let walk: number[] | null = null;
|
|
1914
1948
|
for (let s = 0; s < hull; s++) {
|
|
@@ -1990,12 +2024,14 @@ function removeVertices(run: Run): PhaseEnd {
|
|
|
1990
2024
|
if (accept(run, 'removal', [v])) return { kind: 'replayed' };
|
|
1991
2025
|
} else {
|
|
1992
2026
|
lastBlock = { refusal: block, what: `removing source vertex ${v}` };
|
|
2027
|
+
if (run.kept !== null) run.kept.attempts.set(v, block);
|
|
1993
2028
|
if (run.hooks?.plant === 'veto-ends-search' && typeof block === 'string' && block.startsWith('MQ_SKINNING_RESIDUAL')) return { kind: 'stuck', constraint: lastName() };
|
|
1994
2029
|
}
|
|
1995
2030
|
}
|
|
1996
2031
|
if (taken === 0) {
|
|
1997
2032
|
// A pass that took nothing left the working mesh as it found it, so a refusal the floor decided is measured
|
|
1998
2033
|
// now in the state it was decided in.
|
|
2034
|
+
if (run.kept !== null) run.kept.final = { pass, refusals: new Map([...run.kept.attempts].map(([v, r]) => [v, refusalText(r)])) };
|
|
1999
2035
|
const kept = run.lines === null ? '' : ", or an open line's end or a vertex two lines list";
|
|
2000
2036
|
const constraint = tried === 0 ? `protect: every surviving source vertex is protected (protect.hull, protect.vertices, protect.edges, protect.regionBoundaries or a weightJump edge${kept})` : lastName();
|
|
2001
2037
|
return { kind: 'stuck', constraint };
|
|
@@ -2688,6 +2724,7 @@ function reduceValidated(
|
|
|
2688
2724
|
skinning: null,
|
|
2689
2725
|
hooks,
|
|
2690
2726
|
lines: null,
|
|
2727
|
+
kept: input.explainKept === undefined ? null : { attempts: new Map(), final: null },
|
|
2691
2728
|
};
|
|
2692
2729
|
run.skinning = skinningRunOf(run, skinningAdmitted);
|
|
2693
2730
|
run.lines = lineRunOf(run);
|
|
@@ -2880,6 +2917,7 @@ function finish(run: Run, effective: EffectiveSettings, sourceCounts: MeshCounts
|
|
|
2880
2917
|
linkedMeshes: input.linkedMeshes,
|
|
2881
2918
|
acceptedAt: run.acceptedAt.map((a) => ({ step: a.step, kind: a.kind, count: a.count, sourceVertices: [...a.sourceVertices] })),
|
|
2882
2919
|
...(retriangulation === undefined ? {} : { retriangulation }),
|
|
2920
|
+
...(run.kept === null ? {} : { kept: keptOf(run, termination) }),
|
|
2883
2921
|
};
|
|
2884
2922
|
const counts = candidate.counts!;
|
|
2885
2923
|
const mesh: ReducedMesh = {
|
|
@@ -2894,6 +2932,62 @@ function finish(run: Run, effective: EffectiveSettings, sourceCounts: MeshCounts
|
|
|
2894
2932
|
return { mesh, report: reportOf(effective, sourceCounts, { ...candidate, changes }, termination) };
|
|
2895
2933
|
}
|
|
2896
2934
|
|
|
2935
|
+
/**
|
|
2936
|
+
* A refusal's reason (issue #1374, `KeptRefusals`): a row's code, or the structural condition the refusal opens with.
|
|
2937
|
+
* Every refusal a step writes opens with one of them; one that does not is a defect here, thrown rather than named.
|
|
2938
|
+
*/
|
|
2939
|
+
function refusalReason(text: string): string {
|
|
2940
|
+
const code = /^MQ_[A-Z0-9_]+/.exec(text);
|
|
2941
|
+
if (code !== null) return code[0];
|
|
2942
|
+
if (text.startsWith('line "')) return 'line';
|
|
2943
|
+
for (const condition of ['retriangulation', 'outline', 'protect (a)', 'weightJump (b)']) if (text.startsWith(`${condition}:`)) return condition;
|
|
2944
|
+
throw new Error(`issue #1374: a refusal opens with no reason KeptRefusals names: "${text.slice(0, 120)}"`);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
/**
|
|
2948
|
+
* Issue #1374: `ReductionChanges.kept` — every kept source vertex with its reason: `protected`, else its refusal in the
|
|
2949
|
+
* last pass when one took nothing, else the termination that ended the run without one.
|
|
2950
|
+
*/
|
|
2951
|
+
function keptOf(run: Run, termination: Termination): KeptRefusals {
|
|
2952
|
+
const { work } = run;
|
|
2953
|
+
const hull = run.input.source.hull;
|
|
2954
|
+
const final = run.kept!.final;
|
|
2955
|
+
const unreached = final !== null ? null : termination.reason === 'budget-exhausted' || termination.reason === 'replayed-to-accepted-step' ? termination.reason : 'no-removal-pass';
|
|
2956
|
+
let refusals = final?.refusals ?? null;
|
|
2957
|
+
if (refusals !== null && run.plant === 'kept-reason-shifted') {
|
|
2958
|
+
const order = [...refusals.keys()].sort((a, b) => a - b);
|
|
2959
|
+
refusals = new Map(order.map((v, i) => [v, refusals!.get(order[(i + 1) % order.length])!]));
|
|
2960
|
+
}
|
|
2961
|
+
const vertices: KeptVertex[] = [];
|
|
2962
|
+
for (let v = 0; v < work.nSource; v++) {
|
|
2963
|
+
if (!work.alive[v]) continue;
|
|
2964
|
+
const boundary = v < hull;
|
|
2965
|
+
if (run.protectedVertices.has(v)) {
|
|
2966
|
+
vertices.push({ vertex: v, boundary, reason: 'protected', refusal: null });
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
if (refusals === null) {
|
|
2970
|
+
vertices.push({ vertex: v, boundary, reason: unreached!, refusal: null });
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
const refusal = refusals.get(v);
|
|
2974
|
+
if (refusal === undefined) throw new Error(`issue #1374: source vertex ${v} is kept and not protected, yet removal pass ${final!.pass}, which took nothing, did not refuse it`);
|
|
2975
|
+
vertices.push({ vertex: v, boundary, reason: refusalReason(refusal), refusal });
|
|
2976
|
+
}
|
|
2977
|
+
const rows = new Map<string, { boundary: number; interior: number }>();
|
|
2978
|
+
for (const k of vertices) {
|
|
2979
|
+
const row = rows.get(k.reason) ?? { boundary: 0, interior: 0 };
|
|
2980
|
+
if (k.boundary) row.boundary++;
|
|
2981
|
+
else row.interior++;
|
|
2982
|
+
rows.set(k.reason, row);
|
|
2983
|
+
}
|
|
2984
|
+
return {
|
|
2985
|
+
pass: final === null ? null : final.pass,
|
|
2986
|
+
rows: [...rows.keys()].sort().map((reason) => ({ reason, ...rows.get(reason)! })),
|
|
2987
|
+
...(run.input.explainKept === 'by-vertex' ? { vertices } : {}),
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2897
2991
|
function reportOf(effective: EffectiveSettings, sourceCounts: MeshCounts | null, candidate: CandidateReport, termination: Termination): MeshQualityReport {
|
|
2898
2992
|
return {
|
|
2899
2993
|
spec: 'mesh-quality-report/1',
|
|
@@ -2924,6 +3018,7 @@ function effectiveOf(input: MeshReductionInput, measured: EffectiveSettings, sou
|
|
|
2924
3018
|
...(input.retriangulate === undefined ? {} : { retriangulate: input.retriangulate }),
|
|
2925
3019
|
...(input.removalOrder === undefined ? {} : { removalOrder: input.removalOrder }),
|
|
2926
3020
|
...(input.lines === undefined ? {} : { lines: input.lines }),
|
|
3021
|
+
...(input.explainKept === undefined ? {} : { explainKept: input.explainKept }),
|
|
2927
3022
|
};
|
|
2928
3023
|
}
|
|
2929
3024
|
|