spine-rigc 2.49.0 → 2.51.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/src/validate.ts CHANGED
@@ -77,14 +77,13 @@ import {
77
77
  walkTimelines,
78
78
  } from './timelines.ts';
79
79
  import type { RigInfo } from './types.ts';
80
- import { ASSERTION_KIND, kindRunsUnder, type AssertionProfile } from './assertions/kinds.ts';
80
+ import { ASSERTION_KIND, kindRunsUnder, resolveProfile, type AssertionProfile } from './assertions/kinds.ts';
81
81
  import { verdictHarness, type Failure } from './assertions/harness.ts';
82
82
  import { isObj, type Json } from './assertions/values.ts';
83
83
  import type { SkinEntryFacts } from './assertions/facts/skin_entries.ts';
84
84
  import type { AtlasPageFacts } from './assertions/facts/atlas_pages.ts';
85
85
  import type { SlotColourFacts, SlotTimelines } from './assertions/facts/slot_colour.ts';
86
86
  import { a03RegionWidthHeightFinite } from './assertions/bodies/a03.ts';
87
- import { a11NoClippingAttachments } from './assertions/bodies/a11.ts';
88
87
  import { a17AtlasPageFilesExist } from './assertions/bodies/a17.ts';
89
88
  import { a18DeterministicEmit } from './assertions/bodies/a18.ts';
90
89
  import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from './assertions/bodies/a45.ts';
@@ -108,7 +107,6 @@ import { a50StageBoxIsTheStage } from './assertions/bodies/a50.ts';
108
107
  import { a51BonesLieAlongTheirDeclaredAxis } from './assertions/bodies/a51.ts';
109
108
  import { inactiveAtSetup, type SetupWorldFacts } from './assertions/facts/setup_world.ts';
110
109
  import { SKIP_NO_STAGE_BOX, type StageBoxFacts } from './assertions/facts/stage_box.ts';
111
- import { a27RegionNameMatchesPageFilename } from './assertions/bodies/a27.ts';
112
110
  import type { MeshEntry as MeshAttachmentEntry, MeshFacts } from './assertions/facts/mesh_attachments.ts';
113
111
  import type { ClipEnd, PolygonEntry, PolygonFacts } from './assertions/facts/vertex_polygons.ts';
114
112
  import type { LinkEntry, LinkFacts } from './assertions/facts/linked_meshes.ts';
@@ -168,14 +166,14 @@ export type { Failure } from './assertions/harness.ts';
168
166
  * ⭐ The distinction this draws is the difference between "wrong" and "not how we
169
167
  * do it here", and conflating the two is how a validator stops being usable on
170
168
  * anybody else's data. Fifteen of the 40 assertions are policy — seven for one
171
- * renderer (`spine-html`) and one project's canvas budget, eight for rigc's own
169
+ * renderer (`rig-play`) and one project's canvas budget, eight for rigc's own
172
170
  * formations — and every one of them fires
173
171
  * on real, correct, editor-produced Spine data — the official example projects
174
172
  * carry clipping attachments, unweighted meshes, 116-triangle meshes and packed
175
173
  * atlases, all of which are perfectly valid and none of which spine-html likes.
176
174
  *
177
175
  * - `spine` — is this valid Spine 4.3 that any runtime will play correctly?
178
- * - `spine-html` — the above, plus this project's renderer and archetype policy.
176
+ * - `rig-play` — the above, plus this project's renderer and archetype policy.
179
177
  *
180
178
  * ⚠️ `validate()` has NO default profile — `ValidateInput.profile` is required,
181
179
  * and that is deliberate. A silent default here can only be wrong in one of two
@@ -207,7 +205,7 @@ export const ASSERTION_NAMES: readonly string[] = Object.keys(ASSERTION_KIND);
207
205
 
208
206
  /** How many assertions this profile applies, by the kinds it carries. */
209
207
  export function assertionCountForProfile(profile: ValidateProfile): number {
210
- if (profile === 'spine-html') return ASSERTION_NAMES.length;
208
+ if (profile === 'rig-play') return ASSERTION_NAMES.length;
211
209
  return ASSERTION_NAMES.filter((name) => ASSERTION_KIND[name] === 'validity').length;
212
210
  }
213
211
 
@@ -846,14 +844,12 @@ export function timelineAddCells(data: ReturnType<SkeletonJson['readSkeletonData
846
844
  */
847
845
  export function spineSkinEntries(data: ReturnType<SkeletonJson['readSkeletonData']>): SkinEntryFacts {
848
846
  const regionAttachments: RegionAttachment[] = [];
849
- let clippingCount = 0;
850
847
  for (const skin of data.skins) {
851
848
  for (const entry of skin.getAttachments()) {
852
849
  if (entry.attachment instanceof RegionAttachment) regionAttachments.push(entry.attachment);
853
- else if (entry.attachment instanceof ClippingAttachment) clippingCount++;
854
850
  }
855
851
  }
856
- return { regionAttachments, clippingCount };
852
+ return { regionAttachments };
857
853
  }
858
854
 
859
855
  /**
@@ -2012,9 +2008,10 @@ function once<T>(supply: () => T): () => T {
2012
2008
  }
2013
2009
 
2014
2010
  export function validate(input: ValidateInput): ValidateReport {
2015
- const profile = input.profile;
2011
+ // A former name reads as its profile (issue #1277): the policy switch below and the report both use the resolved one.
2012
+ const profile = resolveProfile(input.profile, 'validate');
2016
2013
  /** True when this profile's rulebook includes the policy layer. */
2017
- const policy = profile === 'spine-html';
2014
+ const policy = profile === 'rig-play';
2018
2015
  // The harness — `check`, `fail`, `skip` and the lists they fill — is
2019
2016
  // `./assertions/harness.ts`'s since issue #1025, and the rule is the one it
2020
2017
  // always was: the model side runs the bodies that moved there in the same
@@ -2566,7 +2563,7 @@ export function validate(input: ValidateInput): ValidateReport {
2566
2563
  });
2567
2564
 
2568
2565
  // --- A12: no dark / two-colour tint --------------------------------------
2569
- // Parsed, then silently ignored by spine-html.
2566
+ // Parsed, then ignored by rig-play's DOM mode.
2570
2567
  check('A12_NO_DARK_COLOR', () => a12NoDarkColor(verdicts, rawSkeletonRoster(raw), rawSlotTimelines(raw)));
2571
2568
 
2572
2569
  // --- A05: curve arrays are 4 numbers per value channel --------------------
@@ -2667,8 +2664,9 @@ export function validate(input: ValidateInput): ValidateReport {
2667
2664
  // `./assertions/bodies/a33.ts` (issue #1025, cut 4c-2).
2668
2665
  check('A33_VERTEX_ATTACHMENT_GEOMETRY', () => a33VertexAttachmentGeometry(verdicts, polygonFacts()));
2669
2666
 
2670
- // --- A11 / A13 / A14: renderer + canvas budgets ----
2671
- check('A11_NO_CLIPPING_ATTACHMENTS', () => a11NoClippingAttachments(verdicts, skinEntries));
2667
+ // --- A13 / A14: canvas budgets ----
2668
+ // A11 stood here until issue #1404 retired it: the player `rig-play` names
2669
+ // draws a clipping attachment in both of its modes.
2672
2670
  // 📐 The two numbers come from the rig spec's `invariants`, never from here.
2673
2671
  // A mesh budget is one consumer's frame time written down — the editor's own
2674
2672
  // example projects ship meshes many times denser and they are valid — so a
@@ -3309,7 +3307,7 @@ export function validate(input: ValidateInput): ValidateReport {
3309
3307
  // first build — and told them, untruthfully, that the file held no transparency
3310
3308
  // at all. That audit is also why the CLI's default is now `spine`, which does
3311
3309
  // not run this rule at all (#221): a stranger reaches this refusal only by
3312
- // asking for `spine-html`. It still has to be both TRUE and actionable when it
3310
+ // asking for `rig-play`. It still has to be both TRUE and actionable when it
3313
3311
  // fires, and it now has to name the profile it belongs to as well as the one
3314
3312
  // that does not ask — the reader opted in, and the message is where they find
3315
3313
  // out what they opted into.
@@ -3385,15 +3383,11 @@ export function validate(input: ValidateInput): ValidateReport {
3385
3383
  // what a `drawOrder` key's offsets are counted against.
3386
3384
  check('A26_SLOT_DRAW_ORDER', () => a26SlotDrawOrder(verdicts, rawSkeletonRoster(raw), input));
3387
3385
 
3388
- // --- A27: region name == the PNG's basename ------------------------------
3389
- //
3390
- // The join key is a chain of three names — attachment -> atlas region -> file —
3391
- // and A08 only holds the first link. The second was held by convention alone:
3392
- // an atlas could declare page `../plates/02_overlay.png` with a region
3393
- // called anything at all, every attachment could agree with it, and the rig
3394
- // would load with the wrong pixels under the right name. One part per page (A06
3395
- // forces it) makes the check exact.
3396
- check('A27_REGION_NAME_MATCHES_PAGE_FILENAME', () => a27RegionNameMatchesPageFilename(verdicts, { atlas }));
3386
+ // A27 stood here until issue #1404 retired it. It held a page's only region
3387
+ // to the page's file basename, a join no reader makes: spine-core and the
3388
+ // player `rig-play` names both take a region off the page record the atlas
3389
+ // states it under, and the player draws a region on a page named for
3390
+ // something else in both of its modes.
3397
3391
 
3398
3392
  // --- A28: a ribbon's rows share their weights ----------------------------
3399
3393
  //
@@ -1,15 +0,0 @@
1
- /**
2
- * A11, the body (issue #1025, step 4c of #380): no clipping attachment — the
3
- * renderer skips them silently. A `renderer` rule, so `spine` never runs it.
4
- *
5
- * Moved out of `src/validate.ts` unchanged but for what it reads: the count is
6
- * a fact (`../facts/skin_entries.ts`).
7
- */
8
- import type { Verdicts } from '../harness.ts';
9
- import type { SkinEntryFacts } from '../facts/skin_entries.ts';
10
-
11
- export function a11NoClippingAttachments({ fail }: Verdicts, { clippingCount }: SkinEntryFacts): void {
12
- if (clippingCount > 0) {
13
- fail('A11_NO_CLIPPING_ATTACHMENTS', `${clippingCount} clipping attachment(s); the renderer skips them silently`);
14
- }
15
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * A27, the body (issue #1025, step 4c of #380): the only region on a page is
3
- * named for the page's file.
4
- *
5
- * Moved out of `src/validate.ts` unchanged but for what it reads: the regions
6
- * are a fact (`../facts/atlas_regions.ts`). Why the second link of the
7
- * attachment -> region -> file chain needs holding stays above the `check`
8
- * call in `validate()`.
9
- */
10
- import { basename } from 'node:path';
11
- import type { Verdicts } from '../harness.ts';
12
- import type { AtlasRegionFacts } from '../facts/atlas_regions.ts';
13
- import { SKIP_NO_ATLAS, SKIP_NO_ATLAS_REGION } from '../reasons.ts';
14
-
15
- export function a27RegionNameMatchesPageFilename({ fail, skip }: Verdicts, { atlas }: AtlasRegionFacts): void {
16
- if (!atlas) return skip('A27_REGION_NAME_MATCHES_PAGE_FILENAME', SKIP_NO_ATLAS);
17
- if (atlas.regions.length === 0) return skip('A27_REGION_NAME_MATCHES_PAGE_FILENAME', SKIP_NO_ATLAS_REGION);
18
- const perPage = new Map<string, number>();
19
- for (const region of atlas.regions) perPage.set(region.page.name, (perPage.get(region.page.name) ?? 0) + 1);
20
- for (const region of atlas.regions) {
21
- // A real packer puts many regions on one page and the names stop matching
22
- // filenames by design. Then this check has nothing to say, so it says
23
- // nothing rather than something wrong.
24
- if ((perPage.get(region.page.name) ?? 0) !== 1) continue;
25
- const expected = basename(region.page.name).replace(/\.png$/i, '');
26
- if (region.name !== expected) {
27
- fail(
28
- 'A27_REGION_NAME_MATCHES_PAGE_FILENAME',
29
- `region "${region.name}" is the only region on page "${region.page.name}", whose basename is "${expected}"`,
30
- );
31
- }
32
- }
33
- }