From 4cb7abe13d49f749b5791add1d91c8e54567622e Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 18 May 2023 16:28:05 -0400
Subject: [PATCH 1/7] Update old comment.

---
 Components/9918/Implementation/Fetch.hpp | 33 +++++-------------------
 1 file changed, 7 insertions(+), 26 deletions(-)

diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp
index efb972617..41ba56cec 100644
--- a/Components/9918/Implementation/Fetch.hpp
+++ b/Components/9918/Implementation/Fetch.hpp
@@ -16,38 +16,19 @@ namespace TI::TMS {
 
 		1)	input is a start position and an end position; they should perform the proper
 			operations for the period: start <= time < end.
-		2)	times are measured relative to a 172-cycles-per-line clock (so: they directly
-			count access windows on the TMS and Master System).
-		3)	within each sequencer, time 0 is the access window that straddles the beginning of
-			horizontal sync. Which, conveniently, is the place to which Grauw's timing diagrams
-			are aligned.
+		2)	times are measured relative to the an appropriate clock — they directly
+			count access windows on the TMS and Master System, and cycles on a Yamaha.
+		3)	within each sequencer, cycle are numbered as per Grauw's timing diagrams. The difference
+			between those and internal timing, if there is one, is handled by the dispatcher.
 		4)	all of these functions are templated with a `use_end` parameter. That will be true if
-			end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
-			for the more usual path of execution.
-
-	[Historically:
-			position 0 was the beginning of the access window immediately after the last pattern/data
-			block fetch that would contribute to this line, in a normal 32-column mode. So:
-
-				* it's cycle 309 on Mattias' TMS diagram;
-				* it's cycle 1238 on his V9938 diagram;
-				* it's after the last background render block in Mask of Destiny's Master System timing diagram.
-
-			That division point was selected, albeit arbitrarily, because it puts all the tile
-			fetches for a single line into the same [0, 171] period.
-
-	I'm moving away from this per the desire not to have V9938 output straddle two lines if horizontally-adjusted,
-	amongst other concerns.]
+			end is < [cycles per line], false otherwise. So functions can use it to eliminate
+			should-exit-now checks (which is likely to be the more usual path of execution).
 
 	Provided for the benefit of the methods below:
 
 		*	the function external_slot(), which will perform any pending VRAM read/write.
-		*	the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
-			switch(start)-based implementation.
 
-	All functions should just spool data to intermediary storage. This is because for most VDPs there is
-	a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
-	for the exceptions.
+	All functions should just spool data to intermediary storage. Fetching and drawing are decoupled.
 */
 
 // MARK: - Address mask helpers.

From c76048bff90b13e639271989ccd8a18eb38697c3 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 18 May 2023 16:37:48 -0400
Subject: [PATCH 2/7] Formalise the idea of Grauw as a separate clock.

---
 .../9918/Implementation/ClockConverter.hpp    | 86 ++++---------------
 Components/9918/Implementation/LineLayout.hpp | 82 ++++++++++++++++++
 .../Clock Signal.xcodeproj/project.pbxproj    |  2 +
 3 files changed, 102 insertions(+), 68 deletions(-)
 create mode 100644 Components/9918/Implementation/LineLayout.hpp

diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp
index d76746aa7..c4e692ce6 100644
--- a/Components/9918/Implementation/ClockConverter.hpp
+++ b/Components/9918/Implementation/ClockConverter.hpp
@@ -11,14 +11,21 @@
 
 #include "../9918.hpp"
 #include "PersonalityTraits.hpp"
+#include "LineLayout.hpp"
 
 namespace TI::TMS {
 
 enum class Clock {
+	/// Whatever rate this VDP runs at, with location 0 being "the start" of the line per internal preference.
 	Internal,
+	/// A 342-cycle/line clock with the same start position as ::Internal.
 	TMSPixel,
+	/// A 171-cycle/line clock that begins at the memory window which starts straight after ::Internal = 0.
 	TMSMemoryWindow,
-	CRT
+	/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
+	CRT,
+	/// Provides the same clock rate as ::Internal but is relocated so that 0 is where Grauw put 0 (i.e. at the start of horizontal sync).
+	Grauw,
 };
 
 template <Personality personality, Clock clk> constexpr int clock_rate() {
@@ -33,6 +40,7 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
 		case Clock::TMSMemoryWindow:	return 171;
 		case Clock::CRT:				return 1368;
 		case Clock::Internal:
+		case Clock::Grauw:
 			if constexpr (is_classic_vdp(personality)) {
 				return 342;
 			} else if constexpr (is_yamaha_vdp(personality)) {
@@ -43,17 +51,25 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
 	}
 }
 
+/// Statelessly converts @c length in @c clock to the internal clock used by VDPs of @c personality throwing away any remainder.
 template <Personality personality, Clock clock> constexpr int to_internal(int length) {
+	if constexpr (clock == Clock::Grauw) {
+		return (length + LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	}
 	return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
 }
 
+/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
 template <Personality personality, Clock clock> constexpr int from_internal(int length) {
+	if constexpr (clock == Clock::Grauw) {
+		return (length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	}
 	return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
 }
 
 /*!
 	Provides a [potentially-]stateful conversion between the external and internal clocks.
-	Unlike the other clock conversions, this one may be non-integral, requiring that
+	Unlike the other clock conversions, this may be non-integral, requiring that
 	an error term be tracked.
 */
 template <Personality personality> class ClockConverter {
@@ -130,72 +146,6 @@ template <Personality personality> class ClockConverter {
 		int cycles_error_ = 0;
 };
 
-
-//
-//
-//
-template <Personality personality, typename Enable = void> struct LineLayout;
-
-//	Line layout is:
-//
-//	[0, EndOfSync]							sync
-//	(EndOfSync, StartOfColourBurst]			blank
-//	(StartOfColourBurst, EndOfColourBurst]	colour burst
-//	(EndOfColourBurst, EndOfLeftErase]		blank
-//	(EndOfLeftErase, EndOfLeftBorder]		border colour
-//	(EndOfLeftBorder, EndOfPixels]			pixel content
-//	(EndOfPixels, EndOfRightBorder]			border colour
-//	[EndOfRightBorder, <end of line>]		blank
-//
-//	... with minor caveats:
-//		* horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
-//		* the Sega VDPs may programatically extend the left border; and
-//		* text mode on all VDPs adjusts border width.
-
-template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
-	constexpr static int EndOfSync			= 26;
-	constexpr static int StartOfColourBurst	= 29;
-	constexpr static int EndOfColourBurst	= 43;
-	constexpr static int EndOfLeftErase		= 50;
-	constexpr static int EndOfLeftBorder	= 63;
-	constexpr static int EndOfPixels		= 319;
-	constexpr static int EndOfRightBorder	= 334;
-
-	constexpr static int CyclesPerLine		= 342;
-
-	constexpr static int TextModeEndOfLeftBorder	= 69;
-	constexpr static int TextModeEndOfPixels		= 309;
-
-	constexpr static int ModeLatchCycle		= 36;	// Just a guess; correlates with the known 144 for the Yamaha VDPs,
-													// and falls into the collection gap between the final sprite
-													// graphics and the initial tiles or pixels.
-
-	/// The number of internal cycles that must elapse between a request to read or write and
-	/// it becoming a candidate for action.
-	constexpr static int VRAMAccessDelay = 6;
-};
-
-template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
-	constexpr static int EndOfSync			= 100;
-	constexpr static int StartOfColourBurst	= 113;
-	constexpr static int EndOfColourBurst	= 167;
-	constexpr static int EndOfLeftErase		= 202;
-	constexpr static int EndOfLeftBorder	= 258;
-	constexpr static int EndOfPixels		= 1282;
-	constexpr static int EndOfRightBorder	= 1341;
-
-	constexpr static int CyclesPerLine		= 1368;
-
-	constexpr static int TextModeEndOfLeftBorder	= 294;
-	constexpr static int TextModeEndOfPixels		= 1254;
-
-	constexpr static int ModeLatchCycle		= 144;
-
-	/// The number of internal cycles that must elapse between a request to read or write and
-	/// it becoming a candidate for action.
-	constexpr static int VRAMAccessDelay = 16;
-};
-
 }
 
 #endif /* ClockConverter_hpp */
diff --git a/Components/9918/Implementation/LineLayout.hpp b/Components/9918/Implementation/LineLayout.hpp
new file mode 100644
index 000000000..c8509b505
--- /dev/null
+++ b/Components/9918/Implementation/LineLayout.hpp
@@ -0,0 +1,82 @@
+//
+//  LineLayout.hpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 18/05/2023.
+//  Copyright © 2023 Thomas Harte. All rights reserved.
+//
+
+#ifndef LineLayout_h
+#define LineLayout_h
+
+namespace TI::TMS {
+
+template <Personality personality, typename Enable = void> struct LineLayout;
+
+//	Line layout is:
+//
+//	[0, EndOfSync]							sync
+//	(EndOfSync, StartOfColourBurst]			blank
+//	(StartOfColourBurst, EndOfColourBurst]	colour burst
+//	(EndOfColourBurst, EndOfLeftErase]		blank
+//	(EndOfLeftErase, EndOfLeftBorder]		border colour
+//	(EndOfLeftBorder, EndOfPixels]			pixel content
+//	(EndOfPixels, EndOfRightBorder]			border colour
+//	[EndOfRightBorder, <end of line>]		blank
+//
+//	... with minor caveats:
+//		* horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
+//		* the Sega VDPs may programatically extend the left border; and
+//		* text mode on all VDPs adjusts border width.
+
+template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
+	constexpr static int EndOfSync			= 26;
+	constexpr static int StartOfColourBurst	= 29;
+	constexpr static int EndOfColourBurst	= 43;
+	constexpr static int EndOfLeftErase		= 50;
+	constexpr static int EndOfLeftBorder	= 63;
+	constexpr static int EndOfPixels		= 319;
+	constexpr static int EndOfRightBorder	= 334;
+
+	constexpr static int CyclesPerLine		= 342;
+
+	constexpr static int TextModeEndOfLeftBorder	= 69;
+	constexpr static int TextModeEndOfPixels		= 309;
+
+	constexpr static int ModeLatchCycle		= 36;	// Just a guess; correlates with the known 144 for the Yamaha VDPs,
+													// and falls into the collection gap between the final sprite
+													// graphics and the initial tiles or pixels.
+
+	constexpr static int LocationOfGrauwZero	= 0;
+
+	/// The number of internal cycles that must elapse between a request to read or write and
+	/// it becoming a candidate for action.
+	constexpr static int VRAMAccessDelay = 6;
+};
+
+template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
+	constexpr static int EndOfSync			= 100;
+	constexpr static int StartOfColourBurst	= 113;
+	constexpr static int EndOfColourBurst	= 167;
+	constexpr static int EndOfLeftErase		= 202;
+	constexpr static int EndOfLeftBorder	= 258;
+	constexpr static int EndOfPixels		= 1282;
+	constexpr static int EndOfRightBorder	= 1341;
+
+	constexpr static int CyclesPerLine		= 1368;
+
+	constexpr static int TextModeEndOfLeftBorder	= 294;
+	constexpr static int TextModeEndOfPixels		= 1254;
+
+	constexpr static int ModeLatchCycle		= 144;
+
+	constexpr static int LocationOfGrauwZero	= 0;
+
+	/// The number of internal cycles that must elapse between a request to read or write and
+	/// it becoming a candidate for action.
+	constexpr static int VRAMAccessDelay = 16;
+};
+
+}
+
+#endif /* LineLayout_h */
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index 3e16aa8a5..75e1fd830 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -1100,6 +1100,7 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
 		42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
 		42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
 		42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
@@ -4738,6 +4739,7 @@
 				4B43983F2967459B006B0BFC /* Draw.hpp */,
 				4B43983E29628538006B0BFC /* Fetch.hpp */,
 				4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */,
+				428168372A16C25C008ECD27 /* LineLayout.hpp */,
 				4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */,
 				4B2A3B5A29993DFA007CE366 /* Storage.hpp */,
 				4BF0BC732982E54700CCA2B5 /* YamahaCommands.hpp */,

From ce8bd011d702dbce6bbcf111cfe02d84ba317630 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 18 May 2023 16:50:46 -0400
Subject: [PATCH 3/7] Add commentary, and TODOs.

---
 Components/9918/Implementation/9918.cpp | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp
index 34af6a73b..7011e3681 100644
--- a/Components/9918/Implementation/9918.cpp
+++ b/Components/9918/Implementation/9918.cpp
@@ -35,12 +35,16 @@ Base<personality>::Base() :
 
 		// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
 		// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
+		//
+		// i.e. it's 304 internal clocks after the end of the left border.
 		mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % LineLayout<personality>::CyclesPerLine;
 
 		// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
 		// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
+		//
+		// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
 		mode_timing_.end_of_frame_interrupt_position.column = mode_timing_.line_interrupt_position - 1;
-		mode_timing_.end_of_frame_interrupt_position.row = 193;
+		mode_timing_.end_of_frame_interrupt_position.row = 192 + (LineLayout<personality>::EndOfLeftBorder + 304) / LineLayout<personality>::CyclesPerLine;
 	}
 
 	if constexpr (is_yamaha_vdp(personality)) {
@@ -81,6 +85,7 @@ TMS9918<personality>::TMS9918() {
 
 template <Personality personality>
 void TMS9918<personality>::set_tv_standard(TVStandard standard) {
+	// TODO: the Yamaha is programmable on this at runtime.
 	this->tv_standard_ = standard;
 	switch(standard) {
 		case TVStandard::PAL:
@@ -214,8 +219,10 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 			// ---------------------------------------
 			// Latch scrolling position, if necessary.
 			// ---------------------------------------
+			// TODO: shouldn't this happen one per frame?
 			if constexpr (is_sega_vdp(personality)) {
-				if(this->fetch_pointer_.column < 61 && end_column >= 61) {
+				constexpr auto latch_time = to_internal<personality, Clock::Grauw>(61);	// TODO: where did this magic constant come from? Is it the same for the Game Gear, etc?
+				if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
 					if(!this->fetch_pointer_.row) {
 						Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
 

From dc425a03d3fa1c0ba4ab36c0ae27b63df1f735e5 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 18 May 2023 16:55:17 -0400
Subject: [PATCH 4/7] Partially resolve.

---
 Components/9918/Implementation/9918.cpp | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/Components/9918/Implementation/9918.cpp b/Components/9918/Implementation/9918.cpp
index 7011e3681..b8a589e35 100644
--- a/Components/9918/Implementation/9918.cpp
+++ b/Components/9918/Implementation/9918.cpp
@@ -219,11 +219,14 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 			// ---------------------------------------
 			// Latch scrolling position, if necessary.
 			// ---------------------------------------
-			// TODO: shouldn't this happen one per frame?
 			if constexpr (is_sega_vdp(personality)) {
-				constexpr auto latch_time = to_internal<personality, Clock::Grauw>(61);	// TODO: where did this magic constant come from? Is it the same for the Game Gear, etc?
-				if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
-					if(!this->fetch_pointer_.row) {
+				if(!this->fetch_pointer_.row) {
+					// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
+					// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
+					// not uncompelling. I can just no longer find my source.
+					constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
+					static_assert(latch_time > 0);
+					if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
 						Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
 
 						if(Storage<personality>::mode4_enable_) {
@@ -242,7 +245,6 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
 			}
 
 
-
 			// ------------------------
 			// Perform memory accesses.
 			// ------------------------

From d117a44069b603cbc57b6406081b0153e0f86fc6 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Fri, 19 May 2023 11:46:49 -0400
Subject: [PATCH 5/7] Allow for potential Grauw offset in TMS and SMS.

---
 .../9918/Implementation/ClockConverter.hpp    | 32 ++++++++++++-----
 Components/9918/Implementation/Fetch.hpp      | 36 +++++++++++--------
 2 files changed, 44 insertions(+), 24 deletions(-)

diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp
index c4e692ce6..9d73ccefc 100644
--- a/Components/9918/Implementation/ClockConverter.hpp
+++ b/Components/9918/Implementation/ClockConverter.hpp
@@ -51,20 +51,34 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
 	}
 }
 
-/// Statelessly converts @c length in @c clock to the internal clock used by VDPs of @c personality throwing away any remainder.
-template <Personality personality, Clock clock> constexpr int to_internal(int length) {
-	if constexpr (clock == Clock::Grauw) {
-		return (length + LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+/// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order.
+template <Personality personality, Clock head, Clock... tail> constexpr int to_internal(int length) {
+	if constexpr (head == Clock::Grauw) {
+		length = (length + LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	} else {
+		length = length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, head>();
+	}
+
+	if constexpr (!sizeof...(tail)) {
+		return length;
+	} else {
+		return to_internal<personality, tail...>(length);
 	}
-	return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
 }
 
 /// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
-template <Personality personality, Clock clock> constexpr int from_internal(int length) {
-	if constexpr (clock == Clock::Grauw) {
-		return (length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+template <Personality personality, Clock head, Clock... tail> constexpr int from_internal(int length) {
+	if constexpr (head == Clock::Grauw) {
+		length = (length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	} else {
+		length = length * clock_rate<personality, head>() / clock_rate<personality, Clock::Internal>();
+	}
+
+	if constexpr (!sizeof...(tail)) {
+		return length;
+	} else {
+		return to_internal<personality, tail...>(length);
 	}
-	return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
 }
 
 /*!
diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp
index 41ba56cec..98d2b9320 100644
--- a/Components/9918/Implementation/Fetch.hpp
+++ b/Components/9918/Implementation/Fetch.hpp
@@ -54,7 +54,7 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
 #define index(n)						\
 	if(use_end && end == n) return;		\
 	[[fallthrough]];					\
-	case n: fetcher.template fetch<n>();
+	case n: fetcher.template fetch<from_internal<personality, Clock::Grauw>(n)>();
 
 	switch(start) {
 		default: assert(false);
@@ -373,7 +373,7 @@ struct RefreshSequencer {
 
 	template <int cycle> void fetch() {
 		if(cycle < 26 || (cycle & 1) || cycle >= 154) {
-			base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 	}
 
@@ -387,16 +387,22 @@ struct TextSequencer {
 	template <int cycle> void fetch() {
 		// The first 30 and the final 4 slots are external.
 		if constexpr (cycle < 30 || cycle >= 150) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 			return;
 		} else {
 			// For the 120 slots in between follow a three-step pattern of:
 			constexpr int offset = cycle - 30;
 			constexpr auto column = AddressT(offset / 3);
 			switch(offset % 3) {
-				case 0:	fetcher.fetch_name(column);																break;	// (1) fetch tile name.
-				case 1:	fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));	break;	// (2) external slot.
-				case 2:	fetcher.fetch_pattern(column);															break;	// (3) fetch tile pattern.
+				case 0:		// (1) fetch tile name.
+					fetcher.fetch_name(column);
+				break;
+				case 1:		// (2) external slot.
+					fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+				break;
+				case 2:		// (3) fetch tile pattern.
+					fetcher.fetch_pattern(column);
+				break;
 			}
 		}
 	}
@@ -413,7 +419,7 @@ struct CharacterSequencer {
 
 	template <int cycle> void fetch() {
 		if(cycle < 5) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		if(cycle == 5) {
@@ -424,7 +430,7 @@ struct CharacterSequencer {
 		}
 
 		if(cycle > 14 && cycle < 19) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
@@ -442,7 +448,7 @@ struct CharacterSequencer {
 				case 0:	character_fetcher.fetch_name(block);	break;
 				case 1:
 					if(!(block & 3)) {
-						character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+						character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 					} else {
 						constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
 						sprite_fetcher.fetch_y(sprite);
@@ -457,7 +463,7 @@ struct CharacterSequencer {
 		}
 
 		if(cycle >= 155 && cycle < 157) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		if(cycle == 157) {
@@ -505,7 +511,7 @@ struct SMSSequencer {
 	// window 0 to HSYNC low.
 	template <int cycle> void fetch() {
 		if(cycle < 3) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		if(cycle == 3) {
@@ -516,7 +522,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle == 15 || cycle == 16) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		if(cycle == 17) {
@@ -537,7 +543,7 @@ struct SMSSequencer {
 				case 0:	fetcher.fetch_tile_name(block);		break;
 				case 1:
 					if(!(block & 3)) {
-						fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+						fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 					} else {
 						constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
 						fetcher.posit_sprite(sprite);
@@ -549,7 +555,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle >= 153 && cycle < 157) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 
 		if(cycle == 157) {
@@ -560,7 +566,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle >= 169) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
 		}
 	}
 

From c75efb7dac72ff1d105f9460ef3e822d372dcdf3 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Fri, 19 May 2023 13:43:28 -0400
Subject: [PATCH 6/7] Also allow for a potential Grauw conversion in Yamaha
 land.

---
 Components/9918/Implementation/Storage.hpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp
index f0a0611b3..70a4bcd92 100644
--- a/Components/9918/Implementation/Storage.hpp
+++ b/Components/9918/Implementation/Storage.hpp
@@ -86,7 +86,8 @@ struct YamahaFetcher {
 			std::array<Event, size> result{};
 			size_t index = 0;
 			for(int c = 0; c < 1368; c++) {
-				const auto event = GeneratorT::event(c);
+				// Specific personality doesn't matter here; both Yamahas use the same internal timing.
+				const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::Grauw>(c));
 				if(!event) {
 					continue;
 				}

From 40d5bd4e58a02e5157bf727f5d30cd98c1792a88 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Fri, 19 May 2023 14:22:22 -0400
Subject: [PATCH 7/7] Switch to purposive name.

---
 .../9918/Implementation/ClockConverter.hpp    | 17 +++++++-----
 Components/9918/Implementation/Fetch.hpp      | 26 +++++++++----------
 Components/9918/Implementation/LineLayout.hpp |  6 ++---
 Components/9918/Implementation/Storage.hpp    |  4 +--
 4 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/Components/9918/Implementation/ClockConverter.hpp b/Components/9918/Implementation/ClockConverter.hpp
index 9d73ccefc..2aa9d840f 100644
--- a/Components/9918/Implementation/ClockConverter.hpp
+++ b/Components/9918/Implementation/ClockConverter.hpp
@@ -24,8 +24,9 @@ enum class Clock {
 	TMSMemoryWindow,
 	/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
 	CRT,
-	/// Provides the same clock rate as ::Internal but is relocated so that 0 is where Grauw put 0 (i.e. at the start of horizontal sync).
-	Grauw,
+	/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
+	/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
+	FromStartOfSync,
 };
 
 template <Personality personality, Clock clk> constexpr int clock_rate() {
@@ -40,7 +41,7 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
 		case Clock::TMSMemoryWindow:	return 171;
 		case Clock::CRT:				return 1368;
 		case Clock::Internal:
-		case Clock::Grauw:
+		case Clock::FromStartOfSync:
 			if constexpr (is_classic_vdp(personality)) {
 				return 342;
 			} else if constexpr (is_yamaha_vdp(personality)) {
@@ -53,8 +54,8 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
 
 /// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order.
 template <Personality personality, Clock head, Clock... tail> constexpr int to_internal(int length) {
-	if constexpr (head == Clock::Grauw) {
-		length = (length + LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	if constexpr (head == Clock::FromStartOfSync) {
+		length = (length + LineLayout<personality>::StartOfSync) % LineLayout<personality>::CyclesPerLine;
 	} else {
 		length = length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, head>();
 	}
@@ -68,8 +69,10 @@ template <Personality personality, Clock head, Clock... tail> constexpr int to_i
 
 /// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
 template <Personality personality, Clock head, Clock... tail> constexpr int from_internal(int length) {
-	if constexpr (head == Clock::Grauw) {
-		length = (length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::LocationOfGrauwZero) % LineLayout<personality>::CyclesPerLine;
+	if constexpr (head == Clock::FromStartOfSync) {
+		length =
+			(length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::StartOfSync)
+				% LineLayout<personality>::CyclesPerLine;
 	} else {
 		length = length * clock_rate<personality, head>() / clock_rate<personality, Clock::Internal>();
 	}
diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp
index 98d2b9320..045770536 100644
--- a/Components/9918/Implementation/Fetch.hpp
+++ b/Components/9918/Implementation/Fetch.hpp
@@ -54,7 +54,7 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
 #define index(n)						\
 	if(use_end && end == n) return;		\
 	[[fallthrough]];					\
-	case n: fetcher.template fetch<from_internal<personality, Clock::Grauw>(n)>();
+	case n: fetcher.template fetch<from_internal<personality, Clock::FromStartOfSync>(n)>();
 
 	switch(start) {
 		default: assert(false);
@@ -373,7 +373,7 @@ struct RefreshSequencer {
 
 	template <int cycle> void fetch() {
 		if(cycle < 26 || (cycle & 1) || cycle >= 154) {
-			base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 	}
 
@@ -387,7 +387,7 @@ struct TextSequencer {
 	template <int cycle> void fetch() {
 		// The first 30 and the final 4 slots are external.
 		if constexpr (cycle < 30 || cycle >= 150) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 			return;
 		} else {
 			// For the 120 slots in between follow a three-step pattern of:
@@ -398,7 +398,7 @@ struct TextSequencer {
 					fetcher.fetch_name(column);
 				break;
 				case 1:		// (2) external slot.
-					fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+					fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 				break;
 				case 2:		// (3) fetch tile pattern.
 					fetcher.fetch_pattern(column);
@@ -419,7 +419,7 @@ struct CharacterSequencer {
 
 	template <int cycle> void fetch() {
 		if(cycle < 5) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		if(cycle == 5) {
@@ -430,7 +430,7 @@ struct CharacterSequencer {
 		}
 
 		if(cycle > 14 && cycle < 19) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
@@ -448,7 +448,7 @@ struct CharacterSequencer {
 				case 0:	character_fetcher.fetch_name(block);	break;
 				case 1:
 					if(!(block & 3)) {
-						character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+						character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 					} else {
 						constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
 						sprite_fetcher.fetch_y(sprite);
@@ -463,7 +463,7 @@ struct CharacterSequencer {
 		}
 
 		if(cycle >= 155 && cycle < 157) {
-			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		if(cycle == 157) {
@@ -511,7 +511,7 @@ struct SMSSequencer {
 	// window 0 to HSYNC low.
 	template <int cycle> void fetch() {
 		if(cycle < 3) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		if(cycle == 3) {
@@ -522,7 +522,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle == 15 || cycle == 16) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		if(cycle == 17) {
@@ -543,7 +543,7 @@ struct SMSSequencer {
 				case 0:	fetcher.fetch_tile_name(block);		break;
 				case 1:
 					if(!(block & 3)) {
-						fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+						fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 					} else {
 						constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
 						fetcher.posit_sprite(sprite);
@@ -555,7 +555,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle >= 153 && cycle < 157) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 
 		if(cycle == 157) {
@@ -566,7 +566,7 @@ struct SMSSequencer {
 		}
 
 		if(cycle >= 169) {
-			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::Grauw>(cycle));
+			fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
 		}
 	}
 
diff --git a/Components/9918/Implementation/LineLayout.hpp b/Components/9918/Implementation/LineLayout.hpp
index c8509b505..460ee9d70 100644
--- a/Components/9918/Implementation/LineLayout.hpp
+++ b/Components/9918/Implementation/LineLayout.hpp
@@ -30,6 +30,7 @@ template <Personality personality, typename Enable = void> struct LineLayout;
 //		* text mode on all VDPs adjusts border width.
 
 template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
+	constexpr static int StartOfSync		= 0;
 	constexpr static int EndOfSync			= 26;
 	constexpr static int StartOfColourBurst	= 29;
 	constexpr static int EndOfColourBurst	= 43;
@@ -47,14 +48,13 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
 													// and falls into the collection gap between the final sprite
 													// graphics and the initial tiles or pixels.
 
-	constexpr static int LocationOfGrauwZero	= 0;
-
 	/// The number of internal cycles that must elapse between a request to read or write and
 	/// it becoming a candidate for action.
 	constexpr static int VRAMAccessDelay = 6;
 };
 
 template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
+	constexpr static int StartOfSync		= 0;
 	constexpr static int EndOfSync			= 100;
 	constexpr static int StartOfColourBurst	= 113;
 	constexpr static int EndOfColourBurst	= 167;
@@ -70,8 +70,6 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
 
 	constexpr static int ModeLatchCycle		= 144;
 
-	constexpr static int LocationOfGrauwZero	= 0;
-
 	/// The number of internal cycles that must elapse between a request to read or write and
 	/// it becoming a candidate for action.
 	constexpr static int VRAMAccessDelay = 16;
diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp
index 70a4bcd92..bb3820720 100644
--- a/Components/9918/Implementation/Storage.hpp
+++ b/Components/9918/Implementation/Storage.hpp
@@ -85,9 +85,9 @@ struct YamahaFetcher {
 		static constexpr std::array<Event, size> events() {
 			std::array<Event, size> result{};
 			size_t index = 0;
-			for(int c = 0; c < 1368; c++) {
+			for(int c = 0; c < 1368; c++) { 
 				// Specific personality doesn't matter here; both Yamahas use the same internal timing.
-				const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::Grauw>(c));
+				const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
 				if(!event) {
 					continue;
 				}