rig-c 0.0.0-stage → 2.20.4
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/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +30 -0
- package/NOTICE.md +145 -0
- package/README.md +817 -3
- package/bin/rigc.cjs +83 -0
- package/cli.ts +61 -0
- package/cli_core.ts +46 -0
- package/docs/AUTHORING.md +9923 -0
- package/docs/FACE.md +1948 -0
- package/docs/INGEST.md +1488 -0
- package/docs/MOTION.md +1241 -0
- package/docs/PROMPTING.md +109 -0
- package/docs/RIGGING.md +1441 -0
- package/docs/SPEC_COVERAGE.md +357 -0
- package/package.json +108 -4
- package/skills/rigc/SKILL.md +133 -0
- package/skills/rigc-face/SKILL.md +60 -0
- package/skills/rigc-ingest/SKILL.md +78 -0
- package/skills/rigc-motion/SKILL.md +51 -0
- package/skills/rigc-rigging/SKILL.md +49 -0
- package/src/areaband.ts +159 -0
- package/src/assertions/bodies/a01.ts +23 -0
- package/src/assertions/bodies/a02.ts +21 -0
- package/src/assertions/bodies/a03.ts +27 -0
- package/src/assertions/bodies/a04.ts +40 -0
- package/src/assertions/bodies/a05.ts +56 -0
- package/src/assertions/bodies/a06.ts +245 -0
- package/src/assertions/bodies/a07.ts +68 -0
- package/src/assertions/bodies/a08.ts +76 -0
- package/src/assertions/bodies/a09.ts +82 -0
- package/src/assertions/bodies/a10.ts +116 -0
- package/src/assertions/bodies/a11.ts +15 -0
- package/src/assertions/bodies/a12.ts +30 -0
- package/src/assertions/bodies/a13.ts +51 -0
- package/src/assertions/bodies/a14.ts +35 -0
- package/src/assertions/bodies/a15.ts +97 -0
- package/src/assertions/bodies/a16.ts +24 -0
- package/src/assertions/bodies/a17.ts +26 -0
- package/src/assertions/bodies/a18.ts +62 -0
- package/src/assertions/bodies/a19.ts +404 -0
- package/src/assertions/bodies/a20.ts +122 -0
- package/src/assertions/bodies/a21.ts +190 -0
- package/src/assertions/bodies/a22.ts +39 -0
- package/src/assertions/bodies/a23.ts +305 -0
- package/src/assertions/bodies/a24.ts +68 -0
- package/src/assertions/bodies/a25.ts +39 -0
- package/src/assertions/bodies/a26.ts +61 -0
- package/src/assertions/bodies/a27.ts +33 -0
- package/src/assertions/bodies/a28.ts +70 -0
- package/src/assertions/bodies/a29.ts +34 -0
- package/src/assertions/bodies/a30.ts +50 -0
- package/src/assertions/bodies/a31.ts +61 -0
- package/src/assertions/bodies/a32.ts +44 -0
- package/src/assertions/bodies/a33.ts +110 -0
- package/src/assertions/bodies/a34.ts +133 -0
- package/src/assertions/bodies/a35.ts +160 -0
- package/src/assertions/bodies/a36.ts +81 -0
- package/src/assertions/bodies/a37.ts +77 -0
- package/src/assertions/bodies/a38.ts +73 -0
- package/src/assertions/bodies/a39.ts +303 -0
- package/src/assertions/bodies/a40.ts +128 -0
- package/src/assertions/bodies/a42.ts +97 -0
- package/src/assertions/bodies/a43.ts +181 -0
- package/src/assertions/bodies/a44.ts +23 -0
- package/src/assertions/bodies/a45.ts +172 -0
- package/src/assertions/bodies/a46.ts +224 -0
- package/src/assertions/bodies/a47.ts +126 -0
- package/src/assertions/bodies/a48.ts +83 -0
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/constraint_words.ts +169 -0
- package/src/assertions/emitted/index.ts +148 -0
- package/src/assertions/facts/animated_bones.ts +30 -0
- package/src/assertions/facts/animation_durations.ts +37 -0
- package/src/assertions/facts/atlas_pages.ts +19 -0
- package/src/assertions/facts/atlas_regions.ts +52 -0
- package/src/assertions/facts/bone_timelines.ts +37 -0
- package/src/assertions/facts/constraint_targets.ts +56 -0
- package/src/assertions/facts/constraints.ts +155 -0
- package/src/assertions/facts/deform_survey.ts +27 -0
- package/src/assertions/facts/event_keys.ts +55 -0
- package/src/assertions/facts/linked_meshes.ts +38 -0
- package/src/assertions/facts/mesh_attachments.ts +100 -0
- package/src/assertions/facts/region_joins.ts +34 -0
- package/src/assertions/facts/sequences.ts +85 -0
- package/src/assertions/facts/skeleton_roster.ts +45 -0
- package/src/assertions/facts/skin_entries.ts +37 -0
- package/src/assertions/facts/skin_members.ts +53 -0
- package/src/assertions/facts/slider_composition.ts +78 -0
- package/src/assertions/facts/slot_colour.ts +43 -0
- package/src/assertions/facts/stage.ts +27 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/facts/stepped_poses.ts +74 -0
- package/src/assertions/facts/two_colour.ts +52 -0
- package/src/assertions/facts/vertex_polygons.ts +53 -0
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/harness.ts +109 -0
- package/src/assertions/inward_advance.ts +58 -0
- package/src/assertions/kinds.ts +105 -0
- package/src/assertions/mesh_kinds.ts +56 -0
- package/src/assertions/model/animated_bones.ts +38 -0
- package/src/assertions/model/animation_durations.ts +57 -0
- package/src/assertions/model/atlas_pages.ts +15 -0
- package/src/assertions/model/atlas_regions.ts +76 -0
- package/src/assertions/model/bone_timelines.ts +58 -0
- package/src/assertions/model/constraint_targets.ts +82 -0
- package/src/assertions/model/constraints.ts +233 -0
- package/src/assertions/model/declared.ts +125 -0
- package/src/assertions/model/deform_survey.ts +24 -0
- package/src/assertions/model/event_keys.ts +45 -0
- package/src/assertions/model/given.ts +45 -0
- package/src/assertions/model/index.ts +398 -0
- package/src/assertions/model/linked_meshes.ts +24 -0
- package/src/assertions/model/mesh_attachments.ts +119 -0
- package/src/assertions/model/parse.ts +146 -0
- package/src/assertions/model/region_joins.ts +67 -0
- package/src/assertions/model/runtime_timelines.ts +78 -0
- package/src/assertions/model/sequences.ts +157 -0
- package/src/assertions/model/skeleton_roster.ts +23 -0
- package/src/assertions/model/skin_entries.ts +69 -0
- package/src/assertions/model/skin_members.ts +64 -0
- package/src/assertions/model/slider_composition.ts +193 -0
- package/src/assertions/model/slot_colour.ts +81 -0
- package/src/assertions/model/stage.ts +28 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/assertions/model/stepped_poses.ts +105 -0
- package/src/assertions/model/two_colour.ts +61 -0
- package/src/assertions/model/vertex_polygons.ts +72 -0
- package/src/assertions/reasons.ts +129 -0
- package/src/assertions/region_lookups.ts +61 -0
- package/src/assertions/report.ts +189 -0
- package/src/assertions/values.ts +39 -0
- package/src/atlas.ts +2870 -0
- package/src/ballot.ts +866 -0
- package/src/bonedist.ts +643 -0
- package/src/chainfit.ts +2752 -0
- package/src/chains.ts +170 -0
- package/src/check.ts +4303 -0
- package/src/checkpics.ts +295 -0
- package/src/cli/core_commands.ts +1627 -0
- package/src/cli/repack.ts +414 -0
- package/src/cli/shared.ts +2776 -0
- package/src/cli/spine_commands.ts +820 -0
- package/src/compile.ts +9414 -0
- package/src/core/additive.ts +458 -0
- package/src/core/animation.ts +1050 -0
- package/src/core/clipping.ts +696 -0
- package/src/core/constraints.ts +1876 -0
- package/src/core/constraints_path.ts +964 -0
- package/src/core/constraints_physics.ts +881 -0
- package/src/core/constraints_slider.ts +635 -0
- package/src/core/deform.ts +613 -0
- package/src/core/draw_order.ts +125 -0
- package/src/core/events.ts +135 -0
- package/src/core/hooks.ts +249 -0
- package/src/core/index.ts +1400 -0
- package/src/core/raw.ts +739 -0
- package/src/core/skins.ts +129 -0
- package/src/core/uvs.ts +469 -0
- package/src/core/vertices.ts +490 -0
- package/src/core/walk.ts +197 -0
- package/src/core/world.ts +289 -0
- package/src/correspondence.ts +15 -0
- package/src/deformbuild.ts +60 -0
- package/src/deformgen.ts +630 -0
- package/src/deformmeasure.ts +732 -0
- package/src/deformreport.ts +373 -0
- package/src/deformstructure.ts +386 -0
- package/src/deformsurvey.ts +2162 -0
- package/src/depth.ts +784 -0
- package/src/diff.ts +2252 -0
- package/src/emit.ts +134 -0
- package/src/emit_spine.ts +854 -0
- package/src/errors.ts +53 -0
- package/src/framing.ts +819 -0
- package/src/generation.ts +139 -0
- package/src/ingest.ts +2293 -0
- package/src/json-position.ts +253 -0
- package/src/keyorder.ts +587 -0
- package/src/keys.ts +486 -0
- package/src/ladder.ts +121 -0
- package/src/mesh.ts +2382 -0
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +2042 -0
- package/src/meshrasters.ts +944 -0
- package/src/meshreduce.ts +1425 -0
- package/src/model.ts +1245 -0
- package/src/motion.ts +809 -0
- package/src/nonfinite.ts +54 -0
- package/src/package_meta.ts +48 -0
- package/src/png.ts +297 -0
- package/src/pose.ts +2324 -0
- package/src/preview.ts +434 -0
- package/src/region_joins.ts +54 -0
- package/src/render.ts +1013 -0
- package/src/render_core.ts +871 -0
- package/src/render_shared.ts +2958 -0
- package/src/repack.ts +495 -0
- package/src/rig.ts +2941 -0
- package/src/slots.ts +892 -0
- package/src/spine_side.ts +138 -0
- package/src/timelines.ts +837 -0
- package/src/trackgen.ts +364 -0
- package/src/transform.ts +310 -0
- package/src/types.ts +1797 -0
- package/src/validate.ts +3875 -0
- package/tools/contact.ts +126 -0
- package/tools/editor_roundtrip.ts +1641 -0
- package/tools/font5x7.ts +101 -0
- package/tools/measure_contact_depth.ts +105 -0
- package/tools/plate.ts +508 -0
- package/tools/png_probe.mjs +72 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a SKIP says, by what the assertion was denied (issues #568, #580, #608).
|
|
3
|
+
*
|
|
4
|
+
* Moved here from `src/validate.ts` by issue #1025 (step 4c of #380), words
|
|
5
|
+
* unchanged: an assertion body now lives in a module that links nothing from
|
|
6
|
+
* the runtime (`./bodies/`), and the bodies print these sentences on both
|
|
7
|
+
* sides — over what spine-core loaded and over the model document. A sentence
|
|
8
|
+
* kept in the file that links spine-core could not be read by the second.
|
|
9
|
+
* `src/validate.ts` re-exports every one, so nothing that imported them from
|
|
10
|
+
* there moves.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* What a SKIP says when `A00_ROUNDTRIP_PARSE` handed an assertion nothing to
|
|
15
|
+
* look at.
|
|
16
|
+
*
|
|
17
|
+
* Two of them rather than one, because what an assertion was denied is the whole
|
|
18
|
+
* content of its SKIP: `A20` wanted the loaded skeleton and `A06` wanted the
|
|
19
|
+
* loaded atlas, and a reader who is told which can tell a rule that is waiting
|
|
20
|
+
* on the parse from one that is waiting on the art. Both point at A00 instead of
|
|
21
|
+
* restating its detail, which the FAIL row prints in full.
|
|
22
|
+
*
|
|
23
|
+
* Exported so a control compares against them rather than quoting them — the
|
|
24
|
+
* same reason `ASSERTION_NAMES` is exported.
|
|
25
|
+
*/
|
|
26
|
+
export const SKIP_NO_SKELETON = 'the round trip did not produce a skeleton to measure (A00 owns that failure)';
|
|
27
|
+
export const SKIP_NO_ATLAS = 'the round trip did not produce an atlas to measure (A00 owns that failure)';
|
|
28
|
+
/**
|
|
29
|
+
* The third of them, and it is NOT waiting on A00 (issue #608): the atlas parsed
|
|
30
|
+
* and it has no pages, which is what a compile that measured no art writes. The
|
|
31
|
+
* four rules whose only subject is a page — A06, A17, A19, A27 — have nothing to
|
|
32
|
+
* look at, and an empty loop walking out of `check()` is reported as a PASS.
|
|
33
|
+
*/
|
|
34
|
+
/*
|
|
35
|
+
* **An empty subject list: SKIP or PASS, and which one is derived (issue #580).**
|
|
36
|
+
*
|
|
37
|
+
* `check()` records a pass when a body runs to its end without a `fail()` or a
|
|
38
|
+
* `skip()`, so a body whose main construct is a loop over a list that is EMPTY
|
|
39
|
+
* passes having measured nothing — the vacuous green one ring out from the bare
|
|
40
|
+
* `return` guards issue #568 converted. Two different things can be true of such
|
|
41
|
+
* a loop, and the criterion decides between them by reading the rule's own name
|
|
42
|
+
* and its `fail()` sentences rather than by preference:
|
|
43
|
+
*
|
|
44
|
+
* * A name of the form ⟨subject⟩_⟨property⟩ — `REGION_WIDTH_HEIGHT_FINITE`,
|
|
45
|
+
* `MESH_TRIANGLES_AND_ENCODING`, `PHYSICS_CONSTRAINT_EFFECTIVE`,
|
|
46
|
+
* `ATLAS_PAGE_SIZE_MATCHES_PNG` — quantifies over the subject it names, and
|
|
47
|
+
* its `fail()` names a member of that subject, the value found and the value
|
|
48
|
+
* required. With no member, nothing was measured: it **SKIPs**, and the
|
|
49
|
+
* reason names the subject that was absent.
|
|
50
|
+
* * A name of the form NO_⟨construct⟩ — `NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS`,
|
|
51
|
+
* `NO_BONE_TRANSFORM_KEY`, `NO_CLIPPING_ATTACHMENTS`, `NO_DARK_COLOR`,
|
|
52
|
+
* `NO_FULL_FRAME_MESH` — quantifies over occurrences of something a correct
|
|
53
|
+
* artifact has NONE of, and its `fail()` reports that the construct is
|
|
54
|
+
* present rather than measuring a value on it. Zero occurrences IS the
|
|
55
|
+
* measurement, so it **PASSes**: the loop is a search of the artifact, and
|
|
56
|
+
* the artifact is what it measured.
|
|
57
|
+
* * A name carrying both halves takes each at its word.
|
|
58
|
+
* `A15_IDLE_NO_MESH_BONE_KEYS` is the pattern and already reads this way: no
|
|
59
|
+
* `idle` animation, or an `idle` with no bone timeline, is the named subject
|
|
60
|
+
* absent and SKIPs; no mesh-driving bone among the bones `idle` does key is
|
|
61
|
+
* the construct absent and passes. `A10_NO_NAN_AFTER_STEPPING` was the same
|
|
62
|
+
* shape until issue #902, and is the next bullet's now: #882 gave it a
|
|
63
|
+
* setup-pose clause whose subject is the bones, beside the stepping clause
|
|
64
|
+
* whose subject is the animations.
|
|
65
|
+
* * An assertion with more than one clause SKIPs only when EVERY clause had
|
|
66
|
+
* nothing to measure, which is the shape `A09`, `A33` and `A38` already
|
|
67
|
+
* carry (`polygons.length === 0 && endsChecked === 0`), and `A10` since #902.
|
|
68
|
+
* `A13_MESH_BUDGET` is why the clause is stated: a declared slot budget is
|
|
69
|
+
* measured against a count of mesh slots, and zero is a count, so that half
|
|
70
|
+
* passes on a rig with no mesh — while a rig that declares only a TRIANGLE
|
|
71
|
+
* budget has nothing left to measure and skips.
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ What the criterion is not allowed to become is a table of assertions with
|
|
74
|
+
* their verdicts written beside them. Every row above is decided by reading the
|
|
75
|
+
* name and the sentences the assertion already prints, so a rule added tomorrow
|
|
76
|
+
* is decided by the same reading and nobody re-opens this question.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What a SKIP says when the artifact carries no member of the subject a rule
|
|
81
|
+
* quantifies over (issue #580), by subject.
|
|
82
|
+
*
|
|
83
|
+
* One constant per SUBJECT rather than one per assertion, for the reason the two
|
|
84
|
+
* above are two: what the rule was denied is the whole content of its SKIP, and
|
|
85
|
+
* three rules denied the same thing should say so in the same words. Exported so
|
|
86
|
+
* a control compares against them rather than quoting them.
|
|
87
|
+
*/
|
|
88
|
+
export const SKIP_NO_REGION_ATTACHMENT = 'the skeleton carries no region attachment';
|
|
89
|
+
export const SKIP_NO_MESH_ATTACHMENT = 'the skeleton carries no mesh attachment';
|
|
90
|
+
export const SKIP_NO_ANIMATION = 'the skeleton carries no animation';
|
|
91
|
+
/**
|
|
92
|
+
* A10's (issue #902): its setup clause reads the bones and its stepping clause
|
|
93
|
+
* the animations, so it skips only when the skeleton carries neither — and the
|
|
94
|
+
* sentence is `SKIP_NO_ANIMATION`'s with the second subject added, so the two
|
|
95
|
+
* cannot drift into different words for the same absence.
|
|
96
|
+
*/
|
|
97
|
+
export const SKIP_NO_POSE = `${SKIP_NO_ANIMATION} and no bone, so there is no setup pose to read and nothing to step`;
|
|
98
|
+
export const SKIP_NO_TIMELINE = 'no animation here carries a timeline';
|
|
99
|
+
export const SKIP_NO_PHYSICS_CONSTRAINT = 'the skeleton declares no physics constraint';
|
|
100
|
+
export const SKIP_NO_ATLAS_PAGE = 'the atlas declares no page';
|
|
101
|
+
export const SKIP_NO_ATLAS_REGION = 'the atlas declares no region';
|
|
102
|
+
/** A49's (issue #1099): the subject is a pair of regions on one page, and an atlas of one-region pages carries none. */
|
|
103
|
+
export const SKIP_NO_ATLAS_REGION_PAIR = 'no page of the atlas carries two regions, so there is no pair of footprints to hold apart';
|
|
104
|
+
export const SKIP_NO_ATTACHMENT_REGION_JOIN =
|
|
105
|
+
'no attachment names a region and the atlas declares none, so there is no attachment-to-region join to hold';
|
|
106
|
+
export const SKIP_NO_TWO_COLOR_TINT =
|
|
107
|
+
'no slot declares a "dark" colour and no animation keys an "rgba2" or "rgb2" timeline, so there is no two-colour tint to read back';
|
|
108
|
+
export const SKIP_NO_SEPARABLE_COLOR =
|
|
109
|
+
'no animation keys an "rgb" or "alpha" timeline, so there is no separable slot colour to read back';
|
|
110
|
+
export const SKIP_NO_LINKED_MESH = 'no attachment in this skeleton takes its geometry from another one';
|
|
111
|
+
export const SKIP_NO_SEQUENCE =
|
|
112
|
+
'no attachment carries a "sequence" block and no animation keys a "sequence" timeline, so there is no numbered series to read back';
|
|
113
|
+
/**
|
|
114
|
+
* A09's, which predates this list and joins it rather than being rewritten: it
|
|
115
|
+
* is the same fact about the same subject, and a control that compares against
|
|
116
|
+
* eight constants and quotes the ninth is a control with a hand-kept exception
|
|
117
|
+
* in it.
|
|
118
|
+
*/
|
|
119
|
+
export const SKIP_NO_DECLARED_DURATION =
|
|
120
|
+
'the motion spec declares no animations and the skeleton has none — a static rig has no duration to compare';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The model side's `SKIP_NO_SKELETON` (issue #1025): the reader refused the
|
|
124
|
+
* document, or the document names a region its pages lack, so a body that
|
|
125
|
+
* reads the model has nothing to read. It names the two model-side parse
|
|
126
|
+
* rules rather than A00, because on this side there is no round trip — the
|
|
127
|
+
* reader and the region rule are what stand in its place (`./model/parse.ts`).
|
|
128
|
+
*/
|
|
129
|
+
export const SKIP_NO_MODEL = 'the model side read no document to measure (A00_MODEL_READ or A00_MODEL_REGIONS_ON_PAGES owns that failure)';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The region names one skin entry will make a loader look up (issue #1025,
|
|
3
|
+
* step 4c of #380) — moved here from `src/validate.ts` unchanged, because A08's
|
|
4
|
+
* body now runs on both sides and the model side links nothing from the
|
|
5
|
+
* runtime. `src/validate.ts` re-exports both names, so nothing that imported
|
|
6
|
+
* them from there moves.
|
|
7
|
+
*/
|
|
8
|
+
import { frameRegionName } from '../core/uvs.ts';
|
|
9
|
+
import { isObj } from './values.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The atlas region names one raw skin entry will make the loader look up — or
|
|
13
|
+
* `null` when the file states a sequence this walk cannot predict.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Measured against the loader rather than reasoned out, because a join that
|
|
16
|
+
* merely looks right is the thing A08 exists to refuse. `readSequence`
|
|
17
|
+
* (`dist/SkeletonJson.js:641-649`) turns an absent or null `sequence` into
|
|
18
|
+
* `new Sequence(1, false)` — one lookup, at the bare path — and a present one
|
|
19
|
+
* into `new Sequence(count ?? 0, true)`, so a sequence map with no `count`
|
|
20
|
+
* looks up nothing at all. Each frame's name is the core's `frameRegionName`
|
|
21
|
+
* (`src/core/uvs.ts`): `start + i`, left-padded with zeros to `digits`, after
|
|
22
|
+
* the path (issue #1015). `PS127` records what the loader asks for and compares,
|
|
23
|
+
* and the core suite holds the frame names to `Sequence.getPath` over 7,680
|
|
24
|
+
* cases.
|
|
25
|
+
*
|
|
26
|
+
* Nothing in `examples/` carries a `sequence` (measured: 0 occurrences across
|
|
27
|
+
* all twelve editor exports), so without this the assertion would have read a
|
|
28
|
+
* sequence's base path as a region name and refused correct foreign data —
|
|
29
|
+
* `A21_MESH_RIM_PINNED`'s old `|| 'ring'` default, one file over.
|
|
30
|
+
*/
|
|
31
|
+
export function attachmentRegionLookups(sequence: unknown, path: string): string[] | null {
|
|
32
|
+
if (sequence === undefined || sequence === null) return [path];
|
|
33
|
+
if (!isObj(sequence)) return null;
|
|
34
|
+
const whole = (value: unknown, fallback: number): number | null => {
|
|
35
|
+
if (value === undefined) return fallback;
|
|
36
|
+
return typeof value === 'number' && Number.isInteger(value) ? value : null;
|
|
37
|
+
};
|
|
38
|
+
const count = whole(sequence.count, 0);
|
|
39
|
+
const start = whole(sequence.start, 1);
|
|
40
|
+
const digits = whole(sequence.digits, 0);
|
|
41
|
+
if (count === null || start === null || digits === null || count < 0) return null;
|
|
42
|
+
const series = { count, start, digits, setup: 0 };
|
|
43
|
+
const lookups: string[] = [];
|
|
44
|
+
for (let i = 0; i < count; i++) lookups.push(frameRegionName(path, series, i));
|
|
45
|
+
return lookups;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One skin entry's join onto the atlas, as the loader will perform it. */
|
|
49
|
+
export interface AttachmentRegionJoin {
|
|
50
|
+
skin: string;
|
|
51
|
+
slot: string;
|
|
52
|
+
placeholder: string;
|
|
53
|
+
/** The attachment's own name — the entry's `name` when it states one, else the placeholder. */
|
|
54
|
+
name: string;
|
|
55
|
+
/**
|
|
56
|
+
* Every atlas region name the loader will ask this atlas for, in the order it
|
|
57
|
+
* asks. Empty for a `sequence` with no `count`, and `null` for a sequence map
|
|
58
|
+
* this walk will not guess at.
|
|
59
|
+
*/
|
|
60
|
+
lookups: string[] | null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The profiles a gate judges under, the one the CLI uses when `--profile` is
|
|
3
|
+
* absent, and the printer of a gate's report — moved here unchanged from
|
|
4
|
+
* `src/validate.ts` (issue #1060), which re-exports all three: the entry that
|
|
5
|
+
* links none of spine-core gates a build on the model side
|
|
6
|
+
* (`./model/index.ts`) and prints its report with the same printer, and
|
|
7
|
+
* `src/validate.ts` links the runtime. Nothing here links it.
|
|
8
|
+
*/
|
|
9
|
+
import type { AssertionProfile } from './kinds.ts';
|
|
10
|
+
import type { VerdictLists } from './harness.ts';
|
|
11
|
+
|
|
12
|
+
export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', 'spine-html'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the CLI uses when `--profile` is absent — and ONLY the CLI. This is not
|
|
16
|
+
* `validate()`'s default; that function has none (`ValidateProfile` in
|
|
17
|
+
* `src/validate.ts` says why).
|
|
18
|
+
*
|
|
19
|
+
* `spine` since issue #221. The published package's pitch is "the output imports
|
|
20
|
+
* into the Spine editor", which is exactly the question `spine` asks, and a
|
|
21
|
+
* stranger's first build was being judged instead against one renderer's policy
|
|
22
|
+
* and one project's canvas budget — 14 rules they have no stake in, with the
|
|
23
|
+
* escape hatch documented only in prose. Defaults beat prose. `spine-html` is
|
|
24
|
+
* still one flag away, and every report names the profile that judged it.
|
|
25
|
+
*/
|
|
26
|
+
export const CLI_DEFAULT_PROFILE: AssertionProfile = 'spine';
|
|
27
|
+
|
|
28
|
+
export function reportLines(report: VerdictLists & { profile: AssertionProfile }): string[] {
|
|
29
|
+
const lines: string[] = [];
|
|
30
|
+
// The profile goes FIRST and names what it left out. A report that says
|
|
31
|
+
// "green" without saying which rulebook produced it is the one thing this
|
|
32
|
+
// switch could make worse than no switch: `--profile spine` green means
|
|
33
|
+
// "valid Spine", never "passes the renderer policy".
|
|
34
|
+
const renderer = report.profileSkipped.filter((p) => p.kind === 'renderer').length;
|
|
35
|
+
const archetype = report.profileSkipped.filter((p) => p.kind === 'archetype').length;
|
|
36
|
+
lines.push(
|
|
37
|
+
report.profileSkipped.length === 0
|
|
38
|
+
? ` .. profile ${report.profile} — every assertion applies`
|
|
39
|
+
: ` .. profile ${report.profile} — ${renderer} renderer-policy and ${archetype} archetype assertion(s) do not apply`,
|
|
40
|
+
);
|
|
41
|
+
for (const name of report.passed) lines.push(` PASS ${name}`);
|
|
42
|
+
for (const s of report.skipped) lines.push(` SKIP ${s.assertion}: ${s.reason}`);
|
|
43
|
+
for (const p of report.profileSkipped) lines.push(` PROF ${p.assertion}: ${p.kind} rule, not in profile "${report.profile}"`);
|
|
44
|
+
for (const f of report.failures) lines.push(` FAIL ${f.assertion}: ${f.detail}`);
|
|
45
|
+
// How many of them MEASURED anything, which is the figure the rows above do
|
|
46
|
+
// not hand a reader (issue #568). Counting `PASS` lines answers a different
|
|
47
|
+
// question — before the sweep that closed #568 a run could print seven of
|
|
48
|
+
// them over a candidate on which five rules had not executed at all.
|
|
49
|
+
//
|
|
50
|
+
// ⚠️ Every figure here is a count of ASSERTIONS and not of rows, which is why
|
|
51
|
+
// the failed side is a Set: `fail()` is called once per finding, so one
|
|
52
|
+
// assertion can print six `FAIL` lines, and a line-count would report 47 of
|
|
53
|
+
// 42. The four buckets partition `ASSERTION_NAMES`, so the total is derived
|
|
54
|
+
// by adding them rather than stated — a `42` written here would be the one
|
|
55
|
+
// number in the report that no run could contradict.
|
|
56
|
+
//
|
|
57
|
+
// The figures are `gateSummary`'s, the one computation the line and the
|
|
58
|
+
// `--report` document (`buildReportGate`) both spell (issue #1213).
|
|
59
|
+
const s = gateSummary(report);
|
|
60
|
+
lines.push(
|
|
61
|
+
` .. ${s.assertions} assertions: ${s.measured} measured (${s.passed} passed, ${s.failed} failed), ` +
|
|
62
|
+
`${s.skipped} skipped, ${s.notInProfile} not in profile "${s.profile}"`,
|
|
63
|
+
);
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The figures the summary line states, as values: what `reportLines` prints
|
|
69
|
+
* as its last line and what the `--report` document carries as a gate's
|
|
70
|
+
* `summary` — one computation, so the two cannot disagree. Every figure counts
|
|
71
|
+
* assertions, not rows (the comment in `reportLines` says why `failed` is a
|
|
72
|
+
* set), and `assertions` is the sum of the four buckets, never a constant.
|
|
73
|
+
*/
|
|
74
|
+
export interface GateSummary {
|
|
75
|
+
assertions: number;
|
|
76
|
+
measured: number;
|
|
77
|
+
passed: number;
|
|
78
|
+
failed: number;
|
|
79
|
+
skipped: number;
|
|
80
|
+
notInProfile: number;
|
|
81
|
+
profile: AssertionProfile;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function gateSummary(report: VerdictLists & { profile: AssertionProfile }): GateSummary {
|
|
85
|
+
const failed = new Set(report.failures.map((f) => f.assertion)).size;
|
|
86
|
+
const measured = report.passed.length + failed;
|
|
87
|
+
return {
|
|
88
|
+
assertions: measured + report.skipped.length + report.profileSkipped.length,
|
|
89
|
+
measured,
|
|
90
|
+
passed: report.passed.length,
|
|
91
|
+
failed,
|
|
92
|
+
skipped: report.skipped.length,
|
|
93
|
+
notInProfile: report.profileSkipped.length,
|
|
94
|
+
profile: report.profile,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// the build report document (`--report <file>`, issue #1213)
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The `spec` of the document `build --report` and `repack --report` write.
|
|
104
|
+
* Versioned and additive: a field is added under the same spec, and one that
|
|
105
|
+
* changes meaning or goes away moves the spec — a dependant that read `/1`
|
|
106
|
+
* keeps reading what `/1` promised.
|
|
107
|
+
*/
|
|
108
|
+
export const BUILD_REPORT_SPEC = 'build-report/1';
|
|
109
|
+
|
|
110
|
+
/** Which supplier judged the gates: the round trip through spine-core (`cli.ts`) or the model side and the rules restated over the emitted text (`cli_core.ts`). */
|
|
111
|
+
export type BuildReportSupplier = 'round-trip' | 'model';
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The core entry's `here:` line as values: how many rules ran on the model
|
|
115
|
+
* side over the document, how many of the round trip's own were restated over
|
|
116
|
+
* the emitted text, and the codes that did not run. `null` on the round trip,
|
|
117
|
+
* which prints no such line.
|
|
118
|
+
*/
|
|
119
|
+
export interface GateHere {
|
|
120
|
+
modelSide: number;
|
|
121
|
+
restated: number;
|
|
122
|
+
notRun: string[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* What one gate's report states, as values: the rows a reader takes off the
|
|
127
|
+
* `PASS`, `SKIP` and `FAIL` lines (the rule names, the SKIP reasons, the FAIL
|
|
128
|
+
* details, each in the order printed), the summary line's figures, the stats
|
|
129
|
+
* line's keys and values, and the core entry's `here:` line. A `PROF` row is
|
|
130
|
+
* counted in `summary.notInProfile` and not listed: nothing reads its row.
|
|
131
|
+
*/
|
|
132
|
+
export interface BuildReportGate {
|
|
133
|
+
/** `compiled` for the gate over the compile, `packed` for `--pack`'s second gate over the pages on disk. */
|
|
134
|
+
atlas: 'compiled' | 'packed';
|
|
135
|
+
passed: string[];
|
|
136
|
+
skipped: Array<{ code: string; reason: string }>;
|
|
137
|
+
failures: Array<{ code: string; detail: string }>;
|
|
138
|
+
summary: GateSummary;
|
|
139
|
+
stats: Record<string, number | string>;
|
|
140
|
+
here: GateHere | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* One `pack:` line as values. `coveredPct` is the figure the line prints, at
|
|
145
|
+
* its one decimal; `pageEdges` is the `--page-edges` the build packed under,
|
|
146
|
+
* which the line states as `, page edges free` or by its absence.
|
|
147
|
+
*/
|
|
148
|
+
export interface PackPageFigures {
|
|
149
|
+
page: string;
|
|
150
|
+
width: number;
|
|
151
|
+
height: number;
|
|
152
|
+
regions: number;
|
|
153
|
+
coveredPct: number;
|
|
154
|
+
padding: number;
|
|
155
|
+
pageEdges: 'pot' | 'free';
|
|
156
|
+
packShape: 'rect' | 'polygon';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The document, keys in the order written. Nothing in it is a time, a path or a machine's: two reports of one build are byte-identical. */
|
|
160
|
+
export interface BuildReportDocument {
|
|
161
|
+
spec: typeof BUILD_REPORT_SPEC;
|
|
162
|
+
command: 'build' | 'repack';
|
|
163
|
+
supplier: BuildReportSupplier;
|
|
164
|
+
gates: BuildReportGate[];
|
|
165
|
+
/** Every `pack:` line, in the order printed; `null` for a build that did not pack. */
|
|
166
|
+
pack: PackPageFigures[] | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** One gate's report as the document carries it — read off the lists `reportLines` prints, so a row is in the document exactly when its line is printed. */
|
|
170
|
+
export function buildReportGate(
|
|
171
|
+
atlas: BuildReportGate['atlas'],
|
|
172
|
+
report: VerdictLists & { profile: AssertionProfile },
|
|
173
|
+
here: GateHere | null,
|
|
174
|
+
): BuildReportGate {
|
|
175
|
+
return {
|
|
176
|
+
atlas,
|
|
177
|
+
passed: [...report.passed],
|
|
178
|
+
skipped: report.skipped.map((s) => ({ code: s.assertion, reason: s.reason })),
|
|
179
|
+
failures: report.failures.map((f) => ({ code: f.assertion, detail: f.detail })),
|
|
180
|
+
summary: gateSummary(report),
|
|
181
|
+
stats: { ...report.stats },
|
|
182
|
+
here,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The document's text: two-space JSON and a final newline, keys in the order `BuildReportDocument` states them. */
|
|
187
|
+
export function buildReportText(doc: BuildReportDocument): string {
|
|
188
|
+
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
189
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How an assertion body reads a value — the helpers the bodies under
|
|
3
|
+
* `./bodies/` share with the ones still in `src/validate.ts` (issue #1025,
|
|
4
|
+
* step 4c of #380), moved here unchanged so both read a value one way. Links
|
|
5
|
+
* nothing from the runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A JSON object as `JSON.parse` returns it. */
|
|
9
|
+
export type Json = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
/** A JSON object, and not an array or `null`. */
|
|
12
|
+
export function isObj(v: unknown): v is Json {
|
|
13
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The time to pose a key at so that the runtime is AT it: the later of the
|
|
18
|
+
* file's number and that number as spine-core stores it (issue #771).
|
|
19
|
+
*
|
|
20
|
+
* 🚨 Every timeline keeps its key times in a `Float32Array`
|
|
21
|
+
* (`Utils.newFloatArray`), and a key time the float cannot hold exactly is
|
|
22
|
+
* stored at the nearest one — for `0.2`, `0.20000000298…`, which is LATER than
|
|
23
|
+
* the double `0.2`. Stepped to the file's own number, a timeline is then just
|
|
24
|
+
* BEFORE its key: before a first key it writes the setup value (`time <
|
|
25
|
+
* frames[0]`), and past a stepped key it still holds the one before
|
|
26
|
+
* (`frames[i] > time`). That is a pose one float step from the key, not the
|
|
27
|
+
* key's — measured on the selftest's own `ingest_probe`, whose `alpha` key at
|
|
28
|
+
* 0.2 posed the setup 1.0 and was refused as `the key states value 0.4`.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ The later of the two rather than `Math.fround` alone: where the float
|
|
31
|
+
* rounds DOWN, the file's number is already past the stored key, and a runtime
|
|
32
|
+
* built without typed arrays stores the double itself — in both, the file's
|
|
33
|
+
* number is the one at or after the key. What a key time rounds to is the
|
|
34
|
+
* runtime's storage and not the file's statement, so a rule judging what a KEY
|
|
35
|
+
* states poses at the key; the rounding itself is nothing an author can repair.
|
|
36
|
+
*/
|
|
37
|
+
export function atStoredKey(time: number): number {
|
|
38
|
+
return Math.max(time, Math.fround(time));
|
|
39
|
+
}
|