286. Manual pygame main loop

Add a manual pygame main loop for the framebuffer smoke runner.

Lesson 286 of 356 · tests/chapter_05_rendering_pipeline/test_286_manual_pygame_main_loop.py

Files to update/create

tools/show_framebuffer.py

Why this step exists

The previous step added helpers that can draw a Framebuffer onto a pygame Surface. This step adds the manual window loop so a developer can visually confirm that the pure Framebuffer data can be displayed.

Important

This is a manual smoke runner, not an automated rendering test. Automated tests must not open a real pygame window.

Run the tool as a Python module

uv run python -m tools.show_framebuffer

Python's -m flag expects a module name, not a file path. The module name uses dots:

tools.show_framebuffer

not as a file path:

tools/show_framebuffer.py

Modern Python supports this project layout as a namespace package, so this lesson does not require an __init__.py file.

Manual commands

Correct:
    uv run python -m tools.show_framebuffer

Incorrect:
    uv run python -m tools/show_framebuffer.py
    uv run python -m tools.show_framebuffer.py

What is the pygame main loop?

A pygame main loop keeps a window alive by repeatedly

1. reading events
2. drawing to the window surface
3. presenting the drawn image

Minimal shape

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    draw_framebuffer(window, framebuffer, SCALE)
    pygame.display.flip()

What is pygame.display.flip()? It presents the current contents of the window surface to the display. In simple terms:

draw pixels -> flip -> user sees pixels

Expected manual visual result

The checkerboard framebuffer should show a black/white or dark/white pattern like:

+----------------+
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
|   ██  ██  ██   |
|   ██  ██  ██   |
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
+----------------+

The exact block size depends on the framebuffer size and SCALE, but the important visual check is:

alternating light and dark squares are visible
closing the window exits cleanly

Suggested implementation example

SCALE = 3


def main() -> None:
    framebuffer = make_checkerboard_framebuffer()

    pygame.init()
    try:
        window = pygame.display.set_mode(
            (framebuffer.width * SCALE, framebuffer.height * SCALE)
        )
        pygame.display.set_caption("Framebuffer Smoke Test")

        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            draw_framebuffer(window, framebuffer, SCALE)
            pygame.display.flip()
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()

Architecture rule

pygame remains outside emulator core. This file may import pygame because it lives under tools/. Do not import pygame from emulator/rendering, emulator/ppu, or emulator/console.

Out of scope

  • testing the real pygame window in pytest
  • loading ROMs
  • rendering live Console output
  • controller input
  • sprites

Run this lesson

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