NES TDD

248. Память OAM и OAMDATA

Реализовать память OAM и поведение OAMADDR/OAMDATA.

Урок 248 из 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_248_oam_memory_and_oamdata.py

Ссылки

https://www.nesdev.org/wiki/PPU_registers#OAMADDR
https://www.nesdev.org/wiki/PPU_registers#OAMDATA
https://www.nesdev.org/wiki/PPU_OAM

Файл для обновления

emulator/ppu/ppu.py

Состояние для добавления

OAM_SIZE = 256
oam: bytearray = field(default_factory=lambda: bytearray(OAM_SIZE))

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

OAM означает Object Attribute Memory (память атрибутов объектов). Это внутренняя 256-байтовая память спрайтов PPU. PPU рендерит спрайты из OAM, а не напрямую из RAM CPU.

Раскладка спрайта

NES OAM stores 64 sprites.
Each sprite uses 4 bytes.

64 sprites * 4 bytes = 256 bytes

Базовая раскладка байтов спрайта

byte 0: Y position
byte 1: tile index
byte 2: attributes
byte 3: X position

Видимые для CPU регистры

$2003 OAMADDR
    selects which OAM byte is currently addressed

$2004 OAMDATA
    reads/writes OAM at the current OAMADDR

Важное поведение для этого шага

  • запись в $2003 устанавливает oam_addr
  • запись в $2004 сохраняет значение в oam[oam_addr]
  • запись в $2004 увеличивает oam_addr с помощью & 0xFF
  • чтение $2004 возвращает oam[oam_addr]

Почему OAM не является VRAM в PpuBus

PpuBus обрабатывает адресное пространство PPU, используемое PPUADDR/PPUDATA

$0000-$3FFF

OAM — это отдельная внутренняя память спрайтов, доступ к которой осуществляется через OAMADDR/OAMDATA

$2003/$2004

Предлагаемый псевдокод реализации

OAM_SIZE = 256

@dataclass
class PPU:
    ...
    oam_addr: int = 0
    oam_data: int = 0
    oam: bytearray = field(default_factory=lambda: bytearray(OAM_SIZE))

    def write_register(self, addr: int, value: int) -> None:
        value = value & 0xFF

        match addr:
            ...
            case 0x2003:
                self.oam_addr = value

            case 0x2004:
                # Preserve old compatibility/debug field.
                self.oam_data = value

                # Write to current OAM byte.
                self.oam[self.oam_addr] = value

                # OAMADDR increments after OAMDATA write.
                self.oam_addr = (self.oam_addr + 1) & 0xFF

            ...

    def read_register(self, addr: int) -> int:
        match addr:
            ...
            case 0x2004:
                self.oam_data = self.oam[self.oam_addr]
                return self.oam_data

Вне рамок

  • OAMDMA по адресу $4014
  • оценка спрайтов
  • рендеринг спрайтов
  • поведение переполнения спрайтов
  • поведение попадания спрайта 0

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

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