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

Investigation of Discrepancy Between Logs and Visualization

Date:2025-12-29 StepID:0347 State: VERIFIED

Summary

Three additional checks were implemented to investigate why, even though the logs show correct correspondence between the framebuffer and the display, the images show visual problems (vertical stripes, white screen, checkerboard). Checks include: (1) checking the entire framebuffer (all lines, not just specific lines), (2) checking scaling and blit to the screen, and (3) line-by-line checking the display after drawing the pixels.

Hardware Concept

Full Framebuffer:The Game Boy framebuffer contains 23040 pixels (160x144). Each line is 160 pixels, and all lines must have data for correct display. If some lines are empty (all pixels with index 0), the display will show problems such as white streaks or empty areas.

Scaling and Blit:The original surface (160x144) is scaled to the screen resolution using interpolation. Scaling may cause slight changes in colors due to interpolation. The blit copies the scaled surface to the screen. The content on the screen should match the original framebuffer, although there may be small differences due to interpolation.

Line by Line Verification:To identify display problems, it is useful to check each surface line after drawing the pixels and compare it with the original framebuffer. This allows you to identify lines where there are discrepancies between the framebuffer and the display.

Implementation

Three main checks were implemented to investigate the discrepancy between the logs and the visualization:

1. Full Framebuffer Check

Added code that checks all framebuffer lines (0-143), not just specific lines. The check counts how many lines have data (not all white), how many lines are empty, and the distribution of indices across the framebuffer. It also identifies problematic lines (empty when they should have data).

# --- Step 0347: Checking the Full Framebuffer ---
if frame_indices is not None and len(frame_indices) == 23040 and \
   self._framebuffer_complete_check_count< 10:
    self._framebuffer_complete_check_count += 1
    
    # Contar líneas con datos (no todas blancas)
    lines_with_data = 0
    lines_empty = 0
    total_non_zero_pixels = 0
    index_counts = [0, 0, 0, 0]
    
    for y in range(144):
        line_start = y * 160
        line_non_zero = 0
        
        for x in range(160):
            idx = line_start + x
            if idx < len(frame_indices):
                color_idx = frame_indices[idx] & 0x03
                if color_idx < 4:
                    index_counts[color_idx] += 1
                    if color_idx != 0:
                        line_non_zero += 1
                        total_non_zero_pixels += 1
        
        if line_non_zero >0:
            lines_with_data += 1
        else:
            lines_empty += 1
    
    logger.info(f"[Renderer-Framebuffer-Complete] Frame {self._framebuffer_complete_check_count} | "
               f"Lines with data: {lines_with_data}/144 | Lines empty: {lines_empty}/144 | "
               f"Total non-zero pixels: {total_non_zero_pixels}/23040 | "
               f"Distribution: 0={index_counts[0]} 1={index_counts[1]} 2={index_counts[2]} 3={index_counts[3]}")
# -------------------------------------------

2. Scaling and Blit Verification

Added code that verifies that scaling and blit work correctly. The verification compares some pixels before and after scaling, and verifies that the color on the screen after blit matches the scaled color.

# --- Step 0347: Scaling and Blit Verification ---
if hasattr(self, '_scaled_surface_cache') and self._scaled_surface_cache is not None and \
   hasattr(self, 'surface') and self.surface is not None and \
   self._scale_blit_check_count< 10:
    self._scale_blit_check_count += 1
    
    # Verificar tamaño de la superficie escalada
    scaled_size = self._scaled_surface_cache.get_size()
    screen_size = self.screen.get_size()
    
    # Verificar algunos píxeles antes y después del escalado
    test_pixels = [(0, 0), (80, 72), (159, 143)]
    
    for x, y in test_pixels:
        # Color en la superficie original (160x144)
        original_color = self.surface.get_at((x, y))
        
        # Calcular posición escalada
        scale_x = int(x * screen_size[0] / 160)
        scale_y = int(y * screen_size[1] / 144)
        
        # Color en la superficie escalada
        scaled_color = self._scaled_surface_cache.get_at((scale_x, scale_y))
        
        # Color en la pantalla después del blit
        screen_color = self.screen.get_at((scale_x, scale_y))
        
        # Verificar que los colores son similares (tolerancia para interpolación)
        if abs(original_color[0] - scaled_color[0]) >20 or \
           abs(original_color[1] - scaled_color[1]) > 20 or \
           abs(original_color[2] - scaled_color[2]) > 20:
            logger.warning(f"[Renderer-Scale-Blit] ⚠️ Very different color scaling!")
# -------------------------------------------

3. Line-by-Line Verification of the Display

Added code that checks each surface line after drawing pixels and compares it to the original framebuffer. The check identifies lines where there are discrepancies between the framebuffer and the display.

# --- Step 0347: Line-by-Line Verification of the Display ---
if hasattr(self, 'surface') and self.surface is not None and \
   frame_indices is not None and len(frame_indices) == 23040 and \
   self._line_by_line_check_count< 5:
    self._line_by_line_check_count += 1
    
    # Verificar algunas líneas específicas
    test_lines = [0, 36, 72, 108, 143]  # Distribuidas uniformemente
    
    for y in test_lines:
        line_start = y * 160
        matches = 0
        mismatches = 0
        
        # Verificar primeros 10 píxeles de la línea
        for x in range(min(10, 160)):
            idx = line_start + x
            if idx < len(frame_indices):
                # Índice del framebuffer
                framebuffer_idx = frame_indices[idx] & 0x03
                expected_rgb = palette[framebuffer_idx]
                
                # Color en la superficie
                surface_color = self.surface.get_at((x, y))
                
                # Comparar (tolerancia pequeña)
                if abs(surface_color[0] - expected_rgb[0]) <= 5 and \
                   abs(surface_color[1] - expected_rgb[1]) <= 5 and \
                   abs(surface_color[2] - expected_rgb[2]) <= 5:
                    matches += 1
                else:
                    mismatches += 1
                    if mismatches <= 3:  # Solo loggear primeros 3 desajustes
                        logger.warning(f"[Renderer-Line-by-Line] Line {y}, Pixel ({x}, {y}): "
                                     f"Mismatch! Expected={expected_rgb}, Actual={surface_color}")
# -------------------------------------------

Design Decisions

  • Complete framebuffer check:All lines in the framebuffer, not just specific lines, are checked to identify problematic lines that could cause visual problems.
  • Scaling and blit verification:Verify that scaling and blit work correctly by comparing pixels before and after scaling, and after blit. This helps identify if the problem is with scaling or blit.
  • Line by line verification:Each line on the surface is checked after drawing the pixels and compared to the original framebuffer. This allows you to identify lines where there are discrepancies.
  • Tolerance for interpolation:A small tolerance (5 for RGB) is used when comparing colors, as interpolation can cause slight changes in colors.

Affected Files

  • src/gpu/renderer.py- Added three checks: full framebuffer, scaling and blit, and line-by-line checking

Tests and Verification

The checks will be run during normal emulator execution. To analyze the results, it is recommended to run tests with multiple ROMs in parallel:

# Run the 5 ROMs in parallel (background processes)
timeout 150 python3 main.py roms/pkmn.gb 2>&1 | tee logs/test_pkmn_step0347.log &
timeout 150 python3 main.py roms/tetris.gb 2>&1 | tee logs/test_tetris_step0347.log &
timeout 150 python3 main.py roms/mario.gbc 2>&1 | tee logs/test_mario_step0347.log &
timeout 150 python3 main.py roms/pkmn-amarillo.gb 2>&1 | tee logs/test_pkmn_amarillo_step0347.log &
timeout 150 python3 main.py roms/Oro.gbc 2>&1 | tee logs/test_oro_step0347.log &

# Wait for all processes to finish
wait

echo "All tests completed"

To analyze the generated logs, the following commands can be used:

# Check full framebuffer
grep "\[Renderer-Framebuffer-Complete\]" logs/test_*_step0347.log | head -n 50

# Check scaling and blit
grep "\[Renderer-Scale-Blit\]" logs/test_*_step0347.log | head -n 50

# Check line by line
grep "\[Renderer-Line-by-Line\]" logs/test_*_step0347.log | head -n 50

Compiled C++ module validation:Checks confirm that the framebuffer is the correct size (23040 pixels = 160x144) and that the pixels are drawn correctly on the surface.

Sources consulted

Educational Integrity

What I Understand Now

  • Full Framebuffer:The Game Boy framebuffer contains 23040 pixels (160x144). All lines must have data for correct display. If some lines are empty, the display will show problems.
  • Scaling and Blit:Scaling may cause slight changes in colors due to interpolation. The blit copies the scaled surface to the screen. It is important to verify that scaling and blit are working correctly.
  • Line by Line Verification:Checking each line on the surface after drawing the pixels and comparing it to the original framebuffer allows you to identify lines where there are discrepancies.

What remains to be confirmed

  • Cause of discrepancy:Although the logs show correct correspondence, the images show visual problems. The implemented checks will help identify the cause.
  • Impact of scaling:Scaling can cause visual artifacts if not handled correctly. It should be verified that scaling does not cause problems.

Hypotheses and Assumptions

It is assumed that the problem could be: (1) only some lines are checked, but others are not, (2) problem in scaling, (3) problem in blit, or (4) problem in reading the entire framebuffer. The checks implemented will help identify which of these hypotheses is correct.

Next Steps

  • [ ] Run tests with the 5 ROMs in parallel (5 simultaneous emulators, ~2.5 minutes total)
  • [ ] Analyze the generated logs to identify problems
  • [ ] If the cause is identified, implement correction in the next step
  • [ ] If the problem persists, perform further analysis and workaround