spine-rigc 1.9.4 → 2.0.1

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/src/validate.ts CHANGED
@@ -85,6 +85,7 @@ import type { SlotColourFacts, SlotTimelines } from './assertions/facts/slot_col
85
85
  import { a03RegionWidthHeightFinite } from './assertions/bodies/a03.ts';
86
86
  import { a11NoClippingAttachments } from './assertions/bodies/a11.ts';
87
87
  import { a17AtlasPageFilesExist } from './assertions/bodies/a17.ts';
88
+ import { a18DeterministicEmit } from './assertions/bodies/a18.ts';
88
89
  import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from './assertions/bodies/a45.ts';
89
90
  import { attachmentRegionLookups, type AttachmentRegionJoin } from './assertions/region_lookups.ts';
90
91
  import { attachmentRegionJoins } from './region_joins.ts';
@@ -182,20 +183,9 @@ export type { Failure } from './assertions/harness.ts';
182
183
  */
183
184
  export type ValidateProfile = AssertionProfile;
184
185
 
185
- export const VALIDATE_PROFILES: readonly ValidateProfile[] = ['spine', 'spine-html'];
186
-
187
- /**
188
- * What the CLI uses when `--profile` is absent — and ONLY the CLI. This is not
189
- * `validate()`'s default; that function has none (above).
190
- *
191
- * `spine` since issue #221. The published package's pitch is "the output imports
192
- * into the Spine editor", which is exactly the question `spine` asks, and a
193
- * stranger's first build was being judged instead against one renderer's policy
194
- * and one project's canvas budget — 14 rules they have no stake in, with the
195
- * escape hatch documented only in prose. Defaults beat prose. `spine-html` is
196
- * still one flag away, and every report names the profile that judged it.
197
- */
198
- export const CLI_DEFAULT_PROFILE: ValidateProfile = 'spine';
186
+ // The profiles, the CLI's default and the report printer — `./assertions/report.ts` since issue #1060, so the
187
+ // entry that links none of spine-core gates and prints with them; every one is re-exported from here.
188
+ export { CLI_DEFAULT_PROFILE, reportLines, VALIDATE_PROFILES } from './assertions/report.ts';
199
189
 
200
190
  // What kind of rule each assertion is — `./assertions/kinds.ts` since issue
201
191
  // #1025, where the model side's harness reads the same table.
@@ -842,19 +832,6 @@ export function timelineAddCells(data: ReturnType<SkeletonJson['readSkeletonData
842
832
  return cells;
843
833
  }
844
834
 
845
- /**
846
- * Where two texts first differ, as A18 names it: the 1-based line and each
847
- * side's line, trimmed and cut to 120 characters.
848
- */
849
- function firstDifferingLine(first: string, second: string): string {
850
- const a = first.split('\n');
851
- const b = second.split('\n');
852
- let i = 0;
853
- while (i < a.length && i < b.length && a[i] === b[i]) i += 1;
854
- const cut = (line: string | undefined): string => (line === undefined ? '(end of text)' : JSON.stringify(line.trim().slice(0, 120)));
855
- return `${i + 1}: ${cut(a[i])} first, ${cut(b[i])} second`;
856
- }
857
-
858
835
  /**
859
836
  * The runtime's supply of `SkinEntryFacts` (issue #1025): every skin of the
860
837
  * loaded skeleton in the loaded order, and each skin's entries as
@@ -3348,28 +3325,19 @@ export function validate(input: ValidateInput): ValidateReport {
3348
3325
  check('A30_STROKE_WITHIN_CAP_CONTAINMENT', () => a30StrokeWithinCapContainment(verdicts, rawBoneTimelines(raw), input));
3349
3326
 
3350
3327
  // --- A18: determinism ----------------------------------------------------
3328
+ // The body is `./assertions/bodies/a18.ts` (issue #1060): the model document's clause, which the model side
3329
+ // runs too, and the Spine pair's two clauses handed to it as this side's encoding findings, in their order.
3330
+ // Same vacuous-pass trap as A09: re-gating artifacts already on disk hands this assertion no second compile.
3351
3331
  check('A18_DETERMINISTIC_EMIT', () => {
3352
- // Same vacuous-pass trap as A09: re-gating artifacts already on disk hands
3353
- // this assertion no second compile, and there is nothing determinate about
3354
- // a comparison that never ran.
3355
- if (!input.reEmit) {
3356
- return skip('A18_DETERMINISTIC_EMIT', 'no second compile to compare against (re-gating artifacts on disk)');
3357
- }
3358
- if (input.reEmit.skeletonText !== input.skeletonText) {
3359
- fail('A18_DETERMINISTIC_EMIT', 'recompiling produced a different skeleton.json');
3360
- }
3361
- if (input.reEmit.atlasText !== input.atlasText) {
3362
- fail('A18_DETERMINISTIC_EMIT', 'recompiling produced a different skeleton.atlas');
3363
- }
3364
- // The model document is compared like the Spine pair (issue #922): it is
3365
- // written beside them, and a map iterated in an order nothing fixed would
3366
- // reach it before it reached either of them — the document writes every
3367
- // `Map` of the model as an array in the map's order.
3368
- if (input.modelText === undefined) {
3369
- fail('A18_DETERMINISTIC_EMIT', 'a second compile was handed over with no first model document to compare its skeleton.model.json against');
3370
- } else if (input.reEmit.modelText !== input.modelText) {
3371
- fail('A18_DETERMINISTIC_EMIT', `recompiling produced a different skeleton.model.json (first differing line ${firstDifferingLine(input.modelText, input.reEmit.modelText)})`);
3372
- }
3332
+ const again = input.reEmit;
3333
+ const encoding =
3334
+ again === undefined
3335
+ ? []
3336
+ : [
3337
+ ...(again.skeletonText !== input.skeletonText ? ['recompiling produced a different skeleton.json'] : []),
3338
+ ...(again.atlasText !== input.atlasText ? ['recompiling produced a different skeleton.atlas'] : []),
3339
+ ];
3340
+ a18DeterministicEmit(verdicts, { again: again !== undefined, encoding, first: input.modelText, second: again?.modelText ?? '' });
3373
3341
  });
3374
3342
 
3375
3343
  // --- every assertion leaves a row ----------------------------------------
@@ -3444,44 +3412,6 @@ function meshWeightsOf(mesh: MeshAttachment): Array<Array<{ bone: number; weight
3444
3412
  return out;
3445
3413
  }
3446
3414
 
3447
- export function reportLines(report: ValidateReport): string[] {
3448
- const lines: string[] = [];
3449
- // The profile goes FIRST and names what it left out. A report that says
3450
- // "green" without saying which rulebook produced it is the one thing this
3451
- // switch could make worse than no switch: `--profile spine` green means
3452
- // "valid Spine", never "passes the renderer policy".
3453
- const renderer = report.profileSkipped.filter((p) => p.kind === 'renderer').length;
3454
- const archetype = report.profileSkipped.filter((p) => p.kind === 'archetype').length;
3455
- lines.push(
3456
- report.profileSkipped.length === 0
3457
- ? ` .. profile ${report.profile} — every assertion applies`
3458
- : ` .. profile ${report.profile} — ${renderer} renderer-policy and ${archetype} archetype assertion(s) do not apply`,
3459
- );
3460
- for (const name of report.passed) lines.push(` PASS ${name}`);
3461
- for (const s of report.skipped) lines.push(` SKIP ${s.assertion}: ${s.reason}`);
3462
- for (const p of report.profileSkipped) lines.push(` PROF ${p.assertion}: ${p.kind} rule, not in profile "${report.profile}"`);
3463
- for (const f of report.failures) lines.push(` FAIL ${f.assertion}: ${f.detail}`);
3464
- // How many of them MEASURED anything, which is the figure the rows above do
3465
- // not hand a reader (issue #568). Counting `PASS` lines answers a different
3466
- // question — before the sweep that closed #568 a run could print seven of
3467
- // them over a candidate on which five rules had not executed at all.
3468
- //
3469
- // ⚠️ Every figure here is a count of ASSERTIONS and not of rows, which is why
3470
- // the failed side is a Set: `fail()` is called once per finding, so one
3471
- // assertion can print six `FAIL` lines, and a line-count would report 47 of
3472
- // 42. The four buckets partition `ASSERTION_NAMES`, so the total is derived
3473
- // by adding them rather than stated — a `42` written here would be the one
3474
- // number in the report that no run could contradict.
3475
- const failed = new Set(report.failures.map((f) => f.assertion));
3476
- const measured = report.passed.length + failed.size;
3477
- const total = measured + report.skipped.length + report.profileSkipped.length;
3478
- lines.push(
3479
- ` .. ${total} assertions: ${measured} measured (${report.passed.length} passed, ${failed.size} failed), ` +
3480
- `${report.skipped.length} skipped, ${report.profileSkipped.length} not in profile "${report.profile}"`,
3481
- );
3482
- return lines;
3483
- }
3484
-
3485
3415
  export function atlasDirOf(atlasPath: string): string {
3486
3416
  return dirname(resolve(atlasPath));
3487
3417
  }