--- a/src/mame/sharp/x68k_crtc.h	2026-08-04 16:07:13.182675756 -0500
+++ b/src/mame/sharp/x68k_crtc.h	2026-08-04 16:32:58.889623301 -0500
@@ -61,7 +61,7 @@
 private:
 	// internal helpers
 	void text_copy(unsigned src, unsigned dest, u8 planes);
-	TIMER_CALLBACK_MEMBER(operation_end);
+	void do_fast_clear();
 	void refresh_mode();
 	TIMER_CALLBACK_MEMBER(hsync);
 	TIMER_CALLBACK_MEMBER(raster_end);
@@ -84,6 +84,12 @@
 	// internal state
 	u16 m_reg[24];  // registers
 	u8 m_operation;  // operation port (0xe80481)
+	// Graphic high-speed clear (operation port bit 1). Writing 1 only
+	// RESERVES the clear; it starts at the next vertical display start and
+	// runs for one frame (two when interlaced). Reads of bit 1 report
+	// m_clear_frames != 0, so software can wait for start then for end.
+	bool m_clear_standby;  // clear reserved, waiting for vertical display start
+	u8 m_clear_frames;     // frames remaining in the running clear (0 = idle)
 	bool m_vblank;  // true if in VBlank
 	bool m_hblank;  // true if in HBlank
 	u16 m_htotal;  // Horizontal Total (in characters)
@@ -111,7 +117,6 @@
 	emu_timer *m_raster_irq_timer;
 	emu_timer *m_vblank_irq_timer;
 	emu_timer *m_raster_end_timer;
-	emu_timer *m_operation_end_timer;
 };
 
 class vinas_device : public x68k_crtc_device
--- a/src/mame/sharp/x68k_crtc.cpp	2026-08-04 16:07:13.181675761 -0500
+++ b/src/mame/sharp/x68k_crtc.cpp	2026-08-04 16:32:58.889623301 -0500
@@ -25,6 +25,8 @@
 	, m_clock_69m(0)
 	, m_clock_50m(0)
 	, m_operation(0)
+	, m_clear_standby(false)
+	, m_clear_frames(0)
 	, m_vblank(false)
 	, m_hblank(false)
 	, m_htotal(0)
@@ -58,7 +60,6 @@
 void x68k_crtc_device::device_start()
 {
 	m_scanline_timer = timer_alloc(FUNC(x68k_crtc_device::hsync), this);
-	m_operation_end_timer = timer_alloc(FUNC(x68k_crtc_device::operation_end), this);
 	m_raster_end_timer = timer_alloc(FUNC(x68k_crtc_device::raster_end), this);
 	m_raster_irq_timer = timer_alloc(FUNC(x68k_crtc_device::raster_irq), this);
 	m_vblank_irq_timer = timer_alloc(FUNC(x68k_crtc_device::vblank_irq), this);
@@ -66,6 +67,8 @@
 	// save state
 	save_item(NAME(m_reg));
 	save_item(NAME(m_operation));
+	save_item(NAME(m_clear_standby));
+	save_item(NAME(m_clear_frames));
 	save_item(NAME(m_vblank));
 	save_item(NAME(m_hblank));
 	save_item(NAME(m_htotal));
@@ -97,6 +100,10 @@
 	m_reg[7] = 552;  // Vertical end
 	m_reg[8] = 27;   // Horizontal adjust
 
+	// No graphic high-speed clear is reserved or running out of reset.
+	m_clear_standby = false;
+	m_clear_frames = 0;
+
 	//m_scanline = screen().vpos();// = m_reg[6];  // Vertical start
 
 	// start VBlank timer
@@ -127,15 +134,58 @@
 	}
 }
 
-TIMER_CALLBACK_MEMBER(x68k_crtc_device::operation_end)
-{
-	if(!(m_operation & param))
+// Graphic high-speed clear, performed when the operation actually starts
+// (at a vertical display start), not when the register is written.
+//
+// Range is determined by the screen size, the real screen size and PAGE 0's
+// scroll position. When the real screen is 1024x1024 the page select is
+// ignored and every page is cleared.
+void x68k_crtc_device::do_fast_clear()
+{
+	// this is based on the docs except for the higher color depth modes which isn't
+	// explicitly described this way but is likely based on how the plane scroll works
+	// XXX: not sufficiently tested especially in hires modes
+	// it seems that it only uses the 0 page scroll registers for where to clear see atomrobo
+	uint16_t xscr = xscr_gfx(0) & 0x1ff;
+	uint16_t yscr = yscr_gfx(0) & 0x1ff;
+	uint16_t mask = 0;
+	for (int page = 0; page < 4; page++)
 	{
-		m_operation |= param;
-		m_operation_end_timer->adjust(attotime::from_msec(5), param);
+		if (!(m_reg[21] & (1 << page)))
+			mask |= (0xf << (page * 4));
+	}
+	for (int y = yscr; y < (m_height + yscr); y++)
+	{
+		if (is_1024x1024())
+		{
+			if (m_width > 256)
+			{
+				for (int x = 0; x < 512; x++)
+				{
+					uint16_t data = m_gvram_read_callback(((y * 512) + x) & 0x3ffff, 0xffff);
+					m_gvram_write_callback(((y * 512) + x) & 0x3ffff, data & mask, 0xffff);
+				}
+			}
+			else
+			{
+				for (int x = 0; x < 256; x++)
+				{
+					uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff);
+					m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff);
+					data = m_gvram_read_callback(((y * 512) + x + xscr + 256) & 0x3ffff, 0xffff);
+					m_gvram_write_callback(((y * 512) + x + xscr + 256) & 0x3ffff, data & mask, mask);
+				}
+			}
+		}
+		else
+		{
+			for (int x = 0; x < m_width; x++)
+			{
+				uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff);
+				m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff);
+			}
+		}
 	}
-	else
-		m_operation &= ~param;
 }
 
 void x68k_crtc_device::refresh_mode()
@@ -215,9 +265,6 @@
 	m_hblank = hstate;
 	m_hsync_callback(!m_hblank);
 
-	if (m_operation & 8)
-		text_copy((m_reg[22] & 0xff00) >> 8, (m_reg[22] & 0x00ff), (m_reg[21] & 0xf));
-
 	int scan = screen().vpos();
 	if (hstate == 1)
 	{
@@ -228,6 +275,18 @@
 	}
 	if (hstate == 0)
 	{
+		// Text raster copy, done ONCE per horizontal period at the start of
+		// the horizontal front porch (this callback fires at m_hend, the
+		// horizontal display end). Operation port bit 3 is a level-sensitive
+		// switch that the CRTC never clears and that has no busy indication:
+		// while it is set, one raster block is copied every scanline. Running
+		// this at the top of the callback instead copied on BOTH hsync edges,
+		// i.e. twice per scanline -- harmless while R22 is stable, but wrong
+		// for the documented idiom of leaving bit 3 on and pacing R22 updates
+		// against hsync.
+		if (m_operation & 8)
+			text_copy((m_reg[22] & 0xff00) >> 8, (m_reg[22] & 0x00ff), (m_reg[21] & 0xf));
+
 		if (scan == (m_vtotal - 1))
 			scan = 0;
 		else
@@ -278,6 +337,30 @@
 	if (val == 0)  // V-DISP off
 	{
 		m_vblank = 0;
+		// This branch is the VDISP 0->1 edge (it asserts m_vdisp_callback
+		// below), which is where the graphic high-speed clear is serviced.
+		// A running clear counts down one frame; otherwise a reservation
+		// made via the operation port starts here. Only one of the two
+		// happens per frame, so a clear reserved during the frame in which
+		// a previous clear is still running does not start until that one
+		// has finished -- which is what makes a write immediately after
+		// busy drops miss the next frame on hardware.
+		if (m_clear_frames != 0)
+		{
+			m_clear_frames--;
+		}
+		else if (m_clear_standby)
+		{
+			m_clear_standby = false;
+			// One vertical period, and no interlace special case: refresh_mode()
+			// divides the vertical timing by m_vmultiple (0.5 when interlaced),
+			// so an interlaced screen already spans BOTH fields. One VDISP-to-
+			// VDISP here is therefore already the two vertical periods an
+			// interlaced clear occupies on hardware.
+			m_clear_frames = 1;
+			if (m_reg[21] & 0xf)
+				do_fast_clear();
+		}
 		vblank_line = m_vend;
 		if (vblank_line > m_vtotal)
 			vblank_line = m_vtotal;
@@ -412,54 +495,14 @@
 		break;
 	case 576:  // operation register
 		m_operation = data & ~2;
-		if ((data & 0x02) && (m_reg[21] & 0xf))  // high-speed graphic screen clear
-		{
-			// this is based on the docs except for the higher color depth modes which isn't
-			// explicitly described this way but is likely based on how the plane scroll works
-			// XXX: not sufficiently tested especially in hires modes
-			// it seems that it only uses the 0 page scroll registers for where to clear see atomrobo
-			uint16_t xscr = xscr_gfx(0) & 0x1ff;
-			uint16_t yscr = yscr_gfx(0) & 0x1ff;
-			uint16_t mask = 0;
-			for (int page = 0; page < 4; page++)
-			{
-				if (!(m_reg[21] & (1 << page)))
-					mask |= (0xf << (page * 4));
-			}
-			for (int y = yscr; y < (m_height + yscr); y++)
-			{
-				if (is_1024x1024())
-				{
-					if (m_width > 256)
-					{
-						for (int x = 0; x < 512; x++)
-						{
-							uint16_t data = m_gvram_read_callback(((y * 512) + x) & 0x3ffff, 0xffff);
-							m_gvram_write_callback(((y * 512) + x) & 0x3ffff, data & mask, 0xffff);
-						}
-					}
-					else
-					{
-						for (int x = 0; x < 256; x++)
-						{
-							uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff);
-							m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff);
-							data = m_gvram_read_callback(((y * 512) + x + xscr + 256) & 0x3ffff, 0xffff);
-							m_gvram_write_callback(((y * 512) + x + xscr + 256) & 0x3ffff, data & mask, mask);
-						}
-					}
-				}
-				else
-				{
-					for (int x = 0; x < m_width; x++)
-					{
-						uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff);
-						m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff);
-					}
-				}
-			}
-		}
-		if (data & 0x02) m_operation_end_timer->adjust(attotime::from_msec(5), 0x02);  // time taken to do operation is a complete guess.
+		// Bit 1 only RESERVES the graphic high-speed clear -- it does not
+		// perform it. Hardware latches the request and starts the clear at the
+		// next vertical display start; see vblank_irq(). Writing 0 cannot
+		// cancel or abort a reservation that has already been made, and a
+		// request made while a clear is already running is dropped rather than
+		// queued: the reservation only takes when bit 1 reads back as 0.
+		if ((data & 0x02) && m_clear_frames == 0)
+			m_clear_standby = true;
 		break;
 	}
 //  LOG("%s CRTC: Wrote %04x to CRTC register %i\n",machine().describe_context(), data, offset);
@@ -493,7 +536,14 @@
 		}
 	}
 	if (offset == 576) // operation port, operation bits are set to 0 when operation is complete
-		return m_operation;
+	{
+		// Bit 1 reads as the graphic high-speed clear's busy state: it is set
+		// from the vertical display start at which the clear begins until the
+		// vertical display start one frame later (two when interlaced). The
+		// documented wait sequence is to poll for bit 1 becoming set (start)
+		// and then for it becoming clear again (end).
+		return m_operation | (m_clear_frames != 0 ? 0x02 : 0x00);
+	}
 //  LOG("CRTC: [%08x] Read from unknown CRTC register %i\n",activecpu_get_pc(),offset);
 	return 0xffff;
 }
