Skip to content →

y-scroll, v and t and ice climber

vertical scrolling (y-scroll)

To properly implement Y-Scroll, the NROM game “Ice Climber” is a great choice to test this, as this game will only scroll vertically on the nametables, and will even wrap.

So, to translate the current address with the offset of the scroll values, we will do something like this:

uint16_t scrolled_address = adr + (scroll_y / 8) * 0x20;

//	crossed NT 
if ( (adr & 0x400) != (scrolled_address & 0x400) ) {
	scrolled_address += 0x40;	//	skip 64 bytes of attribute table
	scrolled_address ^= 0x800;	//	XOR the bit, that changes NTs vertically
	scrolled_address ^= 0x400;      //      remove the line break
//	appears to be in AT area
else if (scrolled_address % 0x400 >= 0x3c0) 	{
	scrolled_address = (scrolled_address & 0x2800 ^ 0x800) + ((scrolled_address % 0x400) - 0x3c0);

But with our current implementation, of resetting the base nametable address, that we introduced to fix the UI flicker in “Super Mario Bros.”, we will experience something like this for “Ice Climber”:

Ice Climber – glitched “base nametable reset on VBlank”

Without the hack, we can see a “proper” vertical scroll (for now):

“proper” vertical scroll (no fine-scroll)

But this will introduce the UI flicker in “Super Mario Bros.” again – so how do we fix this?

UI flicker

register sharing – “v” and “t”

This is the point, where we need to account of a quirk that the NES has. The PPUADDR and the PPUSCROLL registers share internal registers, which leads to values being overwritten at certain points (see the table below).

v and t handling – credits to

The only values we ever use, are in “v”, while “t” is only for temporary calculations. The only thing interesting for us, is the base nametable address, that is going bonkers in our Mario UI (we could read the scroll values form “v” as well, but our current method works just as fine, so we will leave it like that). These are the actual values that “v” (and “t”) represent.

||| || ||||| +++++-- coarse X scroll 
||| || +++++-------- coarse Y scroll 
||| ++-------------- nametable select 
+++----------------- fine Y scroll 

So, we are only interested in the nametable bits. If we look at the table above, we can see that “t” is only copied to “v” on the second write to 0x2006 (PPUADDR), thus making the changed bits usable. And the only writes to “t”, that change the bits of the nametables, are the write to 0x2000 (PPUCTRL), and the first write to 0x2006 (PPUADDR). So, we can remember these values in a variable, and shoot this variable into our PPU_CTRL.base_nametable_address, making it available for the rest of our code.

//	set temp scroll values (also pass to real values)
tmp_PPUSCROLL_y &= 0xf8;
tmp_PPUSCROLL_y |= (adr >> 5);

When done correctly, “Super Mario Bros.” will run without the UI flicker in the second nametable – again, but properly.

fine scrolling – finishing “ice climber”

Since we have both horizontal and vertical scrolling working now, the only thing missing for “Ice Climber” is adding the fine scroll (which for horizontal scrolling was nothing but PPUSCROLL_x % 8).

Since we have the fine scroll value already at hand, the actual modification to our BG drawing routine is very small.

tile_id = (((r+PPUSCROLL_fine_y) / 8) * 32) + ((col + PPUSCROLL_x % 8) / 8);					//	sequential tile number
uint16_t natural_address = PPU_CTRL.base_nametable_address_value + tile_id;
uint16_t scrolled_address = translateScrolledAddress(natural_address, PPUSCROLL_x, PPUSCROLL_y, col, r, tile_id);
tile_nr = VRAM[scrolled_address];												//	tile ID at the current address
adr = PPU_CTRL.background_pattern_table_adr_value + (tile_nr * 0x10) + ((r+PPUSCROLL_fine_y) % 8);	//	adress of the tile in CHR RAM

So now, our vertical scrolling looks as smooth as we have our horizontal scrolling.

Fixed fine scroll vertical


Leave a Reply

Your email address will not be published. Required fields are marked *