@rig-c/core 2.39.0 → 2.40.0

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.
package/docs/RIGGING.md CHANGED
@@ -105,7 +105,7 @@ beside the offsets it exists to make visible:
105
105
  > *"Every limb attachment is OFFSET from its bone, and that is what makes the hinge
106
106
  > visible: art centred on its own pivot turns in place, so a search over one angle
107
107
  > would move nothing and a wrong hinge would cost nothing either."*
108
- > — [`selftest.ts`](https://github.com/firejune/rigc/blob/main/selftest.ts), the chain-fit fixture
108
+ > — [`selftest.ts`](https://github.com/rig-c/core/blob/main/selftest.ts), the chain-fit fixture
109
109
 
110
110
  ⇒ **A correctly placed pivot also collapses the spec.** A pendulum whose bone sits
111
111
  on its hinge needs one `rotate` track and no translate at all; the same part with
@@ -144,7 +144,7 @@ never:
144
144
  it**, so the bone points at its own mass — at the cost of two emitted fields.
145
145
 
146
146
  > *"Neither is checkable from the frames."* — rung 3's first attempt
147
- > ([`2026-08-23-rung3-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-rung3-1/LOOP.md) §11)
147
+ > ([`2026-08-23-rung3-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-rung3-1/LOOP.md) §11)
148
148
 
149
149
  ⇒ Pick one, and say in your log that you picked it. AUTHORING §10.1's naming
150
150
  convention is the same shape of decision and is worth far more (it decides five of
@@ -162,7 +162,7 @@ rigid.
162
162
  > bone you scale-key must sit at that bone's origin, or the offset rides the
163
163
  > scale."*
164
164
  > — spineboy attempt 4
165
- > ([`2026-08-28-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-spineboy-1/LOOP.md) §8)
165
+ > ([`2026-08-28-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-spineboy-1/LOOP.md) §8)
166
166
 
167
167
  ⇒ This is the one exception to §1's rule, and it is not a contradiction: a bone
168
168
  whose **scale** is the animated property is not a hinge, so there is no hinge to
@@ -184,10 +184,10 @@ None of them is an error message. Each one is a number that reads as ordinary.
184
184
 
185
185
  | Signature | What it actually is | Record |
186
186
  | --- | --- | --- |
187
- | **A gap that letting the parts off the hierarchy closes.** `idle` fitted to 10.3 union MAE on the frame the setup pose came from and **23.9** eight frames later; a short relax off the hierarchy recovered it to **14.6** | *"A gap that a relax closes is not a pose the rig cannot reach; it is a pivot in the wrong place."* | spineboy attempt 1 ([`2026-08-23-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-spineboy-1/LOOP.md) §8.2) |
188
- | **A fitted centre that drifts monotonically with the fitted scale.** The per-frame fits wanted each shadow's centre 8–12 units lower whenever the shadow was small | a part scaling about a pivot below its own centre — *"a pivot you have not modelled, not motion you have measured"* | rung 1 second attempt ([`2026-08-26-rung1-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-26-rung1-1/LOOP.md) §2.3) |
189
- | **A knob resting exactly on its bound, on one orientation only.** `torso.x` pinned at −35.0 against a ±35 box on the lying frames and nowhere else | the optimum is outside the box, because a pivot error `e` needs a compensation `−R(θ)·e` that grows with the local angle — *"invisible upright, saturating in the lying poses"* | spineboy attempt 5 ([`2026-08-28-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-spineboy-2/LOOP.md) §3) |
190
- | **An exact fit with no angular spread.** Two ankle joints solved at **rms 0.00 px** with a relative-angle spread of **0° and 2°** | a perfect fit to a system with no unique answer | spineboy, 2026-09-03 first attempt ([`2026-09-03-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-09-03-spineboy-1/LOOP.md) §3.3) |
187
+ | **A gap that letting the parts off the hierarchy closes.** `idle` fitted to 10.3 union MAE on the frame the setup pose came from and **23.9** eight frames later; a short relax off the hierarchy recovered it to **14.6** | *"A gap that a relax closes is not a pose the rig cannot reach; it is a pivot in the wrong place."* | spineboy attempt 1 ([`2026-08-23-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-spineboy-1/LOOP.md) §8.2) |
188
+ | **A fitted centre that drifts monotonically with the fitted scale.** The per-frame fits wanted each shadow's centre 8–12 units lower whenever the shadow was small | a part scaling about a pivot below its own centre — *"a pivot you have not modelled, not motion you have measured"* | rung 1 second attempt ([`2026-08-26-rung1-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-26-rung1-1/LOOP.md) §2.3) |
189
+ | **A knob resting exactly on its bound, on one orientation only.** `torso.x` pinned at −35.0 against a ±35 box on the lying frames and nowhere else | the optimum is outside the box, because a pivot error `e` needs a compensation `−R(θ)·e` that grows with the local angle — *"invisible upright, saturating in the lying poses"* | spineboy attempt 5 ([`2026-08-28-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-spineboy-2/LOOP.md) §3) |
190
+ | **An exact fit with no angular spread.** Two ankle joints solved at **rms 0.00 px** with a relative-angle spread of **0° and 2°** | a perfect fit to a system with no unique answer | spineboy, 2026-09-03 first attempt ([`2026-09-03-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-09-03-spineboy-1/LOOP.md) §3.3) |
191
191
 
192
192
  🚨 **The last one is the trap in its purest form: a zero residual reads as success
193
193
  in every other context on this toolchain.** Here it means the estimator was handed
@@ -219,7 +219,7 @@ across all eight shots:
219
219
  > 21-unit-different answer that fits the upright frames just as well. The joint is
220
220
  > ill-conditioned without the lying poses, which is how the inherited triangulation
221
221
  > (idle zoom-read) went wrong without any frame saying so."*
222
- > — [`2026-08-28-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-spineboy-2/LOOP.md) §5
222
+ > — [`2026-08-28-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-spineboy-2/LOOP.md) §5
223
223
 
224
224
  Two answers 21 art units apart, at residuals that do not separate them. That is
225
225
  what *ill-conditioned* means with a picture attached.
@@ -296,7 +296,7 @@ found that **every one of them measured a basin at the sweep's own cap** — ±4
296
296
  > *"On this figure the joint offsets are **weakly identified at the scale a frame
297
297
  > can see**, and the honest reading is that they are a prior with a measured width
298
298
  > rather than a measurement."*
299
- > — [`2026-09-03-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-09-03-spineboy-2/LOOP.md) §3.5
299
+ > — [`2026-09-03-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-09-03-spineboy-2/LOOP.md) §3.5
300
300
 
301
301
  ⇒ That sentence is the deliverable when the pixels are silent. It is not a hedge:
302
302
  a width is a number, and the next attempt can tell whether its own change is inside
@@ -463,7 +463,7 @@ The failure, watched happening:
463
463
  > rear-thigh −199.8°`, whose net world rotations are −3°, +31° and −19°: the picture
464
464
  > was right and the numbers were unusable. A rotate series that swings 180° between
465
465
  > two keys does not interpolate, it spins the figure through a whole turn."*
466
- > — spineboy attempt 1 ([`2026-08-23-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-spineboy-1/LOOP.md) §8.4)
466
+ > — spineboy attempt 1 ([`2026-08-23-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-spineboy-1/LOOP.md) §8.4)
467
467
 
468
468
  ### 4.1 ⚠️ The exact fold has a precondition, and it is easy to miss
469
469
 
@@ -474,7 +474,7 @@ AUTHORING §10.3 prescribes is not a null operation — it costs pixels:
474
474
  > *"the hip's three children sit 9–13 units off it, so turning the hip moves their
475
475
  > origins and the render. Folding cost MAE on every frame it touched (`idle` mean
476
476
  > 23.0 with the fold against 19.9 without, same search)."*
477
- > — spineboy attempt 2 ([`2026-08-23-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-spineboy-2/LOOP.md) §5)
477
+ > — spineboy attempt 2 ([`2026-08-23-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-spineboy-2/LOOP.md) §5)
478
478
 
479
479
  ⇒ **Test the precondition before folding.** Children at the origin ⇒ exact gauge,
480
480
  fold it (the L1 optimum is the *median* of the children's deltas, which is one line).
@@ -521,10 +521,10 @@ that it is *keyed for a reason the arithmetic states*, not left free for a solve
521
521
 
522
522
  | Bone | Carries | Its children then key |
523
523
  | --- | --- | --- |
524
- | `faceshift` ([`gallery/portrait`](https://github.com/firejune/rigc/tree/main/gallery/portrait/)) | `−R·sin t`, the shift every feature shares in a yaw | only their residual — FACE §3 |
524
+ | `faceshift` ([`gallery/portrait`](https://github.com/rig-c/core/tree/main/gallery/portrait/)) | `−R·sin t`, the shift every feature shares in a yaw | only their residual — FACE §3 |
525
525
  | `comet` (rung 6, rung 8) | the whole travel of a ball and its trail | the squash, and the trail's bend |
526
526
  | `body` (rung 7, second attempt) | **translation only** — *"it holds no attachment, so keying its rotation as well would be a gauge"* | their own rotations |
527
- | `ground` ([`gallery/walk`](https://github.com/firejune/rigc/tree/main/gallery/walk/)) | the contact line | the foot targets — §9.1 |
527
+ | `ground` ([`gallery/walk`](https://github.com/rig-c/core/tree/main/gallery/walk/)) | the contact line | the foot targets — §9.1 |
528
528
 
529
529
  ⭐ **FACE §3's argument for it is an auditing one before it is a rigging one**: a
530
530
  residual is 1–6 units where a total is 30–40, *"nobody can eyeball an error in the
@@ -548,13 +548,13 @@ invisible in the numbers.
548
548
  > and takes a `translatey` (0 → 3.4). Parent the head chain to the bone that scales
549
549
  > and the whole face inflates 1.4% every breath — visible, and invisible in the
550
550
  > spec."*
551
- > — [`gallery/portrait/README.md`](https://github.com/firejune/rigc/blob/main/gallery/portrait/README.md)
551
+ > — [`gallery/portrait/README.md`](https://github.com/rig-c/core/blob/main/gallery/portrait/README.md)
552
552
 
553
553
  The same decision, on a ball and its trail:
554
554
 
555
555
  > *"`ball` is a **sibling** of `tail0` under `comet`, not its parent, so squashing
556
556
  > the ball does not squash the trail."*
557
- > — rung 6 ([`2026-08-23-rung6-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-rung6-1/README.md))
557
+ > — rung 6 ([`2026-08-23-rung6-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-rung6-1/README.md))
558
558
 
559
559
  ⇒ **The construction is always the same three bones**: an artless parent that
560
560
  carries what both share (§4.4), and two children — one that takes the scale and one
@@ -565,7 +565,7 @@ that takes the chain.
565
565
  > *"the neck plate has to take a *fraction* of the head's shift, so it cannot be an
566
566
  > ancestor of the head. `neckbase` is the shared parent; `neck` carries the plate and
567
567
  > `headroll` carries the head"*
568
- > — [`gallery/portrait/README.md`](https://github.com/firejune/rigc/blob/main/gallery/portrait/README.md), the part table
568
+ > — [`gallery/portrait/README.md`](https://github.com/rig-c/core/blob/main/gallery/portrait/README.md), the part table
569
569
 
570
570
  ⭐ **And the pivot goes one link up, which is the same edit for a second reason.**
571
571
  The same rig keys `headroll` and never `head`: *"a head rotates about the top of the
@@ -638,7 +638,7 @@ The record that named it:
638
638
  > setup render is unchanged. walk 0.176 → 0.150, run 0.232 → 0.201, aim 0.212 →
639
639
  > 0.186 after refit."*
640
640
  > — spineboy attempt 4
641
- > ([`2026-08-28-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-spineboy-1/LOOP.md) §6)
641
+ > ([`2026-08-28-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-spineboy-1/LOOP.md) §6)
642
642
 
643
643
  ⭐ **The tie-break is on the frames' side.** *"the shot's own extremes are a
644
644
  measurement, while segment lengths taken off a folded pose are an estimate — so when
@@ -659,7 +659,7 @@ fits converge, every residual is ordinary, and the figure splays.**
659
659
  > leg overshoots the floor by about 90 units and the fitter has to bend it sideways
660
660
  > to land … Re-seeding the leg joints inset from the art's ends — thigh 86 units,
661
661
  > shin 94 — put the ankle at world y **53.5** against the boot's own 54."*
662
- > — [`2026-09-03-spineboy-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-09-03-spineboy-1/LOOP.md) §4.4
662
+ > — [`2026-09-03-spineboy-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-09-03-spineboy-1/LOOP.md) §4.4
663
663
 
664
664
  ⭐ **Why the art misled it, and this generalises:** the art's outer ends are not the
665
665
  joints. A rounded cap's centroid sits at the plate's *tip*, while the joint two
@@ -686,7 +686,7 @@ frames put the standing leg at about 233 — a 79-unit excess with a plausible c
686
686
  > better rig, and the 79 units are a real knee bend in the reference's own stance
687
687
  > rather than an error in this rig. The surgery §8.1 warns about … was therefore not
688
688
  > performed, and the per-frame fitting budget was not spent twice."*
689
- > — [`2026-09-03-spineboy-2/evidence/limb-reach.txt`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-09-03-spineboy-2/evidence/limb-reach.txt)
689
+ > — [`2026-09-03-spineboy-2/evidence/limb-reach.txt`](https://github.com/rig-c/core/blob/main/bench/runs/2026-09-03-spineboy-2/evidence/limb-reach.txt)
690
690
 
691
691
  ⇒ **The reach check tells you a chain and a shot disagree. It does not tell you
692
692
  which one is wrong.** A bent limb and a long limb produce the same excess. One
@@ -736,7 +736,7 @@ form: **is each frame's silhouette an affine image of the part's own drawing?**
736
736
  > freedom the mesh was built with — a four-bone chain, weights blended by height
737
737
  > only."*
738
738
  > — rung 7, first attempt
739
- > ([`2026-08-28-rung7-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-rung7-1/LOOP.md) §5)
739
+ > ([`2026-08-28-rung7-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-rung7-1/LOOP.md) §5)
740
740
 
741
741
  ⇒ Two controls are what make that quotable and they are worth copying: the art
742
742
  through a **known affine** read 0.0044 → 0.0007 (the floor), and the art plus a
@@ -772,10 +772,10 @@ not fail — it degenerates:
772
772
  > *"The first candidate lifted the foot 56 units on a 130-unit leg: the chain has to
773
773
  > fold to a 70-unit span, and a two-bone solve does that by throwing the knee
774
774
  > sideways. 28 units — a fifth of the leg — is the version that reads as a step."*
775
- > — [`gallery/walk/README.md`](https://github.com/firejune/rigc/blob/main/gallery/walk/README.md)
775
+ > — [`gallery/walk/README.md`](https://github.com/rig-c/core/blob/main/gallery/walk/README.md)
776
776
 
777
777
  The same arithmetic on the other side of the chain, from
778
- [`gallery/flex`](https://github.com/firejune/rigc/tree/main/gallery/flex/): rotating a rigid panel about a seam opens a wedge
778
+ [`gallery/flex`](https://github.com/rig-c/core/tree/main/gallery/flex/): rotating a rigid panel about a seam opens a wedge
779
779
  of `halfHeight × tan(angle)` — about 22 px at 20° on that cloth — so *"the bend
780
780
  angles in `motion.json` are chosen against that overlap, not the other way round."*
781
781
 
@@ -801,7 +801,7 @@ differ by exactly the bone's own setup `x`/`y`:
801
801
  > *"the first spec moved the figure by the setup offset a second time. Rotation and
802
802
  > scale needed no correction: their setups are 0 and 1."*
803
803
  > — rung 7, second attempt
804
- > ([`2026-08-28-rung7-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-28-rung7-2/LOOP.md) §5)
804
+ > ([`2026-08-28-rung7-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-28-rung7-2/LOOP.md) §5)
805
805
 
806
806
  The same confusion one attempt earlier, from the other end: *"The first fitter used
807
807
  0 as every knob's neutral value. `bone.pose.x` is the bone's whole local
@@ -817,7 +817,7 @@ the diff names it in one line."*
817
817
 
818
818
  ⚠️ **A bone under a rotated parent is not at the parent's position plus its own
819
819
  numbers, and a leg chain is exactly that case.** That sentence is a comment in
820
- [`gallery/stage.ts`](https://github.com/firejune/rigc/blob/main/gallery/stage.ts), which exists because a drawing script
820
+ [`gallery/stage.ts`](https://github.com/rig-c/core/blob/main/gallery/stage.ts), which exists because a drawing script
821
821
  needed setup-pose world positions and *"writing them a second time in the drawing
822
822
  script is the defect that produces a shadow under nobody's feet, and it is
823
823
  invisible in both files — each is internally consistent."*
@@ -993,7 +993,7 @@ designed around it:
993
993
  > the chest and the legs as siblings. Chosen for identifiability, not anatomy …
994
994
  > Under a pelvis the legs sit above the only reliable anchor and every leg comes back
995
995
  > `no-anchor` on every frame — measured, not predicted."*
996
- > — [`2026-09-03-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-09-03-spineboy-2/README.md)
996
+ > — [`2026-09-03-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-09-03-spineboy-2/README.md)
997
997
 
998
998
  That is the only place in the corpus where a tree was chosen for what could be
999
999
  measured through it rather than for anatomy or for the art, and it is worth knowing
@@ -1014,7 +1014,7 @@ backwards:
1014
1014
  `bendPositive` combinations were indistinguishable at 4× zoom on the frame where the
1015
1015
  difference is largest. `shin_f.worldX` separated them immediately. Look at frames
1016
1016
  for whether it reads; read bone positions for what it is doing."*
1017
- ([`gallery/walk/README.md`](https://github.com/firejune/rigc/blob/main/gallery/walk/README.md))
1017
+ ([`gallery/walk/README.md`](https://github.com/rig-c/core/blob/main/gallery/walk/README.md))
1018
1018
 
1019
1019
  ### 8.4 ⚠️ And `pose` can be confident about the wrong twin
1020
1020
 
@@ -1041,7 +1041,7 @@ list of structural rules and a list of refusals.
1041
1041
  > at the contact line; `foot_f` and `foot_b` are children of `ground`. Parent a foot
1042
1042
  > target to `hip` and the hip carries its own feet up with it, **the solver reports
1043
1043
  > success, and the figure hovers.**"*
1044
- > — [`gallery/walk/README.md`](https://github.com/firejune/rigc/blob/main/gallery/walk/README.md)
1044
+ > — [`gallery/walk/README.md`](https://github.com/rig-c/core/blob/main/gallery/walk/README.md)
1045
1045
 
1046
1046
  Measured on that build, over the stance where `foot_f` is planted: the hip travels
1047
1047
  **7.9 units up and 2.9 sideways** while the foot reads `382.0, 68.0` on every
@@ -1067,15 +1067,15 @@ reaches the target. So:
1067
1067
  ⭐ **This is where the art and the hierarchy stop being two decisions.** Both plates
1068
1068
  have to be **drawn from their own joint**, and the joint's coordinates *inside each
1069
1069
  drawing* are what every `length` and every attachment offset is measured from —
1070
- [`gallery/rigby.ts`](https://github.com/firejune/rigc/blob/main/gallery/rigby.ts) names all four points for one leg, and
1071
- [`gallery/walk/rig.json`](https://github.com/firejune/rigc/blob/main/gallery/walk/rig.json) states them.
1070
+ [`gallery/rigby.ts`](https://github.com/rig-c/core/blob/main/gallery/rigby.ts) names all four points for one leg, and
1071
+ [`gallery/walk/rig.json`](https://github.com/rig-c/core/blob/main/gallery/walk/rig.json) states them.
1072
1072
 
1073
1073
  ### 9.3 A constraint carries the whole subtree, and that is the point
1074
1074
 
1075
1075
  > *"`cart` is the constrained bone; `wheel_b`/`wheel_f` and `hip` are its children,
1076
1076
  > so the constraint carries the wheels and the whole figure with it and the tangent
1077
1077
  > rotation tilts all three."*
1078
- > — [`gallery/ride/README.md`](https://github.com/firejune/rigc/blob/main/gallery/ride/README.md)
1078
+ > — [`gallery/ride/README.md`](https://github.com/rig-c/core/blob/main/gallery/ride/README.md)
1079
1079
 
1080
1080
  📌 Two placement conveniences from the same paragraph, both of the form *put the
1081
1081
  bone where the numbers become trivial*: `root` is at the world origin, *"which is why
@@ -1104,7 +1104,7 @@ default failure mode**, because it drives bones rather than drawing anything.
1104
1104
  > `true`, so any IK timeline that did not restate it overwrote the constraint's value
1105
1105
  > for the whole animation, with the field still in the file and the gate green either
1106
1106
  > way."*
1107
- > — [`gallery/walk/README.md`](https://github.com/firejune/rigc/blob/main/gallery/walk/README.md)
1107
+ > — [`gallery/walk/README.md`](https://github.com/rig-c/core/blob/main/gallery/walk/README.md)
1108
1108
  > (rigc stamps the rig's value onto every emitted ik key)
1109
1109
 
1110
1110
  ⇒ **A structural fact stated in the rig can be overwritten by a per-key default in
@@ -1121,7 +1121,7 @@ all**, unanimously, and paid for it in a measure:
1121
1121
  > point at if asked why there are four rather than one … that measure is the price of
1122
1122
  > the rule, and it is worth paying rather than winning by guessing."*
1123
1123
  > — rung 8, first attempt
1124
- > ([`2026-08-23-rung8-1`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-rung8-1/LOOP.md) §13)
1124
+ > ([`2026-08-23-rung8-1`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-rung8-1/LOOP.md) §13)
1125
1125
 
1126
1126
  ⭐ **The cleanest demonstration that the rule costs something real and is still the
1127
1127
  right rule** is in that same run, which authored two rigs against two references:
@@ -1171,7 +1171,7 @@ the draw order"* — which is what lets one part sit in front of its own parent:
1171
1171
  > arrangement that satisfies both things the frames measure: the gun reads unoccluded
1172
1172
  > in `idle`, and the near leg covers it in `walk`."*
1173
1173
  > — spineboy attempt 2
1174
- > ([`2026-08-23-spineboy-2`](https://github.com/firejune/rigc/blob/main/bench/runs/2026-08-23-spineboy-2/README.md))
1174
+ > ([`2026-08-23-spineboy-2`](https://github.com/rig-c/core/blob/main/bench/runs/2026-08-23-spineboy-2/README.md))
1175
1175
 
1176
1176
  ⚖️ **But parentage is a real question with a real answer, and the evidence for it is
1177
1177
  smoothness.** Rung 4 modelled a chain's top as a *sibling* of the disc it hangs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/core",
3
- "version": "2.39.0",
3
+ "version": "2.40.0",
4
4
  "description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,10 +37,10 @@
37
37
  "author": "firejune",
38
38
  "repository": {
39
39
  "type": "git",
40
- "url": "https://github.com/firejune/rigc.git"
40
+ "url": "https://github.com/rig-c/core.git"
41
41
  },
42
- "homepage": "https://github.com/firejune/rigc#readme",
43
- "bugs": "https://github.com/firejune/rigc/issues",
42
+ "homepage": "https://github.com/rig-c/core#readme",
43
+ "bugs": "https://github.com/rig-c/core/issues",
44
44
  "keywords": [
45
45
  "spine",
46
46
  "spine2d",
@@ -109,25 +109,25 @@ model space, its UVs, its triangles — which is a mesh attachment with those th
109
109
  (AUTHORING §3.4, *Mesh attachment*); a region at the drawable's bounding-box centre
110
110
  keeps only the box. rigc reads none of those formats — FACE §11, the paragraph
111
111
  that opens *No Live2D file is read or written*
112
- ([FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md#11-non-goals--stated-so-nobody-proposes-them-as-gaps)) —
112
+ ([FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md#11-non-goals--stated-so-nobody-proposes-them-as-gaps)) —
113
113
  only the pictures they produce. The rule, the background those frames need and
114
114
  what a wrong one costs: AUTHORING §0, *When the source is a foreign player*.
115
115
 
116
116
  ## Which guide, for which need
117
117
 
118
- Read [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) first, whatever the need: the two
118
+ Read [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) first, whatever the need: the two
119
119
  spec files field by field, the emission rules, the loop, and the failure map. Then:
120
120
 
121
121
  | The request is… | Open | Skill |
122
122
  | --- | --- | --- |
123
- | a **skeleton** — how many bones, where each pivot sits, what hangs off what | [RIGGING.md](https://github.com/firejune/rigc/blob/main/docs/RIGGING.md) | `rigc-rigging` |
124
- | a **movement** — an idle, a loop, from this pose to that one | [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) | `rigc-motion` |
125
- | a **face** — a blink, a gaze, a head turn a few degrees off axis | [FACE.md](https://github.com/firejune/rigc/blob/main/docs/FACE.md) | `rigc-face` |
126
- | a **skeleton.json somebody else authored** — read it, repair it, extend it | [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) | `rigc-ingest` |
127
- | you are the **person operating** the agent rather than the agent | [PROMPTING.md](https://github.com/firejune/rigc/blob/main/docs/PROMPTING.md) | — |
123
+ | a **skeleton** — how many bones, where each pivot sits, what hangs off what | [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) | `rigc-rigging` |
124
+ | a **movement** — an idle, a loop, from this pose to that one | [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) | `rigc-motion` |
125
+ | a **face** — a blink, a gaze, a head turn a few degrees off axis | [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) | `rigc-face` |
126
+ | a **skeleton.json somebody else authored** — read it, repair it, extend it | [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) | `rigc-ingest` |
127
+ | you are the **person operating** the agent rather than the agent | [PROMPTING.md](https://github.com/rig-c/core/blob/main/docs/PROMPTING.md) | — |
128
128
 
129
129
  Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
130
130
  which is the copy that matches the rigc you run; the links go to the repository's
131
131
  `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
132
132
  Formats, the CLI reference and the licence chain:
133
- [README.md](https://github.com/firejune/rigc/blob/main/README.md), installed at `node_modules/rig-c/README.md`.
133
+ [README.md](https://github.com/rig-c/core/blob/main/README.md), installed at `node_modules/rig-c/README.md`.
@@ -9,7 +9,7 @@ compatibility: Requires Bun 1.2 or later and the npm package rig-c.
9
9
 
10
10
  Load this when the request is a **head rather than a body**: a drawn face that
11
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
12
+ is owned by [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md); this file says when to open it and what
13
13
  it will not do for you.
14
14
 
15
15
  ## Non-negotiables
@@ -40,16 +40,16 @@ differential audit and the three limits it does not lift.
40
40
 
41
41
  ## Read, in this order
42
42
 
43
- 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the `deform` timeline field by field
43
+ 1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the `deform` timeline field by field
44
44
  and what rigc refuses in it (§4), the failure map (§5–§6), the editor's
45
45
  conventions (§10).
46
- 2. [MOTION.md](https://github.com/firejune/rigc/blob/main/docs/MOTION.md) — timing, easing and the offset table (§3),
46
+ 2. [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) — timing, easing and the offset table (§3),
47
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
48
+ 3. [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) — the face's own geometry: the closed form every
49
49
  number in a turn comes from (§1), the hierarchy underneath (§3), what
50
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
51
+ 4. Then [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) for the hierarchy as a general rule
52
+ rather than this closed form, and [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the head
53
53
  arrived as a compiled skeleton.
54
54
 
55
55
  Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
@@ -10,7 +10,7 @@ compatibility: Requires Bun 1.2 or later and the npm package rig-c.
10
10
  Load this when what you were handed is **already a skeleton**: a `skeleton.json`
11
11
  with its `.atlas` and page images, and a request to understand it, answer a
12
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
13
+ [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md); this file says when to open it and what it will
14
14
  not do for you.
15
15
 
16
16
  ## Non-negotiables
@@ -61,14 +61,14 @@ thing to reach for; transcription by hand is what you fall back on for a constru
61
61
 
62
62
  ## Read, in this order
63
63
 
64
- 1. [INGEST.md](https://github.com/firejune/rigc/blob/main/docs/INGEST.md) — what every command will and will not do with
64
+ 1. [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) — what every command will and will not do with
65
65
  a foreign file (§0), `ingest` and transcription (§2), what each validator
66
66
  complaint means on an export (§3), and the re-pivot, rename and extend
67
67
  recipes (§4).
68
- 2. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the two spec files the transcription
68
+ 2. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the two spec files the transcription
69
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.
70
+ 3. Then [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) for why the re-pivot edit has the shape
71
+ it has, and [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) for the animation you are adding.
72
72
 
73
73
  Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
74
74
  which is the copy that matches the rigc you run; the links go to the repository's
@@ -10,7 +10,7 @@ compatibility: Requires Bun 1.2 or later and the npm package rig-c.
10
10
  Load this when the request is a **movement rather than a skeleton**: a sentence of
11
11
  intent, between zero and N pictures of what the movement passes through, and a
12
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
13
+ [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md); this file says when to open it and what it will
14
14
  not do for you.
15
15
 
16
16
  ## Non-negotiables
@@ -31,16 +31,16 @@ thing that judges a movement is a person's eye through `rigc vote` — MOTION §
31
31
 
32
32
  ## Read, in this order
33
33
 
34
- 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the motion spec field by field (§4),
34
+ 1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the motion spec field by field (§4),
35
35
  the failure map (§5–§6), reading reference frames (§8) and checking against
36
36
  them (§9), what the editor does when nobody tells it otherwise (§10), and
37
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
38
+ 2. [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) — the normal form every motion request
39
39
  reduces to (§0), timing, easing and the per-bone offset table (§3), and how to
40
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
41
+ 3. Then [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) if the skeleton itself is what you have
42
+ to decide; [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) if the movement is a blink, a gaze or a
43
+ head turn; [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the rig arrived as a compiled
44
44
  skeleton rather than loose parts.
45
45
 
46
46
  Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
@@ -9,7 +9,7 @@ compatibility: Requires Bun 1.2 or later and the npm package rig-c.
9
9
 
10
10
  Load this when the request is a **skeleton rather than a movement**: loose part
11
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
12
+ [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md); this file says when to open it and what it
13
13
  will not do for you.
14
14
 
15
15
  ## Non-negotiables
@@ -30,15 +30,15 @@ are named in RIGGING §11 — neither as a pass bar.
30
30
 
31
31
  ## Read, in this order
32
32
 
33
- 1. [AUTHORING.md](https://github.com/firejune/rigc/blob/main/docs/AUTHORING.md) — the rig spec field by field (§3),
33
+ 1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the rig spec field by field (§3),
34
34
  the failure map (§5–§6), reading a pose out of a picture (§8.1, §11, §12),
35
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
36
+ 2. [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) — the structure itself: what identifies a
37
37
  pivot and what moving one costs (§2–§3), gauges (§4), what a chain can reach
38
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
39
+ 3. Then [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) once the skeleton exists;
40
+ [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) if the figure is a head;
41
+ [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the skeleton was handed to you already
42
42
  compiled.
43
43
 
44
44
  Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A51, the body (issue #1351): the bones a rig declares to share an axis lie
3
+ * along it at the setup pose.
4
+ *
5
+ * For each `invariants.axes` entry the line is drawn through the setup-pose
6
+ * world origins of its two `through` bones, first to second, and every bone
7
+ * of its `bones` must have its setup-pose world x axis within `tolerance`
8
+ * degrees of that line — as a line, so a bone pointing the opposite way (a
9
+ * mirrored bone, a world scaleX of −1) lies along it too. Why the rule exists,
10
+ * and why it holds bones no animation keys, is above its `check` call in
11
+ * `validate()` and on `RigInvariants.axes` in `src/rig.ts`.
12
+ *
13
+ * Reads the declaration from the rig info and the pose from a fact
14
+ * (`../facts/setup_world.ts`), so the body is one function over spine-core's
15
+ * setup pose and rigc's core's. Every angle it prints is a degree in Spine's
16
+ * world (y up), counter-clockwise from +x.
17
+ *
18
+ * Links nothing from the runtime.
19
+ */
20
+ import type { Verdicts } from '../harness.ts';
21
+ import type { SetupBoneFrame, SetupWorldFacts } from '../facts/setup_world.ts';
22
+ import type { RigInfo } from '../../types.ts';
23
+
24
+ const CODE = 'A51_BONES_LIE_ALONG_THEIR_DECLARED_AXIS';
25
+
26
+ /** Degrees, to the four places every angle on this rule's lines is printed at. */
27
+ const deg = (v: number): string => `${v.toFixed(4)}°`;
28
+
29
+ /** How far apart two directions are as lines, in degrees: 0 to 90. */
30
+ export function lineGap(a: number, b: number): number {
31
+ const d = (((a - b) % 180) + 180) % 180;
32
+ return Math.min(d, 180 - d);
33
+ }
34
+
35
+ /** The world angle of a bone's x axis, in degrees — `atan2(c, a)`, the angle spine-core's `getWorldRotationX` reports. */
36
+ export function worldAxisDegrees(frame: SetupBoneFrame): number {
37
+ return (Math.atan2(frame.c, frame.a) * 180) / Math.PI;
38
+ }
39
+
40
+ export function a51BonesLieAlongTheirDeclaredAxis({ fail, skip }: Verdicts, facts: SetupWorldFacts, input: { rig?: RigInfo }): void {
41
+ const rig = input.rig;
42
+ if (!rig) return skip(CODE, 'no rig info (validating a bare directory)');
43
+ if (!rig.axes.length) return skip(CODE, `the rig "${rig.archetype}" declares no axes (invariants.axes)`);
44
+ /** A bone's frame, or the failure that says why there is none. */
45
+ const placed = (axis: string, bone: string): SetupBoneFrame | null => {
46
+ const frame = facts.frameOf(bone);
47
+ if (frame === null) {
48
+ fail(CODE, `axis "${axis}" names bone "${bone}", which the skeleton does not have`);
49
+ return null;
50
+ }
51
+ if (typeof frame === 'string') {
52
+ fail(CODE, `axis "${axis}": ${frame}, so it cannot be held to the axis`);
53
+ return null;
54
+ }
55
+ return frame;
56
+ };
57
+ for (const axis of rig.axes) {
58
+ const [from, to] = axis.through;
59
+ const a = placed(axis.name, from);
60
+ const b = placed(axis.name, to);
61
+ if (a === null || b === null) continue;
62
+ const dx = b.worldX - a.worldX;
63
+ const dy = b.worldY - a.worldY;
64
+ // Exactly one origin, and no threshold: any distance above 0 draws a line, and a threshold would be a number the rig did not state.
65
+ if (dx === 0 && dy === 0) {
66
+ fail(
67
+ CODE,
68
+ `axis "${axis.name}": "${from}" and "${to}" share the world origin (${a.worldX}, ${a.worldY}) at the setup pose, so they draw no line — ` +
69
+ 'name two bones whose origins differ',
70
+ );
71
+ continue;
72
+ }
73
+ const line = (Math.atan2(dy, dx) * 180) / Math.PI;
74
+ const head = `axis "${axis.name}" (through "${from}"→"${to}", ${deg(line)})`;
75
+ for (const bone of axis.bones) {
76
+ const frame = placed(axis.name, bone);
77
+ if (frame === null) continue;
78
+ if (frame.a === 0 && frame.c === 0) {
79
+ fail(CODE, `${head}: bone "${bone}" has no world x axis at the setup pose (the x column of its world matrix is 0, 0 — a scale of 0), so it lies along no line`);
80
+ continue;
81
+ }
82
+ const found = worldAxisDegrees(frame);
83
+ const off = lineGap(found, line);
84
+ if (off <= axis.tolerance) continue;
85
+ fail(
86
+ CODE,
87
+ `${head}: bone "${bone}" world x axis at ${deg(found)}, ${deg(off)} off, tolerance ${axis.tolerance}° — ` +
88
+ 'rotate the bone in the rig spec onto the line and counter-rotate its attachments so the setup pose stays put',
89
+ );
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Each bone's world frame at the setup pose (issue #1351) — what
3
+ * `A51_BONES_LIE_ALONG_THEIR_DECLARED_AXIS` reads: a bone's world origin, and
4
+ * the column of its world matrix its x axis points along.
5
+ *
6
+ * ⭐ **The pose is A50's**, on both suppliers: a fresh skeleton at the setup
7
+ * pose with constraints applied, no physics stepped and no skin set —
8
+ * `setupPose()` then `updateWorldTransform(Physics.none)` through spine-core
9
+ * (`spineSetupWorld` in `../../validate.ts`), `poseSetup` under no skin
10
+ * through rigc's core (`../model/setup_world.ts`). `tools/core_gate.ts` holds
11
+ * the core's setup world to spine-core's at tolerance 0, which is what lets the
12
+ * two print one line.
13
+ *
14
+ * A bone is answered lazily: a rig that declares no axis poses nothing.
15
+ *
16
+ * Links nothing from the runtime.
17
+ */
18
+
19
+ /** A bone's world frame at the setup pose: its origin, and its world x axis `(a, c)`. */
20
+ export interface SetupBoneFrame {
21
+ readonly worldX: number;
22
+ readonly worldY: number;
23
+ readonly a: number;
24
+ readonly c: number;
25
+ }
26
+
27
+ /** What A51 reads. */
28
+ export interface SetupWorldFacts {
29
+ /**
30
+ * The bone's frame at the setup pose; a sentence saying why the setup pose
31
+ * places it nowhere (inactive with no skin set, a setup pose the core does
32
+ * not pose); or `null` when the skeleton has no bone of that name.
33
+ */
34
+ frameOf(bone: string): SetupBoneFrame | string | null;
35
+ }
36
+
37
+ /** The sentence for a bone the setup pose leaves inactive — one spelling for both suppliers. */
38
+ export function inactiveAtSetup(bone: string): string {
39
+ return `bone "${bone}" is inactive with no skin set, so the setup pose places it nowhere`;
40
+ }
@@ -97,6 +97,7 @@ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
97
97
  A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
98
98
  A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP: 'renderer',
99
99
  A50_STAGE_BOX_IS_THE_STAGE: 'validity',
100
+ A51_BONES_LIE_ALONG_THEIR_DECLARED_AXIS: 'validity',
100
101
  };
101
102
 
102
103
  /** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `spine-html` every kind. */
@@ -75,6 +75,7 @@ const RIG_FIELDS: Readonly<Record<keyof RigInfo, Check>> = {
75
75
  deformMayFold: list(str),
76
76
  consumerDrivenMix: list(shape({ type: oneOf(['ik', 'transform']), constraint: str, why: str })),
77
77
  idleDrivesMeshes: nullable(str),
78
+ axes: list(shape({ name: str, through: list(str, 2), bones: list(str), tolerance: num })),
78
79
  basePlates: list(str),
79
80
  meshSlotBudget: nullable(num),
80
81
  meshTriangleBudget: nullable(num),