⚠️ 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.

LCDC, Pallet and Tiles Loading Verification

Date:2025-12-25 StepID:0290 State: VERIFIED

Summary

Implementation of three additional monitors to verify LCDC configuration, application of the BGP palette during rendering, and critically, detect when and where the game loads tile data into VRAM. The findings from Step 0289 confirmed that the problem is that the tiles referenced by the tilemap are empty (just zeros), so we need to track if and when the game is loading tiles into VRAM. The [LCDC-CHANGE], [PALETTE-APPLY] and [TILE-LOAD] monitors were implemented.

Hardware Concept

LCDC (LCD Control Register - 0xFF40):The LCDC register controls the LCD state and rendering characteristics. The critical bits are: Bit 7 (LCD Enable: 1=ON, 0=OFF), Bit 0 (BG Display Enable: 1=ON, 0=OFF), and Bit 4 (Tile Data Base: 0=0x8800 signed, 1=0x8000 unsigned). If the LCD is off (bit 7 = 0), nothing is rendered. If the BG Display is off (bit 0 = 0), the background is not rendered. Bit 4 affects how tile addresses are calculated: in signed mode, Tile ID 0 is at 0x9000 and signed arithmetic is used; In unsigned mode, Tile ID 0 is at 0x8000 and is multiplied directly by 16.

BGP (Background Palette - 0xFF47):The BGP registry maps color indices (0-3) to other indices (0-3). Each pair of bits represents a mapping: bits 0-1 map color 0, bits 2-3 map color 1, bits 4-5 map color 2, and bits 6-7 map color 3. The standard value is 0xE4 (11100100), which is an identity mapping (0→0, 1→1, 2→2, 3→3). If BGP = 0x00, all colors are mapped to index 0 (white/green), causing a monochrome display. During rendering, the PPU reads the raw color index of the tile (0-3) and maps it using BGP to obtain the final index that is written to the framebuffer.

Loading Tiles in VRAM:The tiles are loaded in the Tile Data area (0x8000-0x97FF). Each tile occupies 16 bytes (2 bytes per line, 8 lines). The game can load tiles from ROM (compressed data that is decompressed), RAM (pre-loaded data), or via DMA (massive transfers). If the tiles are not loaded, the tilemap will reference empty tiles (just zeros), resulting in a blank or single-color screen. The [TILE-LOAD] monitor detects writes to the Tile Data area that are likely tile data loads (other than 0x00, which is cleanup).

Fountain:Pan Docs - "LCD Control Register (LCDC)", "Background Palette (BGP)", "Tile Data", "Video RAM (VRAM)"

Implementation

1. Monitor [LCDC-CHANGE] in MMU.cpp

Implemented a monitor in the functionMMU::write()which captures all changes to the LCDC register (0xFF40). The monitor reports the previous value, the new value, the PC that caused the change, the current ROM bank, and the status of critical bits (LCD Enable, BG Display Enable, Window Display Enable). This allows you to check if the game is configuring the LCD correctly and if there are any suspicious changes to the settings.

// --- Step 0290: LCDC Change Monitor ([LCDC-CHANGE]) ---
// Capture all changes to LCDC (0xFF40) to verify LCD configuration.
// LCDC controls LCD state and rendering characteristics:
// - Bit 7: LCD Enable (1=ON, 0=OFF)
// - Bit 6: Window Tile Map (0=0x9800, 1=0x9C00)
// - Bit 5: Window Display Enable (1=ON, 0=OFF)
// - Bit 4: Tile Data Base (0=0x8800 signed, 1=0x8000 unsigned)
// - Bit 3: BG Tile Map (0=0x9800, 1=0x9C00)
// - Bit 2: Sprite Size (0=8x8, 1=8x16)
// - Bit 1: Sprite Display Enable (1=ON, 0=OFF)
// - Bit 0: BG Display Enable (1=ON, 0=OFF)
// Source: Pan Docs - "LCD Control Register (LCDC)"
if (addr == 0xFF40) {
    uint8_t old_lcdc = memory_[addr];
    if (old_lcdc != value) {
        printf("[LCDC-CHANGE] 0x%02X -> 0x%02X on PC:0x%04X (Bank:%d) | LCD:%s BG:%s Window:%s\n",
               old_lcdc, value, debug_current_pc, current_rom_bank_,
               (value & 0x80) ? "ON" : "OFF",
               (value & 0x01) ? "ON" : "OFF",
               (value & 0x20) ? "ON" : "OFF");
    }
}

2. Monitor [PALETTE-APPLY] in PPU.cpp

Implemented a monitor in the functionPPU::render_scanline()which captures how the BGP palette is applied during rendering. The monitor runs only in the center of the screen (LY=72, X=80) and in the first 3 frames to avoid saturating the logs. Reports the raw color index of the tile, the final index after applying BGP, and the BGP value used. This allows you to verify that the palette is being applied correctly during rendering.

// --- Step 0290: Palette Application Monitor ([PALETTE-APPLY]) ---
// Captures how the BGP palette is applied during rendering.
// It is only activated in the center of the screen (LY=72, X=80) and in the first 3 frames
// so as not to saturate the logs.
// Source: Pan Docs - "Background Palette (BGP)"
static int palette_apply_count = 0;
if (ly_ == 72 && x == 80 && palette_apply_count< 3) {
    printf("[PALETTE-APPLY] LY:72 X:80 | ColorIndex:%d ->FinalColor:%d | BGP:0x%02X\n",
           color_index, final_color, bgp);
    palette_apply_count++;
}

3. Monitor [TILE-LOAD] in MMU.cpp (CRITICAL)

Implemented a critical monitor in the functionMMU::write()which detects writes to the Tile Data area (0x8000-0x97FF) that are probably loading tile data (other than 0x00, which is cleanup). This monitor is critical because the findings from Step 0289 confirmed that the tiles referenced by the tilemap are empty (only zeros). The monitor reports the address written, the value written, the approximate Tile ID (calculated by dividing the offset by 16), the byte within the tile (0-15), the PC that originated the write, and the current ROM bank. It has a limit of 500 writes to capture full activity.

// --- Step 0290: Tiles Load Monitor ([TILE-LOAD]) ---
// Detects writes to the Tile Data area (0x8000-0x97FF) that are probably
// be loading tile data (other than 0x00, which is cleanup).
// This monitor is critical because the findings from Step 0289 confirmed that
// the tiles referenced by the tilemap are empty (only zeros).
// Source: Pan Docs - "Tile Data": 0x8000-0x97FF contains 384 tiles of 16 bytes each
if (addr >= 0x8000 && addr<= 0x97FF) {
    // Filtrar valores comunes de inicialización/borrado para detectar datos reales
    if (value != 0x00 && value != 0x7F) {
        static int tile_load_count = 0;
        if (tile_load_count < 500) {  // Límite alto para capturar actividad completa
            // Calcular Tile ID aproximado basado en la dirección
            // Cada tile ocupa 16 bytes, Tile 0 empieza en 0x8000 (unsigned) o 0x9000 (signed)
            uint16_t tile_offset = addr - 0x8000;
            uint8_t tile_id_approx = tile_offset / 16;
            
            printf("[TILE-LOAD] Write %04X=%02X (TileID~%d, Byte:%d) PC:%04X (Bank:%d)\n",
                   addr, value, tile_id_approx, tile_offset % 16, debug_current_pc, current_rom_bank_);
            tile_load_count++;
        }
    }
}

Monitors' Objectives

  • [LCDC-CHANGE]:Check LCD configuration and detect suspicious changes. If the LCD is off or the BG Display is off, nothing will be rendered or only sprites will be rendered.
  • [PALETTE-APPLY]:Verify that the BGP palette is being applied correctly during rendering. If BGP is misconfigured, all colors will be mapped to index 0 (white/green).
  • [TILE-LOAD]:Detect when and where the game loads tile data into VRAM. This is the most important monitor because we need to know if and when the game is loading tiles. If no tile writes are detected, it means that the game is not loading tiles or they are loading but are later deleted.

Affected Files

  • src/core/cpp/MMU.cpp- Implementation of the [LCDC-CHANGE] and [TILE-LOAD] monitors
  • src/core/cpp/PPU.cpp- Monitor implementation [PALETTE-APPLY]

Tests and Verification

The monitors will be activated during the emulator execution and will generate logs with the corresponding prefixes. To verify that they work correctly:

  • Command executed: python setup.py build_ext --inplace
  • Result:C++ module compiled without errors

Compiled C++ module validation:The monitors are implemented in C++ code and will be compiled as part of the Cython module. There are no specific unit tests for these monitors, as they are diagnostic tools that are activated during the emulator execution.

Test command: python main.py roms/pkmn.gb > debug_step_0290.log 2>&1

Expected result:The logs should contain:

  • Lines with prefix[LCDC-CHANGE]every time you change the LCDC register
  • Up to 3 lines with prefix[PALETTE-APPLY](one for each of the first 3 frames in the center of the screen)
  • Up to 500 lines with prefix[TILE-LOAD]when tile data writes to VRAM are detected

Sources consulted

Educational Integrity

What I Understand Now

  • LCDC (LCD Control Register):Controls LCD status and rendering characteristics. If the LCD is off (bit 7 = 0), nothing is rendered. If the BG Display is off (bit 0 = 0), the background is not rendered. Bit 4 affects how tile addresses are calculated.
  • BGP (Background Palette):Maps color indices (0-3) to other indices (0-3). The standard value is 0xE4 (identity mapping). If BGP = 0x00, all colors are mapped to index 0 (white/green), causing a monochrome display.
  • Loading Tiles in VRAM:The tiles are loaded in the Tile Data area (0x8000-0x97FF). If the tiles are not loaded, the tilemap will reference empty tiles (just zeros), resulting in a blank or single-color screen.

What remains to be confirmed

  • Is LCDC being configured correctly?The [LCDC-CHANGE] monitor will allow you to check if the game is configuring the LCD correctly and if there are any suspicious changes.
  • Is the palette being applied correctly?The [PALETTE-APPLY] monitor will allow you to verify that the BGP palette is being applied correctly during rendering.
  • Are tiles being loaded into VRAM?The [TILE-LOAD] monitor will allow you to detect when and where the game loads tile data into VRAM. This is the most important monitor because we need to know if and when the game is loading tiles.

Hypotheses and Assumptions

Hypothesis:Based on the findings from Step 0289, we expect the monitors to confirm that:

  • LCDC is set correctly (LCD ON, BG Display ON)
  • BGP is being applied correctly during rendering
  • The game is NOT loading tiles into VRAM, or the tiles are loaded but then deleted (confirming that the problem is with the data loading)

Next Steps

  • [ ] Run the emulator with Pokémon Red and analyze the logs generated by the new monitors
  • [ ] Analyze [LCDC-CHANGE] logs to verify LCD configuration
  • [ ] Analyze [PALETTE-APPLY] logs to verify palette application
  • [ ] Critical analysis of [TILE-LOAD]:
    • Are tile writes detected in VRAM?
    • When do they occur (PC, frame, timing)?
    • What Tile IDs are being loaded?
    • Are tiles loaded but deleted later?
    • Are there any conditions preventing loading?
  • [ ] Step 0291: Apply corrections based on findings