1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
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)
}
}
|