package ppu // more tests for the scroll registers since they can't be checked outside // the package without a $2006 write that would mess up the bits under test import ( "testing" "crecher.net/gones/v2/cart" ) // nilMapper is enough for these tests type nilMapper struct{} func (nilMapper) CPURead(addr uint16) uint8 { return 0 } func (nilMapper) CPUWrite(addr uint16, v uint8) {} func (nilMapper) PPURead(addr uint16) uint8 { return 0 } func (nilMapper) PPUWrite(addr uint16, v uint8) {} func (nilMapper) Mirroring() cart.Mirroring { return cart.Horizontal } func newPPU() *PPU { return New(nilMapper{}, cart.Horizontal) } func TestCtrlNametableBitsSetT(t *testing.T) { p := newPPU() p.WriteRegister(0x2000, 0x03) // nametable select = 3 -> t bits 10-11 if got := p.t & 0x0C00; got != 0x0C00 { t.Errorf("t&0x0C00 = %#04x, want 0x0C00", got) } p.WriteRegister(0x2000, 0x00) if got := p.t & 0x0C00; got != 0 { t.Errorf("t&0x0C00 = %#04x, want 0 after clearing nametable select", got) } } func TestScrollFirstWriteSetsCoarseXAndFineX(t *testing.T) { p := newPPU() p.WriteRegister(0x2005, 0b11101_011) // coarseX=0b11101=29, fineX=0b011=3 if got := p.t & 0x001F; got != 29 { t.Errorf("coarse X in t = %d, want 29", got) } if p.x != 3 { t.Errorf("fine X = %d, want 3", p.x) } if !p.w { t.Error("w should be set after the first $2005 write") } } func TestScrollSecondWriteSetsCoarseYAndFineY(t *testing.T) { p := newPPU() p.WriteRegister(0x2005, 0x00) // first write, establishes w=true p.WriteRegister(0x2005, 0b10110_101) // coarseY=0b10110=22, fineY=0b101=5 if got := (p.t >> 5) & 0x1F; got != 22 { t.Errorf("coarse Y in t = %d, want 22", got) } if got := (p.t >> 12) & 0x7; got != 5 { t.Errorf("fine Y in t = %d, want 5", got) } if p.w { t.Error("w should be cleared after the second $2005 write") } } func TestScrollWriteDoesNotTouchNametableBits(t *testing.T) { p := newPPU() p.WriteRegister(0x2000, 0x03) // select nametable 3 p.WriteRegister(0x2005, 0xFF) p.WriteRegister(0x2005, 0xFF) if got := p.t & 0x0C00; got != 0x0C00 { t.Errorf("PPUSCROLL writes clobbered the nametable-select bits: t&0x0C00 = %#04x, want 0x0C00", got) } } func TestAddrWriteSequenceSetsTThenV(t *testing.T) { p := newPPU() p.v = 0x1234 // poison v so we can tell the first write didn't touch it p.WriteRegister(0x2006, 0x3D) // high byte: only the low 6 bits are kept, bit 14 cleared if got := p.t & 0xFF00; got != 0x3D00 { t.Errorf("t high byte = %#04x, want 0x3D00", got) } if p.v != 0x1234 { t.Errorf("v changed after the first $2006 write: %#04x", p.v) } p.WriteRegister(0x2006, 0x56) if p.t != 0x3D56 { t.Errorf("t = %#04x, want 0x3D56", p.t) } if p.v != 0x3D56 { t.Errorf("v = %#04x, want 0x3D56 (should be copied from t on the second write)", p.v) } } func TestAddrHighByteClearsBit14(t *testing.T) { p := newPPU() p.WriteRegister(0x2006, 0xFF) // top 2 bits of the written byte are discarded if p.t&0x4000 != 0 { t.Errorf("t bit 14 should always be cleared by a $2006 high-byte write, t = %#04x", p.t) } if got := p.t & 0xFF00; got != 0x3F00 { t.Errorf("t high byte = %#04x, want 0x3F00 (0xFF masked to 6 bits)", got) } }