diff --git a/src/game/npc_avatar.gd b/src/game/npc_avatar.gd index 41d8752..96930cd 100644 --- a/src/game/npc_avatar.gd +++ b/src/game/npc_avatar.gd @@ -535,10 +535,14 @@ func _pose_bone(bone_name: String, angle: float, axis: Vector3) -> void: if not _bone_indices.has(bone_name): return var index: int = _bone_indices[bone_name] - # Bone pose rotation is the absolute local orientation, not an offset from - # rest; compose with the rest rotation so identity preserves the bind pose. + # The pose is rest-relative (global = parent * rest * pose), so the proxy's + # world-space swing axis must be expressed in the bone's rest-local frame. + # `rest * Quaternion(rest.inverse() * axis, angle)` swings around the world + # `axis`; for the old rig (rest = 180 deg) this reduces to the legacy + # `rest * Quaternion(axis, angle)`, so the 13-core fallback is unchanged. var rest := _skeleton.get_bone_rest(index).basis.get_rotation_quaternion() - _skeleton.set_bone_pose_rotation(index, rest * Quaternion(axis, angle)) + var local_axis := rest.inverse() * axis + _skeleton.set_bone_pose_rotation(index, rest * Quaternion(local_axis, angle)) func _build_body(npc_type: int) -> void: _rig = Node3D.new()