spine-rigc 0.18.0 → 0.19.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/cli.ts +4 -1
- package/docs/AUTHORING.md +254 -30
- package/package.json +1 -1
- package/src/compile.ts +215 -14
- package/src/deformmeasure.ts +583 -38
- package/src/validate.ts +81 -1
package/src/validate.ts
CHANGED
|
@@ -45,7 +45,14 @@ import {
|
|
|
45
45
|
// A19 needs the DECODED page, not its header, to measure one region's own
|
|
46
46
|
// rectangle on a shared page.
|
|
47
47
|
import { readPlate } from '../tools/plate.ts';
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
surveyDeformKeys,
|
|
50
|
+
unreachableWhy,
|
|
51
|
+
type DeformDialDispute,
|
|
52
|
+
type DeformDialTie,
|
|
53
|
+
type DeformReach,
|
|
54
|
+
type DialSpan,
|
|
55
|
+
} from './deformmeasure.ts';
|
|
49
56
|
import { colourTypeName, readPngInfo } from './png.ts';
|
|
50
57
|
import { CHANNELS_BY_KIND, KEY_TIME_EPSILON, walkTimelines } from './timelines.ts';
|
|
51
58
|
import type { RigInfo } from './types.ts';
|
|
@@ -238,6 +245,49 @@ function frameClause(reach: DeformReach): string {
|
|
|
238
245
|
return reach.kind === 'slider' ? ` (applied by slider "${reach.slider}", not played on a track)` : '';
|
|
239
246
|
}
|
|
240
247
|
|
|
248
|
+
/**
|
|
249
|
+
* A dial's reach as A39's stats line spells it, or `none` when it can select no
|
|
250
|
+
* part of its animation at all.
|
|
251
|
+
*
|
|
252
|
+
* Six decimals because a key time has six: a reach whose end is printed coarser
|
|
253
|
+
* than the times it is compared against cannot be read against them.
|
|
254
|
+
*/
|
|
255
|
+
function dialSpanText(span: DialSpan | null): string {
|
|
256
|
+
return span === null ? 'none' : `${span.lo.toFixed(6)}..${span.hi.toFixed(6)}s`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* One disputed dial on A39's stats line — both answers, both reaches, and the
|
|
261
|
+
* frames the artifact's answer could not have posed (issue #427).
|
|
262
|
+
*
|
|
263
|
+
* ⭐ **`outside:` is always there, `none` included.** The comparison it reports is
|
|
264
|
+
* what decides whether a disagreement changed anything the survey measured, and a
|
|
265
|
+
* comparison that came out equal must not look like one nobody made. It is the
|
|
266
|
+
* difference between "both answers pose the same frames, so the disagreement is a
|
|
267
|
+
* fact about rigc and not about this rig" and "these key times were surveyed
|
|
268
|
+
* through a field the skeleton does not name and no settable value of the one it
|
|
269
|
+
* does reaches them".
|
|
270
|
+
*
|
|
271
|
+
* ⚠️ No spaces anywhere in it: the stats line is `k=v` pairs joined by spaces, and
|
|
272
|
+
* a value with a space in it turns one reading into two.
|
|
273
|
+
*/
|
|
274
|
+
function dialDisputeText(dispute: DeformDialDispute): string {
|
|
275
|
+
const stated = dispute.statedResponse === null ? 'unmeasured' : dispute.statedResponse.toExponential(3);
|
|
276
|
+
return (
|
|
277
|
+
`${dispute.slider}|artifact:${dispute.bone}.${dispute.stated}@${stated}` +
|
|
278
|
+
`|reaches:${dialSpanText(dispute.statedReach)}` +
|
|
279
|
+
`|probe:${dispute.bone}.${dispute.drive}@${dispute.driveResponse.toExponential(3)}` +
|
|
280
|
+
`|reaches:${dialSpanText(dispute.driveReach)}` +
|
|
281
|
+
`|outside:${dispute.outside.length === 0 ? 'none' : dispute.outside.map((t) => `${t.toFixed(6)}s`).join('+')}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** One tied dial on the same line, in the shape that cannot be read as a dispute. */
|
|
286
|
+
function dialTieText(tie: DeformDialTie): string {
|
|
287
|
+
const rivals = tie.rivals.map((r) => `${tie.bone}.${r.field}@${r.response.toExponential(3)}`).join('+');
|
|
288
|
+
return `${tie.slider}|artifact:${tie.bone}.${tie.drive}@${tie.driveResponse.toExponential(3)}|tied:${rivals}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
241
291
|
const FRAME = 1 / 60;
|
|
242
292
|
const STEP_FRAMES = 120;
|
|
243
293
|
|
|
@@ -1668,6 +1718,36 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
1668
1718
|
);
|
|
1669
1719
|
}
|
|
1670
1720
|
const survey = surveyDeformKeys(data, new Set(input.rig.deformMayFold));
|
|
1721
|
+
// 🚨 Which dial was turned, when rigc's two halves did not simply agree
|
|
1722
|
+
// about that — and BEFORE any of the returns below, because every one of
|
|
1723
|
+
// them is a run that posed frames through this dial (issue #427).
|
|
1724
|
+
//
|
|
1725
|
+
// The verdict used to live in `DeformReach.label`, which `explain` prints
|
|
1726
|
+
// and nothing else does, so a `build`-only run — the normal loop, and the
|
|
1727
|
+
// one an agent that cannot see the rig actually runs — never learned that
|
|
1728
|
+
// the artifact and the probe named different fields.
|
|
1729
|
+
//
|
|
1730
|
+
// ⛔ Not a refusal, and the measurement rather than taste is why (#427).
|
|
1731
|
+
// The frames this survey posed were each checked against `SliderPose.time`
|
|
1732
|
+
// by the runtime itself, so a disagreement cannot make it pose one that
|
|
1733
|
+
// does not happen; and the field it drives is the largest response the
|
|
1734
|
+
// probe found, so it cannot make it miss one that does. What a
|
|
1735
|
+
// disagreement CAN do is leave the rig naming a property no settable value
|
|
1736
|
+
// of turns far enough — which is what `outside` measures and what an
|
|
1737
|
+
// author can act on. A refusal would refuse a rig spine-core poses
|
|
1738
|
+
// correctly at every key, with no edit that would make it green.
|
|
1739
|
+
//
|
|
1740
|
+
// 🔒 A tie is not a disagreement and gets a line that cannot be read as
|
|
1741
|
+
// one: there the probe named no field, the artifact broke the tie, and the
|
|
1742
|
+
// parent-45° geometry that reaches it is legitimate.
|
|
1743
|
+
if (survey.dialTies.length) {
|
|
1744
|
+
stats.deformDialsTied = survey.dialTies.length;
|
|
1745
|
+
stats.deformDialTied = survey.dialTies.map(dialTieText).join(',');
|
|
1746
|
+
}
|
|
1747
|
+
if (survey.dialDisputes.length) {
|
|
1748
|
+
stats.deformDialsDisagreed = survey.dialDisputes.length;
|
|
1749
|
+
stats.deformDialDisagreed = survey.dialDisputes.map(dialDisputeText).join(',');
|
|
1750
|
+
}
|
|
1671
1751
|
/** A key this rule is refusing, by the triple that identifies it. */
|
|
1672
1752
|
const refusedKey = new Set<string>();
|
|
1673
1753
|
for (const key of survey.keys) {
|