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,386 @@
1
+ /**
2
+ * What the deform survey reads of a skeleton's STRUCTURE — as opposed to what
3
+ * it reads off a posed one — behind one interface, and the model document's
4
+ * implementation of it (issue #1019, step 4b of issue #380).
5
+ *
6
+ * ## Why there are two readers
7
+ *
8
+ * The survey (`src/deformmeasure.ts`) poses a skeleton and measures what each
9
+ * deform key does to it, and its posing has run through rigc's own core since
10
+ * issue #969. Everything else it read was still spine-core's: the animations
11
+ * and their deform timelines, each key's time and expanded vertex array, the
12
+ * runtime's curve storage, which slot a timeline names and which other slots it
13
+ * reaches, which skin holds the mesh, and each slider's settings — so `explain`
14
+ * on a rigc build parsed the pair through the runtime to learn what the model
15
+ * document it carries already states. This module states those reads once
16
+ * (`SurveyStructure`), and gives the model document's reading of them
17
+ * (`modelStructure`). The runtime's reading stays in `src/deformmeasure.ts`,
18
+ * which links spine-core; the survey's body is written once, against the
19
+ * interface, and the survey's record names which pair it ran through.
20
+ *
21
+ * ## What the model states, and what is derived from it — each rule measured
22
+ *
23
+ * Every read below was compared with spine-core's, value for value at
24
+ * tolerance 0 (`tools/survey_hashes.ts structure` over the nineteen recipes
25
+ * `tools/emit_hashes.ts` generates; the deform-core suite's `DM12` over the
26
+ * gallery's builds and 80 hand-written rigs — sliders, second skins and linked
27
+ * meshes in slots of their own among them). Where the model holds the
28
+ * runtime's value as it is, the reader passes it on; where it holds the value
29
+ * the runtime COMPUTED from, the computation is one rigc already runs — the
30
+ * core's, measured against the runtime for posing, or the emitter's own order —
31
+ * and not a second copy. Each derivation, with how often the stated value alone
32
+ * would have been the runtime's on the nineteen:
33
+ *
34
+ * - **A key's time** is the float32 of the stated time (`CoreDeformKey.time`,
35
+ * which the core's reader stores) — the runtime's `frames` are a
36
+ * `Float32Array`. The stated double was the runtime's on 43 of 88 keys.
37
+ * - **A key's vertex array** is what the runtime holds for the key, not the
38
+ * run the document states: positions unweighted (each the float32 sum of the
39
+ * setup coordinate and the offset), offsets weighted (each float32), the run
40
+ * placed at its `offset` into the attachment's whole array and zeros (setup
41
+ * positions, unweighted) around it — `heldArray` in `./core/deform.ts`. The
42
+ * stated run was the runtime's array on 0 of 88.
43
+ * - **A Bézier segment's polyline** is the nine points the runtime samples into
44
+ * its curve storage: the cubic through the key's four handles, from the key's
45
+ * stated time at fraction 0 to the next key's at a far end of `0.99999999` —
46
+ * `bezierPolyline` with `DEFORM_CURVE_END`, the core's deform curve. The four
47
+ * handles are, of course, never the eighteen numbers (0 of 23).
48
+ * - **The animations' order** is the Spine file's, which is not the model's:
49
+ * the emitter keys `animations` in the editor's order over the model's names
50
+ * (`editorAnimationOrder` in `src/compile.ts`, the one call `emitAnimations`
51
+ * is handed), and the survey iterates the file's order. The model's own order
52
+ * was the file's on 14 of 19 rows. Since issue #1026 a `rigc-compiled/3`
53
+ * document states that order (`editorOrder.animations`, computed by the
54
+ * same function at compile time) and the survey reads it; a `/2` or `/1`
55
+ * document's is derived by that function (`fileAnimationOrder`), which
56
+ * since issue #1034 returns the file's order — integer-like names first, as
57
+ * an object keys them, where the comparator alone put `-a` before `5`.
58
+ * - **An animation's deform timelines** are in the order the file keys them:
59
+ * the model's, with each of the three levels (skin, slot, attachment) put
60
+ * through `keyedOrder` (`attachmentTimelinesInFileOrder`, issue #1034) —
61
+ * the model's own order on every name that is not integer-like. The sliders
62
+ * the file keeps from the model.
63
+ * - **The skins** are in the file's order (`fileSkinOrder`, issue #1034): the
64
+ * order a `/3` document states, else the emitter's `editorSkinOrder`. This
65
+ * said, until #1034, that the file keeps a skin's order from the model; the
66
+ * emitter sorts the skins (`default` first, the rest by the editor's
67
+ * comparator), and a probe declaring `default, 5, 10, -a, 2b, 01` read
68
+ * `default, -a, 01, 2b, 5, 10` off spine-core and the declared order off the
69
+ * model — no row had carried a set the comparator moves.
70
+ * - **A slider's duration** is the runtime's (`CoreAnimationTimelines.duration`:
71
+ * the last key time of every timeline, float32), not the document's declared
72
+ * one, which was the runtime's on 1 of 2.
73
+ * - **The slots a deform reaches besides its own** (`timelineSlots`) are the
74
+ * slots of every linked mesh whose source is the deformed record and which
75
+ * plays its timelines (`timelines: true`) — the rule `./core/deform.ts`'s
76
+ * *Which slot a deform timeline moves* measured. The survey reads them as a
77
+ * set: whether any of them draws the mesh, and their slot timelines' key
78
+ * times. No corpus row has one; `DM12`'s linked rigs do, on 20 of 20.
79
+ *
80
+ * ⛔ Nothing here defaults a value the document does not state: a record the
81
+ * timeline names that the document does not hold, or an order the editor's
82
+ * comparator cannot settle, is a `CoreInputError` naming it — and the survey's
83
+ * seam then falls back to spine-core naming why, as the posing does.
84
+ *
85
+ * ## Purity
86
+ *
87
+ * Nothing from the Spine runtime package: what the runtime reader needs to
88
+ * carry beside these handles (the parsed objects themselves) is kept by that
89
+ * reader, in `src/deformmeasure.ts`.
90
+ */
91
+ import { CompileError } from './errors.ts';
92
+ import { fileAnimationOrder, fileSkinOrder, keyedOrder, skinsInFileOrder } from './compile.ts';
93
+ import { bezierPolyline } from './core/animation.ts';
94
+ import { DEFORM_CURVE_END, heldArray, type CoreAttachmentTimeline, type CoreDeformKey } from './core/deform.ts';
95
+ import type { DialBoneField } from './core/hooks.ts';
96
+ import { CoreInputError, type CompiledDocument, type CoreAnimation, type CoreAttachment } from './core/index.ts';
97
+
98
+ /** The record a mesh is in the model document — the identity a deform timeline keys and the core poses by. */
99
+ export interface SurveyRecord {
100
+ skin: string;
101
+ slot: string;
102
+ placeholder: string;
103
+ }
104
+
105
+ /** A skin, as the survey wears it. One handle per skin: the survey keys its caches on the handle. */
106
+ export interface SurveySkin {
107
+ readonly name: string;
108
+ }
109
+
110
+ /** A mesh a deform timeline moves, as far as the survey reads it. */
111
+ export interface SurveyMesh {
112
+ /** The attachment's name — the record's stated `name`, else its placeholder. */
113
+ readonly name: string;
114
+ readonly triangles: ArrayLike<number>;
115
+ /** Two numbers per vertex. */
116
+ readonly worldVerticesLength: number;
117
+ /** Whether its vertices are bound to bones (`MeshAttachment.bones !== null`). */
118
+ readonly weighted: boolean;
119
+ /** The other slots the deform reaches (`Attachment.timelineSlots`), read as a set. */
120
+ readonly timelineSlots: readonly number[];
121
+ /** The model record it is, or `null` when no skin holds it — the core then refuses to pose it, by name. */
122
+ readonly record: SurveyRecord | null;
123
+ }
124
+
125
+ /** How the runtime gets from one key's geometry to the next. */
126
+ export type SurveyCurve = { kind: 'linear' } | { kind: 'stepped' } | { kind: 'bezier'; points: ArrayLike<number> };
127
+
128
+ /** One deform timeline of one animation. */
129
+ export interface SurveyDeformTimeline {
130
+ /** The slot it is filed under, by index into `SurveyStructure.slotName`. */
131
+ readonly slotIndex: number;
132
+ /** The mesh it moves, or `null` when the attachment has a vertex array and no triangles (a bounding box, a clipping polygon, a path). */
133
+ readonly mesh: SurveyMesh | null;
134
+ /** Each key's time, as the runtime stores it. */
135
+ readonly frames: ArrayLike<number>;
136
+ /** Key `frame`'s vertex array, as the runtime holds it. */
137
+ vertices(frame: number): ArrayLike<number> | undefined;
138
+ /** The curve from key `frame` to key `frame + 1`. */
139
+ curve(frame: number): SurveyCurve;
140
+ /** The skin that holds the mesh, by name and handle (`null` when none does), and the placeholder the timeline names. */
141
+ placement(): { skin: string; holder: SurveySkin | null; placeholder: string };
142
+ }
143
+
144
+ /** One animation, as far as the survey reads it. */
145
+ export interface SurveyAnimation {
146
+ readonly name: string;
147
+ /** Its deform timelines, in the runtime's order. */
148
+ readonly deforms: readonly SurveyDeformTimeline[];
149
+ /** Every key time of every timeline filed under one of `slots` — the times a slot's visibility can change at. */
150
+ slotKeyTimes(slots: ReadonlySet<number>): number[];
151
+ }
152
+
153
+ /**
154
+ * One slider constraint, as far as the survey reads it.
155
+ *
156
+ * ⚠️ `stated`, `reader`, `from`, `to` and `scale` are the dial's mapping and are
157
+ * read only for a slider with a `bone`: a bone-less one's dial is its time. The
158
+ * two readers part there and nothing reads the difference — spine-core sets no
159
+ * property on a bone-less slider and leaves its `scale` at 0, where the model
160
+ * document states the parser's 1 (measured, `tools/survey_hashes.ts structure`).
161
+ */
162
+ export interface SurveySlider {
163
+ readonly name: string;
164
+ /** Its mix at setup. */
165
+ readonly mix: number;
166
+ /** The animation it applies, by name. */
167
+ readonly animation: string;
168
+ /** That animation's duration, as the runtime computes it. */
169
+ readonly duration: number;
170
+ /** Its driving bone, or `null` on the bone-less form. */
171
+ readonly bone: string | null;
172
+ readonly local: boolean;
173
+ /** The bone field its property reads — `null` on a reader this file does not know. */
174
+ readonly stated: DialBoneField | null;
175
+ /** What that reader is called, for a report that has to name an unknown one. */
176
+ readonly reader: string;
177
+ /** `from` — the property value at which the animation's time is `to`. */
178
+ readonly from: number;
179
+ /** `to`. */
180
+ readonly to: number;
181
+ readonly scale: number;
182
+ readonly loop: boolean;
183
+ }
184
+
185
+ /** Everything the survey reads of a skeleton's structure. */
186
+ export interface SurveyStructure {
187
+ /** Every skin, one handle each, in the skeleton's order — not read by the survey, which wears the skin a timeline names; the instruments walk it. */
188
+ readonly skins: readonly SurveySkin[];
189
+ /** In the runtime's order. */
190
+ readonly animations: readonly SurveyAnimation[];
191
+ /** In the skeleton's constraint order. */
192
+ readonly sliders: readonly SurveySlider[];
193
+ /** A slot's name, or `#<index>` for an index that is not a slot. */
194
+ slotName(index: number): string;
195
+ /** How many skins carry this name — the core wears a skin by its name, so two of one name cannot be posed. */
196
+ skinsNamed(name: string): number;
197
+ }
198
+
199
+ /** The bone field each rig-spec property reads. */
200
+ const PROPERTY_FIELD: Record<string, DialBoneField> = { rotate: 'rotation', x: 'x', y: 'y', scaleX: 'scaleX', scaleY: 'scaleY', shearY: 'shearY' };
201
+
202
+ /** The model document's reading of the survey's structure (the header). */
203
+ export function modelStructure(doc: CompiledDocument): SurveyStructure {
204
+ const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i] as const));
205
+ const skinHandles = new Map<string, SurveySkin>();
206
+ const skinOf = (name: string): SurveySkin | null => {
207
+ if (!doc.skins.some((k) => k.name === name)) return null;
208
+ let handle = skinHandles.get(name);
209
+ if (handle === undefined) {
210
+ handle = { name };
211
+ skinHandles.set(name, handle);
212
+ }
213
+ return handle;
214
+ };
215
+ const recordAt = (skin: string, slot: string, placeholder: string): CoreAttachment | undefined => doc.skins.find((k) => k.name === skin)?.attachments[slot]?.[placeholder];
216
+ /** The slots of every linked mesh that plays this record's timelines (the header's last rule). */
217
+ const linkedSlots = (record: SurveyRecord): number[] => {
218
+ const out: number[] = [];
219
+ for (const skin of doc.skins) {
220
+ for (const [slot, table] of Object.entries(skin.attachments)) {
221
+ for (const entry of Object.values(table)) {
222
+ const g = entry.geometry;
223
+ if (g?.kind !== 'linkedmesh' || entry.timelines !== true) continue;
224
+ if (g.skin !== record.skin || g.slot !== record.slot || g.source !== record.placeholder) continue;
225
+ const at = slotIndex.get(slot);
226
+ if (at !== undefined && !out.includes(at)) out.push(at);
227
+ }
228
+ }
229
+ }
230
+ return out;
231
+ };
232
+ const meshes = new Map<string, SurveyMesh>();
233
+ const meshOf = (t: CoreAttachmentTimeline): SurveyMesh | null => {
234
+ const key = `${t.skin}/${t.slot}/${t.attachment}`;
235
+ const already = meshes.get(key);
236
+ if (already !== undefined) return already;
237
+ const entry = recordAt(t.skin, t.slot, t.attachment);
238
+ if (entry === undefined) throw new CoreInputError(`the deform timeline ${key} names no record of the model document`);
239
+ const g = entry.geometry;
240
+ if (g?.kind !== 'mesh') return null;
241
+ const record = { skin: t.skin, slot: t.slot, placeholder: t.attachment };
242
+ const mesh: SurveyMesh = {
243
+ name: entry.name ?? t.attachment,
244
+ triangles: g.triangles,
245
+ worldVerticesLength: g.vertices.weighted ? 2 * g.vertices.bindings.length : g.vertices.xy.length,
246
+ weighted: g.vertices.weighted,
247
+ timelineSlots: linkedSlots(record),
248
+ record,
249
+ };
250
+ meshes.set(key, mesh);
251
+ return mesh;
252
+ };
253
+ const deformOf = (t: CoreAttachmentTimeline, keys: readonly CoreDeformKey[]): SurveyDeformTimeline => {
254
+ const at = slotIndex.get(t.slot);
255
+ if (at === undefined) throw new CoreInputError(`the deform timeline ${t.skin}/${t.slot}/${t.attachment} names slot "${t.slot}", which is not a slot of the model document`);
256
+ const mesh = meshOf(t);
257
+ const entry = recordAt(t.skin, t.slot, t.attachment);
258
+ const g = entry?.geometry;
259
+ const vertices = g !== undefined && g.kind !== 'region' && g.kind !== 'linkedmesh' ? g.vertices : null;
260
+ const held = new Map<number, number[]>();
261
+ return {
262
+ slotIndex: at,
263
+ mesh,
264
+ frames: keys.map((k) => k.time),
265
+ vertices: (frame) => {
266
+ const key = keys[frame];
267
+ if (key === undefined || vertices === null) return undefined;
268
+ let array = held.get(frame);
269
+ if (array === undefined) {
270
+ array = heldArray(vertices, key);
271
+ held.set(frame, array);
272
+ }
273
+ return array;
274
+ },
275
+ curve: (frame) => {
276
+ const a = keys[frame];
277
+ const b = keys[frame + 1];
278
+ if (a === undefined || b === undefined) throw new CoreInputError(`the deform timeline ${t.skin}/${t.slot}/${t.attachment} has no segment after key ${frame}`);
279
+ if (a.curve === 'linear') return { kind: 'linear' };
280
+ if (a.curve === 'stepped') return { kind: 'stepped' };
281
+ const c = a.curve;
282
+ return { kind: 'bezier', points: bezierPolyline(a.stated, 0, c[0], c[1], c[2], c[3], b.stated, DEFORM_CURVE_END) };
283
+ },
284
+ placement: () => ({ skin: t.skin, holder: skinOf(t.skin), placeholder: t.attachment }),
285
+ };
286
+ };
287
+ const animationOf = (a: CoreAnimation): SurveyAnimation => {
288
+ const deforms: SurveyDeformTimeline[] = [];
289
+ for (const t of attachmentTimelinesInFileOrder(a.timelines.attachments)) if (t.deform !== null) deforms.push(deformOf(t, t.deform));
290
+ return {
291
+ name: a.name,
292
+ deforms,
293
+ slotKeyTimes: (slots) => {
294
+ const times: number[] = [];
295
+ for (const target of a.timelines.slots) {
296
+ const at = slotIndex.get(target.name);
297
+ if (at === undefined || !slots.has(at)) continue;
298
+ for (const tl of target.timelines) for (const k of tl.keys) times.push(k.time);
299
+ }
300
+ for (const t of a.timelines.attachments) {
301
+ const at = slotIndex.get(t.slot);
302
+ if (at === undefined || !slots.has(at)) continue;
303
+ for (const k of [...(t.deform ?? []), ...(t.sequence ?? [])]) times.push(k.time);
304
+ }
305
+ return times;
306
+ },
307
+ };
308
+ };
309
+ // The order the Spine file lists the animations in (`fileAnimationOrder`,
310
+ // issue #1034): stated by a `rigc-compiled/3` document (`editorOrder`, issue
311
+ // #1026), and derived for a `/2` or `/1` one, which does not state it, by the
312
+ // emitter's own rule — integer-like names first, as the file keys them.
313
+ let order: readonly string[];
314
+ try {
315
+ order = fileAnimationOrder(doc);
316
+ } catch (err) {
317
+ if (!(err instanceof CompileError)) throw err;
318
+ throw new CoreInputError(`the animations' order in the Spine file is not settled by the model's names — ${err.message}`);
319
+ }
320
+ const animations = order.map((name) => {
321
+ const a = doc.animations.find((x) => x.name === name);
322
+ if (a === undefined) throw new CoreInputError(`the editor's order named animation "${name}", which is not in the model document`);
323
+ return animationOf(a);
324
+ });
325
+ const sliders: SurveySlider[] = [];
326
+ for (const c of doc.constraints) {
327
+ const r = c.record;
328
+ if (c.kind !== 'slider' || r?.kind !== 'slider') continue;
329
+ sliders.push({
330
+ name: r.name,
331
+ mix: r.mix,
332
+ animation: r.animation,
333
+ duration: r.timelines.duration,
334
+ bone: r.bone,
335
+ local: r.local,
336
+ stated: PROPERTY_FIELD[r.property] ?? null,
337
+ reader: r.property,
338
+ from: r.from,
339
+ to: r.to,
340
+ scale: r.scale,
341
+ loop: r.loop,
342
+ });
343
+ }
344
+ // The skins in the order the Spine file lists them (`fileSkinOrder`, issue #1034), as the runtime's structure lists its skins:
345
+ // stated by a `/3` document, derived for a `/2` or `/1` one by the emitter's rule, which a comparator-moved name set shows.
346
+ let skins: CompiledDocument['skins'];
347
+ try {
348
+ skins = skinsInFileOrder(doc.skins, fileSkinOrder(doc)).map((walk) => walk.skin);
349
+ } catch (err) {
350
+ if (!(err instanceof CompileError)) throw err;
351
+ throw new CoreInputError(`the skins' order in the Spine file is not settled by the model's names — ${err.message}`);
352
+ }
353
+ return {
354
+ skins: skins.flatMap((k) => {
355
+ const handle = skinOf(k.name);
356
+ return handle === null || skins.find((x) => x.name === k.name) !== k ? [] : [handle];
357
+ }),
358
+ animations,
359
+ sliders,
360
+ slotName: (index) => doc.slots[index]?.name ?? `#${index}`,
361
+ skinsNamed: (name) => doc.skins.filter((k) => k.name === name).length,
362
+ };
363
+ }
364
+
365
+ /**
366
+ * An animation's attachment timelines in the order the Spine file keys them
367
+ * (issue #1034): the emitter writes `attachments` as three nested objects —
368
+ * skin, slot, attachment — each filled in the model's order, and an object
369
+ * lists an integer-like key first (`keyedOrder`). So the model's list is
370
+ * grouped skin by skin and slot by slot in the order each first appears, and
371
+ * each level put through `keyedOrder`. Measured on a build whose deform
372
+ * timelines are filed under slots `10`, `5`, `-a` in the model: the file and
373
+ * spine-core list `5, 10, -a`.
374
+ */
375
+ function attachmentTimelinesInFileOrder(timelines: readonly CoreAttachmentTimeline[]): CoreAttachmentTimeline[] {
376
+ const firsts = (names: readonly string[]): string[] => keyedOrder(names.filter((name, i) => names.indexOf(name) === i));
377
+ const out: CoreAttachmentTimeline[] = [];
378
+ for (const skin of firsts(timelines.map((t) => t.skin))) {
379
+ const inSkin = timelines.filter((t) => t.skin === skin);
380
+ for (const slot of firsts(inSkin.map((t) => t.slot))) {
381
+ const inSlot = inSkin.filter((t) => t.slot === slot);
382
+ for (const attachment of firsts(inSlot.map((t) => t.attachment))) out.push(...inSlot.filter((t) => t.attachment === attachment));
383
+ }
384
+ }
385
+ return out;
386
+ }