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,233 @@
1
+ /**
2
+ * The model side's supply of `ConstraintFacts` (issue #1025, step 4c of
3
+ * #380): the document's constraints in update order, the constraint
4
+ * timelines of its animations in the order the runtime builds them, and the
5
+ * slots its skins give a path.
6
+ *
7
+ * Every value is the one the runtime holds after its parse, through a
8
+ * function rigc already runs:
9
+ *
10
+ * - **A constraint's fields** are the core's records (`readModel` →
11
+ * `readConstraintRecord`, `readPathRecord`, `readPhysicsRecord`,
12
+ * `readSliderRecord`), which read a field the record leaves out at the
13
+ * parser's value; a physics record's `massInverse` and `step` are the
14
+ * reader's `1 / mass` and `1 / fps`. A transform constraint reads a mix
15
+ * exactly where one of its `to` entries names that property — the core's
16
+ * `properties`, by `TRANSFORM_MIXES`.
17
+ * - **A slider's animation** is the core's reading of it: its duration is
18
+ * `CoreAnimationTimelines.duration` (the last key time, float32 — the
19
+ * runtime's), and the timelines it carries are the bone and slot timelines
20
+ * plus the count of every later group the reader counts.
21
+ * - **The timelines**: the animations in the file's order
22
+ * (`fileAnimationOrder`, issue #1034: a `rigc-compiled/3` document's
23
+ * `editorOrder`, else the emitter's own rule over its names); in each the
24
+ * groups in the runtime's order (`../facts/constraints.ts`' ⭐), each
25
+ * group's entries in the order the file keys them — the document's, an
26
+ * integer-like constraint name first (`keyedOrder`) — and each entry's
27
+ * timelines in the document's order, which the emitter writes unchanged. A key's time and value are the core's (float32), and a
28
+ * Bézier segment's samples `bezierPolyline` over the numbers the document
29
+ * states — the runtime's curve, as the core reproduces it.
30
+ * - **The reach of a physics timeline naming none** is asked of the core's
31
+ * own `posedPhysics` rather than restated: the timeline is posed once with
32
+ * a key the setup cannot hold (NaN), and the constraints it reached are the
33
+ * ones whose pose field took it; a `reset` timeline's reach is the set
34
+ * `posedPhysics` returns.
35
+ *
36
+ * ⚠️ One value is not the document's: `runtimeClass`, the runtime's class
37
+ * name for a kind, which A42's sentence names `update` on. The document has no
38
+ * field for it and rigc has no function returning it, so it is the table
39
+ * below; the selftest compares it with the class spine-core loaded on every
40
+ * call that reaches a constraint (`VF10`).
41
+ *
42
+ * Links nothing from the runtime.
43
+ */
44
+ import { fileAnimationOrder, keyedOrder, type OrderedDocument } from '../../compile.ts';
45
+ import { bezierPolyline, type CoreAnimationTimelines, type CoreKey } from '../../core/animation.ts';
46
+ import { TRANSFORM_MIXES, TRANSFORM_PROPERTIES, type CoreConstraintRecord } from '../../core/constraints.ts';
47
+ import { EVERY_GLOBAL_PHYSICS, posedPhysics, type CorePhysicsRecord, type PhysicsTimelineKind } from '../../core/constraints_physics.ts';
48
+ import type { CompiledDocument } from '../../core/index.ts';
49
+ import { physicsRuleFor } from '../../timelines.ts';
50
+ import type { ConstraintEntry, ConstraintFacts, ConstraintKind, ConstraintTimeline } from '../facts/constraints.ts';
51
+ import { isObj, type Json } from '../values.ts';
52
+ import { fileOrderedEntries } from './skin_entries.ts';
53
+ import type { ReadDocument } from './parse.ts';
54
+
55
+ /** The runtime's class for each kind — the one value here that is not the document's (the header's ⚠️). */
56
+ export const RUNTIME_CONSTRAINT_CLASS: Readonly<Record<ConstraintKind, string>> = {
57
+ ik: 'IkConstraint',
58
+ transform: 'TransformConstraint',
59
+ path: 'PathConstraint',
60
+ physics: 'PhysicsConstraint',
61
+ slider: 'Slider',
62
+ };
63
+
64
+ /** How many timelines the runtime builds for an animation the core read: every bone and slot timeline, and every later group's count. */
65
+ export function timelineCount(timelines: CoreAnimationTimelines): number {
66
+ let n = 0;
67
+ for (const target of [...timelines.bones, ...timelines.slots]) n += target.timelines.length;
68
+ for (const [, count] of timelines.later) n += count;
69
+ return n;
70
+ }
71
+
72
+ /** Every value one channel of `keys` poses: each key's own, then the samples of the Bézier segment it opens. */
73
+ export function channelValuesOf(keys: readonly CoreKey[], channel: number): number[] {
74
+ const out: number[] = [];
75
+ keys.forEach((a, i) => {
76
+ out.push(a.values[channel]);
77
+ const b = keys[i + 1];
78
+ if (b === undefined || !Array.isArray(a.curve)) return;
79
+ const c = a.curve.slice(channel * 4, channel * 4 + 4);
80
+ const inner = bezierPolyline(a.stated.time, a.stated.values[channel], c[0], c[1], c[2], c[3], b.stated.time, b.stated.values[channel]);
81
+ for (let p = 1; p < inner.length; p += 2) out.push(inner[p]);
82
+ });
83
+ return out;
84
+ }
85
+
86
+ /** The physics constraints a timeline naming none reaches, asked of `posedPhysics` (the header). */
87
+ export function unnamedReach(records: readonly CorePhysicsRecord[], kind: PhysicsTimelineKind): Set<string> {
88
+ if (kind === 'reset') {
89
+ const keys: CoreKey[] = [{ time: 0, values: [], stated: { time: 0, values: [] }, curve: 'stepped' }];
90
+ return posedPhysics(records, [{ name: EVERY_GLOBAL_PHYSICS, kind, keys }], 0, -1, () => true).reset;
91
+ }
92
+ const keys: CoreKey[] = [{ time: 0, values: [Number.NaN], stated: { time: 0, values: [Number.NaN] }, curve: 'linear' }];
93
+ const posed = posedPhysics(records, [{ name: EVERY_GLOBAL_PHYSICS, kind, keys }], 0, -1, () => true).records;
94
+ const field = kind === 'mass' ? 'massInverse' : kind;
95
+ return new Set([...posed.values()].filter((r) => Number.isNaN(r[field])).map((r) => r.name));
96
+ }
97
+
98
+ function entryOf(doc: CompiledDocument, kind: ConstraintKind, name: string, record: CoreConstraintRecord): ConstraintEntry {
99
+ const base = { kind, name, runtimeClass: RUNTIME_CONSTRAINT_CLASS[kind] };
100
+ switch (record.kind) {
101
+ case 'physics':
102
+ return {
103
+ ...base,
104
+ physics: {
105
+ bone: record.bone,
106
+ // The length the core's step is handed (`bone(state, c.bone).length ?? 0` in `src/core/constraints.ts`): the parser's 0 where the document leaves it out.
107
+ boneLength: doc.bones.find((b) => b.name === record.bone)?.length ?? 0,
108
+ components: { x: record.x, y: record.y, rotate: record.rotate, scaleX: record.scaleX, shearX: record.shearX },
109
+ setup: { mix: record.mix, massInverse: record.massInverse, strength: record.strength, damping: record.damping },
110
+ step: record.step,
111
+ },
112
+ };
113
+ case 'path':
114
+ return { ...base, path: { bones: record.bones, slot: record.slot, setup: { mixRotate: record.mixRotate, mixX: record.mixX, mixY: record.mixY } } };
115
+ case 'slider': {
116
+ const anim = doc.animations.find((a) => a.name === record.animation);
117
+ return {
118
+ ...base,
119
+ slider: {
120
+ animation: anim === undefined ? null : { name: anim.name, timelines: timelineCount(anim.timelines), duration: anim.timelines.duration },
121
+ bone: record.bone,
122
+ loop: record.loop,
123
+ scale: record.scale,
124
+ mix: record.mix,
125
+ },
126
+ };
127
+ }
128
+ case 'ik': {
129
+ // The second bone's ancestors, parent first (issue #1205). The core's reader refuses a pair that is not a parent and its child, so on this side the first entry is always the first bone.
130
+ const secondAncestors: string[] = [];
131
+ if (record.bones.length === 2) {
132
+ for (let at = doc.bones.find((b) => b.name === record.bones[1])?.parent; at !== undefined; at = doc.bones.find((b) => b.name === at)?.parent) secondAncestors.push(at);
133
+ }
134
+ return { ...base, ik: { bones: record.bones, target: record.target, mix: record.mix, secondAncestors } };
135
+ }
136
+ case 'transform':
137
+ return {
138
+ ...base,
139
+ transform: {
140
+ bones: record.bones,
141
+ mixes: TRANSFORM_PROPERTIES.map((p) => (record.properties.some((from) => from.to.some((to) => to.property === p)) ? { field: TRANSFORM_MIXES[p], setup: record.mixes[p] } : null)),
142
+ },
143
+ };
144
+ }
145
+ }
146
+
147
+ /** One constraint timeline as the model side walks it: where it is, what it keys, and the core's keys for it. */
148
+ export interface ModelConstraintTimelineSource {
149
+ animation: string;
150
+ kind: ConstraintKind;
151
+ word: string;
152
+ /** The constraint it names, or `EVERY_GLOBAL_PHYSICS`. */
153
+ name: string;
154
+ keys: readonly CoreKey[];
155
+ }
156
+
157
+ /** How the animations are put in the file's order: `fileAnimationOrder`. A parameter only so the selftest can show what another order would print (`VF11`). */
158
+ export type AnimationFileOrder = (doc: OrderedDocument) => readonly string[];
159
+
160
+ /**
161
+ * `entries` in the order the Spine file keys them by name (issue #1034): each
162
+ * constraint group of an animation is an object the emitter fills in the
163
+ * model's order, so the names are grouped as they first appear and put
164
+ * through `keyedOrder` — an integer-like name first — and every entry of one
165
+ * name keeps its place among them.
166
+ */
167
+ function inKeyedOrder<T extends { name: string }>(entries: readonly T[]): T[] {
168
+ const names = entries.map((e) => e.name);
169
+ return keyedOrder(names.filter((n, i) => names.indexOf(n) === i)).flatMap((name) => entries.filter((e) => e.name === name));
170
+ }
171
+
172
+ /** Every constraint timeline of the document, in the order the runtime builds them (the header) — the walk `modelConstraintFacts` reads, and the selftest's measurement of each derivation. */
173
+ export function modelConstraintTimelineSources(read: ReadDocument, animations: AnimationFileOrder = fileAnimationOrder): ModelConstraintTimelineSource[] {
174
+ const { doc } = read;
175
+ const jsonAnimations = Array.isArray(read.json.animations) ? read.json.animations.filter(isObj) : [];
176
+ const out: ModelConstraintTimelineSource[] = [];
177
+ for (const name of animations(doc)) {
178
+ const anim = doc.animations.find((a) => a.name === name);
179
+ if (anim === undefined) continue;
180
+ const c = anim.constraints;
181
+ for (const tl of inKeyedOrder(c.ik)) out.push({ animation: name, kind: 'ik', word: 'ik', name: tl.name, keys: tl.keys });
182
+ for (const tl of inKeyedOrder(c.transform)) out.push({ animation: name, kind: 'transform', word: 'transform', name: tl.name, keys: tl.keys });
183
+ for (const entry of inKeyedOrder(c.path)) {
184
+ for (const word of Object.keys(entry)) {
185
+ if (word === 'position' || word === 'spacing' || word === 'mix') out.push({ animation: name, kind: 'path', word, name: entry.name, keys: entry[word] ?? [] });
186
+ }
187
+ }
188
+ for (const tl of inKeyedOrder(c.physicsKeyed ?? [])) out.push({ animation: name, kind: 'physics', word: tl.kind, name: tl.name, keys: tl.keys });
189
+ // A slider entry's timelines in the order the document lists them, which `readSliderTimelines` keeps by name only.
190
+ const jsonSliders = ((): Json[] => {
191
+ const json = jsonAnimations.find((a) => a.name === name)?.constraints;
192
+ return isObj(json) && Array.isArray(json.slider) ? json.slider.filter(isObj) : [];
193
+ })();
194
+ // Paired with the document's own entry by index before the walk is put in the file's order.
195
+ const sliders = c.slider.map((entry, i) => ({ name: entry.name, entry, listed: Array.isArray(jsonSliders[i]?.timelines) ? (jsonSliders[i].timelines as unknown[]).filter(isObj).map((t) => t.name) : [] }));
196
+ for (const { entry, listed } of inKeyedOrder(sliders)) {
197
+ for (const word of listed) {
198
+ if (word === 'time' || word === 'mix') out.push({ animation: name, kind: 'slider', word, name: entry.name, keys: entry[word] ?? [] });
199
+ }
200
+ }
201
+ }
202
+ return out;
203
+ }
204
+
205
+ export function modelConstraintFacts(read: ReadDocument, animations: AnimationFileOrder = fileAnimationOrder): ConstraintFacts {
206
+ const { doc } = read;
207
+ const constraints: ConstraintEntry[] = doc.constraints.map((c) => {
208
+ if (c.record === undefined) throw new Error(`internal: ${c.kind} constraint "${c.name}" is one the reader returned without its record`);
209
+ return entryOf(doc, c.kind, c.name, c.record);
210
+ });
211
+ const indexOf = (kind: ConstraintKind, name: string): number => doc.constraints.findIndex((c) => c.kind === kind && c.name === name);
212
+ const physicsRecords = doc.constraints.flatMap((c) => (c.record?.kind === 'physics' ? [c.record] : []));
213
+ const timelines: ConstraintTimeline[] = modelConstraintTimelineSources(read, animations).map(({ animation, kind, word, name, keys }) => {
214
+ const constraint = name === EVERY_GLOBAL_PHYSICS && kind === 'physics' ? -1 : indexOf(kind, name);
215
+ const reach = constraint >= 0 ? [constraint] : [...unnamedReach(physicsRecords, word as PhysicsTimelineKind)].map((n) => indexOf('physics', n)).sort((a, b) => a - b);
216
+ const rule = kind === 'physics' ? physicsRuleFor(word) : undefined;
217
+ return {
218
+ animation,
219
+ kind,
220
+ word,
221
+ constraint,
222
+ reach,
223
+ frames: word === 'reset' ? [] : keys.map((k) => ({ time: k.time, value: k.values[0] })),
224
+ channelValues: (channel: number) => (word === 'reset' ? [] : channelValuesOf(keys, channel)),
225
+ ...(rule === undefined ? {} : { posed: (value: number) => rule.toPose(value) }),
226
+ };
227
+ });
228
+ const pathSlots: string[] = [];
229
+ for (const entry of fileOrderedEntries(read)) {
230
+ if (entry.record.kind === 'path' && !pathSlots.includes(entry.slot)) pathSlots.push(entry.slot);
231
+ }
232
+ return { animations: doc.animations.length, constraints, timelines, pathSlots };
233
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * What the build declared, read off the document (issue #1054): the rig info
3
+ * the archetype and declaration rules read, and each animation's declared
4
+ * duration, which A09 holds the keys against.
5
+ *
6
+ * ⭐ **The document is the one source, on every spec it is read at.** A
7
+ * `rigc-compiled/3`, `/2` and `/1` document all carry the `rig` section
8
+ * (`CompileResult.rig` as `modelDocument` writes it) and every animation's
9
+ * `duration` (the motion spec's, which `readModel` reads as
10
+ * `CoreAnimationTimelines.declared` and refuses when it is not a finite
11
+ * number). Until this module the model side took both from its caller, as
12
+ * `validate()` does, and its verdict moved with what the caller handed it:
13
+ * measured over the 19 recipes with the document's own values left out, A09
14
+ * read SKIP on 38 of 38 cells, ten rules read their bare-directory SKIP, and
15
+ * A20 read FAIL on 7 cells where it reads PASS — without the rig every mesh is
16
+ * judged a generated ring (`meshKindOf`), so an authored mesh fails the
17
+ * generator policy. A value the document states is read from the document,
18
+ * and the same value given beside it is refused by name
19
+ * (`refuseDeclaredBeside`), as issue #1026 made the stage and `pma`
20
+ * (`./given.ts`).
21
+ *
22
+ * 🔒 **The rig section is read field by field, never cast.** A body reads a
23
+ * declaration only through `modelRigInfo`, which refuses by its path a field
24
+ * that is missing, of the wrong type or not one the writer writes — the
25
+ * model side's parse runs it (`./parse.ts`, `A00_MODEL_READ`), so a document
26
+ * whose declarations are malformed is refused where the reader refuses, and
27
+ * never judged against a guess.
28
+ *
29
+ * Links nothing from the runtime.
30
+ */
31
+ import type { MeshKind } from '../../mesh.ts';
32
+ import type { RigInfo } from '../../types.ts';
33
+ import { isObj } from '../values.ts';
34
+ import type { ReadDocument } from './parse.ts';
35
+
36
+ /** A rig section the model side cannot read as rig info: every problem, each by its path, in one sentence. */
37
+ export class DeclaredInputError extends Error {}
38
+
39
+ /** The mesh kinds a rig's `meshKinds` may name: the generators' and `authored`. */
40
+ const MESH_KINDS = ['ring', 'ribbon', 'contour', 'grid', 'segments', 'authored'] as const satisfies ReadonlyArray<MeshKind | 'authored'>;
41
+ // Every kind the type names is in the list, or this line does not type-check.
42
+ const MESH_KINDS_COMPLETE: Exclude<MeshKind | 'authored', (typeof MESH_KINDS)[number]> extends never ? true : false = true;
43
+ void MESH_KINDS_COMPLETE;
44
+
45
+ type Check = (value: unknown, at: string, problems: string[]) => void;
46
+ const say = (problems: string[], at: string, value: unknown, what: string): void => void problems.push(`${at} is ${JSON.stringify(value) ?? 'absent'}, not ${what}`);
47
+ const str: Check = (v, at, p) => (typeof v === 'string' ? undefined : say(p, at, v, 'a string'));
48
+ const num: Check = (v, at, p) => (typeof v === 'number' && Number.isFinite(v) ? undefined : say(p, at, v, 'a finite number'));
49
+ const nullable = (inner: Check): Check => (v, at, p) => (v === null ? undefined : inner(v, at, p));
50
+ const list = (inner: Check, length?: number): Check => (v, at, p) => {
51
+ if (!Array.isArray(v) || (length !== undefined && v.length !== length)) return say(p, at, v, length === undefined ? 'a list' : `a list of ${length}`);
52
+ v.forEach((x, i) => inner(x, `${at}[${i}]`, p));
53
+ };
54
+ const record = (inner: Check): Check => (v, at, p) => {
55
+ if (!isObj(v)) return say(p, at, v, 'an object');
56
+ for (const [k, x] of Object.entries(v)) inner(x, `${at}.${k}`, p);
57
+ };
58
+ const oneOf = (words: readonly string[]): Check => (v, at, p) => (typeof v === 'string' && words.includes(v) ? undefined : say(p, at, v, `one of ${words.join(', ')}`));
59
+ const shape = (fields: Readonly<Record<string, Check>>): Check => (v, at, p) => {
60
+ if (!isObj(v)) return say(p, at, v, 'an object');
61
+ for (const [k, check] of Object.entries(fields)) check(v[k], `${at}.${k}`, p);
62
+ for (const k of Object.keys(v)) if (!(k in fields)) p.push(`${at}.${k} is not a field the rig section has`);
63
+ };
64
+
65
+ /** The rig section's fields, as `RigInfo` declares them and the writer writes every one — each required, none defaulted. */
66
+ const RIG_FIELDS: Readonly<Record<keyof RigInfo, Check>> = {
67
+ archetype: str,
68
+ axisBone: nullable(str),
69
+ axisSubtree: list(str),
70
+ detached: list(list(str, 2)),
71
+ slotOrder: nullable(list(str)),
72
+ meshKinds: record(oneOf(MESH_KINDS)),
73
+ meshDeclaredBones: record(list(str)),
74
+ meshSoftBones: record(str),
75
+ deformMayFold: list(str),
76
+ consumerDrivenMix: list(shape({ type: oneOf(['ik', 'transform']), constraint: str, why: str })),
77
+ idleDrivesMeshes: nullable(str),
78
+ basePlates: list(str),
79
+ meshSlotBudget: nullable(num),
80
+ meshTriangleBudget: nullable(num),
81
+ contactDepth: nullable(num),
82
+ capContainmentCeiling: nullable(num),
83
+ massBone: nullable(str),
84
+ inwardUnit: nullable(list(num, 2)),
85
+ };
86
+
87
+ /** Every problem with the document's `rig` section, each by its path — empty when it reads as rig info. */
88
+ export function rigSectionProblems(json: Readonly<Record<string, unknown>>): string[] {
89
+ const problems: string[] = [];
90
+ shape(RIG_FIELDS)(json.rig, 'rig', problems);
91
+ return problems;
92
+ }
93
+
94
+ /** The rig info the document declares — its `rig` section, read field by field; a section that does not read is refused by its paths. */
95
+ export function modelRigInfo(read: ReadDocument): RigInfo {
96
+ const problems = rigSectionProblems(read.json);
97
+ if (problems.length > 0) throw new DeclaredInputError(`the document's rig section does not read as rig info: ${problems.length} problem(s): ${problems.join('; ')}`);
98
+ return read.json.rig as RigInfo;
99
+ }
100
+
101
+ /**
102
+ * Each animation's declared duration, as the document states it and the core
103
+ * reads it (`CoreAnimationTimelines.declared`), keyed in the document's order —
104
+ * the motion spec's, the order `CompileResult.declaredDurations` is filled in.
105
+ */
106
+ export function modelDeclaredDurations(read: ReadDocument): Record<string, number> {
107
+ const out: Record<string, number> = {};
108
+ for (const anim of read.doc.animations) out[anim.name] = anim.timelines.declared;
109
+ return out;
110
+ }
111
+
112
+ /** What a caller may not give beside a document, because every document states it. */
113
+ export type DeclaredValue = 'rig' | 'declaredDurations';
114
+
115
+ /** The sentence a value given beside the document is refused with. */
116
+ export function declaredBesideRefusal(spec: string, what: DeclaredValue): string {
117
+ const given = what === 'rig' ? 'the rig info' : 'the declared durations';
118
+ const stated = what === 'rig' ? 'states it itself in its rig section' : "states each animation's duration itself";
119
+ return `the caller gave ${given} beside a ${spec} document, which ${stated} (issue #1054); a second source for one fact is refused rather than one of them silently read`;
120
+ }
121
+
122
+ /** A caller's value beside a document that states it, refused by name. */
123
+ export function refuseDeclaredBeside(read: ReadDocument, input: { rig?: unknown; declaredDurations?: unknown }, what: DeclaredValue): void {
124
+ if (input[what] !== undefined) throw new Error(declaredBesideRefusal(read.doc.spec, what));
125
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The model side's supply of `DeformSurveyFacts` (issue #1025, cut 4c-3 of
3
+ * step 4c of #380): the survey off the model document alone —
4
+ * `surveyOfModel` (`src/deformsurvey.ts`), the document's structure read by
5
+ * `modelStructure` (`src/deformstructure.ts`) and every pose taken by the
6
+ * core's poser. It is the survey `explain` takes of a build that carries a
7
+ * model document (`surveyOfBuild`'s model path) and the one
8
+ * `tools/survey_hashes.ts` holds byte for byte to spine-core's on every corpus
9
+ * row; called, not restated.
10
+ *
11
+ * A pose the core refuses — a construct it leaves out, a value it computes as
12
+ * no number — is a `CoreInputError` naming why, and it reaches A39 as the
13
+ * refusal: the line the harness prints for a body that threw, the core's
14
+ * sentence in it. A survey that is not the runtime's is never handed on.
15
+ *
16
+ * Links nothing from the runtime.
17
+ */
18
+ import { surveyOfModel } from '../../deformsurvey.ts';
19
+ import type { DeformSurveyFacts } from '../facts/deform_survey.ts';
20
+ import type { ReadDocument } from './parse.ts';
21
+
22
+ export function modelDeformSurvey(read: ReadDocument): DeformSurveyFacts {
23
+ return { survey: (exempt) => surveyOfModel(read.doc, exempt) };
24
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The model side's supply of `EventKeyFacts` (issue #1025, cut 4c-4 of #380):
3
+ * every event key of the document, with the declaration it fires.
4
+ *
5
+ * - **The order is the file's.** The animations by `editorAnimationOrder`
6
+ * (`src/compile.ts`, the function the emitter is handed), and an animation's
7
+ * keys in the document's order, which is the order `emitAnimation` writes
8
+ * the model's list in; an animation with no event key writes no `events`
9
+ * group, so it carries no event timeline here either.
10
+ * - **The moved clause's inputs.** Which of `volume` and `balance` the key
11
+ * states — the document's key carries the fields the file's does, and the
12
+ * parser-default pass leaves an event key's audio fields alone (its row is
13
+ * `time` only) — and whether the declaration names an audio path, the
14
+ * `audio` the document's `events` section states for that name.
15
+ * - **No `kept` finding, ever.** The four clauses that stay with the round
16
+ * trip are states `readModel` refuses (`readEventKeys`), so on a document
17
+ * this side has read none of them can hold.
18
+ *
19
+ * Links nothing from the runtime.
20
+ */
21
+ import { editorAnimationOrder } from '../../compile.ts';
22
+ import type { EventKeyEntry, EventKeyFacts } from '../facts/event_keys.ts';
23
+ import { isObj, type Json } from '../values.ts';
24
+ import type { ReadDocument } from './parse.ts';
25
+
26
+ const list = (v: unknown): Json[] => (Array.isArray(v) ? v.filter(isObj) : []);
27
+
28
+ export function modelEventKeys(read: ReadDocument): EventKeyFacts {
29
+ const audio = new Map<string, boolean>();
30
+ for (const def of list(read.json.events)) if (typeof def.name === 'string') audio.set(def.name, typeof def.audio === 'string');
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 keys: EventKeyEntry[] = [];
34
+ let timelines = 0;
35
+ for (const animation of editorAnimationOrder([...byName.keys()])) {
36
+ const events = list((byName.get(animation) as Json).events);
37
+ if (events.length === 0) continue;
38
+ timelines++;
39
+ events.forEach((key, index) => {
40
+ const name = String(key.name);
41
+ keys.push({ animation, index, kept: [], stopped: false, name, sets: { volume: key.volume !== undefined, balance: key.balance !== undefined }, audio: audio.get(name) ?? false });
42
+ });
43
+ }
44
+ return { parsed: true, animations: true, timelines, keys };
45
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What the model side is given beside the document (issue #1025, step 4c of
3
+ * #380): the two values the census found the document does not hold, which a
4
+ * moved assertion reads — the stage (A14, A19) and each atlas page's `pma`
5
+ * (A06). Both entered the document with issue #1026 (`rigc-compiled/3`):
6
+ * a `/3` document is read for them and this input is for a `/2` or `/1`
7
+ * document alone — given beside a `/3` document it is refused by name
8
+ * (`refuseGivenBeside`) rather than ignored, because a value the caller states
9
+ * beside the document's own is a second source for one fact, and a silent
10
+ * winner between two sources is how a verdict comes to pass for the wrong
11
+ * reason.
12
+ *
13
+ * ⛔ **The model side never reads them off a Spine file itself.** A verdict that
14
+ * read `skeleton.json` would be a second reading of the encoding, which is the
15
+ * round trip's subject; so whoever calls the model side states them, from what
16
+ * it holds. The selftest's supplier check and `tools/verdict_gate.ts` hold the
17
+ * build they gate, and read both off it with one function
18
+ * (`modelGivenOfBuild` in that tool): the stage the skeleton header the build
19
+ * wrote states, and each page's `pma` as rigc's own atlas reader
20
+ * (`parseAtlasText`) reads the atlas the build wrote.
21
+ *
22
+ * Links nothing from the runtime.
23
+ */
24
+
25
+ import type { ReadDocument } from './parse.ts';
26
+
27
+ /**
28
+ * A caller's `given` beside a document that states both values itself
29
+ * (`rigc-compiled/3`, issue #1026), refused by name: the caller is to give
30
+ * them for a `/2` or `/1` document only (`modelGivenOf` in
31
+ * `tools/verdict_gate.ts` gives them exactly then).
32
+ */
33
+ export function refuseGivenBeside(read: ReadDocument, given: ModelGiven | undefined): void {
34
+ if (given !== undefined && read.doc.stated !== null) {
35
+ throw new Error(`the caller gave the stage and the pages' pma beside a ${read.doc.spec} document, which states both itself (issue #1026); a second source for one fact is refused rather than one of them silently read`);
36
+ }
37
+ }
38
+
39
+ /** The stage and the pages' `pma`, as the caller states them. */
40
+ export interface ModelGiven {
41
+ /** The stage the skeleton states — each extent where it states one — or `null` where it states none. */
42
+ readonly stage: { readonly width?: number; readonly height?: number } | null;
43
+ /** Each page's `pma`, in the atlas's page order: one value per page of the document's `pages`. */
44
+ readonly pma: readonly boolean[];
45
+ }