NES TDD

280. Фоновые палитры из палитровой RAM

Построение RGB-фоновых палитр из байтов палитровой RAM PPU.

Урок 280 из 356 · tests/chapter_05_rendering_pipeline/test_280_background_palettes_from_palette_ram.py

Справочная информация

https://www.nesdev.org/wiki/PPU_palettes#Palette_RAM

Файл для создания

emulator/rendering/palette_ram.py

Почему этот шаг важен

Рендерер таблицы имён с учётом атрибутов ожидает четыре разрешённые RGB-фоновые палитры:

background_palettes[palette_id][color_index]

Но настоящие игры NES не хранят RGB-цвета в таблице имён или таблице атрибутов. Они записывают индексы цвета NES в палитровую RAM PPU.

Эта вспомогательная функция преобразует

PPU palette RAM bytes -> RGB background palettes

Что такое палитровая RAM PPU?

Палитровая RAM PPU — это небольшая область в 32 байта, отображённая по адресу

$3F00-$3F1F

Для этого шага мы используем только первые 16 байтов

$3F00-$3F0F = background palette area

Эти байты хранят индексы цвета NES $00-$3F, а не значения RGB.

Поток

palette RAM byte
    -> NES color index $00-$3F
    -> get_nes_rgb_color(index)
    -> RGB tuple

Фоновый / универсальный цвет фона

При рендеринге фона индекс цвета 0 использует общий цвет фона по адресу $3F00.

Поэтому каждая возвращаемая фоновая палитра использует один и тот же первый RGB-цвет

background_palettes[0][0] == backdrop
background_palettes[1][0] == backdrop
background_palettes[2][0] == backdrop
background_palettes[3][0] == backdrop

Расположение фоновой палитры для $3F00-$3F0F:

$3F00 -> backdrop / universal background color

palette 0:
    entry 0 -> $3F00
    entry 1 -> $3F01
    entry 2 -> $3F02
    entry 3 -> $3F03

palette 1:
    entry 0 -> $3F00
    entry 1 -> $3F05
    entry 2 -> $3F06
    entry 3 -> $3F07

palette 2:
    entry 0 -> $3F00
    entry 1 -> $3F09
    entry 2 -> $3F0A
    entry 3 -> $3F0B

palette 3:
    entry 0 -> $3F00
    entry 1 -> $3F0D
    entry 2 -> $3F0E
    entry 3 -> $3F0F

Обратите внимание

$3F04, $3F08, and $3F0C are not used as independent background color-0
entries in this simplified helper. Color index 0 uses the shared backdrop.

Пример возможной реализации

from emulator.rendering.framebuffer import RGBColor
from emulator.rendering.nes_palette import get_nes_rgb_color

PALETTE_RAM_SIZE = 16
TOTAL_PALETTES = 4
COLORS_PER_PALETTE = 4

BackgroundPalettes = list[list[RGBColor]]


def build_background_palettes_from_palette_ram(
    palette_ram: bytes,
) -> BackgroundPalettes:
    if len(palette_ram) != PALETTE_RAM_SIZE:
        raise ValueError(f"Background palette RAM must be {PALETTE_RAM_SIZE} bytes")

    backdrop_color = get_nes_rgb_color(palette_ram[0])
    background_palettes = []

    for palette_id in range(TOTAL_PALETTES):
        base = palette_id * COLORS_PER_PALETTE

        palette = [
            backdrop_color,
            get_nes_rgb_color(palette_ram[base + 1]),
            get_nes_rgb_color(palette_ram[base + 2]),
            get_nes_rgb_color(palette_ram[base + 3]),
        ]

        background_palettes.append(palette)

    return background_palettes

Вне рамок этого шага

  • палитры спрайтов
  • поведение прозрачности спрайтов
  • зеркалирование палитровой RAM
  • чтение напрямую с шины PPU
  • акцентированные цвета PPUMASK
  • отображение через pygame

Запустить этот урок

uv run pytest tests/chapter_05_rendering_pipeline/test_280_background_palettes_from_palette_ram.py -v