Area3D overlaps_body() delayed calculation

Godot Version

Godot 4.4

Question

I am working on a turn-based sumo game. If the player or AI character exit the ring, I want it to declare a winner and end the game. The issue I am encountering is that when a character is pushed out of the ring, the win_condition is not activated (lose_boundary.get_overlapping_bodies() still shows both characters). However, upon the next turn, the game is won/lost.

What I have tested so far:

  • Are my collision shapes and Area3D shapes flawed?
    • I am confident this is not the case. For example, when the AI is clearly outside the boundary and I go for another turn, the outcome results in my player character moving position, but not the AI. Nonetheless, it then triggers the game over. From this, I am fairly confident the issue is timing.
  • Debugging I have tried
    • call_deferred(“check_win_conditions”)
    • lose_boundary.force_update_transform()
    • await get_tree().physics_frame
    • await get_tree().process_frame
func process_turn():
	if not current_state == TurnState.PROCESSING_TURN:
		current_state = TurnState.PROCESSING_TURN

	print("Processing turn...")

	# Ensure both wrestlers have selected a move
	if player_rikishi.current_move == "" or ai_rikishi.current_move == "":
		push_warning("Moves not selected for both Rikishi. Aborting turn.")
		reset_turn() # Go back to waiting state
		return

	# Calculate the outcome based on moves
	calculate_outcome(player_rikishi.current_move, ai_rikishi.current_move)

	# Reset moves for the next turn
	player_rikishi.reset_move()
	ai_rikishi.reset_move()

	# Check for win/loss conditions 
	check_win_conditions()

	# Go back to waiting for input for the next turn
	reset_turn()
	print("Turn complete. Waiting for next input.")

# --- Win condition checks ---
##TODO: Not triggering correctly
func check_win_conditions():
	## Check if players are still valid nodes
	if !is_instance_valid(player_rikishi) or !is_instance_valid(ai_rikishi):
		print("Error: One or more rikishi are invalid")
		return
	
	## A player is out if they're not inside the dohyo area
	print(lose_boundary.get_overlapping_bodies())
	var player_out = !lose_boundary.overlaps_body(player_rikishi)
	var ai_out = !lose_boundary.overlaps_body(ai_rikishi)
	
	if ai_out:
		print("AI is outside the dohyo!")
		game_over(true)
	
	if player_out:
		print("Player is outside the dohyo!")
		game_over(false)

Below is how my wrestlers are moved:

func rotate_opponent_position(thrower: Node3D, opponent: Node3D):
	var direction = opponent.global_transform.origin - thrower.global_transform.origin
	var rotation_axis = Vector3.UP # Set Y-axis as basis of rotation
	var clockwise_or_countercw = (randi() & 2) - 1 # Randomly determine if the rotation is clockwise (1) or counterclockwise (-1)
	var rotated_direction = direction.rotated(rotation_axis, deg_to_rad(throw_rotation * clockwise_or_countercw))
	
	opponent.global_transform.origin = thrower.global_transform.origin + rotated_direction
	
func push_calc(rikishi1: Node3D, rikishi2: Node3D, push_distance: float):
	var direction = (rikishi2.global_transform.origin - rikishi1.global_transform.origin).normalized()
	rikishi1.global_transform.origin += direction * push_distance
	rikishi2.global_transform.origin += direction * push_distance
	

I am a beginner so I imagine there may be a better way to do this, so any advice is appreciated. My guess is that maybe by instantly moving the positions happens so quickly there is no time for the calculation to catch up. However, it seems that adding delays hasn’t worked (maybe I need more).

As explained in the Area3D.get_overlapping_bodies() documentation:

For performance reasons (collisions are all processed at the same time) the list of overlapping bodies is modified once during the physics step, not immediately after objects are moved. Consider using signals instead.

The list is only updated once per frame. You’ll need to use the Area3D.body_entered or Area3D.body_exited signals instead.

Thank you! I read that documentation to the best of my ability to understand it. That is why I tried the below during troubleshooting to no avail.

But per your advice, setting up the signal for body_exited has resolved the issue.