343. Vertical vram timing
Apply vertical v increment and vertical t-to-v copying at PPU timing dots.
Lesson 343 of 356 · tests/chapter_13_scrolling/test_343_vertical_vram_timing.py
File to update
emulator/ppu/ppu.pyReferences
https://www.nesdev.org/wiki/PPU_scrolling#At_dot_256_of_each_scanlineWhy this step exists
Steps 341 and 342 implemented pure vertical address mechanisms. This step connects them to PPU time.
While rendering is enabled
visible and pre-render scanlines, dot 256:
increment fine/coarse Y in v
pre-render scanline, every dot 280-304:
copy fine Y, vertical nametable, and coarse Y from t into vSimplified timing
visible scanline:
... 255 256 257 ...
| |
| +-- horizontal reload
+------ vertical increment
pre-render scanline 261:
... 256 257 ... 280----------------304 ...
| | |
| | +-- vertical reload active at every dot
| +---------- horizontal reload
+-------------- vertical incrementWhy repeat the vertical reload at every dot 280-304? If CPU writes change t during that interval, a later dot can copy the newer vertical state. Treating the interval as one operation at dot 280 would lose that behavior.
Rendering is enabled when either the PPUMASK background bit or sprite bit is set. Post-render and VBlank scanlines do not perform these automatic updates.
Important ordering at dot 256
The horizontal fetch increment runs first and the vertical increment runs second. They own different fields, so both changes must be visible in the resulting v.
Out of scope
- effective viewport recording per scanline
- two-tile prefetch compensation
- framebuffer row composition
- opacity-mask row composition
Complete example implementation
# emulator/ppu/ppu.py
class PPU:
...
# --- NEW BLOCK: APPLY VERTICAL ADDRESS TIMING ---
def _step_vertical_rendering_address(self) -> None:
rendering_enabled = self.mask & (
MASK_SHOW_BACKGROUND | MASK_SHOW_SPRITES
)
if not rendering_enabled:
return
rendering_scanline = (
0 <= self.scanline < 240
or self.scanline == PPU_PRE_RENDER_SCANLINE
)
if not rendering_scanline:
return
if self.cycle == 256:
self.vram_addr = increment_vertical_vram_addr(
self.vram_addr
)
vertical_reload = (
self.scanline == PPU_PRE_RENDER_SCANLINE
and 280 <= self.cycle <= 304
)
if vertical_reload:
self.vram_addr = copy_vertical_scroll_bits(
self.vram_addr,
self.temp_vram_addr,
)
def step(self, cycles: int = 1) -> None:
...
for _ in range(cycles):
self.cycle += 1
self._step_horizontal_rendering_address()
# --- NEW LINE: APPLY VERTICAL TIMING AT THE CURRENT DOT ---
self._step_vertical_rendering_address()
...Run this lesson
uv run pytest tests/chapter_13_scrolling/test_343_vertical_vram_timing.py -v