347. Scanline horizontal pair
Select the horizontal logical nametable pair for one recorded scanline.
Lesson 347 of 356 · tests/chapter_13_scrolling/test_347_scanline_horizontal_pair.py
File to update
emulator/rendering/ppu_background_renderer.pyReferences
https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_nametablesWhy this step exists
The completed timed frame stores one effective vram_addr and fine X value for each visible scanline. Before a later renderer can copy that scanline's pixels, it must know which two horizontally adjacent logical nametables form the source pair.
The nametable-select fields inside vram_addr are:
bit 10: horizontal nametable position inside a 512-pixel-wide pair
bit 11: vertical nametable row that selects the pairPair selection therefore uses only bit 11
bit 11 clear: ($2000, $2400)
bit 11 set: ($2800, $2C00)Intuitive model
upper logical row: $2000 | $2400
lower logical row: $2800 | $2C00The helper chooses one complete row. A later step will use bit 10, coarse X, and fine X to choose the horizontal pixel position within that row.
Important invariants
- only vram_addr bit 11 affects the selected pair
- bit 10 does not change the pair
- coarse X, coarse Y, fine Y, and fine X do not change the pair
- returned values are logical PPU nametable addresses
- cartridge mirroring remains PpuBus behavior
- the helper is pure and performs no rendering or memory access
Common misconception
Bit 10 does not select between the upper and lower horizontal pairs. It identifies the left or right horizontal nametable within the pair and will become part of the viewport-X calculation in the next lesson.
Out of scope
- decoding horizontal viewport X
- reading nametable or pattern-table bytes
- applying cartridge mirroring
- composing framebuffer rows
- selecting completed timed data versus the old fallback
Complete example implementation
# emulator/rendering/ppu_background_renderer.py
# --- UPDATED LINES: IMPORT THE RECORDED SCANLINE VALUE ---
from emulator.ppu.ppu import (
BackgroundScanlineState,
CTRL_BACKGROUND_PATTERN_TABLE,
PPU,
)
...
# --- NEW BLOCK: SELECT ONE SCANLINE'S LOGICAL HORIZONTAL PAIR ---
def _scanline_horizontal_pair(
state: BackgroundScanlineState,
) -> tuple[int, int]:
nametable_y = (state.vram_addr >> 11) & 1
left_base = BASE_NAMETABLE_ADDR + nametable_y * 0x0800
return left_base, left_base + 0x0400Run this lesson
uv run pytest tests/chapter_13_scrolling/test_347_scanline_horizontal_pair.py -v