rig-c 2.31.2 → 2.32.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 +144 -6
- package/src/meshquality.ts +86 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-c",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.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
|
@@ -95,7 +95,11 @@ import {
|
|
|
95
95
|
type MotionReading,
|
|
96
96
|
type MotionRowDetail,
|
|
97
97
|
type MotionSchedule,
|
|
98
|
+
type OverBoundReport,
|
|
99
|
+
type OverBoundRequest,
|
|
100
|
+
type OverBoundRow,
|
|
98
101
|
type ScheduleUsed,
|
|
102
|
+
type TriangleMaxima,
|
|
99
103
|
type SourceMesh,
|
|
100
104
|
type WorstSample,
|
|
101
105
|
} from './meshquality.ts';
|
|
@@ -165,6 +169,17 @@ export interface MotionComparisonInput {
|
|
|
165
169
|
* before any build is read, in the measurement's words. Echoed in `effective` when set, `null` included.
|
|
166
170
|
*/
|
|
167
171
|
motionAmplitude?: MotionAmplitude | null;
|
|
172
|
+
/**
|
|
173
|
+
* Issue #1315: asks each candidate's report for where its motion rows broke — the samples over each declared
|
|
174
|
+
* `MQ_LOCAL_DEFORMATION` bound (the attachment's row and each measured region's), each with the frame of its worst
|
|
175
|
+
* value and the candidate's UV triangle that carries it, at most `maxSamples` per row, worst first, with the count
|
|
176
|
+
* of all of them; and each candidate triangle's worst value of every declared motion row over the selection frames
|
|
177
|
+
* (`triangles`: the over-bound triangles, or `all` with a reading). Nothing else moves: no row, verdict, acceptance
|
|
178
|
+
* or byte of a report without it. Left out, not asked; anything that is not an `OverBoundRequest` in full — `null`
|
|
179
|
+
* included, since asking for nothing is leaving the field out — is refused `COMPARE_INPUT_MISSING` naming the field,
|
|
180
|
+
* before any build is read. Echoed in `effective` when set.
|
|
181
|
+
*/
|
|
182
|
+
overBound?: OverBoundRequest;
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
/**
|
|
@@ -173,7 +188,27 @@ export interface MotionComparisonInput {
|
|
|
173
188
|
* before the builds are read; `null` echoed for a field left out; and the load counted as a required row, so the
|
|
174
189
|
* amplitude moves a verdict. `compareMeshesInMotion` plants none.
|
|
175
190
|
*/
|
|
176
|
-
export type ComparePlant =
|
|
191
|
+
export type ComparePlant =
|
|
192
|
+
| 'amplitude-not-carried'
|
|
193
|
+
| 'null-read-as-left-out'
|
|
194
|
+
| 'amplitude-unvalidated'
|
|
195
|
+
| 'echo-when-unset'
|
|
196
|
+
| 'amplitude-gates-acceptance'
|
|
197
|
+
| OverBoundPlant;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Issue #1315's faults in `overBound`, for the `mesh-compare` suite's negative controls: a listed sample's triangle
|
|
201
|
+
* taken from the reference's carrier rather than the candidate's; the samples listed in domain order rather than worst
|
|
202
|
+
* first; the total counted off the listed samples; the per-triangle table read over every frame rather than the
|
|
203
|
+
* selection's; the field echoed when left out; and the field not validated, so `null` and a malformed request pass.
|
|
204
|
+
*/
|
|
205
|
+
export type OverBoundPlant =
|
|
206
|
+
| 'over-bound-reference-carrier'
|
|
207
|
+
| 'over-bound-domain-order'
|
|
208
|
+
| 'over-bound-total-is-listed'
|
|
209
|
+
| 'over-bound-table-every-frame'
|
|
210
|
+
| 'over-bound-echo-when-unset'
|
|
211
|
+
| 'over-bound-unvalidated';
|
|
177
212
|
|
|
178
213
|
// ---------------------------------------------------------------------------
|
|
179
214
|
// fixed tolerances — the tree's own
|
|
@@ -286,6 +321,14 @@ function validateInput(input: MotionComparisonInput, plant: ComparePlant | null)
|
|
|
286
321
|
throw err;
|
|
287
322
|
}
|
|
288
323
|
}
|
|
324
|
+
if (plant !== 'over-bound-unvalidated' && input.overBound !== undefined) {
|
|
325
|
+
// Issue #1315: a request in full, or the field left out. No count is assumed and no table is chosen for the caller.
|
|
326
|
+
const o: unknown = input.overBound;
|
|
327
|
+
const shape = "an OverBoundRequest { maxSamples, triangles }, or the field left out — null asks for nothing, which is what leaving it out says";
|
|
328
|
+
if (!isObject(o)) missing('overBound', o, shape);
|
|
329
|
+
if (!Number.isInteger(o.maxSamples) || (o.maxSamples as number) < 1) missing('overBound.maxSamples', o.maxSamples, 'a whole number >= 1 — the most over-bound samples listed per row; there is no default');
|
|
330
|
+
if (o.triangles !== 'over-bound' && o.triangles !== 'all') missing('overBound.triangles', o.triangles, "'over-bound' or 'all' — which triangles the per-triangle table lists; there is no default");
|
|
331
|
+
}
|
|
289
332
|
const s = input.schedule;
|
|
290
333
|
if (s === null) return;
|
|
291
334
|
if (!isObject(s)) missing('schedule', s, 'a MotionSchedule, or null for no motion');
|
|
@@ -915,7 +958,9 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
915
958
|
|
|
916
959
|
// --- motion ---------------------------------------------------------------------------------
|
|
917
960
|
const schedule = input.schedule;
|
|
918
|
-
type Motion = { section: EvidenceSection & { schedule: ScheduleUsed }; perFrame: NonNullable<CandidateReport['perFrame']
|
|
961
|
+
type Motion = { section: EvidenceSection & { schedule: ScheduleUsed }; perFrame: NonNullable<CandidateReport['perFrame']>; overBound: OverBoundReport | null } | null;
|
|
962
|
+
// Issue #1315: the request as the caller set it; `?? undefined` only matters under the unvalidated plant.
|
|
963
|
+
const overBound: OverBoundRequest | undefined = input.overBound ?? undefined;
|
|
919
964
|
let motionOf: (b: Build, isReference: boolean) => Motion = () => null;
|
|
920
965
|
if (schedule !== null) {
|
|
921
966
|
const physicsConstraints = reference.doc.constraints.filter((c) => c.kind === 'physics').length;
|
|
@@ -958,6 +1003,9 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
958
1003
|
const posed = isReference ? posedRef : poseBuild(b.doc, order.map((i) => refs[i]), frames, walks);
|
|
959
1004
|
const built: Built[] = [];
|
|
960
1005
|
const perFrame: NonNullable<CandidateReport['perFrame']> = [];
|
|
1006
|
+
// Issue #1315: kept for a candidate only, and only when asked — a call without the field does none of this.
|
|
1007
|
+
const ask = isReference ? undefined : overBound;
|
|
1008
|
+
const located: OverBoundReport | null = ask === undefined ? null : { selectionFrames: frames.filter((f) => f.ref.role === 'selection').length, rows: [], triangles: [] };
|
|
961
1009
|
order.forEach((i, k) => {
|
|
962
1010
|
const a = input.attachments[i];
|
|
963
1011
|
const ref = refs[i];
|
|
@@ -996,6 +1044,18 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
996
1044
|
const squash: Reading[] = [];
|
|
997
1045
|
const inversion: Reading[] = [];
|
|
998
1046
|
const folds: Array<{ triangle: number; frame: string }> = [];
|
|
1047
|
+
// Issue #1315: each sample's worst distance over every frame and the frame of it (first in walk order); each
|
|
1048
|
+
// triangle's worst reading of each row over the selection frames. Bookkeeping over values computed below anyway.
|
|
1049
|
+
const nTri = mesh.triangles.length / 3;
|
|
1050
|
+
const track = located !== null;
|
|
1051
|
+
const sampleWorst = new Float64Array(track ? samples.length : 0).fill(-1);
|
|
1052
|
+
const sampleFrame = new Int32Array(track ? samples.length : 0).fill(-1);
|
|
1053
|
+
const triLocal = new Float64Array(track ? nTri : 0).fill(-1);
|
|
1054
|
+
const triHi = new Float64Array(track ? nTri : 0).fill(-Infinity);
|
|
1055
|
+
const triLo = new Float64Array(track ? nTri : 0).fill(Infinity);
|
|
1056
|
+
const triStretchRead = new Uint8Array(track ? nTri : 0);
|
|
1057
|
+
const triReversed = new Int32Array(track ? nTri : 0);
|
|
1058
|
+
const triInversionRead = new Uint8Array(track ? nTri : 0);
|
|
999
1059
|
const setupWorld = posed.setup[k];
|
|
1000
1060
|
const setupAreas = setupWorld === null ? [] : triangleAreas(setupWorld, mesh.triangles);
|
|
1001
1061
|
const setupBand = setupWorld === null ? 0 : areaBand(setupAreas, setupWorld);
|
|
@@ -1007,6 +1067,7 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1007
1067
|
notDrawn.push(f.ref.id);
|
|
1008
1068
|
return;
|
|
1009
1069
|
}
|
|
1070
|
+
const selected = track && (f.ref.role === 'selection' || plant === 'over-bound-table-every-frame');
|
|
1010
1071
|
// Local deformation: the carried points, sample by sample.
|
|
1011
1072
|
let worst = -1;
|
|
1012
1073
|
let worstAt = -1;
|
|
@@ -1020,6 +1081,16 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1020
1081
|
worst = d;
|
|
1021
1082
|
worstAt = j;
|
|
1022
1083
|
}
|
|
1084
|
+
if (track) {
|
|
1085
|
+
if (d > sampleWorst[j]) {
|
|
1086
|
+
sampleWorst[j] = d;
|
|
1087
|
+
sampleFrame[j] = fi;
|
|
1088
|
+
}
|
|
1089
|
+
if (selected) {
|
|
1090
|
+
const t = candCarrier[j]!.triangle;
|
|
1091
|
+
if (d > triLocal[t]) triLocal[t] = d;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1023
1094
|
for (let r = 0; r < a.regions.length; r++) {
|
|
1024
1095
|
if (inRegion[r][j] && d > regionWorst[r].d) regionWorst[r] = { d, j };
|
|
1025
1096
|
}
|
|
@@ -1046,9 +1117,16 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1046
1117
|
if (sv !== null) {
|
|
1047
1118
|
if (hi === null || sv.max > hi.value) hi = { frame: fi, value: sv.max, at: { triangle: t } };
|
|
1048
1119
|
if (lo === null || sv.min < lo.value) lo = { frame: fi, value: sv.min, at: { triangle: t } };
|
|
1120
|
+
if (selected) {
|
|
1121
|
+
triStretchRead[t] = 1;
|
|
1122
|
+
if (sv.max > triHi[t]) triHi[t] = sv.max;
|
|
1123
|
+
if (sv.min < triLo[t]) triLo[t] = sv.min;
|
|
1124
|
+
}
|
|
1049
1125
|
}
|
|
1126
|
+
if (selected) triInversionRead[t] = 1;
|
|
1050
1127
|
if (Math.abs(after[t]) <= band) continue;
|
|
1051
1128
|
if (Math.sign(setupAreas[t]) !== Math.sign(after[t])) {
|
|
1129
|
+
if (selected) triReversed[t]++;
|
|
1052
1130
|
reversed++;
|
|
1053
1131
|
if (firstReversed === -1) firstReversed = t;
|
|
1054
1132
|
if (folding.has(ref.slot)) folds.push({ triangle: t, frame: f.ref.id });
|
|
@@ -1102,6 +1180,60 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1102
1180
|
// A slot `deformMayFold` names: the row holds no bound (A39 exempts it), keeps its count rather than zeroing it, and lists every fold.
|
|
1103
1181
|
built.push(motionRow(plans.inversion, inversion, frames, schedule, notDrawn, folding.has(ref.slot) ? { folds, degenerateAtSetup } : { degenerateAtSetup }));
|
|
1104
1182
|
}
|
|
1183
|
+
if (located !== null && ask !== undefined) {
|
|
1184
|
+
// Issue #1315: the samples over the local-deformation bound, per measured row, and the per-triangle tables.
|
|
1185
|
+
const localBound = { op: '<=' as const, value: input.bounds.maxLocalDeformation };
|
|
1186
|
+
const listed = (region: string | null, inside: readonly boolean[] | null): OverBoundRow => {
|
|
1187
|
+
const over: Array<{ j: number; v: number }> = [];
|
|
1188
|
+
for (let j = 0; j < samples.length; j++) {
|
|
1189
|
+
if (sampleFrame[j] < 0 || (inside !== null && !inside[j])) continue;
|
|
1190
|
+
const v = r6(sampleWorst[j]);
|
|
1191
|
+
if (judged(v, localBound) === 'fail') over.push({ j, v });
|
|
1192
|
+
}
|
|
1193
|
+
if (plant !== 'over-bound-domain-order') over.sort((p, q) => q.v - p.v || p.j - q.j);
|
|
1194
|
+
const kept = over.slice(0, ask.maxSamples);
|
|
1195
|
+
return {
|
|
1196
|
+
code: 'MQ_LOCAL_DEFORMATION',
|
|
1197
|
+
attachment: ref,
|
|
1198
|
+
region,
|
|
1199
|
+
bound: localBound,
|
|
1200
|
+
total: plant === 'over-bound-total-is-listed' ? kept.length : over.length,
|
|
1201
|
+
samples: kept.map(({ j, v }) => {
|
|
1202
|
+
const s = samples[j];
|
|
1203
|
+
const c = plant === 'over-bound-reference-carrier' ? refCarrier[j]! : candCarrier[j]!;
|
|
1204
|
+
return {
|
|
1205
|
+
at: s.pixel !== null ? { pixel: s.pixel, uv: s.uv } : { vertex: s.vertex ?? -1, uv: s.uv },
|
|
1206
|
+
frame: frames[sampleFrame[j]].ref,
|
|
1207
|
+
value: v,
|
|
1208
|
+
triangle: c.triangle,
|
|
1209
|
+
corners: [c.corners[0], c.corners[1], c.corners[2]],
|
|
1210
|
+
};
|
|
1211
|
+
}),
|
|
1212
|
+
};
|
|
1213
|
+
};
|
|
1214
|
+
if (art >= a.minArtSamples) located.rows.push(listed(null, null));
|
|
1215
|
+
const measuredRegions = a.regions
|
|
1216
|
+
.map((r, ri) => ({ name: r.name, ri }))
|
|
1217
|
+
.filter(({ ri }) => artIn(inRegion[ri]) >= a.regions[ri].minArtSamples)
|
|
1218
|
+
.sort((p, q) => (p.name < q.name ? -1 : p.name > q.name ? 1 : 0));
|
|
1219
|
+
for (const { name, ri } of measuredRegions) located.rows.push(listed(name, inRegion[ri]));
|
|
1220
|
+
const table = (code: string, bound: { op: '<=' | '>='; value: number }, read: (t: number) => number | null): TriangleMaxima => {
|
|
1221
|
+
const values: Array<{ triangle: number; value: number }> = [];
|
|
1222
|
+
for (let t = 0; t < nTri; t++) {
|
|
1223
|
+
const raw = read(t);
|
|
1224
|
+
if (raw === null) continue;
|
|
1225
|
+
const value = r6(raw);
|
|
1226
|
+
if (ask.triangles === 'all' || judged(value, bound) === 'fail') values.push({ triangle: t, value });
|
|
1227
|
+
}
|
|
1228
|
+
values.sort((p, q) => (bound.op === '<=' ? q.value - p.value : p.value - q.value) || p.triangle - q.triangle);
|
|
1229
|
+
return { code, attachment: ref, bound, values };
|
|
1230
|
+
};
|
|
1231
|
+
// Codes in the rows' order (ROW_ORDER: codes as strings); a row with no declared bound has no table.
|
|
1232
|
+
if (setupWorld !== null && plans.inversion.bound !== null) located.triangles.push(table('MQ_INVERSION', plans.inversion.bound, (t) => (triInversionRead[t] === 1 ? triReversed[t] : null)));
|
|
1233
|
+
if (art >= a.minArtSamples) located.triangles.push(table('MQ_LOCAL_DEFORMATION', localBound, (t) => (triLocal[t] >= 0 ? triLocal[t] : null)));
|
|
1234
|
+
if (setupWorld !== null && plans.squash.bound !== null) located.triangles.push(table('MQ_SQUASH', plans.squash.bound, (t) => (triStretchRead[t] === 1 ? triLo[t] : null)));
|
|
1235
|
+
if (setupWorld !== null && plans.stretch.bound !== null) located.triangles.push(table('MQ_STRETCH', plans.stretch.bound, (t) => (triStretchRead[t] === 1 ? triHi[t] : null)));
|
|
1236
|
+
}
|
|
1105
1237
|
// P7's opt-in table: each attachment-level row's value at every frame walked, null where none was taken.
|
|
1106
1238
|
const label = (code: string): string => (order.length > 1 ? `${code}[${nameOf(ref)}]` : code);
|
|
1107
1239
|
const table: Array<[string, Reading[]]> = [
|
|
@@ -1124,17 +1256,18 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1124
1256
|
const ref = refs[order[k]];
|
|
1125
1257
|
grouped.push(...built.filter((x) => x.row.object.attachment === ref).sort((p, q) => ROW_ORDER(p.row, q.row)));
|
|
1126
1258
|
}
|
|
1127
|
-
return { section: { ...sectionOf(grouped), schedule: scheduleUsed }, perFrame };
|
|
1259
|
+
return { section: { ...sectionOf(grouped), schedule: scheduleUsed }, perFrame, overBound: located };
|
|
1128
1260
|
};
|
|
1129
1261
|
}
|
|
1130
1262
|
const referenceMotion = motionOf(reference, true);
|
|
1131
1263
|
const candidateMotions = candidates.map((c) => motionOf(c, false));
|
|
1132
|
-
const reportOf = (b: Build, geometry: { geometry: EvidenceSection | null; counts: MeshCounts | null }, measured: Motion): CandidateReport => {
|
|
1264
|
+
const reportOf = (b: Build, geometry: { geometry: EvidenceSection | null; counts: MeshCounts | null }, measured: Motion, isCandidate: boolean): CandidateReport => {
|
|
1133
1265
|
const motion = measured === null ? null : measured.section;
|
|
1134
1266
|
// P6: the geometry verdict pass (every required row measured and passing), and — when motion is required — the motion verdict pass.
|
|
1135
1267
|
const accepted = geometry.geometry !== null && geometry.geometry.verdict === 'pass' && (!input.motionRequired || (motion !== null && motion.verdict === 'pass'));
|
|
1136
1268
|
const out: CandidateReport = { id: b.id, counts: geometry.counts, geometry: geometry.geometry, motion, accepted };
|
|
1137
1269
|
if (input.perFrame && measured !== null) out.perFrame = measured.perFrame;
|
|
1270
|
+
if (isCandidate && overBound !== undefined) out.overBound = measured === null ? null : measured.overBound;
|
|
1138
1271
|
return out;
|
|
1139
1272
|
};
|
|
1140
1273
|
const effective: EffectiveSettings = {
|
|
@@ -1163,6 +1296,11 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1163
1296
|
schedule,
|
|
1164
1297
|
budget: null,
|
|
1165
1298
|
...(amplitude === undefined ? (plant === 'echo-when-unset' ? { motionAmplitude: null } : {}) : { motionAmplitude: amplitude }),
|
|
1299
|
+
...(overBound === undefined
|
|
1300
|
+
? plant === 'over-bound-echo-when-unset'
|
|
1301
|
+
? { overBound: { maxSamples: 1, triangles: 'over-bound' as const } }
|
|
1302
|
+
: {}
|
|
1303
|
+
: { overBound: { maxSamples: overBound.maxSamples, triangles: overBound.triangles } }),
|
|
1166
1304
|
};
|
|
1167
1305
|
return {
|
|
1168
1306
|
spec: MESH_QUALITY_REPORT_SPEC,
|
|
@@ -1171,8 +1309,8 @@ function compareMeshesInMotionWith(input: MotionComparisonInput, plant: CompareP
|
|
|
1171
1309
|
poser: schedule === null ? null : { kind: 'core', rigcVersion: readVersion() },
|
|
1172
1310
|
motionRequired: input.motionRequired,
|
|
1173
1311
|
sourceCounts: refGeometry.counts,
|
|
1174
|
-
reference: reportOf(reference, refGeometry, referenceMotion),
|
|
1175
|
-
candidates: candidates.map((c, i) => reportOf(c, candGeometry[i], candidateMotions[i])),
|
|
1312
|
+
reference: reportOf(reference, refGeometry, referenceMotion, false),
|
|
1313
|
+
candidates: candidates.map((c, i) => reportOf(c, candGeometry[i], candidateMotions[i], true)),
|
|
1176
1314
|
termination: null,
|
|
1177
1315
|
};
|
|
1178
1316
|
}
|
package/src/meshquality.ts
CHANGED
|
@@ -547,10 +547,69 @@ export interface CandidateReport {
|
|
|
547
547
|
accepted: boolean;
|
|
548
548
|
/** Opt-in (P7): every row's value at every frame. Absent unless asked for. */
|
|
549
549
|
perFrame?: Array<{ code: string; frame: string; value: number | null }>;
|
|
550
|
+
/**
|
|
551
|
+
* Issue #1315, a comparison's candidate only: present exactly when the input set `overBound` — the samples over each
|
|
552
|
+
* declared motion bound with the candidate triangle that carries them, and each triangle's worst value of each motion
|
|
553
|
+
* row over the selection frames. Null when no motion section was measured (no schedule). Absent otherwise.
|
|
554
|
+
*/
|
|
555
|
+
overBound?: OverBoundReport | null;
|
|
550
556
|
/** A `reduce` whose result exists: what the operation changed. Absent on a `measure` and when no mesh is returned. */
|
|
551
557
|
changes?: ReductionChanges;
|
|
552
558
|
}
|
|
553
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Issue #1315: what `MotionComparisonInput.overBound` asks for — how many over-bound samples each row lists at most,
|
|
562
|
+
* worst first (a whole number, 1 or more; no default), and which triangles the per-triangle table lists: those over
|
|
563
|
+
* the row's bound, or every triangle that has a reading. Echoed in `effective` when set.
|
|
564
|
+
*/
|
|
565
|
+
export interface OverBoundRequest {
|
|
566
|
+
maxSamples: number;
|
|
567
|
+
triangles: 'over-bound' | 'all';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* One sample over a motion bound (issue #1315): the sample by §3's id — an art pixel of the mask grid, or a reference
|
|
572
|
+
* hull vertex — with its UV; the frame its worst value over every frame walked was taken at (the first such frame in
|
|
573
|
+
* walk order), whose `role` says selection, held out or baseline; that value; and the candidate's UV triangle that
|
|
574
|
+
* carries the sample, by index and by its three vertex indices.
|
|
575
|
+
*/
|
|
576
|
+
export interface OverBoundSample {
|
|
577
|
+
at: { pixel?: [number, number]; vertex?: number; uv: [number, number] };
|
|
578
|
+
frame: FrameRef;
|
|
579
|
+
value: number;
|
|
580
|
+
triangle: number;
|
|
581
|
+
corners: [number, number, number];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** One sample-taken motion row with a declared bound (issue #1315): every sample over it counted, the worst listed. */
|
|
585
|
+
export interface OverBoundRow {
|
|
586
|
+
code: string;
|
|
587
|
+
attachment: AttachmentRef;
|
|
588
|
+
region: string | null;
|
|
589
|
+
bound: { op: '<=' | '>='; value: number };
|
|
590
|
+
/** Samples whose worst value over every frame walked fails the bound — all of them, whether or not they are listed. */
|
|
591
|
+
total: number;
|
|
592
|
+
/** At most `maxSamples` of them: worst first, then in §3's domain order (art pixels row by row, then hull vertices). */
|
|
593
|
+
samples: OverBoundSample[];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Each candidate triangle's worst value of one motion row over the selection frames (issue #1315). */
|
|
597
|
+
export interface TriangleMaxima {
|
|
598
|
+
code: string;
|
|
599
|
+
attachment: AttachmentRef;
|
|
600
|
+
bound: { op: '<=' | '>='; value: number };
|
|
601
|
+
/** Worst first, then triangle index; over-bound triangles only, or every triangle with a reading, as asked. */
|
|
602
|
+
values: Array<{ triangle: number; value: number }>;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Issue #1315's report block on a compared candidate. */
|
|
606
|
+
export interface OverBoundReport {
|
|
607
|
+
/** How many frames the selection named and the schedule walked — the frames `triangles` reads; 0 leaves every table empty. */
|
|
608
|
+
selectionFrames: number;
|
|
609
|
+
rows: OverBoundRow[];
|
|
610
|
+
triangles: TriangleMaxima[];
|
|
611
|
+
}
|
|
612
|
+
|
|
554
613
|
/**
|
|
555
614
|
* What a reduction changed, counted rather than described — the figures the
|
|
556
615
|
* contract says are reported (§6, P18 and P19) and that no geometry row carries.
|
|
@@ -635,6 +694,8 @@ export interface EffectiveSettings {
|
|
|
635
694
|
removalOrder?: 'deformation-load';
|
|
636
695
|
/** A measurement's `skinning`, echoed when the input set it — `null` included (issue #1294): the source by id and digest. Absent otherwise. */
|
|
637
696
|
skinning?: SkinningEcho | null;
|
|
697
|
+
/** A comparison's `overBound`, echoed when the input set it (issue #1315); absent otherwise. */
|
|
698
|
+
overBound?: OverBoundRequest;
|
|
638
699
|
}
|
|
639
700
|
|
|
640
701
|
export interface MeshCounts {
|
|
@@ -2436,6 +2497,7 @@ function effectiveJson(e: EffectiveSettings): Json {
|
|
|
2436
2497
|
...(e.retriangulate === undefined ? {} : { retriangulate: e.retriangulate }),
|
|
2437
2498
|
...(e.removalOrder === undefined ? {} : { removalOrder: e.removalOrder }),
|
|
2438
2499
|
...(e.skinning === undefined ? {} : { skinning: skinningEchoJson(e.skinning) }),
|
|
2500
|
+
...(e.overBound === undefined ? {} : { overBound: { maxSamples: e.overBound.maxSamples, triangles: e.overBound.triangles } }),
|
|
2439
2501
|
};
|
|
2440
2502
|
}
|
|
2441
2503
|
|
|
@@ -2557,6 +2619,7 @@ function candidateJson(c: CandidateReport): Json {
|
|
|
2557
2619
|
accepted: c.accepted,
|
|
2558
2620
|
};
|
|
2559
2621
|
if (c.perFrame !== undefined) out.perFrame = c.perFrame.map((p) => ({ code: p.code, frame: p.frame, value: p.value }));
|
|
2622
|
+
if (c.overBound !== undefined) out.overBound = overBoundJson(c.overBound);
|
|
2560
2623
|
if (c.changes !== undefined) {
|
|
2561
2624
|
const k = c.changes;
|
|
2562
2625
|
out.changes = {
|
|
@@ -2585,6 +2648,29 @@ function candidateJson(c: CandidateReport): Json {
|
|
|
2585
2648
|
return out;
|
|
2586
2649
|
}
|
|
2587
2650
|
|
|
2651
|
+
function overBoundJson(o: OverBoundReport | null): Json {
|
|
2652
|
+
if (o === null) return null;
|
|
2653
|
+
const boundJson = (b: { op: '<=' | '>='; value: number }): Json => ({ op: b.op, value: b.value });
|
|
2654
|
+
return {
|
|
2655
|
+
selectionFrames: o.selectionFrames,
|
|
2656
|
+
rows: o.rows.map((r) => ({
|
|
2657
|
+
code: r.code,
|
|
2658
|
+
attachment: attachmentJson(r.attachment),
|
|
2659
|
+
region: r.region,
|
|
2660
|
+
bound: boundJson(r.bound),
|
|
2661
|
+
total: r.total,
|
|
2662
|
+
samples: r.samples.map((x) => {
|
|
2663
|
+
const at: { [key: string]: Json } = {};
|
|
2664
|
+
if (x.at.pixel !== undefined) at.pixel = pair(x.at.pixel);
|
|
2665
|
+
if (x.at.vertex !== undefined) at.vertex = x.at.vertex;
|
|
2666
|
+
at.uv = pair(x.at.uv);
|
|
2667
|
+
return { at, frame: frameRefJson(x.frame), value: x.value, triangle: x.triangle, corners: [x.corners[0], x.corners[1], x.corners[2]] };
|
|
2668
|
+
}),
|
|
2669
|
+
})),
|
|
2670
|
+
triangles: o.triangles.map((t) => ({ code: t.code, attachment: attachmentJson(t.attachment), bound: boundJson(t.bound), values: t.values.map((v) => ({ triangle: v.triangle, value: v.value })) })),
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2588
2674
|
const notReachedJson = (s: StopNotReached | undefined): { [key: string]: Json } =>
|
|
2589
2675
|
s === undefined ? {} : { stopAfterAccepted: { requested: s.requested, acceptedSteps: s.acceptedSteps } };
|
|
2590
2676
|
|