329. Decodificar la posición del viewport de fondo

Decodificar una posición de viewport de fondo simplificada a partir del estado de scrolling del PPU.

Lección 329 de 356 · tests/chapter_13_scrolling/test_329_decode_background_viewport_position.py

Archivo a crear

emulator/rendering/background_viewport.py

Documentación de referencia

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers
https://www.nesdev.org/wiki/PPU_scrolling#During_rendering

Por qué existe este paso

El PPU almacena el scrolling como campos de hardware empaquetados en lugar de coordenadas de píxel listas para usar. El renderizado necesita una posición simple de la esquina superior izquierda del viewport, así que este paso decodifica:

temp_vram_addr (t): yyy NN YYYYY XXXXX
fine_x (x):         xxx

Donde

XXXXX -> coarse X tile position, 5 bits
YYYYY -> coarse Y tile position, 5 bits
NN    -> logical nametable X/Y selection
yyy   -> fine Y pixel position, 3 bits
xxx   -> fine X pixel position, 3 bits stored separately

Conversión a píxeles

viewport X = nametable X * 256 + coarse X * 8 + fine X
viewport Y = nametable Y * 240 + coarse Y * 8 + fine Y

Implementación sugerida

# emulator/rendering/background_viewport.py

NAMETABLE_PIXEL_WIDTH = 256
NAMETABLE_PIXEL_HEIGHT = 240
TILE_PIXEL_SIZE = 8

BackgroundViewportPosition = tuple[int, int]


def decode_background_viewport_position(
    temp_vram_addr: int,
    fine_x: int,
) -> BackgroundViewportPosition:
    coarse_x = temp_vram_addr & 0b1_1111
    coarse_y = (temp_vram_addr >> 5) & 0b1_1111

    nametable_x = (temp_vram_addr >> 10) & 1
    nametable_y = (temp_vram_addr >> 11) & 1

    fine_y = (temp_vram_addr >> 12) & 0b111

    viewport_x = (
        nametable_x * NAMETABLE_PIXEL_WIDTH
        + coarse_x * TILE_PIXEL_SIZE
        + fine_x
    )

    viewport_y = (
        nametable_y * NAMETABLE_PIXEL_HEIGHT
        + coarse_y * TILE_PIXEL_SIZE
        + fine_y
    )

    return viewport_x, viewport_y

¿Por qué usar t y x en lugar del antiguo campo de scroll? $2005 recibe escrituras horizontales y verticales, mientras que el campo de scroll de compatibilidad almacena solo el último byte. Los campos t y x al estilo del hardware conservan el estado empaquetado completo.

Límite de precisión

El hardware real renderiza desde la dirección v actual más la X fina tras transferencias temporizadas de t a v. Este tutorial usa actualmente t más la X fina como una instantánea a nivel de frame. Las transferencias exactas en dot-257 y pre-render-dot-280-304 siguen siendo trabajo de timing futuro.

Otra simplificación

Los valores reales de Y grueso 30 y 31 tienen un comportamiento de ajuste especial. Este paso solo realiza una decodificación directa de campo a píxel; el ajuste del viewport llega más tarde.

Fuera del alcance

  • leer bytes de nametable
  • crear un framebuffer
  • cruzar un límite de nametable
  • transferencias de timing exactas de t -> v
  • ajuste de Y grueso 30/31
  • fixtures de ROM comerciales

Ejecutar esta lección

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