Battle State Machine Keeps Rapidly Switching Between Two States When it Shouldn't

I’m having an issue with my State Machine for my battle system… It is constantly swapping between the state_upkeep and the state_selecting. The StateMachine is a node with children nodes underneath it for each state. Also when I do press a move it doesn’t move into state_executing it just keeps going back and forth between state_upkeep and state_selecting.

Here is my StateMachine code:

class_name StateMachine extends Node

var current_state: MachineState
var states: Dictionary = {}

@export var initial_state: MachineState

func _ready():
	# Get all child states
	for child in get_children():
		if child is MachineState:
			states[child.name.to_lower()] = child
			child.state_machine = self
	
	# Set initial state to first one
	current_state = states[initial_state.name.to_lower()]
	current_state.enter()

func _process(delta: float):
	if current_state:
		current_state.update(delta)

func _physics_process(delta: float):
	if current_state:
		current_state.physics_update(delta)

func _input(event: InputEvent):
	if current_state:
		current_state.handle_input(event)

func change_state(state_name: String):
	if state_name.to_lower() in states:
		if current_state:
			current_state.exit()
		
		current_state = states[state_name.to_lower()]
		current_state.enter()
	else:
		print("State not found: ", state_name)

This is my state_upkeep code:

extends MachineState

@export var element_box: HBoxContainer

#Temporary storage for elemental points gained this upkeep
var wood_gained: int
var fire_gained: int
var earth_gained: int
var metal_gained: int
var water_gained: int

func enter() -> void:
	print("Upkeep started")
	#Reset to 0
	wood_gained = 0
	fire_gained = 0
	earth_gained = 0
	metal_gained = 0
	water_gained = 0
	#Generate some elemental points
	generate_elemental_points()
	# Update UI
	if element_box:
		element_box.update_points()
	#Change state to selecting state
	state_machine.change_state("state_selecting")

func generate_elemental_points() -> void:
	#Make sure everything gets at least one
	wood_gained += 1
	fire_gained += 1
	earth_gained += 1
	metal_gained += 1
	water_gained += 1
	
	#Make some get extra at random
	var rng = RandomNumberGenerator.new()
	rng.randomize()
	var num_extra_boosts = rng.randi_range(1,3)
	for i in range(num_extra_boosts):
		var random_element_index = rng.randi_range(0,4)
		match random_element_index:
			0: wood_gained += rng.randi_range(1,2)
			1: fire_gained += rng.randi_range(1,2)
			2: earth_gained += rng.randi_range(1,2)
			3: metal_gained += rng.randi_range(1,2)
			4: water_gained += rng.randi_range(1,2)
	
	#Add points to the player data
	PlayerManager.current_player_data.wood_points += wood_gained
	PlayerManager.current_player_data.fire_points += fire_gained
	PlayerManager.current_player_data.earth_points += earth_gained
	PlayerManager.current_player_data.metal_points += metal_gained
	PlayerManager.current_player_data.water_points += water_gained

This is my state_selecting code:

extends MachineState

@export var battle_ref: Battle

func enter() -> void:
	print("picking moves")
	battle_ref.enable_menu()

This is my main battle code:

class_name Battle extends CanvasLayer

#Access to State Machine
@onready var state_machine: StateMachine = $StateMachine
#Background
@onready var background: TextureRect = $SubViewportContainer/SubViewport/Background
#Elements
@onready var elements_box: HBoxContainer = $SubViewportContainer/SubViewport/MC/PlayerMainMenu/HBox/NPR/MC/HBox/ElementsBox
#Menus
@onready var player_main_menu: Control = $SubViewportContainer/SubViewport/MC/PlayerMainMenu
@onready var player_move_menu: Control = $SubViewportContainer/SubViewport/MC/PlayerMoveMenu
#Info
@onready var info: Control = $SubViewportContainer/SubViewport/MC/Info
#Track choices
var choices: Array = []
#Myxlings
static var player_myxlings: Array[MyxlingBase]
static var opponent_myxlings: Array[MyxlingBase]
#Self reference
static var active_battle_reference: Battle

@onready var state_selecting: Node = $StateMachine/state_selecting

@export var fight_button: Button
@export var item_button: Button
@export var defend_button: Button
@export var run_button: Button

func _ready() -> void:
	active_battle_reference = self
	#Make sure move menu is hidden
	player_move_menu.hide()
	info.update()
	#Disable the menu buttons
	disable_menu()
	#Connect to the buttons
	fight_button.pressed.connect(on_fight_pressed)
	item_button.pressed.connect(on_item_pressed)
	run_button.pressed.connect(on_run_pressed)
	
func enable_menu() -> void:
	fight_button.disabled = false
	item_button.disabled = false
	defend_button.disabled = false
	run_button.disabled = false
	
func disable_menu() -> void:
	fight_button.disabled = true
	item_button.disabled = true
	defend_button.disabled = true
	run_button.disabled = true
	
func on_move_selected(move) -> void:
	print(move)
	state_machine.change_state("state_executing")

func on_fight_pressed():
	print("fight")
	player_move_menu.show()
	
func on_item_pressed() -> void:
	print("items")
	
func on_run_pressed() -> void:
	print("run")

Nothing in the code you posted ever calls change_state("state_upkeep")

There is a state_intro that gets called first and that changes it to state_upkeep that state functions correctly and switches correctly. It’s only when changing from state_upkeep to state_selecting that it starts having issues.

Well if thing switches constantly to state_upkeep then that call must be made somewhere all the time. Start by determining where that call is.

Are you running any autoloads?

There are autoloads but none of them change states in any of my state machines I have. I have stepped through the code and it seems to be working properly. It loops through the physics update for the state_selecting, then all of a sudden after 8 or so cycles it suddenly jumps to the _process function in the state_intro.

This is my state_intro code

extends MachineState

const INTRO_ANIMATION_DURATION: float = 1.5
var current_intro_timer: float = 0.0

func enter() -> void:
	print("Entering intro state")
	
func _process(delta: float) -> void:
	current_intro_timer += delta
	if current_intro_timer >= INTRO_ANIMATION_DURATION:
		state_machine.change_state("state_upkeep")

Why do you have a _process() function in a state script? By the looks of your StateMachine class, states should implement update() instead.

Because evidently I am an imbecile and can’t remember how I structured my own code… I changed that and now it is almost working. The rapid switching has been solved, its a whole different issue now.