⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

DMA and OAM Diagnosis: The Lost Star

Date:2025-12-18 StepID:0077 State: draft

Summary

The emulator shows the static "GAME FREAK" logo in Pokémon Red, but the animation of the shooting star (Sprite) that must lower and transform the text is missing. If the star does not appear, the animation does not start and the game does not proceed to the next screen.

Added diagnostic instrumentation to monitor the DMA (Direct Memory Access) that copies sprite data to the OAM (Object Attribute Memory) and a heartbeat that displays the status of the OAM every second. The goal is to determine if the problem is with the DMA transfer, corrupt/empty OAM, or sprite rendering.

Hardware Concept

The Game Boy usesDMA (Direct Memory Access)to transfer sprite data from any memory region (ROM, RAM, VRAM) to theOAM (Object Attribute Memory), which is a special 160-byte region (0xFE00-0xFE9F) that stores the attributes of up to 40 sprites.

Each sprite occupies 4 bytes in OAM:

  • Byte 0 (Y): Y position of sprite (0-255, but typically 0-159)
  • Byte 1 (X): Sprite's X position (0-255, but typically 0-167)
  • Byte 2 (Tile): Tile index in VRAM (0-255)
  • Byte 3 (Flags): Attributes (priority, X/Y flip, palette, etc.)

To start a DMA transfer, the game writes a valueXXin the registry0xFF46. This immediately copies 160 bytes from the addressXX00until0xFE00(OAM). The transfer is blocking and takes approximately 160 machine cycles.

If DMA is not working correctly, sprites are not copied to OAM and do not appear on the screen. If the OAM is empty (all zeros), the renderer has no sprite data to draw.

Fountain:Pan Docs - DMA Transfer, OAM (Object Attribute Memory)

Implementation

Added detailed DMA logging insrc/memory/mmu.pyand an OAM diagnostic heartbeat insrc/viboy.py.

Modified components

  • src/memory/mmu.py: Added verbose DMA logging showing the source address, the first byte of the source, and a sample of the first 4 bytes of OAM after the transfer.
  • src/viboy.py: Added real-time heartbeat (every second) that shows the OAM status: checksum, number of non-zero bytes, and attributes of the first sprite.

DMA Logging

When writing to the DMA register (0xFF46), the system now logs:

  • Source address (XX00) and the first byte of the source (to verify that there is data)
  • Destination address (0xFE00, start of OAM)
  • After transfer, display the first 4 bytes of OAM decoded as sprite attributes (Y, X, Tile, Flags)

This allows detecting if:

  • The game tries to cast DMA but the source is empty (first byte = 0x00 or 0xFF)
  • DMA runs but data does not reach OAM (OAM still empty after DMA)
  • DMA works correctly but data is incorrect (invalid sprite attributes)

OAM Heartbeat

Every second, the system displays:

  • Checksum: Sum of the first 16 bytes of OAM (4 sprites). If 0, OAM is empty.
  • Non-zero bytes: Number of non-zero bytes in the first 16 bytes. If it is 0, there are no sprites.
  • First sprite: Decoded attributes of the first sprite (Y, X, Tile, Flags)

This allows detecting if:

  • OAM is empty (checksum = 0, non-zero = 0) → DMA does not work or does not run
  • OAM has data but sprite is off screen (Y > 159 or X > 167) → coordinate problem
  • OAM has valid data → problem in sprite renderer

Affected Files

  • src/memory/mmu.py- Added detailed DMA logging with source validation and OAM sample after transfer
  • src/viboy.py- Added real-time heartbeat (every second) showing OAM status

Tests and Verification

This is a diagnostic modification, not a new functional implementation. No unit tests were added, but it will be validated by running ROMs.

Next validation:Executepython main.py pkmn.gband observe the logs to determine:

  • If the message "💾 DMA START" appears → The game tries to launch DMA
  • If "OAM SAMPLE" shows checksum > 0 → The sprites are in OAM
  • If "OAM SAMPLE" shows checksum = 0 → OAM is empty, DMA does not work
  • If "DMA START" does not appear → The game does not try to launch DMA (logic/CPU problem)

State:Pending execution and log analysis.

Sources consulted

Educational Integrity

What I Understand Now

  • DMATransfer:Register 0xFF46 allows 160 bytes to be copied from any address XX00 to OAM (0xFE00). The transfer is immediate and blocking.
  • OAM Structure:Each sprite occupies 4 bytes: Y, X, Tile, Flags. OAM can store up to 40 sprites (160 bytes total).
  • Diagnosis:If OAM is empty, sprites are not drawn. If DMA is not executed, OAM remains empty.

What remains to be confirmed

  • DMA Timing:Should the DMA block access to OAM during the transfer? We currently do not implement access restrictions.
  • DMA source:What memory region does Pokémon Red copy sprites from? (probably WRAM or HRAM)
  • Sprite Rendering:If OAM has valid data but it is not drawn, the problem is with the renderer (priority, palette, 8x16 mode, etc.)

Hypotheses and Assumptions

Main hypothesis:The DMA is not copying data correctly, or the game is not trying to launch DMA. If OAM is empty, the star does not exist and cannot be drawn.

Assumption:We assume that the DMA runs immediately when writing to 0xFF46, with no timing restrictions. In real hardware, the DMA may have access restrictions during certain PPU modes, but we do not implement them for now.

Next Steps

  • [ ] Executepython main.py pkmn.gband analyze the DMA and OAM logs
  • [ ] If DMA does not appear: investigate why the game does not try to launch DMA (CPU/logic issue)
  • [ ] If DMA appears but OAM is empty: investigate why the transfer is not working
  • [ ] If OAM has data but it is not drawn: investigate sprite renderer (priority, palette, 8x16 mode)
  • [ ] If OAM has valid data and is drawn: issue is resolved, continue with next animation