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,398 @@
1
+ /**
2
+ * The model side of the validator (issue #1025, step 4c of #380): the
3
+ * assertions that moved out of `src/validate.ts`, run over the model document
4
+ * and rigc's core instead of over what spine-core loaded.
5
+ *
6
+ * ⭐ **One body, two suppliers.** Every moved assertion is one function under
7
+ * `../bodies/`, written against a fact interface under `../facts/`.
8
+ * `validate()` supplies the facts from spine-core's loaded objects and calls
9
+ * the body inside its own `check`; this entry supplies them from the document
10
+ * (`./parse.ts`, `./skin_entries.ts`, `./atlas_pages.ts`, `./slot_colour.ts`;
11
+ * cut 4c-2's `./mesh_attachments.ts`, `./vertex_polygons.ts`,
12
+ * `./linked_meshes.ts`, `./constraints.ts`; cut 4c-4's
13
+ * `./skeleton_roster.ts`, `./bone_timelines.ts`, `./event_keys.ts`; cut
14
+ * 4c-3's `./deform_survey.ts`, `./animation_durations.ts`, `./two_colour.ts`,
15
+ * `./sequences.ts`; cut 4c-5's `./slider_composition.ts`,
16
+ * `./constraint_targets.ts`; cut 4c-5a's `./stepped_poses.ts`)
17
+ * and calls the same body inside the same harness (`../harness.ts`). The
18
+ * selftest holds the two to the same lines on every call it makes with a
19
+ * model in hand, and `tools/verdict_gate.ts` on every recipe.
20
+ *
21
+ * 🔸 **What the entry is given.** The document's text, the directory its
22
+ * pages resolve against, and the profile — and nothing read off a Spine file:
23
+ * a model-side verdict that read `skeleton.json` would be a second reading of
24
+ * the encoding, which is the round trip's subject and not the rig's. In
25
+ * `given`, the two values a `rigc-compiled/2` or `/1` document does not hold —
26
+ * the stage and each page's `pma` (`./given.ts`); a `/3` document states both
27
+ * (issue #1026) and is read for them, and `given` beside one is refused by
28
+ * name. The rig info and the declared durations are the document's on every
29
+ * spec (issue #1054, `./declared.ts`): cuts 4c-1 to 4c-3 took them from the
30
+ * caller, as `validate()` does, and the verdict moved with what the caller
31
+ * handed over — now either one given beside the document is refused by name.
32
+ *
33
+ * ⛔ **Not wired into any command.** `build` keeps the round trip and its
34
+ * fifty lines; this entry is what the selftest and the instrument call
35
+ * until every moved assertion is on it (the card's design, point 7).
36
+ *
37
+ * Links nothing from the runtime, directly or through what it imports — held
38
+ * by the selftest's `VF` controls.
39
+ */
40
+ import { ASSERTION_KIND, type AssertionProfile } from '../kinds.ts';
41
+ import { verdictHarness, type VerdictHarness, type Verdicts, type VerdictLists } from '../harness.ts';
42
+ import { SKIP_NO_ATLAS, SKIP_NO_MODEL } from '../reasons.ts';
43
+ import { a03RegionWidthHeightFinite } from '../bodies/a03.ts';
44
+ import { a11NoClippingAttachments } from '../bodies/a11.ts';
45
+ import { a17AtlasPageFilesExist } from '../bodies/a17.ts';
46
+ import { a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten } from '../bodies/a45.ts';
47
+ 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';
48
+ import { fileOrderedEntries, modelSkinEntries } from './skin_entries.ts';
49
+ import { modelAtlasPages } from './atlas_pages.ts';
50
+ import { modelSlotColour } from './slot_colour.ts';
51
+ import type { SkinEntryFacts } from '../facts/skin_entries.ts';
52
+ import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
53
+ import type { SlotColourFacts } from '../facts/slot_colour.ts';
54
+ import type { RigInfo } from '../../types.ts';
55
+ import type { ModelGiven } from './given.ts';
56
+ import { modelDeclaredDurations, modelRigInfo, refuseDeclaredBeside } from './declared.ts';
57
+ import { a08RegionNamesMatchAttachments } from '../bodies/a08.ts';
58
+ import { a13MeshBudget } from '../bodies/a13.ts';
59
+ import { a14NoFullFrameMesh } from '../bodies/a14.ts';
60
+ import { a15IdleNoMeshBoneKeys } from '../bodies/a15.ts';
61
+ import { a22MeshUvsInUnitRange } from '../bodies/a22.ts';
62
+ import { a38SkinMembersAreSkinRequired } from '../bodies/a38.ts';
63
+ import { a06AtlasPageSizeMatchesPng } from '../bodies/a06.ts';
64
+ import { a19OverlayPngsHaveAlpha } from '../bodies/a19.ts';
65
+ import { a49PackedFootprintsDoNotOverlap } from '../bodies/a49.ts';
66
+ import { a50StageBoxIsTheStage } from '../bodies/a50.ts';
67
+ import { modelStageBox } from './stage_box.ts';
68
+ import type { StageBoxFacts } from '../facts/stage_box.ts';
69
+ import { a27RegionNameMatchesPageFilename } from '../bodies/a27.ts';
70
+ import { modelAnimatedBones } from './animated_bones.ts';
71
+ import { modelSkinMembers } from './skin_members.ts';
72
+ import { modelRegionJoins } from './region_joins.ts';
73
+ import { modelAtlasRegions } from './atlas_regions.ts';
74
+ import { modelStage } from './stage.ts';
75
+ import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
76
+ import type { SkinMemberFacts } from '../facts/skin_members.ts';
77
+ import type { RegionJoinFacts } from '../facts/region_joins.ts';
78
+ import type { AtlasRegionFacts } from '../facts/atlas_regions.ts';
79
+ import type { StageFacts } from '../facts/stage.ts';
80
+ import { a04MeshTrianglesAndEncoding } from '../bodies/a04.ts';
81
+ import { a20MeshWeightsCoherent } from '../bodies/a20.ts';
82
+ import { a21MeshRimPinned } from '../bodies/a21.ts';
83
+ import { a23PhysicsConstraintEffective } from '../bodies/a23.ts';
84
+ import { a28RibbonRowsShareWeights } from '../bodies/a28.ts';
85
+ import { a33VertexAttachmentGeometry } from '../bodies/a33.ts';
86
+ import { a36PathConstraintEffective } from '../bodies/a36.ts';
87
+ import { a37SliderConstraintEffective } from '../bodies/a37.ts';
88
+ import { a42DrivenConstraintsUpdateAfterTheirDriver } from '../bodies/a42.ts';
89
+ import { a44LinkedMeshStatesNoGeometryOfItsOwn } from '../bodies/a44.ts';
90
+ import { a47IkConstraintNotMutedThroughout } from '../bodies/a47.ts';
91
+ import { a48TransformConstraintNotMutedThroughout } from '../bodies/a48.ts';
92
+ import { modelMeshFacts } from './mesh_attachments.ts';
93
+ import { modelPolygonFacts } from './vertex_polygons.ts';
94
+ import { modelLinkFacts } from './linked_meshes.ts';
95
+ import { modelConstraintFacts } from './constraints.ts';
96
+ import type { MeshFacts } from '../facts/mesh_attachments.ts';
97
+ import type { PolygonFacts } from '../facts/vertex_polygons.ts';
98
+ import type { LinkFacts } from '../facts/linked_meshes.ts';
99
+ import type { ConstraintFacts } from '../facts/constraints.ts';
100
+ import { a12NoDarkColor } from '../bodies/a12.ts';
101
+ import { a24AxisSpaceStroke } from '../bodies/a24.ts';
102
+ import { a25DetachedBoneParentage } from '../bodies/a25.ts';
103
+ import { a26SlotDrawOrder } from '../bodies/a26.ts';
104
+ import { a29StrokeWithinContactDepth } from '../bodies/a29.ts';
105
+ import { a30StrokeWithinCapContainment } from '../bodies/a30.ts';
106
+ import { a32EventKeysResolve } from '../bodies/a32.ts';
107
+ import { modelSkeletonRoster } from './skeleton_roster.ts';
108
+ import { modelBoneTimelines } from './bone_timelines.ts';
109
+ import { modelEventKeys } from './event_keys.ts';
110
+ import type { SkeletonRosterFacts } from '../facts/skeleton_roster.ts';
111
+ import type { BoneTimelineFacts } from '../facts/bone_timelines.ts';
112
+ import type { EventKeyFacts } from '../facts/event_keys.ts';
113
+ import { a39DeformKeepsTriangleWinding } from '../bodies/a39.ts';
114
+ import { a09AnimationDurationMatchesSpec } from '../bodies/a09.ts';
115
+ import { a43TwoColorTintLoadsAndPosesAsWritten } from '../bodies/a43.ts';
116
+ import { a46SequenceAttachmentsShowTheFrameTheFileStates } from '../bodies/a46.ts';
117
+ import { modelDeformSurvey } from './deform_survey.ts';
118
+ import { modelAnimationDurations } from './animation_durations.ts';
119
+ import { modelTwoColour } from './two_colour.ts';
120
+ import { modelSequences } from './sequences.ts';
121
+ import type { DeformSurveyFacts } from '../facts/deform_survey.ts';
122
+ import type { AnimationDurationFacts } from '../facts/animation_durations.ts';
123
+ import type { TwoColourFacts } from '../facts/two_colour.ts';
124
+ import type { SequenceFacts } from '../facts/sequences.ts';
125
+ import { a40SlidersComposeOnASharedTarget } from '../bodies/a40.ts';
126
+ import { modelSliderComposition } from './slider_composition.ts';
127
+ import type { SliderCompositionFacts } from '../facts/slider_composition.ts';
128
+ import { a34ConstraintTimelineTargets } from '../bodies/a34.ts';
129
+ import { modelConstraintTargets } from './constraint_targets.ts';
130
+ import type { ConstraintTargetFacts } from '../facts/constraint_targets.ts';
131
+ import { a10NoNanAfterStepping } from '../bodies/a10.ts';
132
+ import { modelSteppedPoses } from './stepped_poses.ts';
133
+ import type { SteppedPoseFacts } from '../facts/stepped_poses.ts';
134
+
135
+ /** What the model side is given. */
136
+ export interface ModelValidateInput {
137
+ /** The document `build` writes beside the Spine pair (`skeleton.model.json`). */
138
+ modelText: string;
139
+ /** The directory the document's page names resolve against — the build's `--out`. */
140
+ atlasDir: string;
141
+ profile: AssertionProfile;
142
+ /**
143
+ * Refused (issue #1054): every document states the rig info in its `rig`
144
+ * section, and the model side reads it there (`./declared.ts`). A caller
145
+ * that gives it anyway is a second source for one fact, and each rule that
146
+ * reads the declarations refuses the call by name rather than reading
147
+ * either. Kept on the type so a caller still handing it over is named, not
148
+ * silently ignored.
149
+ */
150
+ rig?: RigInfo;
151
+ /**
152
+ * The stage and the pages' `pma`, for a `rigc-compiled/2` or `/1` document,
153
+ * which does not hold them (`./given.ts`). A06, A14 and A19 read them; a
154
+ * caller that gives none there has those three refuse by name rather than
155
+ * read a value nobody stated. A `/3` document states both (issue #1026), and
156
+ * `given` beside it is refused by name.
157
+ */
158
+ given?: ModelGiven;
159
+ /** Refused, as `rig` is (issue #1054): every document states each animation's declared duration, and A09 reads them there. */
160
+ declaredDurations?: Record<string, number>;
161
+ }
162
+
163
+ /** The model side's report: `validate()`'s shape, over the moved assertions and the two parse rules. */
164
+ export interface ModelReport extends VerdictLists {
165
+ profile: AssertionProfile;
166
+ }
167
+
168
+ /**
169
+ * The fact families, composed: one supplier per family under `./`, each a
170
+ * function of the read document. A body names the family it reads, so a later
171
+ * cut that moves an assertion over an existing family adds a body file and a
172
+ * row below, and one over a new family adds a facts file, a supplier file and
173
+ * a field here.
174
+ */
175
+ export interface ModelSupply {
176
+ skinEntries: (read: ReadDocument) => SkinEntryFacts;
177
+ atlasPages: (read: ReadDocument) => AtlasPageFacts;
178
+ slotColour: (read: ReadDocument) => SlotColourFacts;
179
+ animatedBones: (read: ReadDocument) => AnimatedBoneFacts;
180
+ skinMembers: (read: ReadDocument) => SkinMemberFacts;
181
+ regionJoins: (read: ReadDocument) => RegionJoinFacts;
182
+ atlasRegions: (read: ReadDocument, input: ModelValidateInput) => AtlasRegionFacts;
183
+ stage: (read: ReadDocument, input: ModelValidateInput) => StageFacts;
184
+ meshes: (read: ReadDocument) => MeshFacts;
185
+ polygons: (read: ReadDocument) => PolygonFacts;
186
+ links: (read: ReadDocument) => LinkFacts;
187
+ constraints: (read: ReadDocument) => ConstraintFacts;
188
+ skeletonRoster: (read: ReadDocument) => SkeletonRosterFacts;
189
+ boneTimelines: (read: ReadDocument) => BoneTimelineFacts;
190
+ eventKeys: (read: ReadDocument) => EventKeyFacts;
191
+ deformSurvey: (read: ReadDocument) => DeformSurveyFacts;
192
+ animationDurations: (read: ReadDocument) => AnimationDurationFacts;
193
+ twoColour: (read: ReadDocument) => TwoColourFacts;
194
+ sequences: (read: ReadDocument) => SequenceFacts;
195
+ sliderComposition: (read: ReadDocument) => SliderCompositionFacts;
196
+ constraintTargets: (read: ReadDocument) => ConstraintTargetFacts;
197
+ steppedPoses: (read: ReadDocument) => SteppedPoseFacts;
198
+ /** The stage box the document asks for and what its default skin holds there (issue #1168, `./stage_box.ts`). */
199
+ stageBox: (read: ReadDocument) => StageBoxFacts;
200
+ /** The rig info the document declares (issue #1054, `./declared.ts`); a caller's `rig` beside it is refused by name. */
201
+ rigInfo: (read: ReadDocument, input: ModelValidateInput) => RigInfo;
202
+ /** Each animation's declared duration as the document states it (issue #1054); a caller's `declaredDurations` beside it is refused by name. */
203
+ declaredDurations: (read: ReadDocument, input: ModelValidateInput) => Record<string, number>;
204
+ }
205
+
206
+ /** The suppliers the model side runs on. */
207
+ export const MODEL_SUPPLY: ModelSupply = {
208
+ skinEntries: modelSkinEntries,
209
+ atlasPages: modelAtlasPages,
210
+ slotColour: modelSlotColour,
211
+ animatedBones: modelAnimatedBones,
212
+ skinMembers: modelSkinMembers,
213
+ regionJoins: modelRegionJoins,
214
+ atlasRegions: (read, input) => modelAtlasRegions(read, input.given),
215
+ stage: (read, input) => modelStage(read, input.given),
216
+ meshes: modelMeshFacts,
217
+ polygons: modelPolygonFacts,
218
+ links: modelLinkFacts,
219
+ constraints: modelConstraintFacts,
220
+ skeletonRoster: modelSkeletonRoster,
221
+ boneTimelines: modelBoneTimelines,
222
+ eventKeys: modelEventKeys,
223
+ deformSurvey: modelDeformSurvey,
224
+ animationDurations: modelAnimationDurations,
225
+ twoColour: modelTwoColour,
226
+ sequences: modelSequences,
227
+ sliderComposition: modelSliderComposition,
228
+ constraintTargets: modelConstraintTargets,
229
+ steppedPoses: modelSteppedPoses,
230
+ stageBox: modelStageBox,
231
+ rigInfo: (read, input) => {
232
+ refuseDeclaredBeside(read, input, 'rig');
233
+ return modelRigInfo(read);
234
+ },
235
+ declaredDurations: (read, input) => {
236
+ refuseDeclaredBeside(read, input, 'declaredDurations');
237
+ return modelDeclaredDurations(read);
238
+ },
239
+ };
240
+
241
+ /**
242
+ * One moved assertion: its code, and how the model side runs its body once the
243
+ * document is read. The list is the registry of what has moved — a later cut
244
+ * puts its rows where `validate()` runs those rules, not at the end — and the
245
+ * selftest reads it for the codes it compares.
246
+ */
247
+ export interface MovedAssertion {
248
+ code: string;
249
+ /** The body, over the document. Not called when the model side read no document. */
250
+ run: (verdicts: Verdicts, read: ReadDocument, input: ModelValidateInput, supply: ModelSupply) => void;
251
+ /** What the body SKIPs with when there is no document — the model side's counterpart of the reason `validate()` gives when the round trip produced nothing. */
252
+ unread: string;
253
+ /**
254
+ * The body runs behind the reader alone, not behind the region rule — the
255
+ * model side's place for a rule `validate()` runs BEFORE its round trip
256
+ * (A08, issue #589), so that a region miss is named by the rule's own
257
+ * sentence on both sides while the parse beside it refuses the file.
258
+ *
259
+ * Since cut 4c-4 also every rule `validate()` runs over the skeleton JSON
260
+ * whatever its round trip did (A12, A24, A25, A26, A29, A30, and A32, which
261
+ * runs before it; since cut 4c-5 A34): on the runtime's side their verdict does not wait on the
262
+ * load, so on this side it does not wait on the region rule, and where both
263
+ * parses refuse a build the two sides' lines of these rules are still
264
+ * compared.
265
+ */
266
+ beforeTheParse?: boolean;
267
+ }
268
+
269
+ /**
270
+ * The moved assertions, in `validate()`'s report order — the order its `check`
271
+ * calls stand in, so a model-side report lists its PASS, SKIP and FAIL lines in
272
+ * the order the round trip's does. The seven cuts of issue #1025 appended their
273
+ * rows in landing order; issue #1054 put them back, and the selftest's `VF16`
274
+ * names a row that stands out of that order. `SKIP_NO_ATLAS` for
275
+ * A17 rather than `SKIP_NO_MODEL` because that is what A17 says over a missing
276
+ * atlas on either side: its body reads only the pages, and on this side no
277
+ * document means no pages.
278
+ */
279
+ export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
280
+ { code: 'A08_REGION_NAMES_MATCH_ATTACHMENTS', run: (v, read, _input, supply) => (read.doc.pages === null ? v.skip('A08_REGION_NAMES_MATCH_ATTACHMENTS', SKIP_NO_MODEL_PAGES) : a08RegionNamesMatchAttachments(v, supply.regionJoins(read), new Set<string>())), unread: SKIP_NO_MODEL, beforeTheParse: true },
281
+ { code: 'A32_EVENT_KEYS_RESOLVE', run: (v, read, _input, supply) => a32EventKeysResolve(v, supply.eventKeys(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
282
+ { code: 'A34_CONSTRAINT_TIMELINE_TARGETS', run: (v, read, _input, supply) => a34ConstraintTimelineTargets(v, supply.constraintTargets(read)), unread: SKIP_NO_MODEL, beforeTheParse: true },
283
+ { code: 'A12_NO_DARK_COLOR', run: (v, read, _input, supply) => a12NoDarkColor(v, supply.skeletonRoster(read), supply.slotColour(read).slotTimelines), unread: SKIP_NO_MODEL, beforeTheParse: true },
284
+ { code: 'A03_REGION_WIDTH_HEIGHT_FINITE', run: (v, read, _input, supply) => a03RegionWidthHeightFinite(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
285
+ { code: 'A04_MESH_TRIANGLES_AND_ENCODING', run: (v, read, _input, supply) => a04MeshTrianglesAndEncoding(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
286
+ { code: 'A33_VERTEX_ATTACHMENT_GEOMETRY', run: (v, read, _input, supply) => a33VertexAttachmentGeometry(v, supply.polygons(read)), unread: SKIP_NO_MODEL },
287
+ { code: 'A11_NO_CLIPPING_ATTACHMENTS', run: (v, read, _input, supply) => a11NoClippingAttachments(v, supply.skinEntries(read)), unread: SKIP_NO_MODEL },
288
+ { code: 'A13_MESH_BUDGET', run: (v, read, input, supply) => a13MeshBudget(v, supply.meshes(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL },
289
+ { code: 'A14_NO_FULL_FRAME_MESH', run: (v, read, input, supply) => a14NoFullFrameMesh(v, supply.meshes(read), supply.stage(read, input)), unread: SKIP_NO_MODEL },
290
+ { 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 },
291
+ { code: 'A20_MESH_WEIGHTS_COHERENT', run: (v, read, input, supply) => a20MeshWeightsCoherent(v, supply.meshes(read), input.profile === 'spine-html', supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
292
+ { code: 'A21_MESH_RIM_PINNED', run: (v, read, input, supply) => a21MeshRimPinned(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
293
+ { code: 'A22_MESH_UVS_IN_UNIT_RANGE', run: (v, read, _input, supply) => a22MeshUvsInUnitRange(v, supply.meshes(read)), unread: SKIP_NO_MODEL },
294
+ { code: 'A39_DEFORM_KEEPS_TRIANGLE_WINDING', run: (v, read, input, supply) => a39DeformKeepsTriangleWinding(v, supply.deformSurvey(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
295
+ { code: 'A23_PHYSICS_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a23PhysicsConstraintEffective(v, supply.constraints(read), supply.meshes(read)), unread: SKIP_NO_MODEL },
296
+ { code: 'A36_PATH_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a36PathConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
297
+ { code: 'A37_SLIDER_CONSTRAINT_EFFECTIVE', run: (v, read, _input, supply) => a37SliderConstraintEffective(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
298
+ { code: 'A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a47IkConstraintNotMutedThroughout(v, supply.constraints(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
299
+ { code: 'A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT', run: (v, read, input, supply) => a48TransformConstraintNotMutedThroughout(v, supply.constraints(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
300
+ { code: 'A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET', run: (v, read, _input, supply) => a40SlidersComposeOnASharedTarget(v, supply.sliderComposition(read), supply.constraints(read)), unread: SKIP_NO_MODEL },
301
+ { code: 'A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER', run: (v, read, _input, supply) => a42DrivenConstraintsUpdateAfterTheirDriver(v, supply.constraints(read)), unread: SKIP_NO_MODEL },
302
+ { code: 'A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED', run: (v, read, _input, supply) => a38SkinMembersAreSkinRequired(v, supply.skinMembers(read)), unread: SKIP_NO_MODEL },
303
+ { code: 'A09_ANIMATION_DURATION_MATCHES_SPEC', run: (v, read, input, supply) => a09AnimationDurationMatchesSpec(v, supply.animationDurations(read), supply.declaredDurations(read, input)), unread: SKIP_NO_MODEL },
304
+ { code: 'A10_NO_NAN_AFTER_STEPPING', run: (v, read, _input, supply) => a10NoNanAfterStepping(v, supply.boneTimelines(read), supply.steppedPoses(read)), unread: SKIP_NO_MODEL },
305
+ { code: 'A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN', run: (v, read, _input, supply) => a43TwoColorTintLoadsAndPosesAsWritten(v, supply.twoColour(read)), unread: SKIP_NO_MODEL },
306
+ { code: 'A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN', run: (v, read, _input, supply) => a45SeparableColorTimelinesOwnTheirChannelsAndPoseAsWritten(v, supply.slotColour(read)), unread: SKIP_NO_MODEL },
307
+ { code: 'A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN', run: (v, read, _input, supply) => a44LinkedMeshStatesNoGeometryOfItsOwn(v, supply.links(read)), unread: SKIP_NO_MODEL },
308
+ { code: 'A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES', run: (v, read, _input, supply) => a46SequenceAttachmentsShowTheFrameTheFileStates(v, supply.sequences(read)), unread: SKIP_NO_MODEL },
309
+ { code: 'A17_ATLAS_PAGE_FILES_EXIST', run: (v, read, input, supply) => a17AtlasPageFilesExist(v, supply.atlasPages(read), input), unread: SKIP_NO_ATLAS },
310
+ { code: 'A06_ATLAS_PAGE_SIZE_MATCHES_PNG', run: (v, read, input, supply) => a06AtlasPageSizeMatchesPng(v, supply.atlasRegions(read, input), input, input.profile === 'spine-html'), unread: SKIP_NO_ATLAS },
311
+ { 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 },
312
+ { 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 },
313
+ { code: 'A50_STAGE_BOX_IS_THE_STAGE', run: (v, read, _input, supply) => a50StageBoxIsTheStage(v, supply.stageBox(read)), unread: SKIP_NO_MODEL },
314
+ { 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 },
315
+ { 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 },
316
+ { 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 },
317
+ { code: 'A27_REGION_NAME_MATCHES_PAGE_FILENAME', run: (v, read, input, supply) => a27RegionNameMatchesPageFilename(v, supply.atlasRegions(read, input)), unread: SKIP_NO_ATLAS },
318
+ { code: 'A28_RIBBON_ROWS_SHARE_WEIGHTS', run: (v, read, input, supply) => a28RibbonRowsShareWeights(v, supply.meshes(read), supply.rigInfo(read, input)), unread: SKIP_NO_MODEL },
319
+ { 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 },
320
+ { 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 },
321
+ ];
322
+
323
+ /** The codes the model side prints: its two parse rules, then the moved assertions. */
324
+ export const MODEL_SIDE_CODES: readonly string[] = [A00_MODEL_READ, A00_MODEL_REGIONS_ON_PAGES, ...MOVED_ASSERTIONS.map((m) => m.code)];
325
+
326
+ /**
327
+ * The parse: the reader, then the region rule. Returns what the reader read
328
+ * (`null` when it refused) and whether the region rule held too — a
329
+ * `beforeTheParse` body needs only the first.
330
+ */
331
+ function parse(h: VerdictHarness, modelText: string): { read: ReadDocument | null; held: boolean } {
332
+ const read = h.check(A00_MODEL_READ, () => modelRead(h.verdicts, modelText)) ?? null;
333
+ if (read === null) {
334
+ h.check(A00_MODEL_REGIONS_ON_PAGES, () => h.skip(A00_MODEL_REGIONS_ON_PAGES, SKIP_NO_MODEL_DOCUMENT));
335
+ return { read: null, held: false };
336
+ }
337
+ return { read, held: h.check(A00_MODEL_REGIONS_ON_PAGES, () => modelRegionsOnPages(h.verdicts, read)) === true };
338
+ }
339
+
340
+ /**
341
+ * The row the rig's two figures are written before (issue #1114): `validate()`
342
+ * sets `rig` and `profile` where its archetype assertions begin, after every
343
+ * figure the bodies above them write and before A29's and A30's, so the figures
344
+ * line lists them in that place on both entries. `RC39` in `selftest.ts`
345
+ * compares the two lines key by key.
346
+ */
347
+ const RIG_FIGURES_BEFORE = 'A24_AXIS_SPACE_STROKE';
348
+ if (!MOVED_ASSERTIONS.some((m) => m.code === RIG_FIGURES_BEFORE)) throw new Error(`internal: the model side writes the rig's figures before ${RIG_FIGURES_BEFORE}, which it no longer runs`);
349
+
350
+ /**
351
+ * The attachment figures `validate()` counts over the skins it loaded (issue
352
+ * #1114), counted over the document's skins in the file's walk order: a
353
+ * `region` record is a region attachment, and a `mesh` or `linkedmesh` record
354
+ * a mesh attachment — the runtime loads a linked mesh as a mesh. Written only
355
+ * where the document read and its regions held, as the round trip writes them
356
+ * only where it loaded the skeleton, and before any body's figure, which is
357
+ * where `validate()` writes them.
358
+ */
359
+ function attachmentFigures(stats: Record<string, number | string>, read: ReadDocument): void {
360
+ const kinds = fileOrderedEntries(read).map((entry) => entry.record.kind);
361
+ stats.regionAttachments = kinds.filter((kind) => kind === 'region').length;
362
+ stats.meshAttachments = kinds.filter((kind) => kind === 'mesh' || kind === 'linkedmesh').length;
363
+ }
364
+
365
+ /**
366
+ * `rig` — the archetype the document's rig section states (`validate()` prints
367
+ * its caller's; the model side reads the document's, issue #1054 — read
368
+ * directly rather than through `supply.rigInfo`, whose refusal of a caller's
369
+ * `rig` beside the document is the rules' line to print, not this figure's),
370
+ * left out where there is no document to state one — and `profile`, the profile this
371
+ * run was handed: the same argument both entries read off `--profile`.
372
+ */
373
+ function rigFigures(stats: Record<string, number | string>, read: ReadDocument | null, input: ModelValidateInput): void {
374
+ if (read !== null) stats.rig = modelRigInfo(read).archetype;
375
+ stats.profile = input.profile;
376
+ }
377
+
378
+ /**
379
+ * Run the model side: the parse, then every moved assertion — each over the
380
+ * document, or skipped naming the parse when there is none — and return the
381
+ * report in `validate()`'s shape, which `reportLines` in `src/validate.ts`
382
+ * prints. `plant` replaces a family's supplier; only the selftest's plants
383
+ * pass one.
384
+ */
385
+ export function validateModel(input: ModelValidateInput, plant: Partial<ModelSupply> = {}): ModelReport {
386
+ const supply: ModelSupply = { ...MODEL_SUPPLY, ...plant };
387
+ const h = verdictHarness(input.profile, { ...ASSERTION_KIND, ...MODEL_PARSE_KIND }, 'the model side');
388
+ const { read, held } = parse(h, input.modelText);
389
+ if (read !== null && held) attachmentFigures(h.stats, read);
390
+ for (const moved of MOVED_ASSERTIONS) {
391
+ if (moved.code === RIG_FIGURES_BEFORE) rigFigures(h.stats, read, input);
392
+ const doc = read !== null && (held || moved.beforeTheParse === true) ? read : null;
393
+ h.check(moved.code, () => (doc === null ? h.skip(moved.code, moved.unread) : moved.run(h.verdicts, doc, input, supply)));
394
+ }
395
+ const { failures, passed, skipped, profileSkipped, stats } = h;
396
+ return { failures, passed, skipped, profileSkipped, stats, profile: input.profile };
397
+ }
398
+
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The model side's supply of `LinkFacts` (issue #1025, step 4c of #380): the
3
+ * document's `linkedmesh` records, in the order the Spine file spells them —
4
+ * the skins and each skin's slot keys in the file's order (`fileSkinOrder`,
5
+ * issue #1034), each slot's records in table order
6
+ * (`fileSpelledEntries`, `./vertex_polygons.ts`).
7
+ *
8
+ * `encoding` is empty on every link: a link record has no geometry field, so
9
+ * the geometry keys A44 refuses cannot be spelled here (`../facts/linked_meshes.ts`'s 🔒).
10
+ *
11
+ * Links nothing from the runtime.
12
+ */
13
+ import type { LinkEntry, LinkFacts } from '../facts/linked_meshes.ts';
14
+ import { fileSpelledEntries } from './vertex_polygons.ts';
15
+ import type { ReadDocument } from './parse.ts';
16
+
17
+ export function modelLinkFacts(read: ReadDocument): LinkFacts {
18
+ const links: LinkEntry[] = [];
19
+ for (const { skin, slot, placeholder, record } of fileSpelledEntries(read)) {
20
+ if (record.kind !== 'linkedmesh' || typeof record.source !== 'string') continue;
21
+ links.push({ skin, slot, placeholder, source: record.source, encoding: [] });
22
+ }
23
+ return { links };
24
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The model side's supply of `MeshFacts` (issue #1025, step 4c of #380): the
3
+ * document's mesh and linked-mesh records, in the file's walk order, with the
4
+ * geometry and the weights the runtime holds after its parse.
5
+ *
6
+ * Every value is the document's, through a function rigc already runs:
7
+ *
8
+ * - **The order** is `fileOrderedEntries` (`./skin_entries.ts`), the skins
9
+ * put in the file's order (`fileSkinOrder`, issue #1034).
10
+ * - **A binding's bone** is the index the emitter writes for its name,
11
+ * `boneIndexOf` over the document's bones (`src/emit_spine.ts`), and its
12
+ * weight the float32 the runtime stores a vertex array in (`Math.fround`,
13
+ * `src/model.ts`'s 🔸).
14
+ * - **A link's geometry is its source's**: its vertices are the core's
15
+ * `sourceOfDoc`, its `uvs` and triangles the core's `drawWalkOf(...).meshOf`
16
+ * — the two lookups the core poses a linked mesh through — and its hull
17
+ * the source record's, read where those two read it.
18
+ * - **The counts the parser derives**: `worldVerticesLength` is the length
19
+ * of `uvs` (the length the parser hands its vertex reader for a mesh — the
20
+ * reader refuses a document whose `uvs` are not one pair per vertex, so it is
21
+ * also two per vertex), and
22
+ * `hullLength` twice the record's `hull`, which the parser reads at the
23
+ * mesh row's default of the parser-default table when the key is absent
24
+ * (`PARSER_DEFAULTS` in `src/keyorder.ts`, which the selftest holds to the
25
+ * parser row by row).
26
+ *
27
+ * - **`width` and `height`** are the record's, off the document's JSON — a
28
+ * link's its source record's, the two numbers the runtime's `setSourceMesh`
29
+ * writes over the link's own: the core's geometry reader keeps a mesh's
30
+ * vertices, UVs and triangles and not its size, which no pose reads. Since
31
+ * issue #1054 this is the one family of the meshes; cut 4c-1's
32
+ * `SkinMeshFacts` stated the same entries with these two numbers added.
33
+ *
34
+ * `encoding` is empty on every entry: the document states the weighted form
35
+ * outright and names bones, so the clauses about the flat run have no subject
36
+ * here (`../facts/mesh_attachments.ts`'s 🔒).
37
+ *
38
+ * Links nothing from the runtime.
39
+ */
40
+ import { boneIndexOf } from '../../emit_spine.ts';
41
+ import { drawWalkOf, shownRow, sourceOfDoc } from '../../core/index.ts';
42
+ import { PARSER_DEFAULTS } from '../../keyorder.ts';
43
+ import type { ModelVertices } from '../../model.ts';
44
+ import type { MeshBinding, MeshEntry, MeshFacts } from '../facts/mesh_attachments.ts';
45
+ import { fileOrderedEntries } from './skin_entries.ts';
46
+ import type { ReadDocument } from './parse.ts';
47
+ import { isObj } from '../values.ts';
48
+
49
+ /** A record's own `width` and `height` as the document states them, found by skin (the first of that name, as a link resolves its skin), slot and placeholder. */
50
+ function statedSize(read: ReadDocument, skin: string, slot: string, placeholder: string): { width: number; height: number } {
51
+ const skins = Array.isArray(read.json.skins) ? read.json.skins : [];
52
+ const found = skins.find((k: unknown) => isObj(k) && k.name === skin);
53
+ const table = isObj(found) && isObj(found.attachments) ? found.attachments[slot] : undefined;
54
+ const record = isObj(table) ? table[placeholder] : undefined;
55
+ if (!isObj(record) || typeof record.width !== 'number' || typeof record.height !== 'number') {
56
+ throw new Error(`internal: skin "${skin}" slot "${slot}" placeholder "${placeholder}" is a mesh record the document states no width and height for`);
57
+ }
58
+ return { width: record.width, height: record.height };
59
+ }
60
+
61
+ /** The parser's reading of a mesh with no `hull` key, off the parser-default table. */
62
+ function hullDefault(): number {
63
+ const stated = PARSER_DEFAULTS['mesh attachment']?.hull;
64
+ if (typeof stated !== 'number') throw new Error('internal: the parser-default table has no number for a mesh attachment\'s hull');
65
+ return stated;
66
+ }
67
+
68
+ export function modelMeshFacts(read: ReadDocument): MeshFacts {
69
+ const { doc } = read;
70
+ const indexOf = boneIndexOf(doc.bones);
71
+ const sourceVertices = sourceOfDoc(doc);
72
+ const walk = drawWalkOf(doc, []);
73
+ const slotBone = new Map(doc.slots.map((s) => [s.name, s.bone]));
74
+ const meshes: MeshEntry[] = [];
75
+ for (const entry of fileOrderedEntries(read)) {
76
+ const g = entry.record.geometry;
77
+ if (g === undefined || (g.kind !== 'mesh' && g.kind !== 'linkedmesh')) continue;
78
+ const where = `skin "${entry.skin}" slot "${entry.slot}" placeholder "${entry.placeholder}"`;
79
+ let vertices: ModelVertices;
80
+ let uvs: readonly number[];
81
+ let triangles: readonly number[];
82
+ let hull: number | undefined;
83
+ let size: { width: number; height: number };
84
+ if (g.kind === 'mesh') {
85
+ ({ vertices, uvs, triangles, hull } = g);
86
+ size = statedSize(read, entry.skin, entry.slot, entry.placeholder);
87
+ } else {
88
+ // `readModel` resolves every link to a mesh or refuses the document, so a miss here is this module's defect.
89
+ const found = sourceVertices(g.skin, g.slot, g.source);
90
+ const drawn = walk.meshOf(g.skin, g.slot, g.source);
91
+ if (typeof found === 'string' || drawn === undefined) throw new Error(`internal: ${where} is a link the reader returned without a source mesh: ${typeof found === 'string' ? found : 'no uvs'}`);
92
+ vertices = found;
93
+ ({ uvs, triangles } = drawn);
94
+ const source = doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
95
+ hull = source?.kind === 'mesh' ? source.hull : undefined;
96
+ size = statedSize(read, g.skin, g.slot, g.source);
97
+ }
98
+ const bone = slotBone.get(entry.slot);
99
+ if (bone === undefined) throw new Error(`internal: ${where} names a slot the reader accepted and the document does not hold`);
100
+ const weights: MeshBinding[][] | null = vertices.weighted ? vertices.bindings.map((influences) => influences.map((b) => ({ bone: indexOf(b.bone), weight: Math.fround(b.weight) }))) : null;
101
+ meshes.push({
102
+ name: shownRow(entry).name,
103
+ skin: entry.skin,
104
+ slot: entry.slot,
105
+ slotBone: bone,
106
+ placeholder: entry.placeholder,
107
+ link: g.kind === 'linkedmesh' ? { source: g.source } : null,
108
+ triangles,
109
+ worldVerticesLength: uvs.length,
110
+ regionUVs: uvs,
111
+ hullLength: 2 * (hull ?? hullDefault()),
112
+ width: size.width,
113
+ height: size.height,
114
+ weights,
115
+ encoding: [],
116
+ });
117
+ }
118
+ return { bones: doc.bones.map((b) => b.name), meshes };
119
+ }