Index: emu/cpu.c
===================================================================
--- emu/cpu.c	(revision 7b50125d0c8b6074abd7478c3e565703e41dda81)
+++ emu/cpu.c	(revision ac4e192cb8973aecb5752d6cc4436c183b1807cb)
@@ -74,5 +74,5 @@
 static hw_t hw_map[] = {
 	{ 0x180000, 0x200000, 0, fpu_init, fpu_quit, fpu_exec, fpu_read, fpu_write },
-	{ 0x200000, 0x280002, 0, vid_init, vid_quit, vid_exec, vid_read, vid_write },
+	{ 0x200000, 0x280002, 1, vid_init, vid_quit, vid_exec, vid_read, vid_write },
 	{ 0x3a0001, 0x3a4001, 4, tim_init, tim_quit, tim_exec, tim_read, tim_write },
 	{ 0x3a4001, 0x3a8001, 0, lcd_init, lcd_quit, lcd_exec, lcd_read, lcd_write },
Index: emu/vid.c
===================================================================
--- emu/vid.c	(revision 7b50125d0c8b6074abd7478c3e565703e41dda81)
+++ emu/vid.c	(revision ac4e192cb8973aecb5752d6cc4436c183b1807cb)
@@ -24,4 +24,40 @@
 int32_t vid_verbose = 0;
 
+#define SCR_W 512
+#define SCR_H 350
+#define VBL_H 50
+
+#define REG_VCR0 0
+#define REG_VCR1 1
+#define REG_RWBA 2
+#define REG_DSBA 5
+#define REG_ODTBA 7
+#define REG_ATBA 8
+#define REG_ATBAC 11
+
+#define VCR0_UCF 0x0001
+#define VCR0_DEN 0x0008
+
+#define DSBA_BS1 0x0080
+#define DSBA_BS0 0x0100
+
+#define REG_OFF 0x200
+#define PAL_OFF 0x40000
+
+#define OD0_BLA 0x0010
+
+#define WIN_SZ_W 0x10000
+#define MEM_SZ_W 0x20000
+
+#define N_BANKS 2
+
+static int32_t reg_off = REG_OFF;
+
+static uint16_t mem[MEM_SZ_W];
+static int32_t bank = 0;
+
+static int32_t pal[16];
+static int32_t i_pal = 0;
+
 void vid_init(void)
 {
@@ -37,4 +73,91 @@
 {
 	ver3("vid exec");
+
+	static int32_t skip = 99999;
+	static int32_t line = 99999;
+
+	if (++skip < 10) {
+		ver3("vid skip %d", skip);
+		return false;
+	}
+
+	skip = 0;
+	int32_t vcr0 = mem[REG_VCR0];
+
+	if ((vcr0 & VCR0_DEN) == 0) {
+		ver3("vid dis");
+		return false;
+	}
+
+	if (++line < SCR_H + VBL_H) {
+		ver3("vid line %d", line);
+
+		if (line < SCR_H) {
+			++mem[REG_ATBAC];
+		}
+
+		return line == SCR_H;
+	}
+
+	// We get here every 4,000 invocations -> 25 fps.
+
+	ver2("vid frame");
+	line = 0;
+
+	int32_t dsba = mem[REG_DSBA];
+	bank = ((dsba & DSBA_BS0) != 0 ? 1 : 0) + ((dsba & DSBA_BS1) != 0 ? 2 : 0);
+
+	int32_t rwba = mem[REG_RWBA];
+	reg_off = (rwba & 0xfff0) << 1;
+
+	int32_t atba = mem[REG_ATBA];
+	mem[REG_ATBAC] = (uint16_t)atba;
+
+	int32_t odtba = mem[REG_ODTBA] & 0xffc0;
+	int32_t y_beg[16], y_end[16];
+
+	for (int32_t i = 0; i < 16; ++i) {
+		y_beg[i] = -1;
+		y_end[i] = -1;
+
+		uint16_t *od = mem + odtba + 4 * i;
+
+		if ((od[0] & OD0_BLA) != 0) {
+			ver3("obj %d blanked", i);
+			continue;
+		}
+
+		int32_t w = (od[1] & 0xfc00) >> 6;
+
+		if (w == 0) {
+			ver3("obj %d empty", i);
+			continue;
+		}
+
+		int32_t mask = 1 << i;
+
+		for (int32_t k = 0; k < SCR_H; ++k) {
+			if ((mem[atba + k] & mask) == 0) {
+				if (y_beg[i] < 0) {
+					y_beg[i] = k;
+				}
+				else if (y_end[i] < 0) {
+					y_end[i] = k;
+				}
+			}
+		}
+
+		if (y_beg[i] < 0) {
+			ver3("obj %d unused", i);
+			continue;
+		}
+
+		int32_t x = od[1] & 0x03ff;
+		ver2("obj %d %d:%d %d+%d", i, y_beg[i], y_end[i], x, w);
+	}
+
+	int32_t vcr1 = mem[REG_VCR1];
+	int32_t fon_h = (vcr1 & 0xf000) >> 12;
+
 	return false;
 }
@@ -42,10 +165,40 @@
 uint32_t vid_read(uint32_t off, int32_t sz)
 {
-	ver2("vid rd 0x%05x:%d", off, sz * 8);
-	return 0;
+	ver2("vid rd %d 0x%05x:%d", bank, off, sz * 8);
+
+	int32_t off16 = (int32_t)(off / 2);
+
+	if (sz != 2 || bank >= N_BANKS || off % 2 != 0 || off16 >= WIN_SZ_W) {
+		fail("invalid vid rd %d %u:%d", bank, off, sz * 8);
+	}
+
+	if (off16 >= reg_off && off16 < reg_off + 16) {
+		return mem[off16 - reg_off];
+	}
+
+	return mem[bank * WIN_SZ_W + off16];
 }
 
 void vid_write(uint32_t off, int32_t sz, uint32_t val)
 {
-	ver2("vid wr 0x%05x:%d 0x%0*x", off, sz * 8, sz * 2, val);
-}
+	ver2("vid wr %d 0x%05x:%d 0x%0*x", bank, off, sz * 8, sz * 2, val);
+
+	int32_t off16 = (int32_t)(off / 2);
+
+	if (sz != 2 || bank >= N_BANKS || off % 2 != 0 || (off16 >= WIN_SZ_W && off16 != PAL_OFF)) {
+		fail("invalid vid wr %d %u:%d", bank, off, sz * 8);
+	}
+
+	if (off16 == PAL_OFF) {
+		pal[i_pal] = (int32_t)val;
+		i_pal = (i_pal + 1) % 16;
+		return;
+	}
+
+	if (off16 >= reg_off && off16 < reg_off + 16) {
+		mem[off16 - reg_off] = (uint16_t)val;
+		return;
+	}
+
+	mem[bank * WIN_SZ_W + off16] = (uint16_t)val;
+}
