The other day, for whatever reason, I wanted to know how fast the Nintendo logo scrolls down the screen on a DMG. The boot ROM has been dumped since forever and this is a simple matter to investigate in BGB. In BGB select Gameboy as the system type in the system tab, check “bootroms enabled”, and click ok. Then reset the CPU. Now, assuming the boot ROM is in the specified location, you can debug it.
I added a breakpoint (debug, breakpoints) with condition (FF44)=90. This breakpoint triggers on every VBlank, ie when register FF44, LY, the LCD line counter, reaches value $90. This is a more certain way of triggering on VBlank than the interrupt vector for cases when the regular interrupt may not be triggered in time due to interrupts being disabled, or (as in this case) when the code doesn’t use interrupts at all.
By pressing F9 repeatedly and seeing how much the logo would move every time, it seemed that the logo moved 1 pixel every 2 frames. Or not. It actually alternates between waiting 2 and 3 frames between every progression. The code responsible for this is the following, at position $0060 in the boot ROM:
00:0060 ld e,$02 00:0062 ld c,$0C 00:0064 ldh a,[$FF44] 00:0066 cp a,$90 00:0068 jr nz,$0064 00:006A dec c 00:006B jr nz,$0064 00:006D dec e 00:006E jr nz,$0062
This is the wait for VBlank routine. We have three layers of loops here. Outermost, we have a countdown using the E register, to wait for two frames. The innermost loop (0064-0068) wait until LY=$90, which just like with the breakpoint above signifies the start of the VBlank period. Both straightforward mechanisms.
The interesting part is the dec c/jr nz,$0064 mechanism. On the surface, it seems to be added to prevent the outer loop from exiting too early. On the second iteration of the outer loop, LY would still be $90, and the loop would immediately fall through instead of waiting for another frame. The C loop iterates 12 ($C) times, but interestingly jumps through the inner loop as well. This means that LY may or may not stop being $90 before the middle loop ends, depending on the exact time that the loop is entered. In other words, sometimes the code gets stuck in the inner loop for an extra frame when C=1, and then the last dec C is executed, and the outer loop exits.
The animation jitters between 2 and 3 frames consumed between each pixel move, so the animation takes 25% longer time than a naïve reading of the code would suggest. (5 frames per 2 pixels vs 4 frames per 2 pixels.) Now the question is if this was intentional to make the animation go slightly slower, or just a bug. If it was just a bug, it would have been easy to fix by making the dec C loop jump back to 006A (the dec C instruction) and setting a higher initial value for C.
Bug or intentional slowdown of the animation? Would be interesting to know.