Turns out my scrolling was completely correctly implemented *aside* from the loading seam at the top being one tile too high, which explains why it was so hard to find anything wrong when I was looking for the problem in the wrong places. Now it works!