text: /***************************************************************************
  
    Atari TIA video emulation
  
  ***************************************************************************/
  
  #include "driver.h"
  #include "tia.h"
  #include "sound/tiaintf.h"
  
  #define HMOVE_INACTIVE		-200
  #define PLAYER_GFX_SLOTS	4
  
  // Per player graphic
  // - pixel number to start drawing from (0-7, from GRPx) / number of pixels drawn from GRPx
  // - display position to start drawing
  // - size to use
  struct player_gfx {
  	int	start_pixel[PLAYER_GFX_SLOTS];
  	int start_drawing[PLAYER_GFX_SLOTS];
  	int size[PLAYER_GFX_SLOTS];
  	int skipclip[PLAYER_GFX_SLOTS];
  };
  
  static struct player_gfx p0gfx;
  static struct player_gfx p1gfx;
  
  static UINT64 frame_cycles;
  static UINT64 paddle_cycles;
  
  static int horzP0;
  static int horzP1;
  static int horzM0;
  static int horzM1;
  static int horzBL;
  static int motclkP0;
  static int motclkP1;
  static int motclkM0;
  static int motclkM1;
  static int motclkBL;
  static int startP0;
  static int startP1;
  static int startM0;
  static int startM1;
  static int skipclipP0;
  static int skipclipP1;
  static int skipM0delay;
  static int skipM1delay;
  
  static int current_bitmap;
  
  static int prev_x;
  static int prev_y;
  
  static UINT8 VSYNC;
  static UINT8 VBLANK;
  static UINT8 COLUP0;
  static UINT8 COLUP1;
  static UINT8 COLUBK;
  static UINT8 COLUPF;
  static UINT8 CTRLPF;
  static UINT8 GRP0;
  static UINT8 GRP1;
  static UINT8 REFP0;
  static UINT8 REFP1;
  static UINT8 HMP0;
  static UINT8 HMP1;
  static UINT8 HMM0;
  static UINT8 HMM1;
  static UINT8 HMBL;
  static UINT8 VDELP0;
  static UINT8 VDELP1;
  static UINT8 VDELBL;
  static UINT8 NUSIZ0;
  static UINT8 NUSIZ1;
  static UINT8 ENAM0;
  static UINT8 ENAM1;
  static UINT8 ENABL;
  static UINT8 CXM0P;
  static UINT8 CXM1P;
  static UINT8 CXP0FB;
  static UINT8 CXP1FB;
  static UINT8 CXM0FB;
  static UINT8 CXM1FB;
  static UINT8 CXBLPF;
  static UINT8 CXPPMM;
  static UINT8 RESMP0;
  static UINT8 RESMP1;
  static UINT8 PF0;
  static UINT8 PF1;
  static UINT8 PF2;
  static UINT8 INPT4;
  static UINT8 INPT5;
  
  static UINT8 prevGRP0;
  static UINT8 prevGRP1;
  static UINT8 prevENABL;
  
  static int HMOVE_started;
  static int HMOVE_started_previous;
  static UINT8 HMP0_latch;
  static UINT8 HMP1_latch;
  static UINT8 HMM0_latch;
  static UINT8 HMM1_latch;
  static UINT8 HMBL_latch;
  static UINT8 REFLECT;		/* Should playfield be reflected or not */
  static UINT8 NUSIZx_changed;
  
  static bitmap_t *helper[3];
  
  static UINT16 screen_height;
  
  static const int nusiz[8][3] =
  {
  	{ 1, 1, 0 },
  	{ 2, 1, 1 },
  	{ 2, 1, 3 },
  	{ 3, 1, 1 },
  	{ 2, 1, 7 },
  	{ 1, 2, 0 },
  	{ 3, 1, 3 },
  	{ 1, 4, 0 }
  };
  
  static read16_space_func	tia_read_input_port;
  static read8_space_func	tia_get_databus;
  static write16_space_func	tia_vsync_callback;
  
  static void extend_palette(running_machine *machine) {
  	int	i,j;
  
  	for( i = 0; i < 128; i ++ )
  	{
  		rgb_t	new_rgb = palette_get_color( machine, i );
  		UINT8	new_r = RGB_RED( new_rgb );
  		UINT8	new_g = RGB_GREEN( new_rgb );
  		UINT8	new_b = RGB_BLUE( new_rgb );
  
  		for ( j = 0; j < 128; j++ )
  		{
  			rgb_t	old_rgb = palette_get_color( machine, j );
  			UINT8	old_r = RGB_RED( old_rgb );
  			UINT8	old_g = RGB_GREEN( old_rgb );
  			UINT8	old_b = RGB_BLUE( old_rgb );
  
  			palette_set_color_rgb(machine, ( ( i + 1 ) << 7 ) | j,
  				( new_r + old_r ) / 2,
  				( new_g + old_g ) / 2,
  				( new_b + old_b ) / 2 );
  		}
  	}
  }
  
  PALETTE_INIT( tia_NTSC )
  {
  	int i, j;
  
  	static const double color[16][2] =
  	{
  		{  0.000,  0.000 },
  		{  0.144, -0.189 },
  		{  0.231, -0.081 },
  		{  0.243,  0.032 },
  		{  0.217,  0.121 },
  		{  0.117,  0.216 },
  		{  0.021,  0.233 },
  		{ -0.066,  0.196 },
  		{ -0.139,  0.134 },
  		{ -0.182,  0.062 },
  		{ -0.175, -0.022 },
  		{ -0.136, -0.100 },
  		{ -0.069, -0.150 },
  		{  0.005, -0.159 },
  		{  0.071, -0.125 },
  		{  0.124, -0.089 }
  	};
  
  	for (i = 0; i < 16; i++)
  	{
  		double I = color[i][0];
  		double Q = color[i][1];
  
  		for (j = 0; j < 8; j++)
  		{
  			double Y = j / 7.0;
  
  			double R = Y + 0.956 * I + 0.621 * Q;
  			double G = Y - 0.272 * I - 0.647 * Q;
  			double B = Y - 1.106 * I + 1.703 * Q;
  
  			R = pow(R, 0.9) / pow(1, 0.9);
  			G = pow(G, 0.9) / pow(1, 0.9);
  			B = pow(B, 0.9) / pow(1, 0.9);
  
  			if (R < 0) R = 0;
  			if (G < 0) G = 0;
  			if (B < 0) B = 0;
  
  			if (R > 1) R = 1;
  			if (G > 1) G = 1;
  			if (B > 1) B = 1;
  
  			palette_set_color_rgb(machine,8 * i + j,
  				(UINT8) (255 * R + 0.5),
  				(UINT8) (255 * G + 0.5),
  				(UINT8) (255 * B + 0.5));
  		}
  	}
  	extend_palette( machine );
  }
  
  
  PALETTE_INIT( tia_PAL )
  {
  	int i, j;
  
  	static const double color[16][2] =
  	{
  		{  0.000,  0.000 },
  		{  0.000,  0.000 },
  		{ -0.222,  0.032 },
  		{ -0.199, -0.027 },
  		{ -0.173,  0.117 },
  		{ -0.156, -0.197 },
  		{ -0.094,  0.200 },
  		{ -0.071, -0.229 },
  		{  0.070,  0.222 },
  		{  0.069, -0.204 },
  		{  0.177,  0.134 },
  		{  0.163, -0.130 },
  		{  0.219,  0.053 },
  		{  0.259, -0.042 },
  		{  0.000,  0.000 },
  		{  0.000,  0.000 }
  	};
  
  	for (i = 0; i < 16; i++)
  	{
  		double U = color[i][0];
  		double V = color[i][1];
  
  		for (j = 0; j < 8; j++)
  		{
  			double Y = j / 7.0;
  
  			double R = Y + 1.403 * V;
  			double G = Y - 0.344 * U - 0.714 * V;
  			double B = Y + 1.770 * U;
  
  			R = pow(R, 1.2) / pow(1, 1.2);
  			G = pow(G, 1.2) / pow(1, 1.2);
  			B = pow(B, 1.2) / pow(1, 1.2);
  
  			if (R < 0) R = 0;
  			if (G < 0) G = 0;
  			if (B < 0) B = 0;
  
  			if (R > 1) R = 1;
  			if (G > 1) G = 1;
  			if (B > 1) B = 1;
  
  			palette_set_color_rgb(machine,8 * i + j,
  				(UINT8) (255 * R + 0.5),
  				(UINT8) (255 * G + 0.5),
  				(UINT8) (255 * B + 0.5));
  		}
  	}
  	extend_palette( machine );
  }
  
  
  VIDEO_START( tia )
  {
  	int cx = video_screen_get_width(machine->primary_screen);
  
  	screen_height = video_screen_get_height(machine->primary_screen);
  	helper[0] = auto_bitmap_alloc(machine, cx, TIA_MAX_SCREEN_HEIGHT, video_screen_get_format(machine->primary_screen));
  	helper[1] = auto_bitmap_alloc(machine, cx, TIA_MAX_SCREEN_HEIGHT, video_screen_get_format(machine->primary_screen));
  	helper[2] = auto_bitmap_alloc(machine, cx, TIA_MAX_SCREEN_HEIGHT, video_screen_get_format(machine->primary_screen));
  }
  
  
  VIDEO_UPDATE( tia )
  {
  	screen_height = video_screen_get_height(screen);
  	copybitmap(bitmap, helper[2], 0, 0, 0, 0, cliprect);
  	return 0;
  }
  
  
  static void draw_sprite_helper(UINT8* p, UINT8 *col, struct player_gfx *gfx,
  	UINT8 GRP, UINT8 COLUP, UINT8 REFP)
  {
  	int i;
  	int j;
  	int k;
  
  	if (REFP & 8)
  	{
  		GRP = BITSWAP8(GRP, 0, 1, 2, 3, 4, 5, 6, 7);
  	}
  
  	for (i = 0; i < PLAYER_GFX_SLOTS; i++)
  	{
  		int start_pos = gfx->start_drawing[i];
  		for (j = gfx->start_pixel[i]; j < 8; j++)
  		{
  			for (k = 0; k < gfx->size[i]; k++)
  			{
  				if (GRP & (0x80 >> j))
  				{
  					if ( start_pos < 160 || ! gfx->skipclip[i] ) {
  						p[start_pos % 160] = COLUP >> 1;
  						col[start_pos % 160] = COLUP >> 1;
  					}
  				}
  				start_pos++;
  			}
  		}
  	}
  }
  
  
  static void draw_missile_helper(UINT8* p, UINT8* col, int horz, int skipdelay, int latch, int start,
  	UINT8 RESMP, UINT8 ENAM, UINT8 NUSIZ, UINT8 COLUM)
  {
  	int num = nusiz[NUSIZ & 7][0];
  	int skp = nusiz[NUSIZ & 7][2];
  
  	int width = 1 << ((NUSIZ >> 4) & 3);
  
  	int i;
  	int j;
  
  	for (i = 0; i < num; i++)
  	{
  		if ( i == 0 )
  			horz -= skipdelay;
  		if ( i == 1 )
  			horz += skipdelay;
  		if ( i > 0 || start ) {
  			for (j = 0; j < width; j++)
  			{
  				if ((ENAM & 2) && !(RESMP & 2))
  				{
  					if ( latch ) {
  						switch ( horz % 4 ) {
  						case 1:
  							if ( horz >= 0 )
  							{
  								if ( horz < 156 ) {
  									p[(horz + 1) % 160] = COLUM >> 1;
  									col[(horz + 1) % 160] = COLUM >> 1;
  								}
  								p[horz % 160] = COLUM >> 1;
  								col[horz % 160] = COLUM >> 1;
  							}
  							break;
  						case 2:
  						case 3:
  							if ( horz >= 0 )
  							{
  								p[horz % 160] = COLUM >> 1;
  								col[horz % 160] = COLUM >> 1;
  							}
  							break;
  						}
  					} else {
  						if ( horz >= 0 )
  						{
  							p[horz % 160] = COLUM >> 1;
  							col[horz % 160] = COLUM >> 1;
  						}
  					}
  				}
  
  				horz++;
  			}
  		} else {
  			horz+= width;
  		}
  
  		horz += 8 * (skp + 1) - width;
  	}
  }
  
  
  static void draw_playfield_helper(UINT8* p, UINT8* col, int horz,
  	UINT8 COLU, UINT8 REFPF)
  {
  	UINT32 PF =
  		(BITSWAP8(PF0, 0, 1, 2, 3, 4, 5, 6, 7) << 0x10) |
  		(BITSWAP8(PF1, 7, 6, 5, 4, 3, 2, 1, 0) << 0x08) |
  		(BITSWAP8(PF2, 0, 1, 2, 3, 4, 5, 6, 7) << 0x00);
  
  	int i;
  	int j;
  
  	if (REFPF)
  	{
  		UINT32 swap = 0;
  
  		for (i = 0; i < 20; i++)
  		{
  			swap <<= 1;
  
  			if (PF & 1)
  			{
  				swap |= 1;
  			}
  
  			PF >>= 1;
  		}
  
  		PF = swap;
  	}
  
  	for (i = 0; i < 20; i++)
  	{
  		for (j = 0; j < 4; j++)
  		{
  			if (PF & (0x80000 >> i))
  			{
  				p[horz] = COLU >> 1;
  				col[horz] = COLU >> 1;
  			}
  
  			horz++;
  		}
  	}
  }
  
  
  static void draw_ball_helper(UINT8* p, UINT8* col, int horz, UINT8 ENAB)
  {
  	int width = 1 << ((CTRLPF >> 4) & 3);
  
  	int i;
  
  	for (i = 0; i < width; i++)
  	{
  		if (ENAB & 2)
  		{
  			p[horz % 160] = COLUPF >> 1;
  			col[horz % 160] = COLUPF >> 1;
  		}
  
  		horz++;
  	}
  }
  
  
  static void drawS0(UINT8* p, UINT8* col)
  {
  	draw_sprite_helper(p, col, &p0gfx, (VDELP0 & 1) ? prevGRP0 : GRP0, COLUP0, REFP0);
  }
  
  
  static void drawS1(UINT8* p, UINT8* col)
  {
  	draw_sprite_helper(p, col, &p1gfx, (VDELP1 & 1) ? prevGRP1 : GRP1, COLUP1, REFP1);
  }
  
  
  static void drawM0(UINT8* p, UINT8* col)
  {
  	draw_missile_helper(p, col, horzM0, skipM0delay, HMM0_latch, startM0, RESMP0, ENAM0, NUSIZ0, COLUP0);
  }
  
  
  static void drawM1(UINT8* p, UINT8* col)
  {
  	draw_missile_helper(p, col, horzM1, skipM1delay, HMM1_latch, startM1, RESMP1, ENAM1, NUSIZ1, COLUP1);
  }
  
  
  static void drawBL(UINT8* p, UINT8* col)
  {
  	draw_ball_helper(p, col, horzBL, (VDELBL & 1) ? prevENABL : ENABL);
  }
  
  
  static void drawPF(UINT8* p, UINT8 *col)
  {
  	draw_playfield_helper(p, col, 0,
  		((CTRLPF & 6) == 2) ? COLUP0 : COLUPF, 0);
  
  	draw_playfield_helper(p, col, 80,
  		((CTRLPF & 6) == 2) ? COLUP1 : COLUPF, REFLECT);
  }
  
  
  static int collision_check(UINT8* p1, UINT8* p2, int x1, int x2)
  {
  	int i;
  
  	for (i = x1; i < x2; i++)
  	{
  		if (p1[i] != 0xFF && p2[i] != 0xFF)
  		{
  			return 1;
  		}
  	}
  
  	return 0;
  }
  
  
  INLINE int current_x(const address_space *space)
  {
  	return 3 * ((cpu_get_total_cycles(space->cpu) - frame_cycles) % 76) - 68;
  }
  
  
  INLINE int current_y(const address_space *space)
  {
  	return (cpu_get_total_cycles(space->cpu) - frame_cycles) / 76;
  }
  
  
  static void setup_pXgfx(void)
  {
  	int i;
  	for ( i = 0; i < PLAYER_GFX_SLOTS; i++ )
  	{
  		if ( i < nusiz[NUSIZ0 & 7][0] && i >= ( startP0 ? 0 : 1 ) )
  		{
  			p0gfx.size[i] = nusiz[NUSIZ0 & 7][1];
  			if ( i )
  			{
  				p0gfx.start_drawing[i] = ( horzP0 + (p0gfx.size[i] > 1 ? 1 : 0)
  										 + i * 8 * ( nusiz[NUSIZ0 & 7][2] + p0gfx.size[i] ) ) % 160;
  				p0gfx.skipclip[i] = 0;
  			}
  			else
  			{
  				p0gfx.start_drawing[i] = horzP0 + (p0gfx.size[i] > 1 ? 1 : 0 );
  				p0gfx.skipclip[i] = skipclipP0;
  			}
  			p0gfx.start_pixel[i] = 0;
  		}
  		else
  		{
  			p0gfx.start_pixel[i] = 8;
  		}
  		if ( i < nusiz[NUSIZ1 & 7][0] && i >= ( startP1 ? 0 : 1 ) )
  		{
  			p1gfx.size[i] = nusiz[NUSIZ1 & 7][1];
  			if ( i )
  			{
  				p1gfx.start_drawing[i] = ( horzP1 + (p1gfx.size[i] > 1 ? 1 : 0)
  										 + i * 8 * ( nusiz[NUSIZ1 & 7][2] + p1gfx.size[i] ) ) % 160;
  				p1gfx.skipclip[i] = 0;
  			}
  			else
  			{
  				p1gfx.start_drawing[i] = horzP1 + (p1gfx.size[i] > 1 ? 1 : 0);
  				p1gfx.skipclip[i] = skipclipP1;
  			}
  			p1gfx.start_pixel[i] = 0;
  		}
  		else
  		{
  			p1gfx.start_pixel[i] = 8;
  		}
  	}
  }
  
  static void update_bitmap(int next_x, int next_y)
  {
  	int x;
  	int y;
  
  	UINT8 linePF[160];
  	UINT8 lineP0[160];
  	UINT8 lineP1[160];
  	UINT8 lineM0[160];
  	UINT8 lineM1[160];
  	UINT8 lineBL[160];
  
  	UINT8 temp[160];
  
  	if (prev_y >= next_y && prev_x >= next_x)
  	{
  		return;
  	}
  
  	memset(linePF, 0xFF, sizeof linePF);
  	memset(lineP0, 0xFF, sizeof lineP0);
  	memset(lineP1, 0xFF, sizeof lineP1);
  	memset(lineM0, 0xFF, sizeof lineM0);
  	memset(lineM1, 0xFF, sizeof lineM1);
  	memset(lineBL, 0xFF, sizeof lineBL);
  
  	if (VBLANK & 2)
  	{
  		memset(temp, 0, 160);
  	}
  	else
  	{
  		memset(temp, COLUBK >> 1, 160);
  
  		if (CTRLPF & 4)
  		{
  			drawS1(temp, lineP1);
  			drawM1(temp, lineM1);
  			drawS0(temp, lineP0);
  			drawM0(temp, lineM0);
  			drawPF(temp, linePF);
  			drawBL(temp, lineBL);
  		}
  		else
  		{
  			drawPF(temp, linePF);
  			drawBL(temp, lineBL);
  			drawS1(temp, lineP1);
  			drawM1(temp, lineM1);
  			drawS0(temp, lineP0);
  			drawM0(temp, lineM0);
  		}
  	}
  
  	for (y = prev_y; y <= next_y; y++)
  	{
  		UINT16* p;
  
  		int x1 = prev_x;
  		int x2 = next_x;
  		int colx1;
  
  		/* Check if we have crossed a line boundary */
  		if ( y !=  prev_y ) {
  			int redraw_line = 0;
  
  			HMOVE_started_previous = HMOVE_INACTIVE;
  
  			if ( HMOVE_started != HMOVE_INACTIVE ) {
  				/* Apply pending motion clocks from a HMOVE initiated during the scanline */
  				if ( HMOVE_started >= 97 && HMOVE_started < 157 ) {
  					horzP0 -= motclkP0;
  					horzP1 -= motclkP1;
  					horzM0 -= motclkM0;
  					horzM1 -= motclkM1;
  					horzBL -= motclkBL;
  					if (horzP0 < 0)
  						horzP0 += 160;
  					if (horzP1 < 0)
  						horzP1 += 160;
  					if (horzM0 < 0)
  						horzM0 += 160;
  					if (horzM1 < 0)
  						horzM1 += 160;
  					if (horzBL < 0)
  						horzBL += 160;
  					HMOVE_started_previous = HMOVE_started;
  				}
  				HMOVE_started = HMOVE_INACTIVE;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if the playfield reflect bit was changed after the center of the screen */
  			if ( REFLECT != ( CTRLPF & 0x01 ) ) {
  				REFLECT = CTRLPF & 0x01;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if a RESPx or NUSIZx occured during the last line */
  			if ( ! startP0 || ! startP1 || ! startM0 || ! startM1) {
  				startP0 = 1;
  				startP1 = 1;
  				startM0 = 1;
  				startM1 = 1;
  
  				redraw_line = 1;
  			}
  
  			if ( skipclipP0 ) {
  				skipclipP0--;
  				redraw_line = 1;
  			}
  
  			if ( skipclipP1 ) {
  				skipclipP1--;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if HMP0 latch is still set */
  			if ( HMP0_latch ) {
  				horzP0 -= 17;
  				if ( horzP0 < 0 )
  					horzP0 += 160;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if HMP1 latch is still set */
  			if ( HMP1_latch ) {
  				horzP1 -= 17;
  				if ( horzP1 < 0 )
  					horzP1 += 160;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if HMM0 latch is still set */
  			if ( HMM0_latch ) {
  				horzM0 -= 17;
  				if ( horzM0 < 0 )
  					horzM0 += 160;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if HMM1 latch is still set */
  			if ( HMM1_latch ) {
  				horzM1 -= 17;
  				if ( horzM1 < 0 )
  					horzM1 += 160;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if HMBL latch is still set */
  			if ( HMBL_latch ) {
  				horzBL -= 17;
  				if ( horzBL < 0 )
  					horzBL += 160;
  				redraw_line = 1;
  			}
  
  			/* Redraw line if NUSIZx data was changed */
  			if ( NUSIZx_changed ) {
  				NUSIZx_changed = 0;
  				redraw_line = 1;
  			}
  
  			if ( skipM0delay || skipM1delay ) {
  				skipM0delay = 0;
  				skipM1delay = 0;
  				redraw_line = 1;
  			}
  
  			if ( redraw_line ) {
  				if (VBLANK & 2)
  				{
  					setup_pXgfx();
  					memset(temp, 0, 160);
  				}
  				else
  				{
  					memset(linePF, 0xFF, sizeof linePF);
  					memset(lineP0, 0xFF, sizeof lineP0);
  					memset(lineP1, 0xFF, sizeof lineP1);
  					memset(lineM0, 0xFF, sizeof lineM0);
  					memset(lineM1, 0xFF, sizeof lineM1);
  					memset(lineBL, 0xFF, sizeof lineBL);
  
  					memset(temp, COLUBK >> 1, 160);
  
  					setup_pXgfx();
  
  					if (CTRLPF & 4)
  					{
  						drawS1(temp, lineP1);
  						drawM1(temp, lineM1);
  						drawS0(temp, lineP0);
  						drawM0(temp, lineM0);
  						drawPF(temp, linePF);
  						drawBL(temp, lineBL);
  					}
  					else
  					{
  						drawPF(temp, linePF);
  						drawBL(temp, lineBL);
  						drawS1(temp, lineP1);
  						drawM1(temp, lineM1);
  						drawS0(temp, lineP0);
  						drawM0(temp, lineM0);
  					}
  				}
  			}
  		}
  		if (y != prev_y || x1 < 0)
  		{
  			x1 = 0;
  		}
  		if (y != next_y || x2 > 160)
  		{
  			x2 = 160;
  		}
  
  		/* Collision detection also takes place under the extended hblank area */
  		colx1 = ( x1 == 8 && HMOVE_started != HMOVE_INACTIVE ) ? 0 : x1;
  
  		if (collision_check(lineM0, lineP1, colx1, x2))
  			CXM0P |= 0x80;
  		if (collision_check(lineM0, lineP0, colx1, x2))
  			CXM0P |= 0x40;
  		if (collision_check(lineM1, lineP0, colx1, x2))
  			CXM1P |= 0x80;
  		if (collision_check(lineM1, lineP1, colx1, x2))
  			CXM1P |= 0x40;
  		if (collision_check(lineP0, linePF, colx1, x2))
  			CXP0FB |= 0x80;
  		if (collision_check(lineP0, lineBL, colx1, x2))
  			CXP0FB |= 0x40;
  		if (collision_check(lineP1, linePF, colx1, x2))
  			CXP1FB |= 0x80;
  		if (collision_check(lineP1, lineBL, colx1, x2))
  			CXP1FB |= 0x40;
  		if (collision_check(lineM0, linePF, colx1, x2))
  			CXM0FB |= 0x80;
  		if (collision_check(lineM0, lineBL, colx1, x2))
  			CXM0FB |= 0x40;
  		if (collision_check(lineM1, linePF, colx1, x2))
  			CXM1FB |= 0x80;
  		if (collision_check(lineM1, lineBL, colx1, x2))
  			CXM1FB |= 0x40;
  		if (collision_check(lineBL, linePF, colx1, x2))
  			CXBLPF |= 0x80;
  		if (collision_check(lineP0, lineP1, colx1, x2))
  			CXPPMM |= 0x80;
  		if (collision_check(lineM0, lineM1, colx1, x2))
  			CXPPMM |= 0x40;
  
  		p = BITMAP_ADDR16(helper[current_bitmap], y % screen_height, 34);
  
  		for (x = x1; x < x2; x++)
  		{
  			p[x] = temp[x];
  		}
  
  		if ( x2 == 160 && y % screen_height == (screen_height - 1) ) {
  			int	t_y;
  			for ( t_y = 0; t_y < helper[2]->height; t_y++ ) {
  				UINT16*	l0 = BITMAP_ADDR16( helper[current_bitmap], t_y, 0 );
  				UINT16*	l1 = BITMAP_ADDR16( helper[1 - current_bitmap], t_y, 0 );
  				UINT16*	l2 = BITMAP_ADDR16( helper[2], t_y, 0 );
  				int t_x;
  				for( t_x = 0; t_x < helper[2]->width; t_x++ ) {
  					if ( l0[t_x] != l1[t_x] ) {
  						/* Combine both entries */
  						l2[t_x] = ( ( l0[t_x] + 1 ) << 7 ) | l1[t_x];
  					} else {
  						l2[t_x] = l0[t_x];
  					}
  				}
  			}
  			current_bitmap ^= 1;
  		}
  	}
  
  	prev_x = next_x;
  	prev_y = next_y;
  }
  
  
  static WRITE8_HANDLER( WSYNC_w )
  {
  	int cycles = cpu_get_total_cycles(space->cpu) - frame_cycles;
  
  	if (cycles % 76)
  	{
  		cpu_adjust_icount(space->cpu, cycles % 76 - 76);
  	}
  }
  
  
  static WRITE8_HANDLER( VSYNC_w )
  {
  	if (data & 2)
  	{
  		if (!(VSYNC & 2))
  		{
  			int curr_y = current_y(space);
  
  			if ( curr_y > 5 )
  				update_bitmap(
  					video_screen_get_width(space->machine->primary_screen),
  					video_screen_get_height(space->machine->primary_screen));
  
  			if ( tia_vsync_callback ) {
  				tia_vsync_callback( space, 0, curr_y, 0xFFFF );
  			}
  
  			prev_y = 0;
  			prev_x = 0;
  
  			frame_cycles += 76 * current_y(space);
  		}
  	}
  
  	VSYNC = data;
  }
  
  
  static WRITE8_HANDLER( VBLANK_w )
  {
  	if (data & 0x80)
  	{
  		paddle_cycles = cpu_get_total_cycles(space->cpu);
  	}
  	if ( ! ( VBLANK & 0x40 ) ) {
  		INPT4 = 0x80;
  		INPT5 = 0x80;
  	}
  	VBLANK = data;
  }
  
  
  static WRITE8_HANDLER( CTRLPF_w )
  {
  	int curr_x = current_x(space);
  
  	CTRLPF = data;
  	if ( curr_x < 80 ) {
  		REFLECT = CTRLPF & 1;
  	}
  }
  
  static WRITE8_HANDLER( HMP0_w )
  {
  	int curr_x = current_x(space);
  
  	data &= 0xF0;
  
  	if ( data == HMP0 )
  		return;
  
  	/* Check if HMOVE cycles are still being applied */
  	if ( HMOVE_started != HMOVE_INACTIVE && curr_x < MIN( HMOVE_started + 6 + motclkP0 * 4, 7 ) ) {
  		int new_motclkP0 = ( data ^ 0x80 ) >> 4;
  
  		/* Check if new horizontal move can still be applied normally */
  		if ( new_motclkP0 > motclkP0 || curr_x <= MIN( HMOVE_started + 6 + new_motclkP0 * 4, 7 ) ) {
  			horzP0 -= ( new_motclkP0 - motclkP0 );
  			motclkP0 = new_motclkP0;
  		} else {
  			horzP0 -= ( 15 - motclkP0 );
  			motclkP0 = 15;
  			if ( data != 0x70 && data != 0x80 ) {
  				HMP0_latch = 1;
  			}
  		}
  		if ( horzP0 < 0 )
  			horzP0 += 160;
  		horzP0 %= 160;
  		setup_pXgfx();
  	}
  	HMP0 = data;
  }
  
  static WRITE8_HANDLER( HMP1_w )
  {
  	int curr_x = current_x(space);
  
  	data &= 0xF0;
  
  	if ( data == HMP1 )
  		return;
  
  	/* Check if HMOVE cycles are still being applied */
  	if ( HMOVE_started != HMOVE_INACTIVE && curr_x < MIN( HMOVE_started + 6 + motclkP1 * 4, 7 ) ) {
  		int new_motclkP1 = ( data ^ 0x80 ) >> 4;
  
  		/* Check if new horizontal move can still be applied normally */
  		if ( new_motclkP1 > motclkP1 || curr_x <= MIN( HMOVE_started + 6 + new_motclkP1 * 4, 7 ) ) {
  			horzP1 -= ( new_motclkP1 - motclkP1 );
  			motclkP1 = new_motclkP1;
  		} else {
  			horzP1 -= ( 15 - motclkP1 );
  			motclkP1 = 15;
  			if ( data != 0x70 && data != 0x80 ) {
  				HMP1_latch = 1;
  			}
  		}
  		if ( horzP1 < 0 )
  			horzP1 += 160;
  		horzP1 %= 160;
  		setup_pXgfx();
  	}
  	HMP1 = data;
  }
  
  static WRITE8_HANDLER( HMM0_w )
  {
  	int curr_x = current_x(space);
  
  	data &= 0xF0;
  
  	if ( data == HMM0 )
  		return;
  
  	/* Check if HMOVE cycles are still being applied */
  	if ( HMOVE_started != HMOVE_INACTIVE && curr_x < MIN( HMOVE_started + 6 + motclkM0 * 4, 7 ) ) {
  		int new_motclkM0 = ( data ^ 0x80 ) >> 4;
  
  		/* Check if new horizontal move can still be applied normally */
  		if ( new_motclkM0 > motclkM0 || curr_x <= MIN( HMOVE_started + 6 + new_motclkM0 * 4, 7 ) ) {
  			horzM0 -= ( new_motclkM0 - motclkM0 );
  			motclkM0 = new_motclkM0;
  		} else {
  			horzM0 -= ( 15 - motclkM0 );
  			motclkM0 = 15;
  			if ( data != 0x70 && data != 0x80 ) {
  				HMM0_latch = 1;
  			}
  		}
  		if ( horzM0 < 0 )
  			horzM0 += 160;
  		horzM0 %= 160;
  	}
  	HMM0 = data;
  }
  
  static WRITE8_HANDLER( HMM1_w )
  {
  	int curr_x = current_x(space);
  
  	data &= 0xF0;
  
  	if ( data == HMM1 )
  		return;
  
  	/* Check if HMOVE cycles are still being applied */
  	if ( HMOVE_started != HMOVE_INACTIVE && curr_x < MIN( HMOVE_started + 6 + motclkM1 * 4, 7 ) ) {
  		int new_motclkM1 = ( data ^ 0x80 ) >> 4;
  
  		/* Check if new horizontal move can still be applied normally */
  		if ( new_motclkM1 > motclkM1 || curr_x <= MIN( HMOVE_started + 6 + new_motclkM1 * 4, 7 ) ) {
  			horzM1 -= ( new_motclkM1 - motclkM1 );
  			motclkM1 = new_motclkM1;
  		} else {
  			horzM1 -= ( 15 - motclkM1 );
  			motclkM1 = 15;
  			if ( data != 0x70 && data != 0x80 ) {
  				HMM1_latch = 1;
  			}
  		}
  		if ( horzM1 < 0 )
  			horzM1 += 160;
  		horzM1 %= 160;
  	}
  	HMM1 = data;
  }
  
  static WRITE8_HANDLER( HMBL_w )
  {
  	int curr_x = current_x(space);
  
  	data &= 0xF0;
  
  	if ( data == HMBL )
  		return;
  
  	/* Check if HMOVE cycles are still being applied */
  	if ( HMOVE_started != HMOVE_INACTIVE && curr_x < MIN( HMOVE_started + 6 + motclkBL * 4, 7 ) ) {
  		int new_motclkBL = ( data ^ 0x80 ) >> 4;
  
  		/* Check if new horizontal move can still be applied normally */
  		if ( new_motclkBL > motclkBL || curr_x <= MIN( HMOVE_started + 6 + new_motclkBL * 4, 7 ) ) {
  			horzBL -= ( new_motclkBL - motclkBL );
  			motclkBL = new_motclkBL;
  		} else {
  			horzBL -= ( 15 - motclkBL );
  			motclkBL = 15;
  			if ( data != 0x70 && data != 0x80 ) {
  				HMBL_latch = 1;
  			}
  		}
  		if ( horzBL < 0 )
  			horzBL += 160;
  		horzBL %= 160;
  	}
  	HMBL = data;
  }
  
  static WRITE8_HANDLER( HMOVE_w )
  {
  	int curr_x = current_x(space);
  	int curr_y = current_y(space);
  
  	HMOVE_started = curr_x;
  
  	/* Check if we have to undo some of the already applied cycles from an active graphics latch */
  	if ( curr_x + 68 < 17 * 4 ) {
  		int cycle_fix = 17 - ( ( curr_x + 68 + 7 ) / 4 );
  		if ( HMP0_latch )
  			horzP0 = ( horzP0 + cycle_fix ) % 160;
  		if ( HMP1_latch )
  			horzP1 = ( horzP1 + cycle_fix ) % 160;
  		if ( HMM0_latch )
  			horzM0 = ( horzM0 + cycle_fix ) % 160;
  		if ( HMM1_latch )
  			horzM1 = ( horzM1 + cycle_fix ) % 160;
  		if ( HMBL_latch )
  			horzBL = ( horzBL + cycle_fix ) % 160;
  	}
  
  	HMP0_latch = 0;
  	HMP1_latch = 0;
  	HMM0_latch = 0;
  	HMM1_latch = 0;
  	HMBL_latch = 0;
  
  	/* Check if HMOVE activities can be ignored */
  	if ( curr_x >= -5 && curr_x < 97 ) {
  		motclkP0 = 0;
  		motclkP1 = 0;
  		motclkM0 = 0;
  		motclkM1 = 0;
  		motclkBL = 0;
  		HMOVE_started = HMOVE_INACTIVE;
  		return;
  	}
  
  	motclkP0 = ( HMP0 ^ 0x80 ) >> 4;
  	motclkP1 = ( HMP1 ^ 0x80 ) >> 4;
  	motclkM0 = ( HMM0 ^ 0x80 ) >> 4;
  	motclkM1 = ( HMM1 ^ 0x80 ) >> 4;
  	motclkBL = ( HMBL ^ 0x80 ) >> 4;
  
  	/* Adjust number of graphics motion clocks for active display */
  	if ( curr_x >= 97 && curr_x < 151 ) {
  		int skip_motclks = ( 160 - HMOVE_started - 6 ) / 4;
  		motclkP0 -= skip_motclks;
  		motclkP1 -= skip_motclks;
  		motclkM0 -= skip_motclks;
  		motclkM1 -= skip_motclks;
  		motclkBL -= skip_motclks;
  		if ( motclkP0 < 0 )
  			motclkP0 = 0;
  		if ( motclkP1 < 0 )
  			motclkP1 = 0;
  		if ( motclkM0 < 0 )
  			motclkM0 = 0;
  		if ( motclkM1 < 0 )
  			motclkM1 = 0;
  		if ( motclkBL < 0 )
  			motclkBL = 0;
  	}
  
  	if ( curr_x >= -56 && curr_x < -5 ) {
  		int max_motclks = ( 7 - ( HMOVE_started + 5 ) ) / 4;
  		if ( motclkP0 > max_motclks )
  			motclkP0 = max_motclks;
  		if ( motclkP1 > max_motclks )
  			motclkP1 = max_motclks;
  		if ( motclkM0 > max_motclks )
  			motclkM0 = max_motclks;
  		if ( motclkM1 > max_motclks )
  			motclkM1 = max_motclks;
  		if ( motclkBL > max_motclks )
  			motclkBL = max_motclks;
  	}
  
  	/* Apply horizontal motion */
  	if ( curr_x < -5 || curr_x >= 157 ) {
  		horzP0 += 8 - motclkP0;
  		horzP1 += 8 - motclkP1;
  		horzM0 += 8 - motclkM0;
  		horzM1 += 8 - motclkM1;
  		horzBL += 8 - motclkBL;
  
  		if (horzP0 < 0)
  			horzP0 += 160;
  		if (horzP1 < 0)
  			horzP1 += 160;
  		if (horzM0 < 0)
  			horzM0 += 160;
  		if (horzM1 < 0)
  			horzM1 += 160;
  		if (horzBL < 0)
  			horzBL += 160;
  
  		horzP0 %= 160;
  		horzP1 %= 160;
  		horzM0 %= 160;
  		horzM1 %= 160;
  		horzBL %= 160;
  
  		/* When HMOVE is triggered on CPU cycle 75, the HBlank period on the
             next line is also extended. */
  		if (curr_x >= 157)
  		{
  			curr_y += 1;
  			update_bitmap( -8, curr_y );
  		}
  		else
  		{
  			setup_pXgfx();
  		}
  		if (curr_y < screen_height)
  		{
  			memset(BITMAP_ADDR16(helper[current_bitmap], curr_y, 34), 0, 16);
  		}
  
  		prev_x = 8;
  	}
  }
  
  
  static WRITE8_HANDLER( RSYNC_w )
  {
  	/* this address is used in chip testing */
  }
  
  
  static WRITE8_HANDLER( NUSIZ0_w )
  {
  	int curr_x = current_x(space);
  
  	/* Check if relevant bits have changed */
  	if ( ( data & 7 ) != ( NUSIZ0 & 7 ) ) {
  		int i;
  		/* Check if we are (about to start) drawing a copy of the player 0 graphics */
  		for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) {
  			if ( p0gfx.start_pixel[i] < 8 ) {
  				int min_x = p0gfx.start_drawing[i];
  				int size = ( 8 - p0gfx.start_pixel[i] ) * p0gfx.size[i];
  				if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) {
  					if ( curr_x >= min_x % 160 || p0gfx.start_pixel[i] != 0 ) {
  						/* This copy has started drawing */
  						if ( p0gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) {
  							int delay = 1 + ( ( p0gfx.start_pixel[i] + ( curr_x - p0gfx.start_drawing[i] ) ) & 1 );
  							update_bitmap( curr_x + delay, current_y(space) );
  							p0gfx.start_pixel[i] += ( curr_x + delay - p0gfx.start_drawing[i] );
  							if ( p0gfx.start_pixel[i] > 8 )
  								p0gfx.start_pixel[i] = 8;
  							p0gfx.start_drawing[i] = curr_x + delay;
  						} else if ( p0gfx.size[1] > 1 && nusiz[data & 7][1] == 1 ) {
  							int delay = ( curr_x - p0gfx.start_drawing[i] ) & ( p0gfx.size[i] - 1 );
  							if ( delay ) {
  								delay = p0gfx.size[i] - delay;
  							}
  							update_bitmap( curr_x + delay, current_y(space) );
  							p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i];
  							p0gfx.start_drawing[i] = curr_x + delay;
  						} else {
  							p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i];
  							p0gfx.start_drawing[i] = curr_x;
  						}
  						p0gfx.size[i] = nusiz[data & 7][1];
  					} else {
  						/* This copy was just about to start drawing (meltdown) */
  						/* Adjust for 1 clock delay between zoomed and non-zoomed sprites */
  						if ( p0gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) {
  							/* Check for hardware oddity */
  							if ( p0gfx.start_drawing[i] - curr_x == 2 ) {
  								p0gfx.start_drawing[i]--;
  							} else {
  								p0gfx.start_drawing[i]++;
  							}
  						} else if ( p0gfx.size[i] > 1 && nusiz[data & 7][1] == 1 ) {
  							p0gfx.start_drawing[i]--;
  						}
  						p0gfx.size[i] = nusiz[data & 7][1];
  					}
  				} else {
  					/* We are passed the copy or the copy still needs to be done. Mark
                         it as done/invalid, the data will be reset in the next loop. */
  					p0gfx.start_pixel[i] = 8;
  				}
  			}
  		}
  		/* Apply NUSIZ updates to not yet drawn copies */
  		for ( i = ( startP0 ? 0 : 1 ); i < nusiz[data & 7][0]; i++ ) {
  			int j;
  			/* Find an unused p0gfx entry */
  			for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) {
  				if ( p0gfx.start_pixel[j] == 8 )
  					break;
  			}
  			p0gfx.size[j] = nusiz[data & 7][1];
  			p0gfx.start_drawing[j] = ( horzP0 + (p0gfx.size[j] > 1 ? 1 : 0)
  									+ i * 8 * ( nusiz[data & 7][2] + p0gfx.size[j] ) ) % 160;
  			if ( curr_x < p0gfx.start_drawing[j] % 160 ) {
  				p0gfx.start_pixel[j] = 0;
  			}
  		}
  		NUSIZx_changed = 1;
  	}
  	NUSIZ0 = data;
  }
  
  
  static WRITE8_HANDLER( NUSIZ1_w )
  {
  	int curr_x = current_x(space);
  
  	/* Check if relevant bits have changed */
  	if ( ( data & 7 ) != ( NUSIZ1 & 7 ) ) {
  		int i;
  		/* Check if we are (about to start) drawing a copy of the player 1 graphics */
  		for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) {
  			if ( p1gfx.start_pixel[i] < 8 ) {
  				int min_x = p1gfx.start_drawing[i];
  				int size = ( 8 - p1gfx.start_pixel[i] ) * p1gfx.size[i];
  				if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) {
  					if ( curr_x >= min_x % 160 || p1gfx.start_pixel[i] != 0 ) {
  						/* This copy has started drawing */
  						if ( p1gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) {
  							int delay = 1 + ( ( p0gfx.start_pixel[i] + ( curr_x - p0gfx.start_drawing[i] ) ) & 1 );
  							update_bitmap( curr_x + delay, current_y(space) );
  							p1gfx.start_pixel[i] += ( curr_x + delay - p1gfx.start_drawing[i] );
  							if ( p1gfx.start_pixel[i] > 8 )
  								p1gfx.start_pixel[i] = 8;
  							p1gfx.start_drawing[i] = curr_x + delay;
  						} else if ( p1gfx.size[1] > 1 && nusiz[data & 7][1] == 1 ) {
  							int delay = ( curr_x - p1gfx.start_drawing[i] ) & ( p1gfx.size[i] - 1 );
  							if ( delay ) {
  								delay = p1gfx.size[i] - delay;
  							}
  							update_bitmap( curr_x + delay, current_y(space) );
  							p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i];
  							p1gfx.start_drawing[i] = curr_x + delay;
  						} else {
  							p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i];
  							p1gfx.start_drawing[i] = curr_x;
  						}
  						p1gfx.size[i] = nusiz[data & 7][1];
  					} else {
  						/* This copy was just about to start drawing (meltdown) */
  						/* Adjust for 1 clock delay between zoomed and non-zoomed sprites */
  						if ( p1gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) {
  							/* Check for hardware oddity */
  							if ( p1gfx.start_drawing[i] - curr_x == 2 ) {
  								p1gfx.start_drawing[i]--;
  							} else {
  								p1gfx.start_drawing[i]++;
  							}
  						} else if ( p1gfx.size[i] > 1 && nusiz[data & 7][1] == 1 ) {
  							p1gfx.start_drawing[i]--;
  						}
  						p1gfx.size[i] = nusiz[data & 7][1];
  					}
  				} else {
  					/* We are passed the copy or the copy still needs to be done. Mark
                         it as done/invalid, the data will be reset in the next loop. */
  					p1gfx.start_pixel[i] = 8;
  				}
  			}
  		}
  		/* Apply NUSIZ updates to not yet drawn copies */
  		for ( i = ( startP1 ? 0 : 1 ); i < nusiz[data & 7][0]; i++ ) {
  			int j;
  			/* Find an unused p1gfx entry */
  			for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) {
  				if ( p1gfx.start_pixel[j] == 8 )
  					break;
  			}
  			p1gfx.size[j] = nusiz[data & 7][1];
  			p1gfx.start_drawing[j] = ( horzP1 + (p1gfx.size[j] > 1 ? 1 : 0)
  									+ i * 8 * ( nusiz[data & 7][2] + p1gfx.size[j] ) ) % 160;
  			if ( curr_x < p1gfx.start_drawing[j] % 160 ) {
  				p1gfx.start_pixel[j] = 0;
  			}
  		}
  		NUSIZx_changed = 1;
  	}
  	NUSIZ1 = data;
  }
  
  
  static WRITE8_HANDLER( HMCLR_w )
  {
  	HMP0_w( space, offset, 0 );
  	HMP1_w( space, offset, 0 );
  	HMM0_w( space, offset, 0 );
  	HMM1_w( space, offset, 0 );
  	HMBL_w( space, offset, 0 );
  }
  
  
  static WRITE8_HANDLER( CXCLR_w )
  {
  	CXM0P = 0;
  	CXM1P = 0;
  	CXP0FB = 0;
  	CXP1FB = 0;
  	CXM0FB = 0;
  	CXM1FB = 0;
  	CXBLPF = 0;
  	CXPPMM = 0;
  }
  
  
  #define RESXX_APPLY_ACTIVE_HMOVE(HORZ,MOTION,MOTCLK)									\
  	if ( curr_x < MIN( HMOVE_started + 6 + 16 * 4, 7 ) ) {								\
  		int decrements_passed = ( curr_x - ( HMOVE_started + 4 ) ) / 4;					\
  		HORZ += 8;																		\
  		if ( ( MOTCLK - decrements_passed ) > 0 ) {										\
  			HORZ -= ( MOTCLK - decrements_passed );										\
  			if ( HORZ < 0 )																\
  				HORZ += 160;															\
  		}																				\
  	}
  
  #define RESXX_APPLY_PREVIOUS_HMOVE(HORZ,MOTION)												\
  	if ( HMOVE_started_previous != HMOVE_INACTIVE )											\
  	{																						\
  		UINT8	motclk = ( MOTION ^ 0x80 ) >> 4;											\
  		if ( curr_x <= HMOVE_started_previous - 228 + 5 + motclk * 4 )						\
  		{																					\
  			UINT8	motclk_passed = ( curr_x - ( HMOVE_started_previous - 228 + 6 ) ) / 4;	\
  			HORZ -= ( motclk - motclk_passed );												\
  		}																					\
  	}
  
  static WRITE8_HANDLER( RESP0_w )
  {
  	int curr_x = current_x(space);
  	int new_horzP0;
  
  	/* Check if HMOVE is activated during this line */
  	if ( HMOVE_started != HMOVE_INACTIVE ) {
  		new_horzP0 = ( curr_x < 7 ) ? 3 : ( curr_x + 5 );
  		/* If HMOVE is active, adjust for remaining horizontal move clocks if any */
  		RESXX_APPLY_ACTIVE_HMOVE( new_horzP0, HMP0, motclkP0 );
  	} else {
  		new_horzP0 = ( curr_x < -2 ) ? 3 : ( curr_x + 5 );
  		RESXX_APPLY_PREVIOUS_HMOVE( new_horzP0, HMP0 );
  	}
  
  	if ( new_horzP0 != horzP0 ) {
  		int i;
  		horzP0 = new_horzP0;
  		startP0 = 0;
  		skipclipP0 = 2;
  		/* Check if we are (about to start) drawing a copy of the player 0 graphics */
  		for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) {
  			if ( p0gfx.start_pixel[i] < 8 ) {
  				int min_x = p0gfx.start_drawing[i];
  				int size = ( 8 - p0gfx.start_pixel[i] ) * p0gfx.size[i];
  				if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) {
  					if ( curr_x >= min_x ) {
  						/* This copy has started drawing */
  						p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i];
  						p0gfx.start_drawing[i] = curr_x;
  					} else {
  						/* This copy is waiting to start drawing */
  						p0gfx.start_drawing[i] = horzP0;
  					}
  				} else {
  					/* We are passed the copy or the copy still needs to be done. Mark
                         it as done/invalid, the data will be reset in the next loop. */
  					p0gfx.start_pixel[i] = 8;
  				}
  			}
  		}
  		/* Apply NUSIZ and position updates to not yet drawn copies */
  		for ( i = 1; i < nusiz[NUSIZ0 & 7][0]; i++ ) {
  			int j;
  			/* Find an unused p0gfx entry */
  			for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) {
  				if ( p0gfx.start_pixel[j] == 8 )
  					break;
  			}
  			p0gfx.size[j] = nusiz[NUSIZ0 & 7][1];
  			p0gfx.start_drawing[j] = ( horzP0 + (p0gfx.size[j] > 1 ? 1 : 0)
  									+ i * 8 * ( nusiz[NUSIZ0 & 7][2] + p0gfx.size[j] ) ) % 160;
  			if ( curr_x < p0gfx.start_drawing[j] % 160 ) {
  				p0gfx.start_pixel[j] = 0;
  			}
  		}
  	}
  }
  
  
  static WRITE8_HANDLER( RESP1_w )
  {
  	int curr_x = current_x(space);
  	int new_horzP1;
  
  	/* Check if HMOVE is activated during this line */
  	if ( HMOVE_started != HMOVE_INACTIVE ) {
  		new_horzP1 = ( curr_x < 7 ) ? 3 : ( curr_x + 5 );
  		/* If HMOVE is active, adjust for remaining horizontal move clocks if any */
  		RESXX_APPLY_ACTIVE_HMOVE( new_horzP1, HMP1, motclkP1 );
  	} else {
  		new_horzP1 = ( curr_x < -2 ) ? 3 : ( curr_x + 5 );
  		RESXX_APPLY_PREVIOUS_HMOVE( new_horzP1, HMP1 );
  	}
  
  	if ( new_horzP1 != horzP1 ) {
  		int i;
  		horzP1 = new_horzP1;
  		startP1 = 0;
  		skipclipP1 = 2;
  		/* Check if we are (about to start) drawing a copy of the player 1 graphics */
  		for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) {
  			if ( p1gfx.start_pixel[i] < 8 ) {
  				int min_x = p1gfx.start_drawing[i];
  				int size = ( 8 - p1gfx.start_pixel[i] ) * p1gfx.size[i];
  				if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) {
  					if ( curr_x >= min_x ) {
  						/* This copy has started drawing */
  						p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i];
  						p1gfx.start_drawing[i] = curr_x;
  					} else {
  						/* This copy is waiting to start drawing */
  						p1gfx.start_drawing[i] = horzP1;
  					}
  				} else {
  					/* We are passed the copy or the copy still needs to be done. Mark
                         it as done/invalid, the data will be reset in the next loop. */
  					p1gfx.start_pixel[i] = 8;
  				}
  			}
  		}
  		/* Apply NUSIZ and position updates to not yet drawn copies */
  		for ( i = 1; i < nusiz[NUSIZ1 & 7][0]; i++ ) {
  			int j;
  			/* Find an unused p1gfx entry */
  			for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) {
  				if ( p1gfx.start_pixel[j] == 8 )
  					break;
  			}
  			p1gfx.size[j] = nusiz[NUSIZ1 & 7][1];
  			p1gfx.start_drawing[j] = ( horzP1 + (p1gfx.size[j] > 1 ? 1 : 0)
  									+ i * 8 * ( nusiz[NUSIZ1 & 7][2] + p1gfx.size[j] ) ) % 160;
  			if ( curr_x < p1gfx.start_drawing[j] % 160 ) {
  				p1gfx.start_pixel[j] = 0;
  			}
  		}
  	}
  }
  
  
  static WRITE8_HANDLER( RESM0_w )
  {
  	int curr_x = current_x(space);
  	int new_horzM0;
  
  	/* Check if HMOVE is activated during this line */
  	if ( HMOVE_started != HMOVE_INACTIVE ) {
  		new_horzM0 = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		/* If HMOVE is active, adjust for remaining horizontal move clocks if any */
  		RESXX_APPLY_ACTIVE_HMOVE( new_horzM0, HMM0, motclkM0 );
  	} else {
  		new_horzM0 = ( curr_x < -1 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		skipM0delay = ( curr_x < -1 && horzM0 % 160 >= 0 && horzM0 % 160 < 1 ) ? 4 : 0;
  		RESXX_APPLY_PREVIOUS_HMOVE( new_horzM0, HMM0 );
  	}
  	if ( new_horzM0 != horzM0 ) {
  		startM0 = skipM0delay ? 1 : 0;
  		horzM0 = new_horzM0;
  	}
  }
  
  
  static WRITE8_HANDLER( RESM1_w )
  {
  	int curr_x = current_x(space);
  	int new_horzM1;
  
  	/* Check if HMOVE is activated during this line */
  	if ( HMOVE_started != HMOVE_INACTIVE ) {
  		new_horzM1 = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		/* If HMOVE is active, adjust for remaining horizontal move clocks if any */
  		RESXX_APPLY_ACTIVE_HMOVE( new_horzM1, HMM1, motclkM1 );
  	} else {
  		new_horzM1 = ( curr_x < -1 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		skipM1delay = ( curr_x < -1 && horzM1 % 160 >= 0 && horzM1 % 160 < 1 ) ? 4 : 0;
  		RESXX_APPLY_PREVIOUS_HMOVE( new_horzM1, HMM1 );
  	}
  	if ( new_horzM1 != horzM1 ){
  		startM1 = skipM1delay ? 1 : 0;
  		horzM1 = new_horzM1;
  	}
  }
  
  
  static WRITE8_HANDLER( RESBL_w )
  {
  	int curr_x = current_x(space);
  
  	/* Check if HMOVE is activated during this line */
  	if ( HMOVE_started != HMOVE_INACTIVE ) {
  		horzBL = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		/* If HMOVE is active, adjust for remaining horizontal move clocks if any */
  		RESXX_APPLY_ACTIVE_HMOVE( horzBL, HMBL, motclkBL );
  	} else {
  		horzBL = ( curr_x < 0 ) ? 2 : ( ( curr_x + 4 ) % 160 );
  		RESXX_APPLY_PREVIOUS_HMOVE( horzBL, HMBL );
  	}
  }
  
  
  static WRITE8_HANDLER( RESMP0_w )
  {
  	if (RESMP0 & 2)
  	{
  		if ( nusiz[NUSIZ0 & 7][1] > 1 ) {
  			horzM0 = horzP0 + 3 * nusiz[NUSIZ0 & 7][1] - 1;
  		} else {
  			horzM0 = horzP0 + 4 * nusiz[NUSIZ0 & 7][1];
  		}
  		if ( HMOVE_started != HMOVE_INACTIVE ) {
  			horzM0 -= ( 8 - motclkP0 );
  			horzM0 += 8 - motclkM0;
  			if ( horzM0 < 0 )
  				horzM0 += 160;
  		}
  		horzM0 %= 160;
  	}
  
  	RESMP0 = data;
  }
  
  
  static WRITE8_HANDLER( RESMP1_w )
  {
  	if (RESMP1 & 2)
  	{
  		if ( nusiz[NUSIZ1 & 7][1] > 1 ) {
  			horzM1 = horzP1 + 3 * nusiz[NUSIZ1 & 7][1] - 1;
  		} else {
  			horzM1 = horzP1 + 4 * nusiz[NUSIZ1 & 7][1];
  		}
  		if ( HMOVE_started != HMOVE_INACTIVE ) {
  			horzM1 -= ( 8 - motclkP1 );
  			horzM1 += 8 - motclkM1;
  			if ( horzM1 < 0 )
  				horzM1 += 160;
  		}
  		horzM1 %= 160;
  	}
  
  	RESMP1 = data;
  }
  
  
  static WRITE8_HANDLER( GRP0_w )
  {
  	prevGRP1 = GRP1;
  
  	GRP0 = data;
  }
  
  
  static WRITE8_HANDLER( GRP1_w )
  {
  	prevGRP0 = GRP0;
  
  	GRP1 = data;
  
  	prevENABL = ENABL;
  }
  
  
  static READ8_HANDLER( INPT_r )
  {
  	UINT64 elapsed = cpu_get_total_cycles(space->cpu) - paddle_cycles;
  	int input = TIA_INPUT_PORT_ALWAYS_ON;
  	if ( tia_read_input_port )
  	{
  		input = tia_read_input_port(space, offset & 3, 0xFFFF);
  	}
  
  	if ( input == TIA_INPUT_PORT_ALWAYS_ON )
  		return 0x80;
  	if ( input == TIA_INPUT_PORT_ALWAYS_OFF )
  		return 0x00;
  
  	return elapsed > 76 * input ? 0x80 : 0x00;
  }
  
  
  READ8_HANDLER( tia_r )
  {
  	 /* lower bits 0 - 5 seem to depend on the last byte on the
           data bus. If the driver supplied a routine to retrieve
           that we will call that, otherwise we will use the lower
           bit of the offset.
      */
  	UINT8 data = offset & 0x3f;
  
  	if ( tia_get_databus )
  	{
  		data = tia_get_databus(space, offset) & 0x3f;
  	}
  
  	if (!(offset & 0x8))
  	{
  		update_bitmap(current_x(space), current_y(space));
  	}
  
  	switch (offset & 0xF)
  	{
  	case 0x0:
  		return data | CXM0P;
  	case 0x1:
  		return data | CXM1P;
  	case 0x2:
  		return data | CXP0FB;
  	case 0x3:
  		return data | CXP1FB;
  	case 0x4:
  		return data | CXM0FB;
  	case 0x5:
  		return data | CXM1FB;
  	case 0x6:
  		return data | CXBLPF;
  	case 0x7:
  		return data | CXPPMM;
  	case 0x8:
  		return data | INPT_r(space,0);
  	case 0x9:
  		return data | INPT_r(space,1);
  	case 0xA:
  		return data | INPT_r(space,2);
  	case 0xB:
  		return data | INPT_r(space,3);
  	case 0xC:
  		{
  			int	button = tia_read_input_port ? ( tia_read_input_port(space,4,0xFFFF) & 0x80 ) : 0x80;
  			INPT4 = ( VBLANK & 0x40) ? ( INPT4 & button ) : button;
  		}
  		return data | INPT4;
  	case 0xD:
  		{
  			int button = tia_read_input_port ? ( tia_read_input_port(space,5,0xFFFF) & 0x80 ) : 0x80;
  			INPT5 = ( VBLANK & 0x40) ? ( INPT5 & button ) : button;
  		}
  		return data | INPT5;
  	}
  
  	return data;
  }
  
  
  WRITE8_HANDLER( tia_w )
  {
  	static const int delay[0x40] =
  	{
  		 0,	// VSYNC
  		 0,	// VBLANK
  		 0,	// WSYNC
  		 0,	// RSYNC
  		 0,	// NUSIZ0
  		 0,	// NUSIZ1
  		 0,	// COLUP0
  		 0,	// COLUP1
  		 0,	// COLUPF
  		 0,	// COLUBK
  		 0,	// CTRLPF
  		 1,	// REFP0
  		 1,	// REFP1
  		 4,	// PF0
  		 4,	// PF1
  		 4,	// PF2
  		 0,	// RESP0
  		 0,	// RESP1
  		 0,	// RESM0
  		 0,	// RESM1
  		 0,	// RESBL
  		-1,	// AUDC0
  		-1,	// AUDC1
  		-1,	// AUDF0
  		-1,	// AUDF1
  		-1,	// AUDV0
  		-1,	// AUDV1
  		 1,	// GRP0
  		 1,	// GRP1
  		 1,	// ENAM0
  		 1,	// ENAM1
  		 1,	// ENABL
  		 0,	// HMP0
  		 0,	// HMP1
  		 0,	// HMM0
  		 0,	// HMM1
  		 0,	// HMBL
  		 0,	// VDELP0
  		 0,	// VDELP1
  		 0,	// VDELBL
  		 0,	// RESMP0
  		 0,	// RESMP1
  		 3,	// HMOVE
  		 0,	// HMCLR
  		 0,	// CXCLR
  	};
  	int curr_x = current_x(space);
  	int curr_y = current_y(space);
  
  	offset &= 0x3F;
  
  	if (offset >= 0x0D && offset <= 0x0F)
  	{
  		curr_x = ( curr_x + 1 ) & ~3;
  	}
  
  	if (delay[offset] >= 0)
  	{
  		update_bitmap(curr_x + delay[offset], curr_y);
  	}
  
  	switch (offset)
  	{
  	case 0x00:
  		VSYNC_w(space, offset, data);
  		break;
  	case 0x01:
  		VBLANK_w(space, offset, data);
  		break;
  	case 0x02:
  		WSYNC_w(space, offset, data);
  		break;
  	case 0x03:
  		RSYNC_w(space, offset, data);
  		break;
  	case 0x04:
  		NUSIZ0_w(space, offset, data);
  		break;
  	case 0x05:
  		NUSIZ1_w(space, offset, data);
  		break;
  	case 0x06:
  		COLUP0 = data;
  		break;
  	case 0x07:
  		COLUP1 = data;
  		break;
  	case 0x08:
  		COLUPF = data;
  		break;
  	case 0x09:
  		COLUBK = data;
  		break;
  	case 0x0A:
  		CTRLPF_w(space, offset, data);
  		break;
  	case 0x0B:
  		REFP0 = data;
  		break;
  	case 0x0C:
  		REFP1 = data;
  		break;
  	case 0x0D:
  		PF0 = data;
  		break;
  	case 0x0E:
  		PF1 = data;
  		break;
  	case 0x0F:
  		PF2 = data;
  		break;
  	case 0x10:
  		RESP0_w(space, offset, data);
  		break;
  	case 0x11:
  		RESP1_w(space, offset, data);
  		break;
  	case 0x12:
  		RESM0_w(space, offset, data);
  		break;
  	case 0x13:
  		RESM1_w(space, offset, data);
  		break;
  	case 0x14:
  		RESBL_w(space, offset, data);
  		break;
  
  	case 0x15: /* AUDC0 */
  	case 0x16: /* AUDC1 */
  	case 0x17: /* AUDF0 */
  	case 0x18: /* AUDF1 */
  	case 0x19: /* AUDV0 */
  	case 0x1A: /* AUDV1 */
  		tia_sound_w(devtag_get_device(space->machine, "tia"), offset, data);
  		break;
  
  	case 0x1B:
  		GRP0_w(space, offset, data);
  		break;
  	case 0x1C:
  		GRP1_w(space, offset, data);
  		break;
  	case 0x1D:
  		ENAM0 = data;
  		break;
  	case 0x1E:
  		ENAM1 = data;
  		break;
  	case 0x1F:
  		ENABL = data;
  		break;
  	case 0x20:
  		HMP0_w(space, offset, data);
  		break;
  	case 0x21:
  		HMP1_w(space, offset, data);
  		break;
  	case 0x22:
  		HMM0_w(space, offset, data);
  		break;
  	case 0x23:
  		HMM1_w(space, offset, data);
  		break;
  	case 0x24:
  		HMBL_w(space, offset, data);
  		break;
  	case 0x25:
  		VDELP0 = data;
  		break;
  	case 0x26:
  		VDELP1 = data;
  		break;
  	case 0x27:
  		VDELBL = data;
  		break;
  	case 0x28:
  		RESMP0_w(space, offset, data);
  		break;
  	case 0x29:
  		RESMP1_w(space, offset, data);
  		break;
  	case 0x2A:
  		HMOVE_w(space, offset, data);
  		break;
  	case 0x2B:
  		HMCLR_w(space, offset, data);
  		break;
  	case 0x2C:
  		CXCLR_w(space, offset, 0);
  		break;
  	}
  }
  
  
  static void tia_reset(running_machine *machine)
  {
  	int i;
  
  	frame_cycles = 0;
  
  	INPT4 = 0x80;
  	INPT5 = 0x80;
  
  	HMP0 = 0;
  	HMP1 = 0;
  	HMM0 = 0;
  	HMM1 = 0;
  	HMBL = 0;
  
  	startP0 = 1;
  	startP1 = 1;
  
  	HMP0_latch = 0;
  	HMP1_latch = 0;
  	HMM0_latch = 0;
  	HMM1_latch = 0;
  	HMBL_latch = 0;
  
  	startM0 = 1;
  	startM1 = 1;
  	skipM0delay = 0;
  	skipM1delay = 0;
  
  	REFLECT = 0;
  
  	prev_x = 0;
  	prev_y = 0;
  
  	HMOVE_started = HMOVE_INACTIVE;
  
  	motclkP0 = 0;
  	motclkP1 = 0;
  	motclkM0 = 0;
  	motclkM1 = 0;
  	motclkBL = 0;
  
  	for( i = 0; i < PLAYER_GFX_SLOTS; i++ )
  	{
  		p0gfx.start_pixel[i] = 8;
  		p0gfx.size[i] = 1;
  		p1gfx.start_pixel[i] = 8;
  		p1gfx.size[i] = 1;
  	}
  
  	NUSIZx_changed = 0;
  }
  
  
  
  void tia_init(running_machine *machine, const struct tia_interface* ti)
  {
  	assert_always(mame_get_phase(machine) == MAME_PHASE_INIT, "Can only call tia_init at init time!");
  
  	if ( ti ) {
  		tia_read_input_port = ti->read_input_port;
  		tia_get_databus = ti->databus_contents;
  		tia_vsync_callback = ti->vsync_callback;
  	} else {
  		tia_read_input_port = NULL;
  		tia_get_databus = NULL;
  		tia_vsync_callback = NULL;
  	}
  
  	tia_reset( machine );
  
  	add_reset_callback(machine, tia_reset);
  }
  
  