252. Mapeo de la RAM de paleta en el bus de la PPU

Implementa el mapeo de direcciones de la RAM de paleta en PpuBus.

Lección 252 de 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_252_ppu_bus_palette_ram_mapping.py

Referencia

https://www.nesdev.org/wiki/PPU_palettes
https://www.nesdev.org/wiki/PPU_memory_map

Archivo a modificar

emulator/bus/ppu_bus.py

Constantes a añadir

PALETTE_START = 0x3F00
PALETTE_END = 0x3FFF
PALETTE_SIZE = 0x20

Por qué existe este paso

El rango de direcciones de la PPU $3F00-$3FFF es la memoria de paleta. La memoria de paleta no almacena gráficos de tiles. Almacena índices de color de la NES que se usarán más adelante en el renderizado de fondo y de sprites.

Nota de hardware

En la PPU original de la NES, la RAM de paleta es una pequeña área de memoria separada de 32 bytes dentro de la PPU. No es CHR ROM, ni tampoco VRAM de nametable. Tiene su propio comportamiento de duplicado (mirroring) en el rango de direcciones de la PPU $3F00-$3FFF.

Simplificación del tutorial

En esta etapa, todavía no necesitamos separar el almacenamiento en Python en un objeto PaletteRAM independiente. Podemos seguir usando el gran array de VRAM existente como ubicación de almacenamiento físico, siempre que PpuBus normalice las direcciones de paleta antes de leer o escribir.

Modelo mental

PPU.write_register($2007, value)
    -> ppu_bus.write(vram_addr, value)
    -> PpuBus detects whether vram_addr is palette memory
    -> PpuBus normalizes palette mirrors
    -> backing storage receives the byte

Reglas de duplicado de paleta para este paso

$3F00-$3F1F is the 32-byte palette window
$3F20-$3FFF mirrors $3F00-$3F1F

Duplicados especiales del color de fondo

$3F10 mirrors $3F00
$3F14 mirrors $3F04
$3F18 mirrors $3F08
$3F1C mirrors $3F0C

Ejemplo de implementación sugerida

PALETTE_START = 0x3F00
PALETTE_END = 0x3FFF
PALETTE_SIZE = 0x20

def normalize_palette_addr(self, addr: int) -> int:
    index = (addr - PALETTE_START) % PALETTE_SIZE

    if index in (0x10, 0x14, 0x18, 0x1C):
        index -= 0x10

    return PALETTE_START + index

def read(self, addr: int) -> int:
    addr = addr & PPU_ADDRESS_MASK

    if CHR_START <= addr <= CHR_END:
        ...

    if PALETTE_START <= addr <= PALETTE_END:
        return self.vram.read(self.normalize_palette_addr(addr))

    return self.vram.read(addr)

def write(self, addr: int, value: int) -> None:
    addr = addr & PPU_ADDRESS_MASK

    if CHR_START <= addr <= CHR_END:
        ...
        return

    if PALETTE_START <= addr <= PALETTE_END:
        self.vram.write(self.normalize_palette_addr(addr), value)
        return

    self.vram.write(addr, value)

Decisión de diseño importante

La lógica de duplicado de paleta pertenece a PpuBus, no a la PPU. La PPU gestiona el comportamiento de los registros; PpuBus gestiona el enrutamiento de direcciones de memoria de la PPU.

Fuera de alcance

  • colores RGB reales
  • renderizado
  • selección de paleta de fondo/sprites
  • decodificación de atributos de nametable
  • separar la RAM de paleta en su propia clase de almacenamiento

Ejecutar esta lección

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_252_ppu_bus_palette_ram_mapping.py -v