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.
Files changed (213) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/LICENSE +30 -0
  4. package/NOTICE.md +145 -0
  5. package/README.md +817 -3
  6. package/bin/rigc.cjs +83 -0
  7. package/cli.ts +61 -0
  8. package/cli_core.ts +46 -0
  9. package/docs/AUTHORING.md +9923 -0
  10. package/docs/FACE.md +1948 -0
  11. package/docs/INGEST.md +1488 -0
  12. package/docs/MOTION.md +1241 -0
  13. package/docs/PROMPTING.md +109 -0
  14. package/docs/RIGGING.md +1441 -0
  15. package/docs/SPEC_COVERAGE.md +357 -0
  16. package/package.json +108 -4
  17. package/skills/rigc/SKILL.md +133 -0
  18. package/skills/rigc-face/SKILL.md +60 -0
  19. package/skills/rigc-ingest/SKILL.md +78 -0
  20. package/skills/rigc-motion/SKILL.md +51 -0
  21. package/skills/rigc-rigging/SKILL.md +49 -0
  22. package/src/areaband.ts +159 -0
  23. package/src/assertions/bodies/a01.ts +23 -0
  24. package/src/assertions/bodies/a02.ts +21 -0
  25. package/src/assertions/bodies/a03.ts +27 -0
  26. package/src/assertions/bodies/a04.ts +40 -0
  27. package/src/assertions/bodies/a05.ts +56 -0
  28. package/src/assertions/bodies/a06.ts +245 -0
  29. package/src/assertions/bodies/a07.ts +68 -0
  30. package/src/assertions/bodies/a08.ts +76 -0
  31. package/src/assertions/bodies/a09.ts +82 -0
  32. package/src/assertions/bodies/a10.ts +116 -0
  33. package/src/assertions/bodies/a11.ts +15 -0
  34. package/src/assertions/bodies/a12.ts +30 -0
  35. package/src/assertions/bodies/a13.ts +51 -0
  36. package/src/assertions/bodies/a14.ts +35 -0
  37. package/src/assertions/bodies/a15.ts +97 -0
  38. package/src/assertions/bodies/a16.ts +24 -0
  39. package/src/assertions/bodies/a17.ts +26 -0
  40. package/src/assertions/bodies/a18.ts +62 -0
  41. package/src/assertions/bodies/a19.ts +404 -0
  42. package/src/assertions/bodies/a20.ts +122 -0
  43. package/src/assertions/bodies/a21.ts +190 -0
  44. package/src/assertions/bodies/a22.ts +39 -0
  45. package/src/assertions/bodies/a23.ts +305 -0
  46. package/src/assertions/bodies/a24.ts +68 -0
  47. package/src/assertions/bodies/a25.ts +39 -0
  48. package/src/assertions/bodies/a26.ts +61 -0
  49. package/src/assertions/bodies/a27.ts +33 -0
  50. package/src/assertions/bodies/a28.ts +70 -0
  51. package/src/assertions/bodies/a29.ts +34 -0
  52. package/src/assertions/bodies/a30.ts +50 -0
  53. package/src/assertions/bodies/a31.ts +61 -0
  54. package/src/assertions/bodies/a32.ts +44 -0
  55. package/src/assertions/bodies/a33.ts +110 -0
  56. package/src/assertions/bodies/a34.ts +133 -0
  57. package/src/assertions/bodies/a35.ts +160 -0
  58. package/src/assertions/bodies/a36.ts +81 -0
  59. package/src/assertions/bodies/a37.ts +77 -0
  60. package/src/assertions/bodies/a38.ts +73 -0
  61. package/src/assertions/bodies/a39.ts +303 -0
  62. package/src/assertions/bodies/a40.ts +128 -0
  63. package/src/assertions/bodies/a42.ts +97 -0
  64. package/src/assertions/bodies/a43.ts +181 -0
  65. package/src/assertions/bodies/a44.ts +23 -0
  66. package/src/assertions/bodies/a45.ts +172 -0
  67. package/src/assertions/bodies/a46.ts +224 -0
  68. package/src/assertions/bodies/a47.ts +126 -0
  69. package/src/assertions/bodies/a48.ts +83 -0
  70. package/src/assertions/bodies/a49.ts +81 -0
  71. package/src/assertions/bodies/a50.ts +97 -0
  72. package/src/assertions/constraint_words.ts +169 -0
  73. package/src/assertions/emitted/index.ts +148 -0
  74. package/src/assertions/facts/animated_bones.ts +30 -0
  75. package/src/assertions/facts/animation_durations.ts +37 -0
  76. package/src/assertions/facts/atlas_pages.ts +19 -0
  77. package/src/assertions/facts/atlas_regions.ts +52 -0
  78. package/src/assertions/facts/bone_timelines.ts +37 -0
  79. package/src/assertions/facts/constraint_targets.ts +56 -0
  80. package/src/assertions/facts/constraints.ts +155 -0
  81. package/src/assertions/facts/deform_survey.ts +27 -0
  82. package/src/assertions/facts/event_keys.ts +55 -0
  83. package/src/assertions/facts/linked_meshes.ts +38 -0
  84. package/src/assertions/facts/mesh_attachments.ts +100 -0
  85. package/src/assertions/facts/region_joins.ts +34 -0
  86. package/src/assertions/facts/sequences.ts +85 -0
  87. package/src/assertions/facts/skeleton_roster.ts +45 -0
  88. package/src/assertions/facts/skin_entries.ts +37 -0
  89. package/src/assertions/facts/skin_members.ts +53 -0
  90. package/src/assertions/facts/slider_composition.ts +78 -0
  91. package/src/assertions/facts/slot_colour.ts +43 -0
  92. package/src/assertions/facts/stage.ts +27 -0
  93. package/src/assertions/facts/stage_box.ts +65 -0
  94. package/src/assertions/facts/stepped_poses.ts +74 -0
  95. package/src/assertions/facts/two_colour.ts +52 -0
  96. package/src/assertions/facts/vertex_polygons.ts +53 -0
  97. package/src/assertions/footprints.ts +367 -0
  98. package/src/assertions/harness.ts +109 -0
  99. package/src/assertions/inward_advance.ts +58 -0
  100. package/src/assertions/kinds.ts +105 -0
  101. package/src/assertions/mesh_kinds.ts +56 -0
  102. package/src/assertions/model/animated_bones.ts +38 -0
  103. package/src/assertions/model/animation_durations.ts +57 -0
  104. package/src/assertions/model/atlas_pages.ts +15 -0
  105. package/src/assertions/model/atlas_regions.ts +76 -0
  106. package/src/assertions/model/bone_timelines.ts +58 -0
  107. package/src/assertions/model/constraint_targets.ts +82 -0
  108. package/src/assertions/model/constraints.ts +233 -0
  109. package/src/assertions/model/declared.ts +125 -0
  110. package/src/assertions/model/deform_survey.ts +24 -0
  111. package/src/assertions/model/event_keys.ts +45 -0
  112. package/src/assertions/model/given.ts +45 -0
  113. package/src/assertions/model/index.ts +398 -0
  114. package/src/assertions/model/linked_meshes.ts +24 -0
  115. package/src/assertions/model/mesh_attachments.ts +119 -0
  116. package/src/assertions/model/parse.ts +146 -0
  117. package/src/assertions/model/region_joins.ts +67 -0
  118. package/src/assertions/model/runtime_timelines.ts +78 -0
  119. package/src/assertions/model/sequences.ts +157 -0
  120. package/src/assertions/model/skeleton_roster.ts +23 -0
  121. package/src/assertions/model/skin_entries.ts +69 -0
  122. package/src/assertions/model/skin_members.ts +64 -0
  123. package/src/assertions/model/slider_composition.ts +193 -0
  124. package/src/assertions/model/slot_colour.ts +81 -0
  125. package/src/assertions/model/stage.ts +28 -0
  126. package/src/assertions/model/stage_box.ts +51 -0
  127. package/src/assertions/model/stepped_poses.ts +105 -0
  128. package/src/assertions/model/two_colour.ts +61 -0
  129. package/src/assertions/model/vertex_polygons.ts +72 -0
  130. package/src/assertions/reasons.ts +129 -0
  131. package/src/assertions/region_lookups.ts +61 -0
  132. package/src/assertions/report.ts +189 -0
  133. package/src/assertions/values.ts +39 -0
  134. package/src/atlas.ts +2870 -0
  135. package/src/ballot.ts +866 -0
  136. package/src/bonedist.ts +643 -0
  137. package/src/chainfit.ts +2752 -0
  138. package/src/chains.ts +170 -0
  139. package/src/check.ts +4303 -0
  140. package/src/checkpics.ts +295 -0
  141. package/src/cli/core_commands.ts +1627 -0
  142. package/src/cli/repack.ts +414 -0
  143. package/src/cli/shared.ts +2776 -0
  144. package/src/cli/spine_commands.ts +820 -0
  145. package/src/compile.ts +9414 -0
  146. package/src/core/additive.ts +458 -0
  147. package/src/core/animation.ts +1050 -0
  148. package/src/core/clipping.ts +696 -0
  149. package/src/core/constraints.ts +1876 -0
  150. package/src/core/constraints_path.ts +964 -0
  151. package/src/core/constraints_physics.ts +881 -0
  152. package/src/core/constraints_slider.ts +635 -0
  153. package/src/core/deform.ts +613 -0
  154. package/src/core/draw_order.ts +125 -0
  155. package/src/core/events.ts +135 -0
  156. package/src/core/hooks.ts +249 -0
  157. package/src/core/index.ts +1400 -0
  158. package/src/core/raw.ts +739 -0
  159. package/src/core/skins.ts +129 -0
  160. package/src/core/uvs.ts +469 -0
  161. package/src/core/vertices.ts +490 -0
  162. package/src/core/walk.ts +197 -0
  163. package/src/core/world.ts +289 -0
  164. package/src/correspondence.ts +15 -0
  165. package/src/deformbuild.ts +60 -0
  166. package/src/deformgen.ts +630 -0
  167. package/src/deformmeasure.ts +732 -0
  168. package/src/deformreport.ts +373 -0
  169. package/src/deformstructure.ts +386 -0
  170. package/src/deformsurvey.ts +2162 -0
  171. package/src/depth.ts +784 -0
  172. package/src/diff.ts +2252 -0
  173. package/src/emit.ts +134 -0
  174. package/src/emit_spine.ts +854 -0
  175. package/src/errors.ts +53 -0
  176. package/src/framing.ts +819 -0
  177. package/src/generation.ts +139 -0
  178. package/src/ingest.ts +2293 -0
  179. package/src/json-position.ts +253 -0
  180. package/src/keyorder.ts +587 -0
  181. package/src/keys.ts +486 -0
  182. package/src/ladder.ts +121 -0
  183. package/src/mesh.ts +2382 -0
  184. package/src/meshcompare.ts +1188 -0
  185. package/src/meshquality.ts +2042 -0
  186. package/src/meshrasters.ts +944 -0
  187. package/src/meshreduce.ts +1425 -0
  188. package/src/model.ts +1245 -0
  189. package/src/motion.ts +809 -0
  190. package/src/nonfinite.ts +54 -0
  191. package/src/package_meta.ts +48 -0
  192. package/src/png.ts +297 -0
  193. package/src/pose.ts +2324 -0
  194. package/src/preview.ts +434 -0
  195. package/src/region_joins.ts +54 -0
  196. package/src/render.ts +1013 -0
  197. package/src/render_core.ts +871 -0
  198. package/src/render_shared.ts +2958 -0
  199. package/src/repack.ts +495 -0
  200. package/src/rig.ts +2941 -0
  201. package/src/slots.ts +892 -0
  202. package/src/spine_side.ts +138 -0
  203. package/src/timelines.ts +837 -0
  204. package/src/trackgen.ts +364 -0
  205. package/src/transform.ts +310 -0
  206. package/src/types.ts +1797 -0
  207. package/src/validate.ts +3875 -0
  208. package/tools/contact.ts +126 -0
  209. package/tools/editor_roundtrip.ts +1641 -0
  210. package/tools/font5x7.ts +101 -0
  211. package/tools/measure_contact_depth.ts +105 -0
  212. package/tools/plate.ts +508 -0
  213. 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
+ }