fix(weapons): orient the support hand by frame, not by arc plus a twist

The support hand was upside down on the handguard. Its rotation was built
as a shortest arc from the hand's forearm line to the barrel, plus a
constant 0.5 rad twist. A shortest arc is the MINIMAL rotation between two
directions and says nothing at all about roll, so the entire roll of that
hand came from the constant — and a constant is right only for the one rig
it was tuned against.

Orienting a hand onto something it grips is a frame-to-frame problem, and
saying it that way leaves nothing free to guess. A hand wrapping a cylinder
has its curl axis along that cylinder, or the fingers close across the
handguard rather than around it; and its palm faces the cylinder, which for
a hand supporting from underneath means up. The third axis falls out of the
other two. Map the hand's rest anatomical frame onto that target and the
roll is determined rather than chosen.

The frame is the same one the finger curl already uses — along, palm, curl
— now stored whole instead of just its curl axis.

Verified by render on the mannequin (Rigify names, 0.524 m arm) and Kiyoko
(VRoid names, 0.470 m): fingers wrap the handguard from below and over the
top, stock at the shoulder, arms not crossing, consistent across idle, ADS
and run. L_HAND_TWIST is deleted rather than retuned — it was the bug.

Smoke 0 failures.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-26 19:50:25 -04:00
co-authored by Claude Opus 5
parent daed7ab980
commit 2a6b321984
2 changed files with 68 additions and 17 deletions
@@ -225,10 +225,33 @@ Blender-export `IndexFinger1_L`. Segments are ordered by DEPTH BELOW THE HAND,
not by the number in the name — the numbering is not consistent between families,
but the hierarchy always runs knuckle to fingertip.
**The support hand came out upside down**, because its orientation was built as
a shortest arc plus a constant twist: align the hand's forearm line to the barrel
(`Quaternion(fa_rest_dir, aim_dir)`), then add 0.5 rad of roll. A shortest arc
says NOTHING about roll — it is the minimal rotation between two directions — so
the entire roll came from that constant, and a constant is right only for the rig
it was tuned on.
Orienting a hand onto something it grips is a FRAME-TO-FRAME problem, and framing
it that way leaves nothing free to guess:
```
curl axis must lie along the object's axis, or the fingers close ACROSS the
handguard instead of around it
palm must face the object — up, for a hand supporting from underneath
along falls out of the other two (palm x curl)
```
Map the hand's rest anatomical frame onto that target and the roll is determined,
not chosen. Verified on both the Rigify-named mannequin and the VRoid-named
Kiyoko: fingers wrap the handguard from below, over the top.
**Rule:** anything expressed as a constant in a rig's local frame — a mount
rotation, a grip offset, a curl axis, a weapon size — is a guess about one
skeleton. Derive it from the skeleton, or hand it to a solver that already knows
the answer.
rotation, a grip offset, a curl axis, a weapon size, a wrist twist — is a guess
about one skeleton. Derive it from the skeleton, or hand it to a solver that
already knows the answer. And when a rotation needs a specific ROLL, never build
it from a shortest arc: that operator has no opinion about roll, so whatever you
add afterwards is doing all the work.
## 6. Some sources are not salvageable, and the gate should say so