328. Ppuctrl temp nametable bits
Copy PPUCTRL base-nametable selection into temp_vram_addr.
Lesson 328 of 356 · tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py
File to update
emulator/ppu/ppu.pyReference documentation
https://www.nesdev.org/wiki/PPU_scrolling#$2000_(PPUCTRL)_write
https://www.nesdev.org/wiki/PPU_registersWhy this step exists
The PPU scrolling address is assembled from several CPU-visible register writes. The project already handles most of the $2005 PPUSCROLL pieces:
coarse X -> temp_vram_addr bits 0-4
coarse Y -> temp_vram_addr bits 5-9
fine Y -> temp_vram_addr bits 12-14
fine X -> separate fine_x fieldThe missing piece is the logical base-nametable selection
PPUCTRL bits 0-1 -> temp_vram_addr bits 10-11Without this connection, temp_vram_addr behaves as if scrolling always starts from logical nametable 0 ($2000), even when game software selected $2400, $2800, or $2C00. A future viewport renderer would therefore cross the wrong logical boundary.
Internal scrolling layout
temp_vram_addr (t): yyy NN YYYYY XXXXX
XXXXX -> coarse X tile position
YYYYY -> coarse Y tile position
NN -> logical nametable selection
yyy -> fine Y pixel position
fine_x (x) is stored separatelyPPUCTRL mapping
bits 0-1 = 00 -> logical nametable $2000
bits 0-1 = 01 -> logical nametable $2400
bits 0-1 = 10 -> logical nametable $2800
bits 0-1 = 11 -> logical nametable $2C00Suggested implementation
# emulator/ppu/ppu.py
case 0x2000:
self.ctrl = value
# --- NEW LINE: COPY BASE NAMETABLE INTO temp_vram_addr: ...GH.. ........ <- value: ......GH
self.temp_vram_addr = (self.temp_vram_addr & 0b1111_0011_1111_1111) | ((value & CTRL_BASE_NAMETABLE_MASK) << 10)
# --- END NEW LINE ---Why clear bits 10-11 first? A game may change from any logical nametable to any other logical nametable. Using only OR would set new bits but could not clear old bits. The clear-then-insert operation replaces the previous selection while preserving coarse X, coarse Y, and fine Y.
Why not use the old scroll field? $2005 receives two writes, but the compatibility scroll field stores only the most recent byte. temp_vram_addr plus fine_x preserve the complete hardware-style state.
Important distinction
This step records logical nametable selection. Cartridge mirroring then maps that logical selection onto physical nametable RAM. Neither operation moves the visible viewport by itself; viewport extraction/rendering comes in later steps.
Out of scope
- deriving viewport pixel X/Y
- rendering adjacent nametables
- transferring t into v at exact hardware dots
- changing PpuBus mirroring
- commercial ROM fixtures
Run this lesson
uv run pytest tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py -v