I injected some logging into my PPU implementation. It records $2005 and $2006 writes along with the scanline, scanline cycle and w, t, v and x registers before and after the write. Also, by monitoring the pixel colors, it can differentiate between a good and bad frame.
Here's a good frame (scrollY = 203 at the split):
Code:
[$2005] = 00, scanline = 192, cycle = 287 w_before = false, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = true, t_after = 0000, v_after = 1300, x_after = 00, scrollY_after = 193
[$2005] = 00, scanline = 192, cycle = 305 w_before = true, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = false, t_after = 0000, v_after = 1300, x_after = 00, scrollY_after = 193
[$2006] = 3F, scanline = 192, cycle = 323 w_before = false, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = true, t_after = 3F00, v_after = 1300, x_after = 00, scrollY_after = 193
[$2006] = 01, scanline = 193, cycle = 0 w_before = true, t_before = 3F00, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = false, t_after = 3F01, v_after = 3F01, x_after = 00, scrollY_after = 435
[$2006] = 27, scanline = 193, cycle = 186 w_before = false, t_before = 3F01, v_before = 3F08, x_before = 00, scrollY_before = 435 w_after = true, t_after = 2701, v_after = 3F08, x_after = 00, scrollY_after = 435
[$2006] = 00, scanline = 193, cycle = 204 w_before = true, t_before = 2701, v_before = 3F08, x_before = 00, scrollY_before = 435 w_after = false, t_after = 2700, v_after = 2700, x_after = 00, scrollY_after = 194
Here's a bad frame (scrollY = 202 at the split):
Code:
[$2005] = 00, scanline = 192, cycle = 300 w_before = false, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = true, t_after = 0000, v_after = 1300, x_after = 00, scrollY_after = 193
[$2005] = 00, scanline = 192, cycle = 318 w_before = true, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = false, t_after = 0000, v_after = 1300, x_after = 00, scrollY_after = 193
[$2006] = 3F, scanline = 192, cycle = 336 w_before = false, t_before = 0000, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = true, t_after = 3F00, v_after = 1300, x_after = 00, scrollY_after = 193
[$2006] = 01, scanline = 193, cycle = 13 w_before = true, t_before = 3F00, v_before = 1300, x_before = 00, scrollY_before = 193 w_after = false, t_after = 3F01, v_after = 3F01, x_after = 00, scrollY_after = 435
[$2006] = 27, scanline = 193, cycle = 199 w_before = false, t_before = 3F01, v_before = 3F08, x_before = 00, scrollY_before = 435 w_after = true, t_after = 2701, v_after = 3F08, x_after = 00, scrollY_after = 435
[$2006] = 00, scanline = 193, cycle = 217 w_before = true, t_before = 2701, v_before = 3F08, x_before = 00, scrollY_before = 435 w_after = false, t_after = 2700, v_after = 2700, x_after = 00, scrollY_after = 194
I cannot detect any race conditions based on those values. Perhaps someone else will notice something. Thanks for looking.
Edit: Further logging revealed the core issue. The game disables rendering, it does the writes above and then it enables rendering. There is a timing issue where rendering is enabled just prior or just after the Y-increment, which causes the shaking. The Y-increment doesn't get called when rendering is disabled. I'll study this further.