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,60 @@
1
+ ---
2
+ name: rigc-face
3
+ description: Author a face on plain Spine data with rigc — a blink, a gaze shift, a breathing portrait and a head turn a few degrees off axis, built from deform timelines and per-part parallax. Use when the request is a talking or living portrait, a standing character, an expression or a head turn, such as "rig this face", "make the portrait blink and look around" or "turn the head". Not for Live2D file conversion, cutting a face illustration into parts, or VTuber-style real-time face tracking.
4
+ license: MIT
5
+ compatibility: Requires Bun 1.2 or later and the npm package spine-rigc.
6
+ ---
7
+
8
+ # Face — a turn, a gaze and a blink
9
+
10
+ Load this when the request is a **head rather than a body**: a drawn face that
11
+ breathes, blinks, moves its eyes and turns a few degrees off axis. Every rule below
12
+ is owned by [FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md); this file says when to open it and what
13
+ it will not do for you.
14
+
15
+ ## Non-negotiables
16
+
17
+ - **Validation is never bypassed.** `build` writes nothing on a red gate, and there
18
+ is no flag that changes that — AUTHORING §0.
19
+ - **The compiler never invents a value.** A `deform` key states every vertex it
20
+ moves; the geometry a turn implies is evaluated by you and written into the spec,
21
+ never inferred by the compiler — AUTHORING §4 and FACE §1.
22
+ - **The validator's messages are the instructions.** Read each named failure as the
23
+ pointer to the file that has to change — AUTHORING §5.
24
+
25
+ ## What this guide will not do
26
+
27
+ A `deform` key's own geometry is measured now.
28
+ `A39_DEFORM_KEEPS_TRIANGLE_WINDING` refuses a key that folds a mesh inside out —
29
+ per key, per interpolated span between two keys, per frame and per skin — and
30
+ names the reversed triangles with their signed areas. `invariants.deformMayFold`
31
+ in the rig spec is the one declared exemption, so reach for it when the slot
32
+ folds on purpose and never to quiet a refusal you have not read. It is an
33
+ **archetype** rule: a `--profile spine` build prints `PROF` for it and says
34
+ nothing about the fold, so author under `--profile spine-html`.
35
+
36
+ What `A39` still cannot say is whether the projection was the right one —
37
+ nothing measures whether 12° was the angle the shot wanted. FACE §9.2 is that
38
+ demonstration, three builds with one of them refused, and §9.3 is the
39
+ differential audit and the three limits it does not lift.
40
+
41
+ ## Read, in this order
42
+
43
+ 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the `deform` timeline field by field
44
+ and what rigc refuses in it (§4), the failure map (§5–§6), the editor's
45
+ conventions (§10).
46
+ 2. [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) — timing, easing and the offset table (§3),
47
+ candidates and the ballot (§4–§5). A blink and a gaze are ordinary motion work.
48
+ 3. [FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md) — the face's own geometry: the closed form every
49
+ number in a turn comes from (§1), the hierarchy underneath (§3), what
50
+ foreshortens (§5), and the deform audit gap (§9).
51
+ 4. Then [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md) for the hierarchy as a general rule
52
+ rather than this closed form, and [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) if the head
53
+ arrived as a compiled skeleton.
54
+
55
+ Every guide linked here is in the installed package at `node_modules/spine-rigc/docs/`,
56
+ which is the copy that matches the rigc you run; the links go to the repository's
57
+ `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
58
+
59
+ The install line and the build → validate → render → check loop are in the `rigc`
60
+ skill.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: rigc-ingest
3
+ description: Work with a Spine skeleton.json somebody else authored — exported from the Spine editor or another tool — using rigc. Read and validate it, understand a complaint rigc raised about it, decompile it into rigc specs with `rigc ingest`, normalise, re-pivot or rename it, and extend it with an animation it does not have. Use when the input is an existing skeleton.json with its .atlas and page images rather than loose part PNGs. Not for Live2D file conversion or runtime tracking.
4
+ license: MIT
5
+ compatibility: Requires Bun 1.2 or later and the npm package spine-rigc.
6
+ ---
7
+
8
+ # Ingest — a skeleton you did not author
9
+
10
+ Load this when what you were handed is **already a skeleton**: a `skeleton.json`
11
+ with its `.atlas` and page images, and a request to understand it, answer a
12
+ complaint about it, re-express it, or extend it. Every rule below is owned by
13
+ [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md); this file says when to open it and what it will
14
+ not do for you.
15
+
16
+ ## Non-negotiables
17
+
18
+ - **Validation is never bypassed.** `validate` reads a foreign file as it is, and
19
+ `build` writes nothing on a red gate — AUTHORING §0.
20
+ - **The compiler never invents a value, and neither does the decompiler.** The
21
+ specs state every bone, slot and key; what the export left implicit is written
22
+ down before it compiles. `rigc ingest` obeys the same rule from the other side —
23
+ what it cannot read out of the skeleton is a named **finding**, never a guess —
24
+ INGEST §2.0 and §2.
25
+ - **The validator's messages are the instructions.** A red line on an export is a
26
+ fact about the file, and sometimes about the rule — INGEST §3 says which, and
27
+ AUTHORING §5 names the file to change.
28
+
29
+ ## Start here: `rigc ingest`
30
+
31
+ ```bash
32
+ rigc ingest hero.json --out specs/
33
+ rigc build --rig specs/rig.json --motion specs/motion.json --images parts/ --out build/
34
+ ```
35
+
36
+ It reads the skeleton — **only** the skeleton — and writes the rig spec and motion
37
+ spec that rebuild it: **byte for byte for a skeleton rigc emitted**, and for an editor
38
+ export the weaker claim `diff` measures, with three kinds of benign difference left —
39
+ INGEST §2.3. Two values it will not guess: the
40
+ **stage** (a skeleton that declares none is carried as declaring none, and `--stage`
41
+ adds a box to one — an editor export *may* be such a file, though every one in the
42
+ example corpus carries a box, and passing the flag at a file that declares a box is
43
+ refused rather than ignored) and each animation's **duration** (the largest key time,
44
+ recorded as a finding).
45
+ Read `findings.json`: a `BLOCK` line means the rebuild will be missing something and
46
+ the command exits non-zero. Every code it can print — gutter, exit, meaning, what to
47
+ do — is the finding-code table in INGEST §2.0. Keep the `note` both specs carry.
48
+
49
+ ## What this guide will not do
50
+
51
+ rigc still cannot **edit** a skeleton: there is no command that opens one and
52
+ changes it, so every change is expressed in the specs — INGEST §0. `diff`'s ratios
53
+ say how much of a reference's structure a candidate reproduces, so extending or
54
+ renaming a foreign skeleton lowers them by design, and neither `validate` nor `diff`
55
+ has a pass bar — INGEST §0 and §4.
56
+
57
+ ⚠️ This section said *"there is no route from it to specs, so every change goes
58
+ through transcription"* until 2026-09-17. That route exists now and it is the first
59
+ thing to reach for; transcription by hand is what you fall back on for a construct
60
+ `ingest` reports as a blocker.
61
+
62
+ ## Read, in this order
63
+
64
+ 1. [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) — what every command will and will not do with
65
+ a foreign file (§0), `ingest` and transcription (§2), what each validator
66
+ complaint means on an export (§3), and the re-pivot, rename and extend
67
+ recipes (§4).
68
+ 2. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the two spec files the transcription
69
+ targets (§3–§4), the failure map (§5–§6), and the coordinate contract (§11.2).
70
+ 3. Then [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md) for why the re-pivot edit has the shape
71
+ it has, and [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) for the animation you are adding.
72
+
73
+ Every guide linked here is in the installed package at `node_modules/spine-rigc/docs/`,
74
+ which is the copy that matches the rigc you run; the links go to the repository's
75
+ `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
76
+
77
+ The install line and the build → validate → render → check loop are in the `rigc`
78
+ skill.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: rigc-motion
3
+ description: Author a Spine animation with rigc from key poses — an idle, a loop, a move from one picture to another — with timing and spacing, ease in and out, anticipation, arcs, overlap, follow-through, squash and stretch, and candidate variants a person can choose between. Use when the request is a movement on an existing or planned rig, in the animator's words too: "animate this rig", "make it breathe", "go from pose A to pose B", "make it feel heavier", "the cape should follow through". Not for Live2D, separating an image into parts, or real-time tracking.
4
+ license: MIT
5
+ compatibility: Requires Bun 1.2 or later and the npm package spine-rigc.
6
+ ---
7
+
8
+ # Motion — what goes between two poses
9
+
10
+ Load this when the request is a **movement rather than a skeleton**: a sentence of
11
+ intent, between zero and N pictures of what the movement passes through, and a
12
+ Spine animation somebody would choose coming back. Every rule below is owned by
13
+ [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md); this file says when to open it and what it will
14
+ not do for you.
15
+
16
+ ## Non-negotiables
17
+
18
+ - **Validation is never bypassed.** `build` writes nothing on a red gate, and there
19
+ is no flag that changes that — AUTHORING §0.
20
+ - **The compiler never invents a value.** A key with no time, a curve with no kind, a
21
+ bone the rig does not declare — each is refused by name before anything is
22
+ written — AUTHORING §4 and §6.
23
+ - **The validator's messages are the instructions.** Read each named failure as the
24
+ pointer to the file that has to change — AUTHORING §5.
25
+
26
+ ## What this guide will not do
27
+
28
+ Nothing here grades a movement, and no instrument named here can. `build` says a
29
+ file is valid, `check` says how far it is from pictures you were given, and the one
30
+ thing that judges a movement is a person's eye through `rigc vote` — MOTION §0.
31
+
32
+ ## Read, in this order
33
+
34
+ 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the motion spec field by field (§4),
35
+ the failure map (§5–§6), reading reference frames (§8) and checking against
36
+ them (§9), what the editor does when nobody tells it otherwise (§10), and
37
+ reading a pose out of a picture (§11–§12).
38
+ 2. [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) — the normal form every motion request
39
+ reduces to (§0), timing, easing and the per-bone offset table (§3), and how to
40
+ spread candidates so a ballot informs (§4–§5).
41
+ 3. Then [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md) if the skeleton itself is what you have
42
+ to decide; [FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md) if the movement is a blink, a gaze or a
43
+ head turn; [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) if the rig arrived as a compiled
44
+ skeleton rather than loose parts.
45
+
46
+ Every guide linked here is in the installed package at `node_modules/spine-rigc/docs/`,
47
+ which is the copy that matches the rigc you run; the links go to the repository's
48
+ `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
49
+
50
+ The install line and the build → validate → render → check loop are in the `rigc`
51
+ skill.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: rigc-rigging
3
+ description: Decide a Spine rig's hierarchy with rigc — how many bones, where each pivot sits, what hangs off what, offsets, chains and what a chain can reach, siblings versus chains, constraints as structure — and which of those decisions the reference frames can check. Use when the request is a skeleton from loose part PNGs, such as "rig these parts", "make a Spine skeleton" or "where do the joints go", before any motion is authored. Not for Live2D, cutting an illustration into parts, or VTuber-style tracking.
4
+ license: MIT
5
+ compatibility: Requires Bun 1.2 or later and the npm package spine-rigc.
6
+ ---
7
+
8
+ # Rigging — the hierarchy itself
9
+
10
+ Load this when the request is a **skeleton rather than a movement**: loose part
11
+ PNGs in, a bone hierarchy out. Every rule below is owned by
12
+ [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md); this file says when to open it and what it
13
+ will not do for you.
14
+
15
+ ## Non-negotiables
16
+
17
+ - **Validation is never bypassed.** `build` writes nothing on a red gate, and there
18
+ is no flag that changes that — AUTHORING §0.
19
+ - **The compiler never invents a value.** A bone's `parent`, a slot's `bone`, a
20
+ constraint's `target` resolve by name and a miss is refused by name; a missing
21
+ number is a `CompileError` naming the field — AUTHORING §2 and §3.
22
+ - **The validator's messages are the instructions.** Read each named failure as the
23
+ pointer to the file that has to change — AUTHORING §5.
24
+
25
+ ## What this guide will not do
26
+
27
+ Nothing grades a hierarchy. A rig with its head off its torso passes the gate, the
28
+ pixels are nearly blind to structure, and the two instruments that see it at all
29
+ are named in RIGGING §11 — neither as a pass bar.
30
+
31
+ ## Read, in this order
32
+
33
+ 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the rig spec field by field (§3),
34
+ the failure map (§5–§6), reading a pose out of a picture (§8.1, §11, §12),
35
+ and the editor's own conventions (§10).
36
+ 2. [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md) — the structure itself: what identifies a
37
+ pivot and what moving one costs (§2–§3), gauges (§4), what a chain can reach
38
+ (§6), and the instruments that see structure (§11).
39
+ 3. Then [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) once the skeleton exists;
40
+ [FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md) if the figure is a head;
41
+ [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) if the skeleton was handed to you already
42
+ compiled.
43
+
44
+ Every guide linked here is in the installed package at `node_modules/spine-rigc/docs/`,
45
+ which is the copy that matches the rigc you run; the links go to the repository's
46
+ `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
47
+
48
+ The install line and the build → validate → render → check loop are in the `rigc`
49
+ skill.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The area band a triangle's sign is read against — the one A39 reads a
3
+ * winding by, and the one the mesh-quality measurement reads orientation and
4
+ * degeneracy by (`src/meshquality.ts`).
5
+ *
6
+ * Moved here unchanged from `src/deformsurvey.ts`, which imports these back
7
+ * and re-exports every name it exported before, so no caller changed an import
8
+ * — and `stretchSingularValues` after them (issue #1230), for the motion
9
+ * comparison (`src/meshcompare.ts`), which reads a triangle's stretch the way
10
+ * the deform survey does and must not reach the compiler either.
11
+ * They moved because a module the `spine-rigc/mesh` entry reaches has to read
12
+ * the same band, and `src/deformsurvey.ts` reaches the compiler and the core
13
+ * (`src/deformstructure.ts`, `src/core/`) — a geometry entry that loaded the
14
+ * compiler to read three numbers would also close an import cycle through
15
+ * `src/compile.ts`, which imports `src/mesh.ts`.
16
+ *
17
+ * Imports nothing: no clock, no runtime, no other module.
18
+ */
19
+
20
+ /**
21
+ * How near zero a triangle's area has to be, as a fraction of the largest
22
+ * triangle in the same mesh at the same pose, before a sign is not read off it.
23
+ *
24
+ * A RELATIVE band, because an absolute one has no scale that means anything on
25
+ * its own — these are pixel² figures on whatever plate the rig was drawn at, and
26
+ * `gallery/flex`'s leaf tops out at 792.6 px² where `spineboy-pro`'s hoverboard
27
+ * reaches 3338.4 px². On those two it comes to 7.9e-4 and 3.3e-3 px².
28
+ *
29
+ * ⚠️ It is **not** what holds the float32 noise off; `float32AreaNoise` is, and
30
+ * the two are combined rather than ranked because on a big mesh the noise bound
31
+ * is the larger of them. This one is the shape band: it keeps a setup triangle
32
+ * that has no area from being read as a reversal of anything, and a triangle the
33
+ * key collapses onto zero from being read as turned over.
34
+ */
35
+ export const DEFORM_AREA_EPSILON = 1e-6;
36
+
37
+ /** Half an ulp of a float32 mantissa — the relative error of one stored coordinate. */
38
+ const FLOAT32_HALF_ULP = 2 ** -24;
39
+
40
+ /**
41
+ * An upper bound on how much of a triangle's signed area is float32 noise.
42
+ *
43
+ * The world vertices arrive in a `Float32Array`, so each coordinate carries up
44
+ * to `|c|·2⁻²⁴` of error. An area is `½·(Δx₁·Δy₂ − Δx₂·Δy₁)`, and propagating
45
+ * that error through one product gives `Δ·|c|·2⁻²⁴` twice over; four such terms
46
+ * across the two products, halved, bounds the area error by `2·C²·2⁻²⁴` with `C`
47
+ * the largest coordinate magnitude in the mesh (which also bounds every `Δ`).
48
+ * Doubled once more for the subtraction, so the constant is 4.
49
+ *
50
+ * On a mesh whose vertices reach 500 units that is 6e-2 px², i.e. **larger** than
51
+ * the relative band above — which is the whole reason this exists. It is a bound
52
+ * rather than a measurement, and deliberately loose: what has to stay clear of it
53
+ * is a genuine reversal, and the smallest one anywhere in the corpus is
54
+ * `spineboy-pro`'s hoverboard triangle at 8.478 px², more than two orders of
55
+ * magnitude above. Nothing measured lands between the two, so nothing between
56
+ * them is being tuned.
57
+ */
58
+ export function float32AreaNoise(world: ArrayLike<number>): number {
59
+ let coordinate = 0;
60
+ for (let i = 0; i < world.length; i++) coordinate = Math.max(coordinate, Math.abs(world[i]));
61
+ return 4 * coordinate * coordinate * FLOAT32_HALF_ULP;
62
+ }
63
+
64
+ /**
65
+ * Twice-signed area, halved, of every triangle of `triangles` over the
66
+ * interleaved `x, y` world vertices in `world`.
67
+ *
68
+ * The SIGN is the whole point and the magnitude is the tolerance's yardstick, so
69
+ * this returns the signed figure rather than an absolute one. Positive and
70
+ * negative are not "correct" and "wrong" — a mesh may be wound either way, and
71
+ * what A39 reads is whether one triangle's sign CHANGED.
72
+ */
73
+ export function triangleAreas(world: ArrayLike<number>, triangles: ArrayLike<number>): number[] {
74
+ const out: number[] = [];
75
+ for (let t = 0; t + 2 < triangles.length; t += 3) {
76
+ const i0 = triangles[t] * 2;
77
+ const i1 = triangles[t + 1] * 2;
78
+ const i2 = triangles[t + 2] * 2;
79
+ const x0 = world[i0];
80
+ const y0 = world[i0 + 1];
81
+ out.push(0.5 * ((world[i1] - x0) * (world[i2 + 1] - y0) - (world[i2] - x0) * (world[i1 + 1] - y0)));
82
+ }
83
+ return out;
84
+ }
85
+
86
+ /**
87
+ * The dead band a set of areas is read against.
88
+ *
89
+ * Both bands, and the wider one wins. The relative one is about the SHAPE (a
90
+ * triangle with no area has no winding); the noise one is about the arithmetic (a
91
+ * sign read off float32 rounding is not a measurement). Each is the larger on a
92
+ * different mesh.
93
+ */
94
+ export function areaBand(plainAreas: readonly number[], ...worlds: ReadonlyArray<ArrayLike<number>>): number {
95
+ const largest = plainAreas.reduce((m, a) => Math.max(m, Math.abs(a)), 0);
96
+ let band = largest * DEFORM_AREA_EPSILON;
97
+ for (const world of worlds) band = Math.max(band, float32AreaNoise(world));
98
+ return band;
99
+ }
100
+
101
+ /**
102
+ * The two singular values of the linear map that takes one triangle onto the
103
+ * other — the largest and smallest factor by which it scales a direction.
104
+ *
105
+ * ## Why this is the texture's stretch
106
+ *
107
+ * A mesh's uvs are fixed to the attachment and a deform never touches them, so
108
+ * the texture is mapped affinely onto the *plain* triangle and the same texels
109
+ * end up on the *deformed* one. The change in that mapping is exactly `J = D·P⁻¹`
110
+ * with `P` and `D` the two triangles' edge pairs, and its singular values are the
111
+ * worst stretch and the worst squash the drawing takes there. A σ of 1.4 means
112
+ * every texel in that direction is drawn 1.4 px wide; 0.6 means the art is
113
+ * crushed to 60%.
114
+ *
115
+ * `σ₁·σ₂ = |det J|` is the signed-area ratio's magnitude, which is why the two
116
+ * quantities in the report cannot disagree — and `DR01` is the control that says
117
+ * so on a case whose ratio the closed form predicts.
118
+ *
119
+ * Returns `null` for a plain triangle with no area: `P` is singular, there is no
120
+ * map, and inventing one would be the report's own version of the false green
121
+ * this file exists to avoid. Those triangles are counted as `degenerate`.
122
+ */
123
+ export function stretchSingularValues(
124
+ plain: ArrayLike<number>,
125
+ deformed: ArrayLike<number>,
126
+ triangles: ArrayLike<number>,
127
+ t: number,
128
+ ): { max: number; min: number } | null {
129
+ const i0 = triangles[t * 3] * 2;
130
+ const i1 = triangles[t * 3 + 1] * 2;
131
+ const i2 = triangles[t * 3 + 2] * 2;
132
+ const ux = plain[i1] - plain[i0];
133
+ const uy = plain[i1 + 1] - plain[i0 + 1];
134
+ const vx = plain[i2] - plain[i0];
135
+ const vy = plain[i2 + 1] - plain[i0 + 1];
136
+ const det = ux * vy - vx * uy;
137
+ if (det === 0) return null;
138
+ const px = deformed[i1] - deformed[i0];
139
+ const py = deformed[i1 + 1] - deformed[i0 + 1];
140
+ const qx = deformed[i2] - deformed[i0];
141
+ const qy = deformed[i2 + 1] - deformed[i0 + 1];
142
+ // J = D·P⁻¹, written out — P⁻¹ = (1/det)·[[vy, −vx], [−uy, ux]].
143
+ const a = (px * vy - qx * uy) / det;
144
+ const b = (-px * vx + qx * ux) / det;
145
+ const c = (py * vy - qy * uy) / det;
146
+ const d = (-py * vx + qy * ux) / det;
147
+ // σ₁² + σ₂² = ‖J‖²_F and σ₁·σ₂ = |det J|, which is two equations for the two
148
+ // values and needs no eigen decomposition. The discriminant is non-negative in
149
+ // exact arithmetic (it is `(σ₁² − σ₂²)²`); the clamp is for rounding on a map
150
+ // that is very nearly a rotation.
151
+ const frobenius = a * a + b * b + c * c + d * d;
152
+ const determinant = a * d - b * c;
153
+ const discriminant = Math.max(0, frobenius * frobenius - 4 * determinant * determinant);
154
+ const root = Math.sqrt(discriminant);
155
+ return {
156
+ max: Math.sqrt(Math.max(0, (frobenius + root) / 2)),
157
+ min: Math.sqrt(Math.max(0, (frobenius - root) / 2)),
158
+ };
159
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A01, restated over the emitted text (issue #1060): the clause
3
+ * `validate()` runs in `src/validate.ts`, word for word, over the skeleton JSON — the
4
+ * text rigc wrote, read by rigc's own reader rather than by spine-core's. The
5
+ * round trip's copy is unchanged and runs in `cli.ts build`; this one runs in
6
+ * the gate of the entry that links none of the runtime
7
+ * (`../emitted/index.ts`), and the selftest's `RC28` holds the two to the
8
+ * same lines on every recipe and on this assertion's mutants.
9
+ */
10
+ import type { Verdicts } from '../harness.ts';
11
+ import { TOPLEVEL_CONSTRAINT_ARRAYS } from '../../generation.ts';
12
+ import type { Json } from '../values.ts';
13
+
14
+ export function a01NoLegacyToplevelConstraintArrays({ fail }: Verdicts, raw: Json | null): void {
15
+ for (const key of TOPLEVEL_CONSTRAINT_ARRAYS) {
16
+ if (raw && key in raw) {
17
+ fail(
18
+ 'A01_NO_LEGACY_TOPLEVEL_CONSTRAINT_ARRAYS',
19
+ `top-level "${key}" array present; 4.3 wants it inside "constraints" with type:"${key}"`,
20
+ );
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A02, restated over the emitted text (issue #1060): the clause
3
+ * `validate()` runs in `src/validate.ts`, word for word, over the skeleton JSON — the
4
+ * text rigc wrote, read by rigc's own reader rather than by spine-core's. The
5
+ * round trip's copy is unchanged and runs in `cli.ts build`; this one runs in
6
+ * the gate of the entry that links none of the runtime
7
+ * (`../emitted/index.ts`), and the selftest's `RC28` holds the two to the
8
+ * same lines on every recipe and on this assertion's mutants.
9
+ */
10
+ import type { Verdicts } from '../harness.ts';
11
+ import { LEGACY_BONE_INHERIT_KEY } from '../../generation.ts';
12
+ import { isObj, type Json } from '../values.ts';
13
+
14
+ export function a02NoBoneTransformKey({ fail }: Verdicts, raw: Json | null): void {
15
+ const bones = Array.isArray(raw?.bones) ? (raw.bones as unknown[]) : [];
16
+ for (const bone of bones) {
17
+ if (isObj(bone) && LEGACY_BONE_INHERIT_KEY in bone) {
18
+ fail('A02_NO_BONE_TRANSFORM_KEY', `bone "${String(bone.name)}" uses "transform", the key 4.0 and 4.1 spelled; 4.2 and 4.3 spell it "inherit"`);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A03, the body (issue #1025, step 4c of #380): every region attachment has a
3
+ * finite, positive width and height (case 6c).
4
+ *
5
+ * Moved out of `src/validate.ts` unchanged but for what it reads: the region
6
+ * attachments are a fact (`../facts/skin_entries.ts`) rather than the list
7
+ * `validate()` filled from spine-core's loaded skins, so the same body runs over
8
+ * the model document. The `check` call — and so where the verdict sits in the
9
+ * report — stays in `validate()`.
10
+ */
11
+ import type { Verdicts } from '../harness.ts';
12
+ import type { SkinEntryFacts } from '../facts/skin_entries.ts';
13
+ import { SKIP_NO_REGION_ATTACHMENT } from '../reasons.ts';
14
+
15
+ export function a03RegionWidthHeightFinite({ fail, skip }: Verdicts, { regionAttachments }: SkinEntryFacts): void {
16
+ // ⟨subject⟩_⟨property⟩: with no region there is no size to find non-finite,
17
+ // and a loop over nothing used to report that as held (#580).
18
+ if (regionAttachments.length === 0) return skip('A03_REGION_WIDTH_HEIGHT_FINITE', SKIP_NO_REGION_ATTACHMENT);
19
+ for (const att of regionAttachments) {
20
+ if (!Number.isFinite(att.width) || !Number.isFinite(att.height)) {
21
+ fail('A03_REGION_WIDTH_HEIGHT_FINITE', `region "${att.name}" loaded w=${att.width} h=${att.height}`);
22
+ }
23
+ if (att.width <= 0 || att.height <= 0) {
24
+ fail('A03_REGION_WIDTH_HEIGHT_FINITE', `region "${att.name}" has a non-positive size`);
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A04, the body (issue #1025, step 4c of #380): every mesh has triangles, in
3
+ * threes, each an index into its own vertices (case 6f).
4
+ *
5
+ * Moved out of `src/validate.ts` clause by clause. The three clauses about
6
+ * the triangles are about the rig — a document's mesh states its triangles
7
+ * and its vertices, so the same wrongness has a place to live there (where
8
+ * the reader refuses two of the three by the record's name) — and moved. The
9
+ * two about the vertex run — a weighted run whose length is not a multiple of
10
+ * three, an unweighted array of another length than the `uvs` — are about
11
+ * the encoding: the document states the weighted form outright, and the flat
12
+ * run is written by the emitter. They stay with the round trip, which hands
13
+ * their findings in as each mesh's `encoding`, printed where the body always
14
+ * printed them.
15
+ */
16
+ import type { Verdicts } from '../harness.ts';
17
+ import type { MeshFacts } from '../facts/mesh_attachments.ts';
18
+ import { SKIP_NO_MESH_ATTACHMENT } from '../reasons.ts';
19
+
20
+ export function a04MeshTrianglesAndEncoding({ fail, skip }: Verdicts, { meshes }: MeshFacts): void {
21
+ if (meshes.length === 0) return skip('A04_MESH_TRIANGLES_AND_ENCODING', SKIP_NO_MESH_ATTACHMENT);
22
+ for (const mesh of meshes) {
23
+ if (!mesh.triangles || mesh.triangles.length === 0) {
24
+ fail('A04_MESH_TRIANGLES_AND_ENCODING', `mesh "${mesh.name}" has no triangles`);
25
+ continue;
26
+ }
27
+ if (mesh.triangles.length % 3 !== 0) {
28
+ fail('A04_MESH_TRIANGLES_AND_ENCODING', `mesh "${mesh.name}" triangle count is not a multiple of 3`);
29
+ }
30
+ const vertexCount = mesh.worldVerticesLength / 2;
31
+ for (const idx of mesh.triangles) {
32
+ if (idx < 0 || idx >= vertexCount) {
33
+ fail('A04_MESH_TRIANGLES_AND_ENCODING', `mesh "${mesh.name}" index ${idx} is outside 0..${vertexCount - 1}`);
34
+ break;
35
+ }
36
+ }
37
+ // The encoding's own clauses, kept with the round trip (the header).
38
+ for (const detail of mesh.encoding) fail('A04_MESH_TRIANGLES_AND_ENCODING', detail);
39
+ }
40
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * A05, restated over the emitted text (issue #1060): the clause
3
+ * `validate()` runs in `src/validate.ts`, word for word, over the skeleton JSON — the
4
+ * text rigc wrote, read by rigc's own reader rather than by spine-core's. The
5
+ * round trip's copy is unchanged and runs in `cli.ts build`; this one runs in
6
+ * the gate of the entry that links none of the runtime
7
+ * (`../emitted/index.ts`), and the selftest's `RC28` holds the two to the
8
+ * same lines on every recipe and on this assertion's mutants.
9
+ */
10
+ import type { Verdicts } from '../harness.ts';
11
+ import { SKIP_NO_TIMELINE } from '../reasons.ts';
12
+ import { CHANNELS_BY_KIND, walkTimelines } from '../../timelines.ts';
13
+ import { isObj, type Json } from '../values.ts';
14
+
15
+ export function a05CurveArrayLength({ fail, skip }: Verdicts, raw: Json | null): void {
16
+ // Two clauses, so the SKIP needs both to be empty (#580). The vocabulary
17
+ // clause below measures every timeline it is handed — an unchecked name is a
18
+ // finding whether or not any key on it carries a curve — so a skeleton with
19
+ // timelines and no curve at all has still been measured. What measures
20
+ // nothing is a skeleton `walkTimelines` never calls back on.
21
+ let timelines = 0;
22
+ walkTimelines(raw, (path, kind, name, keys) => {
23
+ timelines++;
24
+ const table = CHANNELS_BY_KIND[kind];
25
+ if (!(name in table)) {
26
+ fail('A05_CURVE_ARRAY_LENGTH', `${path}: unchecked ${kind} timeline "${name}" — extend the validator`);
27
+ return;
28
+ }
29
+ const channels = table[name];
30
+ for (const key of keys) {
31
+ if (!isObj(key) || !('curve' in key)) continue;
32
+ const curve = key.curve;
33
+ if (channels === null) {
34
+ fail('A05_CURVE_ARRAY_LENGTH', `${path}: timeline "${name}" cannot carry a curve`);
35
+ continue;
36
+ }
37
+ if (curve === 'stepped') continue;
38
+ if (!Array.isArray(curve)) {
39
+ fail('A05_CURVE_ARRAY_LENGTH', `${path}: curve is ${JSON.stringify(curve)}, expected "stepped" or an array`);
40
+ continue;
41
+ }
42
+ if (curve.length !== channels * 4) {
43
+ fail(
44
+ 'A05_CURVE_ARRAY_LENGTH',
45
+ `${path} (t=${String(key.time ?? 0)}): curve has ${curve.length} numbers, "${name}" needs ${channels} channels x 4 = ${channels * 4}`,
46
+ );
47
+ }
48
+ for (const n of curve) {
49
+ if (typeof n !== 'number' || !Number.isFinite(n)) {
50
+ fail('A05_CURVE_ARRAY_LENGTH', `${path}: curve holds a non-finite value ${JSON.stringify(n)}`);
51
+ }
52
+ }
53
+ }
54
+ });
55
+ if (timelines === 0) return skip('A05_CURVE_ARRAY_LENGTH', SKIP_NO_TIMELINE);
56
+ }