When compared to the _ready function, just instantiating the projectile and adding it as a child will properly make it spawn on the correct global_position.
I assume because the _ready() function runs before the instantiated projectile position was fully set as a child of the turret.
So this:
func _on_timer_timeout() -> void:
var shot = projectile.instantiate()
add_child(shot)
#shot.global_position = turret_top.global_position
Has the same effect independently of the comment in the last line. You can compare it by running these two versions, one on _ready(), one on _on_timer_timeout():
func _ready() -> void:
var shot = projectile.instantiate()
add_child(shot)
func _on_timer_timeout() -> void:
var shot = projectile.instantiate()
add_child(shot)
#shot.global_position = turret_top.global_position
Just to make sure this agrees with the expected behavior.
PS: To be clear, this is regarding the x/z position, the y position is still needed to be corrected by using the turret_top of course.