Alright, after doing some testing around, the way you’re wanting to do things will put you into quadrant two and three. You will need to use functions like Atan2, in order to manage things properly; without quaternions or diving into the imaginary number realm.
An easier way to handle all of this might actually be to just have a conditional set up on the character or controller so that if it detects that you are in water AND under it (not floating on the top), then it will change the behavior of the controls and change the model’s root rotation(could just be done with animations). Due to the character mesh’s “up” vector, as in the direction pointing from their feet to their head, and the controller’s forward vector being offset 90 degrees from each other, you could just change it so that when you’re underwater, the character’s root angle interpolates to the controller’s forward vector. This would make it so that you don’t have to go into Q2 or Q3 and deal with the weird math involved there. This way it would be easy to do the Q1 and Q4 testing; to figure out which one you’re in. The roll will be zero (use a Nearly Equal node because sometimes floats round weird and it will be like 0.00000000001) if you’re in Q1 and it will be non-zero if you’re in Q4; which you can use a nearly equal node set to compare to zero with a NOT attached to the end of.
After that, use a similar clamping method like I showed before. It will function identically to it, except now the model will be rotated 90 degrees to match the direction. Oh and you’d probably want to work out a way to rotate the collision model or just use a separate one for when you’re swimming and turn off the primary one; switching back to the normal one when you get out of the water.