Skip to main content

nes_sim/
ppu.rs

1use crate::cartridge::TVSystem;
2use crate::savestate::{SaveStateError, StateReader, StateWriter};
3
4pub const FRAME_WIDTH: usize = 256;
5pub const FRAME_HEIGHT: usize = 240;
6const VISIBLE_FRAME_PIXELS: usize = FRAME_WIDTH * FRAME_HEIGHT;
7
8const STATUS_SPRITE_OVERFLOW: u8 = 0x20;
9const STATUS_SPRITE_ZERO_HIT: u8 = 0x40;
10const STATUS_VBLANK: u8 = 0x80;
11const MASK_GRAYSCALE: u8 = 0x01;
12const MASK_SHOW_BG_LEFTMOST: u8 = 0x02;
13const MASK_SHOW_SPRITES_LEFTMOST: u8 = 0x04;
14const MASK_SHOW_BG: u8 = 0x08;
15const MASK_SHOW_SPRITES: u8 = 0x10;
16const CTRL_SPRITE_TABLE: u8 = 0x08;
17const CTRL_BG_TABLE: u8 = 0x10;
18const CTRL_VRAM_INCREMENT: u8 = 0x04;
19const CTRL_SPRITE_SIZE: u8 = 0x20;
20const CTRL_NMI_ENABLE: u8 = 0x80;
21const DOTS_PER_SCANLINE: u16 = 341;
22const VISIBLE_SCANLINES: i16 = 240;
23
24#[cfg(test)]
25const NES_RGB_PALETTE: [[u8; 3]; 64] = [
26    [84, 84, 84],
27    [0, 30, 116],
28    [8, 16, 144],
29    [48, 0, 136],
30    [68, 0, 100],
31    [92, 0, 48],
32    [84, 4, 0],
33    [60, 24, 0],
34    [32, 42, 0],
35    [8, 58, 0],
36    [0, 64, 0],
37    [0, 60, 0],
38    [0, 50, 60],
39    [0, 0, 0],
40    [0, 0, 0],
41    [0, 0, 0],
42    [152, 150, 152],
43    [8, 76, 196],
44    [48, 50, 236],
45    [92, 30, 228],
46    [136, 20, 176],
47    [160, 20, 100],
48    [152, 34, 32],
49    [120, 60, 0],
50    [84, 90, 0],
51    [40, 114, 0],
52    [8, 124, 0],
53    [0, 118, 40],
54    [0, 102, 120],
55    [0, 0, 0],
56    [0, 0, 0],
57    [0, 0, 0],
58    [236, 238, 236],
59    [76, 154, 236],
60    [120, 124, 236],
61    [176, 98, 236],
62    [228, 84, 236],
63    [236, 88, 180],
64    [236, 106, 100],
65    [212, 136, 32],
66    [160, 170, 0],
67    [116, 196, 0],
68    [76, 208, 32],
69    [56, 204, 108],
70    [56, 180, 204],
71    [60, 60, 60],
72    [0, 0, 0],
73    [0, 0, 0],
74    [236, 238, 236],
75    [168, 204, 236],
76    [188, 188, 236],
77    [212, 178, 236],
78    [236, 174, 236],
79    [236, 174, 212],
80    [236, 180, 176],
81    [228, 196, 144],
82    [204, 210, 120],
83    [180, 222, 120],
84    [168, 226, 144],
85    [152, 226, 180],
86    [160, 214, 228],
87    [160, 162, 160],
88    [0, 0, 0],
89    [0, 0, 0],
90];
91
92pub trait PPUBus {
93    fn ppu_read(&mut self, addr: u16) -> u8;
94    fn ppu_write(&mut self, addr: u16, data: u8);
95    fn check_a12(&mut self, _addr: u16, _ppu_cycle: u64) {}
96    fn notify_scanline(&mut self, _scanline: i16, _rendering_on: bool) {}
97    fn set_ppu_sprite_phase(&mut self, _sprite_phase: bool) {}
98}
99
100#[derive(Clone, Copy)]
101struct SpriteRenderData {
102    tile_id: u8,
103    row: u8,
104    x: u8,
105    attributes: u8,
106    pattern_lo: u8,
107    pattern_hi: u8,
108    sprite_zero: bool,
109}
110
111impl Default for SpriteRenderData {
112    fn default() -> Self {
113        Self {
114            tile_id: 0xFF,
115            row: 0,
116            x: 0xFF,
117            attributes: 0xFF,
118            pattern_lo: 0,
119            pattern_hi: 0,
120            sprite_zero: false,
121        }
122    }
123}
124
125pub struct PPU {
126    scanline: i16,
127    cycles: u16,
128    frame: u64,
129
130    oam: [u8; 256],
131    oam_addr: u8,
132
133    ctrl: u8,
134    mask: u8,
135    status: u8,
136    open_bus: u8,
137    vram_addr: u16,
138    temp_vram_addr: u16,
139    fine_x: u8,
140    write_latch: bool,
141    read_buffer: u8,
142    odd_frame: bool,
143    next_tile_id: u8,
144    next_tile_attr: u8,
145    next_tile_lsb: u8,
146    next_tile_msb: u8,
147    bg_pattern_shift_lo: u16,
148    bg_pattern_shift_hi: u16,
149    bg_attr_shift_lo: u16,
150    bg_attr_shift_hi: u16,
151
152    // parameters
153    tv_system: TVSystem,
154    num_scanlines: i16,
155    vblank_lines: i16,
156
157    loopy_v: u16,
158    loopy_t: u16,
159    bit_map: [u8; 0xF000],
160    bg_colors: [u8; 0x100],
161    bg_pixels: [u8; 0x100],
162    sprite_present: [bool; 0x100],
163    sprite_behind_bg: [bool; 0x100],
164    scanline_sprites: [SpriteRenderData; 8],
165    scanline_sprite_count: u8,
166    suppress_vblank: bool,
167
168    even: bool,
169    dot_clock: u64,
170
171    // Cached rendering state (updated when mask register is written)
172    bg_on: bool,
173    sprites_on: bool,
174    rendering_on: bool,
175}
176
177impl PPU {
178    pub fn new() -> Self {
179        Self {
180            oam: [0; 256],
181            oam_addr: 0,
182            frame: 0,
183            ctrl: 0,
184            mask: 0,
185            status: 0,
186            open_bus: 0,
187            vram_addr: 0,
188            temp_vram_addr: 0,
189            fine_x: 0,
190            write_latch: false,
191            read_buffer: 0,
192            scanline: 261,
193            cycles: 0,
194            odd_frame: false,
195            next_tile_id: 0,
196            next_tile_attr: 0,
197            next_tile_lsb: 0,
198            next_tile_msb: 0,
199            bg_pattern_shift_lo: 0,
200            bg_pattern_shift_hi: 0,
201            bg_attr_shift_lo: 0,
202            bg_attr_shift_hi: 0,
203            tv_system: TVSystem::NTSC,
204            num_scanlines: 262,
205            vblank_lines: 241,
206
207            loopy_v: 0,
208            loopy_t: 0,
209            bit_map: [0; 0xF000],
210            bg_colors: [0; 0x100],
211            bg_pixels: [0; 0x100],
212            sprite_present: [false; 0x100],
213            sprite_behind_bg: [false; 0x100],
214            scanline_sprites: [SpriteRenderData::default(); 8],
215            scanline_sprite_count: 0,
216            suppress_vblank: false,
217
218            even: false,
219            dot_clock: 0,
220
221            bg_on: false,
222            sprites_on: false,
223            rendering_on: false,
224        }
225    }
226
227    pub fn reset(&mut self) {
228        self.ctrl = 0;
229        self.mask = 0;
230        self.update_rendering_flags();
231        self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
232        self.open_bus = 0;
233        self.vram_addr = 0;
234        self.temp_vram_addr = 0;
235        self.fine_x = 0;
236        self.write_latch = false;
237        self.read_buffer = 0;
238        self.scanline = 261;
239        self.cycles = 0;
240        self.frame = 0;
241        self.odd_frame = false;
242        self.next_tile_id = 0;
243        self.next_tile_attr = 0;
244        self.next_tile_lsb = 0;
245        self.next_tile_msb = 0;
246        self.bg_pattern_shift_lo = 0;
247        self.bg_pattern_shift_hi = 0;
248        self.bg_attr_shift_lo = 0;
249        self.bg_attr_shift_hi = 0;
250        self.set_current_vram_addr(0);
251        self.set_temp_vram_addr(0);
252        self.bg_pixels = [0; 0x100];
253        self.sprite_present = [false; 0x100];
254        self.sprite_behind_bg = [false; 0x100];
255        self.scanline_sprites = [SpriteRenderData::default(); 8];
256        self.scanline_sprite_count = 0;
257        self.suppress_vblank = false;
258        self.dot_clock = 0;
259    }
260
261    pub fn set_parameters(&mut self, tv_system: TVSystem) {
262        self.tv_system = tv_system;
263        match tv_system {
264            TVSystem::NTSC => {
265                self.num_scanlines = 262;
266                self.vblank_lines = 241;
267            }
268            TVSystem::PAL => {
269                self.num_scanlines = 312;
270                self.vblank_lines = 241;
271            }
272            TVSystem::DENDY => {
273                self.num_scanlines = 312;
274                self.vblank_lines = 241;
275            }
276        }
277    }
278
279    #[cfg(test)]
280    pub fn cpu_read_register(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
281        self.cpu_read_register_timed(bus, addr, 0)
282    }
283
284    pub fn cpu_read_register_timed(
285        &mut self,
286        bus: &mut impl PPUBus,
287        addr: u16,
288        cpu_cycle_offset: u8,
289    ) -> u8 {
290        match addr {
291            0x2002 => self.read_status_timed(cpu_cycle_offset),
292            0x2004 => {
293                let data = self.read_oam_data();
294                self.open_bus = data;
295                data
296            }
297            0x2007 => self.read_data(bus),
298            _ => self.open_bus,
299        }
300    }
301
302    #[cfg(test)]
303    pub fn cpu_write_register(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
304        self.cpu_write_register_timed(bus, addr, data, 0);
305    }
306
307    pub fn cpu_write_register_timed(
308        &mut self,
309        bus: &mut impl PPUBus,
310        addr: u16,
311        data: u8,
312        cpu_cycle_offset: u8,
313    ) {
314        self.open_bus = data;
315        let (future_scanline, _, _) = self.predict_status_timing(u16::from(cpu_cycle_offset) * 3);
316
317        match addr {
318            0x2000 => {
319                self.ctrl = data;
320                self.set_temp_vram_addr(
321                    (self.temp_vram_addr & !0x0C00) | (((data as u16) & 0x03) << 10),
322                );
323            }
324            0x2001 => {
325                self.mask = data;
326                self.update_rendering_flags();
327            }
328            0x2003 => self.oam_addr = data,
329            0x2004 => self.write_oam_data_timed(data, future_scanline),
330            0x2005 => self.write_scroll(data),
331            0x2006 => self.write_addr(data),
332            0x2007 => self.write_data_timed(bus, data, future_scanline),
333            _ => {}
334        }
335    }
336
337    pub fn clock(&mut self, bus: &mut impl PPUBus) {
338        // Vblank 快速路径:跳过所有渲染检查
339        if self.scanline >= self.vblank_lines && self.scanline < self.num_scanlines - 1 {
340            if self.scanline == self.vblank_lines && self.cycles == 1 && !self.suppress_vblank {
341                self.status |= STATUS_VBLANK;
342            }
343            self.cycles += 1;
344            if self.cycles >= DOTS_PER_SCANLINE {
345                self.cycles = 0;
346                self.scanline += 1;
347                bus.notify_scanline(self.scanline, false);
348            }
349            self.dot_clock = self.dot_clock.wrapping_add(1);
350            return;
351        }
352
353        let visible_scanline = self.scanline < VISIBLE_SCANLINES;
354        let pre_render_scanline = self.scanline == self.num_scanlines - 1;
355        let render_scanline = visible_scanline || pre_render_scanline;
356        let visible_cycle = self.cycles < 256;
357        let fetch_cycle = visible_cycle || (320..337).contains(&self.cycles);
358
359        if self.scanline == self.num_scanlines - 1 && self.cycles == 1 {
360            self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
361            self.suppress_vblank = false;
362        }
363
364        if render_scanline && self.rendering_on() {
365            if visible_scanline && self.cycles == 0 {
366                self.sprite_present = [false; 0x100];
367                self.sprite_behind_bg = [false; 0x100];
368            }
369
370            if self.bg_on() && fetch_cycle {
371                self.update_bg_shifters();
372                bus.set_ppu_sprite_phase(false);
373                self.fetch_bg(bus);
374            }
375
376            if visible_scanline && visible_cycle {
377                self.draw_bg_pixel(self.cycles as i16, bus);
378                self.draw_sprite_pixel(self.cycles as i16, bus);
379            }
380
381            match self.cycles {
382                255 => self.increment_y(),
383                256 => {
384                    self.load_bg_shifters();
385                    self.transfer_x();
386                    if self.rendering_on() && (visible_scanline || pre_render_scanline) {
387                        self.eval_sprites(bus);
388                    }
389                }
390                279..=303 if pre_render_scanline => self.transfer_y(),
391                _ => {}
392            }
393
394            if (257..321).contains(&self.cycles) {
395                bus.set_ppu_sprite_phase(true);
396                self.fetch_sprite_data(bus);
397            }
398        }
399
400        if self.should_skip_odd_frame_cycle(pre_render_scanline) {
401            self.dot_clock = self.dot_clock.wrapping_add(1);
402            self.start_next_frame();
403            return;
404        }
405
406        self.cycles += 1;
407        if self.cycles >= DOTS_PER_SCANLINE {
408            self.cycles = 0;
409            self.scanline += 1;
410            bus.notify_scanline(self.scanline, self.rendering_on());
411
412            if self.scanline >= self.num_scanlines {
413                self.start_next_frame();
414            }
415        }
416        self.dot_clock = self.dot_clock.wrapping_add(1);
417    }
418
419    pub fn bg_on(&self) -> bool {
420        self.bg_on
421    }
422
423    pub fn sprites_on(&self) -> bool {
424        self.sprites_on
425    }
426
427    pub fn rendering_on(&self) -> bool {
428        self.rendering_on
429    }
430
431    fn update_rendering_flags(&mut self) {
432        self.bg_on = (self.mask & MASK_SHOW_BG) != 0;
433        self.sprites_on = (self.mask & MASK_SHOW_SPRITES) != 0;
434        self.rendering_on = self.bg_on || self.sprites_on;
435    }
436
437    pub fn nmi_line(&self) -> bool {
438        (self.ctrl & CTRL_NMI_ENABLE) != 0 && (self.status & STATUS_VBLANK) != 0
439    }
440
441    pub fn in_vblank(&self) -> bool {
442        (self.status & STATUS_VBLANK) != 0
443    }
444
445    pub fn scanline(&self) -> i16 {
446        self.scanline
447    }
448
449    pub fn frame(&self) -> u64 {
450        self.frame
451    }
452
453    pub fn frame_pixels(&self) -> &[u8] {
454        &self.bit_map[..VISIBLE_FRAME_PIXELS]
455    }
456
457    #[cfg(test)]
458    pub fn frame_rgb(&self) -> Vec<u8> {
459        let mut rgb = Vec::with_capacity(VISIBLE_FRAME_PIXELS * 3);
460        for &pixel in self.frame_pixels() {
461            rgb.extend_from_slice(&palette_index_to_rgb(pixel));
462        }
463        rgb
464    }
465
466    pub fn tv_system(&self) -> TVSystem {
467        self.tv_system
468    }
469
470    pub(crate) fn write_oam_dma(&mut self, data: u8) {
471        self.write_oam_data(data);
472    }
473
474    #[cfg(test)]
475    pub fn oam_byte(&self, index: u8) -> u8 {
476        self.oam[index as usize]
477    }
478
479    pub fn oam_addr(&self) -> u8 {
480        self.oam_addr
481    }
482
483    pub(crate) fn save_state(&self, writer: &mut StateWriter) {
484        writer.write_i16(self.scanline);
485        writer.write_u16(self.cycles);
486        writer.write_u64(self.frame);
487        writer.write_bytes(&self.oam);
488        writer.write_u8(self.oam_addr);
489        writer.write_u8(self.ctrl);
490        writer.write_u8(self.mask);
491        writer.write_u8(self.status);
492        writer.write_u8(self.open_bus);
493        writer.write_u16(self.vram_addr);
494        writer.write_u16(self.temp_vram_addr);
495        writer.write_u8(self.fine_x);
496        writer.write_bool(self.write_latch);
497        writer.write_u8(self.read_buffer);
498        writer.write_bool(self.odd_frame);
499        writer.write_u8(self.next_tile_id);
500        writer.write_u8(self.next_tile_attr);
501        writer.write_u8(self.next_tile_lsb);
502        writer.write_u8(self.next_tile_msb);
503        writer.write_u16(self.bg_pattern_shift_lo);
504        writer.write_u16(self.bg_pattern_shift_hi);
505        writer.write_u16(self.bg_attr_shift_lo);
506        writer.write_u16(self.bg_attr_shift_hi);
507        writer.write_u8(match self.tv_system {
508            TVSystem::NTSC => 0,
509            TVSystem::PAL => 1,
510            TVSystem::DENDY => 2,
511        });
512        writer.write_i16(self.num_scanlines);
513        writer.write_i16(self.vblank_lines);
514        writer.write_u16(self.loopy_v);
515        writer.write_u16(self.loopy_t);
516        writer.write_bytes(&self.bit_map);
517        writer.write_bytes(&self.bg_colors);
518        writer.write_bytes(&self.bg_pixels);
519        for &present in &self.sprite_present {
520            writer.write_bool(present);
521        }
522        for &behind in &self.sprite_behind_bg {
523            writer.write_bool(behind);
524        }
525        for sprite in &self.scanline_sprites {
526            writer.write_u8(sprite.tile_id);
527            writer.write_u8(sprite.row);
528            writer.write_u8(sprite.x);
529            writer.write_u8(sprite.attributes);
530            writer.write_u8(sprite.pattern_lo);
531            writer.write_u8(sprite.pattern_hi);
532            writer.write_bool(sprite.sprite_zero);
533        }
534        writer.write_u8(self.scanline_sprite_count);
535        writer.write_bool(self.suppress_vblank);
536        writer.write_bool(self.even);
537        writer.write_u64(self.dot_clock);
538    }
539
540    pub(crate) fn load_state(
541        &mut self,
542        reader: &mut StateReader<'_>,
543    ) -> Result<(), SaveStateError> {
544        self.scanline = reader.read_i16()?;
545        self.cycles = reader.read_u16()?;
546        self.frame = reader.read_u64()?;
547        reader.read_bytes_into(&mut self.oam)?;
548        self.oam_addr = reader.read_u8()?;
549        self.ctrl = reader.read_u8()?;
550        self.mask = reader.read_u8()?;
551        self.status = reader.read_u8()?;
552        self.open_bus = reader.read_u8()?;
553        self.vram_addr = reader.read_u16()?;
554        self.temp_vram_addr = reader.read_u16()?;
555        self.fine_x = reader.read_u8()?;
556        self.write_latch = reader.read_bool()?;
557        self.read_buffer = reader.read_u8()?;
558        self.odd_frame = reader.read_bool()?;
559        self.next_tile_id = reader.read_u8()?;
560        self.next_tile_attr = reader.read_u8()?;
561        self.next_tile_lsb = reader.read_u8()?;
562        self.next_tile_msb = reader.read_u8()?;
563        self.bg_pattern_shift_lo = reader.read_u16()?;
564        self.bg_pattern_shift_hi = reader.read_u16()?;
565        self.bg_attr_shift_lo = reader.read_u16()?;
566        self.bg_attr_shift_hi = reader.read_u16()?;
567        self.tv_system = match reader.read_u8()? {
568            0 => TVSystem::NTSC,
569            1 => TVSystem::PAL,
570            2 => TVSystem::DENDY,
571            _ => {
572                return Err(SaveStateError::InvalidData(
573                    "invalid TV system in PPU state",
574                ));
575            }
576        };
577        self.num_scanlines = reader.read_i16()?;
578        self.vblank_lines = reader.read_i16()?;
579        self.loopy_v = reader.read_u16()?;
580        self.loopy_t = reader.read_u16()?;
581        reader.read_bytes_into(&mut self.bit_map)?;
582        reader.read_bytes_into(&mut self.bg_colors)?;
583        reader.read_bytes_into(&mut self.bg_pixels)?;
584        for present in &mut self.sprite_present {
585            *present = reader.read_bool()?;
586        }
587        for behind in &mut self.sprite_behind_bg {
588            *behind = reader.read_bool()?;
589        }
590        for sprite in &mut self.scanline_sprites {
591            sprite.tile_id = reader.read_u8()?;
592            sprite.row = reader.read_u8()?;
593            sprite.x = reader.read_u8()?;
594            sprite.attributes = reader.read_u8()?;
595            sprite.pattern_lo = reader.read_u8()?;
596            sprite.pattern_hi = reader.read_u8()?;
597            sprite.sprite_zero = reader.read_bool()?;
598        }
599        self.scanline_sprite_count = reader.read_u8()?;
600        self.suppress_vblank = reader.read_bool()?;
601        self.even = reader.read_bool()?;
602        self.dot_clock = reader.read_u64()?;
603        self.update_rendering_flags();
604        Ok(())
605    }
606
607    fn read_status_timed(&mut self, cpu_cycle_offset: u8) -> u8 {
608        let ppu_cycle_offset = u16::from(cpu_cycle_offset) * 3;
609        let (future_scanline, future_cycles, future_status) =
610            self.predict_status_timing(ppu_cycle_offset);
611
612        let mut status_bits = future_status;
613        // Only apply imminent sprite-0-hit lookahead for sub-cycle peeks.
614        // Wider lookahead windows were causing unstable split timing in games
615        // that poll PPUSTATUS at instruction-level granularity.
616        if cpu_cycle_offset <= 1
617            && (status_bits & STATUS_SPRITE_ZERO_HIT) == 0
618            && self.predict_sprite_zero_hit_within_offset(ppu_cycle_offset)
619        {
620            status_bits |= STATUS_SPRITE_ZERO_HIT;
621        }
622        if future_scanline == self.vblank_lines && future_cycles == 1 {
623            status_bits &= !STATUS_VBLANK;
624            self.suppress_vblank = true;
625        }
626
627        let status = (status_bits & 0xE0) | (self.open_bus & 0x1F);
628        self.status &= !STATUS_VBLANK;
629        self.write_latch = false;
630        self.open_bus = status;
631        status
632    }
633
634    fn predict_status_timing(&self, ppu_cycle_offset: u16) -> (i16, u16, u8) {
635        let mut scanline = self.scanline;
636        let mut cycles = self.cycles;
637        let mut odd_frame = self.odd_frame;
638        let mut status = self.status;
639        let mut suppress_vblank = self.suppress_vblank;
640
641        for _ in 0..ppu_cycle_offset {
642            if scanline == self.vblank_lines && cycles == 1 && !suppress_vblank {
643                status |= STATUS_VBLANK;
644            }
645
646            if scanline == self.num_scanlines - 1 && cycles == 1 {
647                status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
648                suppress_vblank = false;
649            }
650
651            let pre_render_scanline = scanline == self.num_scanlines - 1;
652            let skip_odd_frame_cycle = self.num_scanlines == 262
653                && pre_render_scanline
654                && self.rendering_on()
655                && odd_frame
656                && cycles == DOTS_PER_SCANLINE - 2;
657
658            if skip_odd_frame_cycle {
659                scanline = 0;
660                cycles = 0;
661                odd_frame = !odd_frame;
662                continue;
663            }
664
665            cycles += 1;
666            if cycles >= DOTS_PER_SCANLINE {
667                cycles = 0;
668                scanline += 1;
669                if scanline >= self.num_scanlines {
670                    scanline = 0;
671                    odd_frame = !odd_frame;
672                }
673            }
674        }
675
676        (scanline, cycles, status)
677    }
678
679    fn predict_sprite_zero_hit_within_offset(&self, ppu_cycle_offset: u16) -> bool {
680        if ppu_cycle_offset == 0
681            || !self.bg_on()
682            || !self.sprites_on()
683            || self.scanline < 0
684            || self.scanline >= VISIBLE_SCANLINES
685        {
686            return false;
687        }
688
689        let Some(sprite) = self
690            .scanline_sprites
691            .iter()
692            .take(self.scanline_sprite_count as usize)
693            .find(|sprite| sprite.sprite_zero)
694            .copied()
695        else {
696            return false;
697        };
698
699        let mut cycles = self.cycles;
700        let mut scanline = self.scanline;
701        let mut odd_frame = self.odd_frame;
702
703        let mut bg_pattern_shift_lo = self.bg_pattern_shift_lo;
704        let mut bg_pattern_shift_hi = self.bg_pattern_shift_hi;
705        let mut bg_attr_shift_lo = self.bg_attr_shift_lo;
706        let mut bg_attr_shift_hi = self.bg_attr_shift_hi;
707
708        let show_leftmost_bg = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
709        let show_leftmost_sprites = (self.mask & MASK_SHOW_SPRITES_LEFTMOST) != 0;
710
711        for _ in 0..ppu_cycle_offset {
712            let visible_scanline = scanline < VISIBLE_SCANLINES;
713            let pre_render_scanline = scanline == self.num_scanlines - 1;
714            let render_scanline = visible_scanline || pre_render_scanline;
715            let visible_cycle = cycles < 256;
716            let fetch_cycle = visible_cycle || (320..337).contains(&cycles);
717
718            if render_scanline && self.rendering_on() {
719                if visible_scanline && visible_cycle {
720                    let x = cycles as usize;
721
722                    let bg_pixel = if show_leftmost_bg || x >= 8 {
723                        let bit = 0x8000 >> self.fine_x;
724                        let lo = u8::from((bg_pattern_shift_lo & bit) != 0);
725                        let hi = u8::from((bg_pattern_shift_hi & bit) != 0);
726                        (hi << 1) | lo
727                    } else {
728                        0
729                    };
730
731                    if (show_leftmost_sprites || x >= 8) && x < 255 {
732                        let sprite_x = usize::from(sprite.x);
733                        if x >= sprite_x && x < sprite_x + 8 {
734                            let sprite_pixel = self.sprite_pixel(&sprite, (x - sprite_x) as u8);
735                            if sprite_pixel != 0 && bg_pixel != 0 {
736                                return true;
737                            }
738                        }
739                    }
740                }
741
742                if self.bg_on() && fetch_cycle {
743                    bg_pattern_shift_lo <<= 1;
744                    bg_pattern_shift_hi <<= 1;
745                    bg_attr_shift_lo <<= 1;
746                    bg_attr_shift_hi <<= 1;
747                }
748
749                if self.bg_on() && fetch_cycle && (cycles & 0x07) == 0 {
750                    bg_pattern_shift_lo =
751                        (bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
752                    bg_pattern_shift_hi =
753                        (bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
754
755                    let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
756                        0xFF
757                    } else {
758                        0x00
759                    };
760                    let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
761                        0xFF
762                    } else {
763                        0x00
764                    };
765
766                    bg_attr_shift_lo = (bg_attr_shift_lo & 0xFF00) | attr_lo;
767                    bg_attr_shift_hi = (bg_attr_shift_hi & 0xFF00) | attr_hi;
768                }
769            }
770
771            let skip_odd_frame_cycle = self.num_scanlines == 262
772                && pre_render_scanline
773                && self.rendering_on()
774                && odd_frame
775                && cycles == DOTS_PER_SCANLINE - 2;
776
777            if skip_odd_frame_cycle {
778                scanline = 0;
779                cycles = 0;
780                odd_frame = !odd_frame;
781                continue;
782            }
783
784            cycles += 1;
785            if cycles >= DOTS_PER_SCANLINE {
786                cycles = 0;
787                scanline += 1;
788                if scanline >= self.num_scanlines {
789                    scanline = 0;
790                    odd_frame = !odd_frame;
791                }
792            }
793        }
794
795        false
796    }
797
798    fn read_data(&mut self, bus: &mut impl PPUBus) -> u8 {
799        let addr = self.loopy_v & 0x3FFF;
800        let data = if addr >= 0x3F00 {
801            self.read_buffer = self.ppu_read_bus(bus, addr.wrapping_sub(0x1000));
802            let palette_data = self.ppu_read_bus(bus, addr);
803            self.mask_palette_color(palette_data)
804        } else {
805            let buffered = self.read_buffer;
806            self.read_buffer = self.ppu_read_bus_exposed(bus, addr);
807            buffered
808        };
809
810        self.increment_data_access_vram_addr();
811        self.open_bus = data;
812        data
813    }
814
815    fn write_data_timed(&mut self, bus: &mut impl PPUBus, data: u8, effective_scanline: i16) {
816        let addr = self.loopy_v & 0x3FFF;
817        self.ppu_write_bus_exposed(bus, addr, data);
818        self.increment_data_access_vram_addr_on_scanline(effective_scanline);
819    }
820
821    fn write_scroll(&mut self, data: u8) {
822        if !self.write_latch {
823            self.fine_x = data & 0x07;
824            self.set_temp_vram_addr((self.temp_vram_addr & !0x001F) | ((data as u16) >> 3));
825            self.write_latch = true;
826        } else {
827            self.set_temp_vram_addr(
828                (self.temp_vram_addr & !0x73E0)
829                    | ((((data as u16) >> 3) & 0x1F) << 5)
830                    | (((data as u16) & 0x07) << 12),
831            );
832            self.write_latch = false;
833        }
834    }
835
836    fn write_addr(&mut self, data: u8) {
837        if !self.write_latch {
838            self.set_temp_vram_addr((self.temp_vram_addr & 0x00FF) | (((data as u16) & 0x3F) << 8));
839            self.write_latch = true;
840        } else {
841            self.set_temp_vram_addr((self.temp_vram_addr & 0x7F00) | data as u16);
842            self.set_current_vram_addr(self.temp_vram_addr);
843            self.write_latch = false;
844        }
845    }
846
847    fn increment_vram_addr(&mut self) {
848        let increment = if (self.ctrl & CTRL_VRAM_INCREMENT) != 0 {
849            32
850        } else {
851            1
852        };
853        self.set_current_vram_addr(self.loopy_v.wrapping_add(increment));
854    }
855
856    fn increment_data_access_vram_addr_on_scanline(&mut self, scanline: i16) {
857        if self.rendering_vram_access_active_on_scanline(scanline) {
858            self.increment_x();
859            self.increment_y();
860        } else {
861            self.increment_vram_addr();
862        }
863    }
864
865    fn increment_data_access_vram_addr(&mut self) {
866        self.increment_data_access_vram_addr_on_scanline(self.scanline);
867    }
868
869    fn write_oam_data(&mut self, data: u8) {
870        self.write_oam_data_timed(data, self.scanline);
871    }
872
873    fn write_oam_data_timed(&mut self, data: u8, effective_scanline: i16) {
874        if self.rendering_oam_access_active_on_scanline(effective_scanline) {
875            self.oam_addr = self.oam_addr.wrapping_add(4);
876            return;
877        }
878
879        self.oam[self.oam_addr as usize] = data;
880        self.oam_addr = self.oam_addr.wrapping_add(1);
881    }
882
883    fn read_oam_data(&self) -> u8 {
884        if self.rendering_oam_clear_phase() {
885            return 0xFF;
886        }
887
888        let data = self.oam[self.oam_addr as usize];
889        if (self.oam_addr & 0x03) == 0x02 {
890            data & 0xE3
891        } else {
892            data
893        }
894    }
895
896    fn fetch_bg(&mut self, bus: &mut impl PPUBus) {
897        match self.cycles & 0x07 {
898            0 => {
899                self.load_bg_shifters();
900                self.fetch_nt(bus);
901            }
902            2 => {
903                let addr = 0x23C0
904                    | (self.loopy_v & 0x0C00)
905                    | ((self.loopy_v >> 4) & 0x38)
906                    | ((self.loopy_v >> 2) & 0x07);
907                let attr = self.ppu_read_bus(bus, addr);
908                let shift = ((self.loopy_v >> 4) & 0x04) | (self.loopy_v & 0x02);
909                self.next_tile_attr = (attr >> shift) as u8 & 0x03;
910            }
911            4 => {
912                let addr = self.bg_pattern_addr(self.next_tile_id);
913                self.next_tile_lsb = self.ppu_read_bus_exposed(bus, addr);
914            }
915            6 => {
916                let addr = self.bg_pattern_addr(self.next_tile_id).wrapping_add(8);
917                self.next_tile_msb = self.ppu_read_bus_exposed(bus, addr);
918            }
919            7 => self.increment_x(),
920            _ => {}
921        }
922    }
923
924    fn fetch_nt(&mut self, bus: &mut impl PPUBus) {
925        let addr = 0x2000 | (self.loopy_v & 0x0FFF);
926        self.next_tile_id = self.ppu_read_bus(bus, addr);
927    }
928
929    fn eval_sprites(&mut self, _bus: &mut impl PPUBus) {
930        let target_scanline = if self.scanline == self.num_scanlines - 1 {
931            0
932        } else {
933            (self.scanline as u8).wrapping_add(1)
934        };
935
936        self.scanline_sprites = [SpriteRenderData::default(); 8];
937        self.scanline_sprite_count = 0;
938
939        let sprite_height = self.sprite_height();
940        let mut overflow_start = None;
941        for index in 0..64 {
942            let row = match self.sprite_row_for_scanline(index, target_scanline, sprite_height) {
943                Some(row) => row,
944                None => continue,
945            };
946
947            if self.scanline_sprite_count >= 8 {
948                overflow_start = Some(index);
949                break;
950            }
951
952            let base = index * 4;
953            let tile = self.oam[base + 1];
954            let attributes = self.oam[base + 2];
955            let x = self.oam[base + 3];
956
957            self.scanline_sprites[self.scanline_sprite_count as usize] = SpriteRenderData {
958                tile_id: tile,
959                row,
960                x,
961                attributes,
962                pattern_lo: 0,
963                pattern_hi: 0,
964                sprite_zero: index == 0,
965            };
966            self.scanline_sprite_count += 1;
967
968            if self.scanline_sprite_count >= 8 {
969                overflow_start = Some(index + 1);
970                break;
971            }
972        }
973
974        if let Some(start_index) = overflow_start {
975            if self.sprite_overflow_bugged(start_index, target_scanline, sprite_height) {
976                self.status |= STATUS_SPRITE_OVERFLOW;
977            }
978        }
979    }
980
981    fn sprite_row_for_scanline(
982        &self,
983        sprite_index: usize,
984        target_scanline: u8,
985        sprite_height: u8,
986    ) -> Option<u8> {
987        let sprite_y = self.oam[sprite_index * 4];
988        let sprite_top = if sprite_y == 0xFF {
989            0
990        } else {
991            u16::from(sprite_y) + 1
992        };
993        let target = u16::from(target_scanline);
994        if target < sprite_top {
995            None
996        } else {
997            let row = target - sprite_top;
998            if row >= u16::from(sprite_height) {
999                None
1000            } else {
1001                Some(row as u8)
1002            }
1003        }
1004    }
1005
1006    fn sprite_overflow_bugged(
1007        &self,
1008        start_index: usize,
1009        target_scanline: u8,
1010        sprite_height: u8,
1011    ) -> bool {
1012        let mut n = start_index;
1013        let mut m = 0usize;
1014        while n < 64 {
1015            let value = self.oam[n * 4 + m];
1016            let sprite_top = if value == 0xFF {
1017                0
1018            } else {
1019                u16::from(value) + 1
1020            };
1021            let target = u16::from(target_scanline);
1022            if target >= sprite_top && (target - sprite_top) < u16::from(sprite_height) {
1023                return true;
1024            }
1025
1026            n += 1;
1027            m = (m + 1) & 0x03;
1028        }
1029
1030        false
1031    }
1032
1033    fn fetch_sprite_data(&mut self, bus: &mut impl PPUBus) {
1034        let slot = ((self.cycles - 257) / 8) as usize;
1035        if slot >= 8 {
1036            return;
1037        }
1038
1039        let subcycle = (self.cycles - 257) & 0x07;
1040        match subcycle {
1041            0 | 2 => {
1042                let _ = self.ppu_read_bus(bus, 0x2000 | (self.loopy_v & 0x0FFF));
1043            }
1044            4 => {
1045                let sprite = self.scanline_sprites[slot];
1046                let addr = self.sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row);
1047                self.scanline_sprites[slot].pattern_lo = self.ppu_read_bus_exposed(bus, addr);
1048            }
1049            6 => {
1050                let sprite = self.scanline_sprites[slot];
1051                let addr = self
1052                    .sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row)
1053                    .wrapping_add(8);
1054                self.scanline_sprites[slot].pattern_hi = self.ppu_read_bus_exposed(bus, addr);
1055            }
1056            _ => {}
1057        }
1058    }
1059
1060    fn draw_bg_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1061        let x = offset as usize;
1062        let y = self.scanline as usize;
1063        if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1064            return;
1065        }
1066
1067        let show_leftmost = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1068        let bg_pixel = if self.bg_on() && (show_leftmost || x >= 8) {
1069            self.current_bg_pixel()
1070        } else {
1071            0
1072        };
1073        let bg_palette = if bg_pixel == 0 {
1074            0
1075        } else {
1076            self.current_bg_palette()
1077        };
1078
1079        let palette_addr = if bg_pixel == 0 {
1080            0x3F00
1081        } else {
1082            0x3F00 | (u16::from(bg_palette) << 2) | u16::from(bg_pixel)
1083        };
1084        let palette_data = self.ppu_read_bus(bus, palette_addr);
1085        let color = self.mask_palette_color(palette_data);
1086        let pixel_index = y * 256 + x;
1087
1088        let sprite_in_front = self.sprite_present[x] && !self.sprite_behind_bg[x];
1089        let sprite_visible_behind_bg =
1090            self.sprite_present[x] && self.sprite_behind_bg[x] && bg_pixel == 0;
1091        if !sprite_in_front && !sprite_visible_behind_bg {
1092            self.bit_map[pixel_index] = color;
1093        }
1094        self.bg_colors[x] = color;
1095        self.bg_pixels[x] = bg_pixel;
1096    }
1097
1098    fn draw_sprite_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1099        if !self.sprites_on() {
1100            return;
1101        }
1102
1103        let x = offset as usize;
1104        let y = self.scanline as usize;
1105        if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1106            return;
1107        }
1108
1109        if x < 8 && (self.mask & MASK_SHOW_SPRITES_LEFTMOST) == 0 {
1110            return;
1111        }
1112
1113        for sprite in self
1114            .scanline_sprites
1115            .iter()
1116            .take(self.scanline_sprite_count as usize)
1117            .copied()
1118        {
1119            let sprite_x = usize::from(sprite.x);
1120            if x < sprite_x || x >= sprite_x + 8 {
1121                continue;
1122            }
1123
1124            let sprite_pixel = self.sprite_pixel(&sprite, (x - sprite_x) as u8);
1125            if sprite_pixel == 0 {
1126                continue;
1127            }
1128
1129            let bg_pixel_for_hit = self.bg_pixel_visible_for_sprite_zero_hit(x);
1130            if sprite.sprite_zero && bg_pixel_for_hit != 0 && x < 255 {
1131                self.status |= STATUS_SPRITE_ZERO_HIT;
1132            }
1133
1134            let bg_pixel_visible = self.bg_pixel_visible_to_sprite(x);
1135            let behind_background = (sprite.attributes & 0x20) != 0;
1136            if behind_background && bg_pixel_visible != 0 {
1137                break;
1138            }
1139
1140            let palette = sprite.attributes & 0x03;
1141            let palette_addr = 0x3F10 | (u16::from(palette) << 2) | u16::from(sprite_pixel);
1142            let palette_data = self.ppu_read_bus(bus, palette_addr);
1143            let color = self.mask_palette_color(palette_data);
1144            self.bit_map[y * 256 + x] = color;
1145            self.sprite_present[x] = true;
1146            self.sprite_behind_bg[x] = behind_background;
1147            break;
1148        }
1149    }
1150
1151    fn set_current_vram_addr(&mut self, addr: u16) {
1152        self.vram_addr = addr;
1153        self.loopy_v = addr;
1154    }
1155
1156    fn set_temp_vram_addr(&mut self, addr: u16) {
1157        self.temp_vram_addr = addr;
1158        self.loopy_t = addr;
1159    }
1160
1161    fn should_skip_odd_frame_cycle(&self, pre_render_scanline: bool) -> bool {
1162        self.num_scanlines == 262
1163            && pre_render_scanline
1164            && self.rendering_on()
1165            && self.odd_frame
1166            && self.cycles == DOTS_PER_SCANLINE - 2
1167    }
1168
1169    fn rendering_vram_access_active_on_scanline(&self, scanline: i16) -> bool {
1170        self.rendering_on() && (scanline < VISIBLE_SCANLINES || scanline == self.num_scanlines - 1)
1171    }
1172
1173    fn rendering_oam_access_active(&self) -> bool {
1174        self.rendering_oam_access_active_on_scanline(self.scanline)
1175    }
1176
1177    fn rendering_oam_access_active_on_scanline(&self, scanline: i16) -> bool {
1178        self.rendering_on() && scanline < VISIBLE_SCANLINES
1179    }
1180
1181    fn rendering_oam_clear_phase(&self) -> bool {
1182        self.rendering_oam_access_active() && (1..=64).contains(&self.cycles)
1183    }
1184
1185    fn start_next_frame(&mut self) {
1186        self.scanline = 0;
1187        self.cycles = 0;
1188        self.frame += 1;
1189        self.odd_frame = !self.odd_frame;
1190        self.even = !self.even;
1191    }
1192
1193    fn ppu_read_bus(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1194        let addr = addr & 0x3FFF;
1195        self.observe_mapper_a12_line(bus, addr);
1196        bus.ppu_read(addr)
1197    }
1198
1199    fn ppu_read_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1200        let addr = addr & 0x3FFF;
1201        self.observe_mapper_a12_line(bus, addr);
1202        bus.ppu_read(addr)
1203    }
1204
1205    fn ppu_write_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
1206        let addr = addr & 0x3FFF;
1207        self.observe_mapper_a12_line(bus, addr);
1208        bus.ppu_write(addr, data);
1209    }
1210
1211    fn observe_mapper_a12_line(&mut self, bus: &mut impl PPUBus, addr: u16) {
1212        match addr {
1213            0x0000..=0x1FFF => bus.check_a12(addr, self.dot_clock),
1214            // MMC3 watches the PPU A12 line itself, so nametable/attribute/garbage fetches
1215            // still drive the line low even though they must not create CHR high pulses.
1216            0x2000..=0x2FFF => bus.check_a12(0x0000, self.dot_clock),
1217            _ => {}
1218        }
1219    }
1220
1221    fn bg_pattern_addr(&self, tile_id: u8) -> u16 {
1222        let table = if (self.ctrl & CTRL_BG_TABLE) != 0 {
1223            0x1000
1224        } else {
1225            0x0000
1226        };
1227        let fine_y = (self.loopy_v >> 12) & 0x0007;
1228        table | (u16::from(tile_id) << 4) | fine_y
1229    }
1230
1231    fn sprite_pattern_addr(&self, tile_id: u8, attributes: u8, row: u8) -> u16 {
1232        let mut fine_y = u16::from(row);
1233        let sprite_height = u16::from(self.sprite_height());
1234        if (attributes & 0x80) != 0 {
1235            fine_y = sprite_height - 1 - fine_y;
1236        }
1237
1238        if sprite_height == 16 {
1239            let table = u16::from(tile_id & 0x01) << 12;
1240            let tile = u16::from(tile_id & 0xFE) + (fine_y >> 3);
1241            table | (tile << 4) | (fine_y & 0x07)
1242        } else {
1243            let table = if (self.ctrl & CTRL_SPRITE_TABLE) != 0 {
1244                0x1000
1245            } else {
1246                0x0000
1247            };
1248            table | (u16::from(tile_id) << 4) | fine_y
1249        }
1250    }
1251
1252    fn sprite_height(&self) -> u8 {
1253        if (self.ctrl & CTRL_SPRITE_SIZE) != 0 {
1254            16
1255        } else {
1256            8
1257        }
1258    }
1259
1260    fn sprite_pixel(&self, sprite: &SpriteRenderData, offset: u8) -> u8 {
1261        let bit = if (sprite.attributes & 0x40) != 0 {
1262            offset
1263        } else {
1264            7 - offset
1265        };
1266        let lo = (sprite.pattern_lo >> bit) & 0x01;
1267        let hi = (sprite.pattern_hi >> bit) & 0x01;
1268        (hi << 1) | lo
1269    }
1270
1271    fn mask_palette_color(&self, color: u8) -> u8 {
1272        let color = color & 0x3F;
1273        if (self.mask & MASK_GRAYSCALE) != 0 {
1274            color & 0x30
1275        } else {
1276            color
1277        }
1278    }
1279
1280    fn update_bg_shifters(&mut self) {
1281        if !self.bg_on() {
1282            return;
1283        }
1284
1285        self.bg_pattern_shift_lo <<= 1;
1286        self.bg_pattern_shift_hi <<= 1;
1287        self.bg_attr_shift_lo <<= 1;
1288        self.bg_attr_shift_hi <<= 1;
1289    }
1290
1291    fn load_bg_shifters(&mut self) {
1292        self.bg_pattern_shift_lo =
1293            (self.bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
1294        self.bg_pattern_shift_hi =
1295            (self.bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
1296
1297        let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
1298            0xFF
1299        } else {
1300            0x00
1301        };
1302        let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
1303            0xFF
1304        } else {
1305            0x00
1306        };
1307
1308        self.bg_attr_shift_lo = (self.bg_attr_shift_lo & 0xFF00) | attr_lo;
1309        self.bg_attr_shift_hi = (self.bg_attr_shift_hi & 0xFF00) | attr_hi;
1310    }
1311
1312    fn current_bg_pixel(&self) -> u8 {
1313        let bit = 0x8000 >> self.fine_x;
1314        let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1315        let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1316        (hi << 1) | lo
1317    }
1318
1319    fn current_bg_palette(&self) -> u8 {
1320        let bit = 0x8000 >> self.fine_x;
1321        let lo = u8::from((self.bg_attr_shift_lo & bit) != 0);
1322        let hi = u8::from((self.bg_attr_shift_hi & bit) != 0);
1323        (hi << 1) | lo
1324    }
1325
1326    fn bg_pixel_visible_to_sprite(&self, x: usize) -> u8 {
1327        // draw_bg_pixel runs before draw_sprite_pixel in the same cycle and
1328        // writes the committed BG pixel value into bg_pixels[x], so the sprite
1329        // priority check can use it directly — no shift-register lookahead needed.
1330        self.bg_pixels[x]
1331    }
1332
1333    fn bg_pixel_visible_for_sprite_zero_hit(&self, x: usize) -> u8 {
1334        if !self.bg_on() {
1335            return 0;
1336        }
1337
1338        let show_leftmost = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1339        if !show_leftmost && x < 8 {
1340            return 0;
1341        }
1342
1343        let bit = 0x8000 >> self.fine_x;
1344        let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1345        let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1346        (hi << 1) | lo
1347    }
1348
1349    fn increment_x(&mut self) {
1350        if !self.rendering_on() {
1351            return;
1352        }
1353
1354        if (self.loopy_v & 0x001F) == 31 {
1355            self.loopy_v &= !0x001F;
1356            self.loopy_v ^= 0x0400;
1357        } else {
1358            self.loopy_v = self.loopy_v.wrapping_add(1);
1359        }
1360
1361        self.vram_addr = self.loopy_v;
1362    }
1363
1364    fn increment_y(&mut self) {
1365        if !self.rendering_on() {
1366            return;
1367        }
1368
1369        if (self.loopy_v & 0x7000) != 0x7000 {
1370            self.loopy_v = self.loopy_v.wrapping_add(0x1000);
1371        } else {
1372            self.loopy_v &= !0x7000;
1373            let mut coarse_y = (self.loopy_v & 0x03E0) >> 5;
1374            if coarse_y == 29 {
1375                coarse_y = 0;
1376                self.loopy_v ^= 0x0800;
1377            } else if coarse_y == 31 {
1378                coarse_y = 0;
1379            } else {
1380                coarse_y += 1;
1381            }
1382            self.loopy_v = (self.loopy_v & !0x03E0) | (coarse_y << 5);
1383        }
1384
1385        self.vram_addr = self.loopy_v;
1386    }
1387
1388    fn transfer_x(&mut self) {
1389        if !self.rendering_on() {
1390            return;
1391        }
1392
1393        self.loopy_v = (self.loopy_v & !0x041F) | (self.loopy_t & 0x041F);
1394        self.vram_addr = self.loopy_v;
1395    }
1396
1397    fn transfer_y(&mut self) {
1398        if !self.rendering_on() {
1399            return;
1400        }
1401
1402        self.loopy_v = (self.loopy_v & !0x7BE0) | (self.loopy_t & 0x7BE0);
1403        self.vram_addr = self.loopy_v;
1404    }
1405}
1406
1407#[cfg(test)]
1408pub(crate) fn palette_index_to_rgb(index: u8) -> [u8; 3] {
1409    NES_RGB_PALETTE[(index & 0x3F) as usize]
1410}
1411
1412impl Default for PPU {
1413    fn default() -> Self {
1414        Self::new()
1415    }
1416}
1417
1418#[cfg(test)]
1419mod tests;