Quick answer: Add a SkeletonModifier3D child to the Skeleton3D. Override _process_modification to set bone poses every frame.
You write a one-shot bone override for procedural head-look. Animation overwrites it the next frame. Set in _ready — lost on reparent.
The Symptom
Bone snaps back to its bind pose or animation curve as soon as something else updates the skeleton.
The Fix
# LookAtModifier (extends SkeletonModifier3D)
extends SkeletonModifier3D
@export var bone_name: String = "Head"
@export var target: Node3D
func _process_modification():
var sk = get_skeleton()
var idx = sk.find_bone(bone_name)
if idx == -1 or target == null: return
var rest = sk.get_bone_global_rest(idx)
var look = (target.global_position - sk.global_position * rest.origin).normalized()
var rot = Quaternion(Vector3.FORWARD, look)
sk.set_bone_pose_rotation(idx, rot)
Modifier runs after animation each frame. Override sticks because it’s reapplied every tick, by design.
Verifying
Move the target node. Head bone tracks. Pause animation player — head still tracks (modifier runs independently of AnimationPlayer).
“Modifier reapplies. Override stays. Animation plays underneath.”
Related Issues
For tween signal, see tween signal. For TextMesh3D, see TextMesh3D.
Modifier stays applied. Bone holds.