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/NOTICE.md +2 -2
- package/README.md +8 -8
- package/docs/AUTHORING.md +31 -33
- package/docs/FACE.md +18 -18
- package/docs/INGEST.md +17 -15
- package/docs/RIGGING.md +3 -3
- package/package.json +1 -1
- package/skills/rigc-face/SKILL.md +1 -1
- package/src/assertions/bodies/a06.ts +1 -1
- package/src/assertions/bodies/a12.ts +6 -3
- package/src/assertions/bodies/a19.ts +7 -7
- package/src/assertions/bodies/a20.ts +2 -2
- package/src/assertions/emitted/index.ts +4 -2
- package/src/assertions/facts/atlas_regions.ts +2 -2
- package/src/assertions/facts/skin_entries.ts +1 -3
- package/src/assertions/harness.ts +5 -1
- package/src/assertions/kinds.ts +35 -5
- package/src/assertions/model/index.ts +6 -8
- package/src/assertions/model/skin_entries.ts +2 -4
- package/src/assertions/reasons.ts +1 -1
- package/src/assertions/report.ts +13 -2
- package/src/atlas.ts +1 -1
- package/src/cli/shared.ts +25 -17
- package/src/cli/spine_commands.ts +1 -1
- package/src/compile.ts +1 -1
- package/src/rig.ts +1 -1
- package/src/validate.ts +18 -24
- package/src/assertions/bodies/a11.ts +0 -15
- package/src/assertions/bodies/a27.ts +0 -33
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A06, the body (issue #1025, step 4c of #380): every page the atlas names is
|
|
3
3
|
* the size its PNG is, every region lies inside its page, and — under
|
|
4
|
-
* `
|
|
4
|
+
* `rig-play` — no page claims premultiplied alpha and no region is turned.
|
|
5
5
|
* The clause that refused two regions on one page overlapping is
|
|
6
6
|
* `A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` since issue #1099 (`./a49.ts`).
|
|
7
7
|
*
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A12, the body (issue #1025, cut 4c-4 of #380): no dark colour and no
|
|
3
|
-
* two-colour timeline — both parsed, then
|
|
3
|
+
* two-colour timeline — both parsed, then ignored by rig-play's DOM mode.
|
|
4
|
+
* Only the DOM mode is named (issue #1277): rig-play's canvas mode carries
|
|
5
|
+
* `dark` on the frame, but no test of the player's draws it, so nothing here
|
|
6
|
+
* says it is drawn there.
|
|
4
7
|
*
|
|
5
8
|
* Moved out of `src/validate.ts` unchanged but for what it reads: the slots
|
|
6
9
|
* are a fact (`../facts/skeleton_roster.ts`) and the slot timelines are A45's
|
|
@@ -16,14 +19,14 @@ import type { SlotTimelines } from '../facts/slot_colour.ts';
|
|
|
16
19
|
export function a12NoDarkColor({ fail }: Verdicts, { slots }: SkeletonRosterFacts, slotTimelines: readonly SlotTimelines[]): void {
|
|
17
20
|
for (const slot of slots) {
|
|
18
21
|
if (slot.dark) {
|
|
19
|
-
fail('A12_NO_DARK_COLOR', `slot "${slot.name}" declares a dark colour;
|
|
22
|
+
fail('A12_NO_DARK_COLOR', `slot "${slot.name}" declares a dark colour; rig-play's DOM mode ignores it`);
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
for (const { animation, slot, timelines } of slotTimelines) {
|
|
23
26
|
for (const [name, keys] of Object.entries(timelines)) {
|
|
24
27
|
if (!Array.isArray(keys)) continue;
|
|
25
28
|
if (name === 'rgba2' || name === 'rgb2') {
|
|
26
|
-
fail('A12_NO_DARK_COLOR', `${animation}.slots.${slot}.${name}: two-colour timeline "${name}" is
|
|
29
|
+
fail('A12_NO_DARK_COLOR', `${animation}.slots.${slot}.${name}: two-colour timeline "${name}" is ignored by rig-play's DOM mode`);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
}
|
|
@@ -25,7 +25,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
25
25
|
// A SKIP for the same reason A06's is (#568). This one is invisible under
|
|
26
26
|
// `spine`, where the profile excludes the rule before its body runs — and
|
|
27
27
|
// that is exactly why it was worth finding: `--profile spine-html` reported
|
|
28
|
-
// it, and A27
|
|
28
|
+
// it, and A27 (retired since, issue #1404), green on an atlas nothing had read.
|
|
29
29
|
if (!atlas) return skip('A19_OVERLAY_PNGS_HAVE_ALPHA', SKIP_NO_ATLAS);
|
|
30
30
|
if (atlas.pages.length === 0) return skip('A19_OVERLAY_PNGS_HAVE_ALPHA', SKIP_NO_ATLAS_PAGE);
|
|
31
31
|
// Every sentence this body prints goes through here, so its end can tell
|
|
@@ -145,7 +145,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
145
145
|
// ⭐ Who names it is read off the two bodies, not chosen. Both ask
|
|
146
146
|
// `readPngHeader` (`pngProblem`, the one reader of a page's identity) the
|
|
147
147
|
// same question about the same file, A06 runs under both profiles and this
|
|
148
|
-
// rule under `
|
|
148
|
+
// rule under `rig-play` alone, and A06 FAILs on every page for which it
|
|
149
149
|
// answers with a problem — so on every input that reaches this branch, A06
|
|
150
150
|
// has already named the file and the report is red. A FAIL here would be
|
|
151
151
|
// the second naming and a PASS a certificate over alpha nobody read, so
|
|
@@ -246,7 +246,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
246
246
|
`${region.y} is not where "${region.name}"'s texels are on this file and this rule states ` +
|
|
247
247
|
'nothing about whether it can draw a transparent pixel. The page grid is ' +
|
|
248
248
|
"A06_ATLAS_PAGE_SIZE_MATCHES_PNG's to judge, and it names the ratio and how to declare the page " +
|
|
249
|
-
'honestly. This is renderer policy, and it belongs to --profile
|
|
249
|
+
'honestly. This is renderer policy, and it belongs to --profile rig-play: the default ' +
|
|
250
250
|
'--profile spine does not run this check.',
|
|
251
251
|
);
|
|
252
252
|
}
|
|
@@ -280,7 +280,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
280
280
|
`${plate.width}x${plate.height}, so it states nothing about whether "${region.name}" can draw a ` +
|
|
281
281
|
"transparent pixel. A region's rectangle is A06_ATLAS_PAGE_SIZE_MATCHES_PNG's to judge, and one " +
|
|
282
282
|
'that runs off its page is refused there by name. This is renderer policy, and it belongs to ' +
|
|
283
|
-
'--profile
|
|
283
|
+
'--profile rig-play: the default --profile spine does not run this check.',
|
|
284
284
|
);
|
|
285
285
|
continue;
|
|
286
286
|
}
|
|
@@ -298,7 +298,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
298
298
|
'A19_OVERLAY_PNGS_HAVE_ALPHA',
|
|
299
299
|
`part "${region.name}" is opaque in ${over}, so it would paint a solid rectangle over whatever is ` +
|
|
300
300
|
`drawn behind it. Re-export the part with transparency and pack again. ${exemption} This is ` +
|
|
301
|
-
'renderer policy, and it belongs to --profile
|
|
301
|
+
'renderer policy, and it belongs to --profile rig-play: the default --profile spine does not run ' +
|
|
302
302
|
'this check.',
|
|
303
303
|
);
|
|
304
304
|
}
|
|
@@ -316,7 +316,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
316
316
|
`(${colourTypeName(info.colourType)}) with no tRNS chunk, so it would paint a solid rectangle over ` +
|
|
317
317
|
'whatever is drawn behind it. Re-export it with transparency — as RGBA, or as an indexed or greyscale ' +
|
|
318
318
|
`PNG that keeps its tRNS chunk. ${exemption} This is renderer policy, and it belongs to --profile ` +
|
|
319
|
-
'
|
|
319
|
+
'rig-play: the default --profile spine does not run this check.',
|
|
320
320
|
);
|
|
321
321
|
continue;
|
|
322
322
|
}
|
|
@@ -352,7 +352,7 @@ export function a19OverlayPngsHaveAlpha({ fail: failed, skip }: Verdicts, { atla
|
|
|
352
352
|
`part image "${page.name}" is opaque in every one of its ${plate.width}x${plate.height} texels, so it ` +
|
|
353
353
|
`would paint a solid rectangle over whatever is drawn behind it: its file can hold transparency — ${holds} ` +
|
|
354
354
|
'— and no texel uses it. Re-export the part with the transparency it is meant to have. ' +
|
|
355
|
-
`${exemption} This is renderer policy, and it belongs to --profile
|
|
355
|
+
`${exemption} This is renderer policy, and it belongs to --profile rig-play: the default --profile ` +
|
|
356
356
|
'spine does not run this check.',
|
|
357
357
|
);
|
|
358
358
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A20, the body (issue #1025, step 4c of #380): a weighted mesh's weights
|
|
3
3
|
* cohere — every vertex bound, every weight finite and non-negative, every
|
|
4
|
-
* vertex's weights summing to 1 — and, under `
|
|
4
|
+
* vertex's weights summing to 1 — and, under `rig-play`, a generated mesh is
|
|
5
5
|
* weighted, binds only bones that move it and binds every bone it declares.
|
|
6
6
|
*
|
|
7
7
|
* Moved out of `src/validate.ts` clause by clause, every clause about the
|
|
@@ -72,7 +72,7 @@ export function a20MeshWeightsCoherent({ fail, skip }: Verdicts, { meshes, bones
|
|
|
72
72
|
// In a rigc-GENERATED ring or ribbon it is still a defect: the
|
|
73
73
|
// generator bound a bone that does nothing, which is a bug in the
|
|
74
74
|
// generator and dead work in the runtime's inner loop. So it stays a
|
|
75
|
-
// failure under
|
|
75
|
+
// failure under rig-play and is not one under spine.
|
|
76
76
|
else if (policy && generated && weight === 0) {
|
|
77
77
|
fail(
|
|
78
78
|
'A20_MESH_WEIGHTS_COHERENT',
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Links nothing from the runtime.
|
|
24
24
|
*/
|
|
25
|
-
import { ASSERTION_KIND, type AssertionProfile } from '../kinds.ts';
|
|
25
|
+
import { ASSERTION_KIND, resolveProfile, type AssertionProfile } from '../kinds.ts';
|
|
26
26
|
import { verdictHarness, type VerdictLists } from '../harness.ts';
|
|
27
27
|
import { MOVED_ASSERTIONS } from '../model/index.ts';
|
|
28
28
|
import type { Verdicts } from '../harness.ts';
|
|
@@ -125,7 +125,9 @@ export function roundTripOnlyCodes(): string[] {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/** The restated rules over one build's emitted texts, in `validate()`'s report shape. */
|
|
128
|
-
export function validateEmittedText(
|
|
128
|
+
export function validateEmittedText(given: EmittedTextInput): VerdictLists & { profile: AssertionProfile } {
|
|
129
|
+
// A former profile name reads as its profile (issue #1277), for the rules and the report alike.
|
|
130
|
+
const input: EmittedTextInput = { ...given, profile: resolveProfile(given.profile, 'the emitted text') };
|
|
129
131
|
const known = new Set(EMITTED_TEXT_RULES.map((r) => r.code));
|
|
130
132
|
const unrun = roundTripOnlyCodes().filter((code) => !known.has(code));
|
|
131
133
|
if (unrun.length > 0) {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* The atlas as the page rules read it (issue #1025, step 4c of #380) — the
|
|
3
3
|
* census's F20 and F21 whole: every page's name, declared size and `pma`, every
|
|
4
4
|
* region's rectangle and the page it sits on, and the region a name resolves
|
|
5
|
-
* to. A06, A19 and
|
|
5
|
+
* to. A06, A19 and A49 read it; A17, which reads only the page names, keeps the
|
|
6
6
|
* narrower `./atlas_pages.ts`.
|
|
7
7
|
*
|
|
8
|
-
* ⭐ **The order is a fact, and it is the file's.** A06
|
|
8
|
+
* ⭐ **The order is a fact, and it is the file's.** A06 prints one line
|
|
9
9
|
* per offending page or region, and A06's overlap clause prints a pair in the
|
|
10
10
|
* order it meets them, so `pages` and `regions` hold the atlas's own order —
|
|
11
11
|
* pages as the file lists them, regions page by page in the file's order — and
|
|
@@ -28,10 +28,8 @@ export interface RegionEntry {
|
|
|
28
28
|
readonly path?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** What the skins hold, for A03
|
|
31
|
+
/** What the skins hold, for A03 and A19. */
|
|
32
32
|
export interface SkinEntryFacts {
|
|
33
33
|
/** Every region attachment of every skin, in the file's walk order (the header's ⭐). */
|
|
34
34
|
readonly regionAttachments: readonly RegionEntry[];
|
|
35
|
-
/** How many clipping attachments the skins hold, every skin counted. */
|
|
36
|
-
readonly clippingCount: number;
|
|
37
35
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* This module links nothing from the runtime, directly or through what it
|
|
19
19
|
* imports — the condition for the model side to use it, held by the selftest.
|
|
20
20
|
*/
|
|
21
|
-
import { kindRunsUnder, type AssertionKind, type AssertionProfile } from './kinds.ts';
|
|
21
|
+
import { kindRunsUnder, resolveProfile, type AssertionKind, type AssertionProfile } from './kinds.ts';
|
|
22
22
|
|
|
23
23
|
/** One failure: the assertion's code and the sentence naming the object, the value found and the value required. */
|
|
24
24
|
export interface Failure {
|
|
@@ -75,6 +75,10 @@ export interface VerdictHarness extends VerdictLists {
|
|
|
75
75
|
* the table lacks — `validate` for the gate, so its message is the one it was.
|
|
76
76
|
*/
|
|
77
77
|
export function verdictHarness(profile: AssertionProfile, kinds: Readonly<Record<string, AssertionKind>>, who: string): VerdictHarness {
|
|
78
|
+
// A former name resolves to its profile and anything outside the pair is
|
|
79
|
+
// refused by name — `resolveProfile` says why. The entries resolve before
|
|
80
|
+
// they get here, so this is the floor for a caller of the harness itself.
|
|
81
|
+
profile = resolveProfile(profile, who);
|
|
78
82
|
const failures: Failure[] = [];
|
|
79
83
|
const passed: string[] = [];
|
|
80
84
|
const skipped: VerdictLists['skipped'] = [];
|
package/src/assertions/kinds.ts
CHANGED
|
@@ -12,7 +12,32 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/** The two rulebooks — `ValidateProfile` in `src/validate.ts`, where the choice between them is argued. */
|
|
15
|
-
export type AssertionProfile = 'spine' | '
|
|
15
|
+
export type AssertionProfile = 'spine' | 'rig-play';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A profile's former name, which the CLI's `--profile` still reads for one
|
|
19
|
+
* minor and resolves at parse time, printing the deprecation line that names
|
|
20
|
+
* the profile it reads as (issue #1277). Nothing past the flag parser sees an
|
|
21
|
+
* alias: the rule set is keyed on the profile it resolves to, so the alias and
|
|
22
|
+
* its target cannot come to run different rules.
|
|
23
|
+
*/
|
|
24
|
+
export const PROFILE_ALIASES: Readonly<Record<string, AssertionProfile>> = { 'spine-html': 'rig-play' };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The profile a gate entry runs under: a profile as given, a former name as the
|
|
28
|
+
* profile it became — silently, because the CLI prints the deprecation line and
|
|
29
|
+
* a library caller of `validate()` through a courtesy key passed the former
|
|
30
|
+
* name on the release before this one and must get the same rules — and
|
|
31
|
+
* anything else refused by name. `kindRunsUnder` reads every name but
|
|
32
|
+
* `rig-play` as validity-only, so an unresolved name would run a weaker gate in
|
|
33
|
+
* silence, under a name nobody chose (issue #1277).
|
|
34
|
+
*/
|
|
35
|
+
export function resolveProfile(profile: string, who: string): AssertionProfile {
|
|
36
|
+
if (profile === 'spine' || profile === 'rig-play') return profile;
|
|
37
|
+
const target = Object.hasOwn(PROFILE_ALIASES, profile) ? PROFILE_ALIASES[profile] : undefined;
|
|
38
|
+
if (target !== undefined) return target;
|
|
39
|
+
throw new Error(`${who}: profile ${JSON.stringify(profile)} is not a profile; the profiles are "spine" and "rig-play"`);
|
|
40
|
+
}
|
|
16
41
|
|
|
17
42
|
/** The three kinds a rule can be; the table's own comment says what each means. */
|
|
18
43
|
export type AssertionKind = 'validity' | 'renderer' | 'archetype';
|
|
@@ -45,6 +70,13 @@ export type AssertionKind = 'validity' | 'renderer' | 'archetype';
|
|
|
45
70
|
* restating it as the renderer's own join made it a tautology over the validity
|
|
46
71
|
* half. The argument, with the renderer lines it is measured against, sits above
|
|
47
72
|
* `check('A08_…')`.
|
|
73
|
+
*
|
|
74
|
+
* The numbering has gaps, and they are retirements, not reservations: A41
|
|
75
|
+
* (issue #1202), and A11 and A27 (issue #1404) — two `renderer` rules stating
|
|
76
|
+
* limits the player `rig-play` names does not have, since it draws a clipping
|
|
77
|
+
* attachment and a region on a page named for something else in both of its
|
|
78
|
+
* modes. A retired code is not reused, so a line that names one in an old
|
|
79
|
+
* report still means what it meant.
|
|
48
80
|
*/
|
|
49
81
|
export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
50
82
|
A00_ROUNDTRIP_PARSE: 'validity',
|
|
@@ -58,7 +90,6 @@ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
|
58
90
|
A08_REGION_NAMES_MATCH_ATTACHMENTS: 'validity', // no longer mixed — see above (#574)
|
|
59
91
|
A09_ANIMATION_DURATION_MATCHES_SPEC: 'validity',
|
|
60
92
|
A10_NO_NAN_AFTER_STEPPING: 'validity',
|
|
61
|
-
A11_NO_CLIPPING_ATTACHMENTS: 'renderer',
|
|
62
93
|
A12_NO_DARK_COLOR: 'renderer',
|
|
63
94
|
A13_MESH_BUDGET: 'renderer',
|
|
64
95
|
A14_NO_FULL_FRAME_MESH: 'renderer',
|
|
@@ -74,7 +105,6 @@ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
|
74
105
|
A24_AXIS_SPACE_STROKE: 'archetype',
|
|
75
106
|
A25_DETACHED_BONE_PARENTAGE: 'archetype',
|
|
76
107
|
A26_SLOT_DRAW_ORDER: 'archetype',
|
|
77
|
-
A27_REGION_NAME_MATCHES_PAGE_FILENAME: 'renderer',
|
|
78
108
|
A28_RIBBON_ROWS_SHARE_WEIGHTS: 'archetype',
|
|
79
109
|
A29_STROKE_WITHIN_CONTACT_DEPTH: 'archetype',
|
|
80
110
|
A30_STROKE_WITHIN_CAP_CONTAINMENT: 'archetype',
|
|
@@ -100,7 +130,7 @@ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
|
100
130
|
A51_BONES_LIE_ALONG_THEIR_DECLARED_AXIS: 'validity',
|
|
101
131
|
};
|
|
102
132
|
|
|
103
|
-
/** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `
|
|
133
|
+
/** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `rig-play` every kind. */
|
|
104
134
|
export function kindRunsUnder(kind: AssertionKind, profile: AssertionProfile): boolean {
|
|
105
|
-
return kind === 'validity' || profile === '
|
|
135
|
+
return kind === 'validity' || profile === 'rig-play';
|
|
106
136
|
}
|
|
@@ -37,11 +37,10 @@
|
|
|
37
37
|
* Links nothing from the runtime, directly or through what it imports — held
|
|
38
38
|
* by the selftest's `VF` controls.
|
|
39
39
|
*/
|
|
40
|
-
import { ASSERTION_KIND, type AssertionProfile } from '../kinds.ts';
|
|
40
|
+
import { ASSERTION_KIND, resolveProfile, type AssertionProfile } from '../kinds.ts';
|
|
41
41
|
import { verdictHarness, type VerdictHarness, type Verdicts, type VerdictLists } from '../harness.ts';
|
|
42
42
|
import { SKIP_NO_ATLAS, SKIP_NO_MODEL } from '../reasons.ts';
|
|
43
43
|
import { a03RegionWidthHeightFinite } from '../bodies/a03.ts';
|
|
44
|
-
import { a11NoClippingAttachments } from '../bodies/a11.ts';
|
|
45
44
|
import { a17AtlasPageFilesExist } from '../bodies/a17.ts';
|
|
46
45
|
import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from '../bodies/a45.ts';
|
|
47
46
|
import { A00_MODEL_READ, A00_MODEL_REGIONS_ON_PAGES, MODEL_PARSE_KIND, modelRead, modelRegionsOnPages, SKIP_NO_MODEL_DOCUMENT, SKIP_NO_MODEL_PAGES, type ReadDocument } from './parse.ts';
|
|
@@ -69,7 +68,6 @@ import { modelSetupWorld } from './setup_world.ts';
|
|
|
69
68
|
import type { SetupWorldFacts } from '../facts/setup_world.ts';
|
|
70
69
|
import { modelStageBox } from './stage_box.ts';
|
|
71
70
|
import type { StageBoxFacts } from '../facts/stage_box.ts';
|
|
72
|
-
import { a27RegionNameMatchesPageFilename } from '../bodies/a27.ts';
|
|
73
71
|
import { modelAnimatedBones } from './animated_bones.ts';
|
|
74
72
|
import { modelSkinMembers } from './skin_members.ts';
|
|
75
73
|
import { modelRegionJoins } from './region_joins.ts';
|
|
@@ -290,11 +288,10 @@ export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
|
|
|
290
288
|
{ code: 'A03_REGION_WIDTH_HEIGHT_FINITE', run: (v, read, _input, supply) => a03RegionWidthHeightFinite(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
|
|
291
289
|
{ code: 'A04_MESH_TRIANGLES_AND_ENCODING', run: (v, read, _input, supply) => a04MeshTrianglesAndEncoding(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
|
|
292
290
|
{ code: 'A33_VERTEX_ATTACHMENT_GEOMETRY', run: (v, read, _input, supply) => a33VertexAttachmentGeometry(v, supply.polygons(read)), unread: SKIP_NO_MODEL },
|
|
293
|
-
{ code: 'A11_NO_CLIPPING_ATTACHMENTS', run: (v, read, _input, supply) => a11NoClippingAttachments(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
|
|
294
291
|
{ code: 'A13_MESH_BUDGET', run: (v, read, input, supply) => a13MeshBudget(v, supply.meshes(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
|
|
295
292
|
{ code: 'A14_NO_FULL_FRAME_MESH', run: (v, read, input, supply) => a14NoFullFrameMesh(v, supply.meshes(read), supply.stage(read, input)), unread: SKIP_NO_MODEL },
|
|
296
293
|
{ code: 'A15_IDLE_NO_MESH_BONE_KEYS', run: (v, read, input, supply) => a15IdleNoMeshBoneKeys(v, supply.meshes(read), supply.animatedBones(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
|
|
297
|
-
{ code: 'A20_MESH_WEIGHTS_COHERENT', run: (v, read, input, supply) => a20MeshWeightsCoherent(v, supply.meshes(read), input.profile === '
|
|
294
|
+
{ code: 'A20_MESH_WEIGHTS_COHERENT', run: (v, read, input, supply) => a20MeshWeightsCoherent(v, supply.meshes(read), input.profile === 'rig-play', supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
298
295
|
{ code: 'A21_MESH_RIM_PINNED', run: (v, read, input, supply) => a21MeshRimPinned(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
299
296
|
{ code: 'A22_MESH_UVS_IN_UNIT_RANGE', run: (v, read, _input, supply) => a22MeshUvsInUnitRange(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
|
|
300
297
|
{ code: 'A39_DEFORM_KEEPS_TRIANGLE_WINDING', run: (v, read, input, supply) => a39DeformKeepsTriangleWinding(v, supply.deformSurvey(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
@@ -313,14 +310,13 @@ export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
|
|
|
313
310
|
{ code: 'A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN', run: (v, read, _input, supply) => a44LinkedMeshStatesNoGeometryOfItsOwn(v, supply.links(read)), unread: SKIP_NO_MODEL },
|
|
314
311
|
{ code: 'A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES', run: (v, read, _input, supply) => a46SequenceAttachmentsShowTheFrameTheFileStates(v, supply.sequences(read)), unread: SKIP_NO_MODEL },
|
|
315
312
|
{ code: 'A17_ATLAS_PAGE_FILES_EXIST', run: (v, read, input, supply) => a17AtlasPageFilesExist(v, supply.atlasPages(read), input), unread: SKIP_NO_ATLAS },
|
|
316
|
-
{ code: 'A06_ATLAS_PAGE_SIZE_MATCHES_PNG', run: (v, read, input, supply) => a06AtlasPageSizeMatchesPng(v, supply.atlasRegions(read, input), input, input.profile === '
|
|
313
|
+
{ code: 'A06_ATLAS_PAGE_SIZE_MATCHES_PNG', run: (v, read, input, supply) => a06AtlasPageSizeMatchesPng(v, supply.atlasRegions(read, input), input, input.profile === 'rig-play'), unread: SKIP_NO_ATLAS },
|
|
317
314
|
{ code: 'A19_OVERLAY_PNGS_HAVE_ALPHA', run: (v, read, input, supply) => a19OverlayPngsHaveAlpha(v, supply.atlasRegions(read, input), supply.stage(read, input), supply.skinEntries(read), { atlasDir: input.atlasDir, rig: supply.rigInfo(read, input) }), unread: SKIP_NO_ATLAS },
|
|
318
315
|
{ code: 'A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP', run: (v, read, input, supply) => a49PackedFootprintsDoNotOverlap(v, supply.atlasRegions(read, input), supply.regionJoins(read), supply.meshes(read)), unread: SKIP_NO_ATLAS },
|
|
319
316
|
{ code: 'A50_STAGE_BOX_IS_THE_STAGE', run: (v, read, _input, supply) => a50StageBoxIsTheStage(v, supply.stageBox(read)), unread: SKIP_NO_MODEL },
|
|
320
317
|
{ code: 'A24_AXIS_SPACE_STROKE', run: (v, read, input, supply) => a24AxisSpaceStroke(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
321
318
|
{ code: 'A25_DETACHED_BONE_PARENTAGE', run: (v, read, input, supply) => a25DetachedBoneParentage(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
322
319
|
{ code: 'A26_SLOT_DRAW_ORDER', run: (v, read, input, supply) => a26SlotDrawOrder(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
323
|
-
{ code: 'A27_REGION_NAME_MATCHES_PAGE_FILENAME', run: (v, read, input, supply) => a27RegionNameMatchesPageFilename(v, supply.atlasRegions(read, input)), unread: SKIP_NO_ATLAS },
|
|
324
320
|
{ code: 'A28_RIBBON_ROWS_SHARE_WEIGHTS', run: (v, read, input, supply) => a28RibbonRowsShareWeights(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
|
|
325
321
|
{ code: 'A29_STROKE_WITHIN_CONTACT_DEPTH', run: (v, read, input, supply) => a29StrokeWithinContactDepth(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
326
322
|
{ code: 'A30_STROKE_WITHIN_CAP_CONTAINMENT', run: (v, read, input, supply) => a30StrokeWithinCapContainment(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
@@ -389,7 +385,9 @@ function rigFigures(stats: Record<string, number | string>, read: ReadDocument |
|
|
|
389
385
|
* prints. `plant` replaces a family's supplier; only the selftest's plants
|
|
390
386
|
* pass one.
|
|
391
387
|
*/
|
|
392
|
-
export function validateModel(
|
|
388
|
+
export function validateModel(given: ModelValidateInput, plant: Partial<ModelSupply> = {}): ModelReport {
|
|
389
|
+
// A former profile name reads as its profile (issue #1277), for the policy switches and the report alike.
|
|
390
|
+
const input: ModelValidateInput = { ...given, profile: resolveProfile(given.profile, 'the model side') };
|
|
393
391
|
const supply: ModelSupply = { ...MODEL_SUPPLY, ...plant };
|
|
394
392
|
const h = verdictHarness(input.profile, { ...ASSERTION_KIND, ...MODEL_PARSE_KIND }, 'the model side');
|
|
395
393
|
const { read, held } = parse(h, input.modelText);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The model side's supply of `SkinEntryFacts` (issue #1025, step 4c of #380):
|
|
3
|
-
* the skins' region attachments
|
|
3
|
+
* the skins' region attachments, read off the document.
|
|
4
4
|
*
|
|
5
5
|
* ⭐ **In the file's order, by the emitter's own rule.** The document holds the
|
|
6
6
|
* skins in the spec's order and the Spine file holds them in the editor's
|
|
@@ -55,9 +55,7 @@ export function fileOrderedEntries(read: ReadDocument, order?: SkinOrder): Array
|
|
|
55
55
|
|
|
56
56
|
export function modelSkinEntries(read: ReadDocument, order?: SkinOrder): SkinEntryFacts {
|
|
57
57
|
const regionAttachments: RegionEntry[] = [];
|
|
58
|
-
let clippingCount = 0;
|
|
59
58
|
for (const entry of fileOrderedEntries(read, order)) {
|
|
60
|
-
if (entry.record.kind === 'clipping') clippingCount++;
|
|
61
59
|
if (entry.record.kind !== 'region') continue;
|
|
62
60
|
const geometry = entry.record.geometry;
|
|
63
61
|
// `readModel` reads every region record's geometry or refuses the document, so a record without one is this module's defect.
|
|
@@ -65,5 +63,5 @@ export function modelSkinEntries(read: ReadDocument, order?: SkinOrder): SkinEnt
|
|
|
65
63
|
const shown = shownRow(entry);
|
|
66
64
|
regionAttachments.push({ name: shown.name, width: geometry.region.width, height: geometry.region.height, path: shown.path as string });
|
|
67
65
|
}
|
|
68
|
-
return { regionAttachments
|
|
66
|
+
return { regionAttachments };
|
|
69
67
|
}
|
|
@@ -28,7 +28,7 @@ export const SKIP_NO_ATLAS = 'the round trip did not produce an atlas to measure
|
|
|
28
28
|
/**
|
|
29
29
|
* The third of them, and it is NOT waiting on A00 (issue #608): the atlas parsed
|
|
30
30
|
* and it has no pages, which is what a compile that measured no art writes. The
|
|
31
|
-
*
|
|
31
|
+
* three rules whose only subject is a page — A06, A17, A19 — have nothing to
|
|
32
32
|
* look at, and an empty loop walking out of `check()` is reported as a PASS.
|
|
33
33
|
*/
|
|
34
34
|
/*
|
package/src/assertions/report.ts
CHANGED
|
@@ -10,7 +10,18 @@ import type { AssertionProfile } from './kinds.ts';
|
|
|
10
10
|
import type { VerdictLists } from './harness.ts';
|
|
11
11
|
import { failLine } from '../guide.ts';
|
|
12
12
|
|
|
13
|
-
export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', '
|
|
13
|
+
export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', 'rig-play'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The line `--profile <alias>` prints on stderr before the command runs: the
|
|
17
|
+
* old name, the profile it reads as, and the flag to pass instead (issue
|
|
18
|
+
* #1277). Stderr so a report on stdout reads the same whichever name the
|
|
19
|
+
* caller typed; the report itself names the profile that judged it, which is
|
|
20
|
+
* the target and never the alias.
|
|
21
|
+
*/
|
|
22
|
+
export function profileAliasLine(alias: string, target: AssertionProfile): string {
|
|
23
|
+
return `rigc: --profile ${alias} is deprecated and reads as --profile ${target}, the same rules; it is accepted until the next minor release — pass --profile ${target}`;
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
/**
|
|
16
27
|
* What the CLI uses when `--profile` is absent — and ONLY the CLI. This is not
|
|
@@ -21,7 +32,7 @@ export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', 'spine-h
|
|
|
21
32
|
* into the Spine editor", which is exactly the question `spine` asks, and a
|
|
22
33
|
* stranger's first build was being judged instead against one renderer's policy
|
|
23
34
|
* and one project's canvas budget — 14 rules they have no stake in, with the
|
|
24
|
-
* escape hatch documented only in prose. Defaults beat prose. `
|
|
35
|
+
* escape hatch documented only in prose. Defaults beat prose. `rig-play` is
|
|
25
36
|
* still one flag away, and every report names the profile that judged it.
|
|
26
37
|
*/
|
|
27
38
|
export const CLI_DEFAULT_PROFILE: AssertionProfile = 'spine';
|
package/src/atlas.ts
CHANGED
|
@@ -553,7 +553,7 @@ export interface EmitPage {
|
|
|
553
553
|
* against the unturned pack. But on neither rig did a turn shrink the page: both
|
|
554
554
|
* were already on the smallest power-of-two page their area allows, and a turn
|
|
555
555
|
* that buys no area only moves bytes. Shipping it would also need two clauses
|
|
556
|
-
* changed that say rigc never turns a region — `A06`'s under `
|
|
556
|
+
* changed that say rigc never turns a region — `A06`'s under `rig-play`
|
|
557
557
|
* ([`src/validate.ts`](validate.ts)) and `artUvsOf`
|
|
558
558
|
* ([`src/render.ts`](render.ts)), which returns no art UVs for a turned region
|
|
559
559
|
* attachment, so `check`'s texture substitution would report it unmatched.
|
package/src/cli/shared.ts
CHANGED
|
@@ -26,10 +26,11 @@ import {
|
|
|
26
26
|
CLI_DEFAULT_PROFILE,
|
|
27
27
|
type GateHere,
|
|
28
28
|
type PackPageFigures,
|
|
29
|
+
profileAliasLine,
|
|
29
30
|
VALIDATE_PROFILES,
|
|
30
31
|
} from '../assertions/report.ts';
|
|
31
32
|
import type { VerdictLists } from '../assertions/harness.ts';
|
|
32
|
-
import type
|
|
33
|
+
import { PROFILE_ALIASES, type AssertionProfile } from '../assertions/kinds.ts';
|
|
33
34
|
import { MAX_CANDIDATES, MIN_CANDIDATES } from '../ballot.ts';
|
|
34
35
|
import { BONEDIST_SPEC, IDENTITY_CORRESPONDENCE } from '../correspondence.ts';
|
|
35
36
|
import { ANCHOR_MAX_RESIDUAL, ANCHOR_MAX_UNEXPLAINED, DEFAULT_HINGE_MAX, DEFAULT_HINGE_MIN, DEFAULT_MIN_LEVER_PX, DEFAULT_MIN_VISIBLE, DEFAULT_PASSES } from '../chainfit.ts';
|
|
@@ -988,7 +989,7 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
988
989
|
cuts: 'the cuts.json --cut names',
|
|
989
990
|
profile:
|
|
990
991
|
'which rulebook to check against (default: spine) — spine = valid Spine 4.3 that any runtime plays ' +
|
|
991
|
-
"correctly;
|
|
992
|
+
"correctly; rig-play = also this project's renderer/archetype policy",
|
|
992
993
|
atlas: "the candidate's atlas, when it is not beside the skeleton",
|
|
993
994
|
reference: 'the reference skeleton to pose beside the candidate — a directory or a skeleton.json path',
|
|
994
995
|
'reference-atlas': "the reference's atlas, when it is not beside the reference skeleton",
|
|
@@ -1179,7 +1180,7 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
1179
1180
|
manifest: '<path>',
|
|
1180
1181
|
cut: '<name>',
|
|
1181
1182
|
cuts: '<path>',
|
|
1182
|
-
profile: 'spine|
|
|
1183
|
+
profile: 'spine|rig-play',
|
|
1183
1184
|
atlas: '<path>',
|
|
1184
1185
|
'atlas-in': '<file.atlas>',
|
|
1185
1186
|
'page-size': '<px>',
|
|
@@ -1324,7 +1325,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1324
1325
|
for: 'the gate round-trips every build through it before anything is written',
|
|
1325
1326
|
core: {
|
|
1326
1327
|
usage: [
|
|
1327
|
-
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|
|
|
1328
|
+
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|rig-play] [--copy-images] [--report <file>] (the same files the round-tripped build writes; gated without spine-core — see build --help)',
|
|
1328
1329
|
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] (parts onto shared pages, written into --out)`,
|
|
1329
1330
|
'rigc build … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made)',
|
|
1330
1331
|
'rigc build --cut <name> --cuts <cuts.json>',
|
|
@@ -1348,7 +1349,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1348
1349
|
},
|
|
1349
1350
|
spineFormat: true,
|
|
1350
1351
|
usage: [
|
|
1351
|
-
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|
|
|
1352
|
+
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|rig-play] [--copy-images] [--report <file>]',
|
|
1352
1353
|
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] (parts onto shared pages, written into --out)`,
|
|
1353
1354
|
'rigc build … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made)',
|
|
1354
1355
|
'rigc build --cut <name> --cuts <cuts.json>',
|
|
@@ -1378,8 +1379,8 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1378
1379
|
for: 'the rebuild it writes is gated by build\'s gate, which round-trips it through spine-core',
|
|
1379
1380
|
core: {
|
|
1380
1381
|
usage: [
|
|
1381
|
-
`rigc repack <dir | skeleton.json> --out <dir> [--atlas <path>] [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] [--profile spine|
|
|
1382
|
-
`rigc repack <dir | skeleton.json> --out <dir> --keep-pages [--atlas <path>] [--profile spine|
|
|
1382
|
+
`rigc repack <dir | skeleton.json> --out <dir> [--atlas <path>] [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] [--profile spine|rig-play] [--stage x,y,w,h] [--stage-box <slot>] [--accept-skeleton-differences] [--report <file>] (gated without spine-core — see repack --help)`,
|
|
1383
|
+
`rigc repack <dir | skeleton.json> --out <dir> --keep-pages [--atlas <path>] [--profile spine|rig-play] [--stage x,y,w,h] [--stage-box <slot>] [--report <file>] (skeleton.model.json alone, pages kept)`,
|
|
1383
1384
|
],
|
|
1384
1385
|
notes: [
|
|
1385
1386
|
'a packed build repacked from its own output — skeleton.json, skeleton.atlas and the',
|
|
@@ -1401,8 +1402,8 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1401
1402
|
},
|
|
1402
1403
|
spineFormat: true,
|
|
1403
1404
|
usage: [
|
|
1404
|
-
`rigc repack <dir | skeleton.json> --out <dir> [--atlas <path>] [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] [--profile spine|
|
|
1405
|
-
`rigc repack <dir | skeleton.json> --out <dir> --keep-pages [--atlas <path>] [--profile spine|
|
|
1405
|
+
`rigc repack <dir | skeleton.json> --out <dir> [--atlas <path>] [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] [--profile spine|rig-play] [--stage x,y,w,h] [--stage-box <slot>] [--accept-skeleton-differences] [--report <file>]`,
|
|
1406
|
+
`rigc repack <dir | skeleton.json> --out <dir> --keep-pages [--atlas <path>] [--profile spine|rig-play] [--stage x,y,w,h] [--stage-box <slot>] [--report <file>] (skeleton.model.json alone, pages kept)`,
|
|
1406
1407
|
],
|
|
1407
1408
|
flags: ['out', 'atlas', 'page-size', 'padding', 'page-edges', 'pack-shape', 'profile', 'stage', 'stage-box', 'accept-skeleton-differences', 'keep-pages', 'report'],
|
|
1408
1409
|
overrides: {
|
|
@@ -1469,7 +1470,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1469
1470
|
runtime: { for: 'the gate it re-runs is the round trip through it', rerunsTheGate: true },
|
|
1470
1471
|
spineFormat: true,
|
|
1471
1472
|
usage: [
|
|
1472
|
-
'rigc validate <dir | skeleton.json> [--atlas <path>] [--profile spine|
|
|
1473
|
+
'rigc validate <dir | skeleton.json> [--atlas <path>] [--profile spine|rig-play]',
|
|
1473
1474
|
'rigc validate --cut <name> --cuts <cuts.json> (also re-derives declared durations)',
|
|
1474
1475
|
],
|
|
1475
1476
|
flags: ['atlas', 'profile', 'cut', 'cuts', 'rig', 'motion', 'out', 'manifest', 'images'],
|
|
@@ -1815,17 +1816,17 @@ export function commandHelp(name: string, docs: readonly CommandDoc[] = COMMANDS
|
|
|
1815
1816
|
* `--profile` paragraph states each profile's rule count, which only the
|
|
1816
1817
|
* validator knows (`CliEntry.profileRules`).
|
|
1817
1818
|
*/
|
|
1818
|
-
const USAGE_PARAGRAPHS: ReadonlyArray<{ about: readonly string[] | { flag: string }; lines: (rules: (profile: 'spine' | '
|
|
1819
|
+
const USAGE_PARAGRAPHS: ReadonlyArray<{ about: readonly string[] | { flag: string }; lines: (rules: (profile: 'spine' | 'rig-play') => number) => string[] }> = [
|
|
1819
1820
|
{
|
|
1820
1821
|
about: ['build', 'validate', 'bench'],
|
|
1821
1822
|
lines: (rules) => [
|
|
1822
|
-
'build, validate and bench take --profile spine|
|
|
1823
|
+
'build, validate and bench take --profile spine|rig-play:',
|
|
1823
1824
|
' spine is this valid Spine 4.3 that any runtime plays correctly?',
|
|
1824
1825
|
` THE DEFAULT — ${rules('spine')} rules, and the question the output answers when`,
|
|
1825
1826
|
' you import it into the Spine editor.',
|
|
1826
|
-
'
|
|
1827
|
-
` all ${rules('
|
|
1828
|
-
`${rules('
|
|
1827
|
+
' rig-play the above, plus this project\'s renderer and archetype policy:',
|
|
1828
|
+
` all ${rules('rig-play')} rules, opt-in. Those extra ` +
|
|
1829
|
+
`${rules('rig-play') - rules('spine')} fire on real, correct,`,
|
|
1829
1830
|
' editor-produced Spine data, so they are somebody\'s policy rather',
|
|
1830
1831
|
' than anybody\'s validity.',
|
|
1831
1832
|
'',
|
|
@@ -1955,7 +1956,7 @@ const USAGE_PARAGRAPHS: ReadonlyArray<{ about: readonly string[] | { flag: strin
|
|
|
1955
1956
|
* lines, then every paragraph about only those commands (`USAGE_PARAGRAPHS`).
|
|
1956
1957
|
* `checkout` is the file a source checkout runs this entry as.
|
|
1957
1958
|
*/
|
|
1958
|
-
export function usageText(docs: readonly CommandDoc[], checkout: string, rules: ((profile: 'spine' | '
|
|
1959
|
+
export function usageText(docs: readonly CommandDoc[], checkout: string, rules: ((profile: 'spine' | 'rig-play') => number) | undefined): string {
|
|
1959
1960
|
const names = new Set(docs.map((doc) => doc.name));
|
|
1960
1961
|
const takes = (flag: string): boolean => docs.some((doc) => doc.flags.includes(flag));
|
|
1961
1962
|
const lines = [
|
|
@@ -2002,6 +2003,13 @@ export function usageText(docs: readonly CommandDoc[], checkout: string, rules:
|
|
|
2002
2003
|
export function readProfile(flags: Record<string, string>): AssertionProfile {
|
|
2003
2004
|
const raw = flags.profile;
|
|
2004
2005
|
if (raw === undefined) return CLI_DEFAULT_PROFILE;
|
|
2006
|
+
// A former name resolves here and nowhere else (issue #1277): past this line
|
|
2007
|
+
// the alias does not exist, so it cannot select a rule set of its own.
|
|
2008
|
+
const aliased = Object.hasOwn(PROFILE_ALIASES, raw) ? PROFILE_ALIASES[raw] : undefined;
|
|
2009
|
+
if (aliased !== undefined) {
|
|
2010
|
+
console.error(profileAliasLine(raw, aliased));
|
|
2011
|
+
return aliased;
|
|
2012
|
+
}
|
|
2005
2013
|
const found = VALIDATE_PROFILES.find((p) => p === raw);
|
|
2006
2014
|
if (!found) throw new UsageError(`--profile ${JSON.stringify(raw)}; known profiles: ${VALIDATE_PROFILES.join(', ')}`);
|
|
2007
2015
|
return found;
|
|
@@ -2633,7 +2641,7 @@ export interface CliEntry {
|
|
|
2633
2641
|
/** Every command this entry runs, by name — exactly its set, or the run refuses to start. */
|
|
2634
2642
|
runs: Readonly<Record<string, CommandRun>>;
|
|
2635
2643
|
/** Each profile's rule count, for the `--profile` paragraph; the validator's, so only an entry that links it states it. */
|
|
2636
|
-
profileRules?: (profile: 'spine' | '
|
|
2644
|
+
profileRules?: (profile: 'spine' | 'rig-play') => number;
|
|
2637
2645
|
/** The refusals of bodies only this entry registers (`CliRefusal`). */
|
|
2638
2646
|
refusals?: readonly CliRefusal[];
|
|
2639
2647
|
}
|
|
@@ -818,4 +818,4 @@ export const SPINE_COMMAND_REFUSALS: readonly CliRefusal[] = [
|
|
|
818
818
|
];
|
|
819
819
|
|
|
820
820
|
/** Each profile's rule count — the validator's, which the usage's `--profile` paragraph states. */
|
|
821
|
-
export const PROFILE_RULES = (profile: 'spine' | '
|
|
821
|
+
export const PROFILE_RULES = (profile: 'spine' | 'rig-play'): number => assertionCountForProfile(profile);
|
package/src/compile.ts
CHANGED
|
@@ -1420,7 +1420,7 @@ const SLIDER_TRACKS: Record<string, ValueTrackShape> = {
|
|
|
1420
1420
|
* refused by `A12_NO_DARK_COLOR`, and that is not a contradiction — A12 is filed
|
|
1421
1421
|
* `renderer` in `ASSERTION_KIND`, so it states what ONE renderer ignores rather
|
|
1422
1422
|
* than what is wrong. The `spine` profile `build` runs reports it `PROF` and
|
|
1423
|
-
* never applies it; `--profile
|
|
1423
|
+
* never applies it; `--profile rig-play` is where it fires. Emitting a
|
|
1424
1424
|
* construct one consumer drops is exactly what a profile is for.
|
|
1425
1425
|
*/
|
|
1426
1426
|
export const SLOT_TRACKS: Record<string, 'attachment' | SlotColourShape> = {
|
package/src/rig.ts
CHANGED
|
@@ -426,7 +426,7 @@ export interface RigSlot {
|
|
|
426
426
|
attachment?: string | null;
|
|
427
427
|
/** `rrggbbaa`. Default opaque white. */
|
|
428
428
|
color?: string;
|
|
429
|
-
/** Two-colour tint, `rrggbb`. 🚫 `A12_NO_DARK_COLOR` under `
|
|
429
|
+
/** Two-colour tint, `rrggbb`. 🚫 `A12_NO_DARK_COLOR` under `rig-play`. */
|
|
430
430
|
dark?: string;
|
|
431
431
|
/** Default `normal`. Only the first letter's case is free (`RigSlotBlend`). */
|
|
432
432
|
blend?: RigSlotBlend;
|