package tests import ( "testing" "crecher.net/gones/v2/cart" "crecher.net/gones/v2/ppu" ) const ( black = 0xFF000000 // palette entry $0F white = 0xFFECEEEC // $30 red = 0xFF982220 // $16 blue = 0xFF4C9AEC // $21 ) // writes bytes through $2006/$2007 starting at addr, then points v back at 0 func writeVRAM(p *ppu.PPU, addr uint16, data ...uint8) { p.WriteRegister(0x2006, uint8(addr>>8)) p.WriteRegister(0x2006, uint8(addr)) for _, b := range data { p.WriteRegister(0x2007, b) } p.WriteRegister(0x2006, 0) p.WriteRegister(0x2006, 0) } // ticks two full frames so scroll registers have been copied from t at least once func runFrames(p *ppu.PPU) { for i := 0; i < 2*341*262; i++ { p.Tick() } } func pixel(p *ppu.PPU, x, y int) uint32 { return p.Framebuffer()[y*ppu.ScreenWidth+x] } // tile 1: row 0 is color 1 across, row 1 is color 2 across // palette 0: backdrop black, 1 = white, 2 = red. palette 1 color 1 = blue func newSceneWithTile1() *ppu.PPU { p, _ := newTestPPU(cart.Horizontal) writeVRAM(p, 0x0010, 0xFF, 0x00) writeVRAM(p, 0x0018, 0x00, 0xFF) writeVRAM(p, 0x3F00, 0x0F, 0x30, 0x16) writeVRAM(p, 0x3F05, 0x21) return p } func TestBackgroundDrawsBackdropWhenDisabled(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x2000, 1) runFrames(p) if got := pixel(p, 0, 0); got != black { t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(black)) } } func TestBackgroundTilePixels(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x2000, 1) p.WriteRegister(0x2001, 0x0A) // background + left 8 pixels runFrames(p) tests := []struct { x, y int want uint32 }{ {0, 0, white}, {7, 0, white}, {8, 0, black}, // next tile is tile 0, blank {0, 1, red}, {7, 1, red}, {0, 2, black}, // rows 2-7 of tile 1 are empty } for _, tt := range tests { if got := pixel(p, tt.x, tt.y); got != tt.want { t.Errorf("pixel(%d,%d) = %#08x, want %#08x", tt.x, tt.y, got, tt.want) } } } func TestBackgroundAttributeSelectsPalette(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x2000, 1, 1, 1, 1) writeVRAM(p, 0x23C0, 0x01) // top-left 16x16 quadrant uses palette 1 p.WriteRegister(0x2001, 0x0A) runFrames(p) if got := pixel(p, 0, 0); got != blue { t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(blue)) } if got := pixel(p, 15, 0); got != blue { t.Errorf("pixel(15,0) = %#08x, want %#08x", got, uint32(blue)) } if got := pixel(p, 16, 0); got != white { t.Errorf("pixel(16,0) = %#08x, want %#08x (next quadrant, palette 0)", got, uint32(white)) } } func TestBackgroundLeftClip(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x2000, 1, 1) p.WriteRegister(0x2001, 0x08) // background on, left 8 pixels hidden runFrames(p) if got := pixel(p, 0, 0); got != black { t.Errorf("pixel(0,0) = %#08x, want %#08x (clipped)", got, uint32(black)) } if got := pixel(p, 8, 0); got != white { t.Errorf("pixel(8,0) = %#08x, want %#08x", got, uint32(white)) } } func TestBackgroundFineXScroll(t *testing.T) { p, _ := newTestPPU(cart.Horizontal) writeVRAM(p, 0x0010, 0x01, 0x00) // only the rightmost pixel of row 0 is set writeVRAM(p, 0x3F00, 0x0F, 0x30) writeVRAM(p, 0x2000, 1) p.WriteRegister(0x2005, 7) p.WriteRegister(0x2005, 0) p.WriteRegister(0x2001, 0x0A) runFrames(p) if got := pixel(p, 0, 0); got != white { t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(white)) } if got := pixel(p, 1, 0); got != black { t.Errorf("pixel(1,0) = %#08x, want %#08x", got, uint32(black)) } } func TestBackgroundCoarseXScroll(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x2003, 1) // tile 1 in the 4th column p.WriteRegister(0x2005, 24) // coarse x = 3 p.WriteRegister(0x2005, 0) p.WriteRegister(0x2001, 0x0A) runFrames(p) if got := pixel(p, 0, 0); got != white { t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(white)) } if got := pixel(p, 8, 0); got != black { t.Errorf("pixel(8,0) = %#08x, want %#08x", got, uint32(black)) } } func TestBackgroundPatternTableSelect(t *testing.T) { p := newSceneWithTile1() writeVRAM(p, 0x1018, 0xFF) // tile 1 in the high pattern table, row 0 high plane only = color 2 writeVRAM(p, 0x2000, 1) p.WriteRegister(0x2000, 0x10) p.WriteRegister(0x2001, 0x0A) runFrames(p) if got := pixel(p, 0, 0); got != red { t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(red)) } }