5 / (PI/16 / 4*PI) gives 320 bullets over 5 seconds, meaning 64 bullets per seconds. This means you are creating a timer that lasts less than 1/60th of a second, which is the default frequency of the physics engine. So you are asking the engine to create a timer, process it and react to it in less time than one of its physics frames. Since at the end of the first frame, you'll still have a tiny bit of time left on your timer, the "timeout" signal will only be processed on the next physics frame. Without analyzing the code further, you can see how the total time will be about double what you expected.
ex:
your current interval is 0.015625 sec. A physics frame is processed every 0.016667 sec. You start your timer in a physics frame. On the next physics frame, your timer still has 0.016667 - 0.015625 = 0.001042 sec left. On the next physics frame, you finally have a value below or equal to 0. Meaning you expected the interval to last 0.015625, but it took 2 full physics frames to process so it actually lasted 2 * 0.016667 = 0.033334 sec.
5 * (0.033334 / 0.015625) = 10.66688 sec for the total time. I'm not sure where the other 3.33312 sec to get 14 sec come from, but it's probably some more processing time in the engine.
In brief, it's a precision error since your interval is so small. You have a couple options. You could increase the physics engine's framerate to increase its precision. Or you could batch some bullets together if the interval is low enough.
ex:
var MIN_INTERVAL = 0.02 # at least higher than 1/60th to avoid your problem.
# Would need to be tuned by playtesting to find a value that feels good to play.
func shoot_spiral_V03(time, angle_step, angular_velocity):
var shot_interval = angle_step/float(angular_velocity)
var bullets = time/shot_interval
var time_t = 0
var angle = 0
print(OS.get_time())
var batch_size = 0
for counter in range(0,bullets,1):
batch_size += 1
if batch_size * shot_interval > MIN_INTERVAL:
for batch_index in range(0, batch_size):
angle = (counter + batch_index) * angle_step
emit_signal("shoot_signal", position, Vector2(1,1).rotated(angle), false)
batch_size = 0
yield(get_tree().create_timer(batch_size * shot_interval, false), "timeout")
time_t += shot_interval
print(time_t)
print(OS.get_time())
The inner loop I added makes the shoot_spiral_V03
method's body quite a bit more ugly and unreadable. It would probably be wise to extract it to another function.
I haven't tested it so I'm not sure you'll get exactly 5 seconds, but you should have an actual time closer to what you expect.