PPU palettes
The palette for the background runs from VRAM $3F00 to $3F0F; the palette for the sprites runs from $3F10 to $3F1F. Each color takes up one byte.
$3F00 | Universal background color |
$3F01-$3F03 | Background palette 0 |
$3F05-$3F07 | Background palette 1 |
$3F09-$3F0B | Background palette 2 |
$3F0D-$3F0F | Background palette 3 |
$3F11-$3F13 | Sprite palette 0 |
$3F15-$3F17 | Sprite palette 1 |
$3F19-$3F1B | Sprite palette 2 |
$3F1D-$3F1F | Sprite palette 3 |
Addresses $3F04/$3F08/$3F0C can contain unique data, though these values are not used by the PPU when rendering. Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C.
Another way of looking at it:
43210 ||||| |||++- Pixel value from tile data |++--- Palette number from attribute table or OAM +----- Background/Sprite select
Like most early color game consoles, the NES palette is based on Hue/Saturation/Value
76543210 |||||||| ||||++++- Hue ||++----- Value ++------- Unimplemented, reads back as 0
Hue $0 is light gray, $1-$C are blue to red to green to cyan, $D is dark gray, and $E-$F are black. The canonical code for "black" is $0F. It works this way because of the way colors are represented in an NTSC or PAL signal, with the phase of a color subcarrier controlling the hue. For details, see NTSC video.
Note that most VS Unisystem arcade PPUs have completely different palettes, and Playchoice-10 PPUs render hue $D as black.
If rendering is disabled in PPUMASK ($2001), and the current VRAM address points in $3F00-$3FFF, this color will be shown on screen instead of the backdrop color. A loop that fills the palette will cause each color in turn to be shown on the screen. So to avoid horizontal rainbow bar glitches while loading the palette, wait for a real vertical blank first using an NMI technique.