diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp
index 98089e847..dd8a0ec85 100644
--- a/Components/9918/Implementation/9918.cpp
+++ b/Components/9918/Implementation/9918.cpp
@@ -44,11 +44,10 @@ Base<personality>::Base() :
 	}
 
 	if constexpr (is_yamaha_vdp(personality)) {
-		// TODO: start of sync, or end of sync? Or elsewhere.
-		// Note that there's a bug elsewhere if the proper value of this is zero in the
-		// "if started before but reached this count" logic — that is boxed into considering
-		// a single line only so never sees starts before 0.
-		mode_timing_.line_interrupt_position = LineLayout<personality>::EndOfSync;
+		// TODO: this is used for interrupt _prediction_ but won't handle text modes correctly, and indeed
+		// can't be just a single value where the programmer has changed into or out of text modes during the
+		// middle of a line, since screen mode is latched (so it'll be one value for that line, another from then onwards).a
+		mode_timing_.line_interrupt_position = LineLayout<personality>::EndOfPixels;
 	}
 
 	// Establish that output is delayed after reading by `output_lag` cycles,
@@ -275,11 +274,14 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 			// -------------------------------
 			// Check for interrupt conditions.
 			// -------------------------------
-			if(this->fetch_pointer_.column < this->mode_timing_.line_interrupt_position && end_column >= this->mode_timing_.line_interrupt_position) {
+			if constexpr (is_sega_vdp(personality)) {
 				// The Sega VDP offers a decrementing counter for triggering line interrupts;
 				// it is reloaded either when it overflows or upon every non-pixel line after the first.
 				// It is otherwise decremented.
-				if constexpr (is_sega_vdp(personality)) {
+				if(
+					this->fetch_pointer_.column < this->mode_timing_.line_interrupt_position &&
+					end_column >= this->mode_timing_.line_interrupt_position
+				) {
 					if(this->fetch_pointer_.row >= 0 && this->fetch_pointer_.row <= this->mode_timing_.pixel_lines) {
 						if(!this->line_interrupt_counter_) {
 							this->line_interrupt_pending_ = true;
@@ -291,14 +293,27 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 						this->line_interrupt_counter_ = this->line_interrupt_target_;
 					}
 				}
+			}
 
-				if constexpr (is_yamaha_vdp(personality)) {
-					if(
-						this->vertical_active_ &&
-						this->fetch_pointer_.row == ((this->line_interrupt_target_ - Storage<personality>::vertical_offset_) & 0xff)
-					) {
-						this->line_interrupt_pending_ = true;
-					}
+			if constexpr (is_yamaha_vdp(personality)) {
+				// The Yamaha VDPs allow the user to specify which line an interrupt should occur on,
+				// which is relative to the current vertical base. Such an interrupt will occur immediately
+				// after pixels have ended.
+				if(
+					this->vertical_active_ &&
+					this->fetch_pointer_.column < Storage<personality>::mode_description_.end_cycle &&
+					end_column >= Storage<personality>::mode_description_.end_cycle &&
+					this->fetch_pointer_.row == ((this->line_interrupt_target_ - Storage<personality>::vertical_offset_) & 0xff)
+				) {
+					this->line_interrupt_pending_ = true;
+					Storage<personality>::line_matches_ = true;
+				}
+
+				if(
+					this->fetch_pointer_.column < Storage<personality>::mode_description_.start_cycle &&
+					end_column >= Storage<personality>::mode_description_.start_cycle
+				) {
+					Storage<personality>::line_matches_ = false;
 				}
 			}
 
@@ -369,6 +384,13 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 					desc.pixels_per_byte = pixels_per_byte(this->underlying_mode_);
 					desc.width = width(this->underlying_mode_);
 					desc.rotate_address = interleaves_banks(this->underlying_mode_);
+					if(is_text(this->underlying_mode_)) {
+						desc.start_cycle = LineLayout<personality>::TextModeEndOfLeftBorder;
+						desc.end_cycle = LineLayout<personality>::TextModeEndOfPixels;
+					} else {
+						desc.start_cycle = LineLayout<personality>::EndOfLeftBorder;
+						desc.end_cycle = LineLayout<personality>::EndOfPixels;
+					}
 				}
 
 				// Based on the output mode, pick a line mode.
@@ -1256,6 +1278,7 @@ HalfCycles TMS9918<personality>::get_next_sequence_point() const {
 
 	// Calculate when the next line interrupt will occur.
 	int next_line_interrupt_row = -1;
+	int line_interrupt_position = this->mode_timing_.line_interrupt_position;
 
 	int cycles_to_next_interrupt_threshold = this->mode_timing_.line_interrupt_position - this->fetch_pointer_.column;
 	int line_of_next_interrupt_threshold = this->fetch_pointer_.row;
diff --git a/Components/9918/Implementation/AccessEnums.hpp b/Components/9918/Implementation/AccessEnums.hpp
index 531264ebf..ea6b5e1a2 100644
--- a/Components/9918/Implementation/AccessEnums.hpp
+++ b/Components/9918/Implementation/AccessEnums.hpp
@@ -78,6 +78,10 @@ constexpr bool interleaves_banks(ScreenMode mode) {
 	return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
 }
 
+constexpr bool is_text(ScreenMode mode) {
+	return mode == ScreenMode::Text || mode == ScreenMode::YamahaText80;
+}
+
 enum class FetchMode {
 	Text,
 	Character,
diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp
index 4dce4261d..f0a0611b3 100644
--- a/Components/9918/Implementation/Storage.hpp
+++ b/Components/9918/Implementation/Storage.hpp
@@ -402,6 +402,7 @@ template <Personality personality> struct Storage<personality, std::enable_if_t<
 	uint8_t colour_status_ = 0;
 	uint16_t colour_location_ = 0;
 	uint16_t collision_location_[2]{};
+	bool line_matches_ = false;
 
 	Storage() noexcept {
 		// Seed to something valid.
diff --git a/Components/9918/Implementation/YamahaCommands.hpp b/Components/9918/Implementation/YamahaCommands.hpp
index 6254950bf..72cbd08d3 100644
--- a/Components/9918/Implementation/YamahaCommands.hpp
+++ b/Components/9918/Implementation/YamahaCommands.hpp
@@ -89,6 +89,8 @@ struct ModeDescription {
 	int width = 256;
 	int pixels_per_byte = 4;
 	bool rotate_address = false;
+	int start_cycle = 0;
+	int end_cycle = 0;
 };
 
 struct Command {