To set the jumps equal in speed, I modify the impulse dictionary condition in the enter() function of the air state. Rather than making it a += assignment, I simply make it into an = assignment, instead. I also preface it with resetting the vertical velocity to zero.
Jumping a second time requires some variable tracking. I'll make an export variable in the air state for specifying additional jumps as well as a jump_count variable that ticks down to zero. Under unhandled_input(), I created this addition to the function:
if event.is_action_pressed("jump") and not owner.is_on_floor() and jump_count > 0:
jump_count -= 1
move.velocity.y = 0
move.velocity.y = calculate_jump_velocity( move.jump_impulse ).y
So the extra challenge is that "the player is able to go up a little too fast," which is later specified as splitting the maximum vertical speed between the maximum jump speed and maximum fall speed. I'm admittedly a little confused (I'm sure the solution overview will clear things up) because the max_speed vector is what is limiting the speed of a jump_impulse that has been increased too much. I went ahead and modified the calculate_velocity() function to clamp vertical speed between -jump_impulse (I made a new argument and modified the calls in Air and Move) and max_speed.y, but doing this allows the player to specify any jump speed without limiting a jump height. So I wonder if the challenge is maybe that I'm supposed to specify a height and modify speeds accordingly? In other words, maybe the challenge should have two unique accelerations, one for x axis and one for y axis. I will end the challenge here but note just how powerful this implementation of the finite state machine is.
I think the observably biggest advantage that this system offers is that the finite state machine itself can have its structure modified in the game. Like, as you acquire a power-up, for example, a signal could add new content to save data and basically whenever you load the game, it loads the finite state machine but with new nodes to further extend the capabilities. So you could theoretically, like, effectively have an, idk, "modular" state machine, correct?
Another great idea for an exercise challenge would be the implementation of "Coyote Space." It's a common platformer concept involving user experience; a lot of players often jump a bit too late when trying to jump just off of the edge of a platform. So modifying the conditions in which the run state transitions to the air state (I personally would probably add a minimum fall speed condition) might be a good way to go about that, I think. With my implementation, here, it can be safely added without having to tangle so much with unnecessary jump count issues.