Ah, the threads of our pasts return to haunt us!
This post actually has less to do with 6502 ASM and more to do with just understanding one of the binary functions that has been boggling me ever since I started studying NES coding. I never took the time to actually try to understand the purpose of bitwise rotations (ROL and ROR). A bitwise shift is easy enough to understand: shift the bits left and you multiply the value by a power of 2; shift the bits right and you divide the value by a power of 2; shift four times and you move nybbles around. But what is the point of a bitwise rotation and why is such a function not included in Game Maker?
Over time I've come to realize the difference between 8-bit and 16-bit programming. There isn't much of one: some actions take twice as long in 8-bit than in 16-bit because numbers larger than 255 take up more bits than can be handled in a single call. While working with CV3's code, I noticed certain values seemed to essentially be 16-bit values. For example, RAM offsets $0053 and $0054 are the coordinates of the left edge of the view in the room with the upper byte written last; in other words, although it appears in the RAM as $53$54, it is meant to be read as $54$53. A combined value of $82$01 can thus be interpreted as $0182. Yes, I just wrote two 8-bit values as one 16-bit value. By the way, that number would translate to the left edge of the view is at x:386, which would mean Trevor was at x:514 in the room.
A problem arises when working with 16-bit values in an 8-bit environment, namely how to manipulate both bytes at the same time. Consider a bitwise shift to the left. You can't simply shift both bytes equally:
Intended 16-bit LSR:
$0182 << 4 = $1820
False 8-bit LSR:
[$01$82] << 4 = $10$20
How do we get $18$20 from $01$82? The answer is with a rotation. Bitwise shifts on 8-bit and 16-bit hardware set the carry bit of the status byte depending on if the bit that gets dropped during a shift was set or not. Here's a refresher on how bitwise shifts work:
In a logical shift to the right (LSR), the lowest bit is dropped. The highest bit will be void and gets filled in with a 0. Thus 1001>>1 creates X100 (1); X is replaced with 0 and (1) is transferred to the carry bit. In an arithmetic shift to the left (ASL), the highest bit is dropped and the lowest bit will be void. Thus 1001<<1 creates (1) 001X; X is replaced with 0 and (1) is transferred to the carry bit.
Now that's out of the way, a rotation is simply a shift, but instead of filling the void with 0, it fills the void with the carry bit. So let's look at that value from before, but this time as bits.
0000 0001 1000 0010
$01 $82
The byte you shift first depends on where you are shifting to. This could be confusing on the NES because of the reverse order, so it helps to write it out in 16-bit order as I did here. If you are shifting to the left, you start with the lowest byte ($82); if you are shifting to the right, you start with the highest byte ($01). In this example we want to shift to the left 4 times, so we start with the lowest byte.
0000 0001 (1) 0000 0100
We now perform a rotation in the same direction on the other byte.
0000 001(1) 0000 0100
Do this three more times per our previous example.
0000 0011 (0) 0000 1000
0000 011(0) 0000 1000
0000 0110 (0) 0001 0000
0000 110(0) 0001 0000
0000 1100 (0) 0010 0000
0001 100(0) 0010 0000 ($18$20)
As you can see, that required twice as many steps as a simple 16-bit shift to the left. That's how 16-bit systems are faster than 8-bit systems, even though they can seem to run just as slowly in a poorly programmed game. Game Maker already handles enough bytes, so it has no need for a rotation function.
What this means for me is I can possibly now go back and rewrite my password generator so it's twice as fast and possibly rewrite some code in my current engine to be faster. I've already converted hspd and vspd to 16-bit values, now I just have to work on other 16-bit values (things like score and time I've been handling in 32-bit from the get-go).