/*	
 *	clipuvi.c
 *
 *	P/ECE Signetics 2637 UVI(Universal Video Interface) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sat Apr 30 13:14:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 *	* Thu Jun 02 05:37:00 JST 2005 Naoyuki Sawa
 *	- OBJ<=>BG衝突判定にて、BGのcol,rowを除算にて求めていたのを、シフトに修正しました。
 *	  除算では、非除数が負数の場合の丸め方向が異なるので、正しいcol,rowが得られません。
 *	- たとえばOBJの座標x=-5、OBJ幅=8の場合、
 *		col = x / 8;
 *		x &= 7;
 *	  とすると、col=0、x=3となってしまい、これは誤りです。
 *		col = x >> 3;
 *		x &= 7;
 *	  とすれば、col=-1、x=3となり、これが正しい結果です。
 */
#include "clip.h"

/****************************************************************************
 *	グローバル変数
 ****************************************************************************/

UVI uvi;
unsigned char uvi_font[8/*Line*/ * 64/*Character*/ * 2/*Mode*/] = {
#include "uvi/font.c"
};

#ifdef UVI_GRAYSCALE
const unsigned char uvi_grayscale_table[8/*Color*/] = {
#define UVI_RGB(r,g,b) ((int)((r*0.30 + g*0.59 + b*0.11) * 15 + 0.5) ^ 15)
#else /*UVI_GRAYSCALE*/
const unsigned char uvi_color_table[8/*Color*/][3/*R,G,B*/] = {
#define UVI_RGB(r,g,b) { r*255, g*255, b*255 }
#endif /*UVI_GRAYSCALE*/
UVI_RGB(1,1,1),	/* White */
UVI_RGB(1,1,0),	/* Yellow */
UVI_RGB(0,1,1),	/* Cyan */
UVI_RGB(0,1,0),	/* Green */
UVI_RGB(1,0,1),	/* Magenta */
UVI_RGB(1,0,0),	/* Red */
UVI_RGB(0,0,1),	/* Blue */
UVI_RGB(0,0,0),	/* Black */
#undef UVI_RGB
};

/****************************************************************************
 *	内部関数 (Video)
 ****************************************************************************/

void
uvi_update_pot()
{
	if(uvi.ram[0x1f9] & (1<<6)) { /* P=1 */
		uvi.ram[0x1fe] = uvi.pot[3]; /* $1FE=POT4 */
		uvi.ram[0x1ff] = uvi.pot[2]; /* $1FF=POT3 */
	} else { /* P=0 */
		uvi.ram[0x1fe] = uvi.pot[1]; /* $1FE=POT2 */
		uvi.ram[0x1ff] = uvi.pot[0]; /* $1FF=POT1 */
	}
}

void
uvi_update_color()
{
	int cc;
	int cs;

	if(uvi.ram[0x0fd] & (1<<7)) { /* M = 1 specifies mode 1 */
		//////////////////////////////////
		// Font                         //
		// Video B7 B6 | C1   C2   C3   //
		// ------------+--------------- //
		//    1  X  1  | Cc1  Cc2  Cc3  //
		//    1  X  0  | Cc'1 Cc'2 Cc'3 //
		//    0  1  X  | Cs1  Cs2  Cs3  //
		//    0  0  X  | Cs'1 Cs'2 Cs'3 //
		//////////////////////////////////
		cs = (uvi.ram[0x1f9] >> 0) & 7;	/* Cs1  Cs2  Cs3  */
		cc = (uvi.ram[0x1f9] >> 3) & 7;	/* Cc1  Cc2  Cc3  */
		uvi.color[2][0] = cs;		/* Cs1  Cs2  Cs3  */
		uvi.color[3][0] = cs;		/* Cs1  Cs2  Cs3  */
		uvi.color[1][1] = cc;		/* Cc1  Cc2  Cc3  */
		uvi.color[3][1] = cc;		/* Cc1  Cc2  Cc3  */
		cs = (uvi.ram[0x1f8] >> 0) & 7;	/* Cs'1 Cs'2 Cs'3 */
		cc = (uvi.ram[0x1f8] >> 3) & 7;	/* Cc'1 Cc'2 Cc'3 */
		uvi.color[0][0] = cs;		/* Cs'1 Cs'2 Cs'3 */
		uvi.color[1][0] = cs;		/* Cs'1 Cs'2 Cs'3 */
		uvi.color[0][1] = cc;		/* Cc'1 Cc'2 Cc'3 */
		uvi.color[2][1] = cc;		/* Cc'1 Cc'2 Cc'3 */
	} else { /* M = 0 specifies mode 0 */
		///////////////////////
		// Font              //
		// Video C1  C2  C3  //
		// ----------------- //
		//    1  B7  B6  Cc3 //
		//    0  Cs1 Cs2 Cs3 //
		///////////////////////
		cs = (uvi.ram[0x1f9] >> 0) & 7;	/* Cs1 Cs2 Cs3 */
		cc = (uvi.ram[0x1f9] >> 3) & 1;	/*  0   0  Cc3 */
		uvi.color[0][0] = cs;		/* Cs1 Cs2 Cs3 */
		uvi.color[1][0] = cs;		/* Cs1 Cs2 Cs3 */
		uvi.color[2][0] = cs;		/* Cs1 Cs2 Cs3 */
		uvi.color[3][0] = cs;		/* Cs1 Cs2 Cs3 */
		uvi.color[0][1] = cc; cc += 2;	/*  0   0  Cc3 */
		uvi.color[1][1] = cc; cc += 2;	/*  0   1  Cc3 */
		uvi.color[2][1] = cc; cc += 2;	/*  1   0  Cc3 */
		uvi.color[3][1] = cc;		/*  1   1  Cc3 */
	}

#ifdef UVI_GRAYSCALE
	uvi.color[0][0] = uvi_grayscale_table[uvi.color[0][0]];
	uvi.color[0][1] = uvi_grayscale_table[uvi.color[0][1]];
	uvi.color[1][0] = uvi_grayscale_table[uvi.color[1][0]];
	uvi.color[1][1] = uvi_grayscale_table[uvi.color[1][1]];
	uvi.color[2][0] = uvi_grayscale_table[uvi.color[2][0]];
	uvi.color[2][1] = uvi_grayscale_table[uvi.color[2][1]];
	uvi.color[3][0] = uvi_grayscale_table[uvi.color[3][0]];
	uvi.color[3][1] = uvi_grayscale_table[uvi.color[3][1]];
#endif /*UVI_GRAYSCALE*/
}

void
uvi_partial_update()
{
	unsigned char* vbuff;
	int scan_start;
	int scan_end;
	//
	void (*draw_chr)(int x, int y, int code, int color);
	unsigned short* bg;
	UVIOBJ* obj;
	int h;
	int i;
	int x;
	int y;
	int col;
	int row;
	int code;
	int color;

	/* 実描画無しならば、何もしません。 */
	vbuff = uvi.vbuff;
	if(!vbuff) {
		goto L_EXIT;
	}

	/* 描画領域の上端が可視領域の下端を超えていたら、何もしません。 */
	scan_start = uvi.scan_start;
	if(scan_start >= 208) {
		goto L_EXIT;
	}

	/* 描画領域の上端が可視領域内ならば、描画領域の下端が可視領域の
	 * 下端を超えた状態で、この関数が呼ばれることは無いはずです。
	 * (uvi_next_line()から、scan_end==208時に明示的に呼ばれるから)
	 */
	scan_end = uvi.scan_end;
	ASSERT(scan_end <= 208);

	/* 前回描画後にスキャンラインが進んでいなければ、何もしません。 */
	if(scan_start == scan_end) {
		goto L_EXIT;
	}
	ASSERT(scan_start < scan_end);

	/* スクロール座標によっては、描画されない部分が残る可能性があるので、まず背景色で塗りつぶします。 */
	memset(&vbuff[128 * scan_start], uvi.color[0][0], 128 * (scan_end - scan_start));

	/*-------------------- BG描画 --------------------*/

	/* BGのサイズを選択します。 */
	if(uvi.ram[0x1f9] & (1<<7)) { /* SZ0: 1=x8x8, 0=8x16 */
		h = 8;
		draw_chr = uvi_draw_chr_8x8;
	} else {
		h = 16;
		draw_chr = uvi_draw_chr_8x16;
	}

	/* 垂直スクロール座標を取得します。 */
	y = uvi.scroll_y;

	/* BGを描きます。 */
	for(row = 0; row < 26; row++, y += h) {

		/* 行の上端が描画領域の下端より下ならば、直ちに抜けます。(高速化のため) */
		if(y >= scan_end) {
			break;
		}

		/* 行の下端が描画領域の上端より上ならば、一行飛ばします。(高速化のため) */
		if(y + h <= scan_start) {
			continue;
		}

		/* 水平スクロール座標を取得します。 */
		x = uvi.scroll_x[row];

		/* 一行描きます。 */
		bg = &uvi.bg[row][0];
		col = 16;
		do {
			color = *bg++;		/* 000000cc0gffffff c=Color,g=GM,f=Code */
			code = (unsigned char)color;	/* 0gffffff */
			if(code) {
				color >>= 8;	/* 000000cc         */
				draw_chr(x, y, code, color);
			}
			x += 8;
		} while(--col);
	}

	/*-------------------- OBJ描画 --------------------*/

	/* OBJを描きます。 */
	obj = uvi.obj;
	i = 4;
	do {
		if(obj->size) { /* 1=x8x8, 0=8x16 */
			uvi_draw_obj_8x8(obj);
		} else {
			uvi_draw_obj_8x16(obj);
		}
		obj++;
	} while(--i);

L_EXIT:
	/* 今回の描画領域の下端を、次回の描画領域の上端として設定します。 */
	uvi.scan_start = uvi.scan_end;
}

/* 拡大モードは利用頻度が低いので、高速RAM節約のためにSRAMに配置します。 */
void
uvi_draw_chr_8x16(int x, int y, int code, int color)
{
	unsigned char* vbuff;
	unsigned char* font;
	int y_min;
	int y_max;
	int cs;
	int cc;
	int ix;
	int iy;
	int v;

	y_min = uvi.scan_start;
	y_max = uvi.scan_end;

	/* 完全に描画領域外ならば、何もしません。 */
	if((y <= y_min - 16) || (y >= y_max) ||
	   (x <=     0 -  8) || (x >=   128)) {
		return;
	}

	vbuff = &uvi.vbuff[y * 128 + x];
	font = &uvi_font[code * 8];
	cs = uvi.color[color][0];
	cc = uvi.color[color][1];

	/* 完全に描画領域内ならば、クリッピング不要です。 */
	if((y >= y_min) && (y <= y_max - 16) &&
	   (x >=     0) && (x <=   128 -  8)) {
		iy = 16;
		do {
			v = *font;
			ix = 8;
			do {
				*vbuff++ = (v & 0x80) ? cc : cs;
				v <<= 1;
			} while(--ix);
			vbuff += 128 - 8;
			font += iy & 1;
		} while(--iy);

	/* 描画領域内をはみ出す場合、クリッピングが必要です。 */
	} else {
		iy = 16;
		do {
			if((y >= y_min) && (y < y_max)) {
				v = *font;
				ix = 8;
				do {
					if((x >= 0) && (x < 128)) {
						*vbuff = (v & 0x80) ? cc : cs;
					}
					vbuff++;
					v <<= 1;
					x++;
				} while(--ix);
				vbuff -= 8;
				x -= 8;
			}
			vbuff += 128;
			y += 1;
			font += iy & 1;
		} while(--iy);
	}
}

/* 拡大モードは利用頻度が低いので、高速RAM節約のためにSRAMに配置します。 */
void
uvi_draw_obj_8x16(UVIOBJ* obj)
{
	unsigned char* vbuff;
	unsigned char* font;
	int y_min;
	int y_max;
	int cc;
	int ix;
	int iy;
	int x;
	int y;
	int v;

	x = obj->x;
	y = obj->y;
	y_min = uvi.scan_start;
	y_max = uvi.scan_end;

	/* 完全に描画領域外ならば、何もしません。 */
	if((y <= y_min - 16) || (y >= y_max) ||
	   (x <=     0 -  8) || (x >=   128)) {
		return;
	}

	vbuff = &uvi.vbuff[y * 128 + x];
	font = obj->font;
	cc = obj->color;

	/* 完全に描画領域内ならば、クリッピング不要です。 */
	if((y >= y_min) && (y <= y_max - 16) &&
	   (x >=     0) && (x <=   128 -  8)) {
		iy = 16;
		do {
			v = *font;
			ix = 8;
			do {
				if(v & 0x80) {
					*vbuff = cc;
				}
				vbuff++;
				v <<= 1;
			} while(--ix);
			vbuff += 128 - 8;
			font += iy & 1;
		} while(--iy);

	/* 描画領域内をはみ出す場合、クリッピングが必要です。 */
	} else {
		iy = 16;
		do {
			if((y >= y_min) && (y < y_max)) {
				v = *font;
				ix = 8;
				do {
					if(v & 0x80) {
						if((x >= 0) && (x < 128)) {
							*vbuff = cc;
						}
					}
					vbuff++;
					v <<= 1;
					x++;
				} while(--ix);
				vbuff -= 8;
				x -= 8;
			}
			vbuff += 128;
			y += 1;
			font += iy & 1;
		} while(--iy);
	}
}

int
uvi_collision_obj_8x8_bg_8x8(UVIOBJ* obj)
{
	int x1;
	int y1;
	int x2;
	int y2;
	int col;
	int row;
	int code;

	y1 = obj->y - uvi.scroll_y;
	//row = y1 / 8/*BgHeight*/;
	//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
	row = y1 >> 3;
	y1 &= 7/*BgHeight-1*/;
	y2 = 0;
	do {
		if((unsigned)row < 26) {
			if(uvi.scroll_x[row] != -1) {
				x1 = obj->x - uvi.scroll_x[row];
				//col = x1 / 8/*BgWidth*/;
				//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
				col = x1 >> 3;
				x1 &= 7/*BgWidth-1*/;
				x2 = 0;
				do {
					if((unsigned)col < 16) {
						code = (unsigned char)uvi.bg[row][col];
						if(code) {
							if(uvi_collision_font_8x8_font_8x8(x1, y1, obj->font, x2, y2, &uvi_font[code * 8])) {
								return 1;
							}
						}
					}
					col++;
					x2 += 8/*BgWidth*/;
				} while(x2 < x1 + 8/*ObjWidth*/);
			}
		}
		row++;
		y2 += 8/*BgHeight*/;
	} while(y2 < y1 + 8/*ObjHeight*/);

	return 0;
}

int
uvi_collision_obj_8x8_bg_8x16(UVIOBJ* obj)
{
	int x1;
	int y1;
	int x2;
	int y2;
	int col;
	int row;
	int code;

	y1 = obj->y - uvi.scroll_y;
	//row = y1 / 16/*BgHeight*/;
	//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
	row = y1 >> 4;
	y1 &= 15/*BgHeight-1*/;
	y2 = 0;
	do {
		if((unsigned)row < 26) {
			if(uvi.scroll_x[row] != -1) {
				x1 = obj->x - uvi.scroll_x[row];
				//col = x1 / 8/*BgWidth*/;
				//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
				col = x1 >> 3;
				x1 &= 7/*BgWidth-1*/;
				x2 = 0;
				do {
					if((unsigned)col < 16) {
						code = (unsigned char)uvi.bg[row][col];
						if(code) {
							if(uvi_collision_font_8x8_font_8x16(x1, y1, obj->font, x2, y2, &uvi_font[code * 8])) {
								return 1;
							}
						}
					}
					col++;
					x2 += 8/*BgWidth*/;
				} while(x2 < x1 + 8/*ObjWidth*/);
			}
		}
		row++;
		y2 += 16/*BgHeight*/;
	} while(y2 < y1 + 8/*ObjHeight*/);

	return 0;
}

int
uvi_collision_obj_8x16_bg_8x8(UVIOBJ* obj)
{
	int x1;
	int y1;
	int x2;
	int y2;
	int col;
	int row;
	int code;

	y1 = obj->y - uvi.scroll_y;
	//row = y1 / 8/*BgHeight*/;
	//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
	row = y1 >> 3;
	y1 &= 7/*BgHeight-1*/;
	y2 = 0;
	do {
		if((unsigned)row < 26) {
			if(uvi.scroll_x[row] != -1) {
				x1 = obj->x - uvi.scroll_x[row];
				//col = x1 / 8/*BgWidth*/;
				//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
				col = x1 >> 3;
				x1 &= 7/*BgWidth-1*/;
				x2 = 0;
				do {
					if((unsigned)col < 16) {
						code = (unsigned char)uvi.bg[row][col];
						if(code) {
							if(uvi_collision_font_8x16_font_8x8(x1, y1, obj->font, x2, y2, &uvi_font[code * 8])) {
								return 1;
							}
						}
					}
					col++;
					x2 += 8/*BgWidth*/;
				} while(x2 < x1 + 8/*ObjWidth*/);
			}
		}
		row++;
		y2 += 8/*BgHeight*/;
	} while(y2 < y1 + 16/*ObjHeight*/);

	return 0;
}

int
uvi_collision_obj_8x16_bg_8x16(UVIOBJ* obj)
{
	int x1;
	int y1;
	int x2;
	int y2;
	int col;
	int row;
	int code;

	y1 = obj->y - uvi.scroll_y;
	//row = y1 / 16/*BgHeight*/;
	//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
	row = y1 >> 4;
	y1 &= 15/*BgHeight-1*/;
	y2 = 0;
	do {
		if((unsigned)row < 26) {
			if(uvi.scroll_x[row] != -1) {
				x1 = obj->x - uvi.scroll_x[row];
				//col = x1 / 8/*BgWidth*/;
				//↓非除数が負の場合の丸め方向が異なるので"/"は不可。">>"を使用。(必須!!)
				col = x1 >> 3;
				x1 &= 7/*BgWidth-1*/;
				x2 = 0;
				do {
					if((unsigned)col < 16) {
						code = (unsigned char)uvi.bg[row][col];
						if(code) {
							if(uvi_collision_font_8x16_font_8x16(x1, y1, obj->font, x2, y2, &uvi_font[code * 8])) {
								return 1;
							}
						}
					}
					col++;
					x2 += 8/*BgWidth*/;
				} while(x2 < x1 + 8/*ObjWidth*/);
			}
		}
		row++;
		y2 += 16/*BgHeight*/;
	} while(y2 < y1 + 16/*ObjHeight*/);

	return 0;
}

int
uvi_collision_obj_8x8_obj_8x8(UVIOBJ* obj1, UVIOBJ* obj2)
{
	return uvi_collision_font_8x8_font_8x8(obj1->x, obj1->y, obj1->font, obj2->x, obj2->y, obj2->font);
}

int
uvi_collision_obj_8x8_obj_8x16(UVIOBJ* obj1, UVIOBJ* obj2)
{
	return uvi_collision_font_8x8_font_8x16(obj1->x, obj1->y, obj1->font, obj2->x, obj2->y, obj2->font);
}

int
uvi_collision_obj_8x16_obj_8x8(UVIOBJ* obj1, UVIOBJ* obj2)
{
	return uvi_collision_font_8x16_font_8x8(obj1->x, obj1->y, obj1->font, obj2->x, obj2->y, obj2->font);
}

int
uvi_collision_obj_8x16_obj_8x16(UVIOBJ* obj1, UVIOBJ* obj2)
{
	return uvi_collision_font_8x16_font_8x16(obj1->x, obj1->y, obj1->font, obj2->x, obj2->y, obj2->font);
}

int
uvi_collision_font_8x8_font_8x8(int x1, int y1, const unsigned char* font1/*[8]*/, int x2, int y2, const unsigned char* font2/*[8]*/)
{
	int i;
	int v1;
	int v2;

	if(x1 + 8/*Font1Width */ <= x2) return 0;
	if(x2 + 8/*Font2Width */ <= x1) return 0;
	if(y1 + 8/*Font1Height*/ <= y2) return 0;
	if(y2 + 8/*Font2Height*/ <= y1) return 0;

	if(y1 <= y2) {
		i = y2 - y1;
		font1 += i;
	} else {
		i = y1 - y2;
		font2 += i;
	}
	if(x1 <= x2) {
		x2 -= x1;
		x1 = 0;
	} else {
		x1 -= x2;
		x2 = 0;
	}

	do {
		v1 = *font1++ << x2; /* 左:MSB<->LSB:右 なので、 */
		v2 = *font2++ << x1; /* 相手の座標で左シフトする */
		if(v1 & v2) {
			return 1;
		}
	} while(++i < 8/*Font1,2Height*/);

	return 0;
}

int
uvi_collision_font_8x8_font_8x16(int x1, int y1, const unsigned char* font1/*[8]*/, int x2, int y2, const unsigned char* font2/*[8]*/)
{
	return uvi_collision_font_8x16_font_8x8(x2, y2, font2, x1, y1, font1);
}

/* 使用頻度が低いので、遅くて簡単な実装を選択しました。 */
int
uvi_collision_font_8x16_font_8x8(int x1, int y1, const unsigned char* font1/*[8]*/, int x2, int y2, const unsigned char* font2/*[8]*/)
{
	int i;
	int v1;
	int v2;
	unsigned char _font1[16];
	unsigned char _font2[16];

	if(x1 +  8/*Font1Width */ <= x2) return 0;
	if(x2 +  8/*Font2Width */ <= x1) return 0;
	if(y1 + 16/*Font1Height*/ <= y2) return 0;
	if(y2 +  8/*Font2Height*/ <= y1) return 0;

	for(i = 0; i < 8; i++) {
		_font1[i * 2 + 0] = font1[i]; /* 縦二倍拡大 */
		_font1[i * 2 + 1] = font1[i];
		_font2[i     + 0] = font2[i]; /* 上半分にコピー、下半分は判定無し */
		_font2[i     + 8] = 0;        /* これ以降、Font2Height=16と見なす */
	}
	font1 = _font1;
	font2 = _font2;

	if(y1 <= y2) {
		i = y2 - y1;
		font1 += i;
	} else {
		i = y1 - y2;
		font2 += i;
	}
	if(x1 <= x2) {
		x2 -= x1;
		x1 = 0;
	} else {
		x1 -= x2;
		x2 = 0;
	}

	do {
		v1 = *font1++ << x2; /* 左:MSB<->LSB:右 なので、 */
		v2 = *font2++ << x1; /* 相手の座標で左シフトする */
		if(v1 & v2) {
			return 1;
		}
	} while(++i < 16/*Font1,2Height*/);

	return 0;
}

/* 使用頻度が低いので、遅くて簡単な実装を選択しました。 */
int
uvi_collision_font_8x16_font_8x16(int x1, int y1, const unsigned char* font1/*[8]*/, int x2, int y2, const unsigned char* font2/*[8]*/)
{
	int i;
	int v1;
	int v2;
	unsigned char _font1[16];
	unsigned char _font2[16];

	if(x1 +  8/*Font1Width */ <= x2) return 0;
	if(x2 +  8/*Font2Width */ <= x1) return 0;
	if(y1 + 16/*Font1Height*/ <= y2) return 0;
	if(y2 + 16/*Font2Height*/ <= y1) return 0;

	for(i = 0; i < 8; i++) {
		_font1[i * 2 + 0] = font1[i];	/* 縦二倍拡大 */
		_font1[i * 2 + 1] = font1[i];
		_font2[i * 2 + 0] = font2[i];	/* 縦二倍拡大 */
		_font2[i * 2 + 1] = font2[i];
	}
	font1 = _font1;
	font2 = _font2;

	if(y1 <= y2) {
		i = y2 - y1;
		font1 += i;
	} else {
		i = y1 - y2;
		font2 += i;
	}
	if(x1 <= x2) {
		x2 -= x1;
		x1 = 0;
	} else {
		x1 -= x2;
		x2 = 0;
	}

	do {
		v1 = *font1++ << x2; /* 左:MSB<->LSB:右 なので、 */
		v2 = *font2++ << x1; /* 相手の座標で左シフトする */
		if(v1 & v2) {
			return 1;
		}
	} while(++i < 16/*Font1,2Height*/);

	return 0;
}

/****************************************************************************
 *	アプリケーション用関数 (Video)
 ****************************************************************************/

void
uvi_reset(unsigned char* ram/*[0x400]*/)
{
	if(sizeof(UVI   ) != SIZEOF_UVI   ) DIE();
	if(sizeof(UVIOBJ) != SIZEOF_UVIOBJ) DIE();

	memset(&uvi, 0, sizeof(UVI));
	uvi.ram = ram;				/* 固定 */

	/* Video */
	uvi.obj[0].font = &uvi_font[8 * 56];	/* 固定 */
	uvi.obj[1].font = &uvi_font[8 * 57];	/* 固定 */
	uvi.obj[2].font = &uvi_font[8 * 58];	/* 固定 */
	uvi.obj[3].font = &uvi_font[8 * 59];	/* 固定 */
	memset(&uvi_font[8 * 56], 0, 8 * 8);	/* ユーザー定義フォント */

	/* Sound */
	uvi.noise_lfsr = 1;
}

int
uvi_read(int addr)
{
	ASSERT((addr >= 0) && (addr <= 0x3ff));

	return uvi.ram[addr];
}

void
uvi_write(int addr, int data)
{
#define PARTIAL_UPDATE() do {	\
	uvi_partial_update();	\
	uvi.ram[addr] = data;	\
} while(0)

	ASSERT((addr >= 0) && (addr <= 0x3ff));
	ASSERT((data >= 0) && (data <= 0xff));

	if(uvi.ram[addr] == data) {
		return;
	}

	switch(addr) {
	/*********************************************************************************************
	 *	#RCE AREA (RCE = RAM Chip Enable)
	 *********************************************************************************************/
	//case 0x000: case 0x001: case 0x002: case 0x003: case 0x004: case 0x005: case 0x006: case 0x007:
	//case 0x008: case 0x009: case 0x00a: case 0x00b: case 0x00c: case 0x00d: case 0x00e: case 0x00f:
	//case 0x010: case 0x011: case 0x012: case 0x013: case 0x014: case 0x015: case 0x016: case 0x017:
	//case 0x018: case 0x019: case 0x01a: case 0x01b: case 0x01c: case 0x01d: case 0x01e: case 0x01f:
	//case 0x020: case 0x021: case 0x022: case 0x023: case 0x024: case 0x025: case 0x026: case 0x027:
	//case 0x028: case 0x029: case 0x02a: case 0x02b: case 0x02c: case 0x02d: case 0x02e: case 0x02f:
	//case 0x030: case 0x031: case 0x032: case 0x033: case 0x034: case 0x035: case 0x036: case 0x037:
	//case 0x038: case 0x039: case 0x03a: case 0x03b: case 0x03c: case 0x03d: case 0x03e: case 0x03f:
	//case 0x040: case 0x041: case 0x042: case 0x043: case 0x044: case 0x045: case 0x046: case 0x047:
	//case 0x048: case 0x049: case 0x04a: case 0x04b: case 0x04c: case 0x04d: case 0x04e: case 0x04f:
	//case 0x050: case 0x051: case 0x052: case 0x053: case 0x054: case 0x055: case 0x056: case 0x057:
	//case 0x058: case 0x059: case 0x05a: case 0x05b: case 0x05c: case 0x05d: case 0x05e: case 0x05f:
	//case 0x060: case 0x061: case 0x062: case 0x063: case 0x064: case 0x065: case 0x066: case 0x067:
	//case 0x068: case 0x069: case 0x06a: case 0x06b: case 0x06c: case 0x06d: case 0x06e: case 0x06f:
	//case 0x070: case 0x071: case 0x072: case 0x073: case 0x074: case 0x075: case 0x076: case 0x077:
	//case 0x078: case 0x079: case 0x07a: case 0x07b: case 0x07c: case 0x07d: case 0x07e: case 0x07f:
	//case 0x080: case 0x081: case 0x082: case 0x083: case 0x084: case 0x085: case 0x086: case 0x087:
	//case 0x088: case 0x089: case 0x08a: case 0x08b: case 0x08c: case 0x08d: case 0x08e: case 0x08f:
	//case 0x090: case 0x091: case 0x092: case 0x093: case 0x094: case 0x095: case 0x096: case 0x097:
	//case 0x098: case 0x099: case 0x09a: case 0x09b: case 0x09c: case 0x09d: case 0x09e: case 0x09f:
	//case 0x0a0: case 0x0a1: case 0x0a2: case 0x0a3: case 0x0a4: case 0x0a5: case 0x0a6: case 0x0a7:
	//case 0x0a8: case 0x0a9: case 0x0aa: case 0x0ab: case 0x0ac: case 0x0ad: case 0x0ae: case 0x0af:
	//case 0x0b0: case 0x0b1: case 0x0b2: case 0x0b3: case 0x0b4: case 0x0b5: case 0x0b6: case 0x0b7:
	//case 0x0b8: case 0x0b9: case 0x0ba: case 0x0bb: case 0x0bc: case 0x0bd: case 0x0be: case 0x0bf:
	//case 0x0c0: case 0x0c1: case 0x0c2: case 0x0c3: case 0x0c4: case 0x0c5: case 0x0c6: case 0x0c7:
	//case 0x0c8: case 0x0c9: case 0x0ca: case 0x0cb: case 0x0cc: case 0x0cd: case 0x0ce: case 0x0cf:
	//case 0x0d0: case 0x0d1: case 0x0d2: case 0x0d3: case 0x0d4: case 0x0d5: case 0x0d6: case 0x0d7:
	//case 0x0d8: case 0x0d9: case 0x0da: case 0x0db: case 0x0dc: case 0x0dd: case 0x0de: case 0x0df:
	//case 0x0e0: case 0x0e1: case 0x0e2: case 0x0e3: case 0x0e4: case 0x0e5: case 0x0e6: case 0x0e7:
	//case 0x0e8: case 0x0e9: case 0x0ea: case 0x0eb: case 0x0ec: case 0x0ed: case 0x0ee: case 0x0ef:
	case 0x0f0: case 0x0f2: case 0x0f4: case 0x0f6:
		PARTIAL_UPDATE();
		/* #(Vertical coordinate) of object 1..4 */
		uvi.obj[(addr >> 1) & 3].y = (data ^ 0xff) - 16/*※目分量で調整*/;
		break;
	case 0x0f1: case 0x0f3: case 0x0f5: case 0x0f7:
		PARTIAL_UPDATE();
		/* Horizontal coordinate of object 1..4 */
		uvi.obj[(addr >> 1) & 3].x = data - 43/*※目分量で調整*/;
		break;
	//case 0x0f8: case 0x0f9: case 0x0fa: case 0x0fb: case 0x0fc:
	case 0x0fd:
		PARTIAL_UPDATE();
		/* M = Color mode bit */
		uvi_update_color();
		break;
	//case 0x0fe: case 0x0ff:
	/*********************************************************************************************
	 *	#CE AREA (CE = I/O Chip Enable)
	 *********************************************************************************************/
	//case 0x100: case 0x101: case 0x102: case 0x103: case 0x104: case 0x105: case 0x106: case 0x107:
	//case 0x108: case 0x109: case 0x10a: case 0x10b: case 0x10c: case 0x10d: case 0x10e: case 0x10f:
	//case 0x110: case 0x111: case 0x112: case 0x113: case 0x114: case 0x115: case 0x116: case 0x117:
	//case 0x118: case 0x119: case 0x11a: case 0x11b: case 0x11c: case 0x11d: case 0x11e: case 0x11f:
	//case 0x120: case 0x121: case 0x122: case 0x123: case 0x124: case 0x125: case 0x126: case 0x127:
	//case 0x128: case 0x129: case 0x12a: case 0x12b: case 0x12c: case 0x12d: case 0x12e: case 0x12f:
	//case 0x130: case 0x131: case 0x132: case 0x133: case 0x134: case 0x135: case 0x136: case 0x137:
	//case 0x138: case 0x139: case 0x13a: case 0x13b: case 0x13c: case 0x13d: case 0x13e: case 0x13f:
	//case 0x140: case 0x141: case 0x142: case 0x143: case 0x144: case 0x145: case 0x146: case 0x147:
	//case 0x148: case 0x149: case 0x14a: case 0x14b: case 0x14c: case 0x14d: case 0x14e: case 0x14f:
	//case 0x150: case 0x151: case 0x152: case 0x153: case 0x154: case 0x155: case 0x156: case 0x157:
	//case 0x158: case 0x159: case 0x15a: case 0x15b: case 0x15c: case 0x15d: case 0x15e: case 0x15f:
	//case 0x160: case 0x161: case 0x162: case 0x163: case 0x164: case 0x165: case 0x166: case 0x167:
	//case 0x168: case 0x169: case 0x16a: case 0x16b: case 0x16c: case 0x16d: case 0x16e: case 0x16f:
	//case 0x170: case 0x171: case 0x172: case 0x173: case 0x174: case 0x175: case 0x176: case 0x177:
	//case 0x178: case 0x179: case 0x17a: case 0x17b: case 0x17c: case 0x17d: case 0x17e: case 0x17f:
	/*********************************************************************************************
	 *	UVI INTERNAL
	 *********************************************************************************************/
	case 0x180: case 0x181: case 0x182: case 0x183: case 0x184: case 0x185: case 0x186: case 0x187:
	case 0x188: case 0x189: case 0x18a: case 0x18b: case 0x18c: case 0x18d: case 0x18e: case 0x18f:
	case 0x190: case 0x191: case 0x192: case 0x193: case 0x194: case 0x195: case 0x196: case 0x197:
	case 0x198: case 0x199: case 0x19a: case 0x19b: case 0x19c: case 0x19d: case 0x19e: case 0x19f:
		/* FONT DIAGRAM 1..4 OBJECT 1..4 PATTERN */
	case 0x1a0: case 0x1a1: case 0x1a2: case 0x1a3: case 0x1a4: case 0x1a5: case 0x1a6: case 0x1a7:
	case 0x1a8: case 0x1a9: case 0x1aa: case 0x1ab: case 0x1ac: case 0x1ad: case 0x1ae: case 0x1af:
	case 0x1b0: case 0x1b1: case 0x1b2: case 0x1b3: case 0x1b4: case 0x1b5: case 0x1b6: case 0x1b7:
	case 0x1b8: case 0x1b9: case 0x1ba: case 0x1bb: case 0x1bc: case 0x1bd: case 0x1be: case 0x1bf:
		PARTIAL_UPDATE();
		/* FONT DIAGRAM 5..8 */
		uvi_font[(8 * 56) + (addr - 0x180)] = data;
		break;
	//case 0x1c0: case 0x1c1: case 0x1c2: case 0x1c3: case 0x1c4: case 0x1c5: case 0x1c6: case 0x1c7:
	//case 0x1c8: case 0x1c9: case 0x1ca: case 0x1cb: case 0x1cc: case 0x1cd: case 0x1ce: case 0x1cf:
	//case 0x1d0: case 0x1d1: case 0x1d2: case 0x1d3: case 0x1d4: case 0x1d5: case 0x1d6: case 0x1d7:
	//case 0x1d8: case 0x1d9: case 0x1da: case 0x1db: case 0x1dc: case 0x1dd: case 0x1de: case 0x1df:
	//case 0x1e0: case 0x1e1: case 0x1e2: case 0x1e3: case 0x1e4: case 0x1e5: case 0x1e6: case 0x1e7:
	//case 0x1e8: case 0x1e9: case 0x1ea: case 0x1eb: case 0x1ec: case 0x1ed: case 0x1ee: case 0x1ef:
	//case 0x1f0: case 0x1f1: case 0x1f2: case 0x1f3: case 0x1f4: case 0x1f5: case 0x1f6: case 0x1f7:
	case 0x1f8:
		PARTIAL_UPDATE();
		/* Cs'j - Alternate screen color assignment to color outputs Cj */
		/* Cc'j - Alternate character color assignment to color outputs Cj */
		uvi_update_color();
		break;
	case 0x1f9:
		PARTIAL_UPDATE();
		/* Csj - Screen color assignment to color outputs Cj */
		/* Ccj - Character color assignment to color outputs Cj */
		uvi_update_color();
		break;
	case 0x1fa:
		PARTIAL_UPDATE();
		/* Cij - Color assignment of object 3..4 */
#ifdef UVI_GRAYSCALE
		uvi.obj[3].color = uvi_grayscale_table[(data     ) & 7]; /* OBJ4 */
		uvi.obj[2].color = uvi_grayscale_table[(data >> 3) & 7]; /* OBJ3 */
#else /*UVI_GRAYSCALE*/
		uvi.obj[3].color = (data     ) & 7; /* OBJ4 */
		uvi.obj[2].color = (data >> 3) & 7; /* OBJ3 */
#endif /*UVI_GRAYSCALE*/
		/* SZi - Size of object 3..4 */
		uvi.obj[3].size  = (data >> 6) & 1; /* OBJ4 */
		uvi.obj[2].size  = (data >> 7)    ; /* OBJ3 */
		break;
	case 0x1fb:
		PARTIAL_UPDATE();
		/* Cij - Color assignment of object 1..2 */
#ifdef UVI_GRAYSCALE
		uvi.obj[1].color = uvi_grayscale_table[(data     ) & 7]; /* OBJ2 */
		uvi.obj[0].color = uvi_grayscale_table[(data >> 3) & 7]; /* OBJ1 */
#else /*UVI_GRAYSCALE*/
		uvi.obj[1].color = (data     ) & 7; /* OBJ4 */
		uvi.obj[0].color = (data >> 3) & 7; /* OBJ3 */
#endif /*UVI_GRAYSCALE*/
		/* SZi - Size of object 1..2 */
		uvi.obj[1].size  = (data >> 6) & 1; /* OBJ2 */
		uvi.obj[0].size  = (data >> 7)    ; /* OBJ1 */
		break;
	//case 0x1fc: case 0x1fd: case 0x1fe: case 0x1ff:
	/*********************************************************************************************
	 *	#RCE AREA? (※UVIのマニュアルに載ってません。要確認)
	 *********************************************************************************************/
	//case 0x200: case 0x201: case 0x202: case 0x203: case 0x204: case 0x205: case 0x206: case 0x207:
	//case 0x208: case 0x209: case 0x20a: case 0x20b: case 0x20c: case 0x20d: case 0x20e: case 0x20f:
	//case 0x210: case 0x211: case 0x212: case 0x213: case 0x214: case 0x215: case 0x216: case 0x217:
	//case 0x218: case 0x219: case 0x21a: case 0x21b: case 0x21c: case 0x21d: case 0x21e: case 0x21f:
	//case 0x220: case 0x221: case 0x222: case 0x223: case 0x224: case 0x225: case 0x226: case 0x227:
	//case 0x228: case 0x229: case 0x22a: case 0x22b: case 0x22c: case 0x22d: case 0x22e: case 0x22f:
	//case 0x230: case 0x231: case 0x232: case 0x233: case 0x234: case 0x235: case 0x236: case 0x237:
	//case 0x238: case 0x239: case 0x23a: case 0x23b: case 0x23c: case 0x23d: case 0x23e: case 0x23f:
	//case 0x240: case 0x241: case 0x242: case 0x243: case 0x244: case 0x245: case 0x246: case 0x247:
	//case 0x248: case 0x249: case 0x24a: case 0x24b: case 0x24c: case 0x24d: case 0x24e: case 0x24f:
	//case 0x250: case 0x251: case 0x252: case 0x253: case 0x254: case 0x255: case 0x256: case 0x257:
	//case 0x258: case 0x259: case 0x25a: case 0x25b: case 0x25c: case 0x25d: case 0x25e: case 0x25f:
	//case 0x260: case 0x261: case 0x262: case 0x263: case 0x264: case 0x265: case 0x266: case 0x267:
	//case 0x268: case 0x269: case 0x26a: case 0x26b: case 0x26c: case 0x26d: case 0x26e: case 0x26f:
	//case 0x270: case 0x271: case 0x272: case 0x273: case 0x274: case 0x275: case 0x276: case 0x277:
	//case 0x278: case 0x279: case 0x27a: case 0x27b: case 0x27c: case 0x27d: case 0x27e: case 0x27f:
	//case 0x280: case 0x281: case 0x282: case 0x283: case 0x284: case 0x285: case 0x286: case 0x287:
	//case 0x288: case 0x289: case 0x28a: case 0x28b: case 0x28c: case 0x28d: case 0x28e: case 0x28f:
	//case 0x290: case 0x291: case 0x292: case 0x293: case 0x294: case 0x295: case 0x296: case 0x297:
	//case 0x298: case 0x299: case 0x29a: case 0x29b: case 0x29c: case 0x29d: case 0x29e: case 0x29f:
	//case 0x2a0: case 0x2a1: case 0x2a2: case 0x2a3: case 0x2a4: case 0x2a5: case 0x2a6: case 0x2a7:
	//case 0x2a8: case 0x2a9: case 0x2aa: case 0x2ab: case 0x2ac: case 0x2ad: case 0x2ae: case 0x2af:
	//case 0x2b0: case 0x2b1: case 0x2b2: case 0x2b3: case 0x2b4: case 0x2b5: case 0x2b6: case 0x2b7:
	//case 0x2b8: case 0x2b9: case 0x2ba: case 0x2bb: case 0x2bc: case 0x2bd: case 0x2be: case 0x2bf:
	//case 0x2c0: case 0x2c1: case 0x2c2: case 0x2c3: case 0x2c4: case 0x2c5: case 0x2c6: case 0x2c7:
	//case 0x2c8: case 0x2c9: case 0x2ca: case 0x2cb: case 0x2cc: case 0x2cd: case 0x2ce: case 0x2cf:
	//case 0x2d0: case 0x2d1: case 0x2d2: case 0x2d3: case 0x2d4: case 0x2d5: case 0x2d6: case 0x2d7:
	//case 0x2d8: case 0x2d9: case 0x2da: case 0x2db: case 0x2dc: case 0x2dd: case 0x2de: case 0x2df:
	//case 0x2e0: case 0x2e1: case 0x2e2: case 0x2e3: case 0x2e4: case 0x2e5: case 0x2e6: case 0x2e7:
	//case 0x2e8: case 0x2e9: case 0x2ea: case 0x2eb: case 0x2ec: case 0x2ed: case 0x2ee: case 0x2ef:
	//case 0x2f0: case 0x2f1: case 0x2f2: case 0x2f3: case 0x2f4: case 0x2f5: case 0x2f6: case 0x2f7:
	//case 0x2f8: case 0x2f9: case 0x2fa: case 0x2fb: case 0x2fc: case 0x2fd: case 0x2fe: case 0x2ff:
	/*********************************************************************************************
	 *	不明 (※UVIのマニュアルに載ってません。要確認)
	 *********************************************************************************************/
	//case 0x300: case 0x301: case 0x302: case 0x303: case 0x304: case 0x305: case 0x306: case 0x307:
	//case 0x308: case 0x309: case 0x30a: case 0x30b: case 0x30c: case 0x30d: case 0x30e: case 0x30f:
	//case 0x310: case 0x311: case 0x312: case 0x313: case 0x314: case 0x315: case 0x316: case 0x317:
	//case 0x318: case 0x319: case 0x31a: case 0x31b: case 0x31c: case 0x31d: case 0x31e: case 0x31f:
	//case 0x320: case 0x321: case 0x322: case 0x323: case 0x324: case 0x325: case 0x326: case 0x327:
	//case 0x328: case 0x329: case 0x32a: case 0x32b: case 0x32c: case 0x32d: case 0x32e: case 0x32f:
	//case 0x330: case 0x331: case 0x332: case 0x333: case 0x334: case 0x335: case 0x336: case 0x337:
	//case 0x338: case 0x339: case 0x33a: case 0x33b: case 0x33c: case 0x33d: case 0x33e: case 0x33f:
	//case 0x340: case 0x341: case 0x342: case 0x343: case 0x344: case 0x345: case 0x346: case 0x347:
	//case 0x348: case 0x349: case 0x34a: case 0x34b: case 0x34c: case 0x34d: case 0x34e: case 0x34f:
	//case 0x350: case 0x351: case 0x352: case 0x353: case 0x354: case 0x355: case 0x356: case 0x357:
	//case 0x358: case 0x359: case 0x35a: case 0x35b: case 0x35c: case 0x35d: case 0x35e: case 0x35f:
	//case 0x360: case 0x361: case 0x362: case 0x363: case 0x364: case 0x365: case 0x366: case 0x367:
	//case 0x368: case 0x369: case 0x36a: case 0x36b: case 0x36c: case 0x36d: case 0x36e: case 0x36f:
	//case 0x370: case 0x371: case 0x372: case 0x373: case 0x374: case 0x375: case 0x376: case 0x377:
	//case 0x378: case 0x379: case 0x37a: case 0x37b: case 0x37c: case 0x37d: case 0x37e: case 0x37f:
	//case 0x380: case 0x381: case 0x382: case 0x383: case 0x384: case 0x385: case 0x386: case 0x387:
	//case 0x388: case 0x389: case 0x38a: case 0x38b: case 0x38c: case 0x38d: case 0x38e: case 0x38f:
	//case 0x390: case 0x391: case 0x392: case 0x393: case 0x394: case 0x395: case 0x396: case 0x397:
	//case 0x398: case 0x399: case 0x39a: case 0x39b: case 0x39c: case 0x39d: case 0x39e: case 0x39f:
	//case 0x3a0: case 0x3a1: case 0x3a2: case 0x3a3: case 0x3a4: case 0x3a5: case 0x3a6: case 0x3a7:
	//case 0x3a8: case 0x3a9: case 0x3aa: case 0x3ab: case 0x3ac: case 0x3ad: case 0x3ae: case 0x3af:
	//case 0x3b0: case 0x3b1: case 0x3b2: case 0x3b3: case 0x3b4: case 0x3b5: case 0x3b6: case 0x3b7:
	//case 0x3b8: case 0x3b9: case 0x3ba: case 0x3bb: case 0x3bc: case 0x3bd: case 0x3be: case 0x3bf:
	//case 0x3c0: case 0x3c1: case 0x3c2: case 0x3c3: case 0x3c4: case 0x3c5: case 0x3c6: case 0x3c7:
	//case 0x3c8: case 0x3c9: case 0x3ca: case 0x3cb: case 0x3cc: case 0x3cd: case 0x3ce: case 0x3cf:
	//case 0x3d0: case 0x3d1: case 0x3d2: case 0x3d3: case 0x3d4: case 0x3d5: case 0x3d6: case 0x3d7:
	//case 0x3d8: case 0x3d9: case 0x3da: case 0x3db: case 0x3dc: case 0x3dd: case 0x3de: case 0x3df:
	//case 0x3e0: case 0x3e1: case 0x3e2: case 0x3e3: case 0x3e4: case 0x3e5: case 0x3e6: case 0x3e7:
	//case 0x3e8: case 0x3e9: case 0x3ea: case 0x3eb: case 0x3ec: case 0x3ed: case 0x3ee: case 0x3ef:
	//case 0x3f0: case 0x3f1: case 0x3f2: case 0x3f3: case 0x3f4: case 0x3f5: case 0x3f6: case 0x3f7:
	//case 0x3f8: case 0x3f9: case 0x3fa: case 0x3fb: case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff:
	default:
		uvi.ram[addr] = data; /* 必要!! */
		break;
	}

#undef PARTIAL_UPDATE
}

void
uvi_set_pot(int pot, int data)
{
	ASSERT((pot >= 0) && (pot <= 3));
	ASSERT((data >= 0) && (data <= 0xff));

	uvi.pot[pot] = data;
}

void
uvi_new_frame(unsigned char* vbuff/*[128*208]*/)
{
	/* 仮想画面を格納します。(NULL:実描画無し) */
	uvi.vbuff = vbuff;

	/* 垂直スクロール座標を取得します。フレームの途中での変更は反映しません。(仕様) */
	uvi.scroll_y = (uvi.ram[0x0fc] ^ 0xff) - 17/*※目分量で調整*/;

	/* 衝突判定をクリアします。 */
	uvi.ram[0x1fc] = -1; /* Oic - Collision status between object i and any character */
	uvi.ram[0x1fd] = -1; /* Iij - Collision status between objects i and j */

	/* A/D Potentiometerは、Vblank期間中に正しい値を返し、表示期間中は$FFを返します。
	 * -----------------------------------------------------------
	 * Graphics Mode Control [Signetics 2637 UVI マニュアル (p.8)]
	 * -----------------------------------------------------------
	 * During  VRST: Returns value of A/D counter on POT1..4 inputs. Range = $00..$FE
	 * During #VRST: Returns value $FF
	 */
	uvi.ram[0x1fe] = -1; /* POT2/4, During #VRST */
	uvi.ram[0x1ff] = -1; /* POT1/3, During #VRST */

	/* 全ての行を"未取得"状態とします。 */
	memset(uvi.scroll_x, -1/*未取得*/, sizeof uvi.scroll_x);

	/* スキャンライン0に対応するROW(ram[0x0ff])設定のために、uvi_next_line()を呼びます。
	 * - 可視領域の上端1ラインだけに見える行がある場合のためにも、この処理は必要です。
	 *   uvi_next_line()の先頭でscan_end+1した結果、scan_end=0の状態で処理を行わないと、
	 *   可視領域の上端1ラインだけに見える行のデータを取得するタイミングがありません。
	 *   すなわち、最初にscan_end=-1の状態でuvi_next_line()を呼んでおく必要があります。
	 */
	uvi.scan_start = 0;
	uvi.scan_end = -1; /* uvi_next_line()の先頭で、ただちにscan_end=0になります */
	uvi_next_line();
	ASSERT(uvi.scan_end == 0);
}

int
uvi_next_line()
{
	int col;
	int row;
	int gm;
	int code;
	unsigned char* ram;
	unsigned short* bg;

	/* スキャンラインを進めます。 */
	uvi.scan_end++;

	/* R = ROW; 15 implies beginning of DMA, 13 imdicates end of DMA
	 *
	 *			       スクロール無しの場合
	 *	  0		+--------------------------------+---
	 *			|                                | A
	 *			|  0,1,2,3,4,5,6,7,8,9,10,11,12  | |
	 *			|                                | |
	 *	104		+--------------------------------+ 可視領域
	 *			|                                | |
	 *			|  0,1,2,3,4,5,6,7,8,9,10,11,12  | |
	 *			|                                | V
	 *	208		+--------------------------------+---
	 *			|               13               | Vblank
	 *	262		+--------------------------------+---
	 *
	 *			       スクロール有りの場合
	 *	  0		+--------------------------------+---
	 *			|          15 (※要確認)         | A
	 *	  0+scroll_y	+--------------------------------+ |
	 *			|                                | |
	 *			|  0,1,2,3,4,5,6,7,8,9,10,11,12  | 可視領域
	 *			|                                | |
	 *	104+scroll_y	+--------------------------------+ |
	 *			|  0,1,2,3,4,5 (ｽｸﾛｰﾙした分減る) | V
	 *	208		+--------------------------------+---
	 *			|               13               | Vblank
	 *	262		+--------------------------------+---
	 */
	row = uvi.scan_end - uvi.scroll_y;
	if(row < 0) {
		row = 15;
	} else {
		if(uvi.ram[0x1f9] & (1<<7)) { /* SZ0: 1=x8x8, 0=8x16 */
			row /= 8;
		} else {
			row /= 16;
		}
		if(row < 26) {
			/* 現在のスキャンラインを含む行の水平スクロールとBGデータが未取得ならば... */
			if(uvi.scroll_x[row] == -1/*未取得*/) {

				/* 水平スクロール座標を取得します。行の途中での変更は反映しません。(仕様) */
				uvi.scroll_x[row] = (uvi.ram[0x0fe] >> 5) & 7;

				/* 現在行に対応するBGデータのアドレスと、RAMアドレスを計算します。 */
				bg = &uvi.bg[row][0];
				ram = &uvi.ram[0x000 + row * 16]; /* row= 0..12: ram[0x000..0x0cf] */
				if(row >= 13) {
					ram += 0x200 - (13 * 16); /* row=13..25: ram[0x200..0x2cf] */
				}

				/* ------------------------------------------------------------
				 * Graphics Mode Control [Signetics 2637 UVI マニュアル (p.11)]
				 * ------------------------------------------------------------
				 * At the beggining of each TV scan line,
				 * graphics mode is determined by the state of the MODE bit at UVI address H'1F8':
				 *   (GM = 1) set graphics mode
				 *   (GM = 0) reset graphics mode
				 * Additional graphics mode controls is provided by two special character codes:
				 *   11000000 display blank and set graphics mode
				 *   01000000 display blank and reset graphics mode
				 */
				gm = (uvi.ram[0x1f8] & 0x80) >> 1;
				col = 16;
				do {
					code = *ram++;
					switch(code) {
					case 0x40: gm = 0x00; break;
					case 0xc0: gm = 0x40; break;
					}
					*bg++ = (code & 0xc0) << 2 | gm | (code & 0x3f);
				} while(--col);
			}
			if(row >= 13) {
				row -= 13;
			}
		} else {
			row = 13;
		}
	}
	uvi.ram[0x0ff] = 0xf0 | row; /* R = ROW; 上位4bitは1111固定 */

	/* スキャンラインが可視領域を超えた瞬間に... */
	if(uvi.scan_end == 208) {

		/* 未更新領域を全て描画します。 */
		uvi_partial_update();

		/* 衝突判定を行います。 */
		uvi_collision();

		/* A/D Potentiometerは、Vblank期間中に正しい値を返し、表示期間中は$FFを返します。
		 * -----------------------------------------------------------
		 * Graphics Mode Control [Signetics 2637 UVI マニュアル (p.8)]
		 * -----------------------------------------------------------
		 * During  VRST: Returns value of A/D counter on POT1..4 inputs. Range = $00..$FE
		 * During #VRST: Returns value $FF
		 */
		uvi_update_pot(); /* POT1..4, During VRST */
	}

	/* 次回のスキャンラインがVblank期間ならば1を返します。
	 * ARCADIAの場合、2650 CPU へのSENSE入力に相当します。
	 */
	return (uvi.scan_end >= 208) && (uvi.scan_end <= 261);
}

void
uvi_collision()
{
	int oic = uvi.ram[0x1fc]; /* Oic - Collision status between object i and any character */
	int iij = uvi.ram[0x1fd]; /* Iij - Collision status between objects i and j */
	//
	int oic_mask = 1; /* |---|---|I34|I24|I23|I14|I13|I12| */
	int iij_mask = 1; /* |---|---|---|---|O4c|O3c|O2c|O1c| */
	//
	int retval;
	int i;
	int j;
	UVIOBJ* obj1;
	UVIOBJ* obj2;

	for(i = 0; i < 4; i++) {
		obj1 = &uvi.obj[i];

		/*---------- OBJ×BG ----------*/

		if(obj1->size) { /* 1=x8x8, 0=8x16 */
			if(uvi.ram[0x1f9] & (1<<7)) { /* SZ0: 1=x8x8, 0=8x16 */
				retval = uvi_collision_obj_8x8_bg_8x8(obj1);
			} else {
				retval = uvi_collision_obj_8x8_bg_8x16(obj1);
			}
		} else {
			if(uvi.ram[0x1f9] & (1<<7)) { /* SZ0: 1=x8x8, 0=8x16 */
				retval = uvi_collision_obj_8x16_bg_8x8(obj1);
			} else {
				retval = uvi_collision_obj_8x16_bg_8x16(obj1);
			}
		}
		if(retval) {
			oic &= ~oic_mask;
		}
		oic_mask <<= 1;

		/*---------- OBJ×OBJ ----------*/

		for(j = i + 1; j < 4; j++) {
			obj2 = &uvi.obj[j];
			if(obj1->size) { /* 1=x8x8, 0=8x16 */
				if(obj2->size) { /* 1=x8x8, 0=8x16 */
					retval = uvi_collision_obj_8x8_obj_8x8(obj1, obj2);
				} else {
					retval = uvi_collision_obj_8x8_obj_8x16(obj1, obj2);
				}
			} else {
				if(obj2->size) { /* 1=x8x8, 0=8x16 */
					retval = uvi_collision_obj_8x16_obj_8x8(obj1, obj2);
				} else {
					retval = uvi_collision_obj_8x16_obj_8x16(obj1, obj2);
				}
			}
			if(retval) {
				iij &= ~iij_mask;
			}
			iij_mask <<= 1;
		}
	}

	uvi.ram[0x1fc] = oic;
	uvi.ram[0x1fd] = iij;
}

/****************************************************************************
 *	内部関数 (Sound)
 ****************************************************************************/

#ifndef UVI_ASM

void
uvi_tone_mix(short wbuff[/*UVIBUFLEN*/], int volume, int period)
{
	int i;
	int polar;
	int count;
	int output;

	polar = uvi.tone_polar; /* 取り出し */
	count = uvi.tone_count; /* 取り出し */

	output = polar ? volume : -volume;

	i = UVIBUFLEN;
	do {
		count -= NTSC_COLOR_SUBCARRIER;
		if(count < 0) {
			do {
				polar ^= 1;
			} while((count += period) < 0);
			output = polar ? volume : -volume;
		}
		*wbuff++ += output;
	} while(--i);

	uvi.tone_polar = polar; /* 書き戻し */
	uvi.tone_count = count; /* 書き戻し */
}

void
uvi_noise_mix(short wbuff[/*UVIBUFLEN*/], int volume, int period)
{
	int i;
	int x;
	int lfsr;
	int count;
	int output;

	lfsr  = uvi.noise_lfsr;  /* 取り出し */
	count = uvi.noise_count; /* 取り出し */

	output = (lfsr & 1) ? volume : -volume;

	i = UVIBUFLEN;
	do {
		count -= NTSC_COLOR_SUBCARRIER;
		if(count < 0) {
			do {
				/* 9bit: tap=0,5 (仕様不明。独断です) */
				x = (lfsr ^ (lfsr >> 5)) & 1;
				lfsr = (lfsr >> 1) | (x << 8);
			} while((count += period) < 0);
			output = (lfsr & 1) ? volume : -volume;
		}
		*wbuff++ += output;
	} while(--i);

	uvi.noise_lfsr  = lfsr;  /* 書き戻し */
	uvi.noise_count = count; /* 書き戻し */
}

/*   Tone  符号付き4bit
 * + Noise 符号付き4bit
 * =       符号付き5bit
 */
#define UVI_VOLUME_SHIFT	((16-5)+1/*調整可*/)

void
uvi_volume_shift(short wbuff[/*UVIBUFLEN*/])
{
	int i;
	int v;

	i = UVIBUFLEN;
	do {
		v = *wbuff;
		v <<= UVI_VOLUME_SHIFT;
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*wbuff++ = v;
	} while(--i);
}

#endif /*UVI_ASM*/

/****************************************************************************
 *	アプリケーション用関数 (Sound)
 ****************************************************************************/

void
uvi_mix(short wbuff[/*UVIBUFLEN*/])
{
	int n   = uvi.ram[0x0fd] & 0x7f;   /* N = frequency counter terminal count */
	int ls  = uvi.ram[0x0fe] & 0x07;   /* LS specifies 1 of 8 levels of loudness */
	int fen = uvi.ram[0x0fe] & (1<<3); /* FEN = 1 enables the frequency counter to the sound output */
	int rng = uvi.ram[0x0fe] & (1<<4); /* RNG = 1 enables random noise to the sound output */

	memset(wbuff, 0, sizeof(short) * UVIBUFLEN);
	if(ls && (fen | rng)) {
		if(fen && n) {
			/* -------------------------------------------------------------------
			 * Fixed Frequency Control [Signetics 2637 UVI マニュアル (p.11)] 参照
			 * -------------------------------------------------------------------
			 *
			 *                  水平周波数
			 * 出力周波数     = ----------   (N=0:Off)
			 *                    2(N+1)
			 *
			 *                  水平周波数   NTSC_COLOR_SUBCARRIER/262   NTSC_COLOR_SUBCARRIER
			 * 極性反転周波数 = ---------- = ------------------------- = ---------------------
			 *                      N+1                 N+1                     262(N+1)      
			 *
			 *                  (262(N+1))×SPEAKER_FREQUENCY <- tone_count OverFlow時の変化量
			 * 極性反転周期   = -----------------------------
			 *                      NTSC_COLOR_SUBCARRIER     <- tone_count 毎回の変化量
			 *
			 * NOTE: ((262(N+1))×SPEAKER_FREQUENCY ... min=$003FF700, max=$1FFB8000, OK!
			 */
			uvi_tone_mix(wbuff, ls, (n + 1) * 262 * SPEAKER_FREQUENCY);
		}
		if(rng && n) {
			/* --------------------------------------------------------
			 * Random Noise [Signetics 2637 UVI マニュアル (p.11)] 参照
			 * --------------------------------------------------------
			 *                  水平周波数
			 * 出力周波数     = ----------   (N=0:Off)
			 *                      N
			 *
			 *                  水平周波数   NTSC_COLOR_SUBCARRIER/262   NTSC_COLOR_SUBCARRIER
			 * シフト周波数   = ---------- = ------------------------- = ---------------------
			 *                      N                   N                        262N         
			 *
			 *                  262N×SPEAKER_FREQUENCY <- noise_count OverFlow時の変化量
			 * シフト周期     = -----------------------
			 *                   NTSC_COLOR_SUBCARRIER  <- noise_count 毎回の変化量
			 *
			 * NOTE: 262N×SPEAKER_FREQUENCY ... min=$003FF700, max=$1FBB8900, OK!
			 */
			uvi_noise_mix(wbuff, ls, n * 262 * SPEAKER_FREQUENCY);
		}
		uvi_volume_shift(wbuff);
	}
}
