278. Selección de paleta mediante tabla de atributos
Decodificar la selección de paleta de fondo a partir de una tabla de atributos de nametable.
Lección 278 de 356 · tests/chapter_05_rendering_pipeline/test_278_attribute_table_palette_selection.py
Referencia
https://www.nesdev.org/wiki/PPU_attribute_tablesArchivo a crear
emulator/rendering/attribute_table.pyPor qué existe este paso
El renderizador de nametable actual usa una única paleta compartida de 4 colores para cada tile. Los fondos reales de la NES eligen entre cuatro subpaletas de fondo usando la tabla de atributos del nametable.
Este paso todavía no renderiza con atributos. Solo responde a una pequeña pregunta:
For tile coordinate (tile_x, tile_y), which palette ID does the attribute
table select?¿Qué es una tabla de atributos?
Cada nametable tiene
960 bytes tile IDs
64 bytes attribute tableLa tabla de atributos es una cuadrícula de bytes de 8x8. Cada byte de atributo cubre un área de tiles de 4x4, que son 32x32 píxeles.
Un byte de atributo se divide en cuatro cuadrantes de tiles de 2x2
+-----------------------+
| top-left | top-right |
| 2x2 | 2x2 |
+----------+------------+
| bottom-l | bottom-r |
| 2x2 | 2x2 |
+-----------------------+Cada cuadrante almacena un ID de paleta de 2 bits
0b00 -> palette 0
0b01 -> palette 1
0b10 -> palette 2
0b11 -> palette 3Distinción importante
La tabla de atributos no almacena colores RGB ni almacena índices de color de la NES. Selecciona qué subpaleta de fondo usar. Más adelante, la RAM de paletas y la paleta RGB de la NES convertirán esa selección en colores reales.
Diseño de bits dentro de un byte de atributo
bits 0-1 -> top-left quadrant
bits 2-3 -> top-right quadrant
bits 4-5 -> bottom-left quadrant
bits 6-7 -> bottom-right quadrantEste paso implementa: desempaquetar/leer un cuadrante del byte.
Ejemplo de implementación legible
TABLE_SIZE = 64
BYTES_PER_ROW = 8
def get_attribute_palette_id(
attribute_table: bytes,
tile_x: int,
tile_y: int,
) -> int:
if len(attribute_table) != TABLE_SIZE:
raise ValueError("Attribute table must be 64 bytes")
attribute_x = tile_x // 4
attribute_y = tile_y // 4
attribute_index = attribute_y * BYTES_PER_ROW + attribute_x
attribute_byte = attribute_table[attribute_index]
quadrant_x = (tile_x % 4) // 2
quadrant_y = (tile_y % 4) // 2
is_top_left = quadrant_x == 0 and quadrant_y == 0
is_top_right = quadrant_x == 1 and quadrant_y == 0
is_bottom_left = quadrant_x == 0 and quadrant_y == 1
if is_top_left:
return attribute_byte & 0b11
if is_top_right:
return (attribute_byte >> 2) & 0b11
if is_bottom_left:
return (attribute_byte >> 4) & 0b11
return (attribute_byte >> 6) & 0b11¿Por qué tile_x // 4 y tile_y // 4? Porque cada byte de atributo cubre 4x4 tiles.
¿Por qué (tile_x % 4) // 2?
Dentro de esa área de 4x4
tile positions 0,1 belong to quadrant 0
tile positions 2,3 belong to quadrant 1Así que
0 % 4 // 2 -> 0
1 % 4 // 2 -> 0
2 % 4 // 2 -> 1
3 % 4 // 2 -> 1Fuera de alcance
- renderizado de nametables con atributos
- búsqueda en la RAM de paletas de la PPU
- conversión a paleta RGB
- desplazamiento (scrolling)
- sprites
- pantalla de pygame
Ejecutar esta lección
uv run pytest tests/chapter_05_rendering_pipeline/test_278_attribute_table_palette_selection.py -v