340. Temporización horizontal de vram

Aplica los incrementos horizontales de v y la copia horizontal de t a v en los dots de temporización de la PPU.

Lección 340 de 356 · tests/chapter_13_scrolling/test_340_horizontal_vram_timing.py

Archivo a actualizar

emulator/ppu/ppu.py

Referencias

https://www.nesdev.org/wiki/PPU_scrolling#During_rendering
https://www.nesdev.org/wiki/PPU_rendering#Line-by-line_timing

Por qué existe este paso

Los pasos 338 y 339 implementaron mecanismos de dirección horizontales puros. Este paso los conecta con el tiempo de la PPU.

En scanlines visibles y de pre-renderizado mientras el renderizado está activo

dots 8, 16, 24, ... 256:
    increment horizontal v after each fetched tile

dot 257:
    copy coarse X and horizontal nametable from t into v

dots 328 and 336:
    increment horizontal v while prefetching the first tiles for the next scanline

El renderizado está activo cuando en PPUMASK se activa el fondo o el renderizado de sprites. La temporización de direcciones está inactiva durante los scanlines de post-renderizado y de VBlank.

Línea temporal simplificada

1---------256 257 --------320 321------336 ----340
tile fetches  reload X          prefetch tiles
  ^ every 8                     ^ 328 and 336

Orden importante

PPU.step() primero avanza self.cycle. El valor resultante se trata como el dot actual y después se aplica la temporización de direcciones horizontales. Por lo tanto, una PPU que empieza en el ciclo 256 realiza la recarga horizontal después de un paso, en el dot 257.

Error común

Los incrementos horizontales no se limitan a los píxeles visibles. Los dots 328 y 336 preparan los dos primeros tiles de fondo para el siguiente scanline y también incrementan v.

Fuera del alcance

  • incremento vertical en el dot 256
  • copia vertical de t a v durante el pre-renderizado
  • registro del viewport por scanline
  • cambios del framebuffer y de la máscara de opacidad

Implementación de ejemplo completa

# emulator/ppu/ppu.py

class PPU:
    ...

    # --- NEW BLOCK: APPLY HORIZONTAL ADDRESS TIMING ---
    def _step_horizontal_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

        visible_fetch_increment = (
            1 <= self.cycle <= 256
            and self.cycle % 8 == 0
        )
        prefetch_increment = (
            321 <= self.cycle <= 336
            and self.cycle % 8 == 0
        )

        if visible_fetch_increment or prefetch_increment:
            self.vram_addr = increment_horizontal_vram_addr(
                self.vram_addr
            )

        if self.cycle == 257:
            self.vram_addr = copy_horizontal_scroll_bits(
                self.vram_addr,
                self.temp_vram_addr,
            )

    def step(self, cycles: int = 1) -> None:
        ...

        for _ in range(cycles):
            self.cycle += 1

            # --- NEW LINE: APPLY TIMING AT THE CURRENT DOT ---
            self._step_horizontal_rendering_address()

            ...

Ejecutar esta lección

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