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,105 @@
1
+ /**
2
+ * What kind of rule each assertion is, and which profile runs which kind
3
+ * (issue #1025, step 4c of #380).
4
+ *
5
+ * The table lives here rather than in `src/validate.ts` because two harnesses
6
+ * read it now: `validate()`'s `check`, which runs an assertion over what
7
+ * spine-core loaded, and the model side's (`./harness.ts`), which runs the
8
+ * same body over the model document and the core. A profile has to exclude
9
+ * the same rules on both sides, and two copies of the table is how one of
10
+ * them would come to exclude a rule the other runs. This module links nothing
11
+ * from the runtime, which is the condition for the model side to read it.
12
+ */
13
+
14
+ /** The two rulebooks — `ValidateProfile` in `src/validate.ts`, where the choice between them is argued. */
15
+ export type AssertionProfile = 'spine' | 'spine-html';
16
+
17
+ /** The three kinds a rule can be; the table's own comment says what each means. */
18
+ export type AssertionKind = 'validity' | 'renderer' | 'archetype';
19
+
20
+ /**
21
+ * What kind of rule each assertion is. Every assertion has an entry, and
22
+ * `check()` throws on a name that has none — a new assertion must state its kind
23
+ * rather than defaulting into one, because the default would decide, silently,
24
+ * whether it runs on foreign data.
25
+ *
26
+ * validity — the file is wrong for any consumer. Runs under every profile.
27
+ * renderer — valid Spine that this project's renderer or frame budget refuses.
28
+ * archetype — a structural rule about rigc's own formations, meaningless to a
29
+ * skeleton rigc did not compile.
30
+ *
31
+ * Two assertions are MIXED and are marked `validity` here because their
32
+ * validity half must never stop running; their policy clauses are gated inside
33
+ * the assertion body against `profile`, and each such clause says so where it
34
+ * lives. They are A06 (size-vs-PNG and every region's rectangle inside the page
35
+ * it names are validity; pma and rotation are policy — the rectangle moved
36
+ * across that line in issue #694, and the reason is stated where it now sits;
37
+ * the policy clause about two regions over the same texels left A06 for
38
+ * `A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` in issue #1099, a renderer rule of
39
+ * its own) and A20 (weight coherence is validity; requiring
40
+ * a mesh to be weighted at all is policy).
41
+ *
42
+ * A08 was the third until issue #574 retired its policy clause. It required a
43
+ * skin entry's placeholder to be spelled like the region it resolves to, under
44
+ * a renderer that resolves art by `path` and has never read a placeholder — so
45
+ * restating it as the renderer's own join made it a tautology over the validity
46
+ * half. The argument, with the renderer lines it is measured against, sits above
47
+ * `check('A08_…')`.
48
+ */
49
+ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
50
+ A00_ROUNDTRIP_PARSE: 'validity',
51
+ A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS: 'validity',
52
+ A02_NO_BONE_TRANSFORM_KEY: 'validity',
53
+ A03_REGION_WIDTH_HEIGHT_FINITE: 'validity',
54
+ A04_MESH_TRIANGLES_AND_ENCODING: 'validity',
55
+ A05_CURVE_ARRAY_LENGTH: 'validity',
56
+ A06_ATLAS_PAGE_SIZE_MATCHES_PNG: 'validity', // mixed — see above
57
+ A07_ATLAS_TEXT_SHAPE: 'validity',
58
+ A08_REGION_NAMES_MATCH_ATTACHMENTS: 'validity', // no longer mixed — see above (#574)
59
+ A09_ANIMATION_DURATION_MATCHES_SPEC: 'validity',
60
+ A10_NO_NAN_AFTER_STEPPING: 'validity',
61
+ A11_NO_CLIPPING_ATTACHMENTS: 'renderer',
62
+ A12_NO_DARK_COLOR: 'renderer',
63
+ A13_MESH_BUDGET: 'renderer',
64
+ A14_NO_FULL_FRAME_MESH: 'renderer',
65
+ A15_IDLE_NO_MESH_BONE_KEYS: 'renderer',
66
+ A16_SKELETON_VERSION_4_3: 'validity',
67
+ A17_ATLAS_PAGE_FILES_EXIST: 'validity',
68
+ A18_DETERMINISTIC_EMIT: 'validity',
69
+ A19_OVERLAY_PNGS_HAVE_ALPHA: 'renderer',
70
+ A20_MESH_WEIGHTS_COHERENT: 'validity', // mixed — see above
71
+ A21_MESH_RIM_PINNED: 'archetype',
72
+ A22_MESH_UVS_IN_UNIT_RANGE: 'validity',
73
+ A23_PHYSICS_CONSTRAINT_EFFECTIVE: 'validity',
74
+ A24_AXIS_SPACE_STROKE: 'archetype',
75
+ A25_DETACHED_BONE_PARENTAGE: 'archetype',
76
+ A26_SLOT_DRAW_ORDER: 'archetype',
77
+ A27_REGION_NAME_MATCHES_PAGE_FILENAME: 'renderer',
78
+ A28_RIBBON_ROWS_SHARE_WEIGHTS: 'archetype',
79
+ A29_STROKE_WITHIN_CONTACT_DEPTH: 'archetype',
80
+ A30_STROKE_WITHIN_CAP_CONTAINMENT: 'archetype',
81
+ A31_DRAW_ORDER_OFFSETS_RESOLVE: 'validity',
82
+ A32_EVENT_KEYS_RESOLVE: 'validity',
83
+ A33_VERTEX_ATTACHMENT_GEOMETRY: 'validity',
84
+ A34_CONSTRAINT_TIMELINE_TARGETS: 'validity',
85
+ A35_DEFORM_KEYS_FIT_THE_ATTACHMENT: 'validity',
86
+ A36_PATH_CONSTRAINT_EFFECTIVE: 'validity',
87
+ A37_SLIDER_CONSTRAINT_EFFECTIVE: 'validity',
88
+ A38_SKIN_MEMBERS_ARE_SKIN_REQUIRED: 'validity',
89
+ A39_DEFORM_KEEPS_TRIANGLE_WINDING: 'archetype',
90
+ A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET: 'validity',
91
+ A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER: 'validity',
92
+ A43_TWO_COLOR_TINT_LOADS_AND_POSES_AS_WRITTEN: 'validity',
93
+ A44_LINKED_MESH_STATES_NO_GEOMETRY_OF_ITS_OWN: 'validity',
94
+ A45_SEPARABLE_COLOR_TIMELINES_OWN_THEIR_CHANNELS_AND_POSE_AS_WRITTEN: 'validity',
95
+ A46_SEQUENCE_ATTACHMENTS_SHOW_THE_FRAME_THE_FILE_STATES: 'validity',
96
+ A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
97
+ A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
98
+ A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP: 'renderer',
99
+ A50_STAGE_BOX_IS_THE_STAGE: 'validity',
100
+ };
101
+
102
+ /** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `spine-html` every kind. */
103
+ export function kindRunsUnder(kind: AssertionKind, profile: AssertionProfile): boolean {
104
+ return kind === 'validity' || profile === 'spine-html';
105
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * What built a mesh, as the bodies that hold generator topology to its own
3
+ * rules ask it (issue #1025, step 4c of #380) — `A20`, `A21` and `A28`. Moved
4
+ * here from `validate()`'s closures unchanged but for what they read: a mesh
5
+ * entry of `./facts/mesh_attachments.ts` rather than a loaded attachment and
6
+ * the prelude's link map. Links nothing from the runtime.
7
+ */
8
+ import type { RigInfo } from '../types.ts';
9
+ import type { MeshEntry } from './facts/mesh_attachments.ts';
10
+
11
+ /**
12
+ * What built this mesh. ring unless the rig says otherwise; absent rig info
13
+ * reads as ring (legacy).
14
+ *
15
+ * 🚨 `authored` is not a third topology, it is the ABSENCE of one rigc may
16
+ * assume. Geometry that came in through the rig spec was drawn by somebody
17
+ * with an editor, and its rim, its row pairing and its entry edge are
18
+ * whatever that person made them. The `||` fallback below used to hand such
19
+ * a mesh the string `ring`, and A21 then checked ring topology on a shape
20
+ * that was never a ring — 40 failures on correct data (issue #44).
21
+ *
22
+ * 🔗 A LINKED mesh borrows another attachment's geometry, so no generator
23
+ * topology is a claim about THIS attachment — and the ARTIFACT says which
24
+ * ones they are (the entry's `link`, read off the file's own `source` keys).
25
+ * It is read from there rather than off `meshKinds` for two reasons, and the
26
+ * second is the load-bearing one.
27
+ *
28
+ * 1. `meshKinds` is keyed by SLOT, and a link's slot is not its
29
+ * geometry's: a link to a `ring` in another slot looked up the LINK's
30
+ * slot, found nothing, and took the `|| 'ring'` fallback — issue #44's
31
+ * own default, reached by a new route. Measured before this clause on
32
+ * a correct rig (a ring on slot "sa", a link to it on slot "sb"):
33
+ * **8 A21 failures**, `mesh "sb" rim vertex 0 is pinned to "a", not
34
+ * the slot bone "b"`, one per hull vertex. The rim is pinned exactly
35
+ * where the ring's own slot put it, which is the only place it could
36
+ * be.
37
+ * 2. Writing the link into `meshKinds` instead would have overwritten
38
+ * the source's kind wherever the two share a slot, which is the
39
+ * commonest linked mesh there is — silencing A21 on the mesh rigc
40
+ * DID build. A gate turned off by a feature is worse than a gate
41
+ * that skips something it cannot measure.
42
+ */
43
+ export function meshKindOf(mesh: MeshEntry, rig: RigInfo | undefined): RigInfo['meshKinds'][string] {
44
+ if (mesh.link !== null) return 'authored';
45
+ return rig?.meshKinds[mesh.slot] || 'ring';
46
+ }
47
+
48
+ /**
49
+ * Meshes whose topology is not rigc's to have an opinion about, by name and
50
+ * with the reason each one is on the list — the string several skips need.
51
+ */
52
+ export function authoredMeshNames(list: readonly MeshEntry[], rig: RigInfo | undefined): string[] {
53
+ return list
54
+ .filter((m) => meshKindOf(m, rig) === 'authored')
55
+ .map((m) => (m.link === null ? `"${m.name}"` : `"${m.name}" (linked to "${m.link.source}")`));
56
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The model side's supply of `AnimatedBoneFacts` (issue #1025, step 4c of
3
+ * #380): an animation's bone timelines as the document lists them.
4
+ *
5
+ * The document writes each animation's `bones` as a list in the model's map
6
+ * order, and the emitter writes the Spine file's `bones` group from that same
7
+ * map, entry for entry, leaving the group out when it is empty
8
+ * (`emitAnimation`'s `if (animation.bones.size)`) — so an empty list is a file
9
+ * with no `bones` group, and an animation the document does not hold is one the
10
+ * file does not key. The first animation of a name answers, as the file's
11
+ * object would hold only one.
12
+ *
13
+ * 🔢 **The order is `keyedOrder` over the list's, not the list's** (issue
14
+ * #1039). The group is an object keyed by bone name, so the file lists an
15
+ * array-index name (`5`, `10`) first whatever order the emitter filled it in —
16
+ * measured on a walk whose tracks key `-a, 5, 2b, 10, …`: the document lists
17
+ * that, the file and spine-core `5, 10, -a, 2b, …`, and A15 prints one line per
18
+ * keyed bone in the file's order.
19
+ *
20
+ * Links nothing from the runtime.
21
+ */
22
+ import { keyedOrder } from '../../compile.ts';
23
+ import type { AnimatedBoneFacts } from '../facts/animated_bones.ts';
24
+ import { isObj } from '../values.ts';
25
+ import type { ReadDocument } from './parse.ts';
26
+
27
+ export function modelAnimatedBones(read: ReadDocument): AnimatedBoneFacts {
28
+ const animations = Array.isArray(read.json.animations) ? read.json.animations.filter(isObj) : [];
29
+ return {
30
+ bonesKeyedBy: (name) => {
31
+ const animation = animations.find((a) => a.name === name);
32
+ if (animation === undefined) return undefined;
33
+ const bones = Array.isArray(animation.bones) ? animation.bones.filter(isObj) : [];
34
+ if (bones.length === 0) return null;
35
+ return keyedOrder(bones.map((bone) => String(bone.name)));
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The model side's supply of `AnimationDurationFacts` (issue #1025, cut 4c-3
3
+ * of step 4c of #380): the document's animations in the file's order, each
4
+ * with the runtime's duration and every timeline's own duration.
5
+ *
6
+ * Every number is the core's reading, through functions rigc already runs:
7
+ *
8
+ * - **The roster's order** is the file's, `fileAnimationOrder` (`src/compile.ts`,
9
+ * issue #1034): a `rigc-compiled/3` document's stated `editorOrder`, else the
10
+ * emitter's own rule over the document's names — integer-like names first,
11
+ * as the file keys them.
12
+ * - **An animation's duration** is `CoreAnimationTimelines.duration`, the last
13
+ * key time of every timeline as float32 — the value `tools/pose_oracle.ts
14
+ * compare` holds equal to spine-core's on its `animations` roster.
15
+ * - **A timeline's own duration** is its last key's time as the core read it
16
+ * (float32): each bone and slot timeline, each constraint timeline in the
17
+ * order the runtime builds them (`modelConstraintTimelineSources`), each
18
+ * attachment's deform and sequence timeline, the draw order and the events.
19
+ * How many timelines that makes is the count the runtime builds
20
+ * (`timelineCount`, which the selftest's `VF10` holds equal to the runtime's
21
+ * on every slider animation); a walk that lists another count is this
22
+ * module's defect and throws by name rather than handing A09 a list of the
23
+ * wrong length.
24
+ *
25
+ * Links nothing from the runtime.
26
+ */
27
+ import { fileAnimationOrder } from '../../compile.ts';
28
+ import type { AnimationDuration, AnimationDurationFacts } from '../facts/animation_durations.ts';
29
+ import { modelConstraintTimelineSources, timelineCount } from './constraints.ts';
30
+ import type { ReadDocument } from './parse.ts';
31
+
32
+ /** The last of `times`, which the core read in key order. */
33
+ const lastOf = (times: ReadonlyArray<{ time: number }>): number[] => (times.length === 0 ? [] : [times[times.length - 1].time]);
34
+
35
+ export function modelAnimationDurations(read: ReadDocument): AnimationDurationFacts {
36
+ const constraintTimelines = modelConstraintTimelineSources(read);
37
+ const animations: AnimationDuration[] = [];
38
+ for (const name of fileAnimationOrder(read.doc)) {
39
+ const anim = read.doc.animations.find((a) => a.name === name);
40
+ if (anim === undefined) continue;
41
+ const t = anim.timelines;
42
+ const durations: number[] = [];
43
+ for (const target of [...t.bones, ...t.slots]) for (const tl of target.timelines) durations.push(...lastOf(tl.keys));
44
+ for (const source of constraintTimelines) if (source.animation === name) durations.push(...lastOf(source.keys));
45
+ for (const a of t.attachments) {
46
+ if (a.deform !== null) durations.push(...lastOf(a.deform));
47
+ if (a.sequence !== null) durations.push(...lastOf(a.sequence));
48
+ }
49
+ durations.push(...lastOf(t.drawOrder), ...lastOf(t.events));
50
+ const built = timelineCount(t);
51
+ if (durations.length !== built) {
52
+ throw new Error(`internal: animation "${name}": the durations walk lists ${durations.length} timeline(s) and the core reads ${built}`);
53
+ }
54
+ animations.push({ name, duration: t.duration, timelineDurations: durations });
55
+ }
56
+ return { animations };
57
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The model side's supply of `AtlasPageFacts` (issue #1025, step 4c of #380):
3
+ * the document's `pages` section, which `build` spells from the atlas text it
4
+ * writes (`pagesOfAtlas` in `src/model.ts`), so the page names are the ones
5
+ * the written atlas names, in its order and trimmed as the parser trims them.
6
+ * A `rigc-compiled/1` document states no pages, and reads as no atlas.
7
+ *
8
+ * Links nothing from the runtime.
9
+ */
10
+ import type { AtlasPageFacts } from '../facts/atlas_pages.ts';
11
+ import type { ReadDocument } from './parse.ts';
12
+
13
+ export function modelAtlasPages(read: ReadDocument): AtlasPageFacts {
14
+ return { atlas: read.doc.pages === null ? null : { pages: read.doc.pages.map((page) => ({ name: page.name })) } };
15
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The model side's supply of `AtlasRegionFacts` (issue #1025, step 4c of
3
+ * #380): the document's `pages` section — which `build` spells from the atlas
4
+ * text it writes (`pagesOfAtlas` in `src/model.ts`, the reader `PKR01` holds
5
+ * field for field to spine-core's `TextureAtlas`) — with each page's `pma` as
6
+ * a `rigc-compiled/3` document states it (issue #1026), or, for a `/2`
7
+ * document, which does not, as the caller gives it.
8
+ *
9
+ * - The pages and their regions in the document's order, which is the atlas
10
+ * file's; each region carries the page it sits on.
11
+ * - `findRegion` is the core's lookup, called: `documentPageLookup`
12
+ * (`src/core/uvs.ts`), the first region of exactly that spelling with pages
13
+ * in order — the runtime's `findRegion`, measured there.
14
+ * - A `rigc-compiled/1` document states no pages, and reads as no atlas.
15
+ *
16
+ * ⚠️ On a `/2` document `pma` is the one value here the document does not
17
+ * hold. A caller that gives none, or gives a list that is not one value per
18
+ * page, is refused by name rather than read as `false`: a page's claim the
19
+ * caller did not state is not a claim of straight alpha. On a `/3` document a
20
+ * caller that gives one anyway is refused too (`refuseGivenBeside`): the
21
+ * document is the statement, and a second source for the same fact is how a
22
+ * verdict comes to read one value while the document says another.
23
+ *
24
+ * Links nothing from the runtime.
25
+ */
26
+ import { documentPageLookup } from '../../core/uvs.ts';
27
+ import type { ModelPageRegion } from '../../model.ts';
28
+ import type { AtlasRegionEntry, AtlasRegionFacts, AtlasRegionPage } from '../facts/atlas_regions.ts';
29
+ import type { ReadDocument } from './parse.ts';
30
+ import { refuseGivenBeside, type ModelGiven } from './given.ts';
31
+
32
+ export function modelAtlasRegions(read: ReadDocument, given: ModelGiven | undefined): AtlasRegionFacts {
33
+ refuseGivenBeside(read, given);
34
+ const pages = read.doc.pages;
35
+ if (pages === null) return { atlas: null };
36
+ // A rigc-compiled/3 document states each page's pma (issue #1026); `readModel` requires it on every /3 page.
37
+ const pma = read.doc.stated !== null ? pages.map((page) => page.pma) : given?.pma;
38
+ if (pma === undefined || pma.length !== pages.length || pma.some((v) => typeof v !== 'boolean')) {
39
+ throw new Error(`the caller gave ${pma === undefined ? 'no' : pma.length} page pma value(s) for the document's ${pages.length} page(s); a ${read.doc.spec} document does not state pma, so the model side cannot read a page without it`);
40
+ }
41
+ const outPages: AtlasRegionPage[] = [];
42
+ const regions: AtlasRegionEntry[] = [];
43
+ const byRecord = new Map<ModelPageRegion, AtlasRegionEntry>();
44
+ pages.forEach((page, i) => {
45
+ const at: AtlasRegionPage = { name: page.name, width: page.width, height: page.height, pma: pma[i] === true };
46
+ outPages.push(at);
47
+ for (const region of page.regions) {
48
+ const entry: AtlasRegionEntry = {
49
+ name: region.name,
50
+ page: at,
51
+ x: region.x,
52
+ y: region.y,
53
+ width: region.width,
54
+ height: region.height,
55
+ degrees: region.degrees,
56
+ offsetX: region.offsetX,
57
+ offsetY: region.offsetY,
58
+ originalWidth: region.originalWidth,
59
+ originalHeight: region.originalHeight,
60
+ };
61
+ regions.push(entry);
62
+ byRecord.set(region, entry);
63
+ }
64
+ });
65
+ const lookup = documentPageLookup(pages);
66
+ return {
67
+ atlas: {
68
+ pages: outPages,
69
+ regions,
70
+ findRegion: (name) => {
71
+ const found = lookup(name);
72
+ return found === null ? null : (byRecord.get(found.region) ?? null);
73
+ },
74
+ },
75
+ };
76
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The model side's supply of `BoneTimelineFacts` (issue #1025, cut 4c-4 of
3
+ * #380): every animation's bone timelines as the Spine file states them.
4
+ *
5
+ * The same reading A45's slot timelines are given (`fileSlotTimelines` in
6
+ * `./slot_colour.ts`), over the `bones` group: the animations put in the
7
+ * file's order by `editorAnimationOrder` (`src/compile.ts`, the function the
8
+ * emitter is handed), each animation's bones and their timelines laid out as
9
+ * the file lays them (`animations.<name>.bones.<bone>.<timeline> = keys`,
10
+ * filled in the document's order, which is the order `emitAnimation` writes
11
+ * the model's map in — and read back out of that object, so an array-index
12
+ * bone name comes first as it does in the file: issue #1039 measured this
13
+ * reader already walking the file's order), and the whole handed to the emitter's own `withoutParserDefaults`
14
+ * and `inEditorKeyOrder` (`src/keyorder.ts`) — so a key field the emitter
15
+ * leaves out at the parser's default (`time: 0`, a translate key's `y: 0`) is
16
+ * left out here too, and a body printing a key's `time` prints what it prints
17
+ * over the file. Nothing here restates either rule.
18
+ *
19
+ * Links nothing from the runtime.
20
+ */
21
+ import { editorAnimationOrder } from '../../compile.ts';
22
+ import { inEditorKeyOrder, withoutParserDefaults } from '../../keyorder.ts';
23
+ import type { BoneTimelineFacts, BoneTimelines } from '../facts/bone_timelines.ts';
24
+ import { isObj, type Json } from '../values.ts';
25
+ import type { ReadDocument } from './parse.ts';
26
+
27
+ const list = (v: unknown): Json[] => (Array.isArray(v) ? v.filter(isObj) : []);
28
+
29
+ /** Every animation's bone timelines as the Spine file states them: `animations.<name>.bones.<bone>.<timeline> = keys`. */
30
+ export function fileBoneTimelines(read: ReadDocument): BoneTimelines[] {
31
+ const byName = new Map<string, Json>();
32
+ for (const anim of list(read.json.animations)) if (typeof anim.name === 'string') byName.set(anim.name, anim);
33
+ const animations: Json = {};
34
+ for (const name of editorAnimationOrder([...byName.keys()])) {
35
+ const bones: Json = {};
36
+ for (const bone of list((byName.get(name) as Json).bones)) {
37
+ if (typeof bone.name !== 'string') continue;
38
+ const timelines: Json = {};
39
+ for (const timeline of list(bone.timelines)) {
40
+ if (typeof timeline.name === 'string') timelines[timeline.name] = list(timeline.keys).map((key) => ({ ...key }));
41
+ }
42
+ bones[bone.name] = timelines;
43
+ }
44
+ // An animation keying no bone writes no `bones` group — `emitAnimation`'s `if (animation.bones.size)`.
45
+ animations[name] = Object.keys(bones).length > 0 ? { bones } : {};
46
+ }
47
+ const file = inEditorKeyOrder(withoutParserDefaults({ animations }));
48
+ const out: BoneTimelines[] = [];
49
+ for (const [animation, anim] of Object.entries(file.animations)) {
50
+ if (!isObj(anim) || !isObj(anim.bones)) continue;
51
+ for (const [bone, timelines] of Object.entries(anim.bones)) out.push({ animation, bone, timelines });
52
+ }
53
+ return out;
54
+ }
55
+
56
+ export function modelBoneTimelines(read: ReadDocument): BoneTimelineFacts {
57
+ return { boneTimelines: fileBoneTimelines(read) };
58
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The model side's supply of `ConstraintTargetFacts` (issue #1025, cut 4c-5
3
+ * of step 4c of #380): the document's constraints and every animation's
4
+ * constraint groups, laid out as the Spine file lays them.
5
+ *
6
+ * - **The constraints**: the document's, in its order — which the emitter
7
+ * writes the `constraints` array in — each its name and its kind, the word
8
+ * the file spells its `type` with.
9
+ * - **The animations**: in the file's order (`fileAnimationOrder`, issue
10
+ * #1034); in each the five groups in A34's fixed order, a group only where
11
+ * the document holds an entry for it (`emitAnimation` writes a group only
12
+ * when the model holds something for it); each group's entries in the order
13
+ * the file keys them — an object the emitter fills in the document's order,
14
+ * so its names land as `keyedOrder` lands them, built here as that object;
15
+ * the timeline naming no physics constraint under the empty name the
16
+ * emitter spells it with, where the document spells it `*`
17
+ * (`EVERY_GLOBAL_PHYSICS`); each entry's timelines in the document's order,
18
+ * which the emitter writes unchanged (a name-keyed map no key-order pass
19
+ * touches, `src/keyorder.ts`). A document the reader accepted states every
20
+ * key array as a non-empty list, a named group's entry as named timelines,
21
+ * and only constraints of the kind its group names, so the clauses that
22
+ * refuse those shapes have nothing to fire on here.
23
+ * - **The reach** of a physics timeline naming none: the core's own reading,
24
+ * `unnamedReach` (`./constraints.ts`, which poses the timeline through
25
+ * `posedPhysics` and reads which records took the key), over the document's
26
+ * physics records in its order. The reader refuses a physics timeline name
27
+ * it does not know, so every name asked of it is one the core poses.
28
+ *
29
+ * Links nothing from the runtime.
30
+ */
31
+ import { fileAnimationOrder } from '../../compile.ts';
32
+ import { EVERY_GLOBAL_PHYSICS, PHYSICS_TIMELINE_KINDS, type CorePhysicsRecord } from '../../core/constraints_physics.ts';
33
+ import type { ConstraintTargetFacts, TargetAnimation, TargetEntry, TargetKeyArray } from '../facts/constraint_targets.ts';
34
+ import { isObj, type Json } from '../values.ts';
35
+ import { unnamedReach } from './constraints.ts';
36
+ import type { ReadDocument } from './parse.ts';
37
+
38
+ const list = (v: unknown): Json[] => (Array.isArray(v) ? v.filter(isObj) : []);
39
+ const keyArray = (timeline: string, keys: unknown): TargetKeyArray => ({ timeline, keys: Array.isArray(keys) ? { count: keys.length } : { spelled: `${JSON.stringify(keys)}` } });
40
+
41
+ /** One group's entries as the file keys them: an object filled in the document's order, read back in its key order. */
42
+ function fileEntries(group: 'ik' | 'transform' | 'path' | 'physics' | 'slider', entries: readonly Json[]): TargetEntry[] {
43
+ const keyed: Record<string, TargetEntry> = {};
44
+ for (const entry of entries) {
45
+ if (typeof entry.name !== 'string') continue;
46
+ const name = group === 'physics' && entry.name === EVERY_GLOBAL_PHYSICS ? '' : entry.name;
47
+ keyed[name] =
48
+ group === 'ik' || group === 'transform'
49
+ ? { name, bare: null, keyArrays: [keyArray('', entry.keys)] }
50
+ : { name, bare: null, keyArrays: list(entry.timelines).flatMap((t) => (typeof t.name === 'string' ? [keyArray(t.name, t.keys)] : [])) };
51
+ }
52
+ return Object.values(keyed);
53
+ }
54
+
55
+ export function modelConstraintTargets(read: ReadDocument): ConstraintTargetFacts {
56
+ const { doc } = read;
57
+ const byName = new Map<string, Json>();
58
+ for (const anim of list(read.json.animations)) if (typeof anim.name === 'string') byName.set(anim.name, anim);
59
+ const animations: TargetAnimation[] = [];
60
+ for (const name of fileAnimationOrder(doc)) {
61
+ const anim = byName.get(name);
62
+ if (anim === undefined) continue;
63
+ const groups: TargetAnimation['groups'][number][] = [];
64
+ const constraints = isObj(anim.constraints) ? anim.constraints : {};
65
+ for (const group of ['ik', 'transform', 'path', 'physics', 'slider'] as const) {
66
+ const entries = list(constraints[group]);
67
+ if (entries.length > 0) groups.push({ group, entries: fileEntries(group, entries) });
68
+ }
69
+ animations.push({ name, groups });
70
+ }
71
+ const records = doc.constraints.flatMap((c): CorePhysicsRecord[] => (c.record?.kind === 'physics' ? [c.record] : []));
72
+ return {
73
+ constraints: doc.constraints.map((c) => ({ name: c.name, type: c.kind, spelled: c.name })),
74
+ groupsByAnimation: animations,
75
+ reach: (timeline) => {
76
+ const kind = PHYSICS_TIMELINE_KINDS.find((k) => k === timeline);
77
+ if (kind === undefined) return null;
78
+ const reached = unnamedReach(records, kind);
79
+ return { resets: kind === 'reset', reached: records.filter((r) => reached.has(r.name)).map((r) => r.name) };
80
+ },
81
+ };
82
+ }