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,613 @@
1
+ /**
2
+ * Construct 4 of the core, its remainder (issue #955, step 2f of issue #380):
3
+ * the two timelines an attachment carries — `deform`, a vertex attachment's
4
+ * vertices keyed over time, and `sequence`, the frame a region's series shows
5
+ * — at a sample time, and under a slider at the time and mix it was applied
6
+ * with.
7
+ *
8
+ * Every rule below was measured by posing hand-written skeletons through
9
+ * `tools/pose_oracle.ts dump` (spine-core 4.3.13, `--skin all`,
10
+ * `--physics none`) and comparing a formula over the core's own world
11
+ * transforms (`./world.ts`, exact on every probe) with the rows the dump
12
+ * printed, at the oracle's six-decimal rounding. Nothing here was written
13
+ * from the runtime's source. The core suite's `CD` controls hold the same
14
+ * skeletons against the core at tolerance 0.
15
+ *
16
+ * ## Deform: what a key holds
17
+ *
18
+ * A key states a run — `vertices`, starting at `offset` (0 unstated) — into
19
+ * the attachment's deform array: one `x, y` pair per vertex of an unweighted
20
+ * attachment, one pair per BINDING of a weighted one (in vertex order, then
21
+ * binding order). What the run does not cover is an offset of 0, and a key
22
+ * with no `vertices` is all zeros — the setup geometry.
23
+ *
24
+ * - **Unweighted, the runtime holds the key as positions, each the float32 sum
25
+ * `fround(fround(setup) + fround(offset))`.** Over 800 samples of 40
26
+ * random meshes on rotated, scaled bones, five-decimal numbers: that
27
+ * reading 0 misses; the double sum lerped 788, the lerped positions
28
+ * rounded to float32 791, the offsets lerped and then added 788.
29
+ * - **Weighted, the runtime holds the key as offsets** (`fround(offset)`) and
30
+ * adds each to its binding's float32 coordinate when the world vertices
31
+ * are computed: `(fround(bx) + dx)·a + (fround(by) + dy)·b + worldX`, times
32
+ * the weight, summed. Over 1,600 samples of 80 random meshes over one to
33
+ * three of three bones: 0 misses; the float32 sum held as a position 775
34
+ * and 780, the lerped offset rounded to float32 711 and 716.
35
+ *
36
+ * ## Deform: at a sample time
37
+ *
38
+ * The key search is construct 4's (the last key at or before `t`, key times
39
+ * float32). Before the first key the setup geometry; at or after the last
40
+ * key, and on a `stepped` segment, that key; between two keys the lerp
41
+ * `a + (b − a)·p` of the two held arrays, in double — measured above, and on
42
+ * 7,200 samples of 180 random multi-key timelines (runs short and offset,
43
+ * keys without vertices, stepped, linear and Bézier segments): 0 misses with
44
+ * the Bézier rule below, 114 with the far end 1, every one on a Bézier
45
+ * piece next to the eighth or ninth point. The form `a·(1 − p) + b·p` read
46
+ * the same 0: the two differ below the oracle's grid, so which one the
47
+ * runtime writes is not measurable here, and the first is taken.
48
+ *
49
+ * - **Linear**: `p = (t − t0) / (t1 − t0)`, the key times float32 (a float32
50
+ * `p` missed 341 of 2,400).
51
+ * - **Bézier**: one channel whose value axis runs from 0 to 1 — the fraction
52
+ * of the way from one key's geometry to the next — shaped as a bone
53
+ * channel's is (`bezierPolyline` in `./animation.ts`: the polyline through
54
+ * the cubic's nine points at tenths of its parameter, stated numbers,
55
+ * float32 points) with ONE measured difference: ⚠️ **the recurrence runs to
56
+ * a far end of `0.99999999`, not 1, while the polyline's last point is the
57
+ * next key at 1.** Read off 126 curves, each point fixed by intersecting
58
+ * the two pieces meeting at it (a 10⁵-unit run makes `p` legible to 1e-11):
59
+ * with the far end 1 the points missed 64 of 1,134; with `0.99999999`, 1 —
60
+ * a first point whose piece held too few samples to fit — and every other
61
+ * far end tried missed more (1 − 2⁻²⁷ 17, 0.999999989 10, 0.99999998 68;
62
+ * the exact one-sixth with the far end 1, 68). On the 1,080 points of the
63
+ * 120 random curves among them, the other readings of the recurrence
64
+ * missed more still: float32 handles 222, float32 running sums or
65
+ * constants 258 and more (1,024 combinations tried), the cubic evaluated
66
+ * directly 67. The residual the far end 1 leaves is one float32 step,
67
+ * always downward, growing with the point's index. A bone
68
+ * channel shaped by the same handles read the far end 1 (`CD02` holds
69
+ * both): the deform curve is its own reading. The last piece, from the
70
+ * ninth point to the next key, read exact.
71
+ * ⚠️ **Which form each piece takes (issue #975)**: the first piece, from
72
+ * the key to the first point, is `y1·(t − x0) / (x1 − x0)`; the last, from
73
+ * the ninth point to the next key, `y0 + (1 − y0)·(t − x0) / (x1 − x0)`
74
+ * (each product before its division); the eight between are the bone
75
+ * channel's `y0 + (t − x0) / (x1 − x0) · (y1 − y0)`. A bone channel reads
76
+ * all ten pieces in the between form (`channelAt`, bit-exact at a 1e9
77
+ * amplifier, `CR08`). The forms differ below the oracle's six-decimal grid,
78
+ * so they were read off `pose_oracle --raw` at tolerance 0 on `CR09`'s
79
+ * cells (60 five-vertex meshes, one deform segment each, 40 irrational
80
+ * samples; five-decimal keys / float32-exact keys): every piece in the
81
+ * between form 30 / 31 of 60 exact; the first piece's product alone 52 /
82
+ * 53; the last piece's alone 37 / 37; both 60 / 60 (and 60 / 60 on another
83
+ * seed at 200 samples, where the between form read 14 / 14). The far end
84
+ * stays `0.99999999`: with both forms and the far end 1, 38 / 38.
85
+ *
86
+ * ## Under a slider
87
+ *
88
+ * A slider applies its animation's deforms after the sample's own, at its
89
+ * time, from the CURRENT deform — the sample's, or the setup geometry where
90
+ * the sample set none (the setup positions unweighted, zero offsets
91
+ * weighted) — with alpha its mix: `current + (target − current)·mix`, and
92
+ * `additive` `current + (target − setup)·mix` (the setup positions
93
+ * unweighted, zeros weighted). ⚠️ At mix exactly 1, not additive, it IS the
94
+ * target: `current + (target − current)·1` read 1 ulp off spine-core on 27
95
+ * of 2,790 posed rows of issue #969's hand-written population (`DM01`, the
96
+ * survey's doubles), the target on none. ⚠️ **Additive at mix exactly 1
97
+ * over NO current deform** — at the setup pose, or at a sample whose own
98
+ * animation set none on that slot — **it is the target too**, not
99
+ * `setup + (target − setup)·1` (issue #993, read off `pose_oracle --raw` at
100
+ * tolerance 0; weighted, the setup is zeros and the two agree to the bit).
101
+ * On 1,000 rigs of `CD05`'s generator: that rule 1,000 exact; the stated
102
+ * blend everywhere 985 (15 misses, every one unweighted, additive, mix 1,
103
+ * with no current deform, 1–16 ulp on a vertex). Read at every additive
104
+ * mix 1, current deform or not: `current + target − setup` 917,
105
+ * `current − setup + target` and `target + (current − setup)` 951 each (with
106
+ * no current deform both are the target to the bit — they miss over one),
107
+ * the target itself 850. So over a current deform the stated blend stands:
108
+ * the 107 additive mix-1 rigs whose sample animation keys a deform of its
109
+ * own read exact at every sample with it. Before the timeline's first key it
110
+ * writes nothing. Measured on 300 samples of 60 random rigs — weighted and not,
111
+ * additive and not, mix 1, 0.5 and in [−1, 2], with and without a deform in
112
+ * the sample's own animation: 0 misses; reading the non-additive blend from
113
+ * the setup geometry missed 45, reading additive as the key's positions
114
+ * added missed 100.
115
+ *
116
+ * ## Which slot a deform timeline moves
117
+ *
118
+ * The timeline names a skin, a slot and a placeholder: a RECORD. It moves
119
+ * every slot whose shown record at that moment — after the attachment
120
+ * timelines and the sliders — is that record, or a linked mesh that plays
121
+ * that record's timelines (`timelines` true), whichever slot the timeline is
122
+ * filed under. Measured (`CD07`): a deform keyed on mesh `m` of slot `y`
123
+ * moved a linked mesh of `m` in slot `x` and another in slot `z`, and moved
124
+ * `x` while `y` showed another mesh and while `y` showed nothing; a linked
125
+ * mesh with `timelines` false did not move; a deform keyed on an attachment
126
+ * the slot shows only between two attachment keys moved it there alone.
127
+ *
128
+ * ## A switch
129
+ *
130
+ * The sample's own attachment timelines are applied before its deform and
131
+ * sequence timelines, so those are matched against what the slot shows after
132
+ * the sample's switches. A slider applies its animation after the sample —
133
+ * its attachment key, then its deform and sequence keys — and ⚠️ **a slider
134
+ * switching the slot to ANOTHER attachment clears the deform and the frame
135
+ * the sample set**, while one naming the attachment already shown keeps
136
+ * them. Measured on two meshes `m`, `n` and on two series `r` (setup frame 1)
137
+ * and `q` (setup frame 2) (`CD14`): a slider switching `m` to `n` over a
138
+ * sample deforming `m` drew `n` undeformed, and over a sample deforming `n`
139
+ * drew `n` undeformed too (the sample's deform of `n` found `m` shown); a
140
+ * slider naming `m` over a sample deforming `m` kept the deform; a slider
141
+ * switching to `n` and deforming it drew its own deform; the series alike —
142
+ * `q` at its setup frame after the switch, `r` at the sample's frame 3 when
143
+ * the slider named `r`. ⚠️ Under
144
+ * `--skin all` a placeholder several skins fill shows the LAST of them in the
145
+ * Spine file's skin order, which the model does not hold (the slots' ⚠️ in
146
+ * `./index.ts`), so a timeline keyed on such a placeholder is not posed: the
147
+ * block is absent, naming it. Under `--skin <name>` one record resolves —
148
+ * the named skin's, else the default's (`./skins.ts`) — and a timeline
149
+ * moves it exactly when it is keyed under that record's skin (`CN01`).
150
+ *
151
+ * ## Sequence
152
+ *
153
+ * A region's series shows its setup frame (`sequence.setup`, 0 unstated)
154
+ * until a sequence timeline's first key. From a key — `mode` (`hold`
155
+ * unstated), `index` (0 unstated), `delay` (⚠️ unstated, the key before
156
+ * it's, 0 on the first key) — the frame is `index` plus
157
+ * `floor((t − time) / delay + 0.00001)` steps when `delay` is above 0 (no
158
+ * step otherwise, and none in `hold`), then per mode over `count` frames:
159
+ * `hold` the index held at the last frame, `once` the same, `loop` modulo
160
+ * `count`, `pingpong` modulo `2·(count − 1)` folded back, and each
161
+ * `…Reverse` mode the forward mode's frame `f` read as `count − 1 − f`. The
162
+ * key time and `delay` are float32. Measured on 74,041 samples of 380
163
+ * random timelines, 300 of them with delays built to put the quotient within
164
+ * 1e-6 to 4e-5 of a whole step (52,768 samples): the epsilon 0.00001 missed
165
+ * 0; no epsilon 440, 0.0001 1,266, a float32 quotient 306; on the 52,768,
166
+ * 0.000005 148 and 0.00002 248. ⚠️ **A key stating no
167
+ * delay steps by the key before it's**, carried through keys that state
168
+ * none: a hold with delay 0.2, then a hold at index 1, then a loop at index 0
169
+ * stating no delay stepped every 0.2 from the third key; a delay of 0 stated
170
+ * after one of 0.2 held; the index and the mode are not carried (a key
171
+ * stating no index after one at 3 showed frame 0; one stating no mode
172
+ * after a loop held). A slider applies a
173
+ * sequence key at any mix but 0. Only a region's corners read the frame (its
174
+ * rectangle, `sequence.atlas[frame]`); a mesh's vertices read no atlas.
175
+ *
176
+ * ## Purity
177
+ *
178
+ * As the rest of the core: nothing from the Spine runtime package, nothing
179
+ * from `src/transform.ts`, no clock, no randomness, no I/O.
180
+ */
181
+ import type { ModelSlot, ModelVertices } from '../model.ts';
182
+ import { bezierPolyline, keyIndexAt, type CoreAnimationTimelines, type CoreCurve } from './animation.ts';
183
+ import { activeBones, shownRow, type CompiledDocument, type CoreShown, type CoreSkin, type ShownResolution } from './index.ts';
184
+ import { fillingSkins, slotTimelinesApply, type SlotTimelineGate } from './skins.ts';
185
+ import type { CoreGeometry, ShownGeometry } from './vertices.ts';
186
+
187
+ /** The far end the deform curve's recurrence runs to — the header's measurement. */
188
+ export const DEFORM_CURVE_END = 0.99999999;
189
+
190
+ /** A sequence timeline's modes, as the model spells them. */
191
+ export const SEQUENCE_MODES = ['hold', 'once', 'loop', 'pingpong', 'onceReverse', 'loopReverse', 'pingpongReverse'] as const;
192
+ export type SequenceMode = (typeof SEQUENCE_MODES)[number];
193
+
194
+ /** The step a sequence adds to its quotient before flooring it — the header's measurement. */
195
+ export const SEQUENCE_EPSILON = 0.00001;
196
+
197
+ /** One deform key, as read: its time float32 and as stated, its curve, and its run. */
198
+ export interface CoreDeformKey {
199
+ time: number;
200
+ stated: number;
201
+ curve: CoreCurve;
202
+ offset: number;
203
+ /** The run as the document states it; the runtime stores each float32. */
204
+ vertices: number[];
205
+ }
206
+
207
+ /** One sequence key, as read — each field the parser's value where unstated. */
208
+ export interface CoreSequenceKey {
209
+ time: number;
210
+ mode: SequenceMode;
211
+ index: number;
212
+ /** Float32, as the runtime stores it. */
213
+ delay: number;
214
+ }
215
+
216
+ /** One attachment's timelines in one animation: the skin, slot and placeholder it names, and its deform and sequence keys. */
217
+ export interface CoreAttachmentTimeline {
218
+ skin: string;
219
+ slot: string;
220
+ attachment: string;
221
+ deform: CoreDeformKey[] | null;
222
+ sequence: CoreSequenceKey[] | null;
223
+ }
224
+
225
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
226
+ const finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);
227
+
228
+ /** How long the deform array of a vertex array is: a pair per vertex, or per binding when weighted. */
229
+ export function deformLength(vertices: ModelVertices): number {
230
+ return vertices.weighted ? 2 * vertices.bindings.reduce((n, b) => n + b.length, 0) : vertices.xy.length;
231
+ }
232
+
233
+ function keyTimes(list: unknown[], at: string, problems: string[]): void {
234
+ let last = -Infinity;
235
+ list.forEach((k, i) => {
236
+ if (!isRecord(k)) return;
237
+ const t = k.time;
238
+ if (!finite(t) || t < 0) problems.push(`${at}[${i}]: time is ${JSON.stringify(t)}, not a finite time at or after 0`);
239
+ else if (t <= last) problems.push(`${at}[${i}]: time ${t} is not after the key before it — the writer refuses key times that do not strictly increase`);
240
+ if (finite(t)) last = Math.max(last, t);
241
+ });
242
+ }
243
+
244
+ function readDeformKeys(value: unknown, at: string, vertices: ModelVertices, problems: string[]): CoreDeformKey[] {
245
+ if (!Array.isArray(value) || value.length === 0) {
246
+ problems.push(`${at}: keys is not a non-empty list`);
247
+ return [];
248
+ }
249
+ keyTimes(value, at, problems);
250
+ const length = deformLength(vertices);
251
+ const out: CoreDeformKey[] = [];
252
+ value.forEach((k, i) => {
253
+ const kat = `${at}[${i}]`;
254
+ if (!isRecord(k)) {
255
+ problems.push(`${kat} is not an object`);
256
+ return;
257
+ }
258
+ for (const key of Object.keys(k)) if (!['time', 'offset', 'vertices', 'curve'].includes(key)) problems.push(`${kat}: field "${key}" is not one a deform key carries; it carries [time, offset, vertices, curve]`);
259
+ const run = k.vertices;
260
+ let vertexRun: number[] = [];
261
+ if (run !== undefined) {
262
+ if (!Array.isArray(run) || run.length === 0 || !run.every(finite)) problems.push(`${kat}: vertices is not a non-empty list of finite numbers (omit it for the setup geometry)`);
263
+ else vertexRun = run as number[];
264
+ }
265
+ let offset = 0;
266
+ if (k.offset !== undefined) {
267
+ if (run === undefined) problems.push(`${kat}: offset ${JSON.stringify(k.offset)} with no vertices — a key with no run is the setup geometry, and the writer states no start for it`);
268
+ else if (typeof k.offset !== 'number' || !Number.isInteger(k.offset) || k.offset < 0) problems.push(`${kat}: offset is ${JSON.stringify(k.offset)}, not a whole index at or after 0`);
269
+ else offset = k.offset;
270
+ }
271
+ if (offset + vertexRun.length > length) problems.push(`${kat}: the run starts at ${offset} and is ${vertexRun.length} long, past the ${length} numbers of the attachment's deform array`);
272
+ let curve: CoreCurve = 'linear';
273
+ if (k.curve !== undefined) {
274
+ if (i === value.length - 1) problems.push(`${kat}: the last key carries a curve, which eases to no key — the writer refuses it`);
275
+ else if (k.curve === 'stepped') curve = 'stepped';
276
+ else if (Array.isArray(k.curve) && k.curve.length === 4 && k.curve.every(finite)) curve = k.curve as number[];
277
+ else problems.push(`${kat}: curve is ${JSON.stringify(k.curve)}, not "stepped" nor 4 finite numbers (a deform key's curve is one channel)`);
278
+ }
279
+ const time = finite(k.time) ? k.time : 0;
280
+ out.push({ time: Math.fround(time), stated: time, curve, offset, vertices: vertexRun });
281
+ });
282
+ return out;
283
+ }
284
+
285
+ function readSequenceKeys(value: unknown, at: string, count: number, problems: string[]): CoreSequenceKey[] {
286
+ if (!Array.isArray(value) || value.length === 0) {
287
+ problems.push(`${at}: keys is not a non-empty list`);
288
+ return [];
289
+ }
290
+ keyTimes(value, at, problems);
291
+ const out: CoreSequenceKey[] = [];
292
+ value.forEach((k, i) => {
293
+ const kat = `${at}[${i}]`;
294
+ if (!isRecord(k)) {
295
+ problems.push(`${kat} is not an object`);
296
+ return;
297
+ }
298
+ for (const key of Object.keys(k)) if (!['time', 'mode', 'index', 'delay'].includes(key)) problems.push(`${kat}: field "${key}" is not one a sequence key carries; it carries [time, mode, index, delay]`);
299
+ let mode: SequenceMode = 'hold';
300
+ if (k.mode !== undefined) {
301
+ const m = SEQUENCE_MODES.find((x) => x === k.mode);
302
+ if (m === undefined) problems.push(`${kat}: mode is ${JSON.stringify(k.mode)}, none of ${SEQUENCE_MODES.join(', ')}`);
303
+ else mode = m;
304
+ }
305
+ let index = 0;
306
+ if (k.index !== undefined) {
307
+ if (typeof k.index !== 'number' || !Number.isInteger(k.index) || k.index < 0 || k.index >= count) problems.push(`${kat}: index is ${JSON.stringify(k.index)}, not a frame of the series' ${count} — the writer refuses an index past the last frame`);
308
+ else index = k.index;
309
+ }
310
+ // Unstated, the key before it's (the header's ⚠️).
311
+ let delay = out.length === 0 ? 0 : out[out.length - 1].delay;
312
+ if (k.delay !== undefined) {
313
+ // A negative delay steps backwards through a floor this cut never measured; the writer states none.
314
+ if (!finite(k.delay) || k.delay < 0) problems.push(`${kat}: delay is ${JSON.stringify(k.delay)}, not a finite number of seconds at or above 0`);
315
+ else delay = Math.fround(k.delay);
316
+ }
317
+ out.push({ time: Math.fround(finite(k.time) ? k.time : 0), mode, index, delay });
318
+ });
319
+ return out;
320
+ }
321
+
322
+ /**
323
+ * An animation record's `attachments` group, read: each skin, slot and
324
+ * placeholder one of the document's records, a `deform` only on a vertex
325
+ * attachment (mesh, bounding box, clipping, path — the four the writer keys)
326
+ * with every run fitting its deform array, a `sequence` only on a record with
327
+ * a series, and every key checked field by field. Every problem is named.
328
+ */
329
+ export function readAttachmentTimelines(value: unknown, label: string, skins: readonly CoreSkin[], problems: string[]): CoreAttachmentTimeline[] {
330
+ const out: CoreAttachmentTimeline[] = [];
331
+ const where = `${label}.attachments`;
332
+ if (!Array.isArray(value)) {
333
+ problems.push(`${where} is not a list`);
334
+ return out;
335
+ }
336
+ value.forEach((skinEntry, i) => {
337
+ const sat = `${where}[${i}]`;
338
+ if (!isRecord(skinEntry) || typeof skinEntry.name !== 'string') {
339
+ problems.push(`${sat} names no skin`);
340
+ return;
341
+ }
342
+ const skin = skins.find((k) => k.name === skinEntry.name);
343
+ if (skin === undefined) problems.push(`${sat}: "${skinEntry.name}" is not a skin of this document`);
344
+ if (!Array.isArray(skinEntry.slots)) {
345
+ problems.push(`${sat}: slots is not a list`);
346
+ return;
347
+ }
348
+ skinEntry.slots.forEach((slotEntry: unknown, j: number) => {
349
+ const lat = `${sat}.slots[${j}]`;
350
+ if (!isRecord(slotEntry) || typeof slotEntry.name !== 'string') {
351
+ problems.push(`${lat} names no slot`);
352
+ return;
353
+ }
354
+ if (!Array.isArray(slotEntry.attachments)) {
355
+ problems.push(`${lat}: attachments is not a list`);
356
+ return;
357
+ }
358
+ const slot = slotEntry.name;
359
+ slotEntry.attachments.forEach((attEntry: unknown, m: number) => {
360
+ const aat = `${lat}.attachments[${m}]`;
361
+ if (!isRecord(attEntry) || typeof attEntry.name !== 'string') {
362
+ problems.push(`${aat} names no attachment`);
363
+ return;
364
+ }
365
+ for (const key of Object.keys(attEntry)) if (!['name', 'deform', 'sequence'].includes(key)) problems.push(`${aat}: field "${key}" is not one this reader knows; it reads [name, deform, sequence]`);
366
+ const record = skin?.attachments[slot]?.[attEntry.name];
367
+ const label2 = `${aat} "${skinEntry.name}/${slot}/${attEntry.name}"`;
368
+ if (skin !== undefined && record === undefined) problems.push(`${label2}: skin "${skin.name}" files no attachment "${attEntry.name}" under slot "${slot}"`);
369
+ const timeline: CoreAttachmentTimeline = { skin: skinEntry.name as string, slot, attachment: attEntry.name, deform: null, sequence: null };
370
+ if (attEntry.deform !== undefined && record !== undefined) {
371
+ const g = record.geometry;
372
+ if (g === undefined || (g.kind !== 'mesh' && g.kind !== 'clipping' && g.kind !== 'boundingbox' && g.kind !== 'path')) problems.push(`${label2}: a deform keys the vertices of a mesh, bounding box, clipping or path attachment, and this one is a ${record.kind}`);
373
+ else timeline.deform = readDeformKeys(attEntry.deform, `${label2}.deform`, g.vertices, problems);
374
+ }
375
+ if (attEntry.sequence !== undefined && record !== undefined) {
376
+ const count = record.sequenceCount;
377
+ if (count === undefined) problems.push(`${label2}: a sequence timeline steps a series, and this ${record.kind} carries no sequence`);
378
+ else timeline.sequence = readSequenceKeys(attEntry.sequence, `${label2}.sequence`, count, problems);
379
+ }
380
+ if (attEntry.deform === undefined && attEntry.sequence === undefined) problems.push(`${label2}: carries neither a deform nor a sequence timeline`);
381
+ out.push(timeline);
382
+ });
383
+ });
384
+ });
385
+ return out;
386
+ }
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // evaluation
390
+ // ---------------------------------------------------------------------------
391
+
392
+ /** The array a key holds — the header's *what a key holds*: positions unweighted, offsets weighted. */
393
+ export function heldArray(vertices: ModelVertices, key: CoreDeformKey): number[] {
394
+ const out = setupArray(vertices);
395
+ key.vertices.forEach((v, i) => {
396
+ const at = key.offset + i;
397
+ out[at] = vertices.weighted ? Math.fround(v) : Math.fround(out[at] + Math.fround(v));
398
+ });
399
+ return out;
400
+ }
401
+
402
+ /** The deform array of no deform: the setup positions (float32) unweighted, zero offsets weighted. */
403
+ export function setupArray(vertices: ModelVertices): number[] {
404
+ return vertices.weighted ? new Array<number>(deformLength(vertices)).fill(0) : vertices.xy.map((v) => Math.fround(v));
405
+ }
406
+
407
+ /** The fraction of the way from key `a` to key `b` at `t` — the header's two curves. */
408
+ export function deformPercent(a: CoreDeformKey, b: CoreDeformKey, t: number): number {
409
+ if (a.curve === 'stepped') return 0;
410
+ if (a.curve === 'linear') return (t - a.time) / (b.time - a.time);
411
+ const c = a.curve;
412
+ const inner = bezierPolyline(a.stated, 0, c[0], c[1], c[2], c[3], b.stated, DEFORM_CURVE_END);
413
+ const points = [a.time, 0, ...inner, b.time, 1];
414
+ let i = 2;
415
+ while (i < points.length - 2 && points[i] < t) i += 2;
416
+ const [x0, y0, x1, y1] = [points[i - 2], points[i - 1], points[i], points[i + 1]];
417
+ // The two end pieces multiply before they divide — the header's *which form each piece takes*.
418
+ if (i === 2) return (y1 * (t - x0)) / (x1 - x0);
419
+ if (i === points.length - 2) return y0 + ((1 - y0) * (t - x0)) / (x1 - x0);
420
+ return y0 + ((t - x0) / (x1 - x0)) * (y1 - y0);
421
+ }
422
+
423
+ /** What evaluates a deform timeline at `t`: `deformAt` unless a plant passes another. */
424
+ export type DeformEvaluator = (vertices: ModelVertices, keys: readonly CoreDeformKey[], t: number) => number[] | null;
425
+
426
+ /** The deform array a timeline sets at `t`, or `null` before its first key — the header's *at a sample time*. */
427
+ export function deformAt(vertices: ModelVertices, keys: readonly CoreDeformKey[], t: number): number[] | null {
428
+ const i = keyIndexAt(keys, t);
429
+ if (i < 0) return null;
430
+ const a = keys[i];
431
+ const held = heldArray(vertices, a);
432
+ const b = keys[i + 1];
433
+ if (b === undefined || a.curve === 'stepped') return held;
434
+ const next = heldArray(vertices, b);
435
+ const p = deformPercent(a, b, t);
436
+ return held.map((v, k) => v + (next[k] - v) * p);
437
+ }
438
+
439
+ /** What blends a slider's deform over the current one: `blendDeform` unless a plant passes another. */
440
+ export type DeformBlender = (vertices: ModelVertices, current: readonly number[] | null, target: readonly number[], alpha: number, additive: boolean) => number[];
441
+
442
+ /** A slider's deform over the current one — the header's *under a slider*. */
443
+ export function blendDeform(vertices: ModelVertices, current: readonly number[] | null, target: readonly number[], alpha: number, additive: boolean): number[] {
444
+ const setup = setupArray(vertices);
445
+ const from = current ?? setup;
446
+ // #969: non-additive at mix 1 the target itself, not `c + (target − c)·1`, which is last-bit off it (measured, `./hooks.ts`' DM01).
447
+ if (!additive && alpha === 1) return [...target];
448
+ // #993: additive at mix 1 over no current deform the target too, not `setup + (target − setup)·1` (measured, the core suite's CR10).
449
+ if (additive && alpha === 1 && current === null) return [...target];
450
+ return from.map((c, k) => (additive ? c + (target[k] - setup[k]) * alpha : c + (target[k] - c) * alpha));
451
+ }
452
+
453
+ /**
454
+ * The vertex array a deform array draws: unweighted the positions
455
+ * themselves, weighted each binding's float32 coordinate plus its offset —
456
+ * numbers the world-vertex computation reads as they are, not through
457
+ * `Math.fround` again (`worldVertices`' `coords`).
458
+ */
459
+ export function deformedVertices(vertices: ModelVertices, deform: readonly number[]): ModelVertices {
460
+ if (!vertices.weighted) return { weighted: false, xy: [...deform] };
461
+ let k = 0;
462
+ return {
463
+ weighted: true,
464
+ bindings: vertices.bindings.map((influences) => influences.map((b) => {
465
+ const x = Math.fround(b.x) + deform[k];
466
+ const y = Math.fround(b.y) + deform[k + 1];
467
+ k += 2;
468
+ return { ...b, x, y };
469
+ })),
470
+ };
471
+ }
472
+
473
+ /** The frame a sequence timeline shows at `t`, or `null` before its first key — the header's *Sequence*. */
474
+ export function sequenceFrameAt(keys: readonly CoreSequenceKey[], count: number, t: number): number | null {
475
+ const i = keyIndexAt(keys, t);
476
+ if (i < 0) return null;
477
+ const k = keys[i];
478
+ let n = k.index;
479
+ if (k.mode !== 'hold' && k.delay > 0) n += Math.floor((t - k.time) / k.delay + SEQUENCE_EPSILON);
480
+ const reverse = k.mode.endsWith('Reverse');
481
+ const base = reverse ? k.mode.slice(0, -'Reverse'.length) : k.mode;
482
+ let f: number;
483
+ if (base === 'hold' || base === 'once') f = Math.min(n, count - 1);
484
+ else if (base === 'loop') f = n % count;
485
+ else {
486
+ const period = 2 * (count - 1);
487
+ f = period === 0 ? 0 : n % period;
488
+ if (f >= count) f = period - f;
489
+ }
490
+ return reverse ? count - 1 - f : f;
491
+ }
492
+
493
+ // ---------------------------------------------------------------------------
494
+ // the slots' attachments at a pose
495
+ // ---------------------------------------------------------------------------
496
+
497
+ /** What evaluates a sequence timeline at `t`: `sequenceFrameAt` unless a plant passes another. */
498
+ export type SequenceEvaluator = (keys: readonly CoreSequenceKey[], count: number, t: number) => number | null;
499
+
500
+ /** The applications this module reads off a slider (`SliderApplication` in `./constraints_slider.ts`). */
501
+ export interface AttachmentApplication {
502
+ name: string;
503
+ timelines: { attachments: readonly CoreAttachmentTimeline[]; slots: ReadonlyArray<{ name: string; timelines: ReadonlyArray<{ kind: string; keys?: ReadonlyArray<{ time: number; name?: string | null }> }> }> };
504
+ at: number;
505
+ alpha: number;
506
+ additive: boolean;
507
+ }
508
+
509
+ /** The skin, slot and placeholder a slot's shown record plays timelines as — the header's *which slot* — or null when none can key it. */
510
+ export function timelineIdentity(slot: string, shown: CoreShown): string | null {
511
+ const g = shown.record.geometry;
512
+ if (g?.kind === 'linkedmesh') return shown.record.timelines === true ? `${g.skin}/${g.slot}/${g.source}` : null;
513
+ return `${shown.skin}/${slot}/${shown.placeholder}`;
514
+ }
515
+
516
+ const nameOf = (a: CoreAttachmentTimeline): string => `${a.skin}/${a.slot}/${a.attachment}`;
517
+
518
+ /**
519
+ * Every slot's shown geometry at one pose — the setup's (`sample` null) or a
520
+ * sample's — with the deform array and the series' frame its attachment
521
+ * timelines set, stage by stage as the header's *A switch* states: from what
522
+ * the slot shows after the sample's own timelines (`placeholders`; the setup
523
+ * placeholder at setup), the sample's deform and sequence timelines at `t`;
524
+ * then each slider in order — its attachment key (a switch to another record
525
+ * clears the deform and the frame), then its deform and sequence timelines at
526
+ * the time and mix it was applied with. Slots in the model's order, showing a
527
+ * record that carries geometry; a slot whose placeholder skins fill
528
+ * differently is left to the slots' own absence. `why` names what the core
529
+ * does not pose: a timeline moving a record whose placeholder several skins
530
+ * fill (the header's ⚠️).
531
+ */
532
+ export function attachmentStates(
533
+ doc: CompiledDocument,
534
+ resolve: (doc: CompiledDocument, slot: ModelSlot) => ShownResolution,
535
+ placeholders: ReadonlyMap<string, string | null>,
536
+ sample: { timelines: CoreAnimationTimelines; t: number } | null,
537
+ sliders: readonly AttachmentApplication[],
538
+ plant: { deform?: DeformEvaluator; deformBlend?: DeformBlender; sequence?: SequenceEvaluator; slotTimelines?: SlotTimelineGate } = {},
539
+ ): { shown: ShownGeometry[]; why: string[] } {
540
+ const evalDeform = plant.deform ?? deformAt;
541
+ const blendOf = plant.deformBlend ?? blendDeform;
542
+ const evalFrame = plant.sequence ?? sequenceFrameAt;
543
+ const shown: ShownGeometry[] = [];
544
+ const why: string[] = [];
545
+ const active = activeBones(doc);
546
+ const gate = plant.slotTimelines ?? slotTimelinesApply;
547
+ for (const slot of doc.slots) {
548
+ // A slot on a bone the skin leaves inactive is not animated: no deform, no frame, no slider switch (`./skins.ts`).
549
+ const live = gate(doc, slot, active);
550
+ let placeholder = placeholders.get(slot.name) ?? null;
551
+ let deform: number[] | null = null;
552
+ let frame: number | null = null;
553
+ const seen: string[] = [];
554
+ // What the slot shows now, the record its timelines are keyed as, and what a deform or a frame is computed on.
555
+ const state = (): { s: CoreShown; identity: string | null; vertices: ModelVertices | null; count: number | undefined } | null => {
556
+ if (placeholder === null) return null;
557
+ const s = resolve(doc, { ...slot, setup: placeholder });
558
+ if (s === null || 'conflict' in s || s.record.geometry === undefined) return null;
559
+ const g = s.record.geometry;
560
+ const source = g.kind === 'linkedmesh' ? doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source] : s.record;
561
+ const sg = source?.geometry;
562
+ const vertices = sg !== undefined && sg.kind !== 'region' && sg.kind !== 'linkedmesh' ? sg.vertices : null;
563
+ return { s, identity: timelineIdentity(slot.name, s), vertices, count: source?.sequenceCount };
564
+ };
565
+ const apply = (timelines: readonly CoreAttachmentTimeline[], t: number, blend: { alpha: number; additive: boolean } | null): void => {
566
+ const now = state();
567
+ if (now === null || now.identity === null) return;
568
+ for (const a of timelines) {
569
+ if (nameOf(a) !== now.identity) continue;
570
+ if (!seen.includes(now.identity)) seen.push(now.identity);
571
+ if (a.deform !== null && now.vertices !== null) {
572
+ const target = evalDeform(now.vertices, a.deform, t);
573
+ if (target !== null) deform = blend === null ? target : blendOf(now.vertices, deform, target, blend.alpha, blend.additive);
574
+ }
575
+ if (a.sequence !== null && now.count !== undefined) {
576
+ const f = evalFrame(a.sequence, now.count, t);
577
+ if (f !== null) frame = f;
578
+ }
579
+ }
580
+ };
581
+ if (sample !== null && live) apply(sample.timelines.attachments, sample.t, null);
582
+ for (const app of live ? sliders : []) {
583
+ // The slider's attachment key first, as the slot timelines are (`applySliderSlots` in `./constraints_slider.ts`); a switch to another placeholder clears what the timelines set.
584
+ for (const target of app.timelines.slots) {
585
+ if (target.name !== slot.name) continue;
586
+ for (const tl of target.timelines) {
587
+ if (tl.kind !== 'attachment' || tl.keys === undefined) continue;
588
+ const i = keyIndexAt(tl.keys, app.at);
589
+ if (i < 0) continue;
590
+ const next = tl.keys[i].name ?? null;
591
+ if (next !== placeholder) {
592
+ deform = null;
593
+ frame = null;
594
+ }
595
+ placeholder = next;
596
+ }
597
+ }
598
+ apply(app.timelines.attachments, app.at, { alpha: app.alpha, additive: app.additive });
599
+ }
600
+ const now = state();
601
+ if (now === null) continue;
602
+ const entry: ShownGeometry = { slot: slot.name, bone: slot.bone, name: shownRow(now.s).name, placeholder: now.s.placeholder, skin: now.s.skin, geometry: now.s.record.geometry as CoreGeometry };
603
+ shown.push(entry);
604
+ const fillers = fillingSkins(doc, slot.name, now.s.placeholder).map((k) => `"${k}"`);
605
+ if (seen.length > 0 && fillers.length > 1) {
606
+ why.push(`slot "${slot.name}" shows placeholder "${now.s.placeholder}", which skins ${fillers.join(', ')} fill, and ${seen.map((x) => `"${x}"`).join(', ')} key(s) it — which record --skin all shows, and so whether the timeline moves it, is the Spine file's skin order, not the model's`);
607
+ continue;
608
+ }
609
+ if (deform !== null) entry.deform = deform;
610
+ if (frame !== null) entry.frame = frame;
611
+ }
612
+ return { shown, why };
613
+ }