added input base

This commit is contained in:
2026-08-09 23:36:45 -05:00
parent cda0a7ddb4
commit 5c24828842
4 changed files with 135 additions and 10 deletions
+5 -9
View File
@@ -51,6 +51,7 @@
3. Background pipeline timing (src/ppu.rs dot/bg_fetch, modeled after olcNES Part 4 reference): shifters must shift ONLY during fetch windows (cycles 2-257 and 321-337), with LoadBackgroundShifters + TransferAddressX at cycle 257 (preloads the NEXT scanline's first tile), IncrementScrollY at 256, idle NT fetches at 338/340, TransferAddressY on the pre-render line 280-304. Our code shifted every dot and copied horizontal at 255 -> each scanline started mid-tile, image shifted showing page 1 + page 2. Rewrote dot() with windowed shifters, (cycle-1)%8 fetch phases, load_shifters/update_shifters helpers, bit-mux fine-X (0x8000 >> x) in render_bg_pixel. Frame now renders pixel-correct with correct colors
- ACCEPTANCE MET: SMB renders the full frame correctly (background). Mario himself is a sprite and is NOT rendered yet - that's the next milestone (PPU-2 sprites + $4014 OAM DMA)
- PPU-2 3a SPRITES DONE: $4014 OAM DMA (bus.rs routes to Ppu::oam_dma, copies 256 bytes from CPU RAM page through the 2KB mirror), sprite evaluation at cycle 257 (8 sprites per scanline, overflow flag bit 5, sprite-0 tracking), sprite pattern fetch at cycle 340 (8x8 via ctrl bit 3, 8x16 via tile id bit 0, V-flip with 7-row inversion, H-flip via reverse_bits, hi plane +8), per-sprite shifters with X countdown (decrement X until 0 then shift, cycles 1-257), and render_pixel compositing (bg vs sprite priority via attr bit 5, sprites use palettes 4-7, sprite-0 hit sets status bit 6 at cycle>=9). Fixed one integration bug: the old bg-only pipeline block was left in dot() duplicating the new combined block (shifters shifted/fetched twice per dot) - deleted the old block. ACCEPTANCE MET: Mario visible and the coin sprite animates across frames
- PPU-2 3b INPUT DONE (src/input.rs): InputSource trait (pluggable sources), MaskInput concrete source, Controller struct with strobe-latch + shift-out protocol ($4016/$4017, |0x80 filler so bits 9+ read 1), Key enum + key_to_button (Z=A, X=B, Shift=Select, Enter=Start, Arrows=D-pad). Bus wired. 3 unit tests pass. Fixed match-ordering bug: controller arms were below the $4000-$401F range arm (unreachable); specific arms must come before overlapping ranges
- Backup library mapper audit: mappers 0 (NROM) and 1 (MMC1) cover many games; still need mapper 2 (UNROM: Castlevania, Contra, Megaman 1), mapper 4 (MMC3: SMB2, SMB3, Lolo 2), mapper 7 (AOROM: Who Framed Roger Rabbit)
- Finding: real commercial games rarely use illegal opcodes; none of the backup library needs them. Official-only CPU is sufficient for the goal of playing these games
@@ -68,17 +69,12 @@ Phases in order:
3. NEXT: PPU-2 sprites + input + GUI window - makes SMB actually playable (Mario + enemies visible AND moveable). Detailed plan below. Four phases, sprites first (verifiable headless via PPM + dbg), then input logic (testable in isolation), then the window (first external deps):
3a. SPRITES - DONE (see Done section). Acceptance MET: Mario + coin sprite visible, coin animates
3b. NEXT: INPUT (new src/input.rs, pure logic, testable without GUI):
- InputSource trait (the ONLY new trait): fn buttons(&mut self) -> u8, NES button bit order. Impls: KeyboardInput (winit later), BotInput for automated tests (e.g. "hold right 120 frames" to verify scrolling headlessly)
- Controller struct (concrete, NOT a trait - fixed hardware protocol): holds Box<dyn InputSource>, strobe latch + shift register for $4016/$4017 semantics. read/write protocol implemented once here
- Keyboard defaults: Z=A, X=B, Shift=Select, Enter=Start, Arrows=D-pad. Pure mapping fn, unit-testable
- One Controller per player ($4016 = P1, $4017 = P2); SMB1 is single-controller, two-player support for SMB2 later
3c. BUS WIRING (src/bus.rs): add Controller(s) to NesBus, $4016 write = strobe, $4016/$4017 read = shift out one bit (open-bus for unused bits). Bus stays a trait; controller wiring lives in the concrete NesBus impl like everything else
3d. GUI WINDOW (src/render.rs + new deps): first external crates (egui + egui-winit + winit). main.rs owns the frame loop for ALL modes (nestest/blargg/cart/dbg/gui) - window is passive, renderer is dumb:
3b. INPUT - DONE (see Done section). src/input.rs: InputSource trait + MaskInput + Controller (strobe latch, shift-out on read, |0x80 filler so bits 9+ read 1) + Key enum + key_to_button mapping. 3 unit tests pass (strobe repeats A, A-first shift-out order, B-second). Bus wired: $4016/$4017 read + write_strobe; caught a match-ordering bug (controller arms were below the 0x4000..=0x401F range arm so unreachable - specific arms must come first). $4017 APU frame-counter bits ignored for now (no APU yet)
3c. BUS WIRING - DONE (done together with 3b): Controller in NesBus, $4016/$4017 read = shift out one bit, write = strobe. Open-bus handling for unused controller bits deferred
3d. NEXT: GUI WINDOW (src/render.rs + new deps): first external crates (egui + egui-winit + winit). main.rs owns the frame loop for ALL modes (nestest/blargg/cart/dbg/gui) - window is passive, renderer is dumb:
- Renderer trait gains two default no-op methods (PPM inherits them): fn should_close(&self) -> bool { false }, fn poll_input(&mut self, controller: &mut Controller) {}
- WindowRenderer: egui + winit impl of Renderer. present() converts palette indices -> RGB via index_to_rgb, uploads as texture, draws. poll_input() maps winit key events -> Controller bits. should_close() from winit close request
- Keyboard defaults: Z=A, X=B, Shift=Select, Enter=Start, Arrows=D-pad (key_to_button already in input.rs, winit KeyCode -> Key glue needed)
- PPM stays as headless/dbg output; renders/ no longer written for normal GUI runs (was ~1MB/frame, heavy)
- New run_gui <rom> mode: event pump -> one emulated frame -> present, 60fps
- Acceptance: walk Mario in the window, screen scrolls correctly between nametables (this is how scrolling gets verified - the scroll logic itself is already fixed and correct)