341. Increment vertical vram addr

Increment the vertical component of current rendering address v.

Lesson 341 of 356 · tests/chapter_13_scrolling/test_341_increment_vertical_vram_addr.py

File to update

emulator/ppu/ppu.py

References

https://www.nesdev.org/wiki/PPU_scrolling#Y_increment
https://www.nesdev.org/wiki/PPU_scrolling#Wrapping_around

Why this step exists

At dot 256, the PPU advances the rendering address by one pixel row. Unlike the horizontal increment, vertical movement first advances fine Y inside the current 8-pixel tile:

yyy NN YYYYY XXXXX
+++    +++++
 |       +---- coarse Y: bits 5-9
 +------------ fine Y: bits 12-14

vertical nametable: bit 11

Fine-Y behavior

0 -> 1 -> 2 -> ... -> 7

When fine Y is already 7, it wraps to 0 and coarse Y advances.

Coarse-Y behavior after fine-Y wrapping

coarse Y 0-28 -> increment by one
coarse Y 29   -> 0 and toggle vertical nametable
coarse Y 30   -> 31 through the normal increment branch
coarse Y 31   -> 0 without toggling vertical nametable

Rows 0-29 are the 30 visible tile rows of a nametable. Values 30 and 31 are not normal visible rows, but they can exist because coarse Y is a five-bit field and CPU address operations can load those values.

Why add $1000 for a normal fine-Y increment? Fine Y begins at bit 12, so adding 1 << 12 increments that packed field by one. This does not conceptually mean moving 4096 bytes through PPU memory.

Important invariants

  • horizontal coarse X and nametable selection remain unchanged
  • unrelated internal address bits remain unchanged
  • vertical nametable toggles only for row-29 wrapping
  • the helper is pure and does not mutate PPU

Common misconception

Vertical increment is not simply symmetrical with horizontal increment. Horizontal movement advances one tile column; vertical movement advances one fine pixel row and only sometimes advances the coarse tile row.

Out of scope

  • calling this helper at dot 256
  • vertical t-to-v copying
  • pre-render timing
  • scanline viewport recording
  • framebuffer rendering

Complete example implementation

# emulator/ppu/ppu.py

# --- NEW BLOCK: PURE VERTICAL v INCREMENT ---
def increment_vertical_vram_addr(vram_addr: int) -> int:
    fine_y = (vram_addr >> 12) & 0b111

    if fine_y < 7:
        return vram_addr + (1 << 12)

    vram_addr &= ~0b111_00_00000_00000
    coarse_y = (vram_addr >> 5) & 0b1_1111

    if coarse_y == 29:
        coarse_y = 0
        vram_addr ^= 0b000_10_00000_00000
    elif coarse_y == 31:
        coarse_y = 0
    else:
        coarse_y += 1

    return (
        (vram_addr & ~0b000_00_11111_00000)
        | (coarse_y << 5)
    )

Run this lesson

uv run pytest tests/chapter_13_scrolling/test_341_increment_vertical_vram_addr.py -v