From 6106b6c7ec1d488a3435e9e3b1d63f4f56d7f441 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Nov 2025 22:59:35 +0000 Subject: [PATCH] fix(ppu): call present() at the end of each frame to display output The PPU was writing pixels to the framebuffer correctly via setPixel(), but never called present() to signal that the frame was complete and ready to be displayed. This caused the CLI renderer to never output frames to the terminal, resulting in a white screen. This fix adds present() to the FramebufferInterface and calls it from the PPU when a frame completes (when LY wraps from 153 back to 0 during VBlank). Changes: - Add present() method to FramebufferInterface - Implement present() in ArrayFramebuffer (no-op for testing) - Implement present() in WasmFramebuffer (no-op, JS polls for pixels) - CliRenderer.present() already implemented, now gets called - Call framebuffer->present() from PPU.stepVBlank() when frame completes Fixes issue where Tetris and other games would show white screen in CLI mode because present() was never invoked to trigger terminal rendering. --- src/Frontend/Wasm/WasmFramebuffer.php | 11 +++++++++++ src/Ppu/ArrayFramebuffer.php | 6 ++++++ src/Ppu/FramebufferInterface.php | 8 ++++++++ src/Ppu/Ppu.php | 3 +++ 4 files changed, 28 insertions(+) diff --git a/src/Frontend/Wasm/WasmFramebuffer.php b/src/Frontend/Wasm/WasmFramebuffer.php index 8905518..2ae98f8 100644 --- a/src/Frontend/Wasm/WasmFramebuffer.php +++ b/src/Frontend/Wasm/WasmFramebuffer.php @@ -125,4 +125,15 @@ public function getHeight(): int { return self::HEIGHT; } + + /** + * Present the framebuffer. + * + * For WASM, this is a no-op since JavaScript explicitly polls + * for pixel data via getPixelsRGBA() in the render loop. + */ + public function present(): void + { + // No-op for WASM - JavaScript polls for pixel data + } } diff --git a/src/Ppu/ArrayFramebuffer.php b/src/Ppu/ArrayFramebuffer.php index d39965c..0c347b2 100644 --- a/src/Ppu/ArrayFramebuffer.php +++ b/src/Ppu/ArrayFramebuffer.php @@ -43,4 +43,10 @@ public function clear(): void $this->buffer[$y] = array_fill(0, self::WIDTH, $white); } } + + public function present(): void + { + // No-op for array framebuffer (used for testing) + // Actual rendering implementations (CLI, WASM) should override this + } } diff --git a/src/Ppu/FramebufferInterface.php b/src/Ppu/FramebufferInterface.php index a5168a6..d734826 100644 --- a/src/Ppu/FramebufferInterface.php +++ b/src/Ppu/FramebufferInterface.php @@ -34,4 +34,12 @@ public function getFramebuffer(): array; * Clear the framebuffer (typically to white). */ public function clear(): void; + + /** + * Present the framebuffer (display/render the current frame). + * + * Called by the PPU at the end of each frame to signal that + * the frame is complete and ready to be displayed. + */ + public function present(): void; } diff --git a/src/Ppu/Ppu.php b/src/Ppu/Ppu.php index afb1ad8..f3e9d90 100644 --- a/src/Ppu/Ppu.php +++ b/src/Ppu/Ppu.php @@ -191,6 +191,9 @@ private function stepVBlank(): void $this->ly = 0; $this->updateLycCoincidence(); $this->setMode(PpuMode::OamSearch); + + // Frame complete - present the framebuffer + $this->framebuffer->present(); } } }